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 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23 
24 #include <poll.h>
25 
26 #include "../gstgl_fwd.h"
27 #include <gst/gl/gstglcontext.h>
28 #include <gst/gl/egl/gstglcontext_egl.h>
29 
30 #include "gstgldisplay_gbm.h"
31 #include "gstglwindow_gbm_egl.h"
32 #include "gstgl_gbm_utils.h"
33 #include "../gstglwindow_private.h"
34 
35 #define GST_CAT_DEFAULT gst_gl_window_debug
36 
37 
38 #define GST_GL_WINDOW_GBM_EGL_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
39     GST_TYPE_GL_WINDOW_GBM_EGL, GstGLWindowGBMEGLPrivate))
40 
41 
42 G_DEFINE_TYPE (GstGLWindowGBMEGL, gst_gl_window_gbm_egl, GST_TYPE_GL_WINDOW);
43 
44 
45 static guintptr gst_gl_window_gbm_egl_get_window_handle (GstGLWindow * window);
46 static guintptr gst_gl_window_gbm_egl_get_display (GstGLWindow * window);
47 static void gst_gl_window_gbm_egl_set_window_handle (GstGLWindow * window,
48     guintptr handle);
49 static void gst_gl_window_gbm_egl_close (GstGLWindow * window);
50 static void gst_gl_window_gbm_egl_draw (GstGLWindow * window);
51 
52 static gboolean gst_gl_window_gbm_init_surface (GstGLWindowGBMEGL * window_egl);
53 
54 static void
gst_gl_window_gbm_egl_class_init(GstGLWindowGBMEGLClass * klass)55 gst_gl_window_gbm_egl_class_init (GstGLWindowGBMEGLClass * klass)
56 {
57   GstGLWindowClass *window_class = (GstGLWindowClass *) klass;
58 
59   window_class->get_window_handle =
60       GST_DEBUG_FUNCPTR (gst_gl_window_gbm_egl_get_window_handle);
61   window_class->get_display =
62       GST_DEBUG_FUNCPTR (gst_gl_window_gbm_egl_get_display);
63   window_class->set_window_handle =
64       GST_DEBUG_FUNCPTR (gst_gl_window_gbm_egl_set_window_handle);
65   window_class->close = GST_DEBUG_FUNCPTR (gst_gl_window_gbm_egl_close);
66   window_class->draw = GST_DEBUG_FUNCPTR (gst_gl_window_gbm_egl_draw);
67 
68   /* TODO: add support for set_render_rectangle (assuming this functionality
69    * is possible with libdrm/gbm) */
70 }
71 
72 
73 static void
gst_gl_window_gbm_egl_init(GstGLWindowGBMEGL * window_gbm)74 gst_gl_window_gbm_egl_init (GstGLWindowGBMEGL * window_gbm)
75 {
76   window_gbm->gbm_surf = NULL;
77   window_gbm->current_bo = NULL;
78   window_gbm->prev_bo = NULL;
79   window_gbm->waiting_for_flip = 0;
80 }
81 
82 
83 static guintptr
gst_gl_window_gbm_egl_get_window_handle(GstGLWindow * window)84 gst_gl_window_gbm_egl_get_window_handle (GstGLWindow * window)
85 {
86   return (guintptr) GST_GL_WINDOW_GBM_EGL (window)->gbm_surf;
87 }
88 
89 
90 static guintptr
gst_gl_window_gbm_egl_get_display(GstGLWindow * window)91 gst_gl_window_gbm_egl_get_display (GstGLWindow * window)
92 {
93   return gst_gl_display_get_handle (window->display);
94 }
95 
96 
97 static void
gst_gl_window_gbm_egl_set_window_handle(G_GNUC_UNUSED GstGLWindow * window,G_GNUC_UNUSED guintptr handle)98 gst_gl_window_gbm_egl_set_window_handle (G_GNUC_UNUSED GstGLWindow * window,
99     G_GNUC_UNUSED guintptr handle)
100 {
101   /* TODO: Currently, it is unclear how to use external GBM buffer objects,
102    * since it is not defined how this would work together with DRM page flips
103    */
104 }
105 
106 
107 static void
gst_gl_window_gbm_egl_close(GstGLWindow * window)108 gst_gl_window_gbm_egl_close (GstGLWindow * window)
109 {
110   GstGLWindowGBMEGL *window_egl = GST_GL_WINDOW_GBM_EGL (window);
111 
112   if (window_egl->saved_crtc) {
113     GstGLDisplayGBM *display = (GstGLDisplayGBM *) window->display;
114     drmModeCrtc *crtc = window_egl->saved_crtc;
115     gint err;
116 
117     err = drmModeSetCrtc (display->drm_fd, crtc->crtc_id, crtc->buffer_id,
118         crtc->x, crtc->y, &(display->drm_mode_connector->connector_id), 1,
119         &crtc->mode);
120     if (err)
121       GST_ERROR_OBJECT (window, "Failed to restore previous CRTC mode: %s",
122           g_strerror (errno));
123 
124     drmModeFreeCrtc (crtc);
125     window_egl->saved_crtc = NULL;
126   }
127 
128   if (window_egl->gbm_surf != NULL) {
129     if (window_egl->current_bo != NULL) {
130       gbm_surface_release_buffer (window_egl->gbm_surf, window_egl->current_bo);
131       window_egl->current_bo = NULL;
132     }
133 
134     gbm_surface_destroy (window_egl->gbm_surf);
135     window_egl->gbm_surf = NULL;
136   }
137 
138   GST_GL_WINDOW_CLASS (gst_gl_window_gbm_egl_parent_class)->close (window);
139 }
140 
141 
142 static void
_page_flip_handler(G_GNUC_UNUSED int fd,G_GNUC_UNUSED unsigned int frame,G_GNUC_UNUSED unsigned int sec,G_GNUC_UNUSED unsigned int usec,void * data)143 _page_flip_handler (G_GNUC_UNUSED int fd, G_GNUC_UNUSED unsigned int frame,
144     G_GNUC_UNUSED unsigned int sec, G_GNUC_UNUSED unsigned int usec, void *data)
145 {
146   /* If we reach this point, it means the page flip has been completed.
147    * Signal this by clearing the flag so the poll() loop in draw_cb()
148    * can exit. */
149   int *waiting_for_flip = data;
150   *waiting_for_flip = 0;
151 }
152 
153 static void
draw_cb(gpointer data)154 draw_cb (gpointer data)
155 {
156   GstGLWindowGBMEGL *window_egl = data;
157   GstGLWindow *window = GST_GL_WINDOW (window_egl);
158   GstGLContext *context = gst_gl_window_get_context (window);
159   GstGLContextClass *context_class = GST_GL_CONTEXT_GET_CLASS (context);
160   GstGLDisplayGBM *display = (GstGLDisplayGBM *) window->display;
161   struct gbm_bo *next_bo;
162   GstGLDRMFramebuffer *framebuf;
163   int ret;
164 
165   drmEventContext evctx = {
166     .version = DRM_EVENT_CONTEXT_VERSION,
167     .page_flip_handler = _page_flip_handler,
168   };
169 
170   struct pollfd pfd = {
171     .fd = display->drm_fd,
172     .events = POLLIN,
173     .revents = 0,
174   };
175 
176   /* No display connected */
177   if (!display->drm_mode_info) {
178     GST_ERROR ("No display connected");
179     gst_object_unref (context);
180     return;
181   };
182 
183   /* Rendering, page flipping etc. are connect this way:
184    *
185    * The frames are stored in buffer objects (BOs). Inside the eglSwapBuffers()
186    * call, GBM creates new BOs if necessary. BOs can be "locked" for rendering,
187    * meaning that EGL cannot use them as a render target. If all available
188    * BOs are locked, the GBM code inside eglSwapBuffers() creates a new,
189    * unlocked one. We make use of this to implement triple buffering.
190    *
191    * There are 3 BOs in play:
192    *
193    * * next_bo: The BO we just rendered into.
194    * * current_bo: The currently displayed BO.
195    * * prev_bo: The previously displayed BO.
196    *
197    * current_bo and prev_bo are involed in page flipping. next_bo is not.
198    *
199    * Once rendering is done, the next_bo is retrieved and locked. Then, we
200    * wait until any ongoing page flipping finishes. Once it does, the
201    * current_bo is displayed on screen, and the prev_bo isn't anymore. At
202    * this point, it is safe to release the prev_bo, which unlocks it and
203    * makes it available again as a render target. Then we initiate the
204    * next page flipping; this time, we flip to next_bo. At that point,
205    * next_bo becomes current_bo, and current_bo becomes prev_bo.
206    */
207 
208   /*
209    * There is a special case at the beginning. There is no currently
210    * displayed BO at first, so we create an empty one to get the page
211    * flipping cycle going. Also, we use this first BO for setting up
212    * the CRTC.
213    */
214   if (window_egl->current_bo == NULL) {
215     /* Call eglSwapBuffers() to create a BO. */
216     context_class->swap_buffers (context);
217 
218     /* Lock the BO so we get our first current_bo. */
219     window_egl->current_bo =
220         gbm_surface_lock_front_buffer (window_egl->gbm_surf);
221     framebuf = gst_gl_gbm_drm_fb_get_from_bo (window_egl->current_bo);
222 
223     /* Save the CRTC state */
224     if (!window_egl->saved_crtc)
225       window_egl->saved_crtc =
226           drmModeGetCrtc (display->drm_fd, display->crtc_id);
227 
228     /* Configure CRTC to show this first BO. */
229     ret = drmModeSetCrtc (display->drm_fd, display->crtc_id, framebuf->fb_id,
230         0, 0, &(display->drm_mode_connector->connector_id), 1,
231         display->drm_mode_info);
232 
233     if (ret != 0) {
234       GST_ERROR ("Could not set DRM CRTC: %s (%d)", g_strerror (errno), errno);
235       gst_object_unref (context);
236       /* XXX: it is not possible to communicate the error to the pipeline */
237       return;
238     }
239   }
240 
241   if (window->queue_resize) {
242     guint width, height;
243 
244     gst_gl_window_get_surface_dimensions (window, &width, &height);
245     gst_gl_window_resize (window, width, height);
246   }
247 
248   /* Do the actual drawing */
249   if (window->draw)
250     window->draw (window->draw_data);
251 
252   /* Let the context class call eglSwapBuffers(). As mentioned above,
253    * if necessary, this function creates a new unlocked framebuffer
254    * that can be used as render target. */
255   context_class->swap_buffers (context);
256   gst_object_unref (context);
257 
258   next_bo = gbm_surface_lock_front_buffer (window_egl->gbm_surf);
259   framebuf = gst_gl_gbm_drm_fb_get_from_bo (next_bo);
260   GST_LOG ("rendered new frame into bo %p", (gpointer) next_bo);
261 
262   /* Wait until any ongoing page flipping is done. After this is done,
263    * prev_bo is no longer involved in any page flipping, and can be
264    * safely released. */
265   while (window_egl->waiting_for_flip) {
266     ret = poll (&pfd, 1, -1);
267     if (ret < 0) {
268       if (errno == EINTR)
269         GST_DEBUG ("Signal caught during poll() call");
270       else
271         GST_ERROR ("poll() failed: %s (%d)", g_strerror (errno), errno);
272       /* XXX: it is not possible to communicate errors and interruptions
273        * to the pipeline */
274       return;
275     }
276 
277     drmHandleEvent (display->drm_fd, &evctx);
278   }
279   GST_LOG ("now showing bo %p", (gpointer) (window_egl->current_bo));
280 
281   /* Release prev_bo, since it is no longer shown on screen. */
282   if (G_LIKELY (window_egl->prev_bo != NULL)) {
283     gbm_surface_release_buffer (window_egl->gbm_surf, window_egl->prev_bo);
284     GST_LOG ("releasing bo %p", (gpointer) (window_egl->prev_bo));
285   }
286 
287   /* Presently, current_bo is shown on screen. Schedule the next page
288    * flip, this time flip to next_bo. The flip happens asynchronously, so
289    * we can continue and render etc. in the meantime. */
290   window_egl->waiting_for_flip = 1;
291   ret = drmModePageFlip (display->drm_fd, display->crtc_id, framebuf->fb_id,
292       DRM_MODE_PAGE_FLIP_EVENT, &(window_egl->waiting_for_flip));
293   if (ret != 0) {
294     /* NOTE: According to libdrm sources, the page is _not_
295      * considered flipped if drmModePageFlip() reports an error,
296      * so we do not update the priv->current_bo pointer here */
297     GST_ERROR ("Could not initialize GBM surface");
298     /* XXX: it is not possible to communicate the error to the pipeline */
299     return;
300   }
301 
302   /* At this point, we relabel the current_bo as the prev_bo.
303    * This may not actually be the case yet, but it will be soon - latest
304    * when the wait loop above finishes.
305    * Also, next_bo becomes current_bo. */
306   window_egl->prev_bo = window_egl->current_bo;
307   window_egl->current_bo = next_bo;
308 }
309 
310 
311 static void
gst_gl_window_gbm_egl_draw(GstGLWindow * window)312 gst_gl_window_gbm_egl_draw (GstGLWindow * window)
313 {
314   gst_gl_window_send_message (window, (GstGLWindowCB) draw_cb, window);
315 }
316 
317 
318 static gboolean
gst_gl_window_gbm_init_surface(GstGLWindowGBMEGL * window_egl)319 gst_gl_window_gbm_init_surface (GstGLWindowGBMEGL * window_egl)
320 {
321   /* NOTE: This function cannot be called in the open() vmethod
322    * since context_egl->egl_display and context_egl->egl_config
323    * must have been set to valid values at this point, and open()
324    * is called _before_ these are set.
325    * Also, eglInitialize() is called _after_ the open() vmethod,
326    * which means that the return value of gbm_surface_create()
327    * contains some function pointers that are set to NULL and
328    * shouldn't be. This is because Mesa's eglInitialize() loads
329    * the DRI2 driver and the relevant functions aren't available
330    * until then.
331    *
332    * Therefore, this function is called instead inside
333    * gst_gl_window_gbm_egl_create_window(), which in turn is
334    * called inside gst_gl_context_egl_create_context(). */
335 
336   GstGLWindow *window = GST_GL_WINDOW (window_egl);
337   GstGLDisplayGBM *display = (GstGLDisplayGBM *) window->display;
338   drmModeModeInfo *drm_mode_info = display->drm_mode_info;
339   GstGLContext *context = gst_gl_window_get_context (window);
340   GstGLContextEGL *context_egl = GST_GL_CONTEXT_EGL (context);
341   EGLint gbm_format;
342   int hdisplay, vdisplay;
343   gboolean ret = TRUE;
344 
345   if (drm_mode_info) {
346     vdisplay = drm_mode_info->vdisplay;
347     hdisplay = drm_mode_info->hdisplay;
348   } else {
349     vdisplay = 0;
350     hdisplay = 0;
351   }
352 
353   /* With GBM-based EGL displays and configs, the native visual ID
354    * is a GBM pixel format. */
355   if (!eglGetConfigAttrib (context_egl->egl_display, context_egl->egl_config,
356           EGL_NATIVE_VISUAL_ID, &gbm_format)) {
357     GST_ERROR ("eglGetConfigAttrib failed: %s",
358         gst_egl_get_error_string (eglGetError ()));
359     ret = FALSE;
360     goto cleanup;
361   }
362 
363   /* Create a GBM surface that shall contain the BOs we are
364    * going to render into. */
365   window_egl->gbm_surf = gbm_surface_create (display->gbm_dev,
366       hdisplay, vdisplay, gbm_format,
367       GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING);
368 
369   gst_gl_window_resize (window, hdisplay, vdisplay);
370   gst_gl_window_queue_resize (window);
371 
372   GST_DEBUG ("Successfully created GBM surface %ix%i from info %p", hdisplay,
373       vdisplay, drm_mode_info);
374 
375 cleanup:
376 
377   gst_object_unref (context);
378   return ret;
379 }
380 
381 /* Must be called in the gl thread */
382 GstGLWindowGBMEGL *
gst_gl_window_gbm_egl_new(GstGLDisplay * display)383 gst_gl_window_gbm_egl_new (GstGLDisplay * display)
384 {
385   GstGLWindowGBMEGL *window_egl;
386 
387   if ((gst_gl_display_get_handle_type (display) & GST_GL_DISPLAY_TYPE_GBM) == 0)
388     /* we require a GBM display to create windows */
389     return NULL;
390 
391   window_egl = g_object_new (GST_TYPE_GL_WINDOW_GBM_EGL, NULL);
392 
393   return window_egl;
394 }
395 
396 
397 gboolean
gst_gl_window_gbm_egl_create_window(GstGLWindowGBMEGL * window_egl)398 gst_gl_window_gbm_egl_create_window (GstGLWindowGBMEGL * window_egl)
399 {
400   return gst_gl_window_gbm_init_surface (window_egl);
401 }
402