diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12videosink.cpp b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12videosink.cpp index 7fc09d93e1..f2b240e424 100644 --- a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12videosink.cpp +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12videosink.cpp @@ -108,6 +108,7 @@ enum PROP_DISPLAY_FORMAT, PROP_ERROR_ON_CLOSED, PROP_EXTERNAL_WINDOW_ONLY, + PROP_DIRECT_SWAPCHAIN, }; #define DEFAULT_ADAPTER -1 @@ -129,6 +130,7 @@ enum #define DEFAULT_DISPLAY_FORMAT DXGI_FORMAT_UNKNOWN #define DEFAULT_ERROR_ON_CLOSED TRUE #define DEFAULT_EXTERNAL_WINDOW_ONLY FALSE +#define DEFAULT_DIRECT_SWAPCHAIN FALSE enum { @@ -216,6 +218,7 @@ struct GstD3D12VideoSinkPrivate DXGI_FORMAT display_format = DEFAULT_DISPLAY_FORMAT; std::atomic error_on_closed = { DEFAULT_ERROR_ON_CLOSED }; gboolean external_only = DEFAULT_EXTERNAL_WINDOW_ONLY; + std::atomic direct_swapchain = { DEFAULT_DIRECT_SWAPCHAIN }; }; /* *INDENT-ON* */ @@ -460,6 +463,24 @@ gst_d3d12_video_sink_class_init (GstD3D12VideoSinkClass * klass) DEFAULT_EXTERNAL_WINDOW_ONLY, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + /** + * GstD3D12VideoSink:direct-swapchain: + * + * Attach DXGI swapchain to external window handle directly, instead of + * creating child window. Note that once direct swapchain is configured, + * GDI will no longer work with the given window handle. + * + * If enabled, GstVideoOverlay::set_render_rectangle() will be ignored, + * and application should handle window positioning. + * + * Since: 1.26 + */ + g_object_class_install_property (object_class, PROP_DIRECT_SWAPCHAIN, + g_param_spec_boolean ("direct-swapchain", "Direct Swapchain", + "Attach DXGI swapchain to external window handle directly", + DEFAULT_DIRECT_SWAPCHAIN, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + /** * GstD3D12VideoSink::overlay: * @d3d12videosink: the d3d12videosink element that emitted the signal @@ -695,6 +716,9 @@ gst_d3d12_video_sink_set_property (GObject * object, guint prop_id, case PROP_EXTERNAL_WINDOW_ONLY: priv->external_only = g_value_get_boolean (value); break; + case PROP_DIRECT_SWAPCHAIN: + priv->direct_swapchain = g_value_get_boolean (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -776,6 +800,9 @@ gst_d3d12_video_sink_get_property (GObject * object, guint prop_id, case PROP_EXTERNAL_WINDOW_ONLY: g_value_set_boolean (value, priv->external_only); break; + case PROP_DIRECT_SWAPCHAIN: + g_value_set_boolean (value, priv->direct_swapchain); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1409,7 +1436,7 @@ gst_d3d12_video_sink_open_window (GstD3D12VideoSink * self) 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); + (HWND) window_handle, priv->direct_swapchain); std::lock_guard < std::recursive_mutex > lk (priv->lock); if (ret == GST_FLOW_OK) { diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window-swapchain.cpp b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window-swapchain.cpp index 379f4466f7..e5211bb69b 100644 --- a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window-swapchain.cpp +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window-swapchain.cpp @@ -246,6 +246,19 @@ SwapChain::~SwapChain() lock_.unlock (); } +static inline bool +is_expected_error (HRESULT hr) +{ + switch (hr) { + case DXGI_ERROR_INVALID_CALL: + case E_ACCESSDENIED: + return true; + break; + } + + return false; +} + GstFlowReturn SwapChain::setup_swapchain (GstD3D12Window * window, GstD3D12Device * device, HWND hwnd, DXGI_FORMAT format, const GstVideoInfo * in_info, @@ -278,8 +291,16 @@ SwapChain::setup_swapchain (GstD3D12Window * window, GstD3D12Device * device, ComPtr < IDXGISwapChain1 > swapchain; auto hr = factory->CreateSwapChainForHwnd (cq_handle, hwnd, &desc, nullptr, nullptr, &swapchain); - if (!gst_d3d12_result (hr, device)) + if (FAILED (hr)) { + if (is_expected_error (hr)) { + GST_WARNING_OBJECT (window, + "Expected error 0x%x, maybe window is being closed", (guint) hr); + return GST_D3D12_WINDOW_FLOW_CLOSED; + } + + gst_d3d12_result (hr, device); return GST_FLOW_ERROR; + } hr = swapchain.As (&resource_->swapchain); if (!gst_d3d12_result (hr, device)) @@ -370,8 +391,16 @@ SwapChain::resize_buffer (GstD3D12Window * window) resource_->swapchain->GetDesc (&desc); auto hr = resource_->swapchain->ResizeBuffers (BACK_BUFFER_COUNT, 0, 0, render_format_, desc.Flags); - if (!gst_d3d12_result (hr, device)) + if (FAILED (hr)) { + if (is_expected_error (hr)) { + GST_WARNING_OBJECT (window, + "Expected error 0x%x, maybe window is being closed", (guint) hr); + return GST_D3D12_WINDOW_FLOW_CLOSED; + } + + gst_d3d12_result (hr, device); return GST_FLOW_ERROR; + } for (guint i = 0; i < BACK_BUFFER_COUNT; i++) { ComPtr < ID3D12Resource > backbuf; @@ -534,10 +563,13 @@ SwapChain::present () 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; + case DXGI_ERROR_INVALID_CALL: + case E_ACCESSDENIED: + GST_WARNING ("Present failed, hr: 0x%x", (guint) hr); + return GST_D3D12_WINDOW_FLOW_CLOSED; default: /* Ignore other return code */ break; diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window-win32.cpp b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window-win32.cpp index 744c2a1fa1..27a6db3937 100644 --- a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window-win32.cpp +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window-win32.cpp @@ -30,7 +30,7 @@ GST_DEBUG_CATEGORY_EXTERN (gst_d3d12_window_debug); #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_CREATE_PROXY (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) @@ -59,7 +59,7 @@ SwapChainProxy::~SwapChainProxy () GST_DEBUG_OBJECT (window_, "Destroying proxy %" G_GSIZE_FORMAT, id_); swapchain_ = nullptr; - if (window_thread_ && hwnd_) { + if (window_thread_ && hwnd_ && hwnd_ != parent_hwnd_) { if (window_thread_ == g_thread_self ()) DestroyWindow (hwnd_); else @@ -89,6 +89,12 @@ SwapChainProxy::get_id () return id_; } +GstD3D12Window * +SwapChainProxy::get_window () +{ + return window_; +} + bool SwapChainProxy::has_parent () { @@ -137,7 +143,7 @@ SwapChainProxy::update_render_rect () bool send_msg = false; { std::lock_guard lk (lock_); - if (!hwnd_) + if (!hwnd_ || hwnd_ == parent_hwnd_) return; if (window_thread_ == g_thread_self ()) @@ -267,7 +273,7 @@ SwapChainProxy::handle_mouse_event (UINT msg, WPARAM wparam, LPARAM lparam) auto xpos = GET_X_LPARAM (lparam); auto ypos = GET_Y_LPARAM (lparam); - if (parent_hwnd_) { + if (parent_hwnd_ && parent_hwnd_ != hwnd_) { POINT updated_pos; updated_pos.x = xpos; updated_pos.y = ypos; @@ -477,13 +483,47 @@ SwapChainProxy::handle_swapchain_created () swapchain_->disable_alt_enter (hwnd_); } +void +SwapChainProxy::handle_position_changed (INT width, INT height) +{ + { + std::lock_guard lk (lock_); + if (!hwnd_ || !swapchain_) + return; + + if (width != width_ || height != height_) { + width_ = width; + height_ = height; + } else { + return; + } + } + + auto sc = get_swapchain (); + if (sc) + sc->resize_buffer (window_); +} + +void +SwapChainProxy::release_swapchin () +{ + std::lock_guard lk (lock_); + swapchain_ = nullptr; +} + GstFlowReturn -SwapChainProxy::resize_buffer () +SwapChainProxy::resize_buffer (INT width, INT height) { auto sc = get_swapchain (); if (!sc) return GST_FLOW_OK; + if (width > 0 && height > 0) { + std::lock_guard lk (lock_); + width_ = width; + height_ = height; + } + return sc->resize_buffer (window_); } @@ -570,10 +610,69 @@ parent_wnd_proc (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) server->create_child_hwnd_finish ((GstD3D12Window *) lparam, hwnd, (SIZE_T) wparam); return 0; + } else if (msg == WM_GST_D3D12_CREATE_PROXY) { + server->create_proxy_finish ((GstD3D12Window *) lparam, hwnd, + (SIZE_T) wparam); + return 0; } server->forward_parent_message (hwnd, msg, wparam, lparam); + auto direct_proxy = server->get_direct_proxy (hwnd); + switch (msg) { + case WM_SIZE: + { + auto dproxy = server->get_direct_proxy (hwnd); + if (dproxy) + dproxy->resize_buffer (LOWORD (lparam), HIWORD (lparam)); + break; + } + case WM_WINDOWPOSCHANGED: + { + WINDOWPOS *pos = (WINDOWPOS *) lparam; + if ((pos->flags & SWP_HIDEWINDOW) == 0) { + INT width = pos->cx; + INT height = pos->cy; + if ((pos->flags & SWP_NOSIZE) != 0) { + RECT rect = { }; + GetClientRect (hwnd, &rect); + width = rect.right - rect.left; + height = rect.bottom - rect.top; + } + auto dproxy = server->get_direct_proxy (hwnd); + if (dproxy) + dproxy->handle_position_changed (width, height); + } + break; + } + case WM_KEYDOWN: + case WM_KEYUP: + { + auto dproxy = server->get_direct_proxy (hwnd); + if (dproxy) + dproxy->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_direct_proxy (hwnd); + if (proxy) + proxy->handle_mouse_event (msg, wparam, lparam); + break; + } + default: + break; + } + if (msg == WM_DESTROY) { GST_INFO ("Parent HWND %p is being destroyed", hwnd); server->on_parent_destroy (hwnd); @@ -699,7 +798,7 @@ internal_wnd_proc (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { auto proxy = server->get_proxy (window, id); if (proxy) - proxy->resize_buffer (); + proxy->resize_buffer (0, 0); break; } case WM_SYSKEYDOWN: @@ -753,7 +852,7 @@ register_window_class () GstFlowReturn HwndServer::create_child_hwnd (GstD3D12Window * window, HWND parent_hwnd, - SIZE_T & proxy_id) + gboolean direct_swapchain, SIZE_T & proxy_id) { proxy_id = 0; if (!IsWindow (parent_hwnd)) { @@ -781,6 +880,31 @@ HwndServer::create_child_hwnd (GstD3D12Window * window, HWND parent_hwnd, "subclass proc installed for hwnd %p", parent_hwnd); } + /* Cannot attach multiple swapchain to a single HWND. + * release swapchain if needed */ + if (direct_swapchain) { + for (auto it : state_) { + auto state = it.second; + if (state) { + auto proxy = state->proxy; + if (proxy && proxy->get_window_handle () == parent_hwnd) { + proxy->release_swapchin (); + std::unique_lock lk (state->create_lock); + state->proxy = nullptr; + } + } + } + + auto it = direct_proxy_map_.find (parent_hwnd); + if (it != direct_proxy_map_.end ()) { + auto proxy = it->second.lock (); + if (proxy) + proxy->release_swapchin (); + } + + direct_proxy_map_.erase (parent_hwnd); + } + auto it = state_.find (window); state = it->second; } @@ -800,7 +924,8 @@ HwndServer::create_child_hwnd (GstD3D12Window * window, HWND parent_hwnd, } state->create_state = CreateState::Waiting; - if (!PostMessageW (parent_hwnd, WM_GST_D3D12_ATTACH_INTERNAL_WINDOW, + if (!PostMessageW (parent_hwnd, direct_swapchain ? + WM_GST_D3D12_CREATE_PROXY: WM_GST_D3D12_ATTACH_INTERNAL_WINDOW, (WPARAM) id, (LPARAM) window)) { GST_WARNING_OBJECT (window, "Couldn't post message"); state->create_state = CreateState::None; @@ -912,6 +1037,43 @@ HwndServer::create_child_hwnd_finish (GstD3D12Window * window, } } +void +HwndServer::create_proxy_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; + } + + direct_proxy_map_.insert ({parent_hwnd, proxy}); + + { + std::lock_guard lk (state->create_lock); + proxy->set_window_handles (parent_hwnd, parent_hwnd); + state->create_state = CreateState::Opened; + state->create_cond.notify_all (); + } +} + SIZE_T HwndServer::create_internal_window (GstD3D12Window * window) { @@ -1006,6 +1168,15 @@ HwndServer::release_proxy (GstD3D12Window * window, SIZE_T proxy_id) state->proxy = nullptr; } } + + auto dit = direct_proxy_map_.begin (); + while (dit != direct_proxy_map_.end ()) { + auto proxy = dit->second.lock (); + if (!proxy || proxy->get_window () == window) + dit = direct_proxy_map_.erase (dit); + else + dit++; + } } } @@ -1061,6 +1232,18 @@ HwndServer::on_parent_destroy (HWND parent_hwnd) { std::lock_guard lk (lock_); parent_hwnd_map_.erase (parent_hwnd); + direct_proxy_map_.erase (parent_hwnd); + for (auto it : state_) { + auto state = it.second; + if (state) { + auto proxy = state->proxy; + if (proxy && proxy->get_window_handle () == parent_hwnd) { + proxy->release_swapchin (); + std::unique_lock lk (state->create_lock); + state->proxy = nullptr; + } + } + } } void @@ -1099,4 +1282,16 @@ HwndServer::get_proxy (GstD3D12Window * window, SIZE_T proxy_id) return ret; } + +std::shared_ptr +HwndServer::get_direct_proxy (HWND parent_hwnd) +{ + std::lock_guard lk (lock_); + auto it = direct_proxy_map_.find (parent_hwnd); + if (it == direct_proxy_map_.end ()) + return nullptr; + + return it->second.lock (); +} + /* *INDENT-ON* */ diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window-win32.h b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window-win32.h index 20fc7e0272..85a3e81f65 100644 --- a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window-win32.h +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window-win32.h @@ -49,6 +49,7 @@ public: void set_window_handles (HWND parent_hwnd, HWND child_hwnd); HWND get_window_handle (); SIZE_T get_id (); + GstD3D12Window *get_window (); bool has_parent (); void on_destroy (); void set_fullscreen_on_alt_enter (bool enable); @@ -60,10 +61,12 @@ public: void handle_key_event (UINT msg, WPARAM wparam, LPARAM lparam); void handle_mouse_event (UINT msg, WPARAM wparam, LPARAM lparam); void handle_swapchain_created (); + void handle_position_changed (INT width, INT height); + void release_swapchin (); GstFlowReturn setup_swapchain (GstD3D12Device * device, DXGI_FORMAT format, const GstVideoInfo * in_info, const GstVideoInfo * out_info, GstStructure * conv_config); - GstFlowReturn resize_buffer (); + GstFlowReturn resize_buffer (INT width, INT height); GstFlowReturn set_buffer (GstBuffer * buffer); GstFlowReturn present (); @@ -78,6 +81,8 @@ private: GThread *window_thread_ = nullptr; FullscreenState fstate_; std::shared_ptr swapchain_; + INT width_ = 0; + INT height_ = 0; std::recursive_mutex lock_; }; @@ -104,10 +109,12 @@ public: void unlock_stop_window (GstD3D12Window * window); GstFlowReturn create_child_hwnd (GstD3D12Window * window, - HWND parent_hwnd, SIZE_T & proxy_id); + HWND parent_hwnd, gboolean direct_swapchain, SIZE_T & proxy_id); void create_child_hwnd_finish (GstD3D12Window * window, HWND parent_hwnd, SIZE_T proxy_id); + void create_proxy_finish (GstD3D12Window * window, + HWND parent_hwnd, SIZE_T proxy_id); SIZE_T create_internal_window (GstD3D12Window * window); @@ -122,6 +129,7 @@ public: std::shared_ptr get_proxy (GstD3D12Window * window, SIZE_T proxy_id); + std::shared_ptr get_direct_proxy (HWND parent_hwnd); private: enum CreateState @@ -147,5 +155,6 @@ private: std::recursive_mutex lock_; std::unordered_map> state_; std::unordered_map> parent_hwnd_map_; + std::unordered_map> direct_proxy_map_; }; diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window.cpp b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window.cpp index 64652440df..c20a9979bb 100644 --- a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window.cpp +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window.cpp @@ -335,12 +335,13 @@ gst_d3d12_window_resize_buffer (GstD3D12Window * self) if (!proxy) return GST_FLOW_OK; - return proxy->resize_buffer (); + return proxy->resize_buffer (0, 0); } GstFlowReturn gst_d3d12_window_open (GstD3D12Window * window, GstD3D12Device * device, - guint display_width, guint display_height, HWND parent_hwnd) + guint display_width, guint display_height, HWND parent_hwnd, + gboolean direct_swapchain) { auto priv = window->priv; auto server = HwndServer::get_instance (); @@ -363,7 +364,8 @@ gst_d3d12_window_open (GstD3D12Window * window, GstD3D12Device * device, return GST_FLOW_OK; } - auto ret = server->create_child_hwnd (window, parent_hwnd, priv->proxy_id); + auto ret = server->create_child_hwnd (window, parent_hwnd, + direct_swapchain, priv->proxy_id); if (ret == GST_FLOW_OK) priv->proxy = server->get_proxy (window, priv->proxy_id); diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window.h b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window.h index 2de52c5fa1..959cb8b848 100644 --- a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window.h +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window.h @@ -59,7 +59,8 @@ GstFlowReturn gst_d3d12_window_open (GstD3D12Window * window, GstD3D12Device * device, guint display_width, guint display_height, - HWND parent_hwnd); + HWND parent_hwnd, + gboolean direct_swapchain); GstFlowReturn gst_d3d12_window_prepare (GstD3D12Window * window, GstD3D12Device * device,