From 37e1847464f1f69097bf26a00ee259b33c8b6138 Mon Sep 17 00:00:00 2001 From: Seungha Yang Date: Mon, 10 Jun 2024 23:40:55 +0900 Subject: [PATCH] d3d12videosink: Add support for window handle update A large refactoring commit for adding features and improve performance * Reuse internal converter and overlay compositor: Converter can be reused as long as input and display formats are not changed. Also overlay compositor reconstruction is required only if display format is changed * Don't wait for full GPU flush on resize or close: D3D12 swapchain requires GPU idle in order to resize backbuffer. Thus CPU side waiting is required for swapchain related commands to be finished. However, don't need to wait for full GPU flushing. * Support multiple sink on a single external window Keep installed subclass window procedure even if there's no associated our internal HWND. This will make window procedure hooking less racy. Then parent HWND's message will be transferred to our internal HWNDs if needed. * Adding support for window handle update Application can change target HWND even when videosink is playing or paused state. So, users can call gst_video_overlay_set_window_handle() against d3d12videosink anytime. The videosink will be able to update internal state and setup resource upon requested. Part-of: --- .../gst-libs/gst/d3d12/gstd3d12-private.h | 1 + .../gst/d3d12/gstd3d12commandqueue-private.h | 32 + .../gst/d3d12/gstd3d12commandqueue.cpp | 52 + .../sys/d3d12/gstd3d12videosink.cpp | 341 ++-- .../d3d12/gstd3d12window-swapchain-resource.h | 78 + .../sys/d3d12/gstd3d12window-swapchain.cpp | 582 ++++++ .../sys/d3d12/gstd3d12window-swapchain.h | 60 + .../sys/d3d12/gstd3d12window-win32.cpp | 1102 ++++++++++ .../sys/d3d12/gstd3d12window-win32.h | 151 ++ .../sys/d3d12/gstd3d12window.cpp | 1810 ++++------------- .../sys/d3d12/gstd3d12window.h | 61 +- .../gst-plugins-bad/sys/d3d12/meson.build | 2 + .../examples/d3d12/d3d12videosink-switch.cpp | 351 ++++ .../tests/examples/d3d12/meson.build | 11 +- 14 files changed, 3032 insertions(+), 1602 deletions(-) create mode 100644 subprojects/gst-plugins-bad/gst-libs/gst/d3d12/gstd3d12commandqueue-private.h create mode 100644 subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window-swapchain-resource.h create mode 100644 subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window-swapchain.cpp create mode 100644 subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window-swapchain.h create mode 100644 subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window-win32.cpp create mode 100644 subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window-win32.h create mode 100644 subprojects/gst-plugins-bad/tests/examples/d3d12/d3d12videosink-switch.cpp diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/d3d12/gstd3d12-private.h b/subprojects/gst-plugins-bad/gst-libs/gst/d3d12/gstd3d12-private.h index 83034ffecc..8e88e201ea 100644 --- a/subprojects/gst-plugins-bad/gst-libs/gst/d3d12/gstd3d12-private.h +++ b/subprojects/gst-plugins-bad/gst-libs/gst/d3d12/gstd3d12-private.h @@ -24,6 +24,7 @@ #include #include #include +#include #include /* diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/d3d12/gstd3d12commandqueue-private.h b/subprojects/gst-plugins-bad/gst-libs/gst/d3d12/gstd3d12commandqueue-private.h new file mode 100644 index 0000000000..b9ee0e26f6 --- /dev/null +++ b/subprojects/gst-plugins-bad/gst-libs/gst/d3d12/gstd3d12commandqueue-private.h @@ -0,0 +1,32 @@ +/* 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. + */ + +#pragma once + +#include +#include + +G_BEGIN_DECLS + +GST_D3D12_API +HRESULT gst_d3d12_command_queue_idle_for_swapchain (GstD3D12CommandQueue * queue, + guint64 fence_value, + HANDLE event_handle); + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/d3d12/gstd3d12commandqueue.cpp b/subprojects/gst-plugins-bad/gst-libs/gst/d3d12/gstd3d12commandqueue.cpp index 5a9cbaf4b3..c05f60acbe 100644 --- a/subprojects/gst-plugins-bad/gst-libs/gst/d3d12/gstd3d12commandqueue.cpp +++ b/subprojects/gst-plugins-bad/gst-libs/gst/d3d12/gstd3d12commandqueue.cpp @@ -582,3 +582,55 @@ gst_d3d12_command_queue_drain (GstD3D12CommandQueue * queue) return S_OK; } + +HRESULT +gst_d3d12_command_queue_idle_for_swapchain (GstD3D12CommandQueue * queue, + guint64 fence_value, HANDLE event_handle) +{ + g_return_val_if_fail (GST_IS_D3D12_COMMAND_QUEUE (queue), E_INVALIDARG); + + auto priv = queue->priv; + guint64 fence_to_wait = fence_value; + HRESULT hr; + + { + std::lock_guard < std::mutex > lk (priv->execute_lock); + if (fence_value < priv->fence_val) { + fence_to_wait = fence_value + 1; + } else { + priv->fence_val++; + hr = priv->cq->Signal (priv->fence.Get (), priv->fence_val); + if (FAILED (hr)) { + GST_ERROR_OBJECT (queue, "Signal failed"); + priv->fence_val--; + return hr; + } + + fence_to_wait = priv->fence_val; + } + } + + auto completed = priv->fence->GetCompletedValue (); + if (completed < fence_to_wait) { + bool close_handle = false; + if (!event_handle) { + event_handle = CreateEventEx (nullptr, nullptr, 0, EVENT_ALL_ACCESS); + close_handle = true; + } + + hr = priv->fence->SetEventOnCompletion (fence_to_wait, event_handle); + if (FAILED (hr)) { + GST_ERROR_OBJECT (queue, "SetEventOnCompletion failed"); + if (close_handle) + CloseHandle (event_handle); + + return hr; + } + + WaitForSingleObjectEx (event_handle, INFINITE, FALSE); + if (close_handle) + CloseHandle (event_handle); + } + + return S_OK; +} diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12videosink.cpp b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12videosink.cpp index f29008a299..2d291a2566 100644 --- a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12videosink.cpp +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12videosink.cpp @@ -159,10 +159,12 @@ struct GstD3D12VideoSinkPrivate GstD3D12VideoSinkPrivate () { window = gst_d3d12_window_new (); + convert_config = gst_structure_new_empty ("convert-config"); } ~GstD3D12VideoSinkPrivate () { + gst_structure_free (convert_config); gst_clear_caps (&caps); gst_clear_object (&window); if (pool) { @@ -181,8 +183,10 @@ struct GstD3D12VideoSinkPrivate GstCaps *caps = nullptr; gboolean update_window = FALSE; GstBufferPool *pool = nullptr; + GstStructure *convert_config; gboolean warn_closed_window = FALSE; + gboolean window_open_called = FALSE; std::recursive_mutex lock; /* properties */ @@ -545,6 +549,7 @@ gst_d3d12_video_sink_dispose (GObject * object) auto self = GST_D3D12_VIDEO_SINK (object); auto priv = self->priv; + gst_d3d12_window_invalidate (priv->window); g_signal_handlers_disconnect_by_data (priv->window, self); G_OBJECT_CLASS (parent_class)->dispose (object); @@ -906,6 +911,23 @@ gst_d3d12_video_sink_set_info (GstVideoSink * sink, GstCaps * caps, GST_DEBUG_OBJECT (self, "scaling to %dx%d", GST_VIDEO_SINK_WIDTH (self), GST_VIDEO_SINK_HEIGHT (self)); + if (priv->pool) { + gst_buffer_pool_set_active (priv->pool, FALSE); + gst_clear_object (&priv->pool); + } + + priv->pool = gst_d3d12_buffer_pool_new (self->device); + auto config = gst_buffer_pool_get_config (priv->pool); + + gst_buffer_pool_config_set_params (config, priv->caps, priv->info.size, 0, 0); + if (!gst_buffer_pool_set_config (priv->pool, config) || + !gst_buffer_pool_set_active (priv->pool, TRUE)) { + GST_ELEMENT_ERROR (self, RESOURCE, FAILED, (nullptr), + ("Couldn't setup buffer pool")); + gst_clear_object (&priv->pool); + return FALSE; + } + return TRUE; } @@ -975,92 +997,126 @@ gst_d3d12_video_sink_on_fullscreen (GstD3D12Window * window, g_object_notify (G_OBJECT (self), "fullscreen"); } +static gboolean +gst_d3d12_video_sink_foreach_meta (GstBuffer * buffer, GstMeta ** meta, + GstBuffer * uploaded) +{ + if ((*meta)->info->api != GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE) + return TRUE; + + auto cmeta = (GstVideoOverlayCompositionMeta *) (*meta); + if (!cmeta->overlay) + return TRUE; + + if (gst_video_overlay_composition_n_rectangles (cmeta->overlay) == 0) + return TRUE; + + gst_buffer_add_video_overlay_composition_meta (uploaded, cmeta->overlay); + + return TRUE; +} + static GstFlowReturn -gst_d3d12_video_sink_update_window (GstD3D12VideoSink * self) +gst_d3d12_video_sink_set_buffer (GstD3D12VideoSink * self, + GstBuffer * buffer, gboolean is_prepare) { auto priv = self->priv; - auto overlay = GST_VIDEO_OVERLAY (self); - bool notify_window_handle = false; - guintptr window_handle = 0; - auto window_state = gst_d3d12_window_get_state (priv->window); - - if (window_state == GST_D3D12_WINDOW_STATE_CLOSED) { - GST_WARNING_OBJECT (self, "Window was closed"); - return GST_D3D12_WINDOW_FLOW_CLOSED; - } + GstFlowReturn ret = GST_FLOW_OK; + gboolean set_buffer = FALSE; + gboolean update_window = FALSE; { std::lock_guard < std::recursive_mutex > lk (priv->lock); - if (window_state == GST_D3D12_WINDOW_STATE_OPENED && !priv->update_window) - return GST_FLOW_OK; - - GST_DEBUG_OBJECT (self, "Updating window with caps %" GST_PTR_FORMAT, - priv->caps); - - if (window_state == GST_D3D12_WINDOW_STATE_INIT) { - if (!priv->window_handle) - gst_video_overlay_prepare_window_handle (overlay); - - if (priv->window_handle) { - gst_video_overlay_got_window_handle (overlay, priv->window_handle); - window_handle = priv->window_handle; + if (is_prepare) { + if (priv->update_window) { + set_buffer = FALSE; + update_window = FALSE; } else { - notify_window_handle = true; + set_buffer = TRUE; + update_window = FALSE; } } else { - window_handle = priv->window_handle; + if (priv->update_window) { + set_buffer = TRUE; + update_window = TRUE; + priv->update_window = FALSE; + } else { + set_buffer = FALSE; + update_window = FALSE; + } } - - priv->update_window = FALSE; } - if (priv->pool) { - gst_buffer_pool_set_active (priv->pool, FALSE); - gst_clear_object (&priv->pool); - } + if (update_window) { + gst_structure_set (priv->convert_config, + GST_D3D12_CONVERTER_OPT_GAMMA_MODE, + GST_TYPE_VIDEO_GAMMA_MODE, priv->gamma_mode, + GST_D3D12_CONVERTER_OPT_PRIMARIES_MODE, + GST_TYPE_VIDEO_PRIMARIES_MODE, priv->primaries_mode, + GST_D3D12_CONVERTER_OPT_SAMPLER_FILTER, + GST_TYPE_D3D12_CONVERTER_SAMPLER_FILTER, + gst_d3d12_sampling_method_to_native (priv->sampling_method), + GST_D3D12_CONVERTER_OPT_DEST_ALPHA_MODE, + GST_TYPE_D3D12_CONVERTER_ALPHA_MODE, + GST_VIDEO_INFO_HAS_ALPHA (&priv->info) ? + GST_D3D12_CONVERTER_ALPHA_MODE_PREMULTIPLIED : + GST_D3D12_CONVERTER_ALPHA_MODE_UNSPECIFIED, nullptr); - auto config = gst_structure_new ("convert-config", - GST_D3D12_CONVERTER_OPT_GAMMA_MODE, - GST_TYPE_VIDEO_GAMMA_MODE, priv->gamma_mode, - GST_D3D12_CONVERTER_OPT_PRIMARIES_MODE, - GST_TYPE_VIDEO_PRIMARIES_MODE, priv->primaries_mode, - GST_D3D12_CONVERTER_OPT_SAMPLER_FILTER, - GST_TYPE_D3D12_CONVERTER_SAMPLER_FILTER, - gst_d3d12_sampling_method_to_native (priv->sampling_method), nullptr); - - 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, priv->display_format); - if (ret != GST_FLOW_OK) { - if (ret == GST_FLOW_FLUSHING) { - GST_WARNING_OBJECT (self, "We are flushing"); - gst_d3d12_window_unprepare (priv->window); + ret = gst_d3d12_window_prepare (priv->window, self->device, + GST_VIDEO_SINK_WIDTH (self), GST_VIDEO_SINK_HEIGHT (self), priv->caps, + priv->convert_config, priv->display_format); + if (ret != GST_FLOW_OK) return ret; + } + + if (!set_buffer) + return GST_FLOW_OK; + + GstBuffer *upload = nullptr; + auto mem = gst_buffer_peek_memory (buffer, 0); + if (!gst_is_d3d12_memory (mem)) { + gst_buffer_pool_acquire_buffer (priv->pool, &upload, nullptr); + if (!upload) { + GST_ERROR_OBJECT (self, "Couldn't allocate upload buffer"); + return GST_FLOW_ERROR; } - GST_ELEMENT_ERROR (self, RESOURCE, FAILED, (nullptr), - ("Couldn't setup swapchain")); - return GST_FLOW_ERROR; + GstVideoFrame in_frame, out_frame; + if (!gst_video_frame_map (&in_frame, &priv->info, buffer, GST_MAP_READ)) { + GST_ERROR_OBJECT (self, "Couldn't map input frame"); + gst_buffer_unref (upload); + return GST_FLOW_ERROR; + } + + if (!gst_video_frame_map (&out_frame, &priv->info, upload, GST_MAP_WRITE)) { + GST_ERROR_OBJECT (self, "Couldn't map upload frame"); + gst_video_frame_unmap (&in_frame); + gst_buffer_unref (upload); + return GST_FLOW_ERROR; + } + + auto copy_ret = gst_video_frame_copy (&out_frame, &in_frame); + gst_video_frame_unmap (&out_frame); + gst_video_frame_unmap (&in_frame); + if (!copy_ret) { + GST_ERROR_OBJECT (self, "Couldn't copy frame"); + gst_buffer_unref (upload); + return GST_FLOW_ERROR; + } + + gst_buffer_foreach_meta (buffer, + (GstBufferForeachMetaFunc) gst_d3d12_video_sink_foreach_meta, upload); + + buffer = upload; } - if (notify_window_handle && !window_handle) { - window_handle = gst_d3d12_window_get_window_handle (priv->window); - gst_video_overlay_got_window_handle (overlay, window_handle); - } + ret = gst_d3d12_window_set_buffer (priv->window, buffer); - priv->pool = gst_d3d12_buffer_pool_new (self->device); - config = gst_buffer_pool_get_config (priv->pool); + if (upload) + gst_buffer_unref (upload); - gst_buffer_pool_config_set_params (config, priv->caps, priv->info.size, 0, 0); - if (!gst_buffer_pool_set_config (priv->pool, config) || - !gst_buffer_pool_set_active (priv->pool, TRUE)) { - GST_ELEMENT_ERROR (self, RESOURCE, FAILED, (nullptr), - ("Couldn't setup buffer pool")); - return GST_FLOW_ERROR; - } - - return GST_FLOW_OK; + return ret; } static gboolean @@ -1078,6 +1134,7 @@ gst_d3d12_video_sink_start (GstBaseSink * sink) } priv->warn_closed_window = TRUE; + priv->window_open_called = FALSE; return TRUE; } @@ -1233,7 +1290,7 @@ gst_d3d12_video_sink_query (GstBaseSink * sink, GstQuery * query) return GST_BASE_SINK_CLASS (parent_class)->query (sink, query); } -static void +static GstFlowReturn gst_d3d12_video_sink_check_device_update (GstD3D12VideoSink * self, GstBuffer * buf) { @@ -1241,11 +1298,11 @@ gst_d3d12_video_sink_check_device_update (GstD3D12VideoSink * self, auto mem = gst_buffer_peek_memory (buf, 0); if (!gst_is_d3d12_memory (mem)) - return; + return GST_FLOW_OK; auto dmem = GST_D3D12_MEMORY_CAST (mem); if (gst_d3d12_device_is_equal (dmem->device, self->device)) - return; + return GST_FLOW_OK; GST_INFO_OBJECT (self, "Updating device %" GST_PTR_FORMAT " -> %" GST_PTR_FORMAT, self->device, dmem->device); @@ -1258,25 +1315,90 @@ gst_d3d12_video_sink_check_device_update (GstD3D12VideoSink * self, std::lock_guard < std::recursive_mutex > lk (priv->context_lock); gst_clear_object (&self->device); self->device = (GstD3D12Device *) gst_object_ref (dmem->device); + + if (priv->pool) { + gst_buffer_pool_set_active (priv->pool, FALSE); + gst_clear_object (&priv->pool); + } + + priv->pool = gst_d3d12_buffer_pool_new (self->device); + auto config = gst_buffer_pool_get_config (priv->pool); + + gst_buffer_pool_config_set_params (config, priv->caps, priv->info.size, 0, 0); + if (!gst_buffer_pool_set_config (priv->pool, config) || + !gst_buffer_pool_set_active (priv->pool, TRUE)) { + GST_ELEMENT_ERROR (self, RESOURCE, FAILED, (nullptr), + ("Couldn't setup buffer pool")); + gst_clear_object (&priv->pool); + return GST_FLOW_ERROR; + } + + return GST_FLOW_OK; } -static gboolean -gst_d3d12_video_sink_foreach_meta (GstBuffer * buffer, GstMeta ** meta, - GstBuffer * uploaded) +static GstFlowReturn +gst_d3d12_video_sink_open_window (GstD3D12VideoSink * self) { - if ((*meta)->info->api != GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE) - return TRUE; + auto overlay = GST_VIDEO_OVERLAY (self); + auto priv = self->priv; + guintptr window_handle = 0; + auto is_closed = gst_d3d12_window_is_closed (priv->window); + gboolean need_open = FALSE; - auto cmeta = (GstVideoOverlayCompositionMeta *) (*meta); - if (!cmeta->overlay) - return TRUE; + { + std::lock_guard < std::recursive_mutex > lk (priv->lock); + if (!priv->window_open_called) { + GST_DEBUG_OBJECT (self, "Open was not called, try open"); + gst_video_overlay_prepare_window_handle (overlay); + need_open = TRUE; + } else if (priv->window_handle_updated) { + GST_DEBUG_OBJECT (self, "Set window handle was called, try open again"); + need_open = TRUE; + } else if (is_closed) { + /* Request new window handle */ + GST_LOG_OBJECT (self, "Window was closed, requesting new window handle"); + gst_video_overlay_prepare_window_handle (overlay); + if (priv->window_handle_updated) { + GST_DEBUG_OBJECT (self, "Set window handle was called"); + need_open = TRUE; + } + } - if (gst_video_overlay_composition_n_rectangles (cmeta->overlay) == 0) - return TRUE; + if (!need_open) { + if (!is_closed) + return GST_FLOW_OK; - gst_buffer_add_video_overlay_composition_meta (uploaded, cmeta->overlay); + GST_WARNING_OBJECT (self, "Window was closed"); + return GST_D3D12_WINDOW_FLOW_CLOSED; + } - return TRUE; + window_handle = priv->window_handle; + priv->window_handle_updated = FALSE; + } + + priv->window_open_called = TRUE; + priv->warn_closed_window = TRUE; + auto ret = gst_d3d12_window_open (priv->window, self->device, + GST_VIDEO_SINK_WIDTH (self), GST_VIDEO_SINK_HEIGHT (self), + (HWND) window_handle); + + std::lock_guard < std::recursive_mutex > lk (priv->lock); + if (ret == GST_FLOW_OK) { + if (window_handle) { + GST_DEBUG_OBJECT (self, "Window created with HWND %p", + (void *) window_handle); + gst_video_overlay_got_window_handle (overlay, window_handle); + } else { + auto internal_hwnd = gst_d3d12_window_get_window_handle (priv->window); + GST_DEBUG_OBJECT (self, "Window created with internal HWND %p", + (void *) internal_hwnd); + gst_video_overlay_got_window_handle (overlay, internal_hwnd); + } + + priv->update_window = TRUE; + } + + return ret; } static GstFlowReturn @@ -1284,54 +1406,16 @@ gst_d3d12_video_sink_prepare (GstBaseSink * sink, GstBuffer * buffer) { auto self = GST_D3D12_VIDEO_SINK (sink); auto priv = self->priv; - GstBuffer *upload = nullptr; - auto mem = gst_buffer_peek_memory (buffer, 0); - gst_d3d12_video_sink_check_device_update (self, buffer); - auto ret = gst_d3d12_video_sink_update_window (self); + auto ret = gst_d3d12_video_sink_check_device_update (self, buffer); if (ret != GST_FLOW_OK) goto out; - if (!gst_is_d3d12_memory (mem)) { - gst_buffer_pool_acquire_buffer (priv->pool, &upload, nullptr); - if (!upload) { - GST_ERROR_OBJECT (self, "Couldn't allocate upload buffer"); - return GST_FLOW_ERROR; - } + ret = gst_d3d12_video_sink_open_window (self); + if (ret != GST_FLOW_OK) + goto out; - GstVideoFrame in_frame, out_frame; - if (!gst_video_frame_map (&in_frame, &priv->info, buffer, GST_MAP_READ)) { - GST_ERROR_OBJECT (self, "Couldn't map input frame"); - gst_buffer_unref (upload); - return GST_FLOW_ERROR; - } - - if (!gst_video_frame_map (&out_frame, &priv->info, upload, GST_MAP_WRITE)) { - GST_ERROR_OBJECT (self, "Couldn't map upload frame"); - gst_video_frame_unmap (&in_frame); - gst_buffer_unref (upload); - return GST_FLOW_ERROR; - } - - auto copy_ret = gst_video_frame_copy (&out_frame, &in_frame); - gst_video_frame_unmap (&out_frame); - gst_video_frame_unmap (&in_frame); - if (!copy_ret) { - GST_ERROR_OBJECT (self, "Couldn't copy frame"); - gst_buffer_unref (upload); - return GST_FLOW_ERROR; - } - - gst_buffer_foreach_meta (buffer, - (GstBufferForeachMetaFunc) gst_d3d12_video_sink_foreach_meta, upload); - - buffer = upload; - } - - ret = gst_d3d12_window_set_buffer (priv->window, buffer); - - if (upload) - gst_buffer_unref (upload); + ret = gst_d3d12_video_sink_set_buffer (self, buffer, TRUE); out: if (ret == GST_D3D12_WINDOW_FLOW_CLOSED) { @@ -1359,9 +1443,13 @@ gst_d3d12_video_sink_show_frame (GstVideoSink * sink, GstBuffer * buf) { auto self = GST_D3D12_VIDEO_SINK (sink); auto priv = self->priv; - auto sync = gst_base_sink_get_sync (GST_BASE_SINK_CAST (sink)); + auto ret = gst_d3d12_video_sink_set_buffer (self, buf, FALSE); + if (ret != GST_FLOW_OK) + goto out; - auto ret = gst_d3d12_window_present (priv->window, sync); + ret = gst_d3d12_window_present (priv->window); + +out: if (ret == GST_D3D12_WINDOW_FLOW_CLOSED) { if (priv->error_on_closed) { GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND, @@ -1415,7 +1503,8 @@ gst_d3d12_video_sink_overlay_set_window_handle (GstVideoOverlay * overlay, std::lock_guard < std::recursive_mutex > lk (priv->lock); if (priv->window_handle != window_handle) { priv->window_handle = window_handle; - priv->update_window = TRUE; + if (priv->window_handle) + priv->window_handle_updated = TRUE; } } diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window-swapchain-resource.h b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window-swapchain-resource.h new file mode 100644 index 0000000000..fd18c9983a --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window-swapchain-resource.h @@ -0,0 +1,78 @@ +/* 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. + */ + +#pragma once + +#include +#include "gstd3d12pluginutils.h" +#include "gstd3d12overlaycompositor.h" +#include +#include +#include +#include +#include +#include +#include + +struct SwapChainBuffer +{ + SwapChainBuffer (GstBuffer * buffer, ID3D12Resource * backbuf_resource); + SwapChainBuffer () = delete; + ~SwapChainBuffer(); + + Microsoft::WRL::ComPtr d2d_target; + Microsoft::WRL::ComPtr wrapped_resource; + Microsoft::WRL::ComPtr resource; + GstBuffer *backbuf = nullptr; + bool is_first = true; +}; + +struct SwapChainResource +{ + SwapChainResource (GstD3D12Device * device); + SwapChainResource () = delete; + ~SwapChainResource(); + + void clear_resource (); + bool ensure_d3d11_target (SwapChainBuffer * swapbuf); + bool ensure_d2d_target (SwapChainBuffer * swapbuf); + + Microsoft::WRL::ComPtr swapchain; + Microsoft::WRL::ComPtr cl; + Microsoft::WRL::ComPtr device11on12; + Microsoft::WRL::ComPtr device11; + Microsoft::WRL::ComPtr context11; + Microsoft::WRL::ComPtr factory2d; + Microsoft::WRL::ComPtr device2d; + Microsoft::WRL::ComPtr context2d; + + std::vector> buffers; + GstBuffer *msaa_buf = nullptr; + GstBuffer *cached_buf = nullptr; + GstD3D12Converter *conv = nullptr; + GstD3D12OverlayCompositor *comp = nullptr; + GstD3D12Device *device = nullptr; + GstD3D12CommandAllocatorPool *ca_pool = nullptr; + HANDLE event_handle = nullptr; + UINT64 fence_val = 0; + std::queue prev_fence_val; + DXGI_FORMAT render_format = DXGI_FORMAT_UNKNOWN; + D3D12_RESOURCE_DESC buffer_desc; +}; + diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window-swapchain.cpp b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window-swapchain.cpp new file mode 100644 index 0000000000..379f4466f7 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window-swapchain.cpp @@ -0,0 +1,582 @@ +/* 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 "gstd3d12window-swapchain.h" +#include + +GST_DEBUG_CATEGORY_EXTERN (gst_d3d12_window_debug); +#define GST_CAT_DEFAULT gst_d3d12_window_debug + +/* *INDENT-OFF* */ +using namespace Microsoft::WRL; + +#define BACK_BUFFER_COUNT 3 + +SwapChainBuffer::SwapChainBuffer (GstBuffer * buffer, + ID3D12Resource * backbuf_resource) +{ + backbuf = buffer; + resource = backbuf_resource; +} + +SwapChainBuffer::~SwapChainBuffer () +{ + d2d_target = nullptr; + wrapped_resource = nullptr; + resource = nullptr; + gst_clear_buffer (&backbuf); +} + +SwapChainResource::SwapChainResource (GstD3D12Device * dev) +{ + 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); +} + +SwapChainResource::~SwapChainResource () +{ + GST_DEBUG_OBJECT (device, "Releasing swapchain resource"); + + context2d = nullptr; + device2d = nullptr; + factory2d = nullptr; + + context11 = nullptr; + device11 = nullptr; + device11on12 = nullptr; + + buffers.clear(); + swapchain = nullptr; + cl = nullptr; + + gst_clear_buffer (&msaa_buf); + gst_clear_buffer (&cached_buf); + + gst_clear_object (&conv); + gst_clear_object (&comp); + gst_clear_object (&conv); + gst_clear_object (&ca_pool); + gst_clear_object (&device); + + CloseHandle (event_handle); +} + +void +SwapChainResource::clear_resource () +{ + if (!buffers.empty ()) { + auto cq = gst_d3d12_device_get_command_queue (device, + D3D12_COMMAND_LIST_TYPE_DIRECT); + gst_d3d12_command_queue_idle_for_swapchain (cq, fence_val, event_handle); + prev_fence_val = { }; + } + + if (context11) + gst_d3d12_device_11on12_lock (device); + + buffers.clear (); + gst_clear_buffer (&msaa_buf); + + if (context2d) + context2d->SetTarget (nullptr); + + if (context11) { + context11->ClearState (); + context11->Flush (); + gst_d3d12_device_11on12_unlock (device); + } +} + +static bool +ensure_d3d11 (SwapChainResource * resource) +{ + if (resource->device11on12) + return true; + + auto unknown = gst_d3d12_device_get_11on12_handle (resource->device); + if (!unknown) + return false; + + unknown->QueryInterface (IID_PPV_ARGS (&resource->device11on12)); + resource->device11on12.As (&resource->device11); + resource->device11->GetImmediateContext (&resource->context11); + + return true; +} + +static bool +ensure_d2d (SwapChainResource * resource) +{ + if (resource->context2d) + return true; + + if (!ensure_d3d11 (resource)) + return false; + + HRESULT hr; + if (!resource->factory2d) { + hr = D2D1CreateFactory (D2D1_FACTORY_TYPE_SINGLE_THREADED, + IID_PPV_ARGS (&resource->factory2d)); + if (FAILED (hr)) + return false; + } + + GstD3D12Device11on12LockGuard lk (resource->device); + if (!resource->device2d) { + ComPtr device_dxgi; + hr = resource->device11.As (&device_dxgi); + if (FAILED (hr)) + return false; + + hr = resource->factory2d->CreateDevice (device_dxgi.Get (), + &resource->device2d); + if (FAILED (hr)) + return false; + } + + if (!resource->context2d) { + hr = resource->device2d->CreateDeviceContext ( + D2D1_DEVICE_CONTEXT_OPTIONS_NONE, &resource->context2d); + if (FAILED (hr)) + return false; + } + + return true; +} + +bool +SwapChainResource::ensure_d3d11_target (SwapChainBuffer * swapbuf) +{ + if (swapbuf->wrapped_resource) + return true; + + if (!ensure_d3d11 (this)) + 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 +SwapChainResource::ensure_d2d_target (SwapChainBuffer * swapbuf) +{ + if (swapbuf->d2d_target) + return true; + + if (!ensure_d2d (this)) + return false; + + if (!ensure_d3d11_target (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; +} + +SwapChain::SwapChain (GstD3D12Device * device) +{ + resource_ = std::make_unique (device); +} + +struct AsyncReleaseData +{ + std::unique_ptr resource; +}; + +SwapChain::~SwapChain() +{ + lock_.lock (); + if (!resource_->buffers.empty ()) { + auto cq = gst_d3d12_device_get_command_queue (resource_->device, + D3D12_COMMAND_LIST_TYPE_DIRECT); + gst_d3d12_command_queue_idle_for_swapchain (cq, resource_->fence_val, + resource_->event_handle); + } + + resource_ = nullptr; + + if (converter_config_) + gst_structure_free (converter_config_); + + lock_.unlock (); +} + +GstFlowReturn +SwapChain::setup_swapchain (GstD3D12Window * window, GstD3D12Device * device, + HWND hwnd, DXGI_FORMAT format, const GstVideoInfo * in_info, + const GstVideoInfo * out_info, GstStructure * conv_config, + bool & is_new_swapchain) +{ + is_new_swapchain = false; + std::lock_guard lk (lock_); + if (!gst_d3d12_device_is_equal (device, resource_->device)) { + gst_d3d12_device_fence_wait (resource_->device, + D3D12_COMMAND_LIST_TYPE_DIRECT, resource_->fence_val, + resource_->event_handle); + resource_ = std::make_unique (device); + } + + if (!resource_->swapchain) { + DXGI_SWAP_CHAIN_DESC1 desc = { }; + desc.Format = format; + desc.SampleDesc.Count = 1; + desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + desc.BufferCount = BACK_BUFFER_COUNT; + desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; + + auto device = resource_->device; + auto factory = gst_d3d12_device_get_factory_handle (device); + auto cq = gst_d3d12_device_get_command_queue (device, + D3D12_COMMAND_LIST_TYPE_DIRECT); + auto cq_handle = gst_d3d12_command_queue_get_handle (cq); + + ComPtr < IDXGISwapChain1 > swapchain; + auto hr = factory->CreateSwapChainForHwnd (cq_handle, hwnd, &desc, nullptr, + nullptr, &swapchain); + if (!gst_d3d12_result (hr, device)) + return GST_FLOW_ERROR; + + hr = swapchain.As (&resource_->swapchain); + if (!gst_d3d12_result (hr, device)) + return GST_FLOW_ERROR; + + is_new_swapchain = true; + } else { + resource_->clear_resource (); + if (render_format_ != format) { + gst_clear_object (&resource_->comp); + gst_clear_object (&resource_->conv); + } else { + if (GST_VIDEO_INFO_FORMAT (in_info) != in_format_) { + gst_clear_object (&resource_->conv); + } else if (converter_config_ && + !gst_structure_is_equal (converter_config_, conv_config)) { + gst_clear_object (&resource_->conv); + } + } + } + + if (converter_config_) + gst_structure_free (converter_config_); + converter_config_ = gst_structure_copy (conv_config); + + if (!resource_->conv) { + resource_->conv = gst_d3d12_converter_new (resource_->device, + in_info, out_info, nullptr, nullptr, gst_structure_copy (conv_config)); + if (!resource_->conv) { + GST_ERROR ("Couldn't create converter"); + return GST_FLOW_ERROR; + } + } else { + g_object_set (resource_->conv, "src-x", (gint) 0, "src-y", (gint) 0, + "src-width", in_info->width, "src-height", in_info->height, nullptr); + } + + if (!resource_->comp) { + resource_->comp = gst_d3d12_overlay_compositor_new (resource_->device, + out_info); + if (!resource_->comp) { + GST_ERROR ("Couldn't create overlay compositor"); + return GST_FLOW_ERROR; + } + } + + render_format_ = format; + crop_rect_ = CD3DX12_BOX (0, 0, in_info->width, in_info->height); + prev_crop_rect_ = crop_rect_; + + return resize_buffer (window); +} + +void +SwapChain::disable_alt_enter (HWND hwnd) +{ + std::lock_guard lk (lock_); + if (!resource_ || !resource_->swapchain) + return; + + HRESULT hr = E_FAIL; + { + /* DXGI API is not thread safe, takes global lock */ + static std::recursive_mutex factory_lock; + std::lock_guard flk (factory_lock); + ComPtr < IDXGIFactory1 > parent_factory; + auto hr = resource_->swapchain->GetParent (IID_PPV_ARGS (&parent_factory)); + if (SUCCEEDED (hr)) + hr = parent_factory->MakeWindowAssociation (hwnd, DXGI_MWA_NO_ALT_ENTER); + } + + if (SUCCEEDED (hr)) + GST_DEBUG ("Alt-Enter is disabled for hwnd %p", hwnd); +} + +GstFlowReturn +SwapChain::resize_buffer (GstD3D12Window * window) +{ + std::lock_guard lk (lock_); + if (!resource_->swapchain) + return GST_FLOW_OK; + + auto device = resource_->device; + + resource_->clear_resource (); + + DXGI_SWAP_CHAIN_DESC desc = { }; + resource_->swapchain->GetDesc (&desc); + auto hr = resource_->swapchain->ResizeBuffers (BACK_BUFFER_COUNT, + 0, 0, render_format_, desc.Flags); + if (!gst_d3d12_result (hr, device)) + return GST_FLOW_ERROR; + + for (guint i = 0; i < BACK_BUFFER_COUNT; i++) { + ComPtr < ID3D12Resource > backbuf; + hr = resource_->swapchain->GetBuffer (i, IID_PPV_ARGS (&backbuf)); + if (!gst_d3d12_result (hr, device)) { + GST_ERROR_OBJECT (device, "Couldn't get backbuffer"); + return GST_FLOW_ERROR; + } + + if (i == 0) + resource_->buffer_desc = GetDesc (backbuf); + + auto mem = gst_d3d12_allocator_alloc_wrapped (nullptr, device, + backbuf.Get (), 0, nullptr, nullptr); + auto buf = gst_buffer_new (); + gst_buffer_append_memory (buf, mem); + auto swapbuf = std::make_shared < SwapChainBuffer > (buf, backbuf.Get ()); + resource_->buffers.push_back (swapbuf); + } + + auto buffer_desc = resource_->buffer_desc; + GstD3D12MSAAMode msaa_mode; + gst_d3d12_window_get_msaa (window, msaa_mode); + + UINT sample_count = 1; + switch (msaa_mode) { + case GST_D3D12_MSAA_2X: + sample_count = 2; + break; + case GST_D3D12_MSAA_4X: + sample_count = 4; + break; + case GST_D3D12_MSAA_8X: + sample_count = 8; + break; + default: + break; + } + + auto device_handle = gst_d3d12_device_get_device_handle (device); + D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS feature_data = { }; + feature_data.Format = buffer_desc.Format; + feature_data.SampleCount = sample_count; + + while (feature_data.SampleCount > 1) { + hr = device_handle->CheckFeatureSupport ( + D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS, + &feature_data, sizeof (feature_data)); + if (SUCCEEDED (hr) && feature_data.NumQualityLevels > 0) + break; + + feature_data.SampleCount /= 2; + } + + if (feature_data.SampleCount > 1 && feature_data.NumQualityLevels > 0) { + GST_DEBUG_OBJECT (device, "Enable MSAA x%d with quality level %d", + feature_data.SampleCount, feature_data.NumQualityLevels - 1); + D3D12_HEAP_PROPERTIES heap_prop = + CD3DX12_HEAP_PROPERTIES (D3D12_HEAP_TYPE_DEFAULT); + D3D12_RESOURCE_DESC resource_desc = + CD3DX12_RESOURCE_DESC::Tex2D (buffer_desc.Format, + buffer_desc.Width, buffer_desc.Height, + 1, 1, feature_data.SampleCount, feature_data.NumQualityLevels - 1, + D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET); + D3D12_CLEAR_VALUE clear_value = { }; + clear_value.Format = buffer_desc.Format; + clear_value.Color[0] = 0.0f; + clear_value.Color[1] = 0.0f; + clear_value.Color[2] = 0.0f; + clear_value.Color[3] = 1.0f; + + ComPtr < ID3D12Resource > msaa_texture; + hr = device_handle->CreateCommittedResource (&heap_prop, + D3D12_HEAP_FLAG_NONE, + &resource_desc, D3D12_RESOURCE_STATE_RENDER_TARGET, &clear_value, + IID_PPV_ARGS (&msaa_texture)); + if (gst_d3d12_result (hr, device)) { + auto mem = gst_d3d12_allocator_alloc_wrapped (nullptr, device, + msaa_texture.Get (), 0, nullptr, nullptr); + resource_->msaa_buf = gst_buffer_new (); + gst_buffer_append_memory (resource_->msaa_buf, mem); + } + } + + first_present_ = true; + backbuf_rendered_ = false; + + GstFlowReturn ret = GST_FLOW_OK; + if (resource_->cached_buf) { + ret = set_buffer (window, resource_->cached_buf); + if (ret == GST_FLOW_OK) + ret = present (); + } + + return ret; +} + +GstFlowReturn +SwapChain::set_buffer (GstD3D12Window * window, GstBuffer * buffer) +{ + std::lock_guard lk (lock_); + if (!resource_->swapchain) { + if (!buffer) { + GST_DEBUG_OBJECT (window, "Swapchain is not configured"); + return GST_FLOW_OK; + } + + GST_ERROR_OBJECT (window, "Couldn't set buffer without swapchain"); + return GST_FLOW_ERROR; + } + + if (buffer) + gst_buffer_replace (&resource_->cached_buf, buffer); + + if (!resource_->cached_buf) + return GST_FLOW_OK; + + auto crop_rect = crop_rect_; + auto crop_meta = gst_buffer_get_video_crop_meta (resource_->cached_buf); + if (crop_meta) { + crop_rect = CD3DX12_BOX (crop_meta->x, crop_meta->y, + crop_meta->x + crop_meta->width, crop_meta->y + crop_meta->height); + } + + if (crop_rect != prev_crop_rect_) { + g_object_set (resource_->conv, "src-x", (gint) crop_rect.left, + "src-y", (gint) crop_rect.top, + "src-width", (gint) (crop_rect.right - crop_rect.left), + "src-height", (gint) (crop_rect.bottom - crop_rect.top), nullptr); + prev_crop_rect_ = crop_rect; + } + + before_rendering (); + auto ret = gst_d3d12_window_render (window, resource_.get (), + resource_->cached_buf, first_present_, output_rect_); + after_rendering (); + if (ret == GST_FLOW_OK) + backbuf_rendered_ = true; + + return ret; +} + +GstFlowReturn +SwapChain::present () +{ + std::lock_guard lk (lock_); + if (!resource_->swapchain) + return GST_FLOW_ERROR; + + if (!backbuf_rendered_) + return GST_FLOW_OK; + + DXGI_PRESENT_PARAMETERS params = { }; + if (!first_present_) { + params.DirtyRectsCount = 1; + params.pDirtyRects = &output_rect_; + } + + auto hr = resource_->swapchain->Present1 (0, 0, ¶ms); + + switch (hr) { + case DXGI_ERROR_DEVICE_REMOVED: + case DXGI_ERROR_INVALID_CALL: + case E_OUTOFMEMORY: + gst_d3d12_result (hr, resource_->device); + return GST_FLOW_ERROR; + default: + /* Ignore other return code */ + break; + } + + first_present_ = false; + backbuf_rendered_ = false; + + gst_d3d12_device_execute_command_lists (resource_->device, + D3D12_COMMAND_LIST_TYPE_DIRECT, 0, nullptr, &resource_->fence_val); + resource_->prev_fence_val.push (resource_->fence_val); + + return GST_FLOW_OK; +} + +void +SwapChain::before_rendering () +{ + UINT64 fence_val_to_wait = 0; + auto resource = resource_.get (); + + while (resource->prev_fence_val.size () > BACK_BUFFER_COUNT + 1) { + fence_val_to_wait = resource->prev_fence_val.front (); + resource->prev_fence_val.pop (); + } + + if (fence_val_to_wait) { + auto completed = gst_d3d12_device_get_completed_value (resource->device, + D3D12_COMMAND_LIST_TYPE_DIRECT); + if (completed < fence_val_to_wait) { + gst_d3d12_device_fence_wait (resource_->device, + D3D12_COMMAND_LIST_TYPE_DIRECT, fence_val_to_wait, + resource->event_handle); + } + } +} + +void SwapChain::after_rendering () +{ + resource_->prev_fence_val.push (resource_->fence_val); +} +/* *INDENT-ON* */ diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window-swapchain.h b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window-swapchain.h new file mode 100644 index 0000000000..d9d2e98948 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window-swapchain.h @@ -0,0 +1,60 @@ +/* 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. + */ + +#pragma once + +#include +#include "gstd3d12pluginutils.h" +#include "gstd3d12window-swapchain-resource.h" +#include "gstd3d12window.h" +#include +#include + +class SwapChain +{ +public: + SwapChain (GstD3D12Device * device); + ~SwapChain (); + + GstFlowReturn setup_swapchain (GstD3D12Window * window, + GstD3D12Device * device, HWND hwnd, DXGI_FORMAT format, + const GstVideoInfo * in_info, const GstVideoInfo * out_info, + GstStructure * conv_config, bool & is_new_swapchain); + void disable_alt_enter (HWND hwnd); + GstFlowReturn resize_buffer (GstD3D12Window * window); + GstFlowReturn set_buffer (GstD3D12Window * window, GstBuffer * buffer); + GstFlowReturn present (); + +private: + void before_rendering (); + void after_rendering (); + +private: + std::unique_ptr resource_; + DXGI_FORMAT render_format_ = DXGI_FORMAT_UNKNOWN; + std::recursive_mutex lock_; + GstVideoFormat in_format_ = GST_VIDEO_FORMAT_UNKNOWN; + GstStructure *converter_config_ = nullptr; + bool first_present_ = true; + bool backbuf_rendered_ = false; + RECT output_rect_ = { }; + D3D12_BOX crop_rect_ = { }; + D3D12_BOX prev_crop_rect_ = { }; +}; + diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window-win32.cpp b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window-win32.cpp new file mode 100644 index 0000000000..744c2a1fa1 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window-win32.cpp @@ -0,0 +1,1102 @@ +/* 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 "gstd3d12window-win32.h" + +GST_DEBUG_CATEGORY_EXTERN (gst_d3d12_window_debug); +#define GST_CAT_DEFAULT gst_d3d12_window_debug + +#define WS_GST_D3D12 (WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW) + +#define WM_GST_D3D12_FULLSCREEN (WM_USER + 1) +#define WM_GST_D3D12_ATTACH_INTERNAL_WINDOW (WM_USER + 2) +#define WM_GST_D3D12_DETACH_INTERNAL_WINDOW (WM_USER + 3) +#define WM_GST_D3D12_DESTROY_INTERNAL_WINDOW (WM_USER + 4) +#define WM_GST_D3D12_UPDATE_RENDER_RECT (WM_USER + 5) +#define WM_GST_D3D12_PARENT_SIZE (WM_USER + 6) +#define WM_GST_D3D12_SWAPCHAIN_CREATED (WM_USER + 7) + +#ifndef GET_X_LPARAM +#define GET_X_LPARAM(lp) ((int)(short)LOWORD(lp)) +#endif + +#ifndef GET_Y_LPARAM +#define GET_Y_LPARAM(lp) ((int)(short)HIWORD(lp)) +#endif + +/* *INDENT-OFF* */ +SwapChainProxy::SwapChainProxy (GstD3D12Window * window, SIZE_T id) +{ + g_assert (GST_IS_D3D12_WINDOW (window)); + window_ = (GstD3D12Window *) gst_object_ref (window); + id_ = id; + + GST_DEBUG_OBJECT (window_, "Creating proxy %" G_GSIZE_FORMAT, id_); +} + +SwapChainProxy::~SwapChainProxy () +{ + GST_DEBUG_OBJECT (window_, "Destroying proxy %" G_GSIZE_FORMAT, id_); + + swapchain_ = nullptr; + if (window_thread_ && hwnd_) { + if (window_thread_ == g_thread_self ()) + DestroyWindow (hwnd_); + else + PostMessageW (hwnd_, WM_GST_D3D12_DESTROY_INTERNAL_WINDOW, 0, 0); + } + + gst_object_unref (window_); +} + +void +SwapChainProxy::set_window_handles (HWND parent_hwnd, HWND child_hwnd) +{ + parent_hwnd_ = parent_hwnd; + hwnd_ = child_hwnd; + window_thread_ = g_thread_self (); +} + +HWND +SwapChainProxy::get_window_handle () +{ + return hwnd_; +} + +SIZE_T +SwapChainProxy::get_id () +{ + return id_; +} + +bool +SwapChainProxy::has_parent () +{ + return parent_hwnd_ ? true : false; +} + +void +SwapChainProxy::on_destroy () +{ + std::lock_guard lk (lock_); + hwnd_ = nullptr; + swapchain_ = nullptr; +} + +void +SwapChainProxy::set_fullscreen_on_alt_enter (bool enable) +{ + fstate_.fullscreen_on_alt_enter = enable; +} + +void +SwapChainProxy::toggle_fullscreen (bool enable) +{ + bool send_msg = false; + + { + std::lock_guard lk (lock_); + + /* fullscreen toggle is supported only for internal hwnd */ + if (parent_hwnd_ || !hwnd_) + return; + + if (window_thread_ == g_thread_self ()) + send_msg = true; + } + + if (send_msg) + SendMessageW (hwnd_, WM_GST_D3D12_FULLSCREEN, 0, (LPARAM) enable); + else + PostMessageW (hwnd_, WM_GST_D3D12_FULLSCREEN, 0, (LPARAM) enable); +} + +void +SwapChainProxy::update_render_rect () +{ + bool send_msg = false; + { + std::lock_guard lk (lock_); + if (!hwnd_) + return; + + if (window_thread_ == g_thread_self ()) + send_msg = true; + } + + if (send_msg) + SendMessageW (hwnd_, WM_GST_D3D12_UPDATE_RENDER_RECT, 0, 0); + else + PostMessageW (hwnd_, WM_GST_D3D12_UPDATE_RENDER_RECT, 0, 0); +} + +void +SwapChainProxy::handle_update_render_rect () +{ + GstVideoRectangle rect; + gst_d3d12_window_get_render_rect (window_, &rect); + + if (rect.w == -1 && rect.h == -1 && parent_hwnd_) { + GST_DEBUG_OBJECT (window_, "Back to parent size"); + + RECT parent_rect; + GetClientRect (parent_hwnd_, &parent_rect); + MoveWindow (hwnd_, parent_rect.left, parent_rect.top, + parent_rect.right - parent_rect.left, + parent_rect.bottom - parent_rect.top, FALSE); + } else if (rect.w > 0 && rect.h > 0) { + GST_DEBUG_OBJECT (window_, "Applying render rect"); + MoveWindow (hwnd_, rect.x, rect.y, rect.w, rect.h, FALSE); + } +} + +void +SwapChainProxy::handle_fullscreen_change (bool is_fullscreen) +{ + if (is_fullscreen == fstate_.applied_fullscreen) + return; + + if (is_fullscreen) { + GST_DEBUG_OBJECT (window_, "Enable fullscreen"); + GetWindowPlacement (hwnd_, &fstate_.restore_placement); + + ShowWindow (hwnd_, SW_SHOW); + + fstate_.restore_style = GetWindowLong (hwnd_, GWL_STYLE); + + SetWindowLongA (hwnd_, GWL_STYLE, + fstate_.restore_style & + ~(WS_CAPTION | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_SYSMENU | + WS_THICKFRAME | WS_MAXIMIZE)); + + HMONITOR monitor = MonitorFromWindow (hwnd_, MONITOR_DEFAULTTONEAREST); + MONITORINFO minfo = { }; + minfo.cbSize = sizeof (minfo); + if (!GetMonitorInfo (monitor, &minfo)) { + GST_WARNING_OBJECT (window_, "Couldn't get monitor info"); + return; + } + + SetWindowPos (hwnd_, HWND_TOP, minfo.rcMonitor.left, minfo.rcMonitor.top, + minfo.rcMonitor.right - minfo.rcMonitor.left, + minfo.rcMonitor.bottom - minfo.rcMonitor.top, + SWP_FRAMECHANGED | SWP_NOACTIVATE); + ShowWindow (hwnd_, SW_MAXIMIZE); + } else { + GST_DEBUG_OBJECT (window_, "Back to window mode"); + + SetWindowLongW (hwnd_, GWL_STYLE, fstate_.restore_style); + SetWindowPlacement (hwnd_, &fstate_.restore_placement); + } + + fstate_.applied_fullscreen = is_fullscreen; +} + +void +SwapChainProxy::handle_syskey_down () +{ + if (!fstate_.fullscreen_on_alt_enter) + return; + + WORD state = GetKeyState (VK_RETURN); + BYTE high = HIBYTE (state); + if (high & 0x1) { + LPARAM param = 1; + if (fstate_.applied_fullscreen) + param = 0; + + SendMessageW (hwnd_, WM_GST_D3D12_FULLSCREEN, 0, param); + } +} + +void +SwapChainProxy::handle_key_event (UINT msg, WPARAM wparam, LPARAM lparam) +{ + if (!gst_d3d12_window_get_navigation_events_enabled (window_)) + return; + + gunichar2 keyname[128]; + const gchar *event; + + if (!GetKeyNameTextW (lparam, (LPWSTR) keyname, 128)) + return; + + gchar *name = g_utf16_to_utf8 (keyname, 128, nullptr, nullptr, nullptr); + if (!name) + return; + + if (msg == WM_KEYDOWN) + event = "key-press"; + else + event = "key-release"; + + gst_d3d12_window_on_key_event (window_, event, name); + g_free (name); +} + +void +SwapChainProxy::handle_mouse_event (UINT msg, WPARAM wparam, LPARAM lparam) +{ + if (!gst_d3d12_window_get_navigation_events_enabled (window_)) + return; + + gint button = 0; + const gchar *event = nullptr; + guint modifier = 0; + + auto xpos = GET_X_LPARAM (lparam); + auto ypos = GET_Y_LPARAM (lparam); + + if (parent_hwnd_) { + POINT updated_pos; + updated_pos.x = xpos; + updated_pos.y = ypos; + + if (!ClientToScreen (parent_hwnd_, &updated_pos)) { + GST_WARNING_OBJECT (window_, "Couldn't convert parent position to screen"); + return; + } + + if (!ScreenToClient (hwnd_, &updated_pos)) { + GST_WARNING_OBJECT (window_, "Couldn't convert screen position to client"); + return; + } + + xpos = updated_pos.x; + ypos = updated_pos.y; + } + + switch (msg) { + case WM_MOUSEMOVE: + button = 0; + event = "mouse-move"; + break; + case WM_LBUTTONDOWN: + button = 1; + event = "mouse-button-press"; + break; + case WM_LBUTTONUP: + button = 1; + event = "mouse-button-release"; + break; + case WM_LBUTTONDBLCLK: + button = 1; + event = "mouse-double-click"; + break; + case WM_RBUTTONDOWN: + button = 2; + event = "mouse-button-press"; + break; + case WM_RBUTTONUP: + button = 2; + event = "mouse-button-release"; + break; + case WM_RBUTTONDBLCLK: + button = 2; + event = "mouse-double-click"; + break; + case WM_MBUTTONDOWN: + button = 3; + event = "mouse-button-press"; + break; + case WM_MBUTTONUP: + button = 3; + event = "mouse-button-release"; + break; + case WM_MBUTTONDBLCLK: + button = 3; + event = "mouse-double-click"; + break; + default: + return; + } + + if ((wparam & MK_CONTROL) != 0) + modifier |= GST_NAVIGATION_MODIFIER_CONTROL_MASK; + if ((wparam & MK_LBUTTON) != 0) + modifier |= GST_NAVIGATION_MODIFIER_BUTTON1_MASK; + if ((wparam & MK_RBUTTON) != 0) + modifier |= GST_NAVIGATION_MODIFIER_BUTTON2_MASK; + if ((wparam & MK_MBUTTON) != 0) + modifier |= GST_NAVIGATION_MODIFIER_BUTTON3_MASK; + if ((wparam & MK_SHIFT) != 0) + modifier |= GST_NAVIGATION_MODIFIER_SHIFT_MASK; + + GstVideoRectangle output_rect = { }; + GstVideoOrientationMethod orientation; + gint in_w, in_h; + gst_d3d12_window_get_mouse_pos_info (window_, &output_rect, + in_w, in_h, orientation); + + if (in_w <= 0 || in_h <= 0 || xpos < output_rect.x || + xpos >= output_rect.x + output_rect.w || ypos < output_rect.y || + ypos >= output_rect.y + output_rect.h) { + return; + } + + gint src_w, src_h; + switch (orientation) { + case GST_VIDEO_ORIENTATION_90R: + case GST_VIDEO_ORIENTATION_90L: + case GST_VIDEO_ORIENTATION_UL_LR: + case GST_VIDEO_ORIENTATION_UR_LL: + src_w = in_h; + src_h = in_w; + break; + default: + src_w = in_w; + src_h = in_h; + break; + } + + xpos = ((xpos - output_rect.x) / (double) output_rect.w) * src_w; + ypos = ((ypos - output_rect.y) / (double) output_rect.h) * src_h; + + xpos = CLAMP (xpos, 0, (LONG) (src_w - 1)); + ypos = CLAMP (ypos, 0, (LONG) (src_h - 1)); + + double final_x = 0; + double final_y = 0; + + switch (orientation) { + case GST_VIDEO_ORIENTATION_90R: + final_x = ypos; + final_y = src_w - xpos; + break; + case GST_VIDEO_ORIENTATION_90L: + final_x = src_h - ypos; + final_y = xpos; + break; + case GST_VIDEO_ORIENTATION_UR_LL: + final_x = src_h - ypos; + final_y = src_w - xpos; + break; + case GST_VIDEO_ORIENTATION_UL_LR: + final_x = ypos; + final_y = xpos; + break; + case GST_VIDEO_ORIENTATION_180: + final_x = src_w - xpos; + final_y = src_h - ypos; + break; + case GST_VIDEO_ORIENTATION_HORIZ: + final_x = src_w - xpos; + final_y = ypos; + break; + case GST_VIDEO_ORIENTATION_VERT: + final_x = xpos; + final_y = src_h - ypos; + break; + default: + final_x = xpos; + final_y = ypos; + break; + } + + gst_d3d12_window_on_mouse_event (window_, + event, button, final_x, final_y, modifier); +} + +GstFlowReturn +SwapChainProxy::setup_swapchain (GstD3D12Device * device, + DXGI_FORMAT format, const GstVideoInfo * in_info, + const GstVideoInfo * out_info, GstStructure * conv_config) +{ + std::shared_ptr sc; + HWND hwnd = nullptr; + bool is_new_swapchain = false; + { + std::lock_guard lk (lock_); + if (!hwnd_) { + GST_WARNING_OBJECT (window_, "Window was closed"); + return GST_D3D12_WINDOW_FLOW_CLOSED; + } + + if (!swapchain_) + swapchain_ = std::make_shared (device); + + sc = swapchain_; + hwnd = hwnd_; + } + + auto ret = sc->setup_swapchain (window_, device, + hwnd, format, in_info, out_info, conv_config, is_new_swapchain); + if (ret != GST_FLOW_OK) + return ret; + + if (is_new_swapchain) + PostMessageW (hwnd, WM_GST_D3D12_SWAPCHAIN_CREATED, 0, 0); + + return ret; +} + +std::shared_ptr +SwapChainProxy::get_swapchain () +{ + std::lock_guard lk (lock_); + if (!hwnd_) { + GST_DEBUG_OBJECT (window_, "Window handle is not configured"); + return nullptr; + } + + if (!swapchain_) { + GST_DEBUG_OBJECT (window_, "Swapchain is not configured"); + return nullptr; + } + + return swapchain_; +} + +void +SwapChainProxy::handle_swapchain_created () +{ + std::lock_guard lk (lock_); + if (!hwnd_ || !swapchain_) + return; + + swapchain_->disable_alt_enter (hwnd_); +} + +GstFlowReturn +SwapChainProxy::resize_buffer () +{ + auto sc = get_swapchain (); + if (!sc) + return GST_FLOW_OK; + + return sc->resize_buffer (window_); +} + +GstFlowReturn +SwapChainProxy::set_buffer (GstBuffer * buffer) +{ + auto sc = get_swapchain (); + if (!sc) + return GST_D3D12_WINDOW_FLOW_CLOSED; + + return sc->set_buffer (window_, buffer); +} + +GstFlowReturn +SwapChainProxy::present () +{ + auto sc = get_swapchain (); + if (!sc) + return GST_D3D12_WINDOW_FLOW_CLOSED; + + return sc->present (); +} + +void +HwndServer::register_window (GstD3D12Window * window) +{ + std::lock_guard lk (lock_); + GST_DEBUG_OBJECT (window, "Register"); + state_.insert ({window, std::make_shared ()}); +} + +void +HwndServer::unregister_window (GstD3D12Window * window) +{ + std::lock_guard lk (lock_); + GST_DEBUG_OBJECT (window, "Unregister"); + state_.erase (window); +} + +void +HwndServer::unlock_window (GstD3D12Window * window) +{ + std::lock_guard lk (lock_); + auto it = state_.find (window); + + if (it != state_.end ()) { + std::lock_guard lk (it->second->create_lock); + it->second->flushing = true; + it->second->create_cond.notify_all (); + } +} + +void +HwndServer::unlock_stop_window (GstD3D12Window * window) +{ + std::lock_guard lk (lock_); + auto it = state_.find (window); + + if (it != state_.end ()) { + std::lock_guard lk (it->second->create_lock); + it->second->flushing = false; + it->second->create_cond.notify_all (); + } +} + +#define EXTERNAL_PROC_PROP_NAME L"gst-d3d12-hwnd-external-proc" +#define D3D12_WINDOW_PROP_NAME L"gst-d3d12-hwnd-obj" +#define D3D12_WINDOW_ID_PROP_NAME L"gst-d3d12-hwnd-obj-id" + +static LRESULT CALLBACK +parent_wnd_proc (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + auto external_window_proc = + (WNDPROC) GetPropW (hwnd, EXTERNAL_PROC_PROP_NAME); + + if (!external_window_proc) { + GST_WARNING ("null external proc"); + return DefWindowProcW (hwnd, msg, wparam, lparam); + } + + auto server = HwndServer::get_instance (); + if (msg == WM_GST_D3D12_ATTACH_INTERNAL_WINDOW) { + GST_DEBUG ("Attach internal window"); + server->create_child_hwnd_finish ((GstD3D12Window *) lparam, hwnd, + (SIZE_T) wparam); + return 0; + } + + server->forward_parent_message (hwnd, msg, wparam, lparam); + + if (msg == WM_DESTROY) { + GST_INFO ("Parent HWND %p is being destroyed", hwnd); + server->on_parent_destroy (hwnd); + } + + return CallWindowProcW (external_window_proc, hwnd, msg, wparam, lparam); +} + +struct WindowCreateParams +{ + GstD3D12Window *window; + SIZE_T id; +}; + +static LRESULT CALLBACK +internal_wnd_proc (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + auto server = HwndServer::get_instance (); + + if (msg == WM_NCCREATE) { + LPCREATESTRUCTW lpcs = (LPCREATESTRUCTW) lparam; + auto params = (WindowCreateParams *) lpcs->lpCreateParams; + SetPropW (hwnd, D3D12_WINDOW_PROP_NAME, (HANDLE) params->window); + SetPropW (hwnd, D3D12_WINDOW_ID_PROP_NAME, (HANDLE) params->id); + + gst_object_ref (params->window); + + return DefWindowProcW (hwnd, msg, wparam, lparam); + } else if (msg == WM_GST_D3D12_DESTROY_INTERNAL_WINDOW) { + GST_INFO ("%p, Got custom destroy window event", hwnd); + DestroyWindow (hwnd); + return 0; + } + + auto window = (GstD3D12Window *) GetPropW (hwnd, D3D12_WINDOW_PROP_NAME); + auto id = (SIZE_T) GetPropW (hwnd, D3D12_WINDOW_ID_PROP_NAME); + + if (!window) + return DefWindowProcW (hwnd, msg, wparam, lparam); + + /* Custom event handler */ + if (msg == WM_GST_D3D12_PARENT_SIZE) { + auto proxy = server->get_proxy (window, id); + if (!proxy) + return 0; + + WORD width, height; + + width = LOWORD (lparam); + height = HIWORD (lparam); + + GST_LOG_OBJECT (window, "Parent resize %dx%d", width, height); + + GstVideoRectangle rect; + gst_d3d12_window_get_render_rect (window, &rect); + if (rect.w > 0 && rect.h > 0) + MoveWindow (hwnd, rect.x, rect.y, rect.w, rect.h, FALSE); + else + MoveWindow (hwnd, 0, 0, width, height, FALSE); + + return 0; + } else if (msg == WM_GST_D3D12_UPDATE_RENDER_RECT) { + auto proxy = server->get_proxy (window, id); + if (!proxy) + return 0; + + proxy->handle_update_render_rect (); + return 0; + } else if (msg == WM_GST_D3D12_FULLSCREEN) { + auto proxy = server->get_proxy (window, id); + if (!proxy) + return 0; + + proxy->handle_fullscreen_change (lparam ? true : false); + return 0; + } else if (msg == WM_GST_D3D12_SWAPCHAIN_CREATED) { + auto proxy = server->get_proxy (window, id); + if (!proxy) + return 0; + + proxy->handle_swapchain_created (); + return 0; + } + + switch (msg) { + case WM_KEYDOWN: + case WM_KEYUP: + { + auto proxy = server->get_proxy (window, id); + if (proxy) + proxy->handle_key_event (msg, wparam, lparam); + break; + } + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + case WM_MOUSEMOVE: + case WM_LBUTTONDBLCLK: + case WM_RBUTTONDBLCLK: + case WM_MBUTTONDBLCLK: + { + auto proxy = server->get_proxy (window, id); + if (proxy) + proxy->handle_mouse_event (msg, wparam, lparam); + break; + } + case WM_NCHITTEST: + { + auto proxy = server->get_proxy (window, id); + if (proxy && proxy->has_parent ()) { + /* To passthrough mouse event if external window is used. + * Only hit-test succeeded window can receive/handle some mouse events + * and we want such events to be handled by parent (application) window + */ + return (LRESULT) HTTRANSPARENT; + } + break; + } + case WM_SIZE: + { + auto proxy = server->get_proxy (window, id); + if (proxy) + proxy->resize_buffer (); + break; + } + case WM_SYSKEYDOWN: + { + auto proxy = server->get_proxy (window, id); + if (proxy) + proxy->handle_syskey_down (); + break; + } + case WM_DESTROY: + { + GST_DEBUG ("%p, WM_DESTROY", hwnd); + RemovePropW (hwnd, D3D12_WINDOW_PROP_NAME); + RemovePropW (hwnd, D3D12_WINDOW_ID_PROP_NAME); + + auto proxy = server->get_proxy (window, id); + if (proxy) { + proxy->on_destroy (); + server->on_proxy_destroy (window, id); + } + + gst_object_unref (window); + break; + } + default: + break; + } + + return DefWindowProcW (hwnd, msg, wparam, lparam); +} + +static void +register_window_class () +{ + GST_D3D12_CALL_ONCE_BEGIN { + auto inst = GetModuleHandle (nullptr); + WNDCLASSEXW wc = { }; + + wc.cbSize = sizeof (WNDCLASSEXW); + wc.lpfnWndProc = internal_wnd_proc; + wc.hInstance = inst; + wc.hIcon = LoadIcon (nullptr, IDI_WINLOGO); + wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; + wc.hCursor = LoadCursor (nullptr, IDC_ARROW); + wc.hbrBackground = (HBRUSH) GetStockObject (BLACK_BRUSH); + wc.lpszClassName = L"GstD3D12Hwnd"; + + RegisterClassExW (&wc); + } GST_D3D12_CALL_ONCE_END; +} + +GstFlowReturn +HwndServer::create_child_hwnd (GstD3D12Window * window, HWND parent_hwnd, + SIZE_T & proxy_id) +{ + proxy_id = 0; + if (!IsWindow (parent_hwnd)) { + GST_WARNING_OBJECT (window, "%p is not window handle", parent_hwnd); + return GST_D3D12_WINDOW_FLOW_CLOSED; + } + + std::shared_ptr state; + { + std::lock_guard lk (lock_); + auto external_proc = (WNDPROC) + GetWindowLongPtrW (parent_hwnd, GWLP_WNDPROC); + if (external_proc != (WNDPROC) parent_wnd_proc) { + if (!SetPropW (parent_hwnd, EXTERNAL_PROC_PROP_NAME, + (HANDLE) external_proc)) { + GST_WARNING_OBJECT (window, + "Couldn't store original procedure function"); + return GST_D3D12_WINDOW_FLOW_CLOSED; + } + + SetWindowLongPtrW (parent_hwnd, GWLP_WNDPROC, + (LONG_PTR) parent_wnd_proc); + + GST_DEBUG_OBJECT (window, + "subclass proc installed for hwnd %p", parent_hwnd); + } + + auto it = state_.find (window); + state = it->second; + } + + std::unique_lock lk (state->create_lock); + state->id++; + if (state->id == 0) + state->id++; + + SIZE_T id = state->id; + state->proxy = std::make_shared (window, id); + + if (state->flushing) { + GST_INFO_OBJECT (window, "Window is flushing"); + state->proxy = nullptr; + return GST_FLOW_FLUSHING; + } + + state->create_state = CreateState::Waiting; + if (!PostMessageW (parent_hwnd, WM_GST_D3D12_ATTACH_INTERNAL_WINDOW, + (WPARAM) id, (LPARAM) window)) { + GST_WARNING_OBJECT (window, "Couldn't post message"); + state->create_state = CreateState::None; + state->proxy = nullptr; + return GST_D3D12_WINDOW_FLOW_CLOSED; + } + + while (!state->flushing && state->create_state == CreateState::Waiting) + state->create_cond.wait(lk); + + GstFlowReturn ret = GST_D3D12_WINDOW_FLOW_CLOSED; + if (state->create_state == CreateState::Opened) { + ret = GST_FLOW_OK; + proxy_id = id; + } else { + state->proxy = nullptr; + if (state->flushing) + ret = GST_FLOW_FLUSHING; + } + + state->create_state = CreateState::None; + + return ret; +} + +void +HwndServer::create_child_hwnd_finish (GstD3D12Window * window, + HWND parent_hwnd, SIZE_T proxy_id) +{ + std::shared_ptr state; + std::shared_ptr proxy; + + { + std::lock_guard lk (lock_); + auto it = state_.find (window); + if (it == state_.end ()) { + GST_WARNING ("Window is not registered"); + return; + } + + state = it->second; + proxy = state->proxy; + } + + if (!proxy) { + GST_INFO ("Proxy was released"); + return; + } + + if (proxy->get_id () != proxy_id) { + GST_INFO ("Different proxy id"); + return; + } + + register_window_class (); + + WindowCreateParams params; + params.window = window; + params.id = proxy_id; + + auto child = CreateWindowExW (0, + L"GstD3D12Hwnd", L"GstD3D12Hwnd", + WS_GST_D3D12, 0, 0, 0, 0, (HWND) nullptr, (HMENU) nullptr, + GetModuleHandle (nullptr), ¶ms); + SetWindowLongPtrW (child, GWL_STYLE, WS_CHILD | WS_MAXIMIZE); + SetParent (child, parent_hwnd); + + RECT rect; + GetClientRect (parent_hwnd, &rect); + + GstVideoRectangle user_rect = { }; + gst_d3d12_window_get_render_rect (window, &user_rect); + + if (user_rect.w > 0 && user_rect.h > 0) { + rect.left = user_rect.x; + rect.top = user_rect.y; + rect.right = user_rect.x + user_rect.w; + rect.bottom = user_rect.y + user_rect.h; + } + + SetWindowPos (child, HWND_TOP, rect.left, rect.top, + rect.right - rect.left, rect.bottom - rect.top, + SWP_ASYNCWINDOWPOS | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | + SWP_FRAMECHANGED | SWP_NOACTIVATE); + MoveWindow (child, rect.left, rect.top, + rect.right - rect.left, rect.bottom - rect.top, FALSE); + ShowWindow (child, SW_SHOW); + + { + std::lock_guard lk (lock_); + auto it = parent_hwnd_map_.find (parent_hwnd); + if (it == parent_hwnd_map_.end ()) { + GST_DEBUG ("Register parent hwnd %p with child %p", parent_hwnd, child); + std::vector hwnd_list; + hwnd_list.push_back (child); + parent_hwnd_map_.insert ({parent_hwnd, hwnd_list}); + } else { + it->second.push_back (child); + GST_DEBUG ("New child hwnd %p is added for parent %p, num child %" G_GSIZE_FORMAT, + parent_hwnd, child, it->second.size ()); + } + } + + { + std::lock_guard lk (state->create_lock); + proxy->set_window_handles (parent_hwnd, child); + state->create_state = CreateState::Opened; + state->create_cond.notify_all (); + } +} + +SIZE_T +HwndServer::create_internal_window (GstD3D12Window * window) +{ + std::wstring title; + GstVideoRectangle rect; + GstVideoOrientationMethod orientation; + int x = CW_USEDEFAULT; + int y = CW_USEDEFAULT; + int w, h; + + gst_d3d12_window_get_create_params (window, title, &rect, w, h, orientation); + + std::shared_ptr state; + { + std::lock_guard lk (lock_); + auto it = state_.find (window); + state = it->second; + } + + { + std::unique_lock lk (state->create_lock); + state->id++; + if (state->id == 0) + state->id++; + } + + SIZE_T id = state->id; + auto proxy = std::make_shared (window, id); + + DWORD style = WS_GST_D3D12 | WS_VISIBLE; + if (rect.w > 0 && rect.h > 0) { + x = rect.x; + y = rect.y; + w = rect.w; + h = rect.h; + } else { + RECT rect = { }; + switch (orientation) { + case GST_VIDEO_ORIENTATION_90R: + case GST_VIDEO_ORIENTATION_90L: + case GST_VIDEO_ORIENTATION_UL_LR: + case GST_VIDEO_ORIENTATION_UR_LL: + rect.right = h; + rect.bottom = w; + break; + default: + rect.right = w; + rect.bottom = h; + break; + } + + AdjustWindowRect (&rect, WS_GST_D3D12, FALSE); + + w = rect.right - rect.left; + h = rect.bottom - rect.top; + } + + register_window_class (); + + WindowCreateParams params; + params.window = window; + params.id = id; + auto hwnd = CreateWindowExW (0, L"GstD3D12Hwnd", title.c_str (), + style, x, y, w, h, (HWND) nullptr, (HMENU) nullptr, + GetModuleHandle (nullptr), ¶ms); + proxy->set_window_handles (nullptr, hwnd); + + { + std::lock_guard slk (state->create_lock); + state->proxy = std::move (proxy); + } + + return id; +} + +void +HwndServer::release_proxy (GstD3D12Window * window, SIZE_T proxy_id) +{ + std::shared_ptr proxy; + + { + std::lock_guard lk (lock_); + auto it = state_.find (window); + if (it == state_.end ()) + return; + + auto state = it->second; + { + std::lock_guard slk (state->create_lock); + if (state->proxy && state->proxy->get_id () == proxy_id) { + proxy = state->proxy; + state->proxy = nullptr; + } + } + } +} + +static bool +translate_message (UINT & msg, WPARAM & wparam, LPARAM & lparam) +{ + switch (msg) { + case WM_SIZE: + msg = WM_GST_D3D12_PARENT_SIZE; + return true; + case WM_KEYDOWN: + case WM_KEYUP: + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + case WM_MOUSEMOVE: + case WM_LBUTTONDBLCLK: + case WM_RBUTTONDBLCLK: + case WM_MBUTTONDBLCLK: + return true; + default: + break; + } + + return false; +} + +void +HwndServer::forward_parent_message (HWND parent, UINT msg, WPARAM wparam, LPARAM lparam) +{ + if (translate_message (msg, wparam, lparam)) { + std::vector child_hwnds; + + { + std::lock_guard lk (lock_); + auto it = parent_hwnd_map_.find (parent); + if (it == parent_hwnd_map_.end ()) + return; + + child_hwnds = it->second; + } + + for (auto child : child_hwnds) + SendMessageW (child, msg, wparam, lparam); + } +} + +void +HwndServer::on_parent_destroy (HWND parent_hwnd) +{ + std::lock_guard lk (lock_); + parent_hwnd_map_.erase (parent_hwnd); +} + +void +HwndServer::on_proxy_destroy (GstD3D12Window * window, + SIZE_T proxy_id) +{ + std::lock_guard lk (lock_); + auto it = state_.find (window); + if (it != state_.end ()) { + auto state = it->second; + std::lock_guard slk (state->create_lock); + if (state->proxy && state->proxy->get_id () == proxy_id) { + state->proxy = nullptr; + } + } +} + +std::shared_ptr +HwndServer::get_proxy (GstD3D12Window * window, SIZE_T proxy_id) +{ + std::shared_ptr ret; + + { + std::lock_guard lk (lock_); + auto it = state_.find (window); + if (it == state_.end ()) + return nullptr; + + { + std::lock_guard slk (it->second->create_lock); + ret = it->second->proxy; + if (ret && ret->get_id () != proxy_id) + ret = nullptr; + } + } + + return ret; +} +/* *INDENT-ON* */ diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window-win32.h b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window-win32.h new file mode 100644 index 0000000000..20fc7e0272 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window-win32.h @@ -0,0 +1,151 @@ +/* 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. + */ + +#pragma once + +#include "gstd3d12window.h" +#include "gstd3d12window-swapchain.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct FullscreenState +{ + std::atomic fullscreen_on_alt_enter = { false }; + std::atomic requested_fullscreen = { false }; + std::atomic applied_fullscreen = { false }; + LONG restore_style = 0; + WINDOWPLACEMENT restore_placement = { }; +}; + +class SwapChainProxy +{ +public: + SwapChainProxy (GstD3D12Window * window, SIZE_T id); + ~SwapChainProxy (); + + void set_window_handles (HWND parent_hwnd, HWND child_hwnd); + HWND get_window_handle (); + SIZE_T get_id (); + bool has_parent (); + void on_destroy (); + void set_fullscreen_on_alt_enter (bool enable); + void toggle_fullscreen (bool enable); + void update_render_rect (); + void handle_fullscreen_change (bool is_fullscreen); + void handle_syskey_down (); + void handle_update_render_rect (); + void handle_key_event (UINT msg, WPARAM wparam, LPARAM lparam); + void handle_mouse_event (UINT msg, WPARAM wparam, LPARAM lparam); + void handle_swapchain_created (); + GstFlowReturn setup_swapchain (GstD3D12Device * device, DXGI_FORMAT format, + const GstVideoInfo * in_info, const GstVideoInfo * out_info, + GstStructure * conv_config); + GstFlowReturn resize_buffer (); + GstFlowReturn set_buffer (GstBuffer * buffer); + GstFlowReturn present (); + +private: + std::shared_ptr get_swapchain (); + +private: + GstD3D12Window *window_ = nullptr; + SIZE_T id_ = 0; + HWND hwnd_ = nullptr; + HWND parent_hwnd_ = nullptr; + GThread *window_thread_ = nullptr; + FullscreenState fstate_; + std::shared_ptr swapchain_; + + std::recursive_mutex lock_; +}; + +class HwndServer +{ +public: + HwndServer(const HwndServer &) = delete; + HwndServer& operator=(const HwndServer &) = delete; + static HwndServer * get_instance() + { + static HwndServer *inst = nullptr; + GST_D3D12_CALL_ONCE_BEGIN { + inst = new HwndServer (); + } GST_D3D12_CALL_ONCE_END; + + return inst; + } + + void register_window (GstD3D12Window * window); + void unregister_window (GstD3D12Window * window); + + void unlock_window (GstD3D12Window * window); + void unlock_stop_window (GstD3D12Window * window); + + GstFlowReturn create_child_hwnd (GstD3D12Window * window, + HWND parent_hwnd, SIZE_T & proxy_id); + + void create_child_hwnd_finish (GstD3D12Window * window, + HWND parent_hwnd, SIZE_T proxy_id); + + SIZE_T create_internal_window (GstD3D12Window * window); + + void release_proxy (GstD3D12Window * window, SIZE_T proxy_id); + + void forward_parent_message (HWND parent, + UINT msg, WPARAM wparam, LPARAM lparam); + + void on_parent_destroy (HWND parent_hwnd); + void on_proxy_destroy (GstD3D12Window * window, + SIZE_T proxy_id); + + std::shared_ptr get_proxy (GstD3D12Window * window, + SIZE_T proxy_id); + +private: + enum CreateState + { + None, + Waiting, + Opened, + Closed, + }; + + struct State + { + std::mutex create_lock; + std::condition_variable create_cond; + std::atomic id = { 0 }; + bool flushing = false; + CreateState create_state = CreateState::None; + std::shared_ptr proxy; + }; + + HwndServer () {} + ~HwndServer () {} + std::recursive_mutex lock_; + std::unordered_map> state_; + std::unordered_map> parent_hwnd_map_; +}; + diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window.cpp b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window.cpp index 731e06bb08..64652440df 100644 --- a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window.cpp +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window.cpp @@ -22,7 +22,7 @@ #endif #include "gstd3d12window.h" -#include "gstd3d12overlaycompositor.h" +#include "gstd3d12window-win32.h" #include #include #include @@ -33,35 +33,14 @@ #include #include #include -#include /* *INDENT-OFF* */ using namespace Microsoft::WRL; -static std::mutex global_hwnd_lock; /* *INDENT-ON* */ -GST_DEBUG_CATEGORY_STATIC (gst_d3d12_window_debug); +GST_DEBUG_CATEGORY (gst_d3d12_window_debug); #define GST_CAT_DEFAULT gst_d3d12_window_debug -#define WS_GST_D3D12 (WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW) -#define EXTERNAL_PROC_PROP_NAME L"gst-d3d12-hwnd-external-proc" -#define D3D12_WINDOW_PROP_NAME L"gst-d3d12-hwnd-obj" -#define WM_GST_D3D12_FULLSCREEN (WM_USER + 1) -#define WM_GST_D3D12_CONSTRUCT_INTERNAL_WINDOW (WM_USER + 2) -#define WM_GST_D3D12_DESTROY_INTERNAL_WINDOW (WM_USER + 3) -#define WM_GST_D3D12_UPDATE_RENDER_RECT (WM_USER + 4) - -#define BACK_BUFFER_COUNT 3 - -/* windowsx.h */ -#ifndef GET_X_LPARAM -#define GET_X_LPARAM(lp) ((int)(short)LOWORD(lp)) -#endif - -#ifndef GET_Y_LPARAM -#define GET_Y_LPARAM(lp) ((int)(short)HIWORD(lp)) -#endif - enum { SIGNAL_KEY_EVENT, @@ -99,232 +78,6 @@ gst_d3d12_window_overlay_mode_get_type (void) } /* *INDENT-OFF* */ -struct SwapBuffer -{ - 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; -}; - -struct DeviceContext -{ - DeviceContext () = delete; - DeviceContext (GstD3D12Device * dev) - { - 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); - } - - 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; - } - - 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 () - { - ReleaseSizeDependentResources (); - - CloseHandle (event_handle); - - gst_clear_object (&ca_pool); - gst_clear_buffer (&cached_buf); - gst_clear_object (&conv); - gst_clear_object (&comp); - gst_clear_object (&device); - } - - gboolean BeforeRendering () - { - UINT64 fence_val_to_wait = 0; - gboolean ret = TRUE; - - while (prev_fence_val.size () > BACK_BUFFER_COUNT) { - fence_val_to_wait = prev_fence_val.front (); - prev_fence_val.pop (); - } - - if (fence_val_to_wait) { - auto completed = gst_d3d12_device_get_completed_value (device, - D3D12_COMMAND_LIST_TYPE_DIRECT); - if (completed < fence_val_to_wait) { - ret = gst_d3d12_device_fence_wait (device, - D3D12_COMMAND_LIST_TYPE_DIRECT, fence_val_to_wait, event_handle); - } - } - - return ret; - } - - void AfterRendering () - { - prev_fence_val.push (fence_val); - } - - ComPtr cl; - ComPtr swapchain; - GstBuffer *msaa_buf = nullptr; - std::vector> swap_buffers; - D3D12_RESOURCE_DESC buffer_desc; - GstD3D12Converter *conv = nullptr; - GstD3D12OverlayCompositor *comp = nullptr; - 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 = nullptr; -}; struct GstD3D12WindowPrivate { @@ -362,11 +115,8 @@ struct GstD3D12WindowPrivate gfloat scale_y = 1.0f; /* fullscreen related variables */ - gboolean fullscreen_on_alt_enter = TRUE; - gboolean requested_fullscreen = FALSE; - gboolean applied_fullscreen = FALSE; - LONG restore_style; - WINDOWPLACEMENT restore_placement; + std::atomic fullscreen_on_alt_enter = { FALSE }; + std::atomic requested_fullscreen = { FALSE }; GstD3D12FenceDataPool *fence_data_pool; @@ -377,38 +127,29 @@ struct GstD3D12WindowPrivate RECT dirty_rect = { }; GstVideoInfo input_info; GstVideoInfo display_info; + guint display_width = 8; + guint display_height = 8; - D3D12_BOX crop_rect = { }; - D3D12_BOX prev_crop_rect = { }; - - gboolean force_aspect_ratio = TRUE; std::atomic enable_navigation = { TRUE }; - gboolean first_present = TRUE; - gboolean backbuf_rendered = FALSE; + gboolean force_aspect_ratio = TRUE; + gboolean output_updated = FALSE; - std::unique_ptr ctx; + std::weak_ptr proxy; + SIZE_T proxy_id = 0; std::wstring title; - gboolean update_title = FALSE; - GstD3D12MSAAMode msaa = GST_D3D12_MSAA_DISABLED; - GstD3D12WindowOverlayMode overlay_mode = GST_D3D12_WINDOW_OVERLAY_NONE; + std::atomic msaa = { GST_D3D12_MSAA_DISABLED }; + std::atomic overlay_mode = + { GST_D3D12_WINDOW_OVERLAY_NONE }; /* Win32 window handles */ - std::mutex hwnd_lock; - std::condition_variable hwnd_cond; - HWND hwnd = nullptr; - HWND external_hwnd = nullptr; - std::atomic state = { GST_D3D12_WINDOW_STATE_INIT }; GThread *main_loop_thread = nullptr; - - GThread *internal_hwnd_thread = nullptr; - GMainLoop *loop = nullptr; GMainContext *main_context = nullptr; - gboolean flushing = FALSE; + std::mutex loop_lock; + std::condition_variable loop_cond; }; -/* *INDENT-ON* */ struct _GstD3D12Window { @@ -418,8 +159,7 @@ struct _GstD3D12Window GstD3D12WindowPrivate *priv; }; - -static GstFlowReturn gst_d3d12_window_on_resize (GstD3D12Window * self); +/* *INDENT-ON* */ #define gst_d3d12_window_parent_class parent_class G_DEFINE_TYPE (GstD3D12Window, gst_d3d12_window, GST_TYPE_OBJECT); @@ -477,641 +217,20 @@ gst_d3d12_window_finalize (GObject * object) G_OBJECT_CLASS (parent_class)->finalize (object); } -static void -gst_d3d12_window_on_key_event (GstD3D12Window * self, - UINT msg, WPARAM wparam, LPARAM lparam) +void +gst_d3d12_window_on_key_event (GstD3D12Window * window, const gchar * event, + const gchar * name) { - gunichar2 keyname[128]; - const gchar *event; - - if (!GetKeyNameTextW (lparam, (LPWSTR) keyname, 128)) - return; - - gchar *name = g_utf16_to_utf8 (keyname, 128, nullptr, nullptr, nullptr); - if (!name) - return; - - if (msg == WM_KEYDOWN) - event = "key-press"; - else - event = "key-release"; - - g_signal_emit (self, d3d12_window_signals[SIGNAL_KEY_EVENT], 0, event, name); - g_free (name); + g_signal_emit (window, d3d12_window_signals[SIGNAL_KEY_EVENT], 0, event, + name); } -static void -gst_d3d12_window_on_mouse_event (GstD3D12Window * self, UINT msg, WPARAM wparam, - LPARAM lparam) +void +gst_d3d12_window_on_mouse_event (GstD3D12Window * window, const gchar * event, + gint button, double xpos, double ypos, guint modifier) { - auto priv = self->priv; - gint button = 0; - const gchar *event = nullptr; - guint modifier = 0; - - switch (msg) { - case WM_MOUSEMOVE: - button = 0; - event = "mouse-move"; - break; - case WM_LBUTTONDOWN: - button = 1; - event = "mouse-button-press"; - break; - case WM_LBUTTONUP: - button = 1; - event = "mouse-button-release"; - break; - case WM_LBUTTONDBLCLK: - button = 1; - event = "mouse-double-click"; - break; - case WM_RBUTTONDOWN: - button = 2; - event = "mouse-button-press"; - break; - case WM_RBUTTONUP: - button = 2; - event = "mouse-button-release"; - break; - case WM_RBUTTONDBLCLK: - button = 2; - event = "mouse-double-click"; - break; - case WM_MBUTTONDOWN: - button = 3; - event = "mouse-button-press"; - break; - case WM_MBUTTONUP: - button = 3; - event = "mouse-button-release"; - break; - case WM_MBUTTONDBLCLK: - button = 3; - event = "mouse-double-click"; - break; - default: - return; - } - - if ((wparam & MK_CONTROL) != 0) - modifier |= GST_NAVIGATION_MODIFIER_CONTROL_MASK; - if ((wparam & MK_LBUTTON) != 0) - modifier |= GST_NAVIGATION_MODIFIER_BUTTON1_MASK; - if ((wparam & MK_RBUTTON) != 0) - modifier |= GST_NAVIGATION_MODIFIER_BUTTON2_MASK; - if ((wparam & MK_MBUTTON) != 0) - modifier |= GST_NAVIGATION_MODIFIER_BUTTON3_MASK; - if ((wparam & MK_SHIFT) != 0) - modifier |= GST_NAVIGATION_MODIFIER_SHIFT_MASK; - - GstVideoRectangle output_rect = { }; - GstVideoOrientationMethod orientation; - gint in_w, in_h; - { - std::lock_guard < std::recursive_mutex > lk (priv->lock); - orientation = priv->orientation; - output_rect = priv->output_rect; - in_w = priv->input_info.width; - in_h = priv->input_info.height; - } - auto xpos = GET_X_LPARAM (lparam); - auto ypos = GET_Y_LPARAM (lparam); - - if (in_w <= 0 || in_h <= 0 || xpos < output_rect.x || - xpos >= output_rect.x + output_rect.w || ypos < output_rect.y || - ypos >= output_rect.y + output_rect.h) { - return; - } - - gint src_w, src_h; - switch (orientation) { - case GST_VIDEO_ORIENTATION_90R: - case GST_VIDEO_ORIENTATION_90L: - case GST_VIDEO_ORIENTATION_UL_LR: - case GST_VIDEO_ORIENTATION_UR_LL: - src_w = in_h; - src_h = in_w; - break; - default: - src_w = in_w; - src_h = in_h; - break; - } - - xpos = ((xpos - output_rect.x) / (double) output_rect.w) * src_w; - ypos = ((ypos - output_rect.y) / (double) output_rect.h) * src_h; - - xpos = CLAMP (xpos, 0, (LONG) (src_w - 1)); - ypos = CLAMP (ypos, 0, (LONG) (src_h - 1)); - - double final_x = 0; - double final_y = 0; - - switch (orientation) { - case GST_VIDEO_ORIENTATION_90R: - final_x = ypos; - final_y = src_w - xpos; - break; - case GST_VIDEO_ORIENTATION_90L: - final_x = src_h - ypos; - final_y = xpos; - break; - case GST_VIDEO_ORIENTATION_UR_LL: - final_x = src_h - ypos; - final_y = src_w - xpos; - break; - case GST_VIDEO_ORIENTATION_UL_LR: - final_x = ypos; - final_y = xpos; - break; - case GST_VIDEO_ORIENTATION_180: - final_x = src_w - xpos; - final_y = src_h - ypos; - break; - case GST_VIDEO_ORIENTATION_HORIZ: - final_x = src_w - xpos; - final_y = ypos; - break; - case GST_VIDEO_ORIENTATION_VERT: - final_x = xpos; - final_y = src_h - ypos; - break; - default: - final_x = xpos; - final_y = ypos; - break; - } - - g_signal_emit (self, d3d12_window_signals[SIGNAL_MOUSE_EVENT], 0, - event, button, final_x, final_y, modifier); -} - -static void -gst_d3d12_window_toggle_fullscreen_mode (GstD3D12Window * self, - gboolean emit_signal) -{ - auto priv = self->priv; - HWND hwnd = nullptr; - gboolean is_fullscreen; - ComPtr < IDXGISwapChain > swapchain; - - { - std::lock_guard < std::mutex > hlk (priv->hwnd_lock); - hwnd = priv->external_hwnd ? priv->external_hwnd : priv->hwnd; - - if (!hwnd) - return; - - if (priv->requested_fullscreen == priv->applied_fullscreen) - return; - - { - std::lock_guard < std::recursive_mutex > lk (priv->lock); - if (priv->ctx) - swapchain = priv->ctx->swapchain; - } - - if (!swapchain) - return; - - GST_DEBUG_OBJECT (self, "Change mode to %s", - priv->requested_fullscreen ? "fullscreen" : "windowed"); - - priv->applied_fullscreen = priv->requested_fullscreen; - is_fullscreen = priv->applied_fullscreen; - } - - if (!is_fullscreen) { - SetWindowLongW (hwnd, GWL_STYLE, priv->restore_style); - SetWindowPlacement (hwnd, &priv->restore_placement); - } else { - ComPtr < IDXGIOutput > output; - DXGI_OUTPUT_DESC output_desc; - - /* remember current placement to restore window later */ - GetWindowPlacement (hwnd, &priv->restore_placement); - - /* show window before change style */ - ShowWindow (hwnd, SW_SHOW); - - priv->restore_style = GetWindowLong (hwnd, GWL_STYLE); - - /* Make the window borderless so that the client area can fill the screen */ - SetWindowLongA (hwnd, GWL_STYLE, - priv->restore_style & - ~(WS_CAPTION | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_SYSMENU | - WS_THICKFRAME | WS_MAXIMIZE)); - - swapchain->GetContainingOutput (&output); - output->GetDesc (&output_desc); - - SetWindowPos (hwnd, HWND_TOP, - output_desc.DesktopCoordinates.left, - output_desc.DesktopCoordinates.top, - output_desc.DesktopCoordinates.right, - output_desc.DesktopCoordinates.bottom, - SWP_FRAMECHANGED | SWP_NOACTIVATE); - - ShowWindow (hwnd, SW_MAXIMIZE); - } - - GST_DEBUG_OBJECT (self, "Fullscreen mode change done"); - - if (emit_signal) { - g_signal_emit (self, d3d12_window_signals[SIGNAL_FULLSCREEN], - 0, is_fullscreen); - } -} - -static GstD3D12Window * -gst_d3d12_window_from_hwnd (HWND hwnd) -{ - std::lock_guard < std::mutex > lk (global_hwnd_lock); - auto self = GetPropW (hwnd, D3D12_WINDOW_PROP_NAME); - if (self) - gst_object_ref (self); - - return (GstD3D12Window *) self; -} - -static LRESULT CALLBACK -gst_d3d12_window_proc (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -{ - switch (msg) { - case WM_CREATE: - { - LPCREATESTRUCTW create_param = (LPCREATESTRUCTW) lparam; - SetPropW (hwnd, D3D12_WINDOW_PROP_NAME, create_param->lpCreateParams); - break; - } - case WM_GST_D3D12_DESTROY_INTERNAL_WINDOW: - { - GST_INFO ("Handle destroy window message"); - DestroyWindow (hwnd); - return 0; - } - case WM_GST_D3D12_UPDATE_RENDER_RECT: - { - auto self = gst_d3d12_window_from_hwnd (hwnd); - if (self) { - GstVideoRectangle render_rect = { }; - { - auto priv = self->priv; - std::lock_guard < std::mutex > lk (priv->hwnd_lock); - render_rect = priv->render_rect; - } - - if (render_rect.w > 0 && render_rect.h > 0) { - MoveWindow (hwnd, - render_rect.x, render_rect.y, render_rect.w, render_rect.h, TRUE); - } - - gst_object_unref (self); - } - return 0; - } - case WM_GST_D3D12_FULLSCREEN: - { - auto self = gst_d3d12_window_from_hwnd (hwnd); - if (self) { - gst_d3d12_window_toggle_fullscreen_mode (self, FALSE); - gst_object_unref (self); - } - - return 0; - } - case WM_SYSKEYDOWN: - { - WORD state = GetKeyState (VK_RETURN); - BYTE high = HIBYTE (state); - - if (high & 0x1) { - auto self = gst_d3d12_window_from_hwnd (hwnd); - if (self) { - auto priv = self->priv; - bool do_toggle = false; - { - std::lock_guard < std::mutex > lk (priv->hwnd_lock); - if (priv->fullscreen_on_alt_enter) { - priv->requested_fullscreen = !priv->applied_fullscreen; - do_toggle = true; - } - } - - if (do_toggle) - gst_d3d12_window_toggle_fullscreen_mode (self, TRUE); - - gst_object_unref (self); - } - } - break; - } - case WM_SIZE: - { - auto self = gst_d3d12_window_from_hwnd (hwnd); - if (self) { - gst_d3d12_window_on_resize (self); - gst_object_unref (self); - } - break; - } - case WM_CLOSE: - { - auto self = gst_d3d12_window_from_hwnd (hwnd); - if (self) { - auto priv = self->priv; - GST_DEBUG_OBJECT (self, "Internal window is being closed"); - { - std::lock_guard < std::mutex > lk (priv->hwnd_lock); - RemovePropW (priv->hwnd, D3D12_WINDOW_PROP_NAME); - priv->hwnd = nullptr; - priv->state = GST_D3D12_WINDOW_STATE_CLOSED; - - } - gst_object_unref (self); - } - break; - } - case WM_NCHITTEST: - { - auto self = gst_d3d12_window_from_hwnd (hwnd); - if (self && self->priv->external_hwnd) { - gst_object_unref (self); - - /* To passthrough mouse event if external window is used. - * Only hit-test succeeded window can receive/handle some mouse events - * and we want such events to be handled by parent (application) window - */ - return (LRESULT) HTTRANSPARENT; - } - - gst_clear_object (&self); - break; - } - case WM_KEYDOWN: - case WM_KEYUP: - { - auto self = gst_d3d12_window_from_hwnd (hwnd); - if (self) { - auto priv = self->priv; - if (priv->enable_navigation) - gst_d3d12_window_on_key_event (self, msg, wparam, lparam); - gst_object_unref (self); - } - break; - } - case WM_LBUTTONDOWN: - case WM_LBUTTONUP: - case WM_RBUTTONDOWN: - case WM_RBUTTONUP: - case WM_MBUTTONDOWN: - case WM_MBUTTONUP: - case WM_MOUSEMOVE: - case WM_LBUTTONDBLCLK: - case WM_RBUTTONDBLCLK: - case WM_MBUTTONDBLCLK: - { - auto self = gst_d3d12_window_from_hwnd (hwnd); - if (self) { - auto priv = self->priv; - if (priv->enable_navigation) - gst_d3d12_window_on_mouse_event (self, msg, wparam, lparam); - gst_object_unref (self); - } - break; - } - default: - break; - } - - return DefWindowProcW (hwnd, msg, wparam, lparam); -} - -static void -gst_d3d12_window_create_hwnd (GstD3D12Window * self) -{ - auto priv = self->priv; - auto inst = GetModuleHandle (nullptr); - - { - static std::mutex class_lock; - std::lock_guard < std::mutex > lk (class_lock); - WNDCLASSEXW wc = { }; - - if (!GetClassInfoExW (inst, L"GstD3D12Hwnd", &wc)) { - wc.cbSize = sizeof (WNDCLASSEXW); - wc.lpfnWndProc = gst_d3d12_window_proc; - wc.hInstance = inst; - wc.hIcon = LoadIcon (nullptr, IDI_WINLOGO); - wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; - wc.hCursor = LoadCursor (nullptr, IDC_ARROW); - wc.hbrBackground = (HBRUSH) GetStockObject (BLACK_BRUSH); - wc.lpszClassName = L"GstD3D12Hwnd"; - - RegisterClassExW (&wc); - } - } - - int x = CW_USEDEFAULT; - int y = CW_USEDEFAULT; - int w = 0; - int h = 0; - DWORD style = WS_GST_D3D12; - - std::wstring title = L"Direct3D12 Renderer"; - if (!priv->title.empty ()) - title = priv->title; - - if (!priv->external_hwnd) { - if (priv->render_rect.w > 0 && priv->render_rect.h > 0) { - x = priv->render_rect.x; - y = priv->render_rect.y; - w = priv->render_rect.w; - h = priv->render_rect.h; - } else { - std::lock_guard < std::recursive_mutex > lk (priv->lock); - RECT rect = { }; - switch (priv->orientation) { - case GST_VIDEO_ORIENTATION_90R: - case GST_VIDEO_ORIENTATION_90L: - case GST_VIDEO_ORIENTATION_UL_LR: - case GST_VIDEO_ORIENTATION_UR_LL: - rect.right = GST_VIDEO_INFO_HEIGHT (&priv->display_info); - rect.bottom = GST_VIDEO_INFO_WIDTH (&priv->display_info); - break; - default: - rect.right = GST_VIDEO_INFO_WIDTH (&priv->display_info); - rect.bottom = GST_VIDEO_INFO_HEIGHT (&priv->display_info); - break; - } - - AdjustWindowRect (&rect, WS_GST_D3D12, FALSE); - - w = rect.right - rect.left; - h = rect.bottom - rect.top; - } - - style |= WS_VISIBLE; - } - - priv->hwnd = CreateWindowExW (0, L"GstD3D12Hwnd", title.c_str (), - style, x, y, w, h, (HWND) nullptr, (HMENU) nullptr, inst, self); - priv->applied_fullscreen = FALSE; - priv->internal_hwnd_thread = g_thread_self (); -} - -static void -gst_d3d12_window_release_external_hwnd (GstD3D12Window * self) -{ - auto priv = self->priv; - - if (!priv->external_hwnd) - return; - - auto hwnd = priv->external_hwnd; - priv->external_hwnd = nullptr; - - auto external_proc = (WNDPROC) GetPropW (hwnd, EXTERNAL_PROC_PROP_NAME); - if (!external_proc) { - GST_WARNING_OBJECT (self, "Failed to get original window procedure"); - return; - } - - GST_DEBUG_OBJECT (self, "release external window %" G_GUINTPTR_FORMAT - ", original window procedure %p", (guintptr) hwnd, external_proc); - - RemovePropW (hwnd, EXTERNAL_PROC_PROP_NAME); - RemovePropW (hwnd, D3D12_WINDOW_PROP_NAME); - - if (!SetWindowLongPtrW (hwnd, GWLP_WNDPROC, (LONG_PTR) external_proc)) - GST_WARNING_OBJECT (self, "Couldn't restore original window procedure"); -} - -static LRESULT FAR PASCAL -sub_class_proc (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -{ - auto external_window_proc = - (WNDPROC) GetPropW (hwnd, EXTERNAL_PROC_PROP_NAME); - auto self = gst_d3d12_window_from_hwnd (hwnd); - - if (!self) { - GST_DEBUG ("No object attached to the window, chain up to default"); - return CallWindowProcW (external_window_proc, hwnd, msg, wparam, lparam); - } - - auto priv = self->priv; - switch (msg) { - case WM_GST_D3D12_CONSTRUCT_INTERNAL_WINDOW: - { - GST_DEBUG_OBJECT (self, "Create internal window"); - std::lock_guard < std::mutex > lk (priv->hwnd_lock); - if (priv->hwnd) { - GST_WARNING_OBJECT (self, - "Window already created, probably we have received 2 creation messages"); - gst_object_unref (self); - return 0; - } - - gst_d3d12_window_create_hwnd (self); - SetWindowLongPtrW (priv->hwnd, GWL_STYLE, WS_CHILD | WS_MAXIMIZE); - SetParent (priv->hwnd, priv->external_hwnd); - - RECT rect; - GetClientRect (priv->external_hwnd, &rect); - - if (priv->render_rect.w > 0 && priv->render_rect.h > 0) { - rect.left = priv->render_rect.x; - rect.top = priv->render_rect.y; - rect.right = priv->render_rect.x + priv->render_rect.w; - rect.bottom = priv->render_rect.y + priv->render_rect.h; - } - - SetWindowPos (priv->hwnd, HWND_TOP, rect.left, rect.top, - rect.right - rect.left, rect.bottom - rect.top, - SWP_ASYNCWINDOWPOS | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | - SWP_FRAMECHANGED | SWP_NOACTIVATE); - MoveWindow (priv->hwnd, rect.left, rect.top, - rect.right - rect.left, rect.bottom - rect.top, FALSE); - ShowWindow (priv->hwnd, SW_SHOW); - - priv->state = GST_D3D12_WINDOW_STATE_OPENED; - priv->hwnd_cond.notify_all (); - - /* don't need to be chained up to parent window procedure, - * as this is our custom message */ - gst_object_unref (self); - return 0; - } - case WM_SYSKEYDOWN: - { - WORD state = GetKeyState (VK_RETURN); - BYTE high = HIBYTE (state); - - if (high & 0x1) { - bool do_toggle = false; - { - std::lock_guard < std::mutex > lk (priv->hwnd_lock); - if (priv->fullscreen_on_alt_enter) { - priv->requested_fullscreen = !priv->applied_fullscreen; - do_toggle = true; - } - } - - if (do_toggle) - gst_d3d12_window_toggle_fullscreen_mode (self, TRUE); - } - break; - } - case WM_SIZE: - if (priv->render_rect.w > 0 || priv->render_rect.h > 0) { - MoveWindow (priv->hwnd, - priv->render_rect.x, priv->render_rect.y, - priv->render_rect.w, priv->render_rect.h, FALSE); - } else { - MoveWindow (priv->hwnd, 0, 0, LOWORD (lparam), HIWORD (lparam), FALSE); - } - break; - case WM_DESTROY: - { - std::lock_guard < std::mutex > lk (priv->hwnd_lock); - GST_WARNING_OBJECT (self, "external window is closing"); - - gst_d3d12_window_release_external_hwnd (self); - - if (priv->hwnd) - DestroyWindow (priv->hwnd); - priv->hwnd = nullptr; - - priv->state = GST_D3D12_WINDOW_STATE_CLOSED; - priv->hwnd_cond.notify_all (); - break; - } - case WM_KEYDOWN: - case WM_KEYUP: - if (priv->enable_navigation) - gst_d3d12_window_on_key_event (self, msg, wparam, lparam); - break; - case WM_LBUTTONDOWN: - case WM_LBUTTONUP: - case WM_RBUTTONDOWN: - case WM_RBUTTONUP: - case WM_MBUTTONDOWN: - case WM_MBUTTONUP: - case WM_MOUSEMOVE: - case WM_LBUTTONDBLCLK: - case WM_RBUTTONDBLCLK: - case WM_MBUTTONDBLCLK: - if (priv->enable_navigation) - gst_d3d12_window_on_mouse_event (self, msg, wparam, lparam); - break; - default: - break; - } - - gst_object_unref (self); - - return CallWindowProcW (external_window_proc, hwnd, msg, wparam, lparam); + g_signal_emit (window, d3d12_window_signals[SIGNAL_MOUSE_EVENT], 0, + event, button, xpos, ypos, modifier); } static gboolean @@ -1132,11 +251,16 @@ static gpointer gst_d3d12_window_hwnd_thread_func (GstD3D12Window * self) { auto priv = self->priv; + auto server = HwndServer::get_instance (); g_main_context_push_thread_default (priv->main_context); - gst_d3d12_window_create_hwnd (self); - priv->state = GST_D3D12_WINDOW_STATE_OPENED; + priv->proxy_id = server->create_internal_window (self); + auto proxy = server->get_proxy (self, priv->proxy_id); + priv->proxy = proxy; + + proxy->set_fullscreen_on_alt_enter (priv->fullscreen_on_alt_enter); + proxy->toggle_fullscreen (priv->requested_fullscreen); auto msg_io_ch = g_io_channel_win32_new_messages (0); auto msg_source = g_io_create_watch (msg_io_ch, G_IO_IN); @@ -1147,8 +271,8 @@ gst_d3d12_window_hwnd_thread_func (GstD3D12Window * self) g_source_set_callback (idle_source,[](gpointer data)->gboolean { auto self = GST_D3D12_WINDOW (data); auto priv = self->priv; - std::lock_guard < std::mutex > lk (priv->hwnd_lock); - priv->hwnd_cond.notify_all (); + std::lock_guard < std::mutex > lk (priv->loop_lock); + priv->loop_cond.notify_all (); return G_SOURCE_REMOVE; } , self, nullptr); @@ -1158,7 +282,7 @@ gst_d3d12_window_hwnd_thread_func (GstD3D12Window * self) g_main_loop_run (priv->loop); - g_clear_pointer (&priv->hwnd, DestroyWindow); + proxy = nullptr; g_source_destroy (msg_source); g_source_unref (msg_source); @@ -1169,127 +293,29 @@ gst_d3d12_window_hwnd_thread_func (GstD3D12Window * self) return nullptr; } -static GstFlowReturn -gst_d3d12_window_prepare_hwnd (GstD3D12Window * self, guintptr window_handle) -{ - auto priv = self->priv; - - switch (priv->state) { - case GST_D3D12_WINDOW_STATE_OPENED: - return GST_FLOW_OK; - case GST_D3D12_WINDOW_STATE_CLOSED: - return GST_FLOW_ERROR; - default: - break; - } - - std::unique_lock < std::mutex > lk (priv->hwnd_lock); - priv->external_hwnd = (HWND) window_handle; - if (!priv->external_hwnd) { - priv->main_loop_thread = g_thread_new ("GstD3D12Window", - (GThreadFunc) gst_d3d12_window_hwnd_thread_func, self); - while (!g_main_loop_is_running (priv->loop)) - priv->hwnd_cond.wait (lk); - - return GST_FLOW_OK; - } - - if (!IsWindow (priv->external_hwnd)) { - GST_ERROR_OBJECT (self, "Invalid window handle"); - return GST_FLOW_ERROR; - } - - { - std::lock_guard < std::mutex > glk (global_hwnd_lock); - auto external_proc = - (WNDPROC) GetWindowLongPtrA (priv->external_hwnd, GWLP_WNDPROC); - - SetPropW (priv->external_hwnd, EXTERNAL_PROC_PROP_NAME, - (HANDLE) external_proc); - SetPropW (priv->external_hwnd, D3D12_WINDOW_PROP_NAME, self); - SetWindowLongPtrW (priv->external_hwnd, GWLP_WNDPROC, - (LONG_PTR) sub_class_proc); - } - - PostMessageW (priv->external_hwnd, - WM_GST_D3D12_CONSTRUCT_INTERNAL_WINDOW, 0, 0); - while (priv->external_hwnd && - priv->state == GST_D3D12_WINDOW_STATE_INIT && !priv->flushing) { - priv->hwnd_cond.wait (lk); - } - - if (priv->state != GST_D3D12_WINDOW_STATE_OPENED) { - if (priv->flushing) { - GST_DEBUG_OBJECT (self, "We are flushing"); - return GST_FLOW_FLUSHING; - } - - return GST_FLOW_ERROR; - } - - return GST_FLOW_OK; -} - void gst_d3d12_window_unprepare (GstD3D12Window * window) { - GST_DEBUG_OBJECT (window, "Unprepare"); + GST_DEBUG_OBJECT (window, "Start unprepare"); auto priv = window->priv; + auto server = HwndServer::get_instance (); - { - std::lock_guard < std::recursive_mutex > lk (priv->lock); - priv->ctx = nullptr; - gst_clear_object (&window->device); - } - - if (priv->external_hwnd) { - { - std::lock_guard < std::mutex > lk (global_hwnd_lock); - gst_d3d12_window_release_external_hwnd (window); - } - - std::lock_guard < std::mutex > lk (priv->hwnd_lock); - if (priv->hwnd) { - if (priv->internal_hwnd_thread == g_thread_self ()) { - /* State changing thread is identical to internal window thread. - * window can be closed here */ - - GST_INFO_OBJECT (window, "Closing internal window immediately"); - DestroyWindow (priv->hwnd); - } else { - /* We cannot destroy internal window from non-window thread. - * and we cannot use synchronously SendMessage() method at this point - * since window thread might be waiting for current thread and SendMessage() - * will be blocked until it's called from window thread. - * Instead, posts message so that it can be closed from window thread - * asynchronously */ - GST_INFO_OBJECT (window, "Posting custom destory message"); - PostMessageW (priv->hwnd, WM_GST_D3D12_DESTROY_INTERNAL_WINDOW, 0, 0); - } - } - } + priv->proxy.reset (); + server->release_proxy (window, priv->proxy_id); g_main_loop_quit (priv->loop); g_clear_pointer (&priv->main_loop_thread, g_thread_join); - std::lock_guard < std::mutex > lk (priv->hwnd_lock); - priv->hwnd = nullptr; - priv->external_hwnd = nullptr; - priv->internal_hwnd_thread = nullptr; - priv->state = GST_D3D12_WINDOW_STATE_INIT; - priv->applied_fullscreen = FALSE; + GST_DEBUG_OBJECT (window, "Unprepare done"); } void gst_d3d12_window_unlock (GstD3D12Window * window) { GST_DEBUG_OBJECT (window, "Unlock"); - - auto priv = window->priv; - std::lock_guard < std::mutex > lk (priv->hwnd_lock); - priv->flushing = TRUE; - priv->hwnd_cond.notify_all (); + auto server = HwndServer::get_instance (); + server->unlock_window (window); } void @@ -1297,136 +323,65 @@ gst_d3d12_window_unlock_stop (GstD3D12Window * window) { GST_DEBUG_OBJECT (window, "Unlock stop"); - auto priv = window->priv; - std::lock_guard < std::mutex > lk (priv->hwnd_lock); - priv->flushing = FALSE; - priv->hwnd_cond.notify_all (); + auto server = HwndServer::get_instance (); + server->unlock_stop_window (window); } static GstFlowReturn -gst_d3d12_window_on_resize (GstD3D12Window * self) +gst_d3d12_window_resize_buffer (GstD3D12Window * self) { auto priv = self->priv; - std::lock_guard < std::recursive_mutex > lk (priv->lock); - - /* resize might be called before swapchain configuration */ - if (!priv->ctx) + auto proxy = priv->proxy.lock (); + if (!proxy) return GST_FLOW_OK; - priv->ctx->ReleaseSizeDependentResources (); + return proxy->resize_buffer (); +} - DXGI_SWAP_CHAIN_DESC desc = { }; - priv->ctx->swapchain->GetDesc (&desc); - auto hr = priv->ctx->swapchain->ResizeBuffers (BACK_BUFFER_COUNT, - 0, 0, priv->display_format, desc.Flags); - if (!gst_d3d12_result (hr, self->device)) { - GST_ERROR_OBJECT (self, "Couldn't resize buffers"); - return GST_FLOW_ERROR; +GstFlowReturn +gst_d3d12_window_open (GstD3D12Window * window, GstD3D12Device * device, + guint display_width, guint display_height, HWND parent_hwnd) +{ + auto priv = window->priv; + auto server = HwndServer::get_instance (); + + GST_DEBUG_OBJECT (window, "Opening new window"); + + gst_d3d12_window_unprepare (window); + + priv->display_width = display_width; + priv->display_height = display_height; + + if (!parent_hwnd) { + priv->main_loop_thread = g_thread_new ("GstD3D12Window", + (GThreadFunc) gst_d3d12_window_hwnd_thread_func, window); + + std::unique_lock < std::mutex > lk (priv->loop_lock); + while (!g_main_loop_is_running (priv->loop)) + priv->loop_cond.wait (lk); + + return GST_FLOW_OK; } - for (guint i = 0; i < BACK_BUFFER_COUNT; i++) { - ComPtr < ID3D12Resource > backbuf; - hr = priv->ctx->swapchain->GetBuffer (i, IID_PPV_ARGS (&backbuf)); - if (!gst_d3d12_result (hr, self->device)) { - GST_ERROR_OBJECT (self, "Couldn't get backbuffer"); - return GST_FLOW_ERROR; - } - - if (i == 0) - priv->ctx->buffer_desc = GetDesc (backbuf); - - auto mem = gst_d3d12_allocator_alloc_wrapped (nullptr, self->device, - backbuf.Get (), 0, nullptr, nullptr); - auto buf = gst_buffer_new (); - gst_buffer_append_memory (buf, mem); - auto swapbuf = std::make_shared < SwapBuffer > (buf, backbuf.Get ()); - priv->ctx->swap_buffers.push_back (swapbuf); - } - - guint sample_count = 1; - switch (priv->msaa) { - case GST_D3D12_MSAA_2X: - sample_count = 2; - break; - case GST_D3D12_MSAA_4X: - sample_count = 4; - break; - case GST_D3D12_MSAA_8X: - sample_count = 8; - break; - default: - break; - } - - auto device = gst_d3d12_device_get_device_handle (self->device); - D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS feature_data = { }; - feature_data.Format = priv->ctx->buffer_desc.Format; - feature_data.SampleCount = sample_count; - - while (feature_data.SampleCount > 1) { - hr = device->CheckFeatureSupport (D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS, - &feature_data, sizeof (feature_data)); - if (SUCCEEDED (hr) && feature_data.NumQualityLevels > 0) - break; - - feature_data.SampleCount /= 2; - } - - if (feature_data.SampleCount > 1 && feature_data.NumQualityLevels > 0) { - GST_DEBUG_OBJECT (self, "Enable MSAA x%d with quality level %d", - feature_data.SampleCount, feature_data.NumQualityLevels - 1); - D3D12_HEAP_PROPERTIES heap_prop = - CD3DX12_HEAP_PROPERTIES (D3D12_HEAP_TYPE_DEFAULT); - D3D12_RESOURCE_DESC resource_desc = - CD3DX12_RESOURCE_DESC::Tex2D (priv->ctx->buffer_desc.Format, - priv->ctx->buffer_desc.Width, priv->ctx->buffer_desc.Height, - 1, 1, feature_data.SampleCount, feature_data.NumQualityLevels - 1, - D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET); - D3D12_CLEAR_VALUE clear_value = { }; - clear_value.Format = priv->ctx->buffer_desc.Format; - clear_value.Color[0] = 0.0f; - clear_value.Color[1] = 0.0f; - clear_value.Color[2] = 0.0f; - clear_value.Color[3] = 1.0f; - - ComPtr < ID3D12Resource > msaa_texture; - hr = device->CreateCommittedResource (&heap_prop, D3D12_HEAP_FLAG_NONE, - &resource_desc, D3D12_RESOURCE_STATE_RENDER_TARGET, &clear_value, - IID_PPV_ARGS (&msaa_texture)); - if (!gst_d3d12_result (hr, self->device)) { - GST_ERROR_OBJECT (self, "Couldn't create MSAA texture"); - return GST_FLOW_ERROR; - } - - auto mem = gst_d3d12_allocator_alloc_wrapped (nullptr, self->device, - msaa_texture.Get (), 0, nullptr, nullptr); - priv->ctx->msaa_buf = gst_buffer_new (); - gst_buffer_append_memory (priv->ctx->msaa_buf, mem); - } - - priv->first_present = TRUE; - priv->backbuf_rendered = FALSE; - - /* redraw the last scene if cached buffer exits */ - GstFlowReturn ret = GST_FLOW_OK; - if (priv->ctx->cached_buf) { - ret = gst_d3d12_window_set_buffer (self, priv->ctx->cached_buf); - if (ret == GST_FLOW_OK) - ret = gst_d3d12_window_present (self, TRUE); - } + auto ret = server->create_child_hwnd (window, parent_hwnd, priv->proxy_id); + if (ret == GST_FLOW_OK) + priv->proxy = server->get_proxy (window, priv->proxy_id); return ret; } GstFlowReturn gst_d3d12_window_prepare (GstD3D12Window * window, GstD3D12Device * device, - guintptr window_handle, guint display_width, guint display_height, + guint display_width, guint display_height, GstCaps * caps, GstStructure * config, DXGI_FORMAT display_format) { auto priv = window->priv; GstVideoInfo in_info; GstVideoFormat format = GST_VIDEO_FORMAT_RGBA; priv->display_format = DXGI_FORMAT_R8G8B8A8_UNORM; + priv->display_width = display_width; + priv->display_height = display_height; + gst_video_info_from_caps (&in_info, caps); if (display_format != DXGI_FORMAT_UNKNOWN) { @@ -1448,259 +403,129 @@ gst_d3d12_window_prepare (GstD3D12Window * window, GstD3D12Device * device, } } - { - std::lock_guard < std::recursive_mutex > lk (priv->lock); - gst_video_info_set_format (&priv->display_info, format, - display_width, display_height); - } - - auto ret = gst_d3d12_window_prepare_hwnd (window, window_handle); - if (ret != GST_FLOW_OK) { - GST_WARNING_OBJECT (window, "Couldn't setup window handle"); - if (config) - gst_structure_free (config); - - return ret; - } - - std::unique_lock < std::recursive_mutex > lk (priv->lock); - HRESULT hr; + gst_video_info_set_format (&priv->display_info, format, + display_width, display_height); if (!gst_d3d12_device_is_equal (window->device, device)) { - priv->ctx = nullptr; gst_clear_object (&window->device); window->device = (GstD3D12Device *) gst_object_ref (device); } - if (!priv->ctx) { - auto ctx = std::make_unique < DeviceContext > (device); - - DXGI_SWAP_CHAIN_DESC1 desc = { }; - desc.Format = priv->display_format; - desc.SampleDesc.Count = 1; - desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; - desc.BufferCount = BACK_BUFFER_COUNT; - desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; - - auto factory = gst_d3d12_device_get_factory_handle (device); - auto queue = gst_d3d12_device_get_command_queue (device, - D3D12_COMMAND_LIST_TYPE_DIRECT); - auto cq = gst_d3d12_command_queue_get_handle (queue); - - ComPtr < IDXGISwapChain1 > swapchain; - hr = factory->CreateSwapChainForHwnd (cq, priv->hwnd, - &desc, nullptr, nullptr, &swapchain); - if (!gst_d3d12_result (hr, window->device)) { - GST_ERROR_OBJECT (window, "Couldn't create swapchain"); - if (config) - gst_structure_free (config); - - return GST_FLOW_ERROR; - } - - ComPtr < IDXGIFactory1 > parent_factory; - hr = swapchain->GetParent (IID_PPV_ARGS (&parent_factory)); - if (!gst_d3d12_result (hr, window->device)) { - GST_WARNING_OBJECT (window, "Couldn't get parent factory"); - } else { - hr = parent_factory->MakeWindowAssociation (priv->hwnd, - DXGI_MWA_NO_ALT_ENTER); - if (!gst_d3d12_result (hr, window->device)) { - GST_WARNING_OBJECT (window, "MakeWindowAssociation failed, hr: 0x%x", - (guint) hr); - } - } - - hr = swapchain.As (&ctx->swapchain); - if (!gst_d3d12_result (hr, window->device)) { - GST_ERROR_OBJECT (window, "IDXGISwapChain4 interface is unavailable"); - if (config) - gst_structure_free (config); - return GST_FLOW_ERROR; - } - - priv->ctx = std::move (ctx); - } else { - priv->ctx->WaitGpu (); - } - - priv->input_info = in_info; - priv->crop_rect = CD3DX12_BOX (0, 0, in_info.width, in_info.height); - priv->prev_crop_rect = priv->crop_rect; - - gst_clear_object (&priv->ctx->conv); - gst_clear_object (&priv->ctx->comp); - gst_clear_buffer (&priv->ctx->cached_buf); - - if (GST_VIDEO_INFO_HAS_ALPHA (&in_info)) { - gst_structure_set (config, GST_D3D12_CONVERTER_OPT_DEST_ALPHA_MODE, - GST_TYPE_D3D12_CONVERTER_ALPHA_MODE, - GST_D3D12_CONVERTER_ALPHA_MODE_PREMULTIPLIED, nullptr); - } - - priv->ctx->conv = gst_d3d12_converter_new (window->device, - &priv->input_info, &priv->display_info, nullptr, nullptr, config); - if (!priv->ctx->conv) { - GST_ERROR_OBJECT (window, "Couldn't create converter"); - priv->ctx = nullptr; - return GST_FLOW_ERROR; - } - - priv->ctx->comp = gst_d3d12_overlay_compositor_new (window->device, - &priv->display_info); - if (!priv->ctx->comp) { - GST_ERROR_OBJECT (window, "Couldn't create overlay compositor"); - priv->ctx = nullptr; - return GST_FLOW_ERROR; - } - - ret = gst_d3d12_window_on_resize (window); - if (ret != GST_FLOW_OK) - return ret; - - lk.unlock (); - - std::lock_guard < std::mutex > hlk (priv->hwnd_lock); - if (priv->requested_fullscreen != priv->applied_fullscreen) - PostMessageW (priv->hwnd, WM_GST_D3D12_FULLSCREEN, 0, 0); - - return GST_FLOW_OK; -} - -GstFlowReturn -gst_d3d12_window_set_buffer (GstD3D12Window * window, GstBuffer * buffer) -{ - auto priv = window->priv; - - if (buffer && priv->state != GST_D3D12_WINDOW_STATE_OPENED) { - GST_ERROR_OBJECT (window, "Window was closed"); + auto proxy = priv->proxy.lock (); + if (!proxy) { + GST_WARNING_OBJECT (window, "Window was closed"); return GST_D3D12_WINDOW_FLOW_CLOSED; } - std::lock_guard < std::recursive_mutex > lk (priv->lock); - if (!priv->ctx) { - if (!buffer) { - GST_DEBUG_OBJECT (window, "Swapchain was not configured"); - return GST_FLOW_OK; - } + return proxy->setup_swapchain (device, priv->display_format, &in_info, + &priv->display_info, config); +} - GST_ERROR_OBJECT (window, "Swapchain was not configured"); - return GST_FLOW_ERROR; - } +GstFlowReturn +gst_d3d12_window_render (GstD3D12Window * self, SwapChainResource * resource, + GstBuffer * buffer, bool is_first, RECT & output_rect) +{ + auto priv = self->priv; + auto device = resource->device; + auto cur_idx = resource->swapchain->GetCurrentBackBufferIndex (); + auto swapbuf = resource->buffers[cur_idx]; - if (buffer) - gst_buffer_replace (&priv->ctx->cached_buf, buffer); + { + std::lock_guard < std::recursive_mutex > lk (priv->lock); + if (is_first || priv->output_updated) { + GstVideoRectangle dst_rect = { }; + GstVideoRectangle rst_rect = { }; + dst_rect.w = (gint) resource->buffer_desc.Width; + dst_rect.h = (gint) resource->buffer_desc.Height; - if (!priv->ctx->cached_buf) - return GST_FLOW_OK; + for (size_t i = 0; i < resource->buffers.size (); i++) + resource->buffers[i]->is_first = true; - auto cur_idx = priv->ctx->swapchain->GetCurrentBackBufferIndex (); - auto swapbuf = priv->ctx->swap_buffers[cur_idx]; + if (priv->force_aspect_ratio) { + GstVideoRectangle src_rect = { }; - auto crop_rect = priv->crop_rect; - auto crop_meta = gst_buffer_get_video_crop_meta (priv->ctx->cached_buf); - if (crop_meta) { - crop_rect = CD3DX12_BOX (crop_meta->x, crop_meta->y, - crop_meta->x + crop_meta->width, crop_meta->y + crop_meta->height); - } + switch (priv->orientation) { + 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 (&priv->display_info); + src_rect.h = GST_VIDEO_INFO_WIDTH (&priv->display_info); + break; + default: + src_rect.w = GST_VIDEO_INFO_WIDTH (&priv->display_info); + src_rect.h = GST_VIDEO_INFO_HEIGHT (&priv->display_info); + break; + } - if (crop_rect != priv->prev_crop_rect) { - g_object_set (priv->ctx->conv, "src-x", (gint) crop_rect.left, - "src-y", (gint) crop_rect.top, - "src-width", (gint) (crop_rect.right - crop_rect.left), - "src-height", (gint) (crop_rect.bottom - crop_rect.top), nullptr); - priv->prev_crop_rect = crop_rect; - } - - if (priv->first_present) { - GstVideoRectangle dst_rect = { }; - GstVideoRectangle rst_rect = { }; - dst_rect.w = (gint) priv->ctx->buffer_desc.Width; - dst_rect.h = (gint) priv->ctx->buffer_desc.Height; - - for (size_t i = 0; i < priv->ctx->swap_buffers.size (); i++) - priv->ctx->swap_buffers[i]->first = true; - - if (priv->force_aspect_ratio) { - GstVideoRectangle src_rect = { }; - - switch (priv->orientation) { - 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 (&priv->display_info); - src_rect.h = GST_VIDEO_INFO_WIDTH (&priv->display_info); - break; - default: - src_rect.w = GST_VIDEO_INFO_WIDTH (&priv->display_info); - src_rect.h = GST_VIDEO_INFO_HEIGHT (&priv->display_info); - break; + gst_video_sink_center_rect (src_rect, dst_rect, &rst_rect, TRUE); + } else { + rst_rect = dst_rect; } - gst_video_sink_center_rect (src_rect, dst_rect, &rst_rect, TRUE); - } else { - rst_rect = dst_rect; + priv->output_rect = rst_rect; + priv->dirty_rect.left = rst_rect.x; + priv->dirty_rect.top = rst_rect.y; + priv->dirty_rect.right = rst_rect.x + rst_rect.w; + priv->dirty_rect.bottom = rst_rect.y + rst_rect.h; + + output_rect = priv->dirty_rect; + + g_object_set (resource->conv, "dest-x", priv->output_rect.x, + "dest-y", priv->output_rect.y, "dest-width", priv->output_rect.w, + "dest-height", priv->output_rect.h, nullptr); + + if (gst_d3d12_need_transform (priv->rotation_x, priv->rotation_y, + priv->rotation_z, priv->scale_x, priv->scale_y)) { + gst_d3d12_converter_apply_transform (resource->conv, priv->orientation, + priv->output_rect.w, priv->output_rect.h, priv->fov, priv->ortho, + priv->rotation_x, priv->rotation_y, priv->rotation_z, + priv->scale_x, priv->scale_y); + } else { + g_object_set (resource->conv, + "video-direction", priv->orientation, nullptr); + } + + gst_d3d12_overlay_compositor_update_viewport (resource->comp, + &priv->output_rect); } - priv->output_rect = rst_rect; - priv->dirty_rect.left = rst_rect.x; - priv->dirty_rect.top = rst_rect.y; - priv->dirty_rect.right = rst_rect.x + rst_rect.w; - priv->dirty_rect.bottom = rst_rect.y + rst_rect.h; - - g_object_set (priv->ctx->conv, "dest-x", priv->output_rect.x, - "dest-y", priv->output_rect.y, "dest-width", priv->output_rect.w, - "dest-height", priv->output_rect.h, nullptr); - - if (gst_d3d12_need_transform (priv->rotation_x, priv->rotation_y, - priv->rotation_z, priv->scale_x, priv->scale_y)) { - gst_d3d12_converter_apply_transform (priv->ctx->conv, priv->orientation, - priv->output_rect.w, priv->output_rect.h, priv->fov, priv->ortho, - priv->rotation_x, priv->rotation_y, priv->rotation_z, - priv->scale_x, priv->scale_y); - } else { - g_object_set (priv->ctx->conv, - "video-direction", priv->orientation, nullptr); - } - - gst_d3d12_overlay_compositor_update_viewport (priv->ctx->comp, - &priv->output_rect); + priv->output_updated = FALSE; } - gst_d3d12_overlay_compositor_upload (priv->ctx->comp, priv->ctx->cached_buf); + gst_d3d12_overlay_compositor_upload (resource->comp, buffer); GstD3D12CommandAllocator *gst_ca; - if (!gst_d3d12_command_allocator_pool_acquire (priv->ctx->ca_pool, &gst_ca)) { - GST_ERROR_OBJECT (window, "Couldn't acquire command allocator"); + if (!gst_d3d12_command_allocator_pool_acquire (resource->ca_pool, &gst_ca)) { + GST_ERROR_OBJECT (self, "Couldn't acquire command allocator"); return GST_FLOW_ERROR; } auto ca = gst_d3d12_command_allocator_get_handle (gst_ca); auto hr = ca->Reset (); - if (!gst_d3d12_result (hr, window->device)) { - GST_ERROR_OBJECT (window, "Couldn't reset command list"); + if (!gst_d3d12_result (hr, device)) { + GST_ERROR_OBJECT (self, "Couldn't reset command list"); gst_d3d12_command_allocator_unref (gst_ca); return GST_FLOW_ERROR; } ComPtr < ID3D12GraphicsCommandList > cl; - if (!priv->ctx->cl) { - auto device = gst_d3d12_device_get_device_handle (priv->ctx->device); - hr = device->CreateCommandList (0, D3D12_COMMAND_LIST_TYPE_DIRECT, + if (!resource->cl) { + auto device_handle = gst_d3d12_device_get_device_handle (device); + hr = device_handle->CreateCommandList (0, D3D12_COMMAND_LIST_TYPE_DIRECT, ca, nullptr, IID_PPV_ARGS (&cl)); - if (!gst_d3d12_result (hr, priv->ctx->device)) { - GST_ERROR_OBJECT (window, "Couldn't create command list"); + if (!gst_d3d12_result (hr, device)) { + GST_ERROR_OBJECT (self, "Couldn't create command list"); gst_d3d12_command_allocator_unref (gst_ca); return GST_FLOW_ERROR; } - priv->ctx->cl = cl; + resource->cl = cl; } else { - cl = priv->ctx->cl; + cl = resource->cl; hr = cl->Reset (ca, nullptr); - if (!gst_d3d12_result (hr, priv->ctx->device)) { - GST_ERROR_OBJECT (window, "Couldn't reset command list"); + if (!gst_d3d12_result (hr, device)) { + GST_ERROR_OBJECT (self, "Couldn't reset command list"); gst_d3d12_command_allocator_unref (gst_ca); return GST_FLOW_ERROR; } @@ -1714,9 +539,9 @@ gst_d3d12_window_set_buffer (GstD3D12Window * window, GstBuffer * buffer) auto backbuf_texture = gst_d3d12_memory_get_resource_handle (mem); ID3D12Resource *msaa_resource = nullptr; GstBuffer *conv_outbuf = swapbuf->backbuf; - if (priv->ctx->msaa_buf) { - conv_outbuf = priv->ctx->msaa_buf; - mem = (GstD3D12Memory *) gst_buffer_peek_memory (priv->ctx->msaa_buf, 0); + if (resource->msaa_buf) { + conv_outbuf = resource->msaa_buf; + mem = (GstD3D12Memory *) gst_buffer_peek_memory (conv_outbuf, 0); msaa_resource = gst_d3d12_memory_get_resource_handle (mem); /* MSAA resource must be render target state here already */ } else { @@ -1727,30 +552,29 @@ gst_d3d12_window_set_buffer (GstD3D12Window * window, GstBuffer * buffer) cl->ResourceBarrier (1, &barrier); } - if (swapbuf->first || priv->overlay_mode != GST_D3D12_WINDOW_OVERLAY_NONE) { + if (swapbuf->is_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; + swapbuf->is_first = false; - auto cq = gst_d3d12_device_get_command_queue (priv->ctx->device, + auto cq = gst_d3d12_device_get_command_queue (device, D3D12_COMMAND_LIST_TYPE_DIRECT); auto cq_handle = gst_d3d12_command_queue_get_handle (cq); - if (!gst_d3d12_converter_convert_buffer (priv->ctx->conv, - priv->ctx->cached_buf, conv_outbuf, fence_data, cl.Get (), - cq_handle)) { - GST_ERROR_OBJECT (window, "Couldn't build convert command"); + if (!gst_d3d12_converter_convert_buffer (resource->conv, + buffer, conv_outbuf, fence_data, cl.Get (), cq_handle)) { + GST_ERROR_OBJECT (self, "Couldn't build convert command"); gst_d3d12_fence_data_unref (fence_data); return GST_FLOW_ERROR; } - if (!gst_d3d12_overlay_compositor_draw (priv->ctx->comp, + if (!gst_d3d12_overlay_compositor_draw (resource->comp, conv_outbuf, fence_data, cl.Get ())) { - GST_ERROR_OBJECT (window, "Couldn't build overlay command"); + GST_ERROR_OBJECT (self, "Couldn't build overlay command"); gst_d3d12_fence_data_unref (fence_data); return GST_FLOW_ERROR; } @@ -1760,23 +584,24 @@ gst_d3d12_window_set_buffer (GstD3D12Window * window, GstBuffer * buffer) 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) { + GstD3D12WindowOverlayMode overlay_mode = priv->overlay_mode; + if ((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) == + if ((overlay_mode & GST_D3D12_WINDOW_OVERLAY_D3D11) == GST_D3D12_WINDOW_OVERLAY_D3D11 && - priv->ctx->EnsureD3D11RenderTarget (swapbuf)) { + resource->ensure_d3d11_target (swapbuf.get ())) { selected_overlay_mode |= GST_D3D12_WINDOW_OVERLAY_D3D11; signal_with_lock = true; } - if ((priv->overlay_mode & GST_D3D12_WINDOW_OVERLAY_D2D) == + if ((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)) { + resource->ensure_d2d_target (swapbuf.get ())) { selected_overlay_mode |= GST_D3D12_WINDOW_OVERLAY_D2D; set_d2d_target = true; } @@ -1809,66 +634,58 @@ gst_d3d12_window_set_buffer (GstD3D12Window * window, GstBuffer * buffer) } 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; - } - - if (!priv->ctx->BeforeRendering ()) { - GST_ERROR_OBJECT (window, "Couldn't wait fence value"); + if (!gst_d3d12_result (hr, device)) { + GST_ERROR_OBJECT (self, "Couldn't close command list"); gst_d3d12_fence_data_unref (fence_data); return GST_FLOW_ERROR; } ID3D12CommandList *cmd_list[] = { cl.Get () }; 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"); + 1, cmd_list, &resource->fence_val); + if (!gst_d3d12_result (hr, device)) { + GST_ERROR_OBJECT (self, "Signal failed"); gst_d3d12_fence_data_unref (fence_data); return GST_FLOW_ERROR; } - gst_d3d12_command_queue_set_notify (cq, priv->ctx->fence_val, + gst_d3d12_command_queue_set_notify (cq, resource->fence_val, 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); + gst_d3d12_device_11on12_lock (device); if (set_d2d_target) - priv->ctx->context2d->SetTarget (swapbuf->d2d_target.Get ()); + resource->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 (), + g_signal_emit (self, d3d12_window_signals[SIGNAL_OVERLAY], 0, + cq_handle, backbuf_texture, resource->device11on12.Get (), + swapbuf->wrapped_resource.Get (), resource->context2d.Get (), &viewport); if (signal_with_lock) - gst_d3d12_device_11on12_unlock (window->device); + gst_d3d12_device_11on12_unlock (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"); + if (!gst_d3d12_command_allocator_pool_acquire (resource->ca_pool, &gst_ca)) { + GST_ERROR_OBJECT (self, "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"); + if (!gst_d3d12_result (hr, device)) { + GST_ERROR_OBJECT (self, "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"); + if (!gst_d3d12_result (hr, device)) { + GST_ERROR_OBJECT (self, "Couldn't reset command list"); gst_d3d12_command_allocator_unref (gst_ca); return GST_FLOW_ERROR; } @@ -1881,79 +698,63 @@ gst_d3d12_window_set_buffer (GstD3D12Window * window, GstBuffer * buffer) 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"); + if (!gst_d3d12_result (hr, device)) { + GST_ERROR_OBJECT (self, "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"); + 1, cmd_list, &resource->fence_val); + if (!gst_d3d12_result (hr, device)) { + GST_ERROR_OBJECT (self, "Signal failed"); gst_d3d12_fence_data_unref (fence_data); return GST_FLOW_ERROR; } - gst_d3d12_command_queue_set_notify (cq, priv->ctx->fence_val, + gst_d3d12_command_queue_set_notify (cq, resource->fence_val, fence_data, (GDestroyNotify) gst_d3d12_fence_data_unref); } - priv->ctx->AfterRendering (); - return GST_FLOW_OK; } GstFlowReturn -gst_d3d12_window_present (GstD3D12Window * window, gboolean sync) +gst_d3d12_window_set_buffer (GstD3D12Window * window, GstBuffer * buffer) { auto priv = window->priv; + auto proxy = priv->proxy.lock (); - if (priv->state != GST_D3D12_WINDOW_STATE_OPENED) { + if (!proxy) { GST_WARNING_OBJECT (window, "Window was closed"); return GST_D3D12_WINDOW_FLOW_CLOSED; } - std::lock_guard < std::recursive_mutex > lk (priv->lock); - if (!priv->ctx) { - GST_ERROR_OBJECT (window, "Swapchain was not configured"); - return GST_FLOW_ERROR; + return proxy->set_buffer (buffer); +} + +GstFlowReturn +gst_d3d12_window_present (GstD3D12Window * window) +{ + auto priv = window->priv; + auto proxy = priv->proxy.lock (); + if (!proxy) { + GST_WARNING_OBJECT (window, "Window was closed"); + return GST_D3D12_WINDOW_FLOW_CLOSED; } - if (priv->backbuf_rendered) { - DXGI_PRESENT_PARAMETERS params = { }; - UINT present_flag = sync ? 0 : DXGI_PRESENT_DO_NOT_WAIT; - - if (!priv->first_present) { - params.DirtyRectsCount = 1; - params.pDirtyRects = &priv->dirty_rect; - } - - priv->first_present = FALSE; - priv->backbuf_rendered = FALSE; - - auto hr = priv->ctx->swapchain->Present1 (0, present_flag, ¶ms); - switch (hr) { - case DXGI_ERROR_DEVICE_REMOVED: - case DXGI_ERROR_INVALID_CALL: - case E_OUTOFMEMORY: - gst_d3d12_result (hr, window->device); - return GST_FLOW_ERROR; - default: - /* Ignore other return code */ - break; - } - } - - return GST_FLOW_OK; + return proxy->present (); } guintptr gst_d3d12_window_get_window_handle (GstD3D12Window * window) { auto priv = window->priv; + auto proxy = priv->proxy.lock (); + if (!proxy) + return 0; - return (guintptr) priv->hwnd; + return (guintptr) proxy->get_window_handle (); } void @@ -1963,14 +764,23 @@ gst_d3d12_window_set_render_rect (GstD3D12Window * window, auto priv = window->priv; { - std::lock_guard < std::mutex > lk (priv->hwnd_lock); + std::lock_guard < std::recursive_mutex > lk (priv->lock); priv->render_rect = *rect; } - if (priv->hwnd) { - PostMessageW (priv->hwnd, WM_GST_D3D12_UPDATE_RENDER_RECT, 0, 0); - return; - } + auto proxy = priv->proxy.lock (); + if (proxy) + proxy->update_render_rect (); +} + +void +gst_d3d12_window_get_render_rect (GstD3D12Window * window, + GstVideoRectangle * rect) +{ + auto priv = window->priv; + + std::lock_guard < std::recursive_mutex > lk (priv->lock); + *rect = priv->render_rect; } void @@ -1978,12 +788,19 @@ gst_d3d12_window_set_force_aspect_ratio (GstD3D12Window * window, gboolean force_aspect_ratio) { auto priv = window->priv; + bool updated = false; - std::lock_guard < std::recursive_mutex > lk (priv->lock); - if (priv->force_aspect_ratio != force_aspect_ratio) { - priv->force_aspect_ratio = force_aspect_ratio; - gst_d3d12_window_on_resize (window); + { + std::lock_guard < std::recursive_mutex > lk (priv->lock); + if (priv->force_aspect_ratio != force_aspect_ratio) { + priv->force_aspect_ratio = force_aspect_ratio; + priv->output_updated = TRUE; + updated = true; + } } + + if (updated) + gst_d3d12_window_set_buffer (window, nullptr); } void @@ -1991,11 +808,16 @@ gst_d3d12_window_set_enable_navigation_events (GstD3D12Window * window, gboolean enable) { auto priv = window->priv; - - std::lock_guard < std::recursive_mutex > lk (priv->lock); priv->enable_navigation = enable; } +gboolean +gst_d3d12_window_get_navigation_events_enabled (GstD3D12Window * window) +{ + auto priv = window->priv; + return priv->enable_navigation; +} + void gst_d3d12_window_set_orientation (GstD3D12Window * window, gboolean immediate, GstVideoOrientationMethod orientation, gfloat fov, gboolean ortho, @@ -2003,25 +825,30 @@ gst_d3d12_window_set_orientation (GstD3D12Window * window, gboolean immediate, gfloat scale_x, gfloat scale_y) { auto priv = window->priv; + bool updated = false; - std::lock_guard < std::recursive_mutex > lk (priv->lock); - if (priv->orientation != orientation || priv->fov != fov - || priv->ortho != ortho - || priv->rotation_x != rotation_x || priv->rotation_y != rotation_y - || priv->rotation_z != rotation_z || priv->scale_x != scale_x - || priv->scale_y != scale_y) { - priv->orientation = orientation; - priv->fov = fov; - priv->ortho = ortho; - priv->rotation_x = rotation_x; - priv->rotation_y = rotation_y; - priv->rotation_z = rotation_z; - priv->scale_x = scale_x; - priv->scale_y = scale_y; - priv->first_present = TRUE; - if (immediate) - gst_d3d12_window_set_buffer (window, nullptr); + { + std::lock_guard < std::recursive_mutex > lk (priv->lock); + if (priv->orientation != orientation || priv->fov != fov + || priv->ortho != ortho + || priv->rotation_x != rotation_x || priv->rotation_y != rotation_y + || priv->rotation_z != rotation_z || priv->scale_x != scale_x + || priv->scale_y != scale_y) { + priv->orientation = orientation; + priv->fov = fov; + priv->ortho = ortho; + priv->rotation_x = rotation_x; + priv->rotation_y = rotation_y; + priv->rotation_z = rotation_z; + priv->scale_x = scale_x; + priv->scale_y = scale_y; + priv->output_updated = TRUE; + updated = true; + } } + + if (updated && immediate) + gst_d3d12_window_set_buffer (window, nullptr); } void @@ -2033,7 +860,7 @@ gst_d3d12_window_set_title (GstD3D12Window * window, const gchar * title) if (title) wtitle = (wchar_t *) g_utf8_to_utf16 (title, -1, nullptr, nullptr, nullptr); - std::lock_guard < std::mutex > lk (priv->hwnd_lock); + std::lock_guard < std::recursive_mutex > lk (priv->lock); if (!wtitle) { priv->title.clear (); } else { @@ -2048,15 +875,28 @@ gst_d3d12_window_new (void) auto self = (GstD3D12Window *) g_object_new (GST_TYPE_D3D12_WINDOW, nullptr); gst_object_ref_sink (self); + auto server = HwndServer::get_instance (); + server->register_window (self); + return self; } -GstD3D12WindowState -gst_d3d12_window_get_state (GstD3D12Window * window) +void +gst_d3d12_window_invalidate (GstD3D12Window * window) +{ + auto server = HwndServer::get_instance (); + server->unregister_window (window); +} + +gboolean +gst_d3d12_window_is_closed (GstD3D12Window * window) { auto priv = window->priv; - return priv->state; + if (priv->proxy.expired ()) + return TRUE; + + return FALSE; } void @@ -2064,29 +904,38 @@ gst_d3d12_window_enable_fullscreen_on_alt_enter (GstD3D12Window * window, gboolean enable) { auto priv = window->priv; - std::lock_guard < std::mutex > lk (priv->hwnd_lock); + auto proxy = priv->proxy.lock (); + priv->fullscreen_on_alt_enter = enable; + if (proxy) + proxy->set_fullscreen_on_alt_enter (enable); } void gst_d3d12_window_set_fullscreen (GstD3D12Window * window, gboolean enable) { auto priv = window->priv; - std::lock_guard < std::mutex > lk (priv->hwnd_lock); + auto proxy = priv->proxy.lock (); + priv->requested_fullscreen = enable; - if (priv->hwnd && priv->applied_fullscreen != priv->requested_fullscreen) - PostMessageW (priv->hwnd, WM_GST_D3D12_FULLSCREEN, 0, 0); + if (proxy) + proxy->toggle_fullscreen (enable); } void gst_d3d12_window_set_msaa (GstD3D12Window * window, GstD3D12MSAAMode msaa) { auto priv = window->priv; - std::lock_guard < std::recursive_mutex > lk (priv->lock); - if (priv->msaa != msaa) { - priv->msaa = msaa; - gst_d3d12_window_on_resize (window); - } + auto prev_val = priv->msaa.exchange (msaa); + if (prev_val != msaa) + gst_d3d12_window_resize_buffer (window); +} + +void +gst_d3d12_window_get_msaa (GstD3D12Window * window, GstD3D12MSAAMode & msaa) +{ + auto priv = window->priv; + msaa = priv->msaa; } void @@ -2094,6 +943,39 @@ 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; } + +void +gst_d3d12_window_get_create_params (GstD3D12Window * window, + std::wstring & title, GstVideoRectangle * rect, int &display_width, + int &display_height, GstVideoOrientationMethod & orientation) +{ + auto priv = window->priv; + + std::lock_guard < std::recursive_mutex > lk (priv->lock); + if (priv->title.empty ()) + title = L"Direct3D12 Renderer"; + else + title = priv->title; + + *rect = priv->render_rect; + + display_width = priv->display_width; + display_height = priv->display_height; + orientation = priv->orientation; +} + +void +gst_d3d12_window_get_mouse_pos_info (GstD3D12Window * window, + GstVideoRectangle * out_rect, int &input_width, int &input_height, + GstVideoOrientationMethod & orientation) +{ + auto priv = window->priv; + std::lock_guard < std::recursive_mutex > lk (priv->lock); + + *out_rect = priv->output_rect; + input_width = priv->input_info.width; + input_height = priv->input_info.height; + orientation = priv->orientation; +} diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window.h b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window.h index b6834a4a26..2de52c5fa1 100644 --- a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window.h +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window.h @@ -23,6 +23,8 @@ #include #include #include "gstd3d12pluginutils.h" +#include "gstd3d12window-swapchain-resource.h" +#include G_BEGIN_DECLS @@ -32,13 +34,6 @@ G_BEGIN_DECLS G_DECLARE_FINAL_TYPE (GstD3D12Window, gst_d3d12_window, GST, D3D12_WINDOW, GstObject) -enum GstD3D12WindowState -{ - GST_D3D12_WINDOW_STATE_INIT, - GST_D3D12_WINDOW_STATE_OPENED, - GST_D3D12_WINDOW_STATE_CLOSED, -}; - enum GstD3D12WindowOverlayMode { GST_D3D12_WINDOW_OVERLAY_NONE = 0, @@ -54,13 +49,20 @@ GType gst_d3d12_window_overlay_mode_get_type (void); GstD3D12Window * gst_d3d12_window_new (void); +void gst_d3d12_window_invalidate (GstD3D12Window * window); + guintptr gst_d3d12_window_get_window_handle (GstD3D12Window * window); -GstD3D12WindowState gst_d3d12_window_get_state (GstD3D12Window * window); +gboolean gst_d3d12_window_is_closed (GstD3D12Window * window); + +GstFlowReturn gst_d3d12_window_open (GstD3D12Window * window, + GstD3D12Device * device, + guint display_width, + guint display_height, + HWND parent_hwnd); GstFlowReturn gst_d3d12_window_prepare (GstD3D12Window * window, GstD3D12Device * device, - guintptr window_handle, guint display_width, guint display_height, GstCaps * caps, @@ -76,18 +78,28 @@ void gst_d3d12_window_unlock_stop (GstD3D12Window * window); GstFlowReturn gst_d3d12_window_set_buffer (GstD3D12Window * window, GstBuffer * buffer); -GstFlowReturn gst_d3d12_window_present (GstD3D12Window * window, - gboolean sync); +GstFlowReturn gst_d3d12_window_render (GstD3D12Window * window, + SwapChainResource * resource, + GstBuffer * buffer, + bool is_first, + RECT & output_rect); + +GstFlowReturn gst_d3d12_window_present (GstD3D12Window * window); void gst_d3d12_window_set_render_rect (GstD3D12Window * window, const GstVideoRectangle * rect); +void gst_d3d12_window_get_render_rect (GstD3D12Window * window, + GstVideoRectangle * rect); + void gst_d3d12_window_set_force_aspect_ratio (GstD3D12Window * window, gboolean force_aspect_ratio); void gst_d3d12_window_set_enable_navigation_events (GstD3D12Window * window, gboolean enable); +gboolean gst_d3d12_window_get_navigation_events_enabled (GstD3D12Window * window); + void gst_d3d12_window_set_orientation (GstD3D12Window * window, gboolean immediate, GstVideoOrientationMethod orientation, @@ -111,8 +123,35 @@ void gst_d3d12_window_set_fullscreen (GstD3D12Window * window, void gst_d3d12_window_set_msaa (GstD3D12Window * window, GstD3D12MSAAMode msaa); +void gst_d3d12_window_get_msaa (GstD3D12Window * window, + GstD3D12MSAAMode & msaa); + void gst_d3d12_window_set_overlay_mode (GstD3D12Window * window, GstD3D12WindowOverlayMode mode); +void gst_d3d12_window_on_key_event (GstD3D12Window * window, + const gchar * event, + const gchar * name); + +void gst_d3d12_window_on_mouse_event (GstD3D12Window * window, + const gchar * event, + gint button, + double xpos, + double ypos, + guint modifier); + +void gst_d3d12_window_get_create_params (GstD3D12Window * window, + std::wstring & title, + GstVideoRectangle * rect, + int & display_width, + int & display_height, + GstVideoOrientationMethod & orientation); + +void gst_d3d12_window_get_mouse_pos_info (GstD3D12Window * window, + GstVideoRectangle * out_rect, + int & input_width, + int & input_height, + GstVideoOrientationMethod & orientation); + G_END_DECLS diff --git a/subprojects/gst-plugins-bad/sys/d3d12/meson.build b/subprojects/gst-plugins-bad/sys/d3d12/meson.build index ab602b1af3..57c4724c80 100644 --- a/subprojects/gst-plugins-bad/sys/d3d12/meson.build +++ b/subprojects/gst-plugins-bad/sys/d3d12/meson.build @@ -28,6 +28,8 @@ d3d12_sources = [ 'gstd3d12videosink.cpp', 'gstd3d12vp8dec.cpp', 'gstd3d12vp9dec.cpp', + 'gstd3d12window-swapchain.cpp', + 'gstd3d12window-win32.cpp', 'gstd3d12window.cpp', 'plugin.cpp', ] diff --git a/subprojects/gst-plugins-bad/tests/examples/d3d12/d3d12videosink-switch.cpp b/subprojects/gst-plugins-bad/tests/examples/d3d12/d3d12videosink-switch.cpp new file mode 100644 index 0000000000..4f4045552d --- /dev/null +++ b/subprojects/gst-plugins-bad/tests/examples/d3d12/d3d12videosink-switch.cpp @@ -0,0 +1,351 @@ +/* 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 "../key-handler.h" + +static GMainLoop *loop = nullptr; +static HWND hwnd_0 = nullptr; +static HWND hwnd_1 = nullptr; + +struct AppData +{ + GstElement *pipeline = nullptr; + GstVideoOverlay *overlay_0 = nullptr; + GstVideoOverlay *overlay_1 = nullptr; + guint mode = 0; +}; + +#define APP_DATA_PROP_NAME L"EXAMPLE-APP-DATA" + +static LRESULT CALLBACK +window_proc (HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) +{ + switch (message) { + case WM_NCCREATE: + { + LPCREATESTRUCTW lpcs = (LPCREATESTRUCTW) lparam; + auto data = (AppData *) lpcs->lpCreateParams; + SetPropW (hwnd, APP_DATA_PROP_NAME, data); + break; + } + case WM_DESTROY: + gst_println ("Destroy window %d", hwnd == hwnd_0 ? 0 : 1); + if (loop) + g_main_loop_quit (loop); + break; + case WM_SIZE: + { + auto data = (AppData *) GetPropW (hwnd, APP_DATA_PROP_NAME); + if (!data || !data->overlay_0 || !data->overlay_1) + break; + + if ((data->mode == 1 && hwnd == hwnd_0) || + (data->mode == 2 && hwnd == hwnd_1)) { + RECT rect; + GetClientRect (hwnd, &rect); + gint width = (rect.right - rect.left) / 2; + gint height = (rect.bottom - rect.top); + + auto overlay_0 = data->overlay_0; + auto overlay_1 = data->overlay_1; + + gst_video_overlay_set_render_rectangle (overlay_0, + 0, 0, width, height); + gst_video_overlay_set_render_rectangle (overlay_1, + width, 0, width, height); + } + break; + } + default: + break; + } + + return DefWindowProcW (hwnd, message, wparam, lparam); +} + +static void +keyboard_cb (gchar input, gboolean is_ascii, AppData * data) +{ + if (!is_ascii) + return; + + switch (input) { + case 'q': + case 'Q': + gst_element_send_event (data->pipeline, gst_event_new_eos ()); + break; + case ' ': + data->mode++; + data->mode %= 4; + switch (data->mode) { + case 0: + { + auto overlay_0 = data->overlay_0; + auto overlay_1 = data->overlay_1; + gst_video_overlay_set_window_handle (overlay_0, + (guintptr) hwnd_0); + gst_video_overlay_set_render_rectangle (overlay_0, 0, 0, -1, -1); + + gst_video_overlay_set_window_handle (overlay_1, + (guintptr) hwnd_1); + gst_video_overlay_set_render_rectangle (overlay_1, 0, 0, -1, -1); + break; + } + case 1: + { + RECT rect; + GetClientRect (hwnd_0, &rect); + gint width = (rect.right - rect.left) / 2; + gint height = (rect.bottom - rect.top); + + auto overlay_0 = data->overlay_0; + auto overlay_1 = data->overlay_1; + + gst_video_overlay_set_window_handle (overlay_0, + (guintptr) hwnd_0); + gst_video_overlay_set_render_rectangle (overlay_0, + 0, 0, width, height); + + gst_video_overlay_set_window_handle (overlay_1, + (guintptr) hwnd_0); + gst_video_overlay_set_render_rectangle (overlay_1, + width, 0, width, height); + break; + } + case 2: + { + RECT rect; + GetClientRect (hwnd_1, &rect); + gint width = (rect.right - rect.left) / 2; + gint height = (rect.bottom - rect.top); + + auto overlay_0 = data->overlay_0; + auto overlay_1 = data->overlay_1; + + gst_video_overlay_set_window_handle (overlay_0, + (guintptr) hwnd_1); + gst_video_overlay_set_render_rectangle (overlay_0, + 0, 0, width, height); + + gst_video_overlay_set_window_handle (overlay_1, + (guintptr) hwnd_1); + gst_video_overlay_set_render_rectangle (overlay_1, + width, 0, width, height); + break; + } + case 3: + { + auto overlay_0 = data->overlay_0; + auto overlay_1 = data->overlay_1; + gst_video_overlay_set_window_handle (overlay_0, + (guintptr) hwnd_1); + gst_video_overlay_set_render_rectangle (overlay_0, 0, 0, -1, -1); + + gst_video_overlay_set_window_handle (overlay_1, + (guintptr) hwnd_0); + gst_video_overlay_set_render_rectangle (overlay_1, 0, 0, -1, -1); + break; + } + default: + break; + } + break; + default: + break; + } +} + +static gboolean +bus_msg (GstBus * bus, GstMessage * msg, AppData * data) +{ + 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 != nullptr) + gst_printerrln ("ERROR debug information: %s", dbg); + g_clear_error (&err); + g_free (dbg); + + g_main_loop_quit (loop); + break; + } + case GST_MESSAGE_EOS: + { + gst_println ("Got EOS"); + g_main_loop_quit (loop); + break; + } + default: + break; + } + + return TRUE; +} + +static gboolean +msg_cb (GIOChannel * source, GIOCondition condition, gpointer data) +{ + MSG msg; + + if (!PeekMessageW (&msg, nullptr, 0, 0, PM_REMOVE)) + return G_SOURCE_CONTINUE; + + TranslateMessage (&msg); + DispatchMessage (&msg); + + return G_SOURCE_CONTINUE; +} + +static void +print_keyboard_help (void) +{ + static struct + { + const gchar *key_desc; + const gchar *key_help; + } key_controls[] = { + { + "q", "Quit"}, { + "space", "Toggle render window"} + }; + guint i, chars_to_pad, desc_len, max_desc_len = 0; + + gst_print ("\n%s\n", "Keyboard controls:"); + + for (i = 0; i < G_N_ELEMENTS (key_controls); ++i) { + desc_len = g_utf8_strlen (key_controls[i].key_desc, -1); + max_desc_len = MAX (max_desc_len, desc_len); + } + ++max_desc_len; + + for (i = 0; i < G_N_ELEMENTS (key_controls); ++i) { + chars_to_pad = max_desc_len - g_utf8_strlen (key_controls[i].key_desc, -1); + gst_print ("\t%s", key_controls[i].key_desc); + gst_print ("%-*s: ", chars_to_pad, ""); + gst_print ("%s\n", key_controls[i].key_help); + } + gst_print ("\n"); +} + +gint +main (gint argc, gchar ** argv) +{ + WNDCLASSEXW wc = { }; + HINSTANCE hinstance = GetModuleHandle (nullptr); + GIOChannel *msg_io_channel = nullptr; + RECT wr = { 0, 0, 320, 240 }; + AppData app_data = { }; + + gst_init (nullptr, nullptr); + + print_keyboard_help (); + + loop = g_main_loop_new (nullptr, FALSE); + + /* prepare window */ + wc.cbSize = sizeof (WNDCLASSEXW); + wc.lpfnWndProc = (WNDPROC) window_proc; + wc.hInstance = hinstance; + wc.hIcon = LoadIcon (nullptr, IDI_WINLOGO); + wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; + wc.hCursor = LoadCursor (nullptr, IDC_ARROW); + wc.hbrBackground = (HBRUSH) GetStockObject (BLACK_BRUSH); + wc.lpszClassName = L"GstD3D12VideoSinkExample"; + + RegisterClassExW (&wc); + AdjustWindowRect (&wr, WS_OVERLAPPEDWINDOW, FALSE); + + hwnd_0 = CreateWindowExW (0, wc.lpszClassName, L"Window-0", + WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW | WS_VISIBLE, + CW_USEDEFAULT, CW_USEDEFAULT, + wr.right - wr.left, wr.bottom - wr.top, (HWND) nullptr, (HMENU) nullptr, + hinstance, &app_data); + + hwnd_1 = CreateWindowExW (0, wc.lpszClassName, L"Window-1", + WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW | WS_VISIBLE, + CW_USEDEFAULT, CW_USEDEFAULT, + wr.right - wr.left, wr.bottom - wr.top, (HWND) nullptr, (HMENU) nullptr, + hinstance, &app_data); + + msg_io_channel = g_io_channel_win32_new_messages (0); + g_io_add_watch (msg_io_channel, G_IO_IN, msg_cb, nullptr); + + /* prepare the pipeline */ + app_data.pipeline = gst_parse_launch ("d3d12testsrc pattern=ball ! queue ! " + "d3d12videosink name=sink0 d3d12testsrc ! queue ! " + "d3d12videosink name=sink1", nullptr); + if (!app_data.pipeline) { + gst_printerrln ("Couldn't create pipeline"); + return 0; + } + + auto sink_0 = gst_bin_get_by_name (GST_BIN (app_data.pipeline), "sink0"); + auto sink_1 = gst_bin_get_by_name (GST_BIN (app_data.pipeline), "sink1"); + + app_data.overlay_0 = GST_VIDEO_OVERLAY (sink_0); + app_data.overlay_1 = GST_VIDEO_OVERLAY (sink_1); + + gst_video_overlay_set_window_handle (app_data.overlay_0, (guintptr) hwnd_0); + gst_video_overlay_set_window_handle (app_data.overlay_1, (guintptr) hwnd_1); + + gst_object_unref (sink_0); + gst_object_unref (sink_1); + + gst_bus_add_watch (GST_ELEMENT_BUS (app_data.pipeline), (GstBusFunc) bus_msg, + &app_data); + + set_key_handler ((KeyInputCallback) keyboard_cb, &app_data); + + gst_element_set_state (app_data.pipeline, GST_STATE_PLAYING); + g_main_loop_run (loop); + + gst_element_set_state (app_data.pipeline, GST_STATE_NULL); + gst_bus_remove_watch (GST_ELEMENT_BUS (app_data.pipeline)); + + gst_object_unref (app_data.pipeline); + unset_key_handler (); + + if (hwnd_0) + DestroyWindow (hwnd_0); + + if (hwnd_1) + DestroyWindow (hwnd_1); + + if (msg_io_channel) + g_io_channel_unref (msg_io_channel); + g_main_loop_unref (loop); + + gst_deinit (); + + 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 c5e6bf0890..1f51d2a702 100644 --- a/subprojects/gst-plugins-bad/tests/examples/d3d12/meson.build +++ b/subprojects/gst-plugins-bad/tests/examples/d3d12/meson.build @@ -12,7 +12,16 @@ executable('d3d12enc-dynamic-reconfigure', include_directories : [configinc], dependencies: [gst_dep, gstbase_dep, gstvideo_dep], c_args : gst_plugins_bad_args, - install: false) + install: false +) + +executable('d3d12videosink-switch', + ['d3d12videosink-switch.cpp', '../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()