From 84aecab150bc0fee53f0985a2eeb62c9874d217a Mon Sep 17 00:00:00 2001 From: Seungha Yang Date: Fri, 10 May 2024 22:59:15 +0900 Subject: [PATCH] d3d12videosink: Add overlay signal to support d3d12/d3d11/d2d overlay Conceptually identical to the present signal of d3d11videosink. This signal will be emitted with current render target (i.e., swapchain backbuffer) and command queue. Signal handler can record GPU commands for an overlay image or to blend an image to the render target. In addition to d3d12 resources, videosink will send d3d11 and d2d resources depending on "overlay-mode" property, so that signal handler can render by using preferred/required DirectX API. Part-of: --- .../sys/d3d12/gstd3d12videosink.cpp | 194 +++++++- .../sys/d3d12/gstd3d12window.cpp | 350 ++++++++++++-- .../sys/d3d12/gstd3d12window.h | 19 +- .../gst-plugins-bad/sys/d3d12/meson.build | 2 +- .../examples/d3d12/d3d12videosink-overlay.cpp | 427 ++++++++++++++++++ .../tests/examples/d3d12/meson.build | 17 + 6 files changed, 955 insertions(+), 54 deletions(-) create mode 100644 subprojects/gst-plugins-bad/tests/examples/d3d12/d3d12videosink-overlay.cpp diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12videosink.cpp b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12videosink.cpp index 7abd6e4525..1a44bb72bd 100644 --- a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12videosink.cpp +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12videosink.cpp @@ -28,6 +28,61 @@ #include #include +/** + * GstD3D12VideoSinkDisplayFormat: + * + * Swapchain display format + * + * Since: 1.26 + */ +#define GST_TYPE_D3D12_VIDEO_SINK_DISPLAY_FORMAT (gst_d3d12_video_sink_display_format_type()) +static GType +gst_d3d12_video_sink_display_format_type (void) +{ + static GType format_type = 0; + + GST_D3D12_CALL_ONCE_BEGIN { + static const GEnumValue format_types[] = { + /** + * GstD3D12VideoSinkDisplayFormat::unknown: + * + * Since: 1.26 + */ + {DXGI_FORMAT_UNKNOWN, "DXGI_FORMAT_UNKNOWN", "unknown"}, + + /** + * GstD3D12VideoSinkDisplayFormat::r10g10b10a2-unorm: + * + * Since: 1.26 + */ + {DXGI_FORMAT_R10G10B10A2_UNORM, + "DXGI_FORMAT_R10G10B10A2_UNORM", "r10g10b10a2-unorm"}, + + /** + * GstD3D12VideoSinkDisplayFormat::r8g8b8a8-unorm: + * + * Since: 1.26 + */ + {DXGI_FORMAT_R8G8B8A8_UNORM, + "DXGI_FORMAT_R8G8B8A8_UNORM", "r8g8b8a8-unorm"}, + + /** + * GstD3D12VideoSinkDisplayFormat::b8g8r8a8-unorm: + * + * Since: 1.26 + */ + {DXGI_FORMAT_B8G8R8A8_UNORM, + "DXGI_FORMAT_B8G8R8A8_UNORM", "b8g8r8a8-unorm"}, + {0, nullptr, nullptr}, + }; + + format_type = g_enum_register_static ("GstD3D12VideoSinkDisplayFormat", + format_types); + } GST_D3D12_CALL_ONCE_END; + + return format_type; +} + enum { PROP_0, @@ -49,6 +104,8 @@ enum PROP_SAMPLING_METHOD, PROP_GAMMA_MODE, PROP_PRIMARIES_MODE, + PROP_OVERLAY_MODE, + PROP_DISPLAY_FORMAT, }; #define DEFAULT_ADAPTER -1 @@ -66,6 +123,16 @@ enum #define DEFAULT_SAMPLING_METHOD GST_D3D12_SAMPLING_METHOD_BILINEAR #define DEFAULT_GAMMA_MODE GST_VIDEO_GAMMA_MODE_NONE #define DEFAULT_PRIMARIES_MODE GST_VIDEO_PRIMARIES_MODE_NONE +#define DEFAULT_OVERLAY_MODE GST_D3D12_WINDOW_OVERLAY_NONE +#define DEFAULT_DISPLAY_FORMAT DXGI_FORMAT_UNKNOWN + +enum +{ + SIGNAL_OVERLAY, + SIGNAL_LAST +}; + +static guint d3d12_video_sink_signals[SIGNAL_LAST] = { 0, }; static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, @@ -135,6 +202,8 @@ struct GstD3D12VideoSinkPrivate GstVideoGammaMode gamma_mode = DEFAULT_GAMMA_MODE; GstVideoPrimariesMode primaries_mode = DEFAULT_PRIMARIES_MODE; GstD3D12SamplingMethod sampling_method = DEFAULT_SAMPLING_METHOD; + GstD3D12WindowOverlayMode overlay_mode = DEFAULT_OVERLAY_MODE; + DXGI_FORMAT display_format = DEFAULT_DISPLAY_FORMAT; }; /* *INDENT-ON* */ @@ -147,9 +216,9 @@ struct _GstD3D12VideoSink GstD3D12VideoSinkPrivate *priv; }; -static void gst_d3d12_videosink_set_property (GObject * object, guint prop_id, +static void gst_d3d12_video_sink_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); -static void gst_d3d12_videosink_get_property (GObject * object, guint prop_id, +static void gst_d3d12_video_sink_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); static void gst_d3d12_video_sink_dispose (GObject * object); static void gst_d3d12_video_sink_finalize (GObject * object); @@ -180,6 +249,10 @@ static void gst_d3d12_video_sink_mouse_event (GstD3D12Window * window, GstD3D12VideoSink * self); static void gst_d3d12_video_sink_on_fullscreen (GstD3D12Window * window, gboolean is_fullscreen, GstD3D12VideoSink * self); +static void gst_d3d12_video_sink_on_overlay (GstD3D12Window * window, + gpointer command_queue, gpointer resource12, gpointer device11on12, + gpointer resource11, gpointer context2d, gpointer viewport, + GstD3D12VideoSink * self); static void gst_d3d12_video_sink_video_overlay_init (GstVideoOverlayInterface * iface); @@ -204,8 +277,8 @@ gst_d3d12_video_sink_class_init (GstD3D12VideoSinkClass * klass) auto basesink_class = GST_BASE_SINK_CLASS (klass); auto videosink_class = GST_VIDEO_SINK_CLASS (klass); - object_class->set_property = gst_d3d12_videosink_set_property; - object_class->get_property = gst_d3d12_videosink_get_property; + object_class->set_property = gst_d3d12_video_sink_set_property; + object_class->get_property = gst_d3d12_video_sink_get_property; object_class->finalize = gst_d3d12_video_sink_dispose; object_class->finalize = gst_d3d12_video_sink_finalize; @@ -322,6 +395,87 @@ gst_d3d12_video_sink_class_init (GstD3D12VideoSinkClass * klass) DEFAULT_SAMPLING_METHOD, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + /** + * GstD3D12VideoSink:overlay-mode: + * + * Overly signal type + * + * Since: 1.26 + */ + g_object_class_install_property (object_class, PROP_OVERLAY_MODE, + g_param_spec_flags ("overlay-mode", "Overlay Mode", + "Overlay signal type to use", GST_TYPE_D3D12_WINDOW_OVERLAY_MODE, + DEFAULT_OVERLAY_MODE, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + + /** + * GstD3D12VideoSink:display-format: + * + * Swapchain display format + * + * Since: 1.26 + */ + g_object_class_install_property (object_class, PROP_DISPLAY_FORMAT, + g_param_spec_enum ("display-format", "Display Format", + "Swapchain display format", GST_TYPE_D3D12_VIDEO_SINK_DISPLAY_FORMAT, + DEFAULT_DISPLAY_FORMAT, (GParamFlags) (G_PARAM_READWRITE | + GST_PARAM_MUTABLE_READY | G_PARAM_STATIC_STRINGS))); + + /** + * GstD3D12VideoSink::overlay: + * @d3d12videosink: the d3d12videosink element that emitted the signal + * @command_queue: ID3D12CommandQueue + * @resource12: ID3D12Resource + * @device11on12: (nullable): ID3D11On12Device + * @resource11: (nullable): ID3D11Texture2D + * @context2d: (nullable): ID2D1DeviceContext2 + * @viewport: (nullable): D3D12_RECT, d3d12videosink's current viewport + * + * Signal emitted with Direct3D12, Direct3D11 and Direct2D resources + * associated with swapchain backbuffer. + * + * This signal is emitted from the streaming thread if "overlay-mode" property + * includes GST_D3D12_WINDOW_OVERLAY_D3D12. The @resource12 is a render target + * backbuffer of the swapchain. The Resource state of @resource12 when this + * signal is emitted will be always D3D12_RESOURCE_STATE_RENDER_TARGET and + * signal handler should make sure the state is + * D3D12_RESOURCE_STATE_RENDER_TARGET when signal handler is returned, + * so that state change to final D3D12_RESOURCE_STATE_PRESENT can be + * processed by videosink. + * + * In addition to the d2d12 resources, if "overlay-mode" includes + * GST_D3D12_WINDOW_OVERLAY_D3D11 flag and d3d11on12 API is supported by + * system, @device11on12 and @resource11 will be valid handles. + * Singla handler should not assume the @device11on12 and @resource11 + * are always valid handle since d3d11on12 API may not be supported. + * The @resource11 is wrapped resource created via + * ID3D11On12Device::CreateWrappedResource(). Thus, signal handler must follow + * required steps for d3d11on12 device, for example, + * ID3D11On12Device::AcquireWrappedResources() must be called before recoding + * GPU commands. Once GPU commands are recoded via d3d11 or d2d APIs, + * the resource should be released via + * ID3D11On12Device::ReleaseWrappedResources(), and then + * ID3D11DeviceContext::Flush() must be called in the signal handler. + * + * If "overlay-mode" is GST_D3D12_WINDOW_OVERLAY_D2D and d2d device is + * available, @context2d will be valid handle. When this signal is emitted, + * @context2d has configured render target already. The D2D render target + * is also a resource derived from @resource11 and it's swapchain's backbuffer. + * The same step for d3d11 resource (i.e., acquire, release, and flush) + * is required for d2d as well. + * + * Since the resource is swapchain's backbuffer, signal handler must not hold + * any derived resources such as ID3D11RenderTargetView, so that videosink + * can clear swapchain resources and resize anytime it's needed. + * + * Since: 1.26 + */ + d3d12_video_sink_signals[SIGNAL_OVERLAY] = + g_signal_new_class_handler ("overlay", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, nullptr, nullptr, nullptr, nullptr, + G_TYPE_NONE, 6, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_POINTER, + G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_POINTER); + element_class->set_context = GST_DEBUG_FUNCPTR (gst_d3d12_video_sink_set_context); @@ -363,6 +517,8 @@ gst_d3d12_video_sink_init (GstD3D12VideoSink * self) G_CALLBACK (gst_d3d12_video_sink_mouse_event), self); g_signal_connect (priv->window, "fullscreen", G_CALLBACK (gst_d3d12_video_sink_on_fullscreen), self); + g_signal_connect (priv->window, "overlay", + G_CALLBACK (gst_d3d12_video_sink_on_overlay), self); } static void @@ -388,7 +544,7 @@ gst_d3d12_video_sink_finalize (GObject * object) } static void -gst_d3d12_videosink_set_property (GObject * object, guint prop_id, +gst_d3d12_video_sink_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { auto self = GST_D3D12_VIDEO_SINK (object); @@ -485,6 +641,14 @@ gst_d3d12_videosink_set_property (GObject * object, guint prop_id, } break; } + case PROP_OVERLAY_MODE: + priv->overlay_mode = + (GstD3D12WindowOverlayMode) g_value_get_flags (value); + gst_d3d12_window_set_overlay_mode (priv->window, priv->overlay_mode); + break; + case PROP_DISPLAY_FORMAT: + priv->display_format = (DXGI_FORMAT) g_value_get_enum (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -492,7 +656,7 @@ gst_d3d12_videosink_set_property (GObject * object, guint prop_id, } static void -gst_d3d12_videosink_get_property (GObject * object, guint prop_id, +gst_d3d12_video_sink_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { auto self = GST_D3D12_VIDEO_SINK (object); @@ -554,6 +718,12 @@ gst_d3d12_videosink_get_property (GObject * object, guint prop_id, case PROP_SAMPLING_METHOD: g_value_set_enum (value, priv->sampling_method); break; + case PROP_OVERLAY_MODE: + g_value_set_flags (value, priv->overlay_mode); + break; + case PROP_DISPLAY_FORMAT: + g_value_set_enum (value, priv->display_format); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -829,7 +999,7 @@ gst_d3d12_video_sink_update_window (GstD3D12VideoSink * self) auto ret = gst_d3d12_window_prepare (priv->window, self->device, window_handle, GST_VIDEO_SINK_WIDTH (self), - GST_VIDEO_SINK_HEIGHT (self), priv->caps, config); + GST_VIDEO_SINK_HEIGHT (self), priv->caps, config, priv->display_format); if (ret != GST_FLOW_OK) { if (ret == GST_FLOW_FLUSHING) { GST_WARNING_OBJECT (self, "We are flushing"); @@ -1247,3 +1417,13 @@ gst_d3d12_video_sink_navigation_init (GstNavigationInterface * iface) { iface->send_event_simple = gst_d3d12_video_sink_navigation_send_event; } + +static void +gst_d3d12_video_sink_on_overlay (GstD3D12Window * window, + gpointer command_queue, gpointer resource12, gpointer device11on12, + gpointer resource11, gpointer context2d, gpointer viewport, + GstD3D12VideoSink * self) +{ + g_signal_emit (self, d3d12_video_sink_signals[SIGNAL_OVERLAY], 0, + command_queue, resource12, device11on12, resource11, context2d, viewport); +} diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window.cpp b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window.cpp index 7ae9ad16d8..e241c69717 100644 --- a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window.cpp +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window.cpp @@ -24,6 +24,8 @@ #include "gstd3d12window.h" #include "gstd3d12overlaycompositor.h" #include +#include +#include #include #include #include @@ -65,24 +67,57 @@ enum SIGNAL_KEY_EVENT, SIGNAL_MOUSE_EVENT, SIGNAL_FULLSCREEN, + SIGNAL_OVERLAY, SIGNAL_LAST }; static guint d3d12_window_signals[SIGNAL_LAST] = { 0, }; +GType +gst_d3d12_window_overlay_mode_get_type (void) +{ + static GType mode_type = 0; + + GST_D3D12_CALL_ONCE_BEGIN { + static const GFlagsValue mode_types[] = { + {GST_D3D12_WINDOW_OVERLAY_NONE, "None", "none"}, + {GST_D3D12_WINDOW_OVERLAY_D3D12, + "Emits present signal with Direct3D12 resources", "d3d12"}, + {GST_D3D12_WINDOW_OVERLAY_D3D11, + "Emits present signal with Direct3D12/11 resources", "d3d11"}, + {GST_D3D12_WINDOW_OVERLAY_D2D, + "Emit present signal with Direct3D12/11 and Direct2D resources", + "d2d"}, + {0, nullptr, nullptr}, + }; + + mode_type = g_flags_register_static ("GstD3D12WindowOverlayMode", + mode_types); + } GST_D3D12_CALL_ONCE_END; + + return mode_type; +} + /* *INDENT-OFF* */ struct SwapBuffer { - SwapBuffer (GstBuffer * buf) + SwapBuffer (GstBuffer * buf, ID3D12Resource * res) { backbuf = buf; + resource = res; } ~SwapBuffer () { + d2d_target = nullptr; + wrapped_resource = nullptr; + resource = nullptr; gst_clear_buffer (&backbuf); } + ComPtr d2d_target; + ComPtr wrapped_resource; + ComPtr resource; GstBuffer *backbuf = nullptr; gboolean first = TRUE; }; @@ -94,21 +129,145 @@ struct DeviceContext { event_handle = CreateEventEx (nullptr, nullptr, 0, EVENT_ALL_ACCESS); device = (GstD3D12Device *) gst_object_ref (dev); - auto device_handle = gst_d3d12_device_get_device_handle (device); ca_pool = gst_d3d12_command_allocator_pool_new (device_handle, D3D12_COMMAND_LIST_TYPE_DIRECT); - if (!ca_pool) { - GST_ERROR_OBJECT (device, "Couldn't create command allocator pool"); - return; + } + + bool EnsureD3D11 () + { + if (device11on12) + return true; + + auto unknown = gst_d3d12_device_get_11on12_handle (device); + if (!unknown) + return false; + + unknown->QueryInterface (IID_PPV_ARGS (&device11on12)); + device11on12.As (&device11); + device11->GetImmediateContext (&context11); + + return true; + } + + bool EnsureD2D () + { + if (context2d) + return true; + + if (!EnsureD3D11 ()) + return false; + + HRESULT hr; + if (!factory2d) { + hr = D2D1CreateFactory (D2D1_FACTORY_TYPE_SINGLE_THREADED, + IID_PPV_ARGS (&factory2d)); + if (FAILED (hr)) + return false; } - initialized = true; + GstD3D12Device11on12LockGuard lk (device); + if (!device2d) { + ComPtr device_dxgi; + hr = device11.As (&device_dxgi); + if (FAILED (hr)) + return false; + + hr = factory2d->CreateDevice (device_dxgi.Get (), &device2d); + if (FAILED (hr)) + return false; + } + + if (!context2d) { + hr = device2d->CreateDeviceContext (D2D1_DEVICE_CONTEXT_OPTIONS_NONE, + &context2d); + if (FAILED (hr)) + return false; + } + + return true; + } + + bool EnsureD3D11RenderTarget (std::shared_ptr swapbuf) + { + if (swapbuf->wrapped_resource) + return true; + + if (!EnsureD3D11 ()) + return false; + + D3D11_RESOURCE_FLAGS d3d11_flags = { }; + d3d11_flags.BindFlags = D3D11_BIND_RENDER_TARGET; + auto hr = device11on12->CreateWrappedResource (swapbuf->resource.Get (), + &d3d11_flags, D3D12_RESOURCE_STATE_RENDER_TARGET, + D3D12_RESOURCE_STATE_RENDER_TARGET, + IID_PPV_ARGS (&swapbuf->wrapped_resource)); + if (FAILED (hr)) + return false; + + return true; + } + + bool EnsureD2DRenderTarget (std::shared_ptr swapbuf) + { + if (swapbuf->d2d_target) + return true; + + if (!EnsureD2D ()) + return false; + + if (!EnsureD3D11RenderTarget (swapbuf)) + return false; + + GstD3D12Device11on12LockGuard lk (device); + ComPtr surface; + auto hr = swapbuf->wrapped_resource.As (&surface); + if (FAILED (hr)) + return false; + + D2D1_BITMAP_PROPERTIES1 props = D2D1::BitmapProperties1( + D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW, + D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED)); + + hr = context2d->CreateBitmapFromDxgiSurface (surface.Get (), &props, + &swapbuf->d2d_target); + if (FAILED (hr)) + return false; + + return true; + } + + void WaitGpu () + { + gst_d3d12_device_fence_wait (device, D3D12_COMMAND_LIST_TYPE_DIRECT, + G_MAXUINT64, event_handle); + prev_fence_val = { }; + } + + void ReleaseSizeDependentResources () + { + if (fence_val != 0) + WaitGpu (); + + if (context11) + gst_d3d12_device_11on12_lock (device); + + swap_buffers.clear (); + gst_clear_buffer (&msaa_buf); + + if (context2d) + context2d->SetTarget (nullptr); + + if (context11) { + context11->ClearState (); + context11->Flush (); + gst_d3d12_device_11on12_unlock (device); + } } ~DeviceContext () { - WaitGpu (); + ReleaseSizeDependentResources (); CloseHandle (event_handle); @@ -116,20 +275,9 @@ struct DeviceContext gst_clear_buffer (&cached_buf); gst_clear_object (&conv); gst_clear_object (&comp); - gst_clear_buffer (&msaa_buf); gst_clear_object (&device); } - void WaitGpu () - { - if (!device) - return; - - gst_d3d12_device_fence_wait (device, D3D12_COMMAND_LIST_TYPE_DIRECT, - G_MAXUINT64, event_handle); - prev_fence_val = { }; - } - gboolean BeforeRendering () { UINT64 fence_val_to_wait = 0; @@ -167,10 +315,15 @@ struct DeviceContext GstBuffer *cached_buf = nullptr; GstD3D12Device *device = nullptr; GstD3D12CommandAllocatorPool *ca_pool = nullptr; + ComPtr device11on12; + ComPtr device11; + ComPtr context11; + ComPtr factory2d; + ComPtr device2d; + ComPtr context2d; UINT64 fence_val = 0; std::queue prev_fence_val; - HANDLE event_handle; - bool initialized = false; + HANDLE event_handle = nullptr; }; struct GstD3D12WindowPrivate @@ -239,6 +392,7 @@ struct GstD3D12WindowPrivate gboolean update_title = FALSE; GstD3D12MSAAMode msaa = GST_D3D12_MSAA_DISABLED; + GstD3D12WindowOverlayMode overlay_mode = GST_D3D12_WINDOW_OVERLAY_NONE; /* Win32 window handles */ std::mutex hwnd_lock; @@ -280,21 +434,27 @@ gst_d3d12_window_class_init (GstD3D12WindowClass * klass) object_class->finalize = gst_d3d12_window_finalize; d3d12_window_signals[SIGNAL_KEY_EVENT] = - g_signal_new ("key-event", G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, 0, nullptr, nullptr, nullptr, + g_signal_new_class_handler ("key-event", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, nullptr, nullptr, nullptr, nullptr, G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING); d3d12_window_signals[SIGNAL_MOUSE_EVENT] = - g_signal_new ("mouse-event", G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, 0, nullptr, nullptr, nullptr, + g_signal_new_class_handler ("mouse-event", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, nullptr, nullptr, nullptr, nullptr, G_TYPE_NONE, 5, G_TYPE_STRING, G_TYPE_INT, G_TYPE_DOUBLE, G_TYPE_DOUBLE, G_TYPE_UINT); d3d12_window_signals[SIGNAL_FULLSCREEN] = - g_signal_new ("fullscreen", G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, 0, nullptr, nullptr, nullptr, + g_signal_new_class_handler ("fullscreen", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, nullptr, nullptr, nullptr, nullptr, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); + d3d12_window_signals[SIGNAL_OVERLAY] = + g_signal_new_class_handler ("overlay", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, nullptr, nullptr, nullptr, nullptr, + G_TYPE_NONE, 6, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_POINTER, + G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_POINTER); + GST_DEBUG_CATEGORY_INIT (gst_d3d12_window_debug, "d3d12window", 0, "d3d12window"); } @@ -1153,10 +1313,7 @@ gst_d3d12_window_on_resize (GstD3D12Window * self) if (!priv->ctx) return GST_FLOW_OK; - if (priv->ctx->fence_val != 0) - priv->ctx->WaitGpu (); - priv->ctx->swap_buffers.clear (); - gst_clear_buffer (&priv->ctx->msaa_buf); + priv->ctx->ReleaseSizeDependentResources (); DXGI_SWAP_CHAIN_DESC desc = { }; priv->ctx->swapchain->GetDesc (&desc); @@ -1182,7 +1339,8 @@ gst_d3d12_window_on_resize (GstD3D12Window * self) backbuf.Get (), 0, nullptr, nullptr); auto buf = gst_buffer_new (); gst_buffer_append_memory (buf, mem); - priv->ctx->swap_buffers.push_back (std::make_shared < SwapBuffer > (buf)); + auto swapbuf = std::make_shared < SwapBuffer > (buf, backbuf.Get ()); + priv->ctx->swap_buffers.push_back (swapbuf); } guint sample_count = 1; @@ -1263,7 +1421,7 @@ gst_d3d12_window_on_resize (GstD3D12Window * self) GstFlowReturn gst_d3d12_window_prepare (GstD3D12Window * window, GstD3D12Device * device, guintptr window_handle, guint display_width, guint display_height, - GstCaps * caps, GstStructure * config) + GstCaps * caps, GstStructure * config, DXGI_FORMAT display_format) { auto priv = window->priv; GstVideoInfo in_info; @@ -1271,7 +1429,10 @@ gst_d3d12_window_prepare (GstD3D12Window * window, GstD3D12Device * device, priv->display_format = DXGI_FORMAT_R8G8B8A8_UNORM; gst_video_info_from_caps (&in_info, caps); - if (GST_VIDEO_INFO_COMP_DEPTH (&in_info, 0) > 8) { + if (display_format != DXGI_FORMAT_UNKNOWN) { + priv->display_format = display_format; + format = gst_d3d12_dxgi_format_to_gst (display_format); + } else if (GST_VIDEO_INFO_COMP_DEPTH (&in_info, 0) > 8) { auto device_handle = gst_d3d12_device_get_device_handle (device); D3D12_FEATURE_DATA_FORMAT_SUPPORT format_support = { }; const D3D12_FORMAT_SUPPORT1 support_flags = @@ -1313,13 +1474,6 @@ gst_d3d12_window_prepare (GstD3D12Window * window, GstD3D12Device * device, if (!priv->ctx) { auto ctx = std::make_unique < DeviceContext > (device); - if (!ctx->initialized) { - GST_ERROR_OBJECT (window, "Couldn't initialize device context"); - if (config) - gst_structure_free (config); - - return GST_FLOW_ERROR; - } DXGI_SWAP_CHAIN_DESC1 desc = { }; desc.Format = priv->display_format; @@ -1514,9 +1668,6 @@ gst_d3d12_window_set_buffer (GstD3D12Window * window, GstBuffer * buffer) &priv->output_rect); } - g_object_set (priv->ctx->conv, "fill-border", swapbuf->first, nullptr); - swapbuf->first = FALSE; - guint64 overlay_fence_val = 0; gst_d3d12_overlay_compositor_upload (priv->ctx->comp, priv->ctx->cached_buf, &overlay_fence_val); @@ -1578,6 +1729,15 @@ gst_d3d12_window_set_buffer (GstD3D12Window * window, GstBuffer * buffer) cl->ResourceBarrier (1, &barrier); } + if (swapbuf->first || priv->overlay_mode != GST_D3D12_WINDOW_OVERLAY_NONE) { + FLOAT clear_color[4] = { 0, 0, 0, 1 }; + auto rtv_heap = gst_d3d12_memory_get_render_target_view_heap (mem); + auto cpu_handle = GetCPUDescriptorHandleForHeapStart (rtv_heap); + cl->ClearRenderTargetView (cpu_handle, clear_color, 0, nullptr); + } + + swapbuf->first = FALSE; + auto cq = gst_d3d12_device_get_command_queue (priv->ctx->device, D3D12_COMMAND_LIST_TYPE_DIRECT); auto cq_handle = gst_d3d12_command_queue_get_handle (cq); @@ -1597,6 +1757,32 @@ gst_d3d12_window_set_buffer (GstD3D12Window * window, GstBuffer * buffer) return GST_FLOW_ERROR; } + D3D12_RESOURCE_STATES state_after = D3D12_RESOURCE_STATE_COMMON; + GstD3D12WindowOverlayMode selected_overlay_mode = + GST_D3D12_WINDOW_OVERLAY_NONE; + bool signal_with_lock = false; + bool set_d2d_target = false; + if ((priv->overlay_mode & GST_D3D12_WINDOW_OVERLAY_D3D12) != 0) { + selected_overlay_mode |= GST_D3D12_WINDOW_OVERLAY_D3D12; + state_after = D3D12_RESOURCE_STATE_RENDER_TARGET; + } + + if ((priv->overlay_mode & GST_D3D12_WINDOW_OVERLAY_D3D11) == + GST_D3D12_WINDOW_OVERLAY_D3D11 && + priv->ctx->EnsureD3D11RenderTarget (swapbuf)) { + selected_overlay_mode |= GST_D3D12_WINDOW_OVERLAY_D3D11; + signal_with_lock = true; + } + + if ((priv->overlay_mode & GST_D3D12_WINDOW_OVERLAY_D2D) == + GST_D3D12_WINDOW_OVERLAY_D2D && + (priv->display_format == DXGI_FORMAT_R8G8B8A8_UNORM || + priv->display_format == DXGI_FORMAT_B8G8R8A8_UNORM) && + priv->ctx->EnsureD2DRenderTarget (swapbuf)) { + selected_overlay_mode |= GST_D3D12_WINDOW_OVERLAY_D2D; + set_d2d_target = true; + } + if (msaa_resource) { std::vector < D3D12_RESOURCE_BARRIER > barriers; barriers.push_back (CD3DX12_RESOURCE_BARRIER::Transition (msaa_resource, @@ -1614,9 +1800,9 @@ gst_d3d12_window_set_buffer (GstD3D12Window * window, GstBuffer * buffer) D3D12_RESOURCE_STATE_RESOLVE_SOURCE, D3D12_RESOURCE_STATE_RENDER_TARGET)); barriers.push_back (CD3DX12_RESOURCE_BARRIER::Transition (backbuf_texture, - D3D12_RESOURCE_STATE_RESOLVE_DEST, D3D12_RESOURCE_STATE_COMMON)); + D3D12_RESOURCE_STATE_RESOLVE_DEST, state_after)); cl->ResourceBarrier (barriers.size (), barriers.data ()); - } else { + } else if (state_after == D3D12_RESOURCE_STATE_COMMON) { D3D12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition (backbuf_texture, D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_COMMON); @@ -1650,6 +1836,71 @@ gst_d3d12_window_set_buffer (GstD3D12Window * window, GstBuffer * buffer) fence_data, (GDestroyNotify) gst_d3d12_fence_data_unref); priv->backbuf_rendered = TRUE; + + if (selected_overlay_mode != GST_D3D12_WINDOW_OVERLAY_NONE) { + D3D12_RECT viewport = priv->dirty_rect; + if (signal_with_lock) + gst_d3d12_device_11on12_lock (window->device); + + if (set_d2d_target) + priv->ctx->context2d->SetTarget (swapbuf->d2d_target.Get ()); + + g_signal_emit (window, d3d12_window_signals[SIGNAL_OVERLAY], 0, + cq_handle, backbuf_texture, priv->ctx->device11on12.Get (), + swapbuf->wrapped_resource.Get (), priv->ctx->context2d.Get (), + &viewport); + + if (signal_with_lock) + gst_d3d12_device_11on12_unlock (window->device); + } + + if (state_after != D3D12_RESOURCE_STATE_COMMON) { + if (!gst_d3d12_command_allocator_pool_acquire (priv->ctx->ca_pool, &gst_ca)) { + GST_ERROR_OBJECT (window, "Couldn't acquire command allocator"); + return GST_FLOW_ERROR; + } + + ca = gst_d3d12_command_allocator_get_handle (gst_ca); + hr = ca->Reset (); + if (!gst_d3d12_result (hr, priv->ctx->device)) { + GST_ERROR_OBJECT (window, "Couldn't reset command allocator"); + gst_d3d12_command_allocator_unref (gst_ca); + return GST_FLOW_ERROR; + } + + hr = cl->Reset (ca, nullptr); + if (!gst_d3d12_result (hr, priv->ctx->device)) { + GST_ERROR_OBJECT (window, "Couldn't reset command list"); + gst_d3d12_command_allocator_unref (gst_ca); + return GST_FLOW_ERROR; + } + + gst_d3d12_fence_data_pool_acquire (priv->fence_data_pool, &fence_data); + gst_d3d12_fence_data_add_notify_mini_object (fence_data, gst_ca); + + D3D12_RESOURCE_BARRIER barrier = + CD3DX12_RESOURCE_BARRIER::Transition (backbuf_texture, + D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_COMMON); + cl->ResourceBarrier (1, &barrier); + hr = cl->Close (); + if (!gst_d3d12_result (hr, priv->ctx->device)) { + GST_ERROR_OBJECT (window, "Couldn't close command list"); + gst_d3d12_fence_data_unref (fence_data); + return GST_FLOW_ERROR; + } + + hr = gst_d3d12_command_queue_execute_command_lists (cq, + 1, cmd_list, &priv->ctx->fence_val); + if (!gst_d3d12_result (hr, priv->ctx->device)) { + GST_ERROR_OBJECT (window, "Signal failed"); + gst_d3d12_fence_data_unref (fence_data); + return GST_FLOW_ERROR; + } + + gst_d3d12_command_queue_set_notify (cq, priv->ctx->fence_val, + fence_data, (GDestroyNotify) gst_d3d12_fence_data_unref); + } + priv->ctx->AfterRendering (); return GST_FLOW_OK; @@ -1839,3 +2090,12 @@ gst_d3d12_window_set_msaa (GstD3D12Window * window, GstD3D12MSAAMode msaa) gst_d3d12_window_on_resize (window); } } + +void +gst_d3d12_window_set_overlay_mode (GstD3D12Window * window, + GstD3D12WindowOverlayMode mode) +{ + auto priv = window->priv; + std::lock_guard < std::recursive_mutex > lk (priv->lock); + priv->overlay_mode = mode; +} diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window.h b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window.h index bb81a5f1e6..b6834a4a26 100644 --- a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window.h +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window.h @@ -39,6 +39,19 @@ enum GstD3D12WindowState GST_D3D12_WINDOW_STATE_CLOSED, }; +enum GstD3D12WindowOverlayMode +{ + GST_D3D12_WINDOW_OVERLAY_NONE = 0, + GST_D3D12_WINDOW_OVERLAY_D3D12 = 0x1, + GST_D3D12_WINDOW_OVERLAY_D3D11 = 0x3, + GST_D3D12_WINDOW_OVERLAY_D2D = 0x7, +}; + +DEFINE_ENUM_FLAG_OPERATORS (GstD3D12WindowOverlayMode); + +#define GST_TYPE_D3D12_WINDOW_OVERLAY_MODE (gst_d3d12_window_overlay_mode_get_type()) +GType gst_d3d12_window_overlay_mode_get_type (void); + GstD3D12Window * gst_d3d12_window_new (void); guintptr gst_d3d12_window_get_window_handle (GstD3D12Window * window); @@ -51,7 +64,8 @@ GstFlowReturn gst_d3d12_window_prepare (GstD3D12Window * window, guint display_width, guint display_height, GstCaps * caps, - GstStructure * config); + GstStructure * config, + DXGI_FORMAT display_format); void gst_d3d12_window_unprepare (GstD3D12Window * window); @@ -97,5 +111,8 @@ void gst_d3d12_window_set_fullscreen (GstD3D12Window * window, void gst_d3d12_window_set_msaa (GstD3D12Window * window, GstD3D12MSAAMode msaa); +void gst_d3d12_window_set_overlay_mode (GstD3D12Window * window, + GstD3D12WindowOverlayMode mode); + G_END_DECLS diff --git a/subprojects/gst-plugins-bad/sys/d3d12/meson.build b/subprojects/gst-plugins-bad/sys/d3d12/meson.build index 8edbd577bc..ab602b1af3 100644 --- a/subprojects/gst-plugins-bad/sys/d3d12/meson.build +++ b/subprojects/gst-plugins-bad/sys/d3d12/meson.build @@ -127,7 +127,7 @@ endif d3d12_headers = [ 'd3d11.h', - 'd2d1.h', + 'd2d1_3.h', ] have_d3d12_headers = true diff --git a/subprojects/gst-plugins-bad/tests/examples/d3d12/d3d12videosink-overlay.cpp b/subprojects/gst-plugins-bad/tests/examples/d3d12/d3d12videosink-overlay.cpp new file mode 100644 index 0000000000..8e5d6c33b6 --- /dev/null +++ b/subprojects/gst-plugins-bad/tests/examples/d3d12/d3d12videosink-overlay.cpp @@ -0,0 +1,427 @@ +/* GStreamer + * Copyright (C) 2024 Seungha Yang + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* *INDENT-OFF* */ +using namespace Microsoft::WRL; + +struct OverlayContext +{ + GstElement *pipeline = nullptr; + + ComPtr dwrite_factory; + ComPtr format; + ComPtr layout; + + std::wstring text; + + gint width = 0; + gint height = 0; + FLOAT origin_y = 0; + gint text_width = 0; + gint last_position = 0; + gboolean draw_on_viewport; + + GMainLoop *loop = nullptr; +}; +/* *INDENT-ON* */ + +static gboolean +bus_msg (GstBus * bus, GstMessage * msg, OverlayContext * context) +{ + switch (GST_MESSAGE_TYPE (msg)) { + case GST_MESSAGE_ERROR:{ + GError *err; + gchar *dbg; + + gst_message_parse_error (msg, &err, &dbg); + gst_printerrln ("ERROR %s", err->message); + if (dbg) + gst_printerrln ("ERROR debug information: %s", dbg); + g_clear_error (&err); + g_free (dbg); + + g_main_loop_quit (context->loop); + break; + } + case GST_MESSAGE_EOS: + gst_println ("Got EOS"); + g_main_loop_quit (context->loop); + break; + default: + break; + } + + return TRUE; +} + +/* Calculate text size again per resize event */ +static void +calculate_size (OverlayContext * context, gint width, gint height) +{ + if (width == context->width && height == context->height) + return; + + HRESULT hr; + DWRITE_TEXT_RANGE range; + range.startPosition = 0; + range.length = context->text.length (); + + FLOAT text_height = height / 10.0f; + bool was_decreased = false; + + ComPtr < IDWriteTextLayout > text_layout; + hr = context->dwrite_factory->CreateTextLayout (context->text.c_str (), + context->text.length (), context->format.Get (), G_MAXFLOAT, G_MAXFLOAT, + &text_layout); + g_assert (SUCCEEDED (hr)); + + DWRITE_TEXT_METRICS metrics; + FLOAT font_size; + do { + hr = text_layout->GetMetrics (&metrics); + g_assert (SUCCEEDED (hr)); + + text_layout->GetFontSize (0, &font_size); + if (metrics.height >= text_height) { + if (font_size > 1.0f) { + font_size -= 0.5f; + was_decreased = true; + hr = text_layout->SetFontSize (font_size, range); + g_assert (SUCCEEDED (hr)); + continue; + } + + break; + } + + if (was_decreased) + break; + + if (metrics.height < text_height) { + if (metrics.height >= text_height * 0.9) + break; + + font_size += 0.5f; + hr = text_layout->SetFontSize (font_size, range); + g_assert (SUCCEEDED (hr)); + continue; + } + } while (true); + + text_layout->SetMaxWidth (metrics.widthIncludingTrailingWhitespace + 10); + text_layout->SetMaxHeight (metrics.height); + + context->layout = nullptr; + context->layout = text_layout; + + context->origin_y = height - text_height; + context->text_width = metrics.widthIncludingTrailingWhitespace + 10; + context->width = width; + context->height = height; + context->last_position = 0; +} + +static void +on_overlay_2d (GstElement * sink, ID3D12CommandQueue * queue, + ID3D12Resource * resource12, ID3D11On12Device * device11on12, + ID3D11Texture2D * resource11, ID2D1DeviceContext2 * context2d, + D3D12_RECT * viewport, OverlayContext * context) +{ + ComPtr < ID2D1SolidColorBrush > text_brush; + ComPtr < ID2D1SolidColorBrush > bg_brush; + ComPtr < ID3D11Device > device11; + ComPtr < ID3D11DeviceContext > context11; + HRESULT hr; + FLOAT position; + UINT width, height; + + if (context->draw_on_viewport) { + width = viewport->right - viewport->left; + height = viewport->bottom - viewport->top; + } else { + D3D11_TEXTURE2D_DESC desc; + resource11->GetDesc (&desc); + width = desc.Width; + height = desc.Height; + } + + calculate_size (context, width, height); + + /* We need d3d11 immediate context to call Flush() */ + hr = device11on12->QueryInterface (IID_PPV_ARGS (&device11)); + g_assert (SUCCEEDED (hr)); + device11->GetImmediateContext (&context11); + + /* Acquire wrapped resource to build GPU command by using d2d/d3d11 for + * the wrapped d3d12 resrouce */ + ID3D11Resource *resources[] = { resource11 }; + device11on12->AcquireWrappedResources (resources, 1); + + context2d->BeginDraw (); + hr = context2d->CreateSolidColorBrush (D2D1::ColorF (D2D1::ColorF::Black), + &bg_brush); + g_assert (SUCCEEDED (hr)); + + hr = context2d->CreateSolidColorBrush (D2D1::ColorF (D2D1::ColorF::White), + &text_brush); + g_assert (SUCCEEDED (hr)); + + ComPtr < ID2D1Layer > layer; + hr = context2d->CreateLayer (&layer); + g_assert (SUCCEEDED (hr)); + + D2D1_RECT_F bg_rect; + if (context->draw_on_viewport) { + bg_rect = D2D1::RectF (viewport->left, context->origin_y + viewport->top, + viewport->left + context->width, viewport->top + context->height); + } else { + bg_rect = D2D1::RectF (0, context->origin_y, context->width, + context->height); + } + + /* Draw background */ + context2d->FillRectangle (bg_rect, bg_brush.Get ()); + + /* Create layer to restrict drawing area to specific rect */ + D2D1_LAYER_PARAMETERS params = { }; + params.contentBounds = bg_rect; + params.maskAntialiasMode = D2D1_ANTIALIAS_MODE_PER_PRIMITIVE; + params.maskTransform = D2D1::IdentityMatrix (); + params.opacity = 1; + context2d->PushLayer (¶ms, layer.Get ()); + + /* Draw text */ + position = -context->last_position; + do { + context2d->DrawTextLayout (D2D1::Point2F (position, bg_rect.top), + context->layout.Get (), text_brush.Get (), + static_cast < D2D1_DRAW_TEXT_OPTIONS > + (D2D1_DRAW_TEXT_OPTIONS_CLIP | + D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT)); + position = position + context->text_width; + } while (position < context->width); + + context2d->PopLayer (); + context2d->EndDraw (); + + /* After recording GPU command, should release wrapped resource first, + * then Flush() must be called for the recorded GPU command to be + * executed via d3d12 command queue */ + device11on12->ReleaseWrappedResources (resources, 1); + context11->Flush (); + + /* Update text position for the next rendering */ + context->last_position += 2; + if (context->last_position >= context->text_width) { + context->last_position %= context->text_width; + } +} + +gint +main (gint argc, gchar ** argv) +{ + GOptionContext *option_ctx; + GError *error = nullptr; + gboolean ret; + gchar *text = nullptr; + gint width = 1280; + gint height = 720; + gboolean draw_on_viewport = FALSE; + GOptionEntry options[] = { + {"text", 0, 0, G_OPTION_ARG_STRING, &text, "Text to render", nullptr}, + {"width", 0, 0, G_OPTION_ARG_INT, &width, "Width of video stream", nullptr}, + {"height", 0, 0, G_OPTION_ARG_INT, &height, "Height of video stream", + nullptr}, + {"draw-on-viewport", 0, 0, G_OPTION_ARG_NONE, &draw_on_viewport, + "Draw image only on viewport area", nullptr}, + {nullptr} + }; + OverlayContext context; + GstStateChangeReturn sret; + HRESULT hr; + std::wstring text_wide; + std::string pipeline_str; + DWRITE_TEXT_METRICS metrics; + FLOAT font_size; + bool was_decreased = false; + DWRITE_TEXT_RANGE range; + FLOAT text_height; + gchar **win32_argv; + ComPtr < IDWriteTextFormat > text_format; + ComPtr < IDWriteTextLayout > text_layout; + + win32_argv = g_win32_get_command_line (); + + option_ctx = g_option_context_new ("d3d12videosink present-on-11 example"); + g_option_context_add_main_entries (option_ctx, options, nullptr); + g_option_context_add_group (option_ctx, gst_init_get_option_group ()); + ret = g_option_context_parse_strv (option_ctx, &win32_argv, &error); + g_option_context_free (option_ctx); + g_strfreev (win32_argv); + + if (!ret) { + gst_printerrln ("option parsing failed: %s", error->message); + g_clear_error (&error); + return 1; + } + + /* Prepare device independent D2D objects */ + hr = DWriteCreateFactory (DWRITE_FACTORY_TYPE_SHARED, + __uuidof (context.dwrite_factory.Get ()), + reinterpret_cast < IUnknown ** >(context.dwrite_factory.GetAddressOf ())); + if (FAILED (hr)) { + gst_printerrln ("Couldn't create DirectWrite factory"); + return 1; + } + + hr = context.dwrite_factory->CreateTextFormat (L"Arial", nullptr, + DWRITE_FONT_WEIGHT_BOLD, DWRITE_FONT_STYLE_NORMAL, + DWRITE_FONT_STRETCH_NORMAL, 12.0f, L"en-us", &text_format); + if (FAILED (hr)) { + gst_printerrln ("Couldn't create IDWriteTextFormat"); + return 1; + } + + text_format->SetTextAlignment (DWRITE_TEXT_ALIGNMENT_LEADING); + text_format->SetParagraphAlignment (DWRITE_PARAGRAPH_ALIGNMENT_CENTER); + text_format->SetWordWrapping (DWRITE_WORD_WRAPPING_NO_WRAP); + + if (text && text[0] != '\0') { + std::wstring_convert < std::codecvt_utf8_utf16 < wchar_t >>converter; + text_wide = converter.from_bytes (text); + } else { + /* *INDENT-OFF* */ + text_wide = + std::wstring (L"Hello GStreamer! ๐Ÿ˜Š ์•ˆ๋…•ํ•˜์„ธ์š” GStreamer! ๐Ÿ˜‰ ") + + std::wstring (L"เคจเคฎเคธเฅเคคเฅ‡ GStreamer! โค๏ธ Bonjour GStreamer! ๐Ÿ˜ ") + + std::wstring (L"Hallo GStreamer! ๐Ÿ˜Ž Hola GStreamer! ๐Ÿ˜ ") + + std::wstring (L"ใ“ใ‚“ใซใกใฏ GStreamer! โœŒ๏ธ ไฝ ๅฅฝ GStreamer! ๐Ÿ‘"); + /* *INDENT-ON* */ + } + + text_height = (FLOAT) height / 10.0f; + + hr = context.dwrite_factory->CreateTextLayout (text_wide.c_str (), + text_wide.length (), text_format.Get (), G_MAXFLOAT, G_MAXFLOAT, + &text_layout); + if (FAILED (hr)) { + gst_printerrln ("Couldn't create IDWriteTextLayout"); + return 1; + } + + /* Calculate best font size */ + range.startPosition = 0; + range.length = text_wide.length (); + + do { + hr = text_layout->GetMetrics (&metrics); + g_assert (SUCCEEDED (hr)); + + text_layout->GetFontSize (0, &font_size); + if (metrics.height >= (FLOAT) text_height) { + if (font_size > 1.0f) { + font_size -= 0.5f; + was_decreased = true; + hr = text_layout->SetFontSize (font_size, range); + g_assert (SUCCEEDED (hr)); + continue; + } + + break; + } + + if (was_decreased) + break; + + if (metrics.height < text_height) { + if (metrics.height >= text_height * 0.9) + break; + + font_size += 0.5f; + hr = text_layout->SetFontSize (font_size, range); + g_assert (SUCCEEDED (hr)); + continue; + } + } while (true); + + gst_println ("Calculated font size %lf", font_size); + + /* 10 pixels padding per loop */ + text_layout->SetMaxWidth (metrics.widthIncludingTrailingWhitespace + 10); + text_layout->SetMaxHeight (metrics.height); + + context.text = text_wide; + context.origin_y = (FLOAT) height - text_height; + context.width = width; + context.height = height; + context.text_width = metrics.widthIncludingTrailingWhitespace + 10; + context.layout = text_layout; + context.format = text_format; + context.draw_on_viewport = draw_on_viewport; + + context.loop = g_main_loop_new (nullptr, FALSE); + + pipeline_str = + "d3d12testsrc ! video/x-raw(memory:D3D12Memory),format=RGBA,width=" + + std::to_string (width) + ",height=" + std::to_string (height) + + ",framerate=60/1 ! queue ! d3d12videosink name=sink overlay-mode=d2d " + "display-format=r8g8b8a8-unorm"; + + context.pipeline = gst_parse_launch (pipeline_str.c_str (), nullptr); + g_assert (context.pipeline); + + auto sink = gst_bin_get_by_name (GST_BIN (context.pipeline), "sink"); + g_assert (sink); + + g_signal_connect (sink, "overlay", G_CALLBACK (on_overlay_2d), &context); + gst_object_unref (sink); + + gst_bus_add_watch (GST_ELEMENT_BUS (context.pipeline), + (GstBusFunc) bus_msg, &context); + + sret = gst_element_set_state (context.pipeline, GST_STATE_PLAYING); + g_assert (sret != GST_STATE_CHANGE_FAILURE); + + g_main_loop_run (context.loop); + + gst_element_set_state (context.pipeline, GST_STATE_NULL); + gst_bus_remove_watch (GST_ELEMENT_BUS (context.pipeline)); + gst_object_unref (context.pipeline); + + g_main_loop_unref (context.loop); + g_free (text); + + return 0; +} diff --git a/subprojects/gst-plugins-bad/tests/examples/d3d12/meson.build b/subprojects/gst-plugins-bad/tests/examples/d3d12/meson.build index 949e92548d..c5e6bf0890 100644 --- a/subprojects/gst-plugins-bad/tests/examples/d3d12/meson.build +++ b/subprojects/gst-plugins-bad/tests/examples/d3d12/meson.build @@ -2,9 +2,26 @@ if host_system != 'windows' subdir_done() endif +have_d2d_h = cc.has_header('d2d1_3.h') +have_dwrite_h = cc.has_header('dwrite.h') +have_d3d12video_h = cc.has_header('d3d12video.h') +dwrite_dep = cc.find_library('dwrite', required: false) + executable('d3d12enc-dynamic-reconfigure', ['d3d12enc-dynamic-reconfigure.c', '../key-handler.c'], include_directories : [configinc], dependencies: [gst_dep, gstbase_dep, gstvideo_dep], c_args : gst_plugins_bad_args, install: false) + +if gstd3d12_dep.found() + if have_d2d_h and have_dwrite_h and have_d3d12video_h and dwrite_dep.found() + executable('d3d12videosink-overlay', ['d3d12videosink-overlay.cpp'], + c_args : gst_plugins_bad_args + ['-DGST_USE_UNSTABLE_API'], + cpp_args : gst_plugins_bad_args + ['-DGST_USE_UNSTABLE_API'], + include_directories : [configinc, libsinc], + dependencies: [gst_dep, gstvideo_dep, gstd3d12_dep, dwrite_dep], + install: false, + ) + endif +endif