gstreamer/subprojects/gst-plugins-bad/gst-libs/gst/cuda/gstcudautils.cpp
2024-08-02 01:59:07 +00:00

1961 lines
57 KiB
C++

/* GStreamer
* Copyright (C) <2018-2019> Seungha Yang <seungha.yang@navercorp.com>
*
* 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 "gstcudautils.h"
#include "gstcudacontext.h"
#include "gstcuda-private.h"
#include <atomic>
#include <set>
#include <string>
#include <mutex>
#ifdef HAVE_CUDA_GST_GL
#include <gst/gl/gl.h>
#include <gst/gl/gstglfuncs.h>
#if GST_GL_HAVE_PLATFORM_EGL
#include <gst/gl/egl/egl.h>
#endif
#endif
#ifdef G_OS_WIN32
#include <gst/d3d11/gstd3d11.h>
#include <sddl.h>
#endif
#ifdef HAVE_CUDA_NVMM
#include "gstcudanvmm-private.h"
#include <nvbufsurface.h>
#endif
#include "gstcudamemory.h"
GST_DEBUG_CATEGORY_STATIC (gst_cuda_utils_debug);
#define GST_CAT_DEFAULT gst_cuda_utils_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_CONTEXT);
static void
_init_debug (void)
{
GST_CUDA_CALL_ONCE_BEGIN {
GST_DEBUG_CATEGORY_INIT (gst_cuda_utils_debug, "cudautils", 0,
"CUDA utils");
GST_DEBUG_CATEGORY_GET (GST_CAT_CONTEXT, "GST_CONTEXT");
} GST_CUDA_CALL_ONCE_END;
}
static gboolean
pad_query (const GValue * item, GValue * value, gpointer user_data)
{
GstPad *pad = (GstPad *) g_value_get_object (item);
GstQuery *query = (GstQuery *) user_data;
gboolean res;
res = gst_pad_peer_query (pad, query);
if (res) {
g_value_set_boolean (value, TRUE);
return FALSE;
}
GST_CAT_INFO_OBJECT (GST_CAT_CONTEXT, pad, "pad peer query failed");
return TRUE;
}
static gboolean
run_query (GstElement * element, GstQuery * query, GstPadDirection direction)
{
GstIterator *it;
GstIteratorFoldFunction func = pad_query;
GValue res = { 0 };
g_value_init (&res, G_TYPE_BOOLEAN);
g_value_set_boolean (&res, FALSE);
/* Ask neighbor */
if (direction == GST_PAD_SRC)
it = gst_element_iterate_src_pads (element);
else
it = gst_element_iterate_sink_pads (element);
while (gst_iterator_fold (it, func, &res, query) == GST_ITERATOR_RESYNC)
gst_iterator_resync (it);
gst_iterator_free (it);
return g_value_get_boolean (&res);
}
static void
find_cuda_context (GstElement * element, GstCudaContext ** cuda_ctx)
{
GstQuery *query;
GstContext *ctxt;
/* 1) Query downstream with GST_QUERY_CONTEXT for the context and
* check if upstream already has a context of the specific type
* 2) Query upstream as above.
*/
query = gst_query_new_context (GST_CUDA_CONTEXT_TYPE);
if (run_query (element, query, GST_PAD_SRC)) {
gst_query_parse_context (query, &ctxt);
if (ctxt) {
GST_CAT_INFO_OBJECT (GST_CAT_CONTEXT, element,
"found context (%p) in downstream query", ctxt);
gst_element_set_context (element, ctxt);
}
}
/* although we found cuda context above, the element does not want
* to use the context. Then try to find from the other direction */
if (*cuda_ctx == nullptr && run_query (element, query, GST_PAD_SINK)) {
gst_query_parse_context (query, &ctxt);
if (ctxt) {
GST_CAT_INFO_OBJECT (GST_CAT_CONTEXT, element,
"found context (%p) in upstream query", ctxt);
gst_element_set_context (element, ctxt);
}
}
if (*cuda_ctx == nullptr) {
/* 3) Post a GST_MESSAGE_NEED_CONTEXT message on the bus with
* the required context type and afterwards check if a
* usable context was set now. The message could
* be handled by the parent bins of the element and the
* application.
*/
GstMessage *msg;
GST_CAT_INFO_OBJECT (GST_CAT_CONTEXT, element,
"posting need context message");
msg = gst_message_new_need_context (GST_OBJECT_CAST (element),
GST_CUDA_CONTEXT_TYPE);
gst_element_post_message (element, msg);
}
/*
* Whomever responds to the need-context message performs a
* GstElement::set_context() with the required context in which the element
* is required to update the cuda_ctx or call gst_cuda_handle_set_context().
*/
gst_query_unref (query);
}
static void
context_set_cuda_context (GstContext * context, GstCudaContext * cuda_ctx)
{
GstStructure *s;
guint device_id;
g_return_if_fail (context != nullptr);
g_object_get (G_OBJECT (cuda_ctx), "cuda-device-id", &device_id, nullptr);
GST_CAT_LOG (GST_CAT_CONTEXT,
"setting GstCudaContext(%" GST_PTR_FORMAT
") with cuda-device-id %d on context(%" GST_PTR_FORMAT ")",
cuda_ctx, device_id, context);
s = gst_context_writable_structure (context);
gst_structure_set (s, GST_CUDA_CONTEXT_TYPE, GST_TYPE_CUDA_CONTEXT,
cuda_ctx, "cuda-device-id", G_TYPE_UINT, device_id, nullptr);
}
/**
* gst_cuda_ensure_element_context:
* @element: the #GstElement running the query
* @device_id: preferred device-id, pass device_id >=0 when
* the device_id explicitly required. Otherwise, set -1.
* @cuda_ctx: (inout): the resulting #GstCudaContext
*
* Perform the steps necessary for retrieving a #GstCudaContext from the
* surrounding elements or from the application using the #GstContext mechanism.
*
* If the content of @cuda_ctx is not %NULL, then no #GstContext query is
* necessary for #GstCudaContext.
*
* Returns: whether a #GstCudaContext exists in @cuda_ctx
*
* Since: 1.22
*/
gboolean
gst_cuda_ensure_element_context (GstElement * element, gint device_id,
GstCudaContext ** cuda_ctx)
{
guint target_device_id = 0;
gboolean ret = TRUE;
static std::recursive_mutex lock;
g_return_val_if_fail (element != nullptr, FALSE);
g_return_val_if_fail (cuda_ctx != nullptr, FALSE);
_init_debug ();
std::lock_guard < std::recursive_mutex > lk (lock);
if (*cuda_ctx)
return TRUE;
find_cuda_context (element, cuda_ctx);
if (*cuda_ctx)
return TRUE;
if (device_id > 0)
target_device_id = device_id;
/* No available CUDA context in pipeline, create new one here */
*cuda_ctx = gst_cuda_context_new (target_device_id);
if (*cuda_ctx == nullptr) {
GST_CAT_ERROR_OBJECT (GST_CAT_CONTEXT, element,
"Failed to create CUDA context with device-id %d", device_id);
ret = FALSE;
} else {
GstContext *context;
GstMessage *msg;
/* Propagate new CUDA context */
context = gst_context_new (GST_CUDA_CONTEXT_TYPE, TRUE);
context_set_cuda_context (context, *cuda_ctx);
gst_element_set_context (element, context);
GST_CAT_INFO_OBJECT (GST_CAT_CONTEXT, element,
"posting have context (%p) message with CUDA context (%p)",
context, *cuda_ctx);
msg = gst_message_new_have_context (GST_OBJECT_CAST (element), context);
gst_element_post_message (GST_ELEMENT_CAST (element), msg);
}
return ret;
}
/**
* gst_cuda_handle_set_context:
* @element: a #GstElement
* @context: a #GstContext
* @device_id: preferred device-id, pass device_id >=0 when
* the device_id explicitly required. Otherwise, set -1.
* @cuda_ctx: (inout) (transfer full): location of a #GstCudaContext
*
* Helper function for implementing #GstElementClass.set_context() in
* CUDA capable elements.
*
* Retrieves the #GstCudaContext in @context and places the result in @cuda_ctx.
*
* Returns: whether the @cuda_ctx could be set successfully
*
* Since: 1.22
*/
gboolean
gst_cuda_handle_set_context (GstElement * element,
GstContext * context, gint device_id, GstCudaContext ** cuda_ctx)
{
const gchar *context_type;
g_return_val_if_fail (element != nullptr, FALSE);
g_return_val_if_fail (cuda_ctx != nullptr, FALSE);
_init_debug ();
if (!context)
return FALSE;
context_type = gst_context_get_context_type (context);
if (g_strcmp0 (context_type, GST_CUDA_CONTEXT_TYPE) == 0) {
const GstStructure *str;
GstCudaContext *other_ctx = nullptr;
guint other_device_id = 0;
/* If we had context already, will not replace it */
if (*cuda_ctx)
return TRUE;
str = gst_context_get_structure (context);
if (gst_structure_get (str, GST_CUDA_CONTEXT_TYPE, GST_TYPE_CUDA_CONTEXT,
&other_ctx, nullptr)) {
g_object_get (other_ctx, "cuda-device-id", &other_device_id, nullptr);
if (device_id == -1 || other_device_id == (guint) device_id) {
GST_CAT_DEBUG_OBJECT (GST_CAT_CONTEXT, element, "Found CUDA context");
*cuda_ctx = other_ctx;
return TRUE;
}
gst_object_unref (other_ctx);
}
}
return FALSE;
}
/**
* gst_cuda_handle_context_query:
* @element: a #GstElement
* @query: a #GstQuery of type %GST_QUERY_CONTEXT
* @cuda_ctx: (transfer none) (nullable): a #GstCudaContext
*
* Returns: Whether the @query was successfully responded to from the passed
* @context.
*
* Since: 1.22
*/
gboolean
gst_cuda_handle_context_query (GstElement * element,
GstQuery * query, GstCudaContext * cuda_ctx)
{
const gchar *context_type;
GstContext *context, *old_context;
g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE);
g_return_val_if_fail (GST_IS_QUERY (query), FALSE);
g_return_val_if_fail (cuda_ctx == nullptr
|| GST_IS_CUDA_CONTEXT (cuda_ctx), FALSE);
_init_debug ();
GST_CAT_LOG_OBJECT (GST_CAT_CONTEXT, element,
"handle context query %" GST_PTR_FORMAT, query);
gst_query_parse_context_type (query, &context_type);
if (cuda_ctx && g_strcmp0 (context_type, GST_CUDA_CONTEXT_TYPE) == 0) {
gst_query_parse_context (query, &old_context);
if (old_context)
context = gst_context_copy (old_context);
else
context = gst_context_new (GST_CUDA_CONTEXT_TYPE, TRUE);
context_set_cuda_context (context, cuda_ctx);
gst_query_set_context (query, context);
gst_context_unref (context);
GST_CAT_DEBUG_OBJECT (GST_CAT_CONTEXT, element,
"successfully set %" GST_PTR_FORMAT " on %" GST_PTR_FORMAT, cuda_ctx,
query);
return TRUE;
}
return FALSE;
}
/**
* gst_context_new_cuda_context:
* @cuda_ctx: (transfer none): a #GstCudaContext
*
* Returns: (transfer full): a new #GstContext embedding the @cuda_ctx
*
* Since: 1.22
*/
GstContext *
gst_context_new_cuda_context (GstCudaContext * cuda_ctx)
{
GstContext *context;
g_return_val_if_fail (GST_IS_CUDA_CONTEXT (cuda_ctx), nullptr);
_init_debug ();
context = gst_context_new (GST_CUDA_CONTEXT_TYPE, TRUE);
context_set_cuda_context (context, cuda_ctx);
return context;
}
static const gchar *gst_cuda_quark_strings[] =
{ "GstCudaQuarkGraphicsResource" };
static GQuark gst_cuda_quark_table[GST_CUDA_QUARK_MAX];
static void
init_cuda_quark_once (void)
{
GST_CUDA_CALL_ONCE_BEGIN {
for (guint i = 0; i < GST_CUDA_QUARK_MAX; i++) {
gst_cuda_quark_table[i] =
g_quark_from_static_string (gst_cuda_quark_strings[i]);
}
}
GST_CUDA_CALL_ONCE_END;
}
/**
* gst_cuda_quark_from_id: (skip)
* @id: a #GstCudaQuarkId
*
* Returns: the GQuark for given @id or 0 if @id is unknown value
*
* Since: 1.22
*/
GQuark
gst_cuda_quark_from_id (GstCudaQuarkId id)
{
g_return_val_if_fail (id < GST_CUDA_QUARK_MAX, 0);
init_cuda_quark_once ();
_init_debug ();
return gst_cuda_quark_table[id];
}
/**
* gst_cuda_graphics_resource_new: (skip)
* @context: (transfer none): a #GstCudaContext
* @graphics_context: (transfer none) (nullable): a graphics API specific context object
* @type: a #GstCudaGraphicsResourceType of resource registration
*
* Create new #GstCudaGraphicsResource with given @context and @type
*
* Returns: a new #GstCudaGraphicsResource.
* Free with gst_cuda_graphics_resource_free
*
* Since: 1.22
*/
GstCudaGraphicsResource *
gst_cuda_graphics_resource_new (GstCudaContext *
context, GstObject * graphics_context, GstCudaGraphicsResourceType type)
{
GstCudaGraphicsResource *resource;
g_return_val_if_fail (GST_IS_CUDA_CONTEXT (context), nullptr);
_init_debug ();
resource = g_new0 (GstCudaGraphicsResource, 1);
resource->cuda_context = (GstCudaContext *) gst_object_ref (context);
if (graphics_context) {
resource->graphics_context =
(GstObject *) gst_object_ref (graphics_context);
}
return resource;
}
/**
* gst_cuda_graphics_resource_register_gl_buffer: (skip)
* @resource a #GstCudaGraphicsResource
* @buffer: a GL buffer object
* @flags: a `CUgraphicsRegisterFlags`
*
* Register the @buffer for access by CUDA.
* Must be called from the gl context thread with current cuda context was
* pushed on the current thread
*
* Returns: whether @buffer was registered or not
*
* Since: 1.22
*/
gboolean
gst_cuda_graphics_resource_register_gl_buffer (GstCudaGraphicsResource *
resource, guint buffer, CUgraphicsRegisterFlags flags)
{
CUresult cuda_ret;
g_return_val_if_fail (resource != nullptr, FALSE);
g_return_val_if_fail (resource->registered == FALSE, FALSE);
_init_debug ();
cuda_ret = CuGraphicsGLRegisterBuffer (&resource->resource, buffer, flags);
if (!gst_cuda_result (cuda_ret))
return FALSE;
resource->registered = TRUE;
resource->type = GST_CUDA_GRAPHICS_RESOURCE_GL_BUFFER;
resource->flags = flags;
return TRUE;
}
#ifdef G_OS_WIN32
/**
* gst_cuda_graphics_resource_register_d3d11_resource: (skip)
* @resource a #GstCudaGraphicsResource
* @d3d11_resource: a ID3D11Resource
* @flags: a CUgraphicsRegisterFlags
*
* Register the @d3d11_resource for accessing by CUDA.
* Must be called with d3d11 device lock with current cuda context was
* pushed on the current thread
*
* Returns: whether @d3d11_resource was registered or not
*
* Since: 1.22
*/
gboolean
gst_cuda_graphics_resource_register_d3d11_resource (GstCudaGraphicsResource *
resource, ID3D11Resource * d3d11_resource, CUgraphicsRegisterFlags flags)
{
CUresult cuda_ret;
g_return_val_if_fail (resource != nullptr, FALSE);
g_return_val_if_fail (resource->registered == FALSE, FALSE);
_init_debug ();
cuda_ret = CuGraphicsD3D11RegisterResource (&resource->resource,
d3d11_resource, flags);
if (!gst_cuda_result (cuda_ret))
return FALSE;
resource->registered = TRUE;
resource->type = GST_CUDA_GRAPHICS_RESOURCE_D3D11_RESOURCE;
resource->flags = flags;
return TRUE;
}
#endif
/**
* gst_cuda_graphics_resource_unregister: (skip)
* @resource: a #GstCudaGraphicsResource
*
* Unregister previously registered resource.
* For GL resource, this method must be called from gl context thread.
* Also, current cuda context should be pushed on the current thread
* before calling this method.
*
* Since: 1.22
*/
void
gst_cuda_graphics_resource_unregister (GstCudaGraphicsResource * resource)
{
g_return_if_fail (resource != nullptr);
_init_debug ();
if (!resource->registered)
return;
gst_cuda_result (CuGraphicsUnregisterResource (resource->resource));
resource->resource = nullptr;
resource->registered = FALSE;
return;
}
/**
* gst_cuda_graphics_resource_map: (skip)
* @resource: a #GstCudaGraphicsResource
* @stream: a CUstream
* @flags: a CUgraphicsMapResourceFlags
*
* Map previously registered resource with map flags
*
* Returns: (nullable): the `CUgraphicsResource` if successful or %NULL when failed
*
* Since: 1.22
*/
CUgraphicsResource
gst_cuda_graphics_resource_map (GstCudaGraphicsResource * resource,
CUstream stream, CUgraphicsMapResourceFlags flags)
{
CUresult cuda_ret;
g_return_val_if_fail (resource != nullptr, nullptr);
g_return_val_if_fail (resource->registered != FALSE, nullptr);
_init_debug ();
cuda_ret = CuGraphicsResourceSetMapFlags (resource->resource, flags);
if (!gst_cuda_result (cuda_ret))
return nullptr;
cuda_ret = CuGraphicsMapResources (1, &resource->resource, stream);
if (!gst_cuda_result (cuda_ret))
return nullptr;
resource->mapped = TRUE;
return resource->resource;
}
/**
* gst_cuda_graphics_resource_unmap: (skip)
* @resource: a #GstCudaGraphicsResource
* @stream: a `CUstream`
*
* Unmap previously mapped resource
*
* Since: 1.22
*/
void
gst_cuda_graphics_resource_unmap (GstCudaGraphicsResource * resource,
CUstream stream)
{
g_return_if_fail (resource != nullptr);
g_return_if_fail (resource->registered != FALSE);
_init_debug ();
if (!resource->mapped)
return;
gst_cuda_result (CuGraphicsUnmapResources (1, &resource->resource, stream));
resource->mapped = FALSE;
}
#ifdef HAVE_CUDA_GST_GL
static void
unregister_resource_from_gl_thread (GstGLContext * gl_context,
GstCudaGraphicsResource * resource)
{
GstCudaContext *cuda_context = resource->cuda_context;
if (!gst_cuda_context_push (cuda_context)) {
GST_WARNING_OBJECT (cuda_context, "failed to push CUDA context");
return;
}
gst_cuda_graphics_resource_unregister (resource);
if (!gst_cuda_context_pop (nullptr)) {
GST_WARNING_OBJECT (cuda_context, "failed to pop CUDA context");
}
}
#endif
#ifdef G_OS_WIN32
static void
unregister_d3d11_resource (GstCudaGraphicsResource * resource)
{
GstCudaContext *cuda_context = resource->cuda_context;
GstD3D11Device *device = GST_D3D11_DEVICE (resource->graphics_context);
if (!gst_cuda_context_push (cuda_context)) {
GST_WARNING_OBJECT (cuda_context, "failed to push CUDA context");
return;
}
gst_d3d11_device_lock (device);
gst_cuda_graphics_resource_unregister (resource);
gst_d3d11_device_unlock (device);
if (!gst_cuda_context_pop (nullptr)) {
GST_WARNING_OBJECT (cuda_context, "failed to pop CUDA context");
}
}
#endif
/**
* gst_cuda_graphics_resource_free: (skip)
* @resource: a #GstCudaGraphicsResource
*
* Free @resource
*
* Since: 1.22
*/
void
gst_cuda_graphics_resource_free (GstCudaGraphicsResource * resource)
{
g_return_if_fail (resource != nullptr);
if (resource->registered) {
#ifdef HAVE_CUDA_GST_GL
if (resource->type == GST_CUDA_GRAPHICS_RESOURCE_GL_BUFFER) {
gst_gl_context_thread_add ((GstGLContext *) resource->graphics_context,
(GstGLContextThreadFunc) unregister_resource_from_gl_thread,
resource);
} else if (resource->type == GST_CUDA_GRAPHICS_RESOURCE_EGL_RESOURCE) {
gst_cuda_graphics_resource_unregister (resource);
} else
#endif
#ifdef G_OS_WIN32
if (resource->type == GST_CUDA_GRAPHICS_RESOURCE_D3D11_RESOURCE) {
unregister_d3d11_resource (resource);
} else
#endif
{
/* FIXME: currently only opengl & d3d11 */
g_assert_not_reached ();
}
}
gst_object_unref (resource->cuda_context);
if (resource->graphics_context)
gst_object_unref (resource->graphics_context);
g_free (resource);
}
const gchar *
gst_cuda_buffer_copy_type_to_string (GstCudaBufferCopyType type)
{
switch (type) {
case GST_CUDA_BUFFER_COPY_SYSTEM:
return "SYSTEM";
case GST_CUDA_BUFFER_COPY_CUDA:
return "CUDA";
case GST_CUDA_BUFFER_COPY_GL:
return "GL";
case GST_CUDA_BUFFER_COPY_D3D11:
return "D3D11";
case GST_CUDA_BUFFER_COPY_NVMM:
return "NVMM";
default:
g_assert_not_reached ();
break;
}
return "UNKNOWN";
}
static gboolean
gst_cuda_buffer_fallback_copy (GstBuffer * dst, const GstVideoInfo * dst_info,
GstBuffer * src, const GstVideoInfo * src_info)
{
GstVideoFrame dst_frame, src_frame;
guint i, j;
if (!gst_video_frame_map (&dst_frame, dst_info, dst, GST_MAP_WRITE)) {
GST_ERROR ("Failed to map dst buffer");
return FALSE;
}
if (!gst_video_frame_map (&src_frame, src_info, src, GST_MAP_READ)) {
gst_video_frame_unmap (&dst_frame);
GST_ERROR ("Failed to map src buffer");
return FALSE;
}
/* src and dst resolutions can be different, pick min value */
for (i = 0; i < GST_VIDEO_FRAME_N_PLANES (&dst_frame); i++) {
guint dst_width_in_bytes, src_width_in_bytes;
guint dst_height, src_height;
guint width_in_bytes, height;
guint dst_stride, src_stride;
guint8 *dst_data, *src_data;
dst_width_in_bytes = GST_VIDEO_FRAME_COMP_WIDTH (&dst_frame, i) *
GST_VIDEO_FRAME_COMP_PSTRIDE (&dst_frame, i);
src_width_in_bytes = GST_VIDEO_FRAME_COMP_WIDTH (&src_frame, i) *
GST_VIDEO_FRAME_COMP_PSTRIDE (&src_frame, i);
width_in_bytes = MIN (dst_width_in_bytes, src_width_in_bytes);
dst_height = GST_VIDEO_FRAME_COMP_HEIGHT (&dst_frame, i);
src_height = GST_VIDEO_FRAME_COMP_HEIGHT (&src_frame, i);
height = MIN (dst_height, src_height);
dst_stride = GST_VIDEO_FRAME_PLANE_STRIDE (&dst_frame, i);
src_stride = GST_VIDEO_FRAME_PLANE_STRIDE (&src_frame, i);
dst_data = (guint8 *) GST_VIDEO_FRAME_PLANE_DATA (&dst_frame, i);
src_data = (guint8 *) GST_VIDEO_FRAME_PLANE_DATA (&src_frame, i);
for (j = 0; j < height; j++) {
memcpy (dst_data, src_data, width_in_bytes);
dst_data += dst_stride;
src_data += src_stride;
}
}
gst_video_frame_unmap (&src_frame);
gst_video_frame_unmap (&dst_frame);
return TRUE;
}
static gboolean
map_buffer_and_fill_copy2d (GstBuffer * buf, const GstVideoInfo * info,
GstCudaBufferCopyType copy_type, GstVideoFrame * frame,
GstMapInfo * map_info, gboolean is_src,
CUDA_MEMCPY2D copy_params[GST_VIDEO_MAX_PLANES],
GstCudaGraphicsResource * nvSurfaceArrayResource)
{
gboolean buffer_mapped = FALSE;
guint i;
#ifdef HAVE_CUDA_NVMM
if (copy_type == GST_CUDA_BUFFER_COPY_NVMM) {
NvBufSurface *surface;
NvBufSurfaceParams *surface_params;
NvBufSurfacePlaneParams *plane_params;
if (!gst_buffer_map (buf, map_info, GST_MAP_READ)) {
GST_ERROR ("Failed to map input NVMM buffer");
memset (map_info, 0, sizeof (GstMapInfo));
return FALSE;
}
surface = (NvBufSurface *) map_info->data;
GST_TRACE ("batch-size %d, num-filled %d, memType %d",
surface->batchSize, surface->numFilled, surface->memType);
surface_params = surface->surfaceList;
buffer_mapped = TRUE;
if (!surface_params) {
GST_ERROR ("NVMM memory doesn't hold buffer");
goto error;
}
plane_params = &surface_params->planeParams;
if (plane_params->num_planes != GST_VIDEO_INFO_N_PLANES (info)) {
GST_ERROR ("num_planes mismatch, %d / %d",
plane_params->num_planes, GST_VIDEO_INFO_N_PLANES (info));
goto error;
}
switch (surface->memType) {
/* NVBUF_MEM_DEFAULT on jetson is SURFACE_ARRAY otherwise it is NVBUF_MEM_CUDA_DEVICE */
case NVBUF_MEM_DEFAULT:
#if defined(HAVE_CUDA_NVMM_JETSON)
case NVBUF_MEM_SURFACE_ARRAY:{
if (NvBufSurfaceMapEglImage (surface, 0) != 0) {
GST_ERROR ("could not map EGL Image from NvBufSurface");
goto error;
}
CUresult result;
if ((result =
CuGraphicsEGLRegisterImage (&nvSurfaceArrayResource->resource,
surface_params->mappedAddr.eglImage,
CU_GRAPHICS_MAP_RESOURCE_FLAGS_NONE)) != CUDA_SUCCESS) {
GST_ERROR ("cuGraphicsEGLRegisterImage failed: %u "
"with mapped EGLImageAddress: %p", result,
surface_params->mappedAddr.eglImage);
NvBufSurfaceUnMapEglImage (surface, 0);
goto error;
}
nvSurfaceArrayResource->registered = true;
CUeglFrame eglFrame;
if ((result = CuGraphicsResourceGetMappedEglFrame (&eglFrame,
nvSurfaceArrayResource->resource, 0, 0)) != CUDA_SUCCESS) {
GST_ERROR ("cuGraphicsResourceGetMappedEglFrame failed: %u", result);
NvBufSurfaceUnMapEglImage (surface, 0);
goto error;
}
if ((result = CuCtxSynchronize ()) != CUDA_SUCCESS) {
GST_ERROR ("cuCtxSynchronize failed: %u", result);
NvBufSurfaceUnMapEglImage (surface, 0);
goto error;
}
for (i = 0; i < plane_params->num_planes; i++) {
if (is_src) {
if (eglFrame.frameType == CU_EGL_FRAME_TYPE_PITCH) {
copy_params[i].srcMemoryType = CU_MEMORYTYPE_DEVICE;
copy_params[i].srcDevice =
((CUdeviceptr) eglFrame.frame.pPitch[i]);
} else if (eglFrame.frameType == CU_EGL_FRAME_TYPE_ARRAY) {
copy_params[i].srcMemoryType = CU_MEMORYTYPE_ARRAY;
copy_params[i].srcArray = eglFrame.frame.pArray[i];
}
copy_params[i].srcPitch = plane_params->pitch[i];
} else {
if (eglFrame.frameType == CU_EGL_FRAME_TYPE_PITCH) {
copy_params[i].dstMemoryType = CU_MEMORYTYPE_DEVICE;
copy_params[i].dstDevice =
((CUdeviceptr) eglFrame.frame.pPitch[i]);
} else if (eglFrame.frameType == CU_EGL_FRAME_TYPE_ARRAY) {
copy_params[i].dstMemoryType = CU_MEMORYTYPE_ARRAY;
copy_params[i].dstArray = eglFrame.frame.pArray[i];
}
copy_params[i].dstPitch = plane_params->pitch[i];
}
}
if (NvBufSurfaceUnMapEglImage (surface, 0) != 0) {
GST_ERROR ("could not unmap EGL Image from NvBufSurface");
goto error;
}
break;
}
#endif /* HAVE_CUDA_NVMM_JETSON */
case NVBUF_MEM_CUDA_DEVICE:
{
for (i = 0; i < plane_params->num_planes; i++) {
if (is_src) {
copy_params[i].srcMemoryType = CU_MEMORYTYPE_DEVICE;
copy_params[i].srcDevice = (CUdeviceptr)
((guint8 *) surface_params->dataPtr + plane_params->offset[i]);
copy_params[i].srcPitch = plane_params->pitch[i];
} else {
copy_params[i].dstMemoryType = CU_MEMORYTYPE_DEVICE;
copy_params[i].dstDevice = (CUdeviceptr)
((guint8 *) surface_params->dataPtr + plane_params->offset[i]);
copy_params[i].dstPitch = plane_params->pitch[i];
}
}
break;
}
case NVBUF_MEM_CUDA_PINNED:
{
for (i = 0; i < plane_params->num_planes; i++) {
if (is_src) {
copy_params[i].srcMemoryType = CU_MEMORYTYPE_HOST;
copy_params[i].srcHost =
((guint8 *) surface_params->dataPtr + plane_params->offset[i]);
copy_params[i].srcPitch = plane_params->pitch[i];
} else {
copy_params[i].dstMemoryType = CU_MEMORYTYPE_HOST;
copy_params[i].dstHost =
((guint8 *) surface_params->dataPtr + plane_params->offset[i]);
copy_params[i].dstPitch = plane_params->pitch[i];
}
}
break;
}
case NVBUF_MEM_CUDA_UNIFIED:
{
for (i = 0; i < plane_params->num_planes; i++) {
if (is_src) {
copy_params[i].srcMemoryType = CU_MEMORYTYPE_UNIFIED;
copy_params[i].srcDevice = (CUdeviceptr)
((guint8 *) surface_params->dataPtr + plane_params->offset[i]);
copy_params[i].srcPitch = plane_params->pitch[i];
} else {
copy_params[i].dstMemoryType = CU_MEMORYTYPE_UNIFIED;
copy_params[i].dstDevice = (CUdeviceptr)
((guint8 *) surface_params->dataPtr + plane_params->offset[i]);
copy_params[i].dstPitch = plane_params->pitch[i];
}
}
break;
}
default:
GST_ERROR ("Unexpected NVMM memory type %d", surface->memType);
goto error;
}
for (i = 0; i < plane_params->num_planes; i++) {
gsize width_in_bytes, height;
width_in_bytes = plane_params->width[i] * plane_params->bytesPerPix[i];
height = plane_params->height[i];
if (copy_params[i].WidthInBytes == 0 ||
width_in_bytes < copy_params[i].WidthInBytes) {
copy_params[i].WidthInBytes = width_in_bytes;
}
if (copy_params[i].Height == 0 || height < copy_params[i].Height) {
copy_params[i].Height = height;
}
}
} else
#endif /* HAVE_CUDA_NVMM */
{
GstMapFlags map_flags;
if (is_src)
map_flags = GST_MAP_READ;
else
map_flags = GST_MAP_WRITE;
if (copy_type == GST_CUDA_BUFFER_COPY_CUDA)
map_flags = (GstMapFlags) (map_flags | GST_MAP_CUDA);
if (!gst_video_frame_map (frame, info, buf, map_flags)) {
GST_ERROR ("Failed to map buffer");
goto error;
}
for (i = 0; i < GST_VIDEO_FRAME_N_PLANES (frame); i++) {
gsize width_in_bytes, height;
if (is_src) {
if (copy_type == GST_CUDA_BUFFER_COPY_CUDA) {
copy_params[i].srcMemoryType = CU_MEMORYTYPE_DEVICE;
copy_params[i].srcDevice =
(CUdeviceptr) GST_VIDEO_FRAME_PLANE_DATA (frame, i);
} else {
copy_params[i].srcMemoryType = CU_MEMORYTYPE_HOST;
copy_params[i].srcHost = GST_VIDEO_FRAME_PLANE_DATA (frame, i);
}
copy_params[i].srcPitch = GST_VIDEO_FRAME_PLANE_STRIDE (frame, i);
} else {
if (copy_type == GST_CUDA_BUFFER_COPY_CUDA) {
copy_params[i].dstMemoryType = CU_MEMORYTYPE_DEVICE;
copy_params[i].dstDevice =
(CUdeviceptr) GST_VIDEO_FRAME_PLANE_DATA (frame, i);
} else {
copy_params[i].dstMemoryType = CU_MEMORYTYPE_HOST;
copy_params[i].dstHost = GST_VIDEO_FRAME_PLANE_DATA (frame, i);
}
copy_params[i].dstPitch = GST_VIDEO_FRAME_PLANE_STRIDE (frame, i);
}
width_in_bytes = GST_VIDEO_FRAME_COMP_WIDTH (frame, i) *
GST_VIDEO_FRAME_COMP_PSTRIDE (frame, i);
height = GST_VIDEO_FRAME_COMP_HEIGHT (frame, i);
if (copy_params[i].WidthInBytes == 0 ||
width_in_bytes < copy_params[i].WidthInBytes) {
copy_params[i].WidthInBytes = width_in_bytes;
}
if (copy_params[i].Height == 0 || height < copy_params[i].Height) {
copy_params[i].Height = height;
}
}
}
return TRUE;
error:
if (buffer_mapped) {
gst_buffer_unmap (buf, map_info);
memset (map_info, 0, sizeof (GstMapInfo));
}
return FALSE;
}
static void
unmap_buffer_or_frame (GstBuffer * buf, GstVideoFrame * frame,
GstMapInfo * map_info)
{
if (frame->buffer)
gst_video_frame_unmap (frame);
if (map_info->data)
gst_buffer_unmap (buf, map_info);
}
static gboolean
gst_cuda_buffer_copy_internal (GstBuffer * dst_buf,
GstCudaBufferCopyType dst_type, const GstVideoInfo * dst_info,
GstBuffer * src_buf, GstCudaBufferCopyType src_type,
const GstVideoInfo * src_info, GstCudaContext * context, CUstream stream)
{
GstVideoFrame dst_frame, src_frame;
gboolean ret = FALSE;
GstMapInfo dst_map, src_map;
guint i;
CUDA_MEMCPY2D copy_params[GST_VIDEO_MAX_PLANES];
GstCudaGraphicsResource *nvSurfaceArrayResource = nullptr;
memset (copy_params, 0, sizeof (copy_params));
memset (&dst_frame, 0, sizeof (GstVideoFrame));
memset (&src_frame, 0, sizeof (GstVideoFrame));
memset (&dst_map, 0, sizeof (GstMapInfo));
memset (&src_map, 0, sizeof (GstMapInfo));
/* push context here, because if we have nvmm on jetson, we need the context */
if (!gst_cuda_context_push (context)) {
GST_ERROR_OBJECT (context, "Failed to push our context");
goto unmap_and_out;
}
#ifdef HAVE_CUDA_NVMM_JETSON
/* create new resource for mapping the EGL image from the surface */
nvSurfaceArrayResource =
gst_cuda_graphics_resource_new (context, NULL,
GstCudaGraphicsResourceType::GST_CUDA_GRAPHICS_RESOURCE_EGL_RESOURCE);
#endif
if (!map_buffer_and_fill_copy2d (dst_buf, dst_info,
dst_type, &dst_frame, &dst_map, FALSE, copy_params,
nvSurfaceArrayResource)) {
GST_ERROR_OBJECT (context, "Failed to map output buffer");
#ifdef HAVE_CUDA_NVMM_JETSON
gst_cuda_graphics_resource_unregister (nvSurfaceArrayResource);
gst_cuda_graphics_resource_free (nvSurfaceArrayResource);
nvSurfaceArrayResource = nullptr;
#endif
return FALSE;
}
if (!map_buffer_and_fill_copy2d (src_buf, src_info,
src_type, &src_frame, &src_map, TRUE, copy_params,
nvSurfaceArrayResource)) {
GST_ERROR_OBJECT (context, "Failed to map input buffer");
#ifdef HAVE_CUDA_NVMM_JETSON
gst_cuda_graphics_resource_unregister (nvSurfaceArrayResource);
gst_cuda_graphics_resource_free (nvSurfaceArrayResource);
nvSurfaceArrayResource = nullptr;
#endif
unmap_buffer_or_frame (dst_buf, &dst_frame, &dst_map);
return FALSE;
}
for (i = 0; i < GST_VIDEO_INFO_N_PLANES (dst_info); i++) {
ret = gst_cuda_result (CuMemcpy2DAsync (&copy_params[i], stream));
if (!ret) {
GST_ERROR_OBJECT (context, "Failed to copy plane %d", i);
break;
}
}
gst_cuda_result (CuStreamSynchronize (stream));
#ifdef HAVE_CUDA_NVMM_JETSON
gst_cuda_graphics_resource_unregister (nvSurfaceArrayResource);
gst_cuda_graphics_resource_free (nvSurfaceArrayResource);
nvSurfaceArrayResource = NULL;
#endif
gst_cuda_context_pop (nullptr);
unmap_and_out:
unmap_buffer_or_frame (dst_buf, &src_frame, &src_map);
unmap_buffer_or_frame (src_buf, &dst_frame, &dst_map);
return ret;
}
#ifdef HAVE_CUDA_GST_GL
static gboolean
ensure_gl_interop (void)
{
guint device_count = 0;
CUdevice device_list[1] = { 0, };
CUresult cuda_ret;
cuda_ret = CuGLGetDevices (&device_count,
device_list, 1, CU_GL_DEVICE_LIST_ALL);
if (cuda_ret != CUDA_SUCCESS || device_count == 0)
return FALSE;
return TRUE;
}
typedef struct _GLCopyData
{
GstBuffer *src_buf;
const GstVideoInfo *src_info;
GstBuffer *dst_buf;
const GstVideoInfo *dst_info;
gboolean pbo_to_cuda;
GstCudaBufferCopyType copy_type;
GstCudaContext *context;
CUstream stream;
gboolean ret;
} GLCopyData;
static GstCudaGraphicsResource *
ensure_cuda_gl_graphics_resource (GstCudaContext * context, GstMemory * mem)
{
GQuark quark;
GstCudaGraphicsResource *ret = nullptr;
if (!gst_is_gl_memory_pbo (mem)) {
GST_WARNING_OBJECT (context, "memory is not GL PBO memory, %s",
mem->allocator->mem_type);
return nullptr;
}
quark = gst_cuda_quark_from_id (GST_CUDA_QUARK_GRAPHICS_RESOURCE);
ret = (GstCudaGraphicsResource *)
gst_mini_object_get_qdata (GST_MINI_OBJECT (mem), quark);
if (!ret) {
GstGLMemoryPBO *pbo;
GstGLBuffer *buf;
GstMapInfo info;
ret = gst_cuda_graphics_resource_new (context,
GST_OBJECT (GST_GL_BASE_MEMORY_CAST (mem)->context),
GST_CUDA_GRAPHICS_RESOURCE_GL_BUFFER);
if (!gst_memory_map (mem, &info, (GstMapFlags) (GST_MAP_READ | GST_MAP_GL))) {
GST_ERROR_OBJECT (context, "Failed to map gl memory");
gst_cuda_graphics_resource_free (ret);
return nullptr;
}
pbo = (GstGLMemoryPBO *) mem;
buf = pbo->pbo;
if (!gst_cuda_graphics_resource_register_gl_buffer (ret,
buf->id, CU_GRAPHICS_REGISTER_FLAGS_NONE)) {
GST_ERROR_OBJECT (context, "Failed to register gl buffer");
gst_memory_unmap (mem, &info);
gst_cuda_graphics_resource_free (ret);
return nullptr;
}
gst_memory_unmap (mem, &info);
gst_mini_object_set_qdata (GST_MINI_OBJECT (mem), quark, ret,
(GDestroyNotify) gst_cuda_graphics_resource_free);
}
return ret;
}
static void
gl_copy_thread_func (GstGLContext * gl_context, GLCopyData * data)
{
GstCudaGraphicsResource *resources[GST_VIDEO_MAX_PLANES];
GstCudaGraphicsResource *nvSurfaceArrayResource = nullptr;
guint num_resources;
GstBuffer *gl_buf, *cuda_buf;
GstVideoFrame cuda_frame;
GstMapInfo cuda_map_info;
CUDA_MEMCPY2D copy_params[GST_VIDEO_MAX_PLANES];
guint i;
GstCudaContext *context = data->context;
CUstream stream = data->stream;
memset (copy_params, 0, sizeof (copy_params));
memset (&cuda_frame, 0, sizeof (GstVideoFrame));
memset (&cuda_map_info, 0, sizeof (GstMapInfo));
data->ret = FALSE;
/* Incompatible gl context */
if (!ensure_gl_interop ())
return;
if (!gst_cuda_context_push (context)) {
GST_ERROR_OBJECT (context, "Failed to push context");
unmap_buffer_or_frame (data->pbo_to_cuda ? data->dst_buf : data->src_buf,
&cuda_frame, &cuda_map_info);
return;
}
#ifdef HAVE_CUDA_NVMM_JETSON
nvSurfaceArrayResource =
gst_cuda_graphics_resource_new (context, NULL,
GstCudaGraphicsResourceType::GST_CUDA_GRAPHICS_RESOURCE_EGL_RESOURCE);
#endif
if (data->pbo_to_cuda) {
gl_buf = data->src_buf;
cuda_buf = data->dst_buf;
if (!map_buffer_and_fill_copy2d (cuda_buf,
data->dst_info, data->copy_type, &cuda_frame, &cuda_map_info,
FALSE, copy_params, nvSurfaceArrayResource)) {
GST_ERROR_OBJECT (context, "Failed to map output CUDA buffer");
#ifdef HAVE_CUDA_NVMM_JETSON
gst_cuda_graphics_resource_unregister (nvSurfaceArrayResource);
gst_cuda_graphics_resource_free (nvSurfaceArrayResource);
nvSurfaceArrayResource = nullptr;
#endif
return;
}
} else {
gl_buf = data->dst_buf;
cuda_buf = data->src_buf;
if (!map_buffer_and_fill_copy2d (cuda_buf,
data->src_info, data->copy_type, &cuda_frame, &cuda_map_info,
TRUE, copy_params, nvSurfaceArrayResource)) {
GST_ERROR_OBJECT (context, "Failed to map input CUDA buffer");
#ifdef HAVE_CUDA_NVMM_JETSON
gst_cuda_graphics_resource_unregister (nvSurfaceArrayResource);
gst_cuda_graphics_resource_free (nvSurfaceArrayResource);
nvSurfaceArrayResource = nullptr;
#endif
return;
}
}
num_resources = gst_buffer_n_memory (gl_buf);
g_assert (num_resources >= GST_VIDEO_INFO_N_PLANES (data->src_info));
for (i = 0; i < GST_VIDEO_INFO_N_PLANES (data->src_info); i++) {
GstMemory *mem = gst_buffer_peek_memory (gl_buf, i);
GstGLMemoryPBO *pbo;
resources[i] = ensure_cuda_gl_graphics_resource (context, mem);
if (!resources[i])
goto out;
pbo = (GstGLMemoryPBO *) mem;
if (!data->pbo_to_cuda) {
/* Need PBO -> texture */
GST_MINI_OBJECT_FLAG_SET (mem, GST_GL_BASE_MEMORY_TRANSFER_NEED_UPLOAD);
/* PBO -> sysmem */
GST_MINI_OBJECT_FLAG_SET (pbo->pbo,
GST_GL_BASE_MEMORY_TRANSFER_NEED_DOWNLOAD);
} else {
/* get the texture into the PBO */
gst_gl_memory_pbo_upload_transfer (pbo);
gst_gl_memory_pbo_download_transfer (pbo);
}
}
for (i = 0; i < GST_VIDEO_INFO_N_PLANES (data->src_info); i++) {
CUgraphicsResource cuda_resource;
CUdeviceptr dev_ptr;
size_t size;
gboolean copy_ret;
gsize width_in_bytes, height;
if (data->pbo_to_cuda) {
cuda_resource =
gst_cuda_graphics_resource_map (resources[i], stream,
CU_GRAPHICS_MAP_RESOURCE_FLAGS_READ_ONLY);
} else {
cuda_resource =
gst_cuda_graphics_resource_map (resources[i], stream,
CU_GRAPHICS_MAP_RESOURCE_FLAGS_WRITE_DISCARD);
}
if (!cuda_resource) {
GST_ERROR_OBJECT (context, "Failed to map graphics resource %d", i);
goto out;
}
if (!gst_cuda_result (CuGraphicsResourceGetMappedPointer (&dev_ptr, &size,
cuda_resource))) {
gst_cuda_graphics_resource_unmap (resources[i], stream);
GST_ERROR_OBJECT (context, "Failed to get mapped pointer");
goto out;
}
if (data->pbo_to_cuda) {
copy_params[i].srcMemoryType = CU_MEMORYTYPE_DEVICE;
copy_params[i].srcDevice = dev_ptr;
copy_params[i].srcPitch = GST_VIDEO_INFO_PLANE_STRIDE (data->src_info, i);
width_in_bytes = GST_VIDEO_INFO_COMP_WIDTH (data->src_info, i) *
GST_VIDEO_INFO_COMP_PSTRIDE (data->src_info, i);
height = GST_VIDEO_INFO_COMP_HEIGHT (data->src_info, i);
} else {
copy_params[i].dstMemoryType = CU_MEMORYTYPE_DEVICE;
copy_params[i].dstDevice = dev_ptr;
copy_params[i].dstPitch = GST_VIDEO_INFO_PLANE_STRIDE (data->dst_info, i);
width_in_bytes = GST_VIDEO_INFO_COMP_WIDTH (data->dst_info, i) *
GST_VIDEO_INFO_COMP_PSTRIDE (data->dst_info, i);
height = GST_VIDEO_INFO_COMP_HEIGHT (data->dst_info, i);
}
if (width_in_bytes < copy_params[i].WidthInBytes)
copy_params[i].WidthInBytes = width_in_bytes;
if (height < copy_params[i].Height)
copy_params[i].Height = height;
copy_ret = gst_cuda_result (CuMemcpy2DAsync (&copy_params[i], stream));
gst_cuda_graphics_resource_unmap (resources[i], stream);
if (!copy_ret) {
GST_ERROR_OBJECT (context, "Failed to copy plane %d", i);
goto out;
}
}
data->ret = TRUE;
out:
gst_cuda_result (CuStreamSynchronize (stream));
#ifdef HAVE_CUDA_NVMM_JETSON
gst_cuda_graphics_resource_unregister (nvSurfaceArrayResource);
gst_cuda_graphics_resource_free (nvSurfaceArrayResource);
#endif
gst_cuda_context_pop (nullptr);
unmap_buffer_or_frame (cuda_buf, &cuda_frame, &cuda_map_info);
}
static gboolean
cuda_copy_gl_interop (GstBuffer * dst_buf, const GstVideoInfo * dst_info,
GstBuffer * src_buf, const GstVideoInfo * src_info,
GstGLContext * gl_context, GstCudaContext * context, CUstream stream,
gboolean pbo_to_cuda, GstCudaBufferCopyType copy_type)
{
GLCopyData data;
g_assert (copy_type == GST_CUDA_BUFFER_COPY_CUDA ||
copy_type == GST_CUDA_BUFFER_COPY_NVMM);
data.src_buf = src_buf;
data.src_info = src_info;
data.dst_buf = dst_buf;
data.dst_info = dst_info;
data.pbo_to_cuda = pbo_to_cuda;
data.copy_type = copy_type;
data.context = context;
data.stream = stream;
data.ret = FALSE;
gst_gl_context_thread_add (gl_context,
(GstGLContextThreadFunc) gl_copy_thread_func, &data);
return data.ret;
}
#endif
#ifdef G_OS_WIN32
static gboolean
ensure_d3d11_interop (GstCudaContext * context, GstD3D11Device * device)
{
guint device_count = 0;
guint cuda_device_id;
CUdevice device_list[1] = { 0, };
CUresult cuda_ret;
g_object_get (context, "cuda-device-id", &cuda_device_id, nullptr);
cuda_ret = CuD3D11GetDevices (&device_count,
device_list, 1, gst_d3d11_device_get_device_handle (device),
CU_D3D11_DEVICE_LIST_ALL);
if (cuda_ret != CUDA_SUCCESS || device_count == 0)
return FALSE;
if (device_list[0] != (CUdevice) cuda_device_id)
return FALSE;
return TRUE;
}
static GstCudaGraphicsResource *
ensure_cuda_d3d11_graphics_resource (GstCudaContext * context, GstMemory * mem)
{
static gint64 d3d11_interop_token = 0;
static std::once_flag once_flag;
GstCudaGraphicsResource *ret = nullptr;
GstD3D11Memory *dmem;
GST_CUDA_CALL_ONCE_BEGIN {
d3d11_interop_token = gst_d3d11_create_user_token ();
} GST_CUDA_CALL_ONCE_END;
if (!gst_is_d3d11_memory (mem)) {
GST_WARNING_OBJECT (context, "memory is not D3D11 memory, %s",
mem->allocator->mem_type);
return nullptr;
}
dmem = GST_D3D11_MEMORY_CAST (mem);
ret = (GstCudaGraphicsResource *)
gst_d3d11_memory_get_token_data (dmem, d3d11_interop_token);
if (!ret) {
ret = gst_cuda_graphics_resource_new (context,
GST_OBJECT (GST_D3D11_MEMORY_CAST (mem)->device),
GST_CUDA_GRAPHICS_RESOURCE_D3D11_RESOURCE);
if (!gst_cuda_graphics_resource_register_d3d11_resource (ret,
gst_d3d11_memory_get_resource_handle (GST_D3D11_MEMORY_CAST (mem)),
CU_GRAPHICS_REGISTER_FLAGS_SURFACE_LOAD_STORE)) {
GST_ERROR_OBJECT (context, "failed to register d3d11 resource");
gst_cuda_graphics_resource_free (ret);
ret = nullptr;
} else {
gst_d3d11_memory_set_token_data (dmem, d3d11_interop_token, ret,
(GDestroyNotify) gst_cuda_graphics_resource_free);
}
}
return ret;
}
static gboolean
cuda_copy_d3d11_interop (GstBuffer * dst_buf, const GstVideoInfo * dst_info,
GstBuffer * src_buf, const GstVideoInfo * src_info, GstD3D11Device * device,
GstCudaContext * context, CUstream stream, gboolean d3d11_to_cuda)
{
GstCudaGraphicsResource *resources[GST_VIDEO_MAX_PLANES];
D3D11_TEXTURE2D_DESC desc[GST_VIDEO_MAX_PLANES];
guint num_resources;
GstBuffer *d3d11_buf, *cuda_buf;
GstVideoFrame d3d11_frame, cuda_frame;
GstMapInfo cuda_map_info;
CUDA_MEMCPY2D copy_params[GST_VIDEO_MAX_PLANES];
guint i;
gboolean ret = FALSE;
memset (copy_params, 0, sizeof (copy_params));
memset (&cuda_frame, 0, sizeof (GstVideoFrame));
memset (&cuda_map_info, 0, sizeof (GstMapInfo));
/* Incompatible d3d11 device */
if (!ensure_d3d11_interop (context, device))
return FALSE;
if (d3d11_to_cuda) {
d3d11_buf = src_buf;
cuda_buf = dst_buf;
if (!gst_video_frame_map (&d3d11_frame, src_info, d3d11_buf,
(GstMapFlags) (GST_MAP_READ | GST_MAP_D3D11))) {
GST_ERROR_OBJECT (context, "Failed to map input D3D11 buffer");
return FALSE;
}
if (!map_buffer_and_fill_copy2d (cuda_buf,
dst_info, GST_CUDA_BUFFER_COPY_CUDA, &cuda_frame, &cuda_map_info,
FALSE, copy_params, NULL)) {
GST_ERROR_OBJECT (context, "Failed to map output CUDA buffer");
gst_video_frame_unmap (&d3d11_frame);
return FALSE;
}
} else {
d3d11_buf = dst_buf;
cuda_buf = src_buf;
if (!gst_video_frame_map (&d3d11_frame, dst_info, d3d11_buf,
(GstMapFlags) (GST_MAP_WRITE | GST_MAP_D3D11))) {
GST_ERROR_OBJECT (context, "Failed to map output D3D11 buffer");
return FALSE;
}
if (!map_buffer_and_fill_copy2d (cuda_buf,
src_info, GST_CUDA_BUFFER_COPY_CUDA, &cuda_frame, &cuda_map_info,
TRUE, copy_params, NULL)) {
GST_ERROR_OBJECT (context, "Failed to map input CUDA buffer");
gst_video_frame_unmap (&d3d11_frame);
return FALSE;
}
}
num_resources = gst_buffer_n_memory (d3d11_buf);
g_assert (num_resources >= GST_VIDEO_FRAME_N_PLANES (&d3d11_frame));
if (!gst_cuda_context_push (context)) {
GST_ERROR_OBJECT (context, "Failed to push context");
gst_video_frame_unmap (&d3d11_frame);
unmap_buffer_or_frame (cuda_buf, &cuda_frame, &cuda_map_info);
return FALSE;
}
for (i = 0; i < GST_VIDEO_FRAME_N_PLANES (&d3d11_frame); i++) {
GstMemory *mem = gst_buffer_peek_memory (d3d11_buf, i);
resources[i] = ensure_cuda_d3d11_graphics_resource (context, mem);
if (!resources[i]
|| !gst_d3d11_memory_get_texture_desc (GST_D3D11_MEMORY_CAST (mem),
&desc[i]))
goto out;
}
for (i = 0; i < GST_VIDEO_FRAME_N_PLANES (&d3d11_frame); i++) {
CUgraphicsResource cuda_resource;
CUarray d3d11_array;
gboolean copy_ret;
if (d3d11_to_cuda) {
cuda_resource =
gst_cuda_graphics_resource_map (resources[i], stream,
CU_GRAPHICS_MAP_RESOURCE_FLAGS_READ_ONLY);
} else {
cuda_resource =
gst_cuda_graphics_resource_map (resources[i], stream,
CU_GRAPHICS_MAP_RESOURCE_FLAGS_WRITE_DISCARD);
}
if (!cuda_resource) {
GST_ERROR_OBJECT (context, "Failed to map graphics resource %d", i);
goto out;
}
if (!gst_cuda_result (CuGraphicsSubResourceGetMappedArray (&d3d11_array,
cuda_resource, 0, 0))) {
gst_cuda_graphics_resource_unmap (resources[i], stream);
GST_ERROR_OBJECT (context, "Failed to get mapped array");
goto out;
}
if (d3d11_to_cuda) {
copy_params[i].srcMemoryType = CU_MEMORYTYPE_ARRAY;
copy_params[i].srcArray = d3d11_array;
copy_params[i].srcPitch =
desc[i].Width * GST_VIDEO_FRAME_COMP_PSTRIDE (&d3d11_frame, i);
} else {
copy_params[i].dstMemoryType = CU_MEMORYTYPE_ARRAY;
copy_params[i].dstArray = d3d11_array;
copy_params[i].dstPitch =
desc[i].Width * GST_VIDEO_FRAME_COMP_PSTRIDE (&d3d11_frame, i);
}
copy_ret = gst_cuda_result (CuMemcpy2DAsync (&copy_params[i], stream));
gst_cuda_graphics_resource_unmap (resources[i], stream);
if (!copy_ret) {
GST_ERROR_OBJECT (context, "Failed to copy plane %d", i);
goto out;
}
}
ret = TRUE;
out:
gst_cuda_result (CuStreamSynchronize (stream));
gst_cuda_context_pop (nullptr);
gst_video_frame_unmap (&d3d11_frame);
unmap_buffer_or_frame (cuda_buf, &cuda_frame, &cuda_map_info);
return ret;
}
#endif
gboolean
gst_cuda_buffer_copy (GstBuffer * dst, GstCudaBufferCopyType dst_type,
const GstVideoInfo * dst_info, GstBuffer * src,
GstCudaBufferCopyType src_type, const GstVideoInfo * src_info,
GstCudaContext * context, GstCudaStream * stream)
{
gboolean use_copy_2d = FALSE;
GstMemory *dst_mem, *src_mem;
#ifdef G_OS_WIN32
D3D11_TEXTURE2D_DESC desc;
#endif
GstCudaContext *cuda_context = context;
GstCudaMemory *cmem = nullptr;
GstCudaStream *mem_stream = nullptr;
gboolean ret;
g_return_val_if_fail (GST_IS_BUFFER (dst), FALSE);
g_return_val_if_fail (dst_info != nullptr, FALSE);
g_return_val_if_fail (GST_IS_BUFFER (src), FALSE);
g_return_val_if_fail (src_info != nullptr, FALSE);
g_return_val_if_fail (GST_IS_CUDA_CONTEXT (context), FALSE);
_init_debug ();
if (dst_type == GST_CUDA_BUFFER_COPY_NVMM &&
src_type == GST_CUDA_BUFFER_COPY_NVMM) {
GST_ERROR_OBJECT (context, "Not supported copy NVMM -> NVMM");
return FALSE;
}
if (GST_VIDEO_INFO_FORMAT (dst_info) != GST_VIDEO_INFO_FORMAT (src_info)) {
GST_ERROR_OBJECT (context,
"Copy between different format is not supported");
return FALSE;
}
if (dst_type == GST_CUDA_BUFFER_COPY_CUDA ||
dst_type == GST_CUDA_BUFFER_COPY_NVMM ||
src_type == GST_CUDA_BUFFER_COPY_CUDA ||
src_type == GST_CUDA_BUFFER_COPY_NVMM) {
use_copy_2d = TRUE;
}
if (!use_copy_2d) {
GST_TRACE_OBJECT (context, "Not a device memory, use system memory copy");
return gst_cuda_buffer_fallback_copy (dst, dst_info, src, src_info);
}
dst_mem = gst_buffer_peek_memory (dst, 0);
src_mem = gst_buffer_peek_memory (src, 0);
#ifdef HAVE_CUDA_GST_GL
if (src_type == GST_CUDA_BUFFER_COPY_GL && gst_is_gl_memory_pbo (src_mem)) {
GstGLMemory *gl_mem = (GstGLMemory *) src_mem;
GstGLContext *gl_context = gl_mem->mem.context;
if (dst_type == GST_CUDA_BUFFER_COPY_CUDA && gst_is_cuda_memory (dst_mem)) {
cmem = GST_CUDA_MEMORY_CAST (dst_mem);
cuda_context = cmem->context;
mem_stream = gst_cuda_memory_get_stream (cmem);
if (mem_stream)
stream = mem_stream;
}
GST_TRACE_OBJECT (context, "GL -> %s",
gst_cuda_buffer_copy_type_to_string (dst_type));
ret = cuda_copy_gl_interop (dst, dst_info, src, src_info, gl_context,
cuda_context, gst_cuda_stream_get_handle (stream), TRUE, dst_type);
if (cmem)
GST_MEMORY_FLAG_UNSET (cmem, GST_CUDA_MEMORY_TRANSFER_NEED_SYNC);
return ret;
}
if (dst_type == GST_CUDA_BUFFER_COPY_GL && gst_is_gl_memory_pbo (dst_mem)) {
GstGLMemory *gl_mem = (GstGLMemory *) dst_mem;
GstGLContext *gl_context = gl_mem->mem.context;
if (src_type == GST_CUDA_BUFFER_COPY_CUDA && gst_is_cuda_memory (src_mem)) {
cmem = GST_CUDA_MEMORY_CAST (src_mem);
cuda_context = cmem->context;
/* Use memory's stream object if available */
mem_stream = gst_cuda_memory_get_stream (cmem);
if (mem_stream)
stream = mem_stream;
}
GST_TRACE_OBJECT (context, "%s -> GL",
gst_cuda_buffer_copy_type_to_string (src_type));
return cuda_copy_gl_interop (dst, dst_info, src, src_info, gl_context,
cuda_context, gst_cuda_stream_get_handle (stream), FALSE, src_type);
}
#endif
#ifdef G_OS_WIN32
if (src_type == GST_CUDA_BUFFER_COPY_D3D11 && gst_is_d3d11_memory (src_mem) &&
gst_d3d11_memory_get_texture_desc (GST_D3D11_MEMORY_CAST (src_mem), &desc)
&& desc.Usage == D3D11_USAGE_DEFAULT && gst_is_cuda_memory (dst_mem)) {
GstD3D11Memory *dmem = GST_D3D11_MEMORY_CAST (src_mem);
GstD3D11Device *device = dmem->device;
cmem = GST_CUDA_MEMORY_CAST (dst_mem);
cuda_context = cmem->context;
/* Use memory's stream object if available */
mem_stream = gst_cuda_memory_get_stream (cmem);
if (mem_stream)
stream = mem_stream;
GST_TRACE_OBJECT (context, "D3D11 -> CUDA");
gst_d3d11_device_lock (device);
ret = cuda_copy_d3d11_interop (dst, dst_info, src, src_info, device,
cuda_context, gst_cuda_stream_get_handle (stream), TRUE);
gst_d3d11_device_unlock (device);
GST_MEMORY_FLAG_UNSET (cmem, GST_CUDA_MEMORY_TRANSFER_NEED_SYNC);
return ret;
}
if (dst_type == GST_CUDA_BUFFER_COPY_D3D11 && gst_is_d3d11_memory (dst_mem) &&
gst_d3d11_memory_get_texture_desc (GST_D3D11_MEMORY_CAST (dst_mem), &desc)
&& desc.Usage == D3D11_USAGE_DEFAULT && gst_is_cuda_memory (src_mem)) {
GstD3D11Memory *dmem = GST_D3D11_MEMORY_CAST (dst_mem);
GstD3D11Device *device = dmem->device;
cmem = GST_CUDA_MEMORY_CAST (src_mem);
cuda_context = cmem->context;
/* Use memory's stream object if available */
mem_stream = gst_cuda_memory_get_stream (cmem);
if (mem_stream)
stream = mem_stream;
GST_TRACE_OBJECT (context, "CUDA -> D3D11");
gst_d3d11_device_lock (device);
ret = cuda_copy_d3d11_interop (dst, dst_info, src, src_info, device,
cuda_context, gst_cuda_stream_get_handle (stream), FALSE);
gst_d3d11_device_unlock (device);
return ret;
}
#endif
if (gst_is_cuda_memory (dst_mem)) {
cmem = GST_CUDA_MEMORY_CAST (dst_mem);
} else if (gst_is_cuda_memory (src_mem)) {
cmem = GST_CUDA_MEMORY_CAST (src_mem);
} else {
cmem = nullptr;
}
if (cmem) {
context = cmem->context;
mem_stream = gst_cuda_memory_get_stream (cmem);
if (mem_stream)
stream = mem_stream;
}
GST_TRACE_OBJECT (context, "%s -> %s",
gst_cuda_buffer_copy_type_to_string (src_type),
gst_cuda_buffer_copy_type_to_string (dst_type));
ret = gst_cuda_buffer_copy_internal (dst, dst_type, dst_info,
src, src_type, src_info, cuda_context,
gst_cuda_stream_get_handle (stream));
/* Already synchronized */
if (gst_is_cuda_memory (src_mem))
GST_MEMORY_FLAG_UNSET (src_mem, GST_CUDA_MEMORY_TRANSFER_NEED_SYNC);
return ret;
}
/**
* gst_cuda_create_user_token:
*
* Creates new user token value
*
* Returns: user token value
*
* Since: 1.24
*/
gint64
gst_cuda_create_user_token (void)
{
/* *INDENT-OFF* */
static std::atomic < gint64 > user_token { 0 };
/* *INDENT-ON* */
return user_token.fetch_add (1);
}
static gboolean
_abort_on_error (CUresult result)
{
static std::set < CUresult > abort_list;
GST_CUDA_CALL_ONCE_BEGIN {
const gchar *env = g_getenv ("GST_CUDA_CRITICAL_ERRORS");
if (!env)
return;
gchar **split = g_strsplit (env, ",", 0);
gchar **iter;
for (iter = split; *iter; iter++) {
int error_code = 0;
try {
error_code = std::stoi (*iter);
} catch ( ...) {
GST_WARNING ("Invalid argument \"%s\"", *iter);
continue;
};
if (error_code > 0)
abort_list.insert ((CUresult) error_code);
}
g_strfreev (split);
}
GST_CUDA_CALL_ONCE_END;
if (abort_list.empty ())
return FALSE;
if (abort_list.find (result) != abort_list.end ())
return TRUE;
return FALSE;
}
/**
* _gst_cuda_debug:
* @result: CUDA result code
* @cat: a #GstDebugCategory
* @file: the file that checking the result code
* @function: the function that checking the result code
* @line: the line that checking the result code
*
* Returns: %TRUE if CUDA device API call result is CUDA_SUCCESS
*
* Since: 1.24
*/
gboolean
_gst_cuda_debug (CUresult result, GstDebugCategory * cat,
const gchar * file, const gchar * function, gint line)
{
if (result != CUDA_SUCCESS) {
#ifndef GST_DISABLE_GST_DEBUG
const gchar *_error_name, *_error_text;
CuGetErrorName (result, &_error_name);
CuGetErrorString (result, &_error_text);
gst_debug_log (cat, GST_LEVEL_WARNING, file, function, line,
NULL, "CUDA call failed: %s, %s", _error_name, _error_text);
#endif
if (_abort_on_error (result)) {
GST_ERROR ("Critical error %d, abort", (gint) result);
g_abort ();
}
return FALSE;
}
return TRUE;
}
#ifdef G_OS_WIN32
/* Defined in ntdef.h */
struct OBJECT_ATTRIBUTES
{
ULONG Length;
HANDLE RootDirectory;
PVOID ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor;
PVOID SecurityQualityOfService;
};
#endif
gpointer
gst_cuda_get_win32_handle_metadata (void)
{
#ifdef G_OS_WIN32
static const gchar sddl[] = "D:P(OA;;GARCSDWDWORPWPCCDCLCSWLODTCRFA;;;WD)";
static OBJECT_ATTRIBUTES attr;
static BOOL initialized = FALSE;
/*
* D:P(string_ace) -> DACL protected with "string_ace"
*
* ace_string format
* ace_type;ace_flags;rights;object_guid;inherit_object_guid;account_sid
*
* ace_type: OA (ACCESS_ALLOWED_OBJECT_ACE_TYPE)
*
* ace_flags: (empty)
*
* rights: GARCSDWDWOCCDCLCSWLODTWPRPCRFA (allow all)
* - Generic access rights
* GA (GENERIC_ALL)
* - Standard access right
* RC (READ_CONTROL) SD (DELETE) WD (WRITE_DAC) WO (WRITE_OWNER)
* - Directory service object access right
* RP (ADS_RIGHT_DS_READ_PROP)
* WP (ADS_RIGHT_DS_WRITE_PROP)
* CC (ADS_RIGHT_DS_CREATE_CHILD)
* DC (ADS_RIGHT_DS_DELETE_CHILD)
* LC (ADS_RIGHT_ACTRL_DS_LIST)
* SW (ADS_RIGHT_DS_SELF)
* LO (ADS_RIGHT_DS_LIST_OBJECT)
* DT (ADS_RIGHT_DS_DELETE_TREE)
* CR (ADS_RIGHT_DS_CONTROL_ACCESS)
* - File access rights
* FA (FILE_GENERIC_ALL)
*
* object_guid: (empty)
*
* inherit_object_guid (empty)
*
* account_sid: WD (SDDL_EVERYONE)
*
* See also
* https://learn.microsoft.com/en-us/windows/win32/secauthz/security-descriptor-string-format
*/
GST_CUDA_CALL_ONCE_BEGIN {
PSECURITY_DESCRIPTOR desc;
memset (&attr, 0, sizeof (OBJECT_ATTRIBUTES));
initialized = ConvertStringSecurityDescriptorToSecurityDescriptorA (sddl,
SDDL_REVISION_1, &desc, nullptr);
if (!initialized)
return;
attr.Length = sizeof (OBJECT_ATTRIBUTES);
attr.SecurityDescriptor = desc;
}
GST_CUDA_CALL_ONCE_END;
if (!initialized)
return nullptr;
return &attr;
#else
return nullptr;
#endif
}