gstreamer/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window-win32.cpp
Seungha Yang 37e1847464 d3d12videosink: Add support for window handle update
A large refactoring commit for adding features and improve performance

* Reuse internal converter and overlay compositor:
  Converter can be reused as long as input and display formats are not
  changed. Also overlay compositor reconstruction is required only if
  display format is changed

* Don't wait for full GPU flush on resize or close:
  D3D12 swapchain requires GPU idle in order to resize backbuffer.
  Thus CPU side waiting is required for swapchain related commands
  to be finished. However, don't need to wait for full GPU flushing.

* Support multiple sink on a single external window
  Keep installed subclass window procedure even if there's no associated
  our internal HWND. This will make window procedure hooking less racy.
  Then parent HWND's message will be transferred to our internal HWNDs
  if needed.

* Adding support for window handle update
  Application can change target HWND even when videosink is playing or
  paused state. So, users can call gst_video_overlay_set_window_handle()
  against d3d12videosink anytime. The videosink will be able to update
  internal state and setup resource upon requested.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/7013>
2024-06-17 16:05:00 +00:00

1102 lines
27 KiB
C++

/* GStreamer
* Copyright (C) 2024 Seungha Yang <seungha@centricular.com>
*
* 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 <std::recursive_mutex> 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 <std::recursive_mutex> 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 <std::recursive_mutex> 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<SwapChain> sc;
HWND hwnd = nullptr;
bool is_new_swapchain = false;
{
std::lock_guard <std::recursive_mutex> lk (lock_);
if (!hwnd_) {
GST_WARNING_OBJECT (window_, "Window was closed");
return GST_D3D12_WINDOW_FLOW_CLOSED;
}
if (!swapchain_)
swapchain_ = std::make_shared<SwapChain> (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<SwapChain>
SwapChainProxy::get_swapchain ()
{
std::lock_guard <std::recursive_mutex> 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 <std::recursive_mutex> 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<std::recursive_mutex> lk (lock_);
GST_DEBUG_OBJECT (window, "Register");
state_.insert ({window, std::make_shared<State> ()});
}
void
HwndServer::unregister_window (GstD3D12Window * window)
{
std::lock_guard<std::recursive_mutex> lk (lock_);
GST_DEBUG_OBJECT (window, "Unregister");
state_.erase (window);
}
void
HwndServer::unlock_window (GstD3D12Window * window)
{
std::lock_guard<std::recursive_mutex> lk (lock_);
auto it = state_.find (window);
if (it != state_.end ()) {
std::lock_guard<std::mutex> 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<std::recursive_mutex> lk (lock_);
auto it = state_.find (window);
if (it != state_.end ()) {
std::lock_guard<std::mutex> 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> state;
{
std::lock_guard<std::recursive_mutex> 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<std::mutex> lk (state->create_lock);
state->id++;
if (state->id == 0)
state->id++;
SIZE_T id = state->id;
state->proxy = std::make_shared<SwapChainProxy> (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> 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;
}
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), &params);
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<std::recursive_mutex> 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> 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 <std::mutex> 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> state;
{
std::lock_guard<std::recursive_mutex> lk (lock_);
auto it = state_.find (window);
state = it->second;
}
{
std::unique_lock<std::mutex> lk (state->create_lock);
state->id++;
if (state->id == 0)
state->id++;
}
SIZE_T id = state->id;
auto proxy = std::make_shared<SwapChainProxy> (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), &params);
proxy->set_window_handles (nullptr, hwnd);
{
std::lock_guard <std::mutex> slk (state->create_lock);
state->proxy = std::move (proxy);
}
return id;
}
void
HwndServer::release_proxy (GstD3D12Window * window, SIZE_T proxy_id)
{
std::shared_ptr<SwapChainProxy> proxy;
{
std::lock_guard<std::recursive_mutex> lk (lock_);
auto it = state_.find (window);
if (it == state_.end ())
return;
auto state = it->second;
{
std::lock_guard <std::mutex> 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<HWND> child_hwnds;
{
std::lock_guard <std::recursive_mutex> 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 <std::recursive_mutex> lk (lock_);
parent_hwnd_map_.erase (parent_hwnd);
}
void
HwndServer::on_proxy_destroy (GstD3D12Window * window,
SIZE_T proxy_id)
{
std::lock_guard <std::recursive_mutex> lk (lock_);
auto it = state_.find (window);
if (it != state_.end ()) {
auto state = it->second;
std::lock_guard <std::mutex> slk (state->create_lock);
if (state->proxy && state->proxy->get_id () == proxy_id) {
state->proxy = nullptr;
}
}
}
std::shared_ptr<SwapChainProxy>
HwndServer::get_proxy (GstD3D12Window * window, SIZE_T proxy_id)
{
std::shared_ptr<SwapChainProxy> ret;
{
std::lock_guard <std::recursive_mutex> lk (lock_);
auto it = state_.find (window);
if (it == state_.end ())
return nullptr;
{
std::lock_guard <std::mutex> slk (it->second->create_lock);
ret = it->second->proxy;
if (ret && ret->get_id () != proxy_id)
ret = nullptr;
}
}
return ret;
}
/* *INDENT-ON* */