UltraGrid
UltraGrid Low-Latency High-Resolution Transmission System
Loading...
Searching...
No Matches
drm.cpp
Go to the documentation of this file.
1
5/*
6 * Copyright (c) 2024 CESNET z.s.p.o.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, is permitted provided that the following conditions
10 * are met:
11 *
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 *
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 *
22 * This product includes software developed by the University of Southern
23 * California Information Sciences Institute. This product also includes
24 * software developed by CESNET z.s.p.o.
25 *
26 * 4. Neither the name of the University, Institute, CESNET nor the names of
27 * its contributors may be used to endorse or promote products derived from
28 * this software without specific prior written permission.
29 *
30 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS
31 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
32 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
33 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
34 * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
35 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
36 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
37 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
38 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
39 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
40 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
41 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42 */
43
44#include "config.h"
45#include "config_unix.h"
46#include "config_win32.h"
47
48#include <assert.h>
49#include <stdlib.h>
50#include <string.h>
51
52#include <unistd.h>
53#include <xf86drm.h>
54#include <xf86drmMode.h>
55#include <drm_fourcc.h>
56
57#include <memory>
58#include <vector>
59#include <algorithm>
60
61#include "debug.h"
62#include "host.h"
63#include "lib_common.h"
64#include "pixfmt_conv.h"
65#include "utils/color_out.h"
66#include "utils/macros.h"
67#include "utils/misc.h"
68#include "utils/text.h"
70#include "video.h"
71#include "video_codec.h"
72#include "video_display.h"
73#include "hwaccel_drm.h"
74
75#define MOD_NAME "[drm] "
76
77namespace{
78 struct frame_deleter{ void operator()(video_frame *f){ vf_free(f); } };
79 using frame_uniq = std::unique_ptr<video_frame, frame_deleter>;
80
81 struct drm_connector_deleter{ void operator()(drmModeConnectorPtr c) { drmModeFreeConnector(c); } };
82 using Drm_connector_uniq = std::unique_ptr<drmModeConnector, drm_connector_deleter>;
83
84 struct drm_resources_deleter{ void operator()(drmModeResPtr r) { drmModeFreeResources(r); } };
85 using Drm_res_uniq = std::unique_ptr<drmModeRes, drm_resources_deleter>;
86
87 struct drm_encoder_deleter{ void operator()(drmModeEncoderPtr e) { drmModeFreeEncoder(e); } };
88 using Drm_encoder_uniq = std::unique_ptr<drmModeEncoder, drm_encoder_deleter>;
89
90 struct drm_crtc_deleter{ void operator()(drmModeCrtcPtr c) { drmModeFreeCrtc(c); } };
91 using Drm_crtc_uniq = std::unique_ptr<drmModeCrtc, drm_crtc_deleter>;
92
93 struct drm_plane_res_deleter{ void operator()(drmModePlaneResPtr r) { drmModeFreePlaneResources(r); } };
94 using Drm_plane_res_uniq = std::unique_ptr<drmModePlaneRes, drm_plane_res_deleter>;
95
96 struct drm_plane_deleter{ void operator()(drmModePlanePtr p) { drmModeFreePlane(p); } };
97 using Drm_plane_uniq = std::unique_ptr<drmModePlane, drm_plane_deleter>;
98
99 struct drm_obj_props_deleter{ void operator()(drmModeObjectPropertiesPtr p) { drmModeFreeObjectProperties(p); } };
100 using Drm_object_properties_uniq = std::unique_ptr<drmModeObjectProperties, drm_obj_props_deleter>;
101
102 struct drm_prop_deleter{ void operator()(drmModePropertyPtr p) { drmModeFreeProperty(p); } };
103 using Drm_property_uniq = std::unique_ptr<drmModePropertyRes, drm_prop_deleter>;
104
105 class Fd_uniq{
106 public:
107 Fd_uniq() = default;
108 Fd_uniq(int fd) : fd(fd) { }
109 Fd_uniq(const Fd_uniq&) = delete;
110 Fd_uniq(Fd_uniq&& o){
111 std::swap(fd, o.fd);
112 }
113
114 ~Fd_uniq(){
115 destruct();
116 }
117
118 Fd_uniq& operator=(const Fd_uniq&) = delete;
119 Fd_uniq& operator=(Fd_uniq&& o){
120 std::swap(fd, o.fd);
121 return *this;
122 }
123
124 operator bool() const { return fd > 0; }
125
126 int get() { return fd; }
127 void reset(int fd){
128 destruct();
129 this->fd = fd;
130 }
131 private:
132 void destruct(){
133 if(fd > 0)
134 close(fd);
135
136 fd = -1;
137 }
138 int fd = -1;
139 };
140
141 class MemoryMapping{
142 public:
143 MemoryMapping() = default;
144 ~MemoryMapping() {
145 if(!valid())
146 return;
147
148 munmap(ptr, mapped_size);
149 }
150
151 MemoryMapping(const MemoryMapping&) = delete;
152 MemoryMapping& operator=(const MemoryMapping&) = delete;
153
154 MemoryMapping(MemoryMapping&& o) noexcept{
155 std::swap(ptr, o.ptr);
156 std::swap(mapped_size, o.mapped_size);
157 }
158
159 MemoryMapping& operator=(MemoryMapping&& o) noexcept{
160 std::swap(ptr, o.ptr);
161 std::swap(mapped_size, o.mapped_size);
162
163 return *this;
164 }
165
166 static MemoryMapping create(void *addr, size_t len, int prot, int flags, int fildes, off_t off){
167 MemoryMapping res;
168 res.ptr = mmap(addr, len, prot, flags, fildes, off);
169 if(res.ptr != MAP_FAILED)
170 res.mapped_size = len;
171
172 return res;
173 };
174
175 bool valid() const { return ptr != MAP_FAILED; }
176 void *get() const { return ptr; }
177 size_t size() const { return mapped_size; }
178 private:
179 void *ptr = MAP_FAILED;
180 size_t mapped_size = 0;
181 };
182
183 template<typename T, class Deleter>
184 class Uniq_wrapper{
185 public:
186 Uniq_wrapper() = default;
187 Uniq_wrapper(T val): val(val) { }
188 ~Uniq_wrapper(){
189 Deleter()(val);
190 }
191
192 Uniq_wrapper(const Uniq_wrapper&) = delete;
193 Uniq_wrapper& operator=(const Uniq_wrapper&) = delete;
194
195 Uniq_wrapper(Uniq_wrapper&& o){
196 std::swap(val, o.val);
197 }
198
199 Uniq_wrapper& operator=(Uniq_wrapper&& o){
200 std::swap(val, o.val);
201 return *this;
202 }
203
204 T& get() { return val; }
205 private:
206 T val = {};
207
208 };
209
210 struct Fb_handle{
211 int dri_fd = -1;
212 uint32_t handle;
213 };
214
215 struct Fb_handle_deleter {
216 void operator()(Fb_handle h){
217 if(h.dri_fd < 0)
218 return;
219
220 drm_mode_destroy_dumb destroy_info = {};
221 destroy_info.handle = h.handle;
222 drmIoctl(h.dri_fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_info);
223 }
224 };
225 using Fb_handle_uniq = Uniq_wrapper<Fb_handle, Fb_handle_deleter>;
226
227 struct Fb_id{
228 int dri_fd = -1;
229 uint32_t id;
230 };
231
232 struct Fb_id_deleter {
233 void operator()(Fb_id i){
234 if(i.dri_fd < 0)
235 return;
236
237 drmModeRmFB(i.dri_fd, i.id);
238 }
239 };
240 using Fb_id_uniq = Uniq_wrapper<Fb_id, Fb_id_deleter>;
241
242 class Gem_handle_manager{
243 /* The handles returned from drmPrimeFDToHandle() must not be
244 * double free'd. Two frames can apparently also point to the
245 * same prime buffer, so we really need to reference count it
246 * ourselves.
247 */
248 private:
249 void add_ref(uint32_t handle){
250 handle_map[handle]++;
251 };
252
253 void unref(uint32_t handle){
254 int refs = handle_map[handle]--;
255 assert(refs >= 0 && "Unref called on invalid handle");
256
257 if(refs == 0){
258 drmIoctl(dri_fd, DRM_IOCTL_GEM_CLOSE, &handle);
259 handle_map.erase(handle);
260 }
261 }
262 int dri_fd = -1;
263 std::map<uint32_t, int> handle_map;
264
265 public:
266 Gem_handle_manager(int dri_fd): dri_fd(dri_fd) { }
267
268 class Handle{
269 public:
270 Handle() = default;
271 ~Handle(){
272 if(ctx)
273 ctx->unref(handle);
274 }
275
276 Handle(const Handle&) = delete;
277 Handle& operator=(const Handle&) = delete;
278 Handle(Handle&& o){
279 std::swap(handle, o.handle);
280 std::swap(ctx, o.ctx);
281 }
282 Handle& operator=(Handle&& o){
283 std::swap(handle, o.handle);
284 std::swap(ctx, o.ctx);
285 return *this;
286 }
287
288 uint32_t get() const {
289 return handle;
290 }
291 private:
292 friend Gem_handle_manager;
293 Handle(uint32_t handle, Gem_handle_manager *ctx): handle(handle), ctx(ctx){
294 ctx->add_ref(handle);
295 }
296 uint32_t handle = 0;
297 Gem_handle_manager *ctx = nullptr;
298 };
299
300 Handle get_handle(int fd){
301 uint32_t handle = 0;
302 int res = drmPrimeFDToHandle(dri_fd, fd, &handle);
303 if(res < 0){
304 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Failed to get a GEM handle from prime fd %d\n", fd);
305 return {};
306 }
307 return Handle(handle, this);
308 }
309
310 };
311
312} //anon namespace
313 //
314
315struct Drm_state {
316 Fd_uniq dri_fd;
317
318 std::unique_ptr<Gem_handle_manager> gem_manager;
319
320 Drm_res_uniq res;
321 Drm_connector_uniq connector;
322 Drm_encoder_uniq encoder;
323 Drm_crtc_uniq crtc;
324 int crtc_index = -1;
325
326 std::set<uint32_t> supported_drm_formats;
327 bool prime_support = false;
328
329 drmModeModeInfoPtr mode_info;
330};
331
333 uint32_t width = 0;
334 uint32_t height = 0;
335 uint32_t pitch = 0;
336 size_t size = 0;
337
338 Fb_handle_uniq handle;
339
340 uint32_t pix_fmt = 0;
341
342 Fb_id_uniq id;
343 MemoryMapping map;
344};
345
347 frame_uniq frame;
348 Gem_handle_manager::Handle gem_objects[4] = {};
349 Fb_id_uniq id;
350};
351
374
375static std::string get_connector_str(int type, uint32_t id){
376 std::string res;
377 switch(type){
378 case DRM_MODE_CONNECTOR_VGA: res = "VGA"; break;
379 case DRM_MODE_CONNECTOR_HDMIA: res = "HDMI-A"; break;
380 case DRM_MODE_CONNECTOR_HDMIB: res = "HDMI-B"; break;
381 case DRM_MODE_CONNECTOR_DisplayPort: res = "DP"; break;
382 case DRM_MODE_CONNECTOR_eDP: res = "eDP"; break;
383 case DRM_MODE_CONNECTOR_DVII: res = "DVI-I"; break;
384 case DRM_MODE_CONNECTOR_DVID: res = "DVI-D"; break;
385 case DRM_MODE_CONNECTOR_DVIA: res = "DVI-A"; break;
386#ifdef DRM_MODE_CONNECTOR_USB
387 case DRM_MODE_CONNECTOR_USB: res = "USB"; break;
388#endif
389 default:
390 res = std::to_string(type);
391 }
392
393 res += "-";
394 res += std::to_string(id);
395
396 return res;
397}
398
400 drmVersionPtr version = drmGetVersion(s->drm.dri_fd.get());
401 if(!version){
402 log_msg(LOG_LEVEL_WARNING, MOD_NAME "Failed to get DRM driver version\n");
403 return;
404 }
405
406 log_msg(LOG_LEVEL_INFO, MOD_NAME "DRM version: %d.%d.%d (%s), Driver: %s\n",
407 version->version_major,
408 version->version_minor,
409 version->version_patchlevel,
410 version->date,
411 version->name);
412 drmFreeVersion(version);
413}
414
415static Fd_uniq open_dri(drm_display_state *s){
416
417 auto do_open = [](const char *path) -> Fd_uniq {
418 int fd = open(path, O_RDWR);
419 if(fd < 0){
420 if(errno != ENOENT)
421 log_msg(LOG_LEVEL_INFO, MOD_NAME "Failed to open %s (%s)\n", path, strerror(errno));
422
423 return {};
424 }
425 Fd_uniq ret(fd);
426
427 Drm_res_uniq resources(drmModeGetResources(fd));
428 if(!resources){
429 log_msg(LOG_LEVEL_INFO, MOD_NAME "Failed to get resources on %s (%s)\n", path, strerror(errno));
430 return {};
431 }
432
433 uint64_t dumb_support = false;
434 int res = 0;
435 res = drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &dumb_support);
436 if(res < 0 || !dumb_support){
437 log_msg(LOG_LEVEL_WARNING, MOD_NAME "%s does not support dumb buffers\n", path);
438 return {};
439 }
440
441 log_msg(LOG_LEVEL_INFO, MOD_NAME "Opened %s DRI device\n", path);
442 return ret;
443 };
444
445 if(!s->device_path.empty()){
446 auto ret = do_open(s->device_path.c_str());
447 if(!ret)
448 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Failed to open specified device (%s)\n", s->device_path.c_str());
449
450 return ret;
451 }
452
453 char buf[256] = {};
454 const int max_index = 32;
455 for(int i = 0; i < max_index; i++){
456 snprintf(buf, sizeof(buf), DRM_DEV_NAME, DRM_DIR_NAME, i);
457 auto ret = do_open(buf);
458 if(ret)
459 return ret;
460 }
461 log_msg(LOG_LEVEL_ERROR, MOD_NAME "No suitable DRI device found\n");
462 return {};
463}
464
465static int64_t get_property(int dri, drmModeObjectPropertiesPtr props, std::string_view name){
466 for(unsigned i = 0; i < props->count_props; i++){
467 Drm_property_uniq prop(drmModeGetProperty(dri, props->props[i]));
468 if(!prop)
469 continue;
470
471 if(prop->name == name)
472 return props->prop_values[i];
473 }
474 return -1;
475}
476
478 int dri = s->drm.dri_fd.get();
479 Drm_plane_res_uniq plane_res(drmModeGetPlaneResources(dri));
480 if(!plane_res){
481 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Failed to get plane resources (%s)\n", strerror(errno));
482 return false;
483 }
484
485 Drm_plane_uniq primary_plane;
486 for(unsigned i = 0; i < plane_res->count_planes; i++){
487 Drm_plane_uniq plane(drmModeGetPlane(dri, plane_res->planes[i]));
488 if(!(plane->possible_crtcs & (1 << s->drm.crtc_index)))
489 continue;
490
491 Drm_object_properties_uniq props(drmModeObjectGetProperties(dri, plane_res->planes[i], DRM_MODE_OBJECT_PLANE));
492 if(!props){
493 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Failed to get plane props (%s)\n", strerror(errno));
494 continue;
495 }
496 if(get_property(dri, props.get(), "type") == DRM_PLANE_TYPE_PRIMARY){
497 primary_plane = std::move(plane);
498 break;
499 }
500 }
501
502 if(!primary_plane){
503 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Failed to find the primary plane\n");
504 return false;
505 }
506
507 for(unsigned i = 0; i < primary_plane->count_formats; i++){
508 s->drm.supported_drm_formats.insert(primary_plane->formats[i]);
509 }
510
511 return true;
512}
513
514static std::vector<Drm_connector_uniq> get_connectors(drm_display_state *s){
515 std::vector<Drm_connector_uniq> res;
516
517 for(int i = 0; i < s->drm.res->count_connectors; i++){
518 Drm_connector_uniq conn(drmModeGetConnector(s->drm.dri_fd.get(), s->drm.res->connectors[i]));
519 if(!conn)
520 continue;
521
522 res.push_back(std::move(conn));
523 }
524
525 return res;
526}
527
529 s->drm.dri_fd = open_dri(s);
530
531 int dri = s->drm.dri_fd.get();
532 if(dri < 0){
533 return false;
534 }
535
537
538 int res = 0;
539 res = drmSetClientCap(dri, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
540 if(res != 0){
541 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Failed to set universal planes capab\n");
542 return false;
543 }
544
545 s->drm.gem_manager = std::make_unique<Gem_handle_manager>(dri);
546
547 s->drm.res.reset(drmModeGetResources(dri));
548 if(!s->drm.res){
549 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Failed to get DRI resources: %s\n", strerror(errno));
550 return false;
551 }
552
553 uint64_t prime_support = false;
554 res = drmGetCap(dri, DRM_CAP_PRIME, &prime_support);
555 if(res < 0 || !prime_support){
556 log_msg(LOG_LEVEL_WARNING, MOD_NAME "DRM device does not support PRIME buffers\n");
557 }
558 s->drm.prime_support = prime_support;
559
560 return true;
561}
562
563static const char *get_connector_status_str(drmModeConnection c){
564 switch(c){
565 case DRM_MODE_CONNECTED:
566 return " (Connected)";
567 case DRM_MODE_DISCONNECTED:
568 return " (Not connected)";
569 case DRM_MODE_UNKNOWNCONNECTION:
570 default:
571 return "";
572 }
573}
574
576 color_printf("Connectors:\n");
577
578 auto connectors = get_connectors(s);
579 for(auto& connector : connectors){
580 color_printf("\t%s%s:\n", get_connector_str(connector->connector_type, connector->connector_type_id).c_str(),
581 get_connector_status_str(connector->connection));
582
583 for(int i = 0; i < connector->count_modes; i++){
584 auto mode_info = &connector->modes[i];
585 color_printf("\t\t%dx%d@%d (%s)%s\n", mode_info->hdisplay, mode_info->vdisplay, mode_info->vrefresh, mode_info->name,
586 mode_info->type & DRM_MODE_TYPE_PREFERRED ? " preferred" : "");
587 }
588 }
589}
590
592 auto connectors = get_connectors(s);
593
594 for(auto& connector : connectors){
595 if(!s->req_connector.empty()){
596 if(s->req_connector == get_connector_str(connector->connector_type, connector->connector_type_id)){
597 s->drm.connector = std::move(connector);
598 break;
599 } else {
600 continue;
601 }
602 }
603
604 if(connector->count_modes > 0 && connector->connection == DRM_MODE_CONNECTED){
605 s->drm.connector = std::move(connector);
606 break;
607 }
608 }
609
610 if(!s->drm.connector){
611 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Connector not found\n");
612 return false;
613 }
614
615 bool find_preferred_mode = s->req_width <= 0 && s->req_height <= 0 && s->req_rate <= 0;
616 for(int i = 0; i < s->drm.connector->count_modes; i++){
617 auto mode = &s->drm.connector->modes[i];
618
619 if(s->req_width > 0 && mode->hdisplay != s->req_width)
620 continue;
621
622 if(s->req_height > 0 && mode->vdisplay != s->req_height)
623 continue;
624
625 if(s->req_rate > 0 && mode->vrefresh != s->req_rate)
626 continue;
627
628 if(find_preferred_mode && !(mode->type & DRM_MODE_TYPE_PREFERRED)){
629 continue;
630 }
631
632 s->drm.mode_info = mode;
633 break;
634 }
635
636 if(!s->drm.mode_info){
637 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Mode not found\n");
638 return false;
639 }
640
641 int dri = s->drm.dri_fd.get();
642
643 std::vector<int> encoder_list;
644 if(s->drm.connector->encoder_id){
645 encoder_list.push_back(s->drm.connector->encoder_id);
646 }
647 for(int i = 0; i < s->drm.connector->count_encoders; i++){
648 encoder_list.push_back(s->drm.connector->encoders[i]);
649 }
650
651 for(int encoder_id : encoder_list){
652 s->drm.encoder.reset(drmModeGetEncoder(dri, encoder_id));
653 if(!s->drm.encoder)
654 continue;
655
656 std::vector<unsigned> crtc_list;
657 if(s->drm.encoder->crtc_id){
658 crtc_list.push_back(s->drm.encoder->crtc_id);
659 }
660 for(int i = 0; i < s->drm.res->count_crtcs; i++){
661 if((1 << i) & s->drm.encoder->possible_crtcs){
662 crtc_list.push_back(s->drm.res->crtcs[i]);
663 }
664 }
665
666 for(unsigned crtc_id : crtc_list){
667 s->drm.crtc.reset(drmModeGetCrtc(dri, crtc_id));
668 if(s->drm.crtc){
669 for(int i = 0; i < s->drm.res->count_crtcs; i++){
670 if(s->drm.res->crtcs[i] == crtc_id){
671 s->drm.crtc_index = i;
672 break;
673 }
674 }
675 break;
676 }
677 }
678 if(s->drm.crtc)
679 break;
680 }
681
682
683 if(!s->drm.crtc){
684 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Failed to find encoder crtc pair\n");
685 return false;
686 }
687
688 if(!probe_drm_formats(s)){
689 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Failed to probe DRM supported formats\n");
690 return false;
691 }
692
693 int res = 0;
694 res = drmSetMaster(dri);
695 if(res != 0){
696 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Unable to get DRM master. Is X11 or Wayland running?\n");
697 return false;
698 }
699
700 return true;
701}
702
703static Framebuffer create_dumb_fb(int dri, int width, int height, uint32_t pix_fmt){
704 Framebuffer buf;
705
706 //TODO: Currently pix_fmt is assumed to be single plane and 32 bpp
707
708 int res = 0;
709
710 {
711 Fb_handle handle;
712 drm_mode_create_dumb create_info = {};
713 create_info.width = width;
714 create_info.height = height;
715 create_info.bpp = 32;
716 res = drmIoctl(dri, DRM_IOCTL_MODE_CREATE_DUMB, &create_info);
717 if(res != 0){
718 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Failed to create a dumb framebuffer (%d)\n", res);
719 return {};
720 }
721 handle.handle = create_info.handle;
722 buf.width = width;
723 buf.height = height;
724 buf.pitch = create_info.pitch;
725 buf.size = create_info.size;
726 handle.dri_fd = dri;
727 buf.handle = Fb_handle_uniq(handle);
728 }
729
730 {
731 uint32_t handles[] = {buf.handle.get().handle, 0, 0, 0};
732 uint32_t pitches[] = {buf.pitch, 0, 0, 0};
733 uint32_t offsets[] = {0, 0, 0, 0};
734 Fb_id fb_id;
735 res = drmModeAddFB2(dri, width, height,
736 pix_fmt, handles, pitches, offsets, &fb_id.id, 0);
737
738 if(res != 0){
739 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Failed to add framebuffer (%d)\n", res);
740 return {};
741 }
742
743 fb_id.dri_fd = dri;
744 buf.id = Fb_id_uniq(fb_id);
745 }
746
747 log_msg(LOG_LEVEL_INFO, MOD_NAME "Created dumb buffer: pitch %u, handle: %u\n", buf.pitch, buf.handle.get().handle);
748
749
750 struct drm_mode_map_dumb map_info = {};
751 map_info.handle = buf.handle.get().handle;
752
753 res = drmIoctl(dri, DRM_IOCTL_MODE_MAP_DUMB, &map_info);
754 if(res != 0){
755 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Failed to get map info (%d)\n", res);
756 return {};
757 }
758
759 buf.map = MemoryMapping::create(0, buf.size, PROT_READ | PROT_WRITE, MAP_SHARED, dri, map_info.offset);
760 if(!buf.map.valid()){
761 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Failed to map buffer\n");
762 return {};
763 }
764
765 return buf;
766}
767
768static bool set_framebuffer(drm_display_state *s, uint32_t fb_id){
769 int res = 0;
770 res = drmModeSetCrtc(s->drm.dri_fd.get(), s->drm.crtc->crtc_id,
771 fb_id, 0, 0, &s->drm.connector->connector_id, 1, s->drm.mode_info);
772 if(res < 0){
773 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Failed to set crtc (%d)\n", res);
774 return false;
775 }
776 return true;
777}
778
780 int res = 0;
781 res = drmModeSetCrtc(s->drm.dri_fd.get(), s->drm.crtc->crtc_id,
782 s->splashscreen.id.get().id, 0, 0, &s->drm.connector->connector_id, 1, s->drm.mode_info);
783 if(res < 0){
784 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Failed to set crtc (%d)\n", res);
785 }
786}
787
788static void draw_frame(Framebuffer *dst, video_frame *src, bool center = true){
789 auto dst_p = static_cast<char *>(dst->map.get());
790 auto src_p = static_cast<char *>(src->tiles[0].data);
791
792 auto src_w = src->tiles[0].width;
793 auto src_h = src->tiles[0].height;
794
795 unsigned x = 0;
796 unsigned y = 0;
797
798 if(center){
799 if(src_w < dst->width)
800 x = (dst->width - src_w) / 2;
801 if(src_h < dst->height)
802 y = (dst->height - src_h) / 2;
803 }
804
805 int width = std::min(src_w, dst->width - x);
806 int height = std::min(src_h, dst->height - y);
807
808 auto src_pitch = vc_get_linesize(src_w, src->color_spec);
809 auto linesize = vc_get_size(width, src->color_spec);
810
811 dst_p += dst->pitch * y;
812 dst_p += vc_get_size(x, src->color_spec);
813
814 for(int y = 0; y < height; y++){
815 memcpy(dst_p, src_p, linesize);
816 dst_p += dst->pitch;
817 src_p += src_pitch;
818 }
819}
820
822 return s->drm.supported_drm_formats.find(fmt) != s->drm.supported_drm_formats.end();
823}
824
825static Framebuffer get_splash_fb(drm_display_state *s, int width, int height){
826 frame_uniq splash_frame(get_splashscreen());
827
828 uint32_t pix_fmt;
829 if(drm_format_supported(s, DRM_FORMAT_XBGR8888)){
830 pix_fmt = DRM_FORMAT_XBGR8888;
831 } else {
832 //XRGB_8888 should be always present
833 //red and blue will be swapped, but for splash it doesn't matter much
834 pix_fmt = DRM_FORMAT_XRGB8888;
835 }
836 auto fb = create_dumb_fb(s->drm.dri_fd.get(), width, height, pix_fmt);
837 draw_frame(&fb, splash_frame.get(), true);
838
839 return fb;
840}
841
842static void *display_drm_init(struct module *parent, const char *cfg, unsigned int flags)
843{
845
846 auto s = std::make_unique<drm_display_state>();
847
848 if(cfg)
849 s->cfg = cfg;
850
851 bool help_requested = false;
852 std::string_view sv_cfg(s->cfg);
853 while(!sv_cfg.empty()){
854 auto token = tokenize(sv_cfg, ':');
855 auto key = tokenize(token, '=');
856 auto val = tokenize(token, '=');
857
858 if(key == "help"){
859 help_requested = true;
860 } else if(key == "dev"){
861 s->device_path = val;
862 } else if(key == "connector"){
863 s->req_connector = val;
864 } else if(key == "mode"){
865 auto res = tokenize(val, '@');
866 auto rate = tokenize(val, '@');
867
868 auto width = tokenize(res, 'x');
869 auto height = tokenize(res, 'x');
870
871 parse_num(width, s->req_width);
872 parse_num(height, s->req_height);
873 parse_num(rate, s->req_rate);
874 }
875 }
876
877 if(!init_drm_state(s.get())){
878 return nullptr;
879 }
880
881 if(help_requested){
882 color_printf("\n");
883 color_printf("DRM display\n");
884 color_printf(TBOLD(TRED("\t-t drm"))"[:dev=<path>][:connector=<c>][:mode=<w>x<h>[@<rate>]]\n");
885 color_printf("where:\n");
886 color_printf(TBOLD("\tpath") " - Path to the DRI device. If not specified /dev/video[0..32] are tried.\n");
887 color_printf(TBOLD("\tconnector") " - The physical connector the display device is plugged into.\n");
888 color_printf(TBOLD("\tmode") " - Video mode to use. If not specified, the preferred mode is used. \n");
889 color_printf("\n");
890 print_connectors(s.get());
891 return INIT_NOERR;
892 }
893
894 if(!setup_crtc(s.get())){
895 return nullptr;
896 }
897
898 s->splashscreen = get_splash_fb(s.get(), s->drm.mode_info->hdisplay, s->drm.mode_info->vdisplay);
899
900 draw_splash(s.get());
901
902 return s.release();
903}
904
905static void display_drm_done(void *state)
906{
907 auto s = std::unique_ptr<drm_display_state>(static_cast<drm_display_state *>(state));
908
909 int res = 0;
910 res = drmModeSetCrtc(s->drm.dri_fd.get(), s->drm.crtc->crtc_id, s->drm.crtc->buffer_id,
911 s->drm.crtc->x , s->drm.crtc->y, &s->drm.connector->connector_id, 1, &s->drm.crtc->mode);
912 if(res < 0){
913 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Failed to restore original crtc (%d)\n", res);
914 }
915}
916
917static struct video_frame *display_drm_getf(void *state)
918{
919 auto s = static_cast<drm_display_state *>(state);
920
921 while(!s->free_frames.empty()){
922 auto frame = std::move(s->free_frames.back());
923 s->free_frames.pop_back();
924
925 if(video_desc_eq(video_desc_from_frame(frame.get()), s->desc)){
926 return frame.release();
927 }
928 }
929
930 return vf_alloc_desc_data(s->desc);
931}
932
934 std::swap(s->front_buffer, s->back_buffer);
935 return set_framebuffer(s, s->front_buffer.id.get().id);
936}
937
939 assert(frame->color_spec == DRM_PRIME);
940
941 Drm_prime_fb fb;
942 fb.frame = std::move(frame);
943 auto drm_frame = (drm_prime_frame *) fb.frame->tiles[0].data;
944
945 for(int i = 0; i < drm_frame->fd_count; i++){
946 fb.gem_objects[i] = s->drm.gem_manager->get_handle(drm_frame->dmabuf_fds[i]);
947 }
948
949 uint32_t handles[4] = {};
950 for(int i = 0; i < drm_frame->planes; i++){
951 handles[i] = fb.gem_objects[drm_frame->fd_indices[i]].get();
952 }
953
954 int res = 0;
955 Fb_id fb_id;
956 res = drmModeAddFB2WithModifiers(s->drm.dri_fd.get(), fb.frame->tiles[0].width, fb.frame->tiles[0].height, drm_frame->drm_format,
957 handles, drm_frame->pitches, drm_frame->offsets, drm_frame->modifiers, &fb_id.id, DRM_MODE_FB_MODIFIERS);
958 if(res != 0){
959 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Failed to add FB\n");
960 }
961 fb_id.dri_fd = s->drm.dri_fd.get();
962 fb.id = Fb_id_uniq(fb_id);
963
964 return fb;
965}
966
967static void recycle_frame(drm_display_state *s, frame_uniq& frame){
968 vf_recycle(frame.get());
969 s->free_frames.push_back(std::move(frame));
970}
971
973 frame_uniq frame = std::move(fb.frame);
974 fb = {};
975 if(frame){
976 vf_recycle(frame.get());
977 s->free_frames.push_back(std::move(frame));
978 }
979}
980
981static bool display_drm_putf(void *state, struct video_frame *f, long long flags)
982{
983 frame_uniq frame(f);
984
985 if (!frame) {
986 return true;
987 }
988
989 auto s = static_cast<drm_display_state *>(state);
990
991 if(flags == PUTF_DISCARD){
993 return true;
994 }
995
996 if(frame->color_spec == DRM_PRIME){
997 Drm_prime_fb fb = drm_fb_from_frame(s, std::move(frame));
998 if(!set_framebuffer(s, fb.id.get().id)){
999 return false;
1000 }
1001
1002 recycle_prime_frame(s, s->drm_prime_fb);
1003 s->drm_prime_fb = std::move(fb);
1004 return true;
1005 }
1006
1007 draw_frame(&s->back_buffer, frame.get());
1008 recycle_frame(s, frame);
1009 swap_buffers(s);
1010
1011 return true;
1012}
1013
1014static bool get_codecs(drm_display_state *s, void *val, size_t *len){
1015 std::vector<codec_t> out;
1016
1017 if(s->drm.prime_support)
1018 out.push_back(DRM_PRIME);
1019
1020 if(s->drm.supported_drm_formats.find(DRM_FORMAT_UYVY) != s->drm.supported_drm_formats.end())
1021 out.push_back(UYVY);
1022
1023 out.push_back(RGBA);
1024
1025 size_t length = sizeof(codec_t) * out.size();
1026
1027 if(*len < length) {
1028 return false;
1029 }
1030 *len = length;
1031 memcpy(val, out.data(), *len);
1032
1033 return true;
1034
1035}
1036
1037static bool display_drm_get_property(void *state, int property, void *val, size_t *len)
1038{
1039 auto s = static_cast<drm_display_state *>(state);
1040
1041 int rgb_shift[] = {0, 8, 16};
1042 if(!drm_format_supported(s, DRM_FORMAT_XBGR8888)){
1043 std::swap(rgb_shift[0], rgb_shift[2]);
1044 }
1045
1046 switch (property) {
1048 return get_codecs(s, val, len);
1050 if(sizeof(rgb_shift) > *len) {
1051 return false;
1052 }
1053 memcpy(val, rgb_shift, sizeof(rgb_shift));
1054 *len = sizeof(rgb_shift);
1055 break;
1056 default:
1057 return false;
1058 }
1059 return true;
1060}
1061
1063{
1064 auto s = static_cast<drm_display_state *>(state);
1065
1066 s->desc = desc;
1067
1068 s->frame.reset(vf_alloc_desc_data(desc));
1069
1070 uint32_t pix_fmt;
1071
1072 switch(desc.color_spec){
1073 case RGBA:
1074 pix_fmt = drm_format_supported(s, DRM_FORMAT_XBGR8888) ? DRM_FORMAT_XBGR8888 : DRM_FORMAT_XRGB8888;
1075 break;
1076 case UYVY:
1077 pix_fmt = DRM_FORMAT_UYVY;
1078 break;
1079 case DRM_PRIME:
1080 /* We don't create dumb buffers in this case, we import framebuffers from video frames instead
1081 */
1082 pix_fmt = 0; //UNUSED
1083 if(!s->drm.mode_info || s->drm.mode_info->hdisplay > desc.width || s->drm.mode_info->vdisplay > desc.height){
1084 log_msg(LOG_LEVEL_WARNING, MOD_NAME "Video mode resolution must be less or equal to DRM_PRIME frames. Trying to find a suitable mode...\n");
1085 s->drm.mode_info = nullptr;
1086 for(int i = 0; i < s->drm.connector->count_modes; i++){
1087 auto mode = &s->drm.connector->modes[i];
1088
1089 if(mode->hdisplay > desc.width)
1090 continue;
1091
1092 if(mode->vdisplay > desc.height)
1093 continue;
1094
1095 s->drm.mode_info = mode;
1096 break;
1097 }
1098 if(!s->drm.mode_info){
1099 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Failed to find suitable mode\n");
1100 return false;
1101 }
1102 }
1103 return true;
1104 default:
1105 return false;
1106 }
1107
1108 if(desc.width > s->drm.mode_info->hdisplay || desc.height > s->drm.mode_info->vdisplay){
1109 log_msg(LOG_LEVEL_WARNING, MOD_NAME "Video resolution is larger than framebuffer. Only part of video frames will be visible\n");
1110 }
1111
1112 s->front_buffer = create_dumb_fb(s->drm.dri_fd.get(), s->drm.mode_info->hdisplay, s->drm.mode_info->vdisplay, pix_fmt);
1113 s->back_buffer = create_dumb_fb(s->drm.dri_fd.get(), s->drm.mode_info->hdisplay, s->drm.mode_info->vdisplay, pix_fmt);
1114
1115
1116 return true;
1117}
1118
1119static void display_drm_probe(struct device_info **available_cards, int *count, void (**deleter)(void *)) {
1120 UNUSED(deleter);
1121 *available_cards = NULL;
1122 *count = 0;
1123}
1124
1128 NULL, // _run
1134 NULL, // _put_audio_frame
1135 NULL, // _reconfigure_audio
1136 MOD_NAME,
1137};
1138
1140
#define UNUSED(x)
void ** state
int void * val
struct video_desc desc
struct video_frame * frame
const char * fmt
int void size_t * len
const char unsigned int flags
int void(** deleter)(void *))
int property
void vf_free(struct video_frame *buf)
Frees video_frame structure.
struct video_desc video_desc_from_frame(const struct video_frame *frame)
Returns struct video_desc from video frame.
int vc_get_linesize(unsigned int width, codec_t codec)
struct video_frame * vf_alloc_desc_data(struct video_desc desc)
int color_printf(const char *format,...)
#define TRED(x)
Definition color_out.h:75
#define TBOLD(x)
Definition color_out.h:70
void log_msg(int level, const char *format,...)
Definition debug.cpp:130
#define LOG_LEVEL_INFO
normal reporting
Definition debug.h:62
#define LOG_LEVEL_ERROR
general errors
Definition debug.h:59
#define LOG_LEVEL_WARNING
less severe errors
Definition debug.h:60
static Drm_prime_fb drm_fb_from_frame(drm_display_state *s, frame_uniq frame)
Definition drm.cpp:938
static void * display_drm_init(struct module *parent, const char *cfg, unsigned int flags)
Definition drm.cpp:842
static std::string get_connector_str(int type, uint32_t id)
Definition drm.cpp:375
static const struct video_display_info display_drm_info
Definition drm.cpp:1125
static bool drm_format_supported(drm_display_state *s, uint32_t fmt)
Definition drm.cpp:821
static bool display_drm_reconfigure(void *state, struct video_desc desc)
Definition drm.cpp:1062
static int64_t get_property(int dri, drmModeObjectPropertiesPtr props, std::string_view name)
Definition drm.cpp:465
static void print_connectors(drm_display_state *s)
Definition drm.cpp:575
static void display_drm_done(void *state)
Definition drm.cpp:905
static void recycle_frame(drm_display_state *s, frame_uniq &frame)
Definition drm.cpp:967
static bool probe_drm_formats(drm_display_state *s)
Definition drm.cpp:477
static Framebuffer create_dumb_fb(int dri, int width, int height, uint32_t pix_fmt)
Definition drm.cpp:703
static Fd_uniq open_dri(drm_display_state *s)
Definition drm.cpp:415
static Framebuffer get_splash_fb(drm_display_state *s, int width, int height)
Definition drm.cpp:825
static struct video_frame * display_drm_getf(void *state)
Definition drm.cpp:917
static void print_drm_driver_info(drm_display_state *s)
Definition drm.cpp:399
static std::vector< Drm_connector_uniq > get_connectors(drm_display_state *s)
Definition drm.cpp:514
static void recycle_prime_frame(drm_display_state *s, Drm_prime_fb &fb)
Definition drm.cpp:972
static bool display_drm_get_property(void *state, int property, void *val, size_t *len)
Definition drm.cpp:1037
static const char * get_connector_status_str(drmModeConnection c)
Definition drm.cpp:563
static bool setup_crtc(drm_display_state *s)
Definition drm.cpp:591
static void draw_splash(drm_display_state *s)
Definition drm.cpp:779
#define MOD_NAME
Definition drm.cpp:75
static bool display_drm_putf(void *state, struct video_frame *f, long long flags)
Definition drm.cpp:981
static bool set_framebuffer(drm_display_state *s, uint32_t fb_id)
Definition drm.cpp:768
static bool swap_buffers(drm_display_state *s)
Definition drm.cpp:933
static void draw_frame(Framebuffer *dst, video_frame *src, bool center=true)
Definition drm.cpp:788
static void display_drm_probe(struct device_info **available_cards, int *count, void(**deleter)(void *))
Definition drm.cpp:1119
static bool init_drm_state(drm_display_state *s)
Definition drm.cpp:528
static bool get_codecs(drm_display_state *s, void *val, size_t *len)
Definition drm.cpp:1014
#define PUTF_DISCARD
#define VIDEO_DISPLAY_ABI_VERSION
struct video_frame * get_splashscreen()
@ DISPLAY_PROPERTY_RGB_SHIFT
red,green,blue shift - int[3] (bits)
@ DISPLAY_PROPERTY_CODECS
list of natively supported codecs - codec_t[]
int count
Definition hd-rum.c:44
#define INIT_NOERR
Definition host.h:73
@ LIBRARY_CLASS_VIDEO_DISPLAY
Definition lib_common.h:79
#define REGISTER_MODULE(name, info, lclass, abi)
Registers module to global modules' registry.
Definition lib_common.h:144
enum openssl_mode mode
const char * parent
Definition settings.cpp:198
Option::OptType type
Definition settings.cpp:194
const char * name
Definition settings.cpp:193
std::string_view tokenize(std::string_view &str, char delim, char quot)
Tokenizer for string_view.
bool parse_num(std::string_view sv, T &res, int base=10)
frame_uniq frame
Definition drm.cpp:347
Gem_handle_manager::Handle gem_objects[4]
Definition drm.cpp:348
Fb_id_uniq id
Definition drm.cpp:349
drmModeModeInfoPtr mode_info
Definition drm.cpp:329
Drm_connector_uniq connector
Definition drm.cpp:321
Drm_crtc_uniq crtc
Definition drm.cpp:323
Drm_res_uniq res
Definition drm.cpp:320
Fd_uniq dri_fd
Definition drm.cpp:316
int crtc_index
Definition drm.cpp:324
std::unique_ptr< Gem_handle_manager > gem_manager
Definition drm.cpp:318
bool prime_support
Definition drm.cpp:327
Drm_encoder_uniq encoder
Definition drm.cpp:322
std::set< uint32_t > supported_drm_formats
Definition drm.cpp:326
uint32_t height
Definition drm.cpp:334
uint32_t pitch
Definition drm.cpp:335
Fb_id_uniq id
Definition drm.cpp:342
uint32_t pix_fmt
Definition drm.cpp:340
size_t size
Definition drm.cpp:336
MemoryMapping map
Definition drm.cpp:343
uint32_t width
Definition drm.cpp:333
Fb_handle_uniq handle
Definition drm.cpp:338
std::string cfg
Definition drm.cpp:353
frame_uniq frame
Definition drm.cpp:370
Framebuffer back_buffer
Definition drm.cpp:364
unsigned req_rate
Definition drm.cpp:358
std::string req_connector
Definition drm.cpp:355
Drm_prime_fb drm_prime_fb
Definition drm.cpp:367
video_desc desc
Definition drm.cpp:369
Drm_state drm
Definition drm.cpp:360
Framebuffer splashscreen
Definition drm.cpp:362
std::vector< frame_uniq > free_frames
Definition drm.cpp:372
Framebuffer front_buffer
Definition drm.cpp:365
std::string device_path
Definition drm.cpp:354
void operator()(video_frame *f)
Definition pipewire.cpp:77
unsigned int width
Definition types.h:280
char * data
Raw tile data. Pointer must be at least 4B aligned.
Definition types.h:287
unsigned int height
Definition types.h:281
codec_t color_spec
Definition types.h:182
unsigned int width
width of every tile (!)
Definition types.h:179
unsigned int height
height of every tile (!)
Definition types.h:180
Struct video_frame represents a video frame and contains video description.
Definition types.h:305
struct tile tiles[FLEXIBLE_ARRAY_MEMBER]
Definition types.h:345
codec_t color_spec
Definition types.h:306
codec_t
Definition types.h:65
@ UYVY
YCbCr 422 8-bit - Cb Y0 Cr Y1.
Definition types.h:71
@ DRM_PRIME
DRM Prime buffer, data contains struct drm_prime_frame.
Definition types.h:107
@ RGBA
RGBA 8-bit, big-endian.
Definition types.h:70
This is an umbrella header for video functions.
int vc_get_size(unsigned int width, codec_t codec)
const char * key
Definition cineform.cpp:125
void vf_recycle(struct video_frame *buf)
This function should be called before returning a frame back to a frame pool. Currently this is used ...
bool video_desc_eq(struct video_desc a, struct video_desc b)
Compares two video descriptions.