mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-03 16:09:39 +00:00
472 lines
15 KiB
C
472 lines
15 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.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "gstgldisplay_gbm.h"
|
|
#include "gstgl_gbm_utils.h"
|
|
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
|
|
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,
|
|
const gchar * drm_connector_name);
|
|
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);
|
|
|
|
if (display_gbm->drm_fd >= 0)
|
|
close (display_gbm->drm_fd);
|
|
|
|
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, const gchar *
|
|
drm_connector_name)
|
|
{
|
|
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 *connected_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) {
|
|
drmModeConnector *candidate_connector =
|
|
drmModeGetConnector (display_gbm->drm_fd,
|
|
display_gbm->drm_mode_resources->connectors[i]);
|
|
gchar *candidate_name;
|
|
|
|
candidate_name = g_strdup_printf ("%s-%i",
|
|
gst_gl_gbm_get_name_for_drm_connector (candidate_connector),
|
|
candidate_connector->connector_type_id);
|
|
|
|
GST_DEBUG ("Found DRM connector #%d \"%s\" with ID %" G_GUINT32_FORMAT, i,
|
|
candidate_name, candidate_connector->connector_id);
|
|
|
|
/* If we already picked a connector, and connected_connector is therefore
|
|
* non-NULL, then are just printing information about the other connectors
|
|
* for logging purposes by now, so don't actually do anything with this
|
|
* connector. Just loop instead. */
|
|
if (connected_connector != NULL) {
|
|
drmModeFreeConnector (candidate_connector);
|
|
g_free (candidate_name);
|
|
continue;
|
|
}
|
|
|
|
if (drm_connector_name != NULL) {
|
|
if (g_ascii_strcasecmp (drm_connector_name, candidate_name) != 0) {
|
|
drmModeFreeConnector (candidate_connector);
|
|
g_free (candidate_name);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (candidate_connector->connection == DRM_MODE_CONNECTED) {
|
|
if (drm_connector_name != NULL)
|
|
GST_DEBUG ("Picking DRM connector #%d because it is connected and "
|
|
"has a matching name \"%s\"", i, candidate_name);
|
|
else
|
|
GST_DEBUG ("Picking DRM connector #%d because it is connected", i);
|
|
connected_connector = candidate_connector;
|
|
g_free (candidate_name);
|
|
break;
|
|
} else {
|
|
if (drm_connector_name != NULL)
|
|
GST_WARNING ("DRM connector #%d has a matching name \"%s\" but is "
|
|
"not connected; not picking it", i, candidate_name);
|
|
drmModeFreeConnector (candidate_connector);
|
|
g_free (candidate_name);
|
|
}
|
|
}
|
|
|
|
if (connected_connector == NULL) {
|
|
GST_ERROR ("No connected DRM connector found");
|
|
goto cleanup;
|
|
}
|
|
|
|
display_gbm->drm_mode_connector = connected_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;
|
|
gboolean preferred_mode_found = FALSE;
|
|
|
|
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 (!preferred_mode_found
|
|
&& ((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)
|
|
preferred_mode_found = TRUE;
|
|
}
|
|
}
|
|
|
|
if (display_gbm->drm_mode_info == NULL) {
|
|
GST_ERROR ("No usable DRM mode found");
|
|
goto cleanup;
|
|
}
|
|
|
|
GST_DEBUG ("Selected DRM mode #%d (is preferred: %d)", selected_mode_index,
|
|
preferred_mode_found);
|
|
}
|
|
|
|
/* 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 *selected_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) {
|
|
drmModeEncoder *candidate_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 (candidate_encoder));
|
|
|
|
if ((selected_encoder == NULL) &&
|
|
(candidate_encoder->encoder_id ==
|
|
display_gbm->drm_mode_connector->encoder_id)) {
|
|
selected_encoder = candidate_encoder;
|
|
GST_DEBUG ("DRM encoder #%d corresponds to selected DRM connector "
|
|
"-> selected", i);
|
|
} else
|
|
drmModeFreeEncoder (candidate_encoder);
|
|
}
|
|
|
|
if (selected_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 = selected_encoder->crtc_id;
|
|
drmModeFreeEncoder (selected_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 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;
|
|
const gchar *drm_connector_name;
|
|
|
|
_init_debug ();
|
|
|
|
drm_node_name = g_getenv ("GST_GL_GBM_DRM_DEVICE");
|
|
drm_connector_name = g_getenv ("GST_GL_GBM_DRM_CONNECTOR");
|
|
|
|
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 (drm_connector_name != NULL) {
|
|
GST_DEBUG ("GST_GL_GBM_DRM_CONNECTOR variable set to value \"%s\"; "
|
|
"will use this name to match connector(s) against", drm_connector_name);
|
|
}
|
|
|
|
if (!gst_gl_display_gbm_setup_drm (display, drm_connector_name)) {
|
|
GST_WARNING ("Failed to initialize DRM");
|
|
}
|
|
|
|
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;
|
|
}
|