3 * Copyright (C) 2018 Carlos Rafael Giani <dv@pseudoterminal.org>
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.
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.
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.
26 #include "../gstgl_fwd.h"
27 #include <gst/gl/gstglcontext.h>
28 #include <gst/gl/egl/gstglcontext_egl.h>
30 #include "gstgldisplay_gbm.h"
31 #include "gstglwindow_gbm_egl.h"
32 #include "gstgl_gbm_utils.h"
33 #include "../gstglwindow_private.h"
35 #define GST_CAT_DEFAULT gst_gl_window_debug
38 #define GST_GL_WINDOW_GBM_EGL_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
39 GST_TYPE_GL_WINDOW_GBM_EGL, GstGLWindowGBMEGLPrivate))
42 G_DEFINE_TYPE (GstGLWindowGBMEGL, gst_gl_window_gbm_egl, GST_TYPE_GL_WINDOW);
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,
49 static void gst_gl_window_gbm_egl_close (GstGLWindow * window);
50 static void gst_gl_window_gbm_egl_draw (GstGLWindow * window);
52 static gboolean gst_gl_window_gbm_init_surface (GstGLWindowGBMEGL * window_egl);
56 gst_gl_window_gbm_egl_class_init (GstGLWindowGBMEGLClass * klass)
58 GstGLWindowClass *window_class = (GstGLWindowClass *) klass;
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);
69 /* TODO: add support for set_render_rectangle (assuming this functionality
70 * is possible with libdrm/gbm) */
75 gst_gl_window_gbm_egl_init (GstGLWindowGBMEGL * window_gbm)
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;
85 gst_gl_window_gbm_egl_get_window_handle (GstGLWindow * window)
87 return (guintptr) GST_GL_WINDOW_GBM_EGL (window)->gbm_surf;
92 gst_gl_window_gbm_egl_get_display (GstGLWindow * window)
94 return gst_gl_display_get_handle (window->display);
99 gst_gl_window_gbm_egl_set_window_handle (G_GNUC_UNUSED GstGLWindow * window,
100 G_GNUC_UNUSED guintptr handle)
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
109 gst_gl_window_gbm_egl_close (GstGLWindow * window)
111 GstGLWindowGBMEGL *window_egl = GST_GL_WINDOW_GBM_EGL (window);
113 if (window_egl->saved_crtc) {
114 GstGLDisplayGBM *display = (GstGLDisplayGBM *) window->display;
115 drmModeCrtc *crtc = window_egl->saved_crtc;
118 err = drmModeSetCrtc (display->drm_fd, crtc->crtc_id, crtc->buffer_id,
119 crtc->x, crtc->y, &(display->drm_mode_connector->connector_id), 1,
122 GST_ERROR_OBJECT (window, "Failed to restore previous CRTC mode: %s",
125 drmModeFreeCrtc (crtc);
126 window_egl->saved_crtc = NULL;
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;
135 gbm_surface_destroy (window_egl->gbm_surf);
136 window_egl->gbm_surf = NULL;
139 GST_GL_WINDOW_CLASS (gst_gl_window_gbm_egl_parent_class)->close (window);
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)
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()
150 int *waiting_for_flip = data;
151 *waiting_for_flip = 0;
155 draw_cb (gpointer data)
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;
166 drmEventContext evctx = {
167 .version = DRM_EVENT_CONTEXT_VERSION,
168 .page_flip_handler = _page_flip_handler,
171 struct pollfd pfd = {
172 .fd = display->drm_fd,
177 /* No display connected */
178 if (!display->drm_mode_info) {
179 GST_ERROR ("No display connected");
180 gst_object_unref (context);
184 /* Rendering, page flipping etc. are connect this way:
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.
192 * There are 3 BOs in play:
194 * * next_bo: The BO we just rendered into.
195 * * current_bo: The currently displayed BO.
196 * * prev_bo: The previously displayed BO.
198 * current_bo and prev_bo are involed in page flipping. next_bo is not.
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.
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
215 if (window_egl->current_bo == NULL) {
216 /* Call eglSwapBuffers() to create a BO. */
217 context_class->swap_buffers (context);
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);
224 /* Save the CRTC state */
225 if (!window_egl->saved_crtc)
226 window_egl->saved_crtc =
227 drmModeGetCrtc (display->drm_fd, display->crtc_id);
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);
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 */
242 /* Do the actual drawing */
244 window->draw (window->draw_data);
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);
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);
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);
263 GST_DEBUG ("Signal caught during poll() call");
265 GST_ERROR ("poll() failed: %s (%d)", g_strerror (errno), errno);
266 /* XXX: it is not possible to communicate errors and interruptions
271 drmHandleEvent (display->drm_fd, &evctx);
273 GST_LOG ("now showing bo %p", (gpointer) (window_egl->current_bo));
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));
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));
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 */
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;
306 gst_gl_window_gbm_egl_draw (GstGLWindow * window)
308 gst_gl_window_send_message (window, (GstGLWindowCB) draw_cb, window);
313 gst_gl_window_gbm_init_surface (GstGLWindowGBMEGL * window_egl)
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
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(). */
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);
336 int hdisplay, vdisplay;
340 vdisplay = drm_mode_info->vdisplay;
341 hdisplay = drm_mode_info->hdisplay;
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 ()));
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);
363 gst_gl_window_resize (window, hdisplay, vdisplay);
365 GST_DEBUG ("Successfully created GBM surface");
369 gst_object_unref (context);
374 /* Must be called in the gl thread */
376 gst_gl_window_gbm_egl_new (GstGLDisplay * display)
378 GstGLWindowGBMEGL *window_egl;
380 if ((gst_gl_display_get_handle_type (display) & GST_GL_DISPLAY_TYPE_GBM) == 0)
381 /* we require a GBM display to create windows */
384 window_egl = g_object_new (GST_TYPE_GL_WINDOW_GBM_EGL, NULL);
391 gst_gl_window_gbm_egl_create_window (GstGLWindowGBMEGL * window_egl)
393 return gst_gl_window_gbm_init_surface (window_egl);