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