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