mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-19 00:01:23 +00:00
4df219f336
This makes it possible to use the GStreamer OpenGL elements without a windowing system if a libdrm- and Mesa3D-supported GPU is present https://bugzilla.gnome.org/show_bug.cgi?id=782923
357 lines
12 KiB
C
357 lines
12 KiB
C
/*
|
|
* 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.
|
|
*/
|
|
|
|
#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)
|
|
{
|
|
GstGLWindowGBMEGL *window_egl = GST_GL_WINDOW_GBM_EGL (window);
|
|
|
|
/* This function is called in here, and not in the open()
|
|
* vmethod. The reason for this is explained inside the
|
|
* gst_gl_window_gbm_init_surface() function. */
|
|
if (window_egl->gbm_surf == NULL) {
|
|
if (!gst_gl_window_gbm_init_surface (window_egl))
|
|
return 0;
|
|
}
|
|
|
|
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,
|
|
};
|
|
|
|
/* 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);
|
|
/* 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
|
|
* until then. */
|
|
|
|
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;
|
|
|
|
/* 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 ()));
|
|
return FALSE;
|
|
}
|
|
|
|
/* 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,
|
|
drm_mode_info->hdisplay, drm_mode_info->vdisplay, gbm_format,
|
|
GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING);
|
|
|
|
gst_gl_window_resize (window, drm_mode_info->hdisplay,
|
|
drm_mode_info->vdisplay);
|
|
|
|
GST_DEBUG ("Successfully created GBM surface");
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
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;
|
|
}
|