From: Carlos Rafael Giani Date: Fri, 23 Feb 2018 18:59:45 +0000 (+0100) Subject: gl: Add Mesa3D GBM backend X-Git-Tag: 1.19.3~511^2~1839 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=4df219f3362a95c87c746641ca6c491c45b83e0d;p=platform%2Fupstream%2Fgstreamer.git gl: Add Mesa3D GBM backend 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 --- diff --git a/configure.ac b/configure.ac index eee94a4..f860d9e 100644 --- a/configure.ac +++ b/configure.ac @@ -951,6 +951,7 @@ gst-libs/gst/gl/wayland/Makefile gst-libs/gst/gl/win32/Makefile gst-libs/gst/gl/x11/Makefile gst-libs/gst/gl/viv-fb/Makefile +gst-libs/gst/gl/gbm/Makefile gst-libs/gst/riff/Makefile gst-libs/gst/rtp/Makefile gst-libs/gst/rtsp/Makefile diff --git a/gst-libs/gst/gl/Makefile.am b/gst-libs/gst/gl/Makefile.am index 9023725..ca476e7 100644 --- a/gst-libs/gst/gl/Makefile.am +++ b/gst-libs/gst/gl/Makefile.am @@ -134,6 +134,11 @@ SUBDIRS += viv-fb libgstgl_@GST_API_VERSION@_la_LIBADD += viv-fb/libgstgl-viv-fb.la endif +if HAVE_WINDOW_GBM +SUBDIRS += gbm +libgstgl_@GST_API_VERSION@_la_LIBADD += gbm/libgstgl-gbm.la +endif + if USE_EGL SUBDIRS += egl libgstgl_@GST_API_VERSION@_la_LIBADD += egl/libgstgl-egl.la diff --git a/gst-libs/gst/gl/egl/gstgldisplay_egl.c b/gst-libs/gst/gl/egl/gstgldisplay_egl.c index 81f9794..b2265dc 100644 --- a/gst-libs/gst/gl/egl/gstgldisplay_egl.c +++ b/gst-libs/gst/gl/egl/gstgldisplay_egl.c @@ -150,6 +150,14 @@ gst_gl_display_egl_get_from_native (GstGLDisplayType type, guintptr display) NULL); } #endif +#if GST_GL_HAVE_WINDOW_GBM + if (ret == EGL_NO_DISPLAY && (type & GST_GL_DISPLAY_TYPE_GBM) && + (gst_gl_check_extension ("EGL_MESA_platform_gbm", egl_exts) || + gst_gl_check_extension ("EGL_MESA_platform_gbm", egl_exts))) { + ret = _gst_eglGetPlatformDisplay (EGL_PLATFORM_GBM_MESA, (gpointer) display, + NULL); + } +#endif /* android only has one winsys/display connection */ if (ret != EGL_NO_DISPLAY) diff --git a/gst-libs/gst/gl/gbm/Makefile.am b/gst-libs/gst/gl/gbm/Makefile.am new file mode 100644 index 0000000..0abf688 --- /dev/null +++ b/gst-libs/gst/gl/gbm/Makefile.am @@ -0,0 +1,31 @@ +## Process this file with automake to produce Makefile.in + +noinst_LTLIBRARIES = libgstgl-gbm.la + +libgstgl_gbm_la_SOURCES = \ + gstgl_gbm_utils.c \ + gstgldisplay_gbm.c \ + gstglwindow_gbm_egl.c + +noinst_HEADERS = \ + gstgl_gbm_utils.h \ + gstgldisplay_gbm.h \ + gstglwindow_gbm_egl.h + +libgstgl_gbm_la_CFLAGS = \ + -I$(top_srcdir)/gst-libs \ + -I$(top_builddir)/gst-libs \ + $(GL_CFLAGS) \ + $(GST_PLUGINS_BASE_CFLAGS) \ + $(GST_BASE_CFLAGS) \ + $(GST_CFLAGS) \ + $(DRM_CFLAGS) \ + $(GBM_CFLAGS) \ + $(G_UDEV_CFLAGS) + +libgstgl_gbm_la_LDFLAGS = \ + $(GST_LIB_LDFLAGS) \ + $(GST_ALL_LDFLAGS) \ + $(DRM_LIBS) \ + $(GBM_LIBS) \ + $(G_UDEV_LIBS) diff --git a/gst-libs/gst/gl/gbm/gstgl_gbm_utils.c b/gst-libs/gst/gl/gbm/gstgl_gbm_utils.c new file mode 100644 index 0000000..2537b42 --- /dev/null +++ b/gst-libs/gst/gl/gbm/gstgl_gbm_utils.c @@ -0,0 +1,529 @@ +/* + * GStreamer + * Copyright (C) 2018 Carlos Rafael Giani + * + * 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 +#include +#include +#include "gstgl_gbm_utils.h" + +GST_DEBUG_CATEGORY_EXTERN (gst_gl_gbm_debug); +#define GST_CAT_DEFAULT gst_gl_gbm_debug + + +const gchar * +gst_gl_gbm_get_name_for_drm_connector (drmModeConnector * connector) +{ + g_assert (connector != NULL); + + switch (connector->connector_type) { + case DRM_MODE_CONNECTOR_Unknown: + return "Unknown"; + case DRM_MODE_CONNECTOR_VGA: + return "VGA"; + case DRM_MODE_CONNECTOR_DVII: + return "DVI-I"; + case DRM_MODE_CONNECTOR_DVID: + return "DVI-D"; + case DRM_MODE_CONNECTOR_DVIA: + return "DVI-A"; + case DRM_MODE_CONNECTOR_Composite: + return "Composite"; + case DRM_MODE_CONNECTOR_SVIDEO: + return "S-Video"; + case DRM_MODE_CONNECTOR_LVDS: + return "LVDS"; + case DRM_MODE_CONNECTOR_Component: + return "Component"; + case DRM_MODE_CONNECTOR_9PinDIN: + return "9-Pin DIN"; + case DRM_MODE_CONNECTOR_DisplayPort: + return "DisplayPort"; + case DRM_MODE_CONNECTOR_HDMIA: + return "HDMI-A"; + case DRM_MODE_CONNECTOR_HDMIB: + return "HDMI-B"; + case DRM_MODE_CONNECTOR_TV: + return "TV"; + case DRM_MODE_CONNECTOR_eDP: + return "eDP"; + case DRM_MODE_CONNECTOR_VIRTUAL: + return "Virtual"; + case DRM_MODE_CONNECTOR_DSI: + return "DSI"; + default: + return ""; + } +} + + +const gchar * +gst_gl_gbm_get_name_for_drm_encoder (drmModeEncoder * encoder) +{ + switch (encoder->encoder_type) { + case DRM_MODE_ENCODER_NONE: + return "none"; + case DRM_MODE_ENCODER_DAC: + return "DAC"; + case DRM_MODE_ENCODER_TMDS: + return "TMDS"; + case DRM_MODE_ENCODER_LVDS: + return "LVDS"; + case DRM_MODE_ENCODER_TVDAC: + return "TVDAC"; + case DRM_MODE_ENCODER_VIRTUAL: + return "Virtual"; + case DRM_MODE_ENCODER_DSI: + return "DSI"; + default: + return ""; + } +} + + +const gchar * +gst_gl_gbm_format_to_string (guint32 format) +{ + if (format == GBM_BO_FORMAT_XRGB8888) + format = GBM_FORMAT_XRGB8888; + if (format == GBM_BO_FORMAT_ARGB8888) + format = GBM_FORMAT_ARGB8888; + + switch (format) { + case GBM_FORMAT_C8: + return "C8"; + case GBM_FORMAT_RGB332: + return "RGB332"; + case GBM_FORMAT_BGR233: + return "BGR233"; + case GBM_FORMAT_NV12: + return "NV12"; + case GBM_FORMAT_XRGB4444: + return "XRGB4444"; + case GBM_FORMAT_XBGR4444: + return "XBGR4444"; + case GBM_FORMAT_RGBX4444: + return "RGBX4444"; + case GBM_FORMAT_BGRX4444: + return "BGRX4444"; + case GBM_FORMAT_XRGB1555: + return "XRGB1555"; + case GBM_FORMAT_XBGR1555: + return "XBGR1555"; + case GBM_FORMAT_RGBX5551: + return "RGBX5551"; + case GBM_FORMAT_BGRX5551: + return "BGRX5551"; + case GBM_FORMAT_ARGB4444: + return "ARGB4444"; + case GBM_FORMAT_ABGR4444: + return "ABGR4444"; + case GBM_FORMAT_RGBA4444: + return "RGBA4444"; + case GBM_FORMAT_BGRA4444: + return "BGRA4444"; + case GBM_FORMAT_ARGB1555: + return "ARGB1555"; + case GBM_FORMAT_ABGR1555: + return "ABGR1555"; + case GBM_FORMAT_RGBA5551: + return "RGBA5551"; + case GBM_FORMAT_BGRA5551: + return "BGRA5551"; + case GBM_FORMAT_RGB565: + return "RGB565"; + case GBM_FORMAT_BGR565: + return "BGR565"; + case GBM_FORMAT_YUYV: + return "YUYV"; + case GBM_FORMAT_YVYU: + return "YVYU"; + case GBM_FORMAT_UYVY: + return "UYVY"; + case GBM_FORMAT_VYUY: + return "VYUY"; + case GBM_FORMAT_RGB888: + return "RGB888"; + case GBM_FORMAT_BGR888: + return "BGR888"; + case GBM_FORMAT_XRGB8888: + return "XRGB8888"; + case GBM_FORMAT_XBGR8888: + return "XBGR8888"; + case GBM_FORMAT_RGBX8888: + return "RGBX8888"; + case GBM_FORMAT_BGRX8888: + return "BGRX8888"; + case GBM_FORMAT_AYUV: + return "AYUV"; + case GBM_FORMAT_XRGB2101010: + return "XRGB2101010"; + case GBM_FORMAT_XBGR2101010: + return "XBGR2101010"; + case GBM_FORMAT_RGBX1010102: + return "RGBX1010102"; + case GBM_FORMAT_BGRX1010102: + return "BGRX1010102"; + case GBM_FORMAT_ARGB8888: + return "ARGB8888"; + case GBM_FORMAT_ABGR8888: + return "ABGR8888"; + case GBM_FORMAT_RGBA8888: + return "RGBA8888"; + case GBM_FORMAT_BGRA8888: + return "BGRA8888"; + case GBM_FORMAT_ARGB2101010: + return "ARGB2101010"; + case GBM_FORMAT_ABGR2101010: + return "ABGR2101010"; + case GBM_FORMAT_RGBA1010102: + return "RGBA1010102"; + case GBM_FORMAT_BGRA1010102: + return "BGRA1010102"; + + default: + return ""; + } + + return NULL; +} + + +int +gst_gl_gbm_depth_from_format (guint32 format) +{ + if (format == GBM_BO_FORMAT_XRGB8888) + format = GBM_FORMAT_XRGB8888; + if (format == GBM_BO_FORMAT_ARGB8888) + format = GBM_FORMAT_ARGB8888; + + switch (format) { + case GBM_FORMAT_C8: + case GBM_FORMAT_RGB332: + case GBM_FORMAT_BGR233: + return 8; + + case GBM_FORMAT_NV12: + case GBM_FORMAT_XRGB4444: + case GBM_FORMAT_XBGR4444: + case GBM_FORMAT_RGBX4444: + case GBM_FORMAT_BGRX4444: + return 12; + + case GBM_FORMAT_XRGB1555: + case GBM_FORMAT_XBGR1555: + case GBM_FORMAT_RGBX5551: + case GBM_FORMAT_BGRX5551: + return 15; + + case GBM_FORMAT_ARGB4444: + case GBM_FORMAT_ABGR4444: + case GBM_FORMAT_RGBA4444: + case GBM_FORMAT_BGRA4444: + case GBM_FORMAT_ARGB1555: + case GBM_FORMAT_ABGR1555: + case GBM_FORMAT_RGBA5551: + case GBM_FORMAT_BGRA5551: + case GBM_FORMAT_RGB565: + case GBM_FORMAT_BGR565: + case GBM_FORMAT_YUYV: + case GBM_FORMAT_YVYU: + case GBM_FORMAT_UYVY: + case GBM_FORMAT_VYUY: + return 16; + + case GBM_FORMAT_RGB888: + case GBM_FORMAT_BGR888: + case GBM_FORMAT_XRGB8888: + case GBM_FORMAT_XBGR8888: + case GBM_FORMAT_RGBX8888: + case GBM_FORMAT_BGRX8888: + case GBM_FORMAT_AYUV: + return 24; + + case GBM_FORMAT_XRGB2101010: + case GBM_FORMAT_XBGR2101010: + case GBM_FORMAT_RGBX1010102: + case GBM_FORMAT_BGRX1010102: + return 30; + + case GBM_FORMAT_ARGB8888: + case GBM_FORMAT_ABGR8888: + case GBM_FORMAT_RGBA8888: + case GBM_FORMAT_BGRA8888: + case GBM_FORMAT_ARGB2101010: + case GBM_FORMAT_ABGR2101010: + case GBM_FORMAT_RGBA1010102: + case GBM_FORMAT_BGRA1010102: + return 32; + + default: + GST_ERROR ("unknown GBM format %" G_GUINT32_FORMAT, format); + } + + return 0; +} + + +int +gst_gl_gbm_bpp_from_format (guint32 format) +{ + if (format == GBM_BO_FORMAT_XRGB8888) + format = GBM_FORMAT_XRGB8888; + if (format == GBM_BO_FORMAT_ARGB8888) + format = GBM_FORMAT_ARGB8888; + + switch (format) { + case GBM_FORMAT_C8: + case GBM_FORMAT_RGB332: + case GBM_FORMAT_BGR233: + return 8; + + case GBM_FORMAT_NV12: + return 12; + + case GBM_FORMAT_XRGB4444: + case GBM_FORMAT_XBGR4444: + case GBM_FORMAT_RGBX4444: + case GBM_FORMAT_BGRX4444: + case GBM_FORMAT_ARGB4444: + case GBM_FORMAT_ABGR4444: + case GBM_FORMAT_RGBA4444: + case GBM_FORMAT_BGRA4444: + case GBM_FORMAT_XRGB1555: + case GBM_FORMAT_XBGR1555: + case GBM_FORMAT_RGBX5551: + case GBM_FORMAT_BGRX5551: + case GBM_FORMAT_ARGB1555: + case GBM_FORMAT_ABGR1555: + case GBM_FORMAT_RGBA5551: + case GBM_FORMAT_BGRA5551: + case GBM_FORMAT_RGB565: + case GBM_FORMAT_BGR565: + case GBM_FORMAT_YUYV: + case GBM_FORMAT_YVYU: + case GBM_FORMAT_UYVY: + case GBM_FORMAT_VYUY: + return 16; + + case GBM_FORMAT_RGB888: + case GBM_FORMAT_BGR888: + return 24; + + case GBM_FORMAT_XRGB8888: + case GBM_FORMAT_XBGR8888: + case GBM_FORMAT_RGBX8888: + case GBM_FORMAT_BGRX8888: + case GBM_FORMAT_ARGB8888: + case GBM_FORMAT_ABGR8888: + case GBM_FORMAT_RGBA8888: + case GBM_FORMAT_BGRA8888: + case GBM_FORMAT_XRGB2101010: + case GBM_FORMAT_XBGR2101010: + case GBM_FORMAT_RGBX1010102: + case GBM_FORMAT_BGRX1010102: + case GBM_FORMAT_ARGB2101010: + case GBM_FORMAT_ABGR2101010: + case GBM_FORMAT_RGBA1010102: + case GBM_FORMAT_BGRA1010102: + case GBM_FORMAT_AYUV: + return 32; + + default: + GST_ERROR ("unknown GBM format %" G_GUINT32_FORMAT, format); + } + + return 0; +} + + +static void +gst_gl_gbm_drm_fb_destroy_callback (struct gbm_bo *bo, void *data) +{ + int drm_fd = gbm_device_get_fd (gbm_bo_get_device (bo)); + GstGLDRMFramebuffer *fb = (GstGLDRMFramebuffer *) (data); + + if (fb->fb_id) + drmModeRmFB (drm_fd, fb->fb_id); + + g_slice_free1 (sizeof (GstGLDRMFramebuffer), fb); +} + + +GstGLDRMFramebuffer * +gst_gl_gbm_drm_fb_get_from_bo (struct gbm_bo *bo) +{ + GstGLDRMFramebuffer *fb; + int drm_fd; + guint32 width, height, stride, format, handle; + int depth, bpp; + int ret; + + /* We want to use this buffer object (abbr. "bo") as a scanout buffer. + * To that end, we associate the bo with the DRM by using drmModeAddFB(). + * However, this needs to be called exactly once for the given bo, and the + * counterpart, drmModeRmFB(), needs to be called when the bo is cleaned up. + * + * To fulfill these requirements, add extra framebuffer information to the + * bo as "user data". This way, if this user data pointer is NULL, it means + * that no framebuffer information was generated yet & the bo was not set + * as a scanout buffer with drmModeAddFB() yet, and we have perform these + * steps. Otherwise, if it is non-NULL, we know we do not have to set up + * anything (since it was done already) and just return the pointer to the + * framebuffer information. */ + fb = (GstGLDRMFramebuffer *) (gbm_bo_get_user_data (bo)); + if (fb != NULL) { + /* The bo was already set up as a scanout framebuffer. Just + * return the framebuffer information. */ + return fb; + } + + /* If this point is reached, then we have to setup the bo as a + * scanout framebuffer. */ + + drm_fd = gbm_device_get_fd (gbm_bo_get_device (bo)); + + fb = g_slice_alloc0 (sizeof (GstGLDRMFramebuffer)); + fb->bo = bo; + + width = gbm_bo_get_width (bo); + height = gbm_bo_get_height (bo); + stride = gbm_bo_get_stride (bo); + format = gbm_bo_get_format (bo); + handle = gbm_bo_get_handle (bo).u32; + + depth = gst_gl_gbm_depth_from_format (format); + bpp = gst_gl_gbm_bpp_from_format (format); + + GST_DEBUG ("Attempting to add GBM BO as scanout framebuffer width/height: %" + G_GUINT32_FORMAT "/%" G_GUINT32_FORMAT " pixels stride: %" + G_GUINT32_FORMAT " bytes format: %s depth: %d bits total bpp: %d bits", + width, height, stride, gst_gl_gbm_format_to_string (format), depth, bpp); + + /* Set the bo as a scanout framebuffer */ + ret = drmModeAddFB (drm_fd, width, height, depth, bpp, stride, handle, + &fb->fb_id); + if (ret != 0) { + GST_ERROR ("Failed to add GBM BO as scanout framebuffer: %s (%d)", + g_strerror (errno), errno); + g_slice_free1 (sizeof (GstGLDRMFramebuffer), fb); + return NULL; + } + + /* Add the framebuffer information to the bo as user data, and also install a callback + * that cleans up this extra information whenever the bo itself is discarded */ + gbm_bo_set_user_data (bo, fb, gst_gl_gbm_drm_fb_destroy_callback); + + return fb; +} + + +int +gst_gl_gbm_find_and_open_drm_node (void) +{ + /* In here we use GUDev to try to autodetect the GPU */ + + int drm_fd = -1; + GUdevClient *gudev_client = NULL; + GUdevEnumerator *gudev_enum = NULL; + GList *devlist = NULL; + GList *deventry = NULL; + const gchar *subsystems[2] = { "drm", NULL }; + + gudev_client = g_udev_client_new (subsystems); + if (gudev_client == NULL) { + GST_ERROR ("Could not create gudev client"); + goto cleanup; + } + GST_DEBUG ("Created gudev client"); + + gudev_enum = g_udev_enumerator_new (gudev_client); + if (gudev_enum == NULL) { + GST_ERROR ("Could not create gudev enumerator"); + goto cleanup; + } + GST_DEBUG ("Created gudev enumerator"); + + /* TODO: To be 100% sure we pick the right device, also check + * if this is a GPU, because a pure scanout device could also + * have a DRM subsystem for example. However, currently it is + * unclear how to do that. By trying to create an EGL context? */ + g_udev_enumerator_add_match_subsystem (gudev_enum, "drm"); + devlist = g_udev_enumerator_execute (gudev_enum); + GST_DEBUG ("Scanned for udev devices with a drm subsytem"); + + if (devlist == NULL) { + GST_WARNING ("Found no matching DRM devices"); + goto cleanup; + } + GST_DEBUG ("Got %u potentially matching device(s)", g_list_length (devlist)); + + for (deventry = devlist; deventry != NULL; deventry = deventry->next) { + GUdevDevice *gudevice = G_UDEV_DEVICE (deventry->data); + const gchar *devnode = g_udev_device_get_device_file (gudevice); + + if ((devnode == NULL) || !g_str_has_prefix (devnode, "/dev/dri/card")) + continue; + + GST_DEBUG ("Found DRM device with device node \"%s\"", devnode); + + drm_fd = open (devnode, O_RDWR | O_CLOEXEC); + if (drm_fd < 0) { + GST_WARNING ("Cannot open device node \"%s\": %s (%d)", devnode, + g_strerror (errno), errno); + continue; + } + + GST_DEBUG ("Device node \"%s\" is a valid DRM device node", devnode); + break; + } + + +done: + + if (devlist != NULL) { + g_list_free_full (devlist, g_object_unref); + devlist = NULL; + GST_DEBUG ("Cleaned up device list"); + } + + if (gudev_enum != NULL) { + g_object_unref (G_OBJECT (gudev_enum)); + gudev_enum = NULL; + GST_DEBUG ("Cleaned up gudev enumerator"); + } + + if (gudev_client != NULL) { + g_object_unref (G_OBJECT (gudev_client)); + gudev_client = NULL; + GST_DEBUG ("Cleaned up gudev client"); + } + + return drm_fd; + + +cleanup: + + if (drm_fd >= 0) { + close (drm_fd); + drm_fd = -1; + } + + goto done; +} diff --git a/gst-libs/gst/gl/gbm/gstgl_gbm_utils.h b/gst-libs/gst/gl/gbm/gstgl_gbm_utils.h new file mode 100644 index 0000000..c500297 --- /dev/null +++ b/gst-libs/gst/gl/gbm/gstgl_gbm_utils.h @@ -0,0 +1,49 @@ +/* + * GStreamer + * Copyright (C) 2018 Carlos Rafael Giani + * + * 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. + */ + +#ifndef __GST_GL_GBM_PRIVATE_H__ +#define __GST_GL_GBM_PRIVATE_H__ + +#include +#include +#include +#include + + +typedef struct _GstGLDRMFramebuffer GstGLDRMFramebuffer; + +struct _GstGLDRMFramebuffer +{ + struct gbm_bo *bo; + guint32 fb_id; +}; + +const gchar* gst_gl_gbm_get_name_for_drm_connector (drmModeConnector * connector); +const gchar* gst_gl_gbm_get_name_for_drm_encoder (drmModeEncoder * encoder); +const gchar* gst_gl_gbm_format_to_string (guint32 format); +int gst_gl_gbm_depth_from_format (guint32 format); +int gst_gl_gbm_bpp_from_format (guint32 format); + +GstGLDRMFramebuffer* gst_gl_gbm_drm_fb_get_from_bo (struct gbm_bo *bo); + +int gst_gl_gbm_find_and_open_drm_node (void); + + +#endif /* __GST_GL_DISPLAY_GBM_UTILS_H__ */ diff --git a/gst-libs/gst/gl/gbm/gstgldisplay_gbm.c b/gst-libs/gst/gl/gbm/gstgldisplay_gbm.c new file mode 100644 index 0000000..ae4c3cf --- /dev/null +++ b/gst-libs/gst/gl/gbm/gstgldisplay_gbm.c @@ -0,0 +1,423 @@ +/* + * GStreamer + * Copyright (C) 2018 Carlos Rafael Giani + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstgldisplay_gbm.h" +#include "gstgl_gbm_utils.h" + +#include +#include +#include + +GST_DEBUG_CATEGORY (gst_gl_gbm_debug); + +GST_DEBUG_CATEGORY_STATIC (gst_gl_display_debug); +#define GST_CAT_DEFAULT gst_gl_display_debug + + +#define INVALID_CRTC ((guint32)0) + + +G_DEFINE_TYPE (GstGLDisplayGBM, gst_gl_display_gbm, GST_TYPE_GL_DISPLAY); + + +static void gst_gl_display_gbm_finalize (GObject * object); +static guintptr gst_gl_display_gbm_get_handle (GstGLDisplay * display); + +static guint32 gst_gl_gbm_find_crtc_id_for_encoder (GstGLDisplayGBM * + display_gbm, drmModeEncoder const *encoder); +static guint32 gst_gl_gbm_find_crtc_id_for_connector (GstGLDisplayGBM * + display_gbm); + +static gboolean gst_gl_display_gbm_setup_drm (GstGLDisplayGBM * display_gbm); +static void gst_gl_display_gbm_shutdown_drm (GstGLDisplayGBM * display_gbm); + +static gboolean gst_gl_display_gbm_setup_gbm (GstGLDisplayGBM * display_gbm); +static void gst_gl_display_gbm_shutdown_gbm (GstGLDisplayGBM * display_gbm); + + +static void +gst_gl_display_gbm_class_init (GstGLDisplayGBMClass * klass) +{ + GST_GL_DISPLAY_CLASS (klass)->get_handle = + GST_DEBUG_FUNCPTR (gst_gl_display_gbm_get_handle); + + G_OBJECT_CLASS (klass)->finalize = gst_gl_display_gbm_finalize; +} + +static void +gst_gl_display_gbm_init (GstGLDisplayGBM * display_gbm) +{ + GstGLDisplay *display = (GstGLDisplay *) display_gbm; + display->type = GST_GL_DISPLAY_TYPE_GBM; + + display_gbm->drm_fd = -1; +} + +static void +gst_gl_display_gbm_finalize (GObject * object) +{ + GstGLDisplayGBM *display_gbm = GST_GL_DISPLAY_GBM (object); + + gst_gl_display_gbm_shutdown_gbm (display_gbm); + gst_gl_display_gbm_shutdown_drm (display_gbm); + + G_OBJECT_CLASS (gst_gl_display_gbm_parent_class)->finalize (object); +} + +static guintptr +gst_gl_display_gbm_get_handle (GstGLDisplay * display) +{ + return (guintptr) GST_GL_DISPLAY_GBM (display)->gbm_dev; +} + + +static guint32 +gst_gl_gbm_find_crtc_id_for_encoder (GstGLDisplayGBM * display_gbm, + drmModeEncoder const *encoder) +{ + int i; + for (i = 0; i < display_gbm->drm_mode_resources->count_crtcs; ++i) { + /* possible_crtcs is a bitmask as described here: + * https://dvdhrm.wordpress.com/2012/09/13/linux-drm-mode-setting-api */ + guint32 const crtc_mask = 1 << i; + guint32 const crtc_id = display_gbm->drm_mode_resources->crtcs[i]; + + if (encoder->possible_crtcs & crtc_mask) + return crtc_id; + } + + /* No match found */ + return INVALID_CRTC; +} + + +static guint32 +gst_gl_gbm_find_crtc_id_for_connector (GstGLDisplayGBM * display_gbm) +{ + int i; + for (i = 0; i < display_gbm->drm_mode_connector->count_encoders; ++i) { + guint32 encoder_id = display_gbm->drm_mode_connector->encoders[i]; + drmModeEncoder *encoder = + drmModeGetEncoder (display_gbm->drm_fd, encoder_id); + + if (encoder != NULL) { + guint32 crtc_id = + gst_gl_gbm_find_crtc_id_for_encoder (display_gbm, encoder); + drmModeFreeEncoder (encoder); + + if (crtc_id != INVALID_CRTC) + return crtc_id; + } + } + + /* No match found */ + return INVALID_CRTC; +} + + +static gboolean +gst_gl_display_gbm_setup_drm (GstGLDisplayGBM * display_gbm) +{ + int i; + + g_assert (display_gbm != NULL); + g_assert (display_gbm->drm_fd >= 0); + + /* Get the DRM mode resources */ + display_gbm->drm_mode_resources = drmModeGetResources (display_gbm->drm_fd); + if (display_gbm->drm_mode_resources == NULL) { + GST_ERROR ("Could not get DRM resources: %s (%d)", g_strerror (errno), + errno); + goto cleanup; + } + GST_DEBUG ("Got DRM resources"); + + /* Find a connected connector. The connector is where the pixel data is + * finally sent to, and typically connects to some form of display, like an + * HDMI TV, an LVDS panel etc. */ + { + drmModeConnector *connector = NULL; + + GST_DEBUG ("Checking %d DRM connector(s)", + display_gbm->drm_mode_resources->count_connectors); + for (i = 0; i < display_gbm->drm_mode_resources->count_connectors; ++i) { + connector = drmModeGetConnector (display_gbm->drm_fd, + display_gbm->drm_mode_resources->connectors[i]); + GST_DEBUG ("Found DRM connector #%d \"%s\" with ID %" G_GUINT32_FORMAT, i, + gst_gl_gbm_get_name_for_drm_connector (connector), + connector->connector_id); + + if (connector->connection == DRM_MODE_CONNECTED) { + GST_DEBUG ("DRM connector #%d is connected", i); + break; + } + + drmModeFreeConnector (connector); + connector = NULL; + } + + if (connector == NULL) { + GST_ERROR ("No connected DRM connector found"); + goto cleanup; + } + + display_gbm->drm_mode_connector = connector; + } + + /* Check out what modes are supported by the chosen connector, + * and pick either the "preferred" mode or the one with the largest + * pixel area. */ + { + int selected_mode_index = -1; + int selected_mode_area = -1; + + GST_DEBUG ("Checking %d DRM mode(s) from selected connector", + display_gbm->drm_mode_connector->count_modes); + for (i = 0; i < display_gbm->drm_mode_connector->count_modes; ++i) { + drmModeModeInfo *current_mode = + &(display_gbm->drm_mode_connector->modes[i]); + int current_mode_area = current_mode->hdisplay * current_mode->vdisplay; + + GST_DEBUG ("Found DRM mode #%d width/height %" G_GUINT16_FORMAT "/%" + G_GUINT16_FORMAT " hsync/vsync start %" G_GUINT16_FORMAT "/%" + G_GUINT16_FORMAT " hsync/vsync end %" G_GUINT16_FORMAT "/%" + G_GUINT16_FORMAT " htotal/vtotal %" G_GUINT16_FORMAT "/%" + G_GUINT16_FORMAT " hskew %" G_GUINT16_FORMAT " vscan %" + G_GUINT16_FORMAT " vrefresh %" G_GUINT32_FORMAT " preferred %d", i, + current_mode->hdisplay, current_mode->vdisplay, + current_mode->hsync_start, current_mode->vsync_start, + current_mode->hsync_end, current_mode->vsync_end, + current_mode->htotal, current_mode->vtotal, current_mode->hskew, + current_mode->vscan, current_mode->vrefresh, + (current_mode->type & DRM_MODE_TYPE_PREFERRED) ? TRUE : FALSE); + + if ((current_mode->type & DRM_MODE_TYPE_PREFERRED) || + (current_mode_area > selected_mode_area)) { + display_gbm->drm_mode_info = current_mode; + selected_mode_area = current_mode_area; + selected_mode_index = i; + + if (current_mode->type & DRM_MODE_TYPE_PREFERRED) + break; + } + } + + if (display_gbm->drm_mode_info == NULL) { + GST_ERROR ("No usable DRM mode found"); + goto cleanup; + } + + GST_DEBUG ("Selected DRM mode #%d", selected_mode_index); + } + + /* Find an encoder that is attached to the chosen connector. Also find the + * index/id of the CRTC associated with this encoder. The encoder takes pixel + * data from the CRTC and transmits it to the connector. The CRTC roughly + * represents the scanout framebuffer. + * + * Ultimately, we only care about the CRTC index & ID, so the encoder + * reference is discarded here once these are found. The CRTC index is the + * index in the m_drm_mode_resources' CRTC array, while the ID is an identifier + * used by the DRM to refer to the CRTC universally. (We need the CRTC + * information for page flipping and DRM scanout framebuffer configuration.) */ + { + drmModeEncoder *encoder = NULL; + + GST_DEBUG ("Checking %d DRM encoder(s)", + display_gbm->drm_mode_resources->count_encoders); + for (i = 0; i < display_gbm->drm_mode_resources->count_encoders; ++i) { + encoder = drmModeGetEncoder (display_gbm->drm_fd, + display_gbm->drm_mode_resources->encoders[i]); + + GST_DEBUG ("Found DRM encoder #%d \"%s\"", i, + gst_gl_gbm_get_name_for_drm_encoder (encoder)); + + if (encoder->encoder_id == display_gbm->drm_mode_connector->encoder_id) { + GST_DEBUG ("DRM encoder #%d corresponds to selected DRM connector " + "-> selected", i); + break; + } + drmModeFreeEncoder (encoder); + encoder = NULL; + } + + if (encoder == NULL) { + GST_DEBUG ("No encoder found; searching for CRTC ID in the connector"); + display_gbm->crtc_id = + gst_gl_gbm_find_crtc_id_for_connector (display_gbm); + } else { + GST_DEBUG ("Using CRTC ID from selected encoder"); + display_gbm->crtc_id = encoder->crtc_id; + drmModeFreeEncoder (encoder); + } + + if (display_gbm->crtc_id == INVALID_CRTC) { + GST_ERROR ("No CRTC found"); + goto cleanup; + } + + GST_DEBUG ("CRTC with ID %" G_GUINT32_FORMAT " found; now locating it in " + "the DRM mode resources CRTC array", display_gbm->crtc_id); + + for (i = 0; i < display_gbm->drm_mode_resources->count_crtcs; ++i) { + if (display_gbm->drm_mode_resources->crtcs[i] == display_gbm->crtc_id) { + display_gbm->crtc_index = i; + break; + } + } + + if (display_gbm->crtc_index < 0) { + GST_ERROR ("No matching CRTC entry in DRM resources found"); + goto cleanup; + } + + GST_DEBUG ("CRTC with ID %" G_GUINT32_FORMAT " can be found at index #%d " + "in the DRM mode resources CRTC array", display_gbm->crtc_id, + display_gbm->crtc_index); + } + + GST_DEBUG ("DRM structures initialized"); + return TRUE; + +cleanup: + gst_gl_display_gbm_shutdown_drm (display_gbm); + return FALSE; +} + + +static void +gst_gl_display_gbm_shutdown_drm (GstGLDisplayGBM * display_gbm) +{ + g_assert (display_gbm != NULL); + + display_gbm->drm_mode_info = NULL; + + display_gbm->crtc_index = -1; + display_gbm->crtc_id = INVALID_CRTC; + + if (display_gbm->drm_mode_connector != NULL) { + drmModeFreeConnector (display_gbm->drm_mode_connector); + display_gbm->drm_mode_connector = NULL; + } + + if (display_gbm->drm_mode_resources != NULL) { + drmModeFreeResources (display_gbm->drm_mode_resources); + display_gbm->drm_mode_resources = NULL; + } +} + + +static gboolean +gst_gl_display_gbm_setup_gbm (GstGLDisplayGBM * display_gbm) +{ + display_gbm->gbm_dev = gbm_create_device (display_gbm->drm_fd); + if (display_gbm->gbm_dev == NULL) { + GST_ERROR ("Creating GBM device failed"); + return FALSE; + } + + GST_DEBUG ("GBM structures initialized"); + return TRUE; +} + + +static void +gst_gl_display_gbm_shutdown_gbm (GstGLDisplayGBM * display_gbm) +{ + if (display_gbm->gbm_dev != NULL) { + gbm_device_destroy (display_gbm->gbm_dev); + display_gbm->gbm_dev = NULL; + } +} + + +static void +_init_debug (void) +{ + static volatile gsize _init = 0; + + if (g_once_init_enter (&_init)) { + GST_DEBUG_CATEGORY_GET (gst_gl_display_debug, "gldisplay"); + GST_DEBUG_CATEGORY_INIT (gst_gl_gbm_debug, "gleglgbm", 0, + "Mesa3D EGL GBM debugging"); + g_once_init_leave (&_init, 1); + } +} + + +GstGLDisplayGBM * +gst_gl_display_gbm_new (void) +{ + int drm_fd = -1; + GstGLDisplayGBM *display; + const gchar *drm_node_name; + + _init_debug (); + + drm_node_name = g_getenv ("GST_GL_GBM_DRM_DEVICE"); + + if (drm_node_name != NULL) { + GST_DEBUG ("attempting to open device %s (specified by the " + "GST_GL_GBM_DRM_DEVICE environment variable)", drm_node_name); + drm_fd = open (drm_node_name, O_RDWR | O_CLOEXEC); + if (drm_fd < 0) { + GST_ERROR ("could not open DRM device %s: %s (%d)", drm_node_name, + g_strerror (errno), errno); + return NULL; + } + } else { + GST_DEBUG ("GST_GL_GBM_DRM_DEVICE environment variable is not " + "set - trying to autodetect device"); + drm_fd = gst_gl_gbm_find_and_open_drm_node (); + if (drm_fd < 0) { + GST_ERROR ("could not find or open DRM device"); + return NULL; + } + } + + display = g_object_new (GST_TYPE_GL_DISPLAY_GBM, NULL); + display->drm_fd = drm_fd; + + if (!gst_gl_display_gbm_setup_drm (display)) { + GST_ERROR ("Failed to initialize DRM"); + goto cleanup; + } + + if (!gst_gl_display_gbm_setup_gbm (display)) { + GST_ERROR ("Failed to initialize GBM"); + goto cleanup; + } + + GST_DEBUG ("Created GBM EGL display %p", (gpointer) display); + + return display; + +cleanup: + gst_gl_display_gbm_shutdown_gbm (display); + gst_gl_display_gbm_shutdown_drm (display); + gst_object_unref (G_OBJECT (display)); + if (drm_fd >= 0) + close (drm_fd); + return NULL; +} diff --git a/gst-libs/gst/gl/gbm/gstgldisplay_gbm.h b/gst-libs/gst/gl/gbm/gstgldisplay_gbm.h new file mode 100644 index 0000000..567354a --- /dev/null +++ b/gst-libs/gst/gl/gbm/gstgldisplay_gbm.h @@ -0,0 +1,77 @@ +/* + * GStreamer + * Copyright (C) 2018 Carlos Rafael Giani + * + * 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. + */ + +#ifndef __GST_GL_DISPLAY_GBM_H__ +#define __GST_GL_DISPLAY_GBM_H__ + +#include +#include +#include +#include +#include +#include + +G_BEGIN_DECLS + +GType gst_gl_display_gbm_get_type (void); + +#define GST_TYPE_GL_DISPLAY_GBM (gst_gl_display_gbm_get_type()) +#define GST_GL_DISPLAY_GBM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GL_DISPLAY_GBM,GstGLDisplayGBM)) +#define GST_GL_DISPLAY_GBM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_GL_DISPLAY_GBM,GstGLDisplayGBMClass)) +#define GST_IS_GL_DISPLAY_GBM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GL_DISPLAY_GBM)) +#define GST_IS_GL_DISPLAY_GBM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_GL_DISPLAY_GBM)) +#define GST_GL_DISPLAY_GBM_CAST(obj) ((GstGLDisplayGBM*)(obj)) + +#define GST_GL_DISPLAY_GBM_PRIVATE(obj) (((GstGLDisplayGBM*)(obj))->priv) + +typedef struct _GstGLDisplayGBM GstGLDisplayGBM; +typedef struct _GstGLDisplayGBMClass GstGLDisplayGBMClass; + +struct _GstGLDisplayGBM +{ + GstGLDisplay parent; + + /* */ + + int drm_fd; + drmModeRes *drm_mode_resources; + drmModeConnector *drm_mode_connector; + drmModeModeInfo *drm_mode_info; + int crtc_index; + guint32 crtc_id; + + struct gbm_device *gbm_dev; + + gpointer _reserved[GST_PADDING]; +}; + +struct _GstGLDisplayGBMClass +{ + GstGLDisplayClass object_class; + + /*< private >*/ + gpointer _reserved[GST_PADDING_LARGE]; +}; + +GstGLDisplayGBM *gst_gl_display_gbm_new (void); + +G_END_DECLS + +#endif /* __GST_GL_DISPLAY_GBM_H__ */ diff --git a/gst-libs/gst/gl/gbm/gstglwindow_gbm_egl.c b/gst-libs/gst/gl/gbm/gstglwindow_gbm_egl.c new file mode 100644 index 0000000..6cd4c33 --- /dev/null +++ b/gst-libs/gst/gl/gbm/gstglwindow_gbm_egl.c @@ -0,0 +1,357 @@ +/* + * GStreamer + * Copyright (C) 2018 Carlos Rafael Giani + * + * 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 + +#include "../gstgl_fwd.h" +#include +#include + +#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; +} diff --git a/gst-libs/gst/gl/gbm/gstglwindow_gbm_egl.h b/gst-libs/gst/gl/gbm/gstglwindow_gbm_egl.h new file mode 100644 index 0000000..ba2a03f --- /dev/null +++ b/gst-libs/gst/gl/gbm/gstglwindow_gbm_egl.h @@ -0,0 +1,69 @@ +/* + * GStreamer + * Copyright (C) 2018 Carlos Rafael Giani + * + * 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. + */ + +#ifndef __GST_GL_WINDOW_GBM_EGL_H__ +#define __GST_GL_WINDOW_GBM_EGL_H__ + +#include +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_GL_WINDOW_GBM_EGL (gst_gl_window_gbm_egl_get_type()) +#define GST_GL_WINDOW_GBM_EGL(o) (G_TYPE_CHECK_INSTANCE_CAST((o), GST_TYPE_GL_WINDOW_GBM_EGL, GstGLWindowGBMEGL)) +#define GST_GL_WINDOW_GBM_EGL_CLASS(k) (G_TYPE_CHECK_CLASS((k), GST_TYPE_GL_WINDOW_GBM_EGL, GstGLWindowGBMEGLClass)) +#define GST_IS_GL_WINDOW_GBM_EGL(o) (G_TYPE_CHECK_INSTANCE_TYPE((o), GST_TYPE_GL_WINDOW_GBM_EGL)) +#define GST_IS_GL_WINDOW_GBM_EGL_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE((k), GST_TYPE_GL_WINDOW_GBM_EGL)) +#define GST_GL_WINDOW_GBM_EGL_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), GST_TYPE_GL_WINDOW_GBM_EGL, GstGLWindowGBMEGL_Class)) + +typedef struct _GstGLWindowGBMEGL GstGLWindowGBMEGL; +typedef struct _GstGLWindowGBMEGLClass GstGLWindowGBMEGLClass; + +typedef struct _GstGLDisplayGBM GstGLDisplayGBM; + +struct _GstGLWindowGBMEGL { + /*< private >*/ + GstGLWindow parent; + + struct gbm_surface *gbm_surf; + struct gbm_bo *current_bo, *prev_bo; + int waiting_for_flip; + + GstGLDisplayGBM *display; + + gpointer _reserved[GST_PADDING]; +}; + +struct _GstGLWindowGBMEGLClass { + /*< private >*/ + GstGLWindowClass parent_class; + + /*< private >*/ + gpointer _reserved[GST_PADDING_LARGE]; +}; + +GType gst_gl_window_gbm_egl_get_type (void); + +GstGLWindowGBMEGL * gst_gl_window_gbm_egl_new (GstGLDisplay * display); + +G_END_DECLS + +#endif /* __GST_GL_WINDOW_GBM_EGL_H__ */ diff --git a/gst-libs/gst/gl/gstgldisplay.c b/gst-libs/gst/gl/gstgldisplay.c index 459392a..f272571 100644 --- a/gst-libs/gst/gl/gstgldisplay.c +++ b/gst-libs/gst/gl/gstgldisplay.c @@ -73,6 +73,9 @@ #if GST_GL_HAVE_WINDOW_VIV_FB #include #endif +#if GST_GL_HAVE_WINDOW_GBM +#include +#endif GST_DEBUG_CATEGORY_STATIC (gst_context); GST_DEBUG_CATEGORY_STATIC (gst_gl_display_debug); @@ -313,6 +316,11 @@ gst_gl_display_new (void) display = GST_GL_DISPLAY (gst_gl_display_viv_fb_new (disp_idx)); } #endif +#if GST_GL_HAVE_WINDOW_GBM + if (!display && (!user_choice || g_strstr_len (user_choice, 3, "gbm"))) { + display = GST_GL_DISPLAY (gst_gl_display_gbm_new ()); + } +#endif #if GST_GL_HAVE_PLATFORM_EGL if (!display && (!platform_choice || g_strstr_len (platform_choice, 3, "egl"))) diff --git a/gst-libs/gst/gl/gstgldisplay.h b/gst-libs/gst/gl/gstgldisplay.h index 7e8fe68..abfc7b5 100644 --- a/gst-libs/gst/gl/gstgldisplay.h +++ b/gst-libs/gst/gl/gstgldisplay.h @@ -49,6 +49,7 @@ GType gst_gl_display_get_type (void); * @GST_GL_DISPLAY_TYPE_DISPMANX: Dispmanx display * @GST_GL_DISPLAY_TYPE_EGL: EGL display * @GST_GL_DISPLAY_TYPE_VIV_FB: Vivante Framebuffer display + * @GST_GL_DISPLAY_TYPE_GBM: Mesa3D GBM display * @GST_GL_DISPLAY_TYPE_ANY: any display type */ typedef enum @@ -61,6 +62,7 @@ typedef enum GST_GL_DISPLAY_TYPE_DISPMANX = (1 << 4), GST_GL_DISPLAY_TYPE_EGL = (1 << 5), GST_GL_DISPLAY_TYPE_VIV_FB = (1 << 6), + GST_GL_DISPLAY_TYPE_GBM = (1 << 7), GST_GL_DISPLAY_TYPE_ANY = G_MAXUINT32 } GstGLDisplayType; diff --git a/gst-libs/gst/gl/gstglwindow.c b/gst-libs/gst/gl/gstglwindow.c index 0889e6a..f7237a4 100644 --- a/gst-libs/gst/gl/gstglwindow.c +++ b/gst-libs/gst/gl/gstglwindow.c @@ -62,6 +62,9 @@ #if GST_GL_HAVE_WINDOW_VIV_FB #include "viv-fb/gstglwindow_viv_fb_egl.h" #endif +#if GST_GL_HAVE_WINDOW_GBM +#include "gbm/gstglwindow_gbm_egl.h" +#endif #if GST_GL_HAVE_WINDOW_DISPMANX #include "dispmanx/gstglwindow_dispmanx_egl.h" #endif @@ -279,6 +282,10 @@ gst_gl_window_new (GstGLDisplay * display) if (!window && (!user_choice || g_strstr_len (user_choice, 6, "viv-fb"))) window = GST_GL_WINDOW (gst_gl_window_viv_fb_egl_new (display)); #endif +#if GST_GL_HAVE_WINDOW_GBM + if (!window && (!user_choice || g_strstr_len (user_choice, 3, "gbm"))) + window = GST_GL_WINDOW (gst_gl_window_gbm_egl_new (display)); +#endif if (!window) { /* subclass returned a NULL window */ diff --git a/m4/gst-gl.m4 b/m4/gst-gl.m4 index 5f3d5be..ffca666 100644 --- a/m4/gst-gl.m4 +++ b/m4/gst-gl.m4 @@ -126,6 +126,7 @@ HAVE_GLES2=no HAVE_GLES3_H=no HAVE_WAYLAND_EGL=no HAVE_VIV_FB_EGL=no +HAVE_GBM_EGL=no HAVE_EGL_RPI=no case $host in @@ -171,6 +172,12 @@ case $host in AC_CHECK_LIB([EGL], [fbGetDisplay], [HAVE_VIV_FB_EGL=yes]) fi + if test "x$HAVE_EGL" = "xyes" -a "x$HAVE_DRM" = "xyes"; then + PKG_CHECK_MODULES(GBM, gbm, HAVE_GBM_EGL=yes, HAVE_GBM_EGL=no) + AC_SUBST(GBM_CFLAGS) + AC_SUBST(GBM_LIBS) + fi + dnl FIXME: Mali EGL depends on GLESv1 or GLESv2 AC_CHECK_HEADER([EGL/fbdev_window.h], [ @@ -467,6 +474,16 @@ case $host in fi fi + if test "x$HAVE_GBM_EGL" = "xyes"; then + if test "x$NEED_EGL" = "xno" -o "x$HAVE_EGL" = "xno"; then + AC_MSG_WARN([EGL is required by the Mesa GBM EGL backend]) + else + HAVE_WINDOW_GBM=yes + GL_LIBS="$GL_LIBS" + GL_CFLAGS="$GL_CFLAGS" + fi + fi + if test "x$HAVE_X11_XCB" = "xyes" -a "x$HAVE_EGL_RPI" = "xno"; then if test "x$NEED_X11" != "xno"; then GL_LIBS="$GL_LIBS $X11_XCB_LIBS" @@ -511,7 +528,7 @@ case $host in fi else if test "x$NEED_EGL" != "xno"; then - if test "x$HAVE_WINDOW_WAYLAND" = "xyes" -o "x$HAVE_WINDOW_X11" = "xyes" -o "x$HAVE_WINDOW_DISPMANX" = "xyes" -o "x$HAVE_WINDOW_VIV_FB" = "xyes"; then + if test "x$HAVE_WINDOW_WAYLAND" = "xyes" -o "x$HAVE_WINDOW_X11" = "xyes" -o "x$HAVE_WINDOW_DISPMANX" = "xyes" -o "x$HAVE_WINDOW_VIV_FB" = "xyes" -o "x$HAVE_WINDOW_GBM" = "xyes"; then GL_LIBS="$GL_LIBS -lEGL $EGL_LIBS" GL_CFLAGS="$GL_CFLAGS $EGL_CFLAGS" USE_EGL=yes @@ -669,6 +686,7 @@ GST_GL_HAVE_WINDOW_ANDROID=0 GST_GL_HAVE_WINDOW_DISPMANX=0 GST_GL_HAVE_WINDOW_EAGL=0 GST_GL_HAVE_WINDOW_VIV_FB=0 +GST_GL_HAVE_WINDOW_GBM=0 if test "x$HAVE_WINDOW_X11" = "xyes"; then GL_WINDOWS="x11 $GL_WINDOWS" @@ -702,6 +720,10 @@ if test "x$HAVE_WINDOW_VIV_FB" = "xyes"; then GL_WINDOWS="viv-fb $GL_WINDOWS" GST_GL_HAVE_WINDOW_VIV_FB=1 fi +if test "x$HAVE_WINDOW_GBM" = "xyes"; then + GL_WINDOWS="gbm $GL_WINDOWS" + GST_GL_HAVE_WINDOW_GBM=1 +fi GL_CONFIG_DEFINES="$GL_CONFIG_DEFINES #define GST_GL_HAVE_WINDOW_X11 $GST_GL_HAVE_WINDOW_X11 @@ -712,6 +734,7 @@ GL_CONFIG_DEFINES="$GL_CONFIG_DEFINES #define GST_GL_HAVE_WINDOW_DISPMANX $GST_GL_HAVE_WINDOW_DISPMANX #define GST_GL_HAVE_WINDOW_EAGL $GST_GL_HAVE_WINDOW_EAGL #define GST_GL_HAVE_WINDOW_VIV_FB $GST_GL_HAVE_WINDOW_VIV_FB +#define GST_GL_HAVE_WINDOW_GBM $GST_GL_HAVE_WINDOW_GBM " dnl PLATFORM's @@ -789,6 +812,7 @@ if test "x$GL_APIS" = "x" -o "x$GL_PLATFORMS" = "x" -o "x$GL_WINDOWS" = "x"; the HAVE_WINDOW_COCOA=no HAVE_WINDOW_EAGL=no HAVE_WINDOW_VIV_FB=no + HAVE_WINDOW_GBM=no fi AC_SUBST(GL_APIS) @@ -808,6 +832,7 @@ AM_CONDITIONAL(HAVE_WINDOW_WAYLAND, test "x$HAVE_WINDOW_WAYLAND" = "xyes") AM_CONDITIONAL(HAVE_WINDOW_ANDROID, test "x$HAVE_WINDOW_ANDROID" = "xyes") AM_CONDITIONAL(HAVE_WINDOW_EAGL, test "x$HAVE_WINDOW_EAGL" = "xyes") AM_CONDITIONAL(HAVE_WINDOW_VIV_FB, test "x$HAVE_WINDOW_VIV_FB" = "xyes") +AM_CONDITIONAL(HAVE_WINDOW_GBM, test "x$HAVE_WINDOW_GBM" = "xyes") AM_CONDITIONAL(USE_OPENGL, test "x$USE_OPENGL" = "xyes") AM_CONDITIONAL(USE_GLES2, test "x$USE_GLES2" = "xyes")