gstreamer/gst-libs/gst/vaapi/gstvaapisurface_egl.c
Víctor Manuel Jáquez Leal 7a3b25884c libs: egl: surface: export EGLImage as DMABuf if GEM not supported
This code path is used when frames are rendered as textures through
GstVideoGLTextureUploadMeta with EGL, mainly under Wayland.

Originally the EGLImage was exported as GEM, which was handled by
Intel drivers, but Gallium ones cannot create VA surfaces from
GEM buffers, only DMABuf.

This patch checks the memory types supported by VA driver to choose
the render the EGLImages from GEM or DMABuf, because GEM is still
better where supported.

DMABuf is well handled either by intel-vaapi-driver and gallium.

Fixes: #137
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-vaapi/-/merge_requests/122>
2020-08-14 12:43:20 +02:00

351 lines
12 KiB
C

/*
* gstvaapisurface_egl.c - VA surface abstraction (EGL interop)
*
* Copyright (C) 2014 Intel Corporation
* Author: Gwenole Beauchesne <gwenole.beauchesne@intel.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1
* 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
#include "sysdeps.h"
#include "gstvaapisurface_egl.h"
#include "gstvaapibufferproxy_priv.h"
#include "gstvaapicompat.h"
#include "gstvaapidisplay_egl_priv.h"
#include "gstvaapifilter.h"
#include "gstvaapiimage_priv.h"
#include "gstvaapisurface_drm.h"
#include "gstvaapisurface_priv.h"
#if USE_DRM
#include <drm_fourcc.h>
#else
#define DRM_FORMAT_MOD_LINEAR 0ULL
#define DRM_FORMAT_MOD_INVALID ((1ULL << 56) - 1)
#endif
typedef struct
{
GstVaapiDisplayEGL *display;
EGLImageKHR image;
GstVideoFormat format;
guint width;
guint height;
guint mem_types;
GstVaapiSurface *surface; /* result */
} CreateSurfaceWithEGLImageArgs;
static GstVaapiSurface *
do_create_surface_with_egl_image_unlocked (GstVaapiDisplayEGL * display,
EGLImageKHR image, GstVideoFormat format, guint width, guint height,
guint mem_types)
{
GstVaapiDisplay *const base_display = GST_VAAPI_DISPLAY_CAST (display);
EglContext *const ctx = GST_VAAPI_DISPLAY_EGL_CONTEXT (display);
EglVTable *vtable;
if (!ctx || !(vtable = egl_context_get_vtable (ctx, FALSE)))
return NULL;
if ((mem_types & VA_SURFACE_ATTRIB_MEM_TYPE_KERNEL_DRM)
&& vtable->has_EGL_MESA_drm_image) {
gsize size, offset[GST_VIDEO_MAX_PLANES] = { 0, };
gint name, stride[GST_VIDEO_MAX_PLANES] = { 0, };
/* EGL_MESA_drm_image extension */
if (!vtable->eglExportDRMImageMESA (ctx->display->base.handle.p, image,
&name, NULL, &stride[0]))
goto error_export_image_gem_buf;
size = height * stride[0];
/*
* XXX: The below surface creation may fail on Intel due to:
* https://github.com/01org/intel-vaapi-driver/issues/222
* A permanent fix for that problem will be released in intel-vaapi-driver
* version 1.8.4 and later, and also in 1.8.3-1ubuntu1.
* However if you don't have that fix then a simple workaround is to
* uncomment this line of code:
* size = GST_ROUND_UP_32 (height) * stride[0];
*/
return gst_vaapi_surface_new_with_gem_buf_handle (base_display, name, size,
format, width, height, offset, stride);
}
if ((mem_types & VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME)
&& vtable->has_EGL_MESA_image_dma_buf_export) {
int fourcc, num_planes, fd;
EGLint offset = 0;
EGLint stride = 0;
EGLuint64KHR modifier;
GstVideoInfo vi;
if (!vtable->eglExportDMABUFImageQueryMESA (ctx->display->base.handle.p,
image, &fourcc, &num_planes, &modifier))
goto error_export_dma_buf_image_query;
/* Don't allow multi-plane dmabufs */
if (num_planes != 1)
goto error_bad_parameters;
/* FIXME: We don't support modifiers */
if (modifier != DRM_FORMAT_MOD_LINEAR && modifier != DRM_FORMAT_MOD_INVALID)
goto error_bad_parameters;
/* fix color format if needed */
if (fourcc == GST_MAKE_FOURCC ('A', 'B', '2', '4'))
format = gst_vaapi_video_format_from_va_fourcc (VA_FOURCC_RGBA);
else if (fourcc == GST_MAKE_FOURCC ('A', 'R', '2', '4'))
format = gst_vaapi_video_format_from_va_fourcc (VA_FOURCC_BGRA);
if (!vtable->eglExportDMABUFImageMESA (ctx->display->base.handle.p, image,
&fd, &stride, &offset))
goto error_export_dma_buf_image;
gst_video_info_set_format (&vi, format, width, height);
GST_VIDEO_INFO_PLANE_OFFSET (&vi, 0) = offset;
GST_VIDEO_INFO_PLANE_STRIDE (&vi, 0) = stride;
return gst_vaapi_surface_new_with_dma_buf_handle (base_display, fd, &vi);
}
#ifndef GST_DISABLE_GST_DEBUG
{
GString *str = g_string_new (NULL);
if (mem_types & VA_SURFACE_ATTRIB_MEM_TYPE_VA)
g_string_append (str, "VA ");
if (mem_types & VA_SURFACE_ATTRIB_MEM_TYPE_V4L2)
g_string_append (str, "V4L2 ");
if (mem_types & VA_SURFACE_ATTRIB_MEM_TYPE_USER_PTR)
g_string_append (str, "PTR ");
if (mem_types & VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2)
g_string_append (str, "PRIME_2 ");
GST_ERROR ("missing EGL extensions for memory types: %s", str->str);
g_string_free (str, TRUE);
}
#endif
return NULL;
/* ERRORS */
error_export_image_gem_buf:
{
GST_ERROR ("failed to export EGL image to GEM buffer");
return NULL;
}
error_export_dma_buf_image_query:
{
GST_ERROR ("failed to query EGL image for dmabuf export");
return NULL;
}
error_bad_parameters:
{
GST_ERROR ("multi-planed nor non-linear dmabufs are not supported");
return NULL;
}
error_export_dma_buf_image:
{
GST_ERROR ("missing EGL_MESA_image_dma_buf_export extension");
return NULL;
}
}
static void
do_create_surface_with_egl_image (CreateSurfaceWithEGLImageArgs * args)
{
GST_VAAPI_DISPLAY_LOCK (args->display);
args->surface = do_create_surface_with_egl_image_unlocked (args->display,
args->image, args->format, args->width, args->height, args->mem_types);
GST_VAAPI_DISPLAY_UNLOCK (args->display);
}
// Creates VA surface with EGLImage buffer as backing storage (internal)
static inline GstVaapiSurface *
create_surface_with_egl_image (GstVaapiDisplayEGL * display, EGLImageKHR image,
GstVideoFormat format, guint width, guint height, guint mem_types)
{
CreateSurfaceWithEGLImageArgs args =
{ display, image, format, width, height, mem_types };
if (!egl_context_run (GST_VAAPI_DISPLAY_EGL_CONTEXT (display),
(EglContextRunFunc) do_create_surface_with_egl_image, &args))
return NULL;
return args.surface;
}
// Creates VA surface from an EGLImage buffer copy (internal)
static GstVaapiSurface *
create_surface_from_egl_image (GstVaapiDisplayEGL * display,
const GstVideoInfo * vip, EGLImageKHR image, GstVideoFormat format,
guint width, guint height, guint flags)
{
GstVaapiDisplay *const base_display = GST_VAAPI_DISPLAY_CAST (display);
GstVaapiSurface *img_surface = NULL, *out_surface = NULL;
gboolean use_native_format = TRUE;
GstVaapiFilter *filter = NULL;
GstVaapiFilterStatus filter_status;
img_surface = create_surface_with_egl_image (display, image, format,
width, height, 0);
if (!img_surface)
return NULL;
if (vip) {
use_native_format =
GST_VIDEO_INFO_FORMAT (vip) == GST_VIDEO_FORMAT_ENCODED ||
GST_VIDEO_INFO_FORMAT (vip) == GST_VIDEO_FORMAT_UNKNOWN;
if (GST_VIDEO_INFO_WIDTH (vip) && GST_VIDEO_INFO_HEIGHT (vip)) {
width = GST_VIDEO_INFO_WIDTH (vip);
height = GST_VIDEO_INFO_HEIGHT (vip);
}
}
if (use_native_format) {
out_surface = gst_vaapi_surface_new (base_display,
GST_VAAPI_CHROMA_TYPE_YUV420, width, height);
} else {
out_surface = gst_vaapi_surface_new_with_format (base_display,
GST_VIDEO_INFO_FORMAT (vip), width, height, 0);
}
if (!out_surface)
goto error_create_surface;
filter = gst_vaapi_filter_new (base_display);
if (!filter)
goto error_create_filter;
filter_status = gst_vaapi_filter_process (filter,
img_surface, out_surface, flags);
if (filter_status != GST_VAAPI_FILTER_STATUS_SUCCESS)
goto error_convert_surface;
gst_vaapi_surface_unref (img_surface);
gst_object_unref (filter);
return out_surface;
/* ERRORS */
error_create_surface:
GST_ERROR ("failed to create output surface format:%s size:%dx%d",
gst_vaapi_video_format_to_string (vip ? GST_VIDEO_INFO_FORMAT (vip) :
GST_VIDEO_FORMAT_ENCODED), width, height);
goto error_cleanup;
error_convert_surface:
GST_ERROR ("failed to transfer EGL image to VA surface (status = %d)",
filter_status);
goto error_cleanup;
error_create_filter:
GST_ERROR ("failed to create video processing filter");
// fall-through
error_cleanup:
gst_mini_object_replace ((GstMiniObject **) & img_surface, NULL);
gst_mini_object_replace ((GstMiniObject **) & out_surface, NULL);
gst_vaapi_filter_replace (&filter, NULL);
return NULL;
}
/**
* gst_vaapi_surface_new_from_egl_image:
* @display: a #GstVaapiDisplay
* @vip: the desired (optional) #GstVideoInfo constraints
* @image: the EGL image to import
* @format: the EGL @image format
* @width: the EGL @image width in pixels
* @height: the EGL @image height in pixels
* @flags: postprocessing flags. See #GstVaapiSurfaceRenderFlags
*
* Creates a new #GstVaapiSurface with a copy of the EGL image
* contents. i.e. the input EGL @image can be disposed and the
* resulting VA surface would still be valid with the contents at the
* time this function was called.
*
* If @vip is %NULL, then the resulting VA surface will be created
* with the same video format and size as the original @image. If @vip
* is non-%NULL and the desired format is GST_VIDEO_FORMAT_ENCODED,
* then the resulting VA surface will have the best "native" HW
* format, usually NV12.
*
* Return value: the newly allocated #GstVaapiSurface object, or %NULL
* if creation from EGL image failed, or is not supported
*/
GstVaapiSurface *
gst_vaapi_surface_new_from_egl_image (GstVaapiDisplay * base_display,
const GstVideoInfo * vip, EGLImageKHR image, GstVideoFormat format,
guint width, guint height, guint flags)
{
GstVaapiDisplayEGL *display;
g_return_val_if_fail (GST_VAAPI_IS_DISPLAY_EGL (base_display), NULL);
g_return_val_if_fail (image != EGL_NO_IMAGE_KHR, NULL);
g_return_val_if_fail (width > 0, NULL);
g_return_val_if_fail (height > 0, NULL);
display = GST_VAAPI_DISPLAY_EGL (base_display);
if (!display || !GST_VAAPI_IS_DISPLAY_EGL (display))
goto error_invalid_display;
return create_surface_from_egl_image (display, vip, image, format,
width, height, flags);
/* ERRORS */
error_invalid_display:
GST_ERROR ("invalid display (NULL or not of EGL class");
return NULL;
}
/**
* gst_vaapi_surface_new_with_egl_image:
* @display: a #GstVaapiDisplay
* @image: the EGL image to import
* @format: the EGL @image format
* @width: the EGL @image width in pixels
* @height: the EGL @image height in pixels
* @mem_types: the supported memory types
*
* Creates a new #GstVaapiSurface bound to an external EGL image.
*
* The caller maintains the lifetime of the EGL image object. In
* particular, the EGL image shall not be destroyed before the last
* reference to the resulting VA surface is released.
*
* Return value: the newly allocated #GstVaapiSurface object, or %NULL
* if creation from EGL image failed, or is not supported
*/
GstVaapiSurface *
gst_vaapi_surface_new_with_egl_image (GstVaapiDisplay * base_display,
EGLImageKHR image, GstVideoFormat format, guint width, guint height,
guint mem_types)
{
GstVaapiDisplayEGL *display;
g_return_val_if_fail (GST_VAAPI_IS_DISPLAY_EGL (base_display), NULL);
g_return_val_if_fail (image != EGL_NO_IMAGE_KHR, NULL);
g_return_val_if_fail (width > 0, NULL);
g_return_val_if_fail (height > 0, NULL);
display = GST_VAAPI_DISPLAY_EGL (base_display);
if (!display || !GST_VAAPI_IS_DISPLAY_EGL (display))
goto error_invalid_display;
return create_surface_with_egl_image (display, image, format, width, height,
mem_types);
/* ERRORS */
error_invalid_display:
GST_ERROR ("invalid display (NULL or not of EGL class");
return NULL;
}