gstreamer/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12window.cpp
2024-01-09 13:52:44 +00:00

1767 lines
51 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 <mutex>
#include <condition_variable>
#include <vector>
#include <memory>
#include <atomic>
#include <wrl.h>
#include <string>
/* *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
enum
{
SIGNAL_KEY_EVENT,
SIGNAL_MOUSE_EVENT,
SIGNAL_FULLSCREEN,
SIGNAL_LAST
};
static guint d3d12_window_signals[SIGNAL_LAST] = { 0, };
/* *INDENT-OFF* */
struct SwapBuffer
{
SwapBuffer (GstBuffer * buf)
{
backbuf = buf;
}
~SwapBuffer ()
{
gst_clear_buffer (&backbuf);
}
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);
D3D12_COMMAND_QUEUE_DESC queue_desc = { };
queue_desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
queue_desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
queue = gst_d3d12_command_queue_new (device,
&queue_desc, BACK_BUFFER_COUNT * 2);
if (!queue) {
GST_ERROR_OBJECT (device, "Couldn't create command queue");
return;
}
ca_pool = gst_d3d12_command_allocator_pool_new (device,
D3D12_COMMAND_LIST_TYPE_DIRECT);
if (!ca_pool) {
GST_ERROR_OBJECT (device, "Couldn't create command allocator pool");
return;
}
initialized = true;
}
~DeviceContext ()
{
WaitGpu ();
CloseHandle (event_handle);
gst_clear_object (&queue);
gst_clear_object (&ca_pool);
gst_clear_buffer (&cached_buf);
gst_clear_object (&conv);
gst_clear_object (&comp);
gst_clear_buffer (&msaa_buf);
gst_clear_object (&device);
}
void WaitGpu ()
{
if (!queue)
return;
gst_d3d12_command_queue_fence_wait (queue, G_MAXUINT64, event_handle);
}
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;
GstD3D12CommandQueue *queue = nullptr;
GstD3D12CommandAllocatorPool *ca_pool = nullptr;
UINT64 fence_val = 0;
HANDLE event_handle;
bool initialized = false;
};
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;
/* 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 ("key-event", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, 0, nullptr, nullptr, nullptr,
G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING);
d3d12_window_signals[SIGNAL_MOUSE_EVENT] =
g_signal_new ("mouse-event", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, 0, nullptr, nullptr, nullptr,
G_TYPE_NONE, 4, G_TYPE_STRING, G_TYPE_INT, G_TYPE_DOUBLE, G_TYPE_DOUBLE);
d3d12_window_signals[SIGNAL_FULLSCREEN] =
g_signal_new ("fullscreen", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, 0, nullptr, nullptr, nullptr,
G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
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;
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_RBUTTONDOWN:
button = 2;
event = "mouse-button-press";
break;
case WM_RBUTTONUP:
button = 2;
event = "mouse-button-release";
break;
case WM_MBUTTONDOWN:
button = 3;
event = "mouse-button-press";
break;
case WM_MBUTTONUP:
button = 3;
event = "mouse-button-release";
break;
default:
return;
}
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 = LOWORD (lparam);
auto ypos = HIWORD (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);
}
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:
{
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;
}
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;
if (priv->ctx->fence_val != 0)
priv->ctx->WaitGpu ();
priv->ctx->swap_buffers.clear ();
gst_clear_buffer (&priv->ctx->msaa_buf);
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 = backbuf->GetDesc ();
auto mem = gst_d3d12_allocator_alloc_wrapped (nullptr, self->device,
backbuf.Get (), 0);
auto buf = gst_buffer_new ();
gst_buffer_append_memory (buf, mem);
priv->ctx->swap_buffers.push_back (std::make_shared < SwapBuffer > (buf));
}
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);
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);
}
return ret;
}
GstFlowReturn
gst_d3d12_window_prepare (GstD3D12Window * window, GstD3D12Device * device,
guintptr window_handle, guint display_width, guint display_height,
GstCaps * caps, GstStructure * config)
{
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 (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 (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);
if (!ctx->initialized) {
GST_ERROR_OBJECT (window, "Couldn't initialize device context");
if (config)
gst_structure_free (config);
return GST_FLOW_ERROR;
}
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);
ComPtr < ID3D12CommandQueue > cq;
gst_d3d12_command_queue_get_handle (ctx->queue, &cq);
ComPtr < IDXGISwapChain1 > swapchain;
hr = factory->CreateSwapChainForHwnd (cq.Get (), 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);
}
g_object_set (priv->ctx->conv, "fill-border", swapbuf->first, nullptr);
swapbuf->first = FALSE;
gst_d3d12_overlay_compositor_upload (priv->ctx->comp, priv->ctx->cached_buf);
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;
}
ComPtr < ID3D12CommandAllocator > ca;
gst_d3d12_command_allocator_get_handle (gst_ca, &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.Get (), 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.Get (), 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 (!gst_d3d12_converter_convert_buffer (priv->ctx->conv,
priv->ctx->cached_buf, conv_outbuf, fence_data, cl.Get ())) {
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;
}
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, D3D12_RESOURCE_STATE_COMMON));
cl->ResourceBarrier (barriers.size (), barriers.data ());
} else {
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;
}
guint64 max_fence_val = 0;
auto num_mem = gst_buffer_n_memory (priv->ctx->cached_buf);
for (guint i = 0; i < num_mem; i++) {
mem = (GstD3D12Memory *) gst_buffer_peek_memory (priv->ctx->cached_buf, 0);
if (mem->fence_value > max_fence_val)
max_fence_val = mem->fence_value;
}
auto completed = gst_d3d12_device_get_completed_value (priv->ctx->device,
D3D12_COMMAND_LIST_TYPE_DIRECT);
if (completed < max_fence_val) {
auto device_queue = gst_d3d12_device_get_command_queue (priv->ctx->device,
D3D12_COMMAND_LIST_TYPE_DIRECT);
ComPtr < ID3D12Fence > fence;
gst_d3d12_command_queue_get_fence (device_queue, &fence);
gst_d3d12_command_queue_execute_wait (priv->ctx->queue, fence.Get (),
max_fence_val);
}
ID3D12CommandList *cmd_list[] = { cl.Get () };
hr = gst_d3d12_command_queue_execute_command_lists (priv->ctx->queue,
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 (priv->ctx->queue, priv->ctx->fence_val,
fence_data, (GDestroyNotify) gst_d3d12_fence_data_unref);
priv->backbuf_rendered = TRUE;
return GST_FLOW_OK;
}
GstFlowReturn
gst_d3d12_window_present (GstD3D12Window * window)
{
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 = { };
if (!priv->first_present) {
params.DirtyRectsCount = 1;
params.pDirtyRects = &priv->dirty_rect;
}
auto hr = priv->ctx->swapchain->Present1 (0,
DXGI_PRESENT_DO_NOT_WAIT, &params);
priv->first_present = FALSE;
priv->backbuf_rendered = FALSE;
if (hr != DXGI_ERROR_WAS_STILL_DRAWING &&
!gst_d3d12_result (hr, window->device))
GST_WARNING_OBJECT (window, "Present return 0x%x", (guint) hr);
}
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);
}
}