1 /*
2 * GStreamer
3 * Copyright (C) 2018 Carlos Rafael Giani <dv@pseudoterminal.org>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public
16 * License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 */
20
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24
25 #include "gstgldisplay_gbm.h"
26 #include "gstgl_gbm_utils.h"
27
28 #include <unistd.h>
29 #include <fcntl.h>
30 #include <errno.h>
31
32 GST_DEBUG_CATEGORY (gst_gl_gbm_debug);
33
34 GST_DEBUG_CATEGORY_STATIC (gst_gl_display_debug);
35 #define GST_CAT_DEFAULT gst_gl_display_debug
36
37
38 #define INVALID_CRTC ((guint32)0)
39
40
41 G_DEFINE_TYPE (GstGLDisplayGBM, gst_gl_display_gbm, GST_TYPE_GL_DISPLAY);
42
43
44 static void gst_gl_display_gbm_finalize (GObject * object);
45 static guintptr gst_gl_display_gbm_get_handle (GstGLDisplay * display);
46
47 static guint32 gst_gl_gbm_find_crtc_id_for_encoder (GstGLDisplayGBM *
48 display_gbm, drmModeEncoder const *encoder);
49 static guint32 gst_gl_gbm_find_crtc_id_for_connector (GstGLDisplayGBM *
50 display_gbm);
51
52 static gboolean gst_gl_display_gbm_setup_drm (GstGLDisplayGBM * display_gbm,
53 const gchar * drm_connector_name);
54 static void gst_gl_display_gbm_shutdown_drm (GstGLDisplayGBM * display_gbm);
55
56 static gboolean gst_gl_display_gbm_setup_gbm (GstGLDisplayGBM * display_gbm);
57 static void gst_gl_display_gbm_shutdown_gbm (GstGLDisplayGBM * display_gbm);
58
59
60 static void
gst_gl_display_gbm_class_init(GstGLDisplayGBMClass * klass)61 gst_gl_display_gbm_class_init (GstGLDisplayGBMClass * klass)
62 {
63 GST_GL_DISPLAY_CLASS (klass)->get_handle =
64 GST_DEBUG_FUNCPTR (gst_gl_display_gbm_get_handle);
65
66 G_OBJECT_CLASS (klass)->finalize = gst_gl_display_gbm_finalize;
67 }
68
69 static void
gst_gl_display_gbm_init(GstGLDisplayGBM * display_gbm)70 gst_gl_display_gbm_init (GstGLDisplayGBM * display_gbm)
71 {
72 GstGLDisplay *display = (GstGLDisplay *) display_gbm;
73 display->type = GST_GL_DISPLAY_TYPE_GBM;
74
75 display_gbm->drm_fd = -1;
76 }
77
78 static void
gst_gl_display_gbm_finalize(GObject * object)79 gst_gl_display_gbm_finalize (GObject * object)
80 {
81 GstGLDisplayGBM *display_gbm = GST_GL_DISPLAY_GBM (object);
82
83 gst_gl_display_gbm_shutdown_gbm (display_gbm);
84 gst_gl_display_gbm_shutdown_drm (display_gbm);
85
86 if (display_gbm->drm_fd >= 0)
87 close (display_gbm->drm_fd);
88
89 G_OBJECT_CLASS (gst_gl_display_gbm_parent_class)->finalize (object);
90 }
91
92 static guintptr
gst_gl_display_gbm_get_handle(GstGLDisplay * display)93 gst_gl_display_gbm_get_handle (GstGLDisplay * display)
94 {
95 return (guintptr) GST_GL_DISPLAY_GBM (display)->gbm_dev;
96 }
97
98
99 static guint32
gst_gl_gbm_find_crtc_id_for_encoder(GstGLDisplayGBM * display_gbm,drmModeEncoder const * encoder)100 gst_gl_gbm_find_crtc_id_for_encoder (GstGLDisplayGBM * display_gbm,
101 drmModeEncoder const *encoder)
102 {
103 int i;
104 for (i = 0; i < display_gbm->drm_mode_resources->count_crtcs; ++i) {
105 /* possible_crtcs is a bitmask as described here:
106 * https://dvdhrm.wordpress.com/2012/09/13/linux-drm-mode-setting-api */
107 guint32 const crtc_mask = 1 << i;
108 guint32 const crtc_id = display_gbm->drm_mode_resources->crtcs[i];
109
110 if (encoder->possible_crtcs & crtc_mask)
111 return crtc_id;
112 }
113
114 /* No match found */
115 return INVALID_CRTC;
116 }
117
118
119 static guint32
gst_gl_gbm_find_crtc_id_for_connector(GstGLDisplayGBM * display_gbm)120 gst_gl_gbm_find_crtc_id_for_connector (GstGLDisplayGBM * display_gbm)
121 {
122 int i;
123 for (i = 0; i < display_gbm->drm_mode_connector->count_encoders; ++i) {
124 guint32 encoder_id = display_gbm->drm_mode_connector->encoders[i];
125 drmModeEncoder *encoder =
126 drmModeGetEncoder (display_gbm->drm_fd, encoder_id);
127
128 if (encoder != NULL) {
129 guint32 crtc_id =
130 gst_gl_gbm_find_crtc_id_for_encoder (display_gbm, encoder);
131 drmModeFreeEncoder (encoder);
132
133 if (crtc_id != INVALID_CRTC)
134 return crtc_id;
135 }
136 }
137
138 /* No match found */
139 return INVALID_CRTC;
140 }
141
142
143 static gboolean
gst_gl_display_gbm_setup_drm(GstGLDisplayGBM * display_gbm,const gchar * drm_connector_name)144 gst_gl_display_gbm_setup_drm (GstGLDisplayGBM * display_gbm, const gchar *
145 drm_connector_name)
146 {
147 int i;
148
149 g_assert (display_gbm != NULL);
150 g_assert (display_gbm->drm_fd >= 0);
151
152 /* Get the DRM mode resources */
153 display_gbm->drm_mode_resources = drmModeGetResources (display_gbm->drm_fd);
154 if (display_gbm->drm_mode_resources == NULL) {
155 GST_ERROR ("Could not get DRM resources: %s (%d)", g_strerror (errno),
156 errno);
157 goto cleanup;
158 }
159 GST_DEBUG ("Got DRM resources");
160
161 /* Find a connected connector. The connector is where the pixel data is
162 * finally sent to, and typically connects to some form of display, like an
163 * HDMI TV, an LVDS panel etc. */
164 {
165 drmModeConnector *connected_connector = NULL;
166
167 GST_DEBUG ("Checking %d DRM connector(s)",
168 display_gbm->drm_mode_resources->count_connectors);
169 for (i = 0; i < display_gbm->drm_mode_resources->count_connectors; ++i) {
170 drmModeConnector *candidate_connector =
171 drmModeGetConnector (display_gbm->drm_fd,
172 display_gbm->drm_mode_resources->connectors[i]);
173 gchar *candidate_name;
174
175 candidate_name = g_strdup_printf ("%s-%i",
176 gst_gl_gbm_get_name_for_drm_connector (candidate_connector),
177 candidate_connector->connector_type_id);
178
179 GST_DEBUG ("Found DRM connector #%d \"%s\" with ID %" G_GUINT32_FORMAT, i,
180 candidate_name, candidate_connector->connector_id);
181
182 /* If we already picked a connector, and connected_connector is therefore
183 * non-NULL, then are just printing information about the other connectors
184 * for logging purposes by now, so don't actually do anything with this
185 * connector. Just loop instead. */
186 if (connected_connector != NULL) {
187 drmModeFreeConnector (candidate_connector);
188 g_free (candidate_name);
189 continue;
190 }
191
192 if (drm_connector_name != NULL) {
193 if (g_ascii_strcasecmp (drm_connector_name, candidate_name) != 0) {
194 drmModeFreeConnector (candidate_connector);
195 g_free (candidate_name);
196 continue;
197 }
198 }
199
200 if (candidate_connector->connection == DRM_MODE_CONNECTED) {
201 if (drm_connector_name != NULL)
202 GST_DEBUG ("Picking DRM connector #%d because it is connected and "
203 "has a matching name \"%s\"", i, candidate_name);
204 else
205 GST_DEBUG ("Picking DRM connector #%d because it is connected", i);
206 connected_connector = candidate_connector;
207 g_free (candidate_name);
208 break;
209 } else {
210 if (drm_connector_name != NULL)
211 GST_WARNING ("DRM connector #%d has a matching name \"%s\" but is "
212 "not connected; not picking it", i, candidate_name);
213 drmModeFreeConnector (candidate_connector);
214 g_free (candidate_name);
215 }
216 }
217
218 if (connected_connector == NULL) {
219 GST_ERROR ("No connected DRM connector found");
220 goto cleanup;
221 }
222
223 display_gbm->drm_mode_connector = connected_connector;
224 }
225
226 /* Check out what modes are supported by the chosen connector,
227 * and pick either the "preferred" mode or the one with the largest
228 * pixel area. */
229 {
230 int selected_mode_index = -1;
231 int selected_mode_area = -1;
232 gboolean preferred_mode_found = FALSE;
233
234 GST_DEBUG ("Checking %d DRM mode(s) from selected connector",
235 display_gbm->drm_mode_connector->count_modes);
236 for (i = 0; i < display_gbm->drm_mode_connector->count_modes; ++i) {
237 drmModeModeInfo *current_mode =
238 &(display_gbm->drm_mode_connector->modes[i]);
239 int current_mode_area = current_mode->hdisplay * current_mode->vdisplay;
240
241 GST_DEBUG ("Found DRM mode #%d width/height %" G_GUINT16_FORMAT "/%"
242 G_GUINT16_FORMAT " hsync/vsync start %" G_GUINT16_FORMAT "/%"
243 G_GUINT16_FORMAT " hsync/vsync end %" G_GUINT16_FORMAT "/%"
244 G_GUINT16_FORMAT " htotal/vtotal %" G_GUINT16_FORMAT "/%"
245 G_GUINT16_FORMAT " hskew %" G_GUINT16_FORMAT " vscan %"
246 G_GUINT16_FORMAT " vrefresh %" G_GUINT32_FORMAT " preferred %d", i,
247 current_mode->hdisplay, current_mode->vdisplay,
248 current_mode->hsync_start, current_mode->vsync_start,
249 current_mode->hsync_end, current_mode->vsync_end,
250 current_mode->htotal, current_mode->vtotal, current_mode->hskew,
251 current_mode->vscan, current_mode->vrefresh,
252 (current_mode->type & DRM_MODE_TYPE_PREFERRED) ? TRUE : FALSE);
253
254 if (!preferred_mode_found
255 && ((current_mode->type & DRM_MODE_TYPE_PREFERRED)
256 || (current_mode_area > selected_mode_area))) {
257 display_gbm->drm_mode_info = current_mode;
258 selected_mode_area = current_mode_area;
259 selected_mode_index = i;
260
261 if (current_mode->type & DRM_MODE_TYPE_PREFERRED)
262 preferred_mode_found = TRUE;
263 }
264 }
265
266 if (display_gbm->drm_mode_info == NULL) {
267 GST_ERROR ("No usable DRM mode found");
268 goto cleanup;
269 }
270
271 GST_DEBUG ("Selected DRM mode #%d (is preferred: %d)", selected_mode_index,
272 preferred_mode_found);
273 }
274
275 /* Find an encoder that is attached to the chosen connector. Also find the
276 * index/id of the CRTC associated with this encoder. The encoder takes pixel
277 * data from the CRTC and transmits it to the connector. The CRTC roughly
278 * represents the scanout framebuffer.
279 *
280 * Ultimately, we only care about the CRTC index & ID, so the encoder
281 * reference is discarded here once these are found. The CRTC index is the
282 * index in the m_drm_mode_resources' CRTC array, while the ID is an identifier
283 * used by the DRM to refer to the CRTC universally. (We need the CRTC
284 * information for page flipping and DRM scanout framebuffer configuration.) */
285 {
286 drmModeEncoder *selected_encoder = NULL;
287
288 GST_DEBUG ("Checking %d DRM encoder(s)",
289 display_gbm->drm_mode_resources->count_encoders);
290 for (i = 0; i < display_gbm->drm_mode_resources->count_encoders; ++i) {
291 drmModeEncoder *candidate_encoder =
292 drmModeGetEncoder (display_gbm->drm_fd,
293 display_gbm->drm_mode_resources->encoders[i]);
294
295 GST_DEBUG ("Found DRM encoder #%d \"%s\"", i,
296 gst_gl_gbm_get_name_for_drm_encoder (candidate_encoder));
297
298 if ((selected_encoder == NULL) &&
299 (candidate_encoder->encoder_id ==
300 display_gbm->drm_mode_connector->encoder_id)) {
301 selected_encoder = candidate_encoder;
302 GST_DEBUG ("DRM encoder #%d corresponds to selected DRM connector "
303 "-> selected", i);
304 } else
305 drmModeFreeEncoder (candidate_encoder);
306 }
307
308 if (selected_encoder == NULL) {
309 GST_DEBUG ("No encoder found; searching for CRTC ID in the connector");
310 display_gbm->crtc_id =
311 gst_gl_gbm_find_crtc_id_for_connector (display_gbm);
312 } else {
313 GST_DEBUG ("Using CRTC ID from selected encoder");
314 display_gbm->crtc_id = selected_encoder->crtc_id;
315 drmModeFreeEncoder (selected_encoder);
316 }
317
318 if (display_gbm->crtc_id == INVALID_CRTC) {
319 GST_ERROR ("No CRTC found");
320 goto cleanup;
321 }
322
323 GST_DEBUG ("CRTC with ID %" G_GUINT32_FORMAT " found; now locating it in "
324 "the DRM mode resources CRTC array", display_gbm->crtc_id);
325
326 for (i = 0; i < display_gbm->drm_mode_resources->count_crtcs; ++i) {
327 if (display_gbm->drm_mode_resources->crtcs[i] == display_gbm->crtc_id) {
328 display_gbm->crtc_index = i;
329 break;
330 }
331 }
332
333 if (display_gbm->crtc_index < 0) {
334 GST_ERROR ("No matching CRTC entry in DRM resources found");
335 goto cleanup;
336 }
337
338 GST_DEBUG ("CRTC with ID %" G_GUINT32_FORMAT " can be found at index #%d "
339 "in the DRM mode resources CRTC array", display_gbm->crtc_id,
340 display_gbm->crtc_index);
341 }
342
343 GST_DEBUG ("DRM structures initialized");
344 return TRUE;
345
346 cleanup:
347 gst_gl_display_gbm_shutdown_drm (display_gbm);
348 return FALSE;
349 }
350
351
352 static void
gst_gl_display_gbm_shutdown_drm(GstGLDisplayGBM * display_gbm)353 gst_gl_display_gbm_shutdown_drm (GstGLDisplayGBM * display_gbm)
354 {
355 g_assert (display_gbm != NULL);
356
357 display_gbm->drm_mode_info = NULL;
358
359 display_gbm->crtc_index = -1;
360 display_gbm->crtc_id = INVALID_CRTC;
361
362 if (display_gbm->drm_mode_connector != NULL) {
363 drmModeFreeConnector (display_gbm->drm_mode_connector);
364 display_gbm->drm_mode_connector = NULL;
365 }
366
367 if (display_gbm->drm_mode_resources != NULL) {
368 drmModeFreeResources (display_gbm->drm_mode_resources);
369 display_gbm->drm_mode_resources = NULL;
370 }
371 }
372
373
374 static gboolean
gst_gl_display_gbm_setup_gbm(GstGLDisplayGBM * display_gbm)375 gst_gl_display_gbm_setup_gbm (GstGLDisplayGBM * display_gbm)
376 {
377 display_gbm->gbm_dev = gbm_create_device (display_gbm->drm_fd);
378 if (display_gbm->gbm_dev == NULL) {
379 GST_ERROR ("Creating GBM device failed");
380 return FALSE;
381 }
382
383 GST_DEBUG ("GBM structures initialized");
384 return TRUE;
385 }
386
387
388 static void
gst_gl_display_gbm_shutdown_gbm(GstGLDisplayGBM * display_gbm)389 gst_gl_display_gbm_shutdown_gbm (GstGLDisplayGBM * display_gbm)
390 {
391 if (display_gbm->gbm_dev != NULL) {
392 gbm_device_destroy (display_gbm->gbm_dev);
393 display_gbm->gbm_dev = NULL;
394 }
395 }
396
397
398 static void
_init_debug(void)399 _init_debug (void)
400 {
401 static gsize _init = 0;
402
403 if (g_once_init_enter (&_init)) {
404 GST_DEBUG_CATEGORY_GET (gst_gl_display_debug, "gldisplay");
405 GST_DEBUG_CATEGORY_INIT (gst_gl_gbm_debug, "gleglgbm", 0,
406 "Mesa3D EGL GBM debugging");
407 g_once_init_leave (&_init, 1);
408 }
409 }
410
411
412 GstGLDisplayGBM *
gst_gl_display_gbm_new(void)413 gst_gl_display_gbm_new (void)
414 {
415 int drm_fd = -1;
416 GstGLDisplayGBM *display;
417 const gchar *drm_node_name;
418 const gchar *drm_connector_name;
419
420 _init_debug ();
421
422 drm_node_name = g_getenv ("GST_GL_GBM_DRM_DEVICE");
423 drm_connector_name = g_getenv ("GST_GL_GBM_DRM_CONNECTOR");
424
425 if (drm_node_name != NULL) {
426 GST_DEBUG ("attempting to open device %s (specified by the "
427 "GST_GL_GBM_DRM_DEVICE environment variable)", drm_node_name);
428 drm_fd = open (drm_node_name, O_RDWR | O_CLOEXEC);
429 if (drm_fd < 0) {
430 GST_ERROR ("could not open DRM device %s: %s (%d)", drm_node_name,
431 g_strerror (errno), errno);
432 return NULL;
433 }
434 } else {
435 GST_DEBUG ("GST_GL_GBM_DRM_DEVICE environment variable is not "
436 "set - trying to autodetect device");
437 drm_fd = gst_gl_gbm_find_and_open_drm_node ();
438 if (drm_fd < 0) {
439 GST_ERROR ("could not find or open DRM device");
440 return NULL;
441 }
442 }
443
444 display = g_object_new (GST_TYPE_GL_DISPLAY_GBM, NULL);
445 display->drm_fd = drm_fd;
446
447 if (drm_connector_name != NULL) {
448 GST_DEBUG ("GST_GL_GBM_DRM_CONNECTOR variable set to value \"%s\"; "
449 "will use this name to match connector(s) against", drm_connector_name);
450 }
451
452 if (!gst_gl_display_gbm_setup_drm (display, drm_connector_name)) {
453 GST_WARNING ("Failed to initialize DRM");
454 }
455
456 if (!gst_gl_display_gbm_setup_gbm (display)) {
457 GST_ERROR ("Failed to initialize GBM");
458 goto cleanup;
459 }
460
461 GST_DEBUG ("Created GBM EGL display %p", (gpointer) display);
462
463 return display;
464
465 cleanup:
466 gst_gl_display_gbm_shutdown_gbm (display);
467 gst_gl_display_gbm_shutdown_drm (display);
468 gst_object_unref (G_OBJECT (display));
469 if (drm_fd >= 0)
470 close (drm_fd);
471 return NULL;
472 }
473