/* GStreamer * Copyright (C) 2023 Seungha Yang * * 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 #include #include #include #include #include #include #include /* *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; auto device_handle = gst_d3d12_device_get_device_handle (device); queue = gst_d3d12_command_queue_new (device_handle, &queue_desc, D3D12_FENCE_FLAG_NONE, 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_handle, 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 cl; ComPtr swapchain; GstBuffer *msaa_buf = nullptr; std::vector> 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 enable_navigation = { TRUE }; gboolean first_present = TRUE; gboolean backbuf_rendered = FALSE; std::unique_ptr 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 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 = GetDesc (backbuf); auto mem = gst_d3d12_allocator_alloc_wrapped (nullptr, self->device, backbuf.Get (), 0, nullptr, nullptr); auto buf = gst_buffer_new (); gst_buffer_append_memory (buf, mem); 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, nullptr, nullptr); priv->ctx->msaa_buf = gst_buffer_new (); gst_buffer_append_memory (priv->ctx->msaa_buf, mem); } priv->first_present = TRUE; priv->backbuf_rendered = FALSE; /* redraw the last scene if cached buffer exits */ GstFlowReturn ret = GST_FLOW_OK; if (priv->ctx->cached_buf) { ret = gst_d3d12_window_set_buffer (self, priv->ctx->cached_buf); if (ret == GST_FLOW_OK) ret = gst_d3d12_window_present (self); } 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 (!gst_d3d12_device_is_equal (window->device, device)) { priv->ctx = nullptr; gst_clear_object (&window->device); window->device = (GstD3D12Device *) gst_object_ref (device); } if (!priv->ctx) { auto ctx = std::make_unique < DeviceContext > (device); 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); auto cq = gst_d3d12_command_queue_get_handle (ctx->queue); ComPtr < IDXGISwapChain1 > swapchain; hr = factory->CreateSwapChainForHwnd (cq, priv->hwnd, &desc, nullptr, nullptr, &swapchain); if (!gst_d3d12_result (hr, window->device)) { GST_ERROR_OBJECT (window, "Couldn't create swapchain"); if (config) gst_structure_free (config); return GST_FLOW_ERROR; } ComPtr < IDXGIFactory1 > parent_factory; hr = swapchain->GetParent (IID_PPV_ARGS (&parent_factory)); if (!gst_d3d12_result (hr, window->device)) { GST_WARNING_OBJECT (window, "Couldn't get parent factory"); } else { hr = parent_factory->MakeWindowAssociation (priv->hwnd, DXGI_MWA_NO_ALT_ENTER); if (!gst_d3d12_result (hr, window->device)) { GST_WARNING_OBJECT (window, "MakeWindowAssociation failed, hr: 0x%x", (guint) hr); } } hr = swapchain.As (&ctx->swapchain); if (!gst_d3d12_result (hr, window->device)) { GST_ERROR_OBJECT (window, "IDXGISwapChain4 interface is unavailable"); if (config) gst_structure_free (config); return GST_FLOW_ERROR; } priv->ctx = std::move (ctx); } else { priv->ctx->WaitGpu (); } priv->input_info = in_info; priv->crop_rect = CD3DX12_BOX (0, 0, in_info.width, in_info.height); priv->prev_crop_rect = priv->crop_rect; gst_clear_object (&priv->ctx->conv); gst_clear_object (&priv->ctx->comp); gst_clear_buffer (&priv->ctx->cached_buf); if (GST_VIDEO_INFO_HAS_ALPHA (&in_info)) { gst_structure_set (config, GST_D3D12_CONVERTER_OPT_DEST_ALPHA_MODE, GST_TYPE_D3D12_CONVERTER_ALPHA_MODE, GST_D3D12_CONVERTER_ALPHA_MODE_PREMULTIPLIED, nullptr); } priv->ctx->conv = gst_d3d12_converter_new (window->device, &priv->input_info, &priv->display_info, nullptr, nullptr, config); if (!priv->ctx->conv) { GST_ERROR_OBJECT (window, "Couldn't create converter"); priv->ctx = nullptr; return GST_FLOW_ERROR; } priv->ctx->comp = gst_d3d12_overlay_compositor_new (window->device, &priv->display_info); if (!priv->ctx->comp) { GST_ERROR_OBJECT (window, "Couldn't create overlay compositor"); priv->ctx = nullptr; return GST_FLOW_ERROR; } ret = gst_d3d12_window_on_resize (window); if (ret != GST_FLOW_OK) return ret; lk.unlock (); std::lock_guard < std::mutex > hlk (priv->hwnd_lock); if (priv->requested_fullscreen != priv->applied_fullscreen) PostMessageW (priv->hwnd, WM_GST_D3D12_FULLSCREEN, 0, 0); return GST_FLOW_OK; } GstFlowReturn gst_d3d12_window_set_buffer (GstD3D12Window * window, GstBuffer * buffer) { auto priv = window->priv; if (buffer && priv->state != GST_D3D12_WINDOW_STATE_OPENED) { GST_ERROR_OBJECT (window, "Window was closed"); return GST_D3D12_WINDOW_FLOW_CLOSED; } std::lock_guard < std::recursive_mutex > lk (priv->lock); if (!priv->ctx) { if (!buffer) { GST_DEBUG_OBJECT (window, "Swapchain was not configured"); return GST_FLOW_OK; } GST_ERROR_OBJECT (window, "Swapchain was not configured"); return GST_FLOW_ERROR; } if (buffer) gst_buffer_replace (&priv->ctx->cached_buf, buffer); if (!priv->ctx->cached_buf) return GST_FLOW_OK; auto cur_idx = priv->ctx->swapchain->GetCurrentBackBufferIndex (); auto swapbuf = priv->ctx->swap_buffers[cur_idx]; auto crop_rect = priv->crop_rect; auto crop_meta = gst_buffer_get_video_crop_meta (priv->ctx->cached_buf); if (crop_meta) { crop_rect = CD3DX12_BOX (crop_meta->x, crop_meta->y, crop_meta->x + crop_meta->width, crop_meta->y + crop_meta->height); } if (crop_rect != priv->prev_crop_rect) { g_object_set (priv->ctx->conv, "src-x", (gint) crop_rect.left, "src-y", (gint) crop_rect.top, "src-width", (gint) (crop_rect.right - crop_rect.left), "src-height", (gint) (crop_rect.bottom - crop_rect.top), nullptr); priv->prev_crop_rect = crop_rect; } if (priv->first_present) { GstVideoRectangle dst_rect = { }; GstVideoRectangle rst_rect = { }; dst_rect.w = (gint) priv->ctx->buffer_desc.Width; dst_rect.h = (gint) priv->ctx->buffer_desc.Height; for (size_t i = 0; i < priv->ctx->swap_buffers.size (); i++) priv->ctx->swap_buffers[i]->first = true; if (priv->force_aspect_ratio) { GstVideoRectangle src_rect = { }; switch (priv->orientation) { case GST_VIDEO_ORIENTATION_90R: case GST_VIDEO_ORIENTATION_90L: case GST_VIDEO_ORIENTATION_UL_LR: case GST_VIDEO_ORIENTATION_UR_LL: src_rect.w = GST_VIDEO_INFO_HEIGHT (&priv->display_info); src_rect.h = GST_VIDEO_INFO_WIDTH (&priv->display_info); break; default: src_rect.w = GST_VIDEO_INFO_WIDTH (&priv->display_info); src_rect.h = GST_VIDEO_INFO_HEIGHT (&priv->display_info); break; } gst_video_sink_center_rect (src_rect, dst_rect, &rst_rect, TRUE); } else { rst_rect = dst_rect; } priv->output_rect = rst_rect; priv->dirty_rect.left = rst_rect.x; priv->dirty_rect.top = rst_rect.y; priv->dirty_rect.right = rst_rect.x + rst_rect.w; priv->dirty_rect.bottom = rst_rect.y + rst_rect.h; g_object_set (priv->ctx->conv, "dest-x", priv->output_rect.x, "dest-y", priv->output_rect.y, "dest-width", priv->output_rect.w, "dest-height", priv->output_rect.h, nullptr); if (gst_d3d12_need_transform (priv->rotation_x, priv->rotation_y, priv->rotation_z, priv->scale_x, priv->scale_y)) { gst_d3d12_converter_apply_transform (priv->ctx->conv, priv->orientation, priv->output_rect.w, priv->output_rect.h, priv->fov, priv->ortho, priv->rotation_x, priv->rotation_y, priv->rotation_z, priv->scale_x, priv->scale_y); } else { g_object_set (priv->ctx->conv, "video-direction", priv->orientation, nullptr); } gst_d3d12_overlay_compositor_update_viewport (priv->ctx->comp, &priv->output_rect); } g_object_set (priv->ctx->conv, "fill-border", swapbuf->first, nullptr); swapbuf->first = FALSE; guint64 overlay_fence_val = 0; gst_d3d12_overlay_compositor_upload (priv->ctx->comp, priv->ctx->cached_buf, &overlay_fence_val); GstD3D12CommandAllocator *gst_ca; if (!gst_d3d12_command_allocator_pool_acquire (priv->ctx->ca_pool, &gst_ca)) { GST_ERROR_OBJECT (window, "Couldn't acquire command allocator"); return GST_FLOW_ERROR; } auto ca = gst_d3d12_command_allocator_get_handle (gst_ca); auto hr = ca->Reset (); if (!gst_d3d12_result (hr, window->device)) { GST_ERROR_OBJECT (window, "Couldn't reset command list"); gst_d3d12_command_allocator_unref (gst_ca); return GST_FLOW_ERROR; } ComPtr < ID3D12GraphicsCommandList > cl; if (!priv->ctx->cl) { auto device = gst_d3d12_device_get_device_handle (priv->ctx->device); hr = device->CreateCommandList (0, D3D12_COMMAND_LIST_TYPE_DIRECT, ca, nullptr, IID_PPV_ARGS (&cl)); if (!gst_d3d12_result (hr, priv->ctx->device)) { GST_ERROR_OBJECT (window, "Couldn't create command list"); gst_d3d12_command_allocator_unref (gst_ca); return GST_FLOW_ERROR; } priv->ctx->cl = cl; } else { cl = priv->ctx->cl; hr = cl->Reset (ca, nullptr); if (!gst_d3d12_result (hr, priv->ctx->device)) { GST_ERROR_OBJECT (window, "Couldn't reset command list"); gst_d3d12_command_allocator_unref (gst_ca); return GST_FLOW_ERROR; } } GstD3D12FenceData *fence_data; gst_d3d12_fence_data_pool_acquire (priv->fence_data_pool, &fence_data); gst_d3d12_fence_data_add_notify_mini_object (fence_data, gst_ca); auto mem = (GstD3D12Memory *) gst_buffer_peek_memory (swapbuf->backbuf, 0); auto backbuf_texture = gst_d3d12_memory_get_resource_handle (mem); ID3D12Resource *msaa_resource = nullptr; GstBuffer *conv_outbuf = swapbuf->backbuf; if (priv->ctx->msaa_buf) { conv_outbuf = priv->ctx->msaa_buf; mem = (GstD3D12Memory *) gst_buffer_peek_memory (priv->ctx->msaa_buf, 0); msaa_resource = gst_d3d12_memory_get_resource_handle (mem); /* MSAA resource must be render target state here already */ } else { D3D12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition (backbuf_texture, D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_RENDER_TARGET); cl->ResourceBarrier (1, &barrier); } if (!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; } max_fence_val = MAX (max_fence_val, overlay_fence_val); 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); auto fence = gst_d3d12_command_queue_get_fence_handle (device_queue); gst_d3d12_command_queue_execute_wait (priv->ctx->queue, fence, 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, 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); } }