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()