d3d12videosink: Add direct-swapchain property

Because DXGI flip mode swapchain will disallow GDI operation
to a HWND once swapchain is configured, videosink has been creating
child window of application's window. However, since window creation
would take a few milliseconds, it can cause performance issue such as
UI freezing. Adding a property so that videosink can attach
DXGI swapchain diretly to application's window in order to improve
performance.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/7013>
This commit is contained in:
Seungha Yang 2024-06-16 21:21:44 +09:00 committed by GStreamer Marge Bot
parent dd1d2f5ff7
commit e5d2dd8e8d
6 changed files with 284 additions and 18 deletions

View file

@ -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<gboolean> error_on_closed = { DEFAULT_ERROR_ON_CLOSED };
gboolean external_only = DEFAULT_EXTERNAL_WINDOW_ONLY;
std::atomic<gboolean> 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) {

View file

@ -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;

View file

@ -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 <std::recursive_mutex> 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 <std::recursive_mutex> 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 <std::recursive_mutex> 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 <std::recursive_mutex> 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<std::mutex> 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> state;
std::shared_ptr<SwapChainProxy> proxy;
std::lock_guard<std::recursive_mutex> 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 <std::mutex> 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 <std::recursive_mutex> 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<std::mutex> 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<SwapChainProxy>
HwndServer::get_direct_proxy (HWND parent_hwnd)
{
std::lock_guard <std::recursive_mutex> lk (lock_);
auto it = direct_proxy_map_.find (parent_hwnd);
if (it == direct_proxy_map_.end ())
return nullptr;
return it->second.lock ();
}
/* *INDENT-ON* */

View file

@ -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> 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<SwapChainProxy> get_proxy (GstD3D12Window * window,
SIZE_T proxy_id);
std::shared_ptr<SwapChainProxy> get_direct_proxy (HWND parent_hwnd);
private:
enum CreateState
@ -147,5 +155,6 @@ private:
std::recursive_mutex lock_;
std::unordered_map<GstD3D12Window *, std::shared_ptr<State>> state_;
std::unordered_map<HWND, std::vector<HWND>> parent_hwnd_map_;
std::unordered_map<HWND, std::weak_ptr<SwapChainProxy>> direct_proxy_map_;
};

View file

@ -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);

View file

@ -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,