gstglwindow_gbm_egl.c 12.4 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
 * GStreamer
 * Copyright (C) 2018 Carlos Rafael Giani <dv@pseudoterminal.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */
20
21
22
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154

#include <poll.h>

#include "../gstgl_fwd.h"
#include <gst/gl/gstglcontext.h>
#include <gst/gl/egl/gstglcontext_egl.h>

#include "gstgldisplay_gbm.h"
#include "gstglwindow_gbm_egl.h"
#include "gstgl_gbm_utils.h"
#include "../gstglwindow_private.h"

#define GST_CAT_DEFAULT gst_gl_window_debug


#define GST_GL_WINDOW_GBM_EGL_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
    GST_TYPE_GL_WINDOW_GBM_EGL, GstGLWindowGBMEGLPrivate))


G_DEFINE_TYPE (GstGLWindowGBMEGL, gst_gl_window_gbm_egl, GST_TYPE_GL_WINDOW);


static guintptr gst_gl_window_gbm_egl_get_window_handle (GstGLWindow * window);
static guintptr gst_gl_window_gbm_egl_get_display (GstGLWindow * window);
static void gst_gl_window_gbm_egl_set_window_handle (GstGLWindow * window,
    guintptr handle);
static void gst_gl_window_gbm_egl_close (GstGLWindow * window);
static void gst_gl_window_gbm_egl_draw (GstGLWindow * window);

static gboolean gst_gl_window_gbm_init_surface (GstGLWindowGBMEGL * window_egl);
static void gst_gl_window_gbm_egl_cleanup (GstGLWindowGBMEGL * window_egl);



static void
gst_gl_window_gbm_egl_class_init (GstGLWindowGBMEGLClass * klass)
{
  GstGLWindowClass *window_class = (GstGLWindowClass *) klass;

  window_class->get_window_handle =
      GST_DEBUG_FUNCPTR (gst_gl_window_gbm_egl_get_window_handle);
  window_class->get_display =
      GST_DEBUG_FUNCPTR (gst_gl_window_gbm_egl_get_display);
  window_class->set_window_handle =
      GST_DEBUG_FUNCPTR (gst_gl_window_gbm_egl_set_window_handle);
  window_class->close = GST_DEBUG_FUNCPTR (gst_gl_window_gbm_egl_close);
  window_class->draw = GST_DEBUG_FUNCPTR (gst_gl_window_gbm_egl_draw);

  /* TODO: add support for set_render_rectangle (assuming this functionality
   * is possible with libdrm/gbm) */
}


static void
gst_gl_window_gbm_egl_init (GstGLWindowGBMEGL * window_gbm)
{
  window_gbm->gbm_surf = NULL;
  window_gbm->current_bo = NULL;
  window_gbm->prev_bo = NULL;
  window_gbm->waiting_for_flip = 0;
}


static guintptr
gst_gl_window_gbm_egl_get_window_handle (GstGLWindow * window)
{
  return (guintptr) GST_GL_WINDOW_GBM_EGL (window)->gbm_surf;
}


static guintptr
gst_gl_window_gbm_egl_get_display (GstGLWindow * window)
{
  return gst_gl_display_get_handle (window->display);
}


static void
gst_gl_window_gbm_egl_set_window_handle (G_GNUC_UNUSED GstGLWindow * window,
    G_GNUC_UNUSED guintptr handle)
{
  /* TODO: Currently, it is unclear how to use external GBM buffer objects,
   * since it is not defined how this would work together with DRM page flips
   */
}


static void
gst_gl_window_gbm_egl_close (GstGLWindow * window)
{
  GstGLWindowGBMEGL *window_egl = GST_GL_WINDOW_GBM_EGL (window);

  gst_gl_window_gbm_egl_cleanup (window_egl);

  GST_GL_WINDOW_CLASS (gst_gl_window_gbm_egl_parent_class)->close (window);
}


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)
{
  /* If we reach this point, it means the page flip has been completed.
   * Signal this by clearing the flag so the poll() loop in draw_cb()
   * can exit. */
  int *waiting_for_flip = data;
  *waiting_for_flip = 0;
}

static void
draw_cb (gpointer data)
{
  GstGLWindowGBMEGL *window_egl = data;
  GstGLWindow *window = GST_GL_WINDOW (window_egl);
  GstGLContext *context = gst_gl_window_get_context (window);
  GstGLContextClass *context_class = GST_GL_CONTEXT_GET_CLASS (context);
  GstGLDisplayGBM *display = (GstGLDisplayGBM *) window->display;
  struct gbm_bo *next_bo;
  GstGLDRMFramebuffer *framebuf;
  int ret;

  drmEventContext evctx = {
    .version = DRM_EVENT_CONTEXT_VERSION,
    .page_flip_handler = _page_flip_handler,
  };

  struct pollfd pfd = {
    .fd = display->drm_fd,
    .events = POLLIN,
    .revents = 0,
  };

freyr's avatar
freyr committed
155
156
157
  /* No display connected */
  if (!display->drm_mode_info) {
    GST_ERROR ("No display connected");
158
    gst_object_unref (context);
freyr's avatar
freyr committed
159
160
161
    return;
  };

162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
  /* Rendering, page flipping etc. are connect this way:
   *
   * The frames are stored in buffer objects (BOs). Inside the eglSwapBuffers()
   * call, GBM creates new BOs if necessary. BOs can be "locked" for rendering,
   * meaning that EGL cannot use them as a render target. If all available
   * BOs are locked, the GBM code inside eglSwapBuffers() creates a new,
   * unlocked one. We make use of this to implement triple buffering.
   *
   * There are 3 BOs in play:
   *
   * * next_bo: The BO we just rendered into.
   * * current_bo: The currently displayed BO.
   * * prev_bo: The previously displayed BO.
   *
   * current_bo and prev_bo are involed in page flipping. next_bo is not.
   *
   * Once rendering is done, the next_bo is retrieved and locked. Then, we 
   * wait until any ongoing page flipping finishes. Once it does, the
   * current_bo is displayed on screen, and the prev_bo isn't anymore. At
   * this point, it is safe to release the prev_bo, which unlocks it and
   * makes it available again as a render target. Then we initiate the
   * next page flipping; this time, we flip to next_bo. At that point,
   * next_bo becomes current_bo, and current_bo becomes prev_bo.
   */

  /*
   * There is a special case at the beginning. There is no currently
   * displayed BO at first, so we create an empty one to get the page
   * flipping cycle going. Also, we use this first BO for setting up
   * the CRTC.
   */
  if (window_egl->current_bo == NULL) {
    /* Call eglSwapBuffers() to create a BO. */
    context_class->swap_buffers (context);

    /* Lock the BO so we get our first current_bo. */
    window_egl->current_bo =
        gbm_surface_lock_front_buffer (window_egl->gbm_surf);
    framebuf = gst_gl_gbm_drm_fb_get_from_bo (window_egl->current_bo);

    /* Configure CRTC to show this first BO. */
    ret = drmModeSetCrtc (display->drm_fd, display->crtc_id, framebuf->fb_id,
        0, 0, &(display->drm_mode_connector->connector_id), 1,
        display->drm_mode_info);

    if (ret != 0) {
      GST_ERROR ("Could not set DRM CRTC: %s (%d)", g_strerror (errno), errno);
209
      gst_object_unref (context);
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
      /* XXX: it is not possible to communicate the error to the pipeline */
      return;
    }
  }

  /* Do the actual drawing */
  if (window->draw)
    window->draw (window->draw_data);

  /* Let the context class call eglSwapBuffers(). As mentioned above,
   * if necessary, this function creates a new unlocked framebuffer
   * that can be used as render target. */
  context_class->swap_buffers (context);
  gst_object_unref (context);

  next_bo = gbm_surface_lock_front_buffer (window_egl->gbm_surf);
  framebuf = gst_gl_gbm_drm_fb_get_from_bo (next_bo);
  GST_LOG ("rendered new frame into bo %p", (gpointer) next_bo);

  /* Wait until any ongoing page flipping is done. After this is done,
   * prev_bo is no longer involved in any page flipping, and can be
   * safely released. */
  while (window_egl->waiting_for_flip) {
    ret = poll (&pfd, 1, -1);
    if (ret < 0) {
      if (errno == EINTR)
        GST_DEBUG ("Signal caught during poll() call");
      else
        GST_ERROR ("poll() failed: %s (%d)", g_strerror (errno), errno);
      /* XXX: it is not possible to communicate errors and interruptions
       * to the pipeline */
      return;
    }

    drmHandleEvent (display->drm_fd, &evctx);
  }
  GST_LOG ("now showing bo %p", (gpointer) (window_egl->current_bo));

  /* Release prev_bo, since it is no longer shown on screen. */
  if (G_LIKELY (window_egl->prev_bo != NULL)) {
    gbm_surface_release_buffer (window_egl->gbm_surf, window_egl->prev_bo);
    GST_LOG ("releasing bo %p", (gpointer) (window_egl->prev_bo));
  }

  /* Presently, current_bo is shown on screen. Schedule the next page
   * flip, this time flip to next_bo. The flip happens asynchronously, so
   * we can continue and render etc. in the meantime. */
  window_egl->waiting_for_flip = 1;
  ret = drmModePageFlip (display->drm_fd, display->crtc_id, framebuf->fb_id,
      DRM_MODE_PAGE_FLIP_EVENT, &(window_egl->waiting_for_flip));
  if (ret != 0) {
    /* NOTE: According to libdrm sources, the page is _not_
     * considered flipped if drmModePageFlip() reports an error,
     * so we do not update the priv->current_bo pointer here */
    GST_ERROR ("Could not initialize GBM surface");
    /* XXX: it is not possible to communicate the error to the pipeline */
    return;
  }

  /* At this point, we relabel the current_bo as the prev_bo.
   * This may not actually be the case yet, but it will be soon - latest
   * when the wait loop above finishes.
   * Also, next_bo becomes current_bo. */
  window_egl->prev_bo = window_egl->current_bo;
  window_egl->current_bo = next_bo;
}


static void
gst_gl_window_gbm_egl_draw (GstGLWindow * window)
{
  gst_gl_window_send_message (window, (GstGLWindowCB) draw_cb, window);
}


static gboolean
gst_gl_window_gbm_init_surface (GstGLWindowGBMEGL * window_egl)
{
  /* NOTE: This function cannot be called in the open() vmethod
   * since context_egl->egl_display and context_egl->egl_config
   * must have been set to valid values at this point, and open()
   * is called _before_ these are set.
   * Also, eglInitialize() is called _after_ the open() vmethod,
   * which means that the return value of gbm_surface_create()
   * contains some function pointers that are set to NULL and
   * shouldn't be. This is because Mesa's eglInitialize() loads
   * the DRI2 driver and the relevant functions aren't available
297
298
299
300
301
   * until then.
   *
   * Therefore, this function is called instead inside
   * gst_gl_window_gbm_egl_create_window(), which in turn is
   * called inside gst_gl_context_egl_create_context(). */
302
303
304
305
306
307
308

  GstGLWindow *window = GST_GL_WINDOW (window_egl);
  GstGLDisplayGBM *display = (GstGLDisplayGBM *) window->display;
  drmModeModeInfo *drm_mode_info = display->drm_mode_info;
  GstGLContext *context = gst_gl_window_get_context (window);
  GstGLContextEGL *context_egl = GST_GL_CONTEXT_EGL (context);
  EGLint gbm_format;
freyr's avatar
freyr committed
309
  int hdisplay, vdisplay;
310
  gboolean ret = TRUE;
freyr's avatar
freyr committed
311
312
313
314
315
316
317
318

  if (drm_mode_info) {
    vdisplay = drm_mode_info->vdisplay;
    hdisplay = drm_mode_info->hdisplay;
  } else {
    vdisplay = 0;
    hdisplay = 0;
  }
319
320
321
322
323
324
325

  /* With GBM-based EGL displays and configs, the native visual ID
   * is a GBM pixel format. */
  if (!eglGetConfigAttrib (context_egl->egl_display, context_egl->egl_config,
          EGL_NATIVE_VISUAL_ID, &gbm_format)) {
    GST_ERROR ("eglGetConfigAttrib failed: %s",
        gst_egl_get_error_string (eglGetError ()));
326
327
    ret = FALSE;
    goto cleanup;
328
329
330
331
332
  }

  /* Create a GBM surface that shall contain the BOs we are
   * going to render into. */
  window_egl->gbm_surf = gbm_surface_create (display->gbm_dev,
freyr's avatar
freyr committed
333
      hdisplay, vdisplay, gbm_format,
334
335
      GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING);

freyr's avatar
freyr committed
336
  gst_gl_window_resize (window, hdisplay, vdisplay);
337
338
339

  GST_DEBUG ("Successfully created GBM surface");

340
341
342
343
cleanup:

  gst_object_unref (context);
  return ret;
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
}


static void
gst_gl_window_gbm_egl_cleanup (GstGLWindowGBMEGL * window_egl)
{
  if (window_egl->gbm_surf != NULL) {
    if (window_egl->current_bo != NULL) {
      gbm_surface_release_buffer (window_egl->gbm_surf, window_egl->current_bo);
      window_egl->current_bo = NULL;
    }

    gbm_surface_destroy (window_egl->gbm_surf);
    window_egl->gbm_surf = NULL;
  }
}


/* Must be called in the gl thread */
GstGLWindowGBMEGL *
gst_gl_window_gbm_egl_new (GstGLDisplay * display)
{
  GstGLWindowGBMEGL *window_egl;

  if ((gst_gl_display_get_handle_type (display) & GST_GL_DISPLAY_TYPE_GBM) == 0)
    /* we require a GBM display to create windows */
    return NULL;

  window_egl = g_object_new (GST_TYPE_GL_WINDOW_GBM_EGL, NULL);

  return window_egl;
}
376
377
378
379
380
381
382


gboolean
gst_gl_window_gbm_egl_create_window (GstGLWindowGBMEGL * window_egl)
{
  return gst_gl_window_gbm_init_surface (window_egl);
}