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