gstreamer/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window.cpp
Seungha Yang 84aecab150 d3d12videosink: Add overlay signal to support d3d12/d3d11/d2d overlay
Conceptually identical to the present signal of d3d11videosink.
This signal will be emitted with current render target
(i.e., swapchain backbuffer) and command queue. Signal handler
can record GPU commands for an overlay image or to blend
an image to the render target.

In addition to d3d12 resources, videosink will send
d3d11 and d2d resources depending on "overlay-mode"
property, so that signal handler can render by using
preferred/required DirectX API.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/6838>
2024-05-24 15:55:17 +00:00

2101 lines
61 KiB
C++

/* GStreamer
* Copyright (C) 2023 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.h"
#include "gstd3d12overlaycompositor.h"
#include <directx/d3dx12.h>
#include <d3d11on12.h>
#include <d2d1_3.h>
#include <mutex>
#include <condition_variable>
#include <vector>
#include <memory>
#include <atomic>
#include <wrl.h>
#include <string>
#include <queue>
/* *INDENT-OFF* */
using namespace Microsoft::WRL;
static std::mutex global_hwnd_lock;
/* *INDENT-ON* */
GST_DEBUG_CATEGORY_STATIC (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,
SIGNAL_MOUSE_EVENT,
SIGNAL_FULLSCREEN,
SIGNAL_OVERLAY,
SIGNAL_LAST
};
static guint d3d12_window_signals[SIGNAL_LAST] = { 0, };
GType
gst_d3d12_window_overlay_mode_get_type (void)
{
static GType mode_type = 0;
GST_D3D12_CALL_ONCE_BEGIN {
static const GFlagsValue mode_types[] = {
{GST_D3D12_WINDOW_OVERLAY_NONE, "None", "none"},
{GST_D3D12_WINDOW_OVERLAY_D3D12,
"Emits present signal with Direct3D12 resources", "d3d12"},
{GST_D3D12_WINDOW_OVERLAY_D3D11,
"Emits present signal with Direct3D12/11 resources", "d3d11"},
{GST_D3D12_WINDOW_OVERLAY_D2D,
"Emit present signal with Direct3D12/11 and Direct2D resources",
"d2d"},
{0, nullptr, nullptr},
};
mode_type = g_flags_register_static ("GstD3D12WindowOverlayMode",
mode_types);
} GST_D3D12_CALL_ONCE_END;
return mode_type;
}
/* *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<ID2D1Bitmap1> d2d_target;
ComPtr<ID3D11Texture2D> wrapped_resource;
ComPtr<ID3D12Resource> 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<IDXGIDevice> 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<SwapBuffer> 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<SwapBuffer> swapbuf)
{
if (swapbuf->d2d_target)
return true;
if (!EnsureD2D ())
return false;
if (!EnsureD3D11RenderTarget (swapbuf))
return false;
GstD3D12Device11on12LockGuard lk (device);
ComPtr<IDXGISurface> 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<ID3D12GraphicsCommandList> cl;
ComPtr<IDXGISwapChain4> swapchain;
GstBuffer *msaa_buf = nullptr;
std::vector<std::shared_ptr<SwapBuffer>> 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<ID3D11On12Device> device11on12;
ComPtr<ID3D11Device> device11;
ComPtr<ID3D11DeviceContext> context11;
ComPtr<ID2D1Factory3> factory2d;
ComPtr<ID2D1Device2> device2d;
ComPtr<ID2D1DeviceContext2> context2d;
UINT64 fence_val = 0;
std::queue<UINT64> prev_fence_val;
HANDLE event_handle = nullptr;
};
struct GstD3D12WindowPrivate
{
GstD3D12WindowPrivate ()
{
main_context = g_main_context_new ();
loop = g_main_loop_new (main_context, FALSE);
render_rect.w = -1;
render_rect.h = -1;
gst_video_info_init (&input_info);
gst_video_info_init (&display_info);
fence_data_pool = gst_d3d12_fence_data_pool_new ();
}
~GstD3D12WindowPrivate ()
{
g_main_loop_unref (loop);
g_main_context_unref (main_context);
gst_clear_object (&fence_data_pool);
}
std::recursive_mutex lock;
DXGI_FORMAT display_format = DXGI_FORMAT_R8G8B8A8_UNORM;
GstVideoOrientationMethod orientation = GST_VIDEO_ORIENTATION_IDENTITY;
gfloat fov = 90.0f;
gboolean ortho = FALSE;
gfloat rotation_x = 0;
gfloat rotation_y = 0;
gfloat rotation_z = 0;
gfloat scale_x = 1.0f;
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;
GstD3D12FenceDataPool *fence_data_pool;
/* User specified rect */
GstVideoRectangle render_rect = { };
GstVideoRectangle output_rect = { };
RECT dirty_rect = { };
GstVideoInfo input_info;
GstVideoInfo display_info;
D3D12_BOX crop_rect = { };
D3D12_BOX prev_crop_rect = { };
gboolean force_aspect_ratio = TRUE;
std::atomic<gboolean> enable_navigation = { TRUE };
gboolean first_present = TRUE;
gboolean backbuf_rendered = FALSE;
std::unique_ptr<DeviceContext> ctx;
std::wstring title;
gboolean update_title = FALSE;
GstD3D12MSAAMode msaa = GST_D3D12_MSAA_DISABLED;
GstD3D12WindowOverlayMode 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<GstD3D12WindowState> 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;
};
/* *INDENT-ON* */
struct _GstD3D12Window
{
GstObject parent;
GstD3D12Device *device;
GstD3D12WindowPrivate *priv;
};
static GstFlowReturn gst_d3d12_window_on_resize (GstD3D12Window * self);
#define gst_d3d12_window_parent_class parent_class
G_DEFINE_TYPE (GstD3D12Window, gst_d3d12_window, GST_TYPE_OBJECT);
static void gst_d3d12_window_finalize (GObject * object);
static void
gst_d3d12_window_class_init (GstD3D12WindowClass * klass)
{
auto object_class = G_OBJECT_CLASS (klass);
object_class->finalize = gst_d3d12_window_finalize;
d3d12_window_signals[SIGNAL_KEY_EVENT] =
g_signal_new_class_handler ("key-event", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, nullptr, nullptr, nullptr, nullptr,
G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING);
d3d12_window_signals[SIGNAL_MOUSE_EVENT] =
g_signal_new_class_handler ("mouse-event", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, nullptr, nullptr, nullptr, nullptr,
G_TYPE_NONE, 5, G_TYPE_STRING, G_TYPE_INT, G_TYPE_DOUBLE, G_TYPE_DOUBLE,
G_TYPE_UINT);
d3d12_window_signals[SIGNAL_FULLSCREEN] =
g_signal_new_class_handler ("fullscreen", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, nullptr, nullptr, nullptr, nullptr,
G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
d3d12_window_signals[SIGNAL_OVERLAY] =
g_signal_new_class_handler ("overlay", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, nullptr, nullptr, nullptr, nullptr,
G_TYPE_NONE, 6, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_POINTER,
G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_POINTER);
GST_DEBUG_CATEGORY_INIT (gst_d3d12_window_debug,
"d3d12window", 0, "d3d12window");
}
static void
gst_d3d12_window_init (GstD3D12Window * self)
{
self->priv = new GstD3D12WindowPrivate ();
}
static void
gst_d3d12_window_finalize (GObject * object)
{
auto self = GST_D3D12_WINDOW (object);
delete self->priv;
gst_clear_object (&self->device);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gst_d3d12_window_on_key_event (GstD3D12Window * self,
UINT msg, WPARAM wparam, LPARAM lparam)
{
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);
}
static void
gst_d3d12_window_on_mouse_event (GstD3D12Window * self, UINT msg, WPARAM wparam,
LPARAM lparam)
{
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);
}
static gboolean
msg_io_cb (GIOChannel * source, GIOCondition condition, gpointer data)
{
MSG msg;
if (!PeekMessage (&msg, nullptr, 0, 0, PM_REMOVE))
return G_SOURCE_CONTINUE;
TranslateMessage (&msg);
DispatchMessage (&msg);
return G_SOURCE_CONTINUE;
}
static gpointer
gst_d3d12_window_hwnd_thread_func (GstD3D12Window * self)
{
auto priv = self->priv;
g_main_context_push_thread_default (priv->main_context);
gst_d3d12_window_create_hwnd (self);
priv->state = GST_D3D12_WINDOW_STATE_OPENED;
auto msg_io_ch = g_io_channel_win32_new_messages (0);
auto msg_source = g_io_create_watch (msg_io_ch, G_IO_IN);
g_source_set_callback (msg_source, (GSourceFunc) msg_io_cb, nullptr, nullptr);
g_source_attach (msg_source, priv->main_context);
auto idle_source = g_idle_source_new ();
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 ();
return G_SOURCE_REMOVE;
}
, self, nullptr);
g_source_attach (idle_source, priv->main_context);
g_source_unref (idle_source);
g_main_loop_run (priv->loop);
g_clear_pointer (&priv->hwnd, DestroyWindow);
g_source_destroy (msg_source);
g_source_unref (msg_source);
g_io_channel_unref (msg_io_ch);
g_main_context_pop_thread_default (priv->main_context);
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");
auto priv = window->priv;
{
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);
}
}
}
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;
}
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 ();
}
void
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 ();
}
static GstFlowReturn
gst_d3d12_window_on_resize (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)
return GST_FLOW_OK;
priv->ctx->ReleaseSizeDependentResources ();
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;
}
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);
}
return ret;
}
GstFlowReturn
gst_d3d12_window_prepare (GstD3D12Window * window, GstD3D12Device * device,
guintptr window_handle, 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;
gst_video_info_from_caps (&in_info, caps);
if (display_format != DXGI_FORMAT_UNKNOWN) {
priv->display_format = display_format;
format = gst_d3d12_dxgi_format_to_gst (display_format);
} else if (GST_VIDEO_INFO_COMP_DEPTH (&in_info, 0) > 8) {
auto device_handle = gst_d3d12_device_get_device_handle (device);
D3D12_FEATURE_DATA_FORMAT_SUPPORT format_support = { };
const D3D12_FORMAT_SUPPORT1 support_flags =
D3D12_FORMAT_SUPPORT1_RENDER_TARGET | D3D12_FORMAT_SUPPORT1_DISPLAY;
format_support.Format = DXGI_FORMAT_R10G10B10A2_UNORM;
format_support.Support1 = support_flags;
auto hr = device_handle->CheckFeatureSupport (D3D12_FEATURE_FORMAT_SUPPORT,
&format_support, sizeof (format_support));
if (SUCCEEDED (hr) && (format_support.Support1 & support_flags)
== support_flags) {
priv->display_format = DXGI_FORMAT_R10G10B10A2_UNORM;
format = GST_VIDEO_FORMAT_RGB10A2_LE;
}
}
{
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;
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");
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;
}
GST_ERROR_OBJECT (window, "Swapchain was not configured");
return GST_FLOW_ERROR;
}
if (buffer)
gst_buffer_replace (&priv->ctx->cached_buf, buffer);
if (!priv->ctx->cached_buf)
return GST_FLOW_OK;
auto cur_idx = priv->ctx->swapchain->GetCurrentBackBufferIndex ();
auto swapbuf = priv->ctx->swap_buffers[cur_idx];
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);
}
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;
}
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);
}
guint64 overlay_fence_val = 0;
gst_d3d12_overlay_compositor_upload (priv->ctx->comp, priv->ctx->cached_buf,
&overlay_fence_val);
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");
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");
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,
ca, nullptr, IID_PPV_ARGS (&cl));
if (!gst_d3d12_result (hr, priv->ctx->device)) {
GST_ERROR_OBJECT (window, "Couldn't create command list");
gst_d3d12_command_allocator_unref (gst_ca);
return GST_FLOW_ERROR;
}
priv->ctx->cl = cl;
} else {
cl = priv->ctx->cl;
hr = cl->Reset (ca, nullptr);
if (!gst_d3d12_result (hr, priv->ctx->device)) {
GST_ERROR_OBJECT (window, "Couldn't reset command list");
gst_d3d12_command_allocator_unref (gst_ca);
return GST_FLOW_ERROR;
}
}
GstD3D12FenceData *fence_data;
gst_d3d12_fence_data_pool_acquire (priv->fence_data_pool, &fence_data);
gst_d3d12_fence_data_add_notify_mini_object (fence_data, gst_ca);
auto mem = (GstD3D12Memory *) gst_buffer_peek_memory (swapbuf->backbuf, 0);
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);
msaa_resource = gst_d3d12_memory_get_resource_handle (mem);
/* MSAA resource must be render target state here already */
} else {
D3D12_RESOURCE_BARRIER barrier =
CD3DX12_RESOURCE_BARRIER::Transition (backbuf_texture,
D3D12_RESOURCE_STATE_COMMON,
D3D12_RESOURCE_STATE_RENDER_TARGET);
cl->ResourceBarrier (1, &barrier);
}
if (swapbuf->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;
auto cq = gst_d3d12_device_get_command_queue (priv->ctx->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");
gst_d3d12_fence_data_unref (fence_data);
return GST_FLOW_ERROR;
}
if (!gst_d3d12_overlay_compositor_draw (priv->ctx->comp,
conv_outbuf, fence_data, cl.Get ())) {
GST_ERROR_OBJECT (window, "Couldn't build overlay command");
gst_d3d12_fence_data_unref (fence_data);
return GST_FLOW_ERROR;
}
D3D12_RESOURCE_STATES state_after = D3D12_RESOURCE_STATE_COMMON;
GstD3D12WindowOverlayMode selected_overlay_mode =
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) {
selected_overlay_mode |= GST_D3D12_WINDOW_OVERLAY_D3D12;
state_after = D3D12_RESOURCE_STATE_RENDER_TARGET;
}
if ((priv->overlay_mode & GST_D3D12_WINDOW_OVERLAY_D3D11) ==
GST_D3D12_WINDOW_OVERLAY_D3D11 &&
priv->ctx->EnsureD3D11RenderTarget (swapbuf)) {
selected_overlay_mode |= GST_D3D12_WINDOW_OVERLAY_D3D11;
signal_with_lock = true;
}
if ((priv->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)) {
selected_overlay_mode |= GST_D3D12_WINDOW_OVERLAY_D2D;
set_d2d_target = true;
}
if (msaa_resource) {
std::vector < D3D12_RESOURCE_BARRIER > barriers;
barriers.push_back (CD3DX12_RESOURCE_BARRIER::Transition (msaa_resource,
D3D12_RESOURCE_STATE_RENDER_TARGET,
D3D12_RESOURCE_STATE_RESOLVE_SOURCE));
barriers.push_back (CD3DX12_RESOURCE_BARRIER::Transition (backbuf_texture,
D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_RESOLVE_DEST));
cl->ResourceBarrier (barriers.size (), barriers.data ());
cl->ResolveSubresource (backbuf_texture, 0, msaa_resource, 0,
priv->display_format);
barriers.clear ();
barriers.push_back (CD3DX12_RESOURCE_BARRIER::Transition (msaa_resource,
D3D12_RESOURCE_STATE_RESOLVE_SOURCE,
D3D12_RESOURCE_STATE_RENDER_TARGET));
barriers.push_back (CD3DX12_RESOURCE_BARRIER::Transition (backbuf_texture,
D3D12_RESOURCE_STATE_RESOLVE_DEST, state_after));
cl->ResourceBarrier (barriers.size (), barriers.data ());
} else if (state_after == D3D12_RESOURCE_STATE_COMMON) {
D3D12_RESOURCE_BARRIER barrier =
CD3DX12_RESOURCE_BARRIER::Transition (backbuf_texture,
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");
gst_d3d12_fence_data_unref (fence_data);
return GST_FLOW_ERROR;
}
if (!priv->ctx->BeforeRendering ()) {
GST_ERROR_OBJECT (window, "Couldn't wait fence value");
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");
gst_d3d12_fence_data_unref (fence_data);
return GST_FLOW_ERROR;
}
gst_d3d12_command_queue_set_notify (cq, priv->ctx->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);
if (set_d2d_target)
priv->ctx->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 (),
&viewport);
if (signal_with_lock)
gst_d3d12_device_11on12_unlock (window->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");
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");
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");
gst_d3d12_command_allocator_unref (gst_ca);
return GST_FLOW_ERROR;
}
gst_d3d12_fence_data_pool_acquire (priv->fence_data_pool, &fence_data);
gst_d3d12_fence_data_add_notify_mini_object (fence_data, gst_ca);
D3D12_RESOURCE_BARRIER barrier =
CD3DX12_RESOURCE_BARRIER::Transition (backbuf_texture,
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");
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");
gst_d3d12_fence_data_unref (fence_data);
return GST_FLOW_ERROR;
}
gst_d3d12_command_queue_set_notify (cq, priv->ctx->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)
{
auto priv = window->priv;
if (priv->state != GST_D3D12_WINDOW_STATE_OPENED) {
GST_ERROR_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;
}
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, &params);
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;
}
guintptr
gst_d3d12_window_get_window_handle (GstD3D12Window * window)
{
auto priv = window->priv;
return (guintptr) priv->hwnd;
}
void
gst_d3d12_window_set_render_rect (GstD3D12Window * window,
const GstVideoRectangle * rect)
{
auto priv = window->priv;
{
std::lock_guard < std::mutex > lk (priv->hwnd_lock);
priv->render_rect = *rect;
}
if (priv->hwnd) {
PostMessageW (priv->hwnd, WM_GST_D3D12_UPDATE_RENDER_RECT, 0, 0);
return;
}
}
void
gst_d3d12_window_set_force_aspect_ratio (GstD3D12Window * window,
gboolean force_aspect_ratio)
{
auto priv = window->priv;
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);
}
}
void
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;
}
void
gst_d3d12_window_set_orientation (GstD3D12Window * window, gboolean immediate,
GstVideoOrientationMethod orientation, gfloat fov, gboolean ortho,
gfloat rotation_x, gfloat rotation_y, gfloat rotation_z,
gfloat scale_x, gfloat scale_y)
{
auto priv = window->priv;
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);
}
}
void
gst_d3d12_window_set_title (GstD3D12Window * window, const gchar * title)
{
auto priv = window->priv;
wchar_t *wtitle = nullptr;
if (title)
wtitle = (wchar_t *) g_utf8_to_utf16 (title, -1, nullptr, nullptr, nullptr);
std::lock_guard < std::mutex > lk (priv->hwnd_lock);
if (!wtitle) {
priv->title.clear ();
} else {
priv->title = wtitle;
}
g_free (wtitle);
}
GstD3D12Window *
gst_d3d12_window_new (void)
{
auto self = (GstD3D12Window *) g_object_new (GST_TYPE_D3D12_WINDOW, nullptr);
gst_object_ref_sink (self);
return self;
}
GstD3D12WindowState
gst_d3d12_window_get_state (GstD3D12Window * window)
{
auto priv = window->priv;
return priv->state;
}
void
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);
priv->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);
priv->requested_fullscreen = enable;
if (priv->hwnd && priv->applied_fullscreen != priv->requested_fullscreen)
PostMessageW (priv->hwnd, WM_GST_D3D12_FULLSCREEN, 0, 0);
}
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);
}
}
void
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;
}