gstreamer/subprojects/gst-plugins-bad/sys/d3d11/gstd3d11window_dummy.cpp
Seungha Yang a27c5c81df d3d11videosink: Fix deadlock when parent window is busy
Deadlock sequence:
* From a streaming thread, d3d11videosink sends synchronous message
  to the parent window, so that internal (child) window can be
  constructed on the parent window's thread
* App thread (parent window thread) is waiting for pipeline's
  state change (to GST_STATE_NULL) but streaming thread is
  blocked and waiting for app thread

To avoid the deadlock, GstD3D11WindowWin32 should send message
to the parent window asynchronously.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/3570>
2022-12-15 17:37:48 +00:00

336 lines
10 KiB
C++

/*
* GStreamer
* Copyright (C) 2020 Seungha Yang <seungha@centricular.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 "gstd3d11window_dummy.h"
#include "gstd3d11pluginutils.h"
#include <wrl.h>
/* *INDENT-OFF* */
using namespace Microsoft::WRL;
/* *INDENT-ON* */
GST_DEBUG_CATEGORY_EXTERN (gst_d3d11_window_debug);
#define GST_CAT_DEFAULT gst_d3d11_window_debug
struct _GstD3D11WindowDummy
{
GstD3D11Window parent;
ID3D11Texture2D *fallback_texture;
ID3D11VideoProcessorOutputView *fallback_pov;
ID3D11RenderTargetView *fallback_rtv;
GstD3D11Fence *fence;
};
#define gst_d3d11_window_dummy_parent_class parent_class
G_DEFINE_TYPE (GstD3D11WindowDummy, gst_d3d11_window_dummy,
GST_TYPE_D3D11_WINDOW);
static void gst_d3d11_window_dummy_on_resize (GstD3D11Window * window,
guint width, guint height);
static GstFlowReturn gst_d3d11_window_dummy_prepare (GstD3D11Window * window,
guint display_width, guint display_height, GstCaps * caps,
GstStructure * config, DXGI_FORMAT display_format, GError ** error);
static void gst_d3d11_window_dummy_unprepare (GstD3D11Window * window);
static gboolean
gst_d3d11_window_dummy_open_shared_handle (GstD3D11Window * window,
GstD3D11WindowSharedHandleData * data);
static gboolean
gst_d3d11_window_dummy_release_shared_handle (GstD3D11Window * window,
GstD3D11WindowSharedHandleData * data);
static void
gst_d3d11_window_dummy_class_init (GstD3D11WindowDummyClass * klass)
{
GstD3D11WindowClass *window_class = GST_D3D11_WINDOW_CLASS (klass);
window_class->on_resize =
GST_DEBUG_FUNCPTR (gst_d3d11_window_dummy_on_resize);
window_class->prepare = GST_DEBUG_FUNCPTR (gst_d3d11_window_dummy_prepare);
window_class->unprepare =
GST_DEBUG_FUNCPTR (gst_d3d11_window_dummy_unprepare);
window_class->open_shared_handle =
GST_DEBUG_FUNCPTR (gst_d3d11_window_dummy_open_shared_handle);
window_class->release_shared_handle =
GST_DEBUG_FUNCPTR (gst_d3d11_window_dummy_release_shared_handle);
}
static void
gst_d3d11_window_dummy_init (GstD3D11WindowDummy * self)
{
}
static GstFlowReturn
gst_d3d11_window_dummy_prepare (GstD3D11Window * window,
guint display_width, guint display_height, GstCaps * caps,
GstStructure * config, DXGI_FORMAT display_format, GError ** error)
{
gst_clear_object (&window->compositor);
gst_clear_object (&window->converter);
/* We are supporting only RGBA, BGRA or RGB10A2_LE formats but we don't know
* which format texture will be used at this moment */
gst_video_info_from_caps (&window->info, caps);
window->render_rect.left = 0;
window->render_rect.top = 0;
window->render_rect.right = display_width;
window->render_rect.bottom = display_height;
window->input_rect.left = 0;
window->input_rect.top = 0;
window->input_rect.right = GST_VIDEO_INFO_WIDTH (&window->info);
window->input_rect.bottom = GST_VIDEO_INFO_HEIGHT (&window->info);
gst_video_info_set_format (&window->render_info,
GST_VIDEO_FORMAT_BGRA, display_width, display_height);
/* TODO: not sure which colorspace should be used, let's use BT709 since
* it's default and most common one */
window->render_info.colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT709;
window->render_info.colorimetry.transfer = GST_VIDEO_TRANSFER_BT709;
window->render_info.colorimetry.range = GST_VIDEO_COLOR_RANGE_0_255;
if (config) {
gst_structure_set (config, GST_D3D11_CONVERTER_OPT_BACKEND,
GST_TYPE_D3D11_CONVERTER_BACKEND, GST_D3D11_CONVERTER_BACKEND_SHADER,
nullptr);
} else {
config = gst_structure_new ("converter-config",
GST_D3D11_CONVERTER_OPT_BACKEND, GST_TYPE_D3D11_CONVERTER_BACKEND,
GST_D3D11_CONVERTER_BACKEND_SHADER, nullptr);
}
GstD3D11DeviceLockGuard lk (window->device);
window->converter = gst_d3d11_converter_new (window->device, &window->info,
&window->render_info, config);
if (!window->converter) {
GST_ERROR_OBJECT (window, "Cannot create converter");
g_set_error (error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_FAILED,
"Cannot create converter");
return GST_FLOW_ERROR;
}
window->compositor =
gst_d3d11_overlay_compositor_new (window->device, &window->render_info);
if (!window->compositor) {
GST_ERROR_OBJECT (window, "Cannot create overlay compositor");
g_set_error (error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_FAILED,
"Cannot create overlay compositor");
return GST_FLOW_ERROR;
}
return GST_FLOW_OK;
}
static void
gst_d3d11_window_dummy_unprepare (GstD3D11Window * window)
{
GstD3D11WindowDummy *self = GST_D3D11_WINDOW_DUMMY (window);
gst_clear_d3d11_fence (&self->fence);
}
static void
gst_d3d11_window_dummy_on_resize (GstD3D11Window * window,
guint width, guint height)
{
GstVideoRectangle src_rect, dst_rect, rst_rect;
dst_rect.x = 0;
dst_rect.y = 0;
dst_rect.w = width;
dst_rect.h = height;
if (window->force_aspect_ratio) {
src_rect.x = 0;
src_rect.y = 0;
src_rect.w = GST_VIDEO_INFO_WIDTH (&window->render_info);
src_rect.h = GST_VIDEO_INFO_HEIGHT (&window->render_info);
switch (window->method) {
case GST_VIDEO_ORIENTATION_90R:
case GST_VIDEO_ORIENTATION_90L:
case GST_VIDEO_ORIENTATION_UL_LR:
case GST_VIDEO_ORIENTATION_UR_LL:
src_rect.w = GST_VIDEO_INFO_HEIGHT (&window->render_info);
src_rect.h = GST_VIDEO_INFO_WIDTH (&window->render_info);
break;
default:
src_rect.w = GST_VIDEO_INFO_WIDTH (&window->render_info);
src_rect.h = GST_VIDEO_INFO_HEIGHT (&window->render_info);
break;
}
gst_video_sink_center_rect (src_rect, dst_rect, &rst_rect, TRUE);
} else {
rst_rect = dst_rect;
}
window->render_rect.left = rst_rect.x;
window->render_rect.top = rst_rect.y;
window->render_rect.right = rst_rect.x + rst_rect.w;
window->render_rect.bottom = rst_rect.y + rst_rect.h;
window->first_present = TRUE;
}
static gboolean
gst_d3d11_window_dummy_open_shared_handle (GstD3D11Window * window,
GstD3D11WindowSharedHandleData * data)
{
GstD3D11Device *device = window->device;
ID3D11Device *device_handle;
HRESULT hr;
ComPtr < ID3D11Texture2D > texture;
ComPtr < IDXGIKeyedMutex > keyed_mutex;
ID3D11RenderTargetView *rtv;
GstMemory *mem;
GstD3D11Memory *dmem;
D3D11_TEXTURE2D_DESC desc;
gboolean use_keyed_mutex = FALSE;
device_handle = gst_d3d11_device_get_device_handle (device);
if ((data->texture_misc_flags & D3D11_RESOURCE_MISC_SHARED_NTHANDLE) ==
D3D11_RESOURCE_MISC_SHARED_NTHANDLE) {
ComPtr < ID3D11Device1 > device1_handle;
hr = device_handle->QueryInterface (IID_PPV_ARGS (&device1_handle));
if (!gst_d3d11_result (hr, device))
return FALSE;
hr = device1_handle->OpenSharedResource1 (data->shared_handle,
IID_PPV_ARGS (&texture));
} else {
hr = device_handle->OpenSharedResource (data->shared_handle,
IID_PPV_ARGS (&texture));
}
if (!gst_d3d11_result (hr, device))
return FALSE;
texture->GetDesc (&desc);
use_keyed_mutex = (desc.MiscFlags & D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX) ==
D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX;
if (use_keyed_mutex) {
hr = texture->QueryInterface (IID_PPV_ARGS (&keyed_mutex));
if (!gst_d3d11_result (hr, device)) {
GST_ERROR_OBJECT (window, "Keyed mutex is unavailable");
return FALSE;
}
}
mem = gst_d3d11_allocator_alloc_wrapped (nullptr,
device, texture.Get (), desc.Width * desc.Height * 4, nullptr, nullptr);
if (!mem) {
GST_ERROR_OBJECT (window, "Couldn't allocate memory");
return FALSE;
}
dmem = GST_D3D11_MEMORY_CAST (mem);
rtv = gst_d3d11_memory_get_render_target_view (dmem, 0);
if (!rtv) {
GST_ERROR_OBJECT (window, "Render target view is unavailable");
gst_memory_unref (mem);
return FALSE;
}
if (keyed_mutex) {
hr = keyed_mutex->AcquireSync (data->acquire_key, INFINITE);
if (!gst_d3d11_result (hr, device)) {
GST_ERROR_OBJECT (window, "Couldn't acquire sync");
gst_memory_unref (mem);
return FALSE;
}
}
/* Everything is prepared now */
gst_d3d11_window_dummy_on_resize (window, desc.Width, desc.Height);
/* Move owned resources */
data->render_target = gst_buffer_new ();
gst_buffer_append_memory (data->render_target, mem);
if (keyed_mutex)
data->keyed_mutex = keyed_mutex.Detach ();
return TRUE;
}
static gboolean
gst_d3d11_window_dummy_release_shared_handle (GstD3D11Window * window,
GstD3D11WindowSharedHandleData * data)
{
GstD3D11WindowDummy *self = GST_D3D11_WINDOW_DUMMY (window);
GstD3D11Device *device = window->device;
HRESULT hr;
/* TODO: cache owned resource for the later reuse? */
if (data->keyed_mutex) {
hr = data->keyed_mutex->ReleaseSync (data->release_key);
gst_d3d11_result (hr, device);
GST_D3D11_CLEAR_COM (data->keyed_mutex);
} else {
/* If keyed mutex is not used, let's handle sync manually by using
* fence. Issued GPU commands might not be finished yet */
if (!self->fence)
self->fence = gst_d3d11_device_create_fence (device);
if (!self->fence) {
GST_ERROR_OBJECT (self, "Couldn't Create event query");
return FALSE;
}
if (!gst_d3d11_fence_signal (self->fence) ||
!gst_d3d11_fence_wait (self->fence)) {
GST_ERROR_OBJECT (self, "Couldn't sync GPU operation");
return FALSE;
}
}
gst_clear_buffer (&data->render_target);
return TRUE;
}
GstD3D11Window *
gst_d3d11_window_dummy_new (GstD3D11Device * device)
{
GstD3D11Window *window;
g_return_val_if_fail (GST_IS_D3D11_DEVICE (device), NULL);
window = (GstD3D11Window *)
g_object_new (GST_TYPE_D3D11_WINDOW_DUMMY, "d3d11device", device, NULL);
window->initialized = TRUE;
gst_object_ref_sink (window);
return window;
}