mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-18 07:47:17 +00:00
72237d2563
Adding "msaa" property to support MSAA rendering Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/5891>
1727 lines
49 KiB
C++
1727 lines
49 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;
|
|
|
|
/* 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)
|
|
{
|
|
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");
|
|
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");
|
|
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");
|
|
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");
|
|
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);
|
|
|
|
GstStructure *config = nullptr;
|
|
if (GST_VIDEO_INFO_HAS_ALPHA (&in_info)) {
|
|
config = gst_structure_new ("converter-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 (buffer);
|
|
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,
|
|
"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 (fence_data, gst_ca,
|
|
(GDestroyNotify) gst_d3d12_command_allocator_unref);
|
|
|
|
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, ¶ms);
|
|
|
|
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,
|
|
GstVideoOrientationMethod orientation)
|
|
{
|
|
auto priv = window->priv;
|
|
|
|
std::lock_guard < std::recursive_mutex > lk (priv->lock);
|
|
if (priv->orientation != orientation) {
|
|
priv->orientation = orientation;
|
|
priv->first_present = TRUE;
|
|
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);
|
|
}
|
|
}
|