| /* GStreamer | |
| * Copyright (C) 2023 Seungha Yang <seungha@centricular.com> | |
| * | |
| * This library is free software; you can redistribute it and/or | |
| * modify it under the terms of the GNU Library General Public | |
| * License as published by the Free Software Foundation; either | |
| * version 2 of the License, or (at your option) any later version. | |
| * | |
| * This library is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| * Library General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Library General Public | |
| * License along with this library; if not, write to the | |
| * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, | |
| * Boston, MA 02110-1301, USA. | |
| */ | |
| #ifdef HAVE_CONFIG_H | |
| #include "config.h" | |
| #endif | |
| #include "gstd3d12window.h" | |
| #include "gstd3d12overlaycompositor.h" | |
| #include <directx/d3dx12.h> | |
| #include <mutex> | |
| #include <condition_variable> | |
| #include <vector> | |
| #include <memory> | |
| #include <atomic> | |
| #include <wrl.h> | |
| #include <string> | |
| /* *INDENT-OFF* */ | |
| using namespace Microsoft::WRL; | |
| static std::mutex global_hwnd_lock; | |
| /* *INDENT-ON* */ | |
| GST_DEBUG_CATEGORY_STATIC (gst_d3d12_window_debug); | |
| #define GST_CAT_DEFAULT gst_d3d12_window_debug | |
| #define WS_GST_D3D12 (WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW) | |
| #define EXTERNAL_PROC_PROP_NAME L"gst-d3d12-hwnd-external-proc" | |
| #define D3D12_WINDOW_PROP_NAME L"gst-d3d12-hwnd-obj" | |
| #define WM_GST_D3D12_FULLSCREEN (WM_USER + 1) | |
| #define WM_GST_D3D12_CONSTRUCT_INTERNAL_WINDOW (WM_USER + 2) | |
| #define WM_GST_D3D12_DESTROY_INTERNAL_WINDOW (WM_USER + 3) | |
| #define WM_GST_D3D12_UPDATE_RENDER_RECT (WM_USER + 4) | |
| #define BACK_BUFFER_COUNT 3 | |
| enum | |
| { | |
| SIGNAL_KEY_EVENT, | |
| SIGNAL_MOUSE_EVENT, | |
| SIGNAL_FULLSCREEN, | |
| SIGNAL_LAST | |
| }; | |
| static guint d3d12_window_signals[SIGNAL_LAST] = { 0, }; | |
| /* *INDENT-OFF* */ | |
| struct SwapBuffer | |
| { | |
| SwapBuffer (GstBuffer * buf) | |
| { | |
| backbuf = buf; | |
| } | |
| ~SwapBuffer () | |
| { | |
| gst_clear_buffer (&backbuf); | |
| } | |
| GstBuffer *backbuf = nullptr; | |
| gboolean first = TRUE; | |
| }; | |
| struct DeviceContext | |
| { | |
| DeviceContext () = delete; | |
| DeviceContext (GstD3D12Device * dev) | |
| { | |
| event_handle = CreateEventEx (nullptr, nullptr, 0, EVENT_ALL_ACCESS); | |
| device = (GstD3D12Device *) gst_object_ref (dev); | |
| D3D12_COMMAND_QUEUE_DESC queue_desc = { }; | |
| queue_desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; | |
| queue_desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; | |
| queue = gst_d3d12_command_queue_new (device, | |
| &queue_desc, BACK_BUFFER_COUNT * 2); | |
| if (!queue) { | |
| GST_ERROR_OBJECT (device, "Couldn't create command queue"); | |
| return; | |
| } | |
| ca_pool = gst_d3d12_command_allocator_pool_new (device, | |
| D3D12_COMMAND_LIST_TYPE_DIRECT); | |
| if (!ca_pool) { | |
| GST_ERROR_OBJECT (device, "Couldn't create command allocator pool"); | |
| return; | |
| } | |
| initialized = true; | |
| } | |
| ~DeviceContext () | |
| { | |
| WaitGpu (); | |
| CloseHandle (event_handle); | |
| gst_clear_object (&queue); | |
| gst_clear_object (&ca_pool); | |
| gst_clear_buffer (&cached_buf); | |
| gst_clear_object (&conv); | |
| gst_clear_object (&comp); | |
| gst_clear_buffer (&msaa_buf); | |
| gst_clear_object (&device); | |
| } | |
| void WaitGpu () | |
| { | |
| if (!queue) | |
| return; | |
| gst_d3d12_command_queue_fence_wait (queue, G_MAXUINT64, event_handle); | |
| } | |
| ComPtr<ID3D12GraphicsCommandList> cl; | |
| ComPtr<IDXGISwapChain4> swapchain; | |
| GstBuffer *msaa_buf = nullptr; | |
| std::vector<std::shared_ptr<SwapBuffer>> swap_buffers; | |
| D3D12_RESOURCE_DESC buffer_desc; | |
| GstD3D12Converter *conv = nullptr; | |
| GstD3D12OverlayCompositor *comp = nullptr; | |
| GstBuffer *cached_buf = nullptr; | |
| GstD3D12Device *device = nullptr; | |
| GstD3D12CommandQueue *queue = nullptr; | |
| GstD3D12CommandAllocatorPool *ca_pool = nullptr; | |
| UINT64 fence_val = 0; | |
| HANDLE event_handle; | |
| bool initialized = false; | |
| }; | |
| struct GstD3D12WindowPrivate | |
| { | |
| GstD3D12WindowPrivate () | |
| { | |
| main_context = g_main_context_new (); | |
| loop = g_main_loop_new (main_context, FALSE); | |
| render_rect.w = -1; | |
| render_rect.h = -1; | |
| gst_video_info_init (&input_info); | |
| gst_video_info_init (&display_info); | |
| fence_data_pool = gst_d3d12_fence_data_pool_new (); | |
| } | |
| ~GstD3D12WindowPrivate () | |
| { | |
| g_main_loop_unref (loop); | |
| g_main_context_unref (main_context); | |
| gst_clear_object (&fence_data_pool); | |
| } | |
| std::recursive_mutex lock; | |
| DXGI_FORMAT display_format = DXGI_FORMAT_R8G8B8A8_UNORM; | |
| GstVideoOrientationMethod orientation = GST_VIDEO_ORIENTATION_IDENTITY; | |
| gfloat fov = 90.0f; | |
| gboolean ortho = FALSE; | |
| gfloat rotation_x = 0; | |
| gfloat rotation_y = 0; | |
| gfloat rotation_z = 0; | |
| gfloat scale_x = 1.0f; | |
| gfloat scale_y = 1.0f; | |
| /* fullscreen related variables */ | |
| gboolean fullscreen_on_alt_enter = TRUE; | |
| gboolean requested_fullscreen = FALSE; | |
| gboolean applied_fullscreen = FALSE; | |
| LONG restore_style; | |
| WINDOWPLACEMENT restore_placement; | |
| GstD3D12FenceDataPool *fence_data_pool; | |
| /* User specified rect */ | |
| GstVideoRectangle render_rect = { }; | |
| GstVideoRectangle output_rect = { }; | |
| RECT dirty_rect = { }; | |
| GstVideoInfo input_info; | |
| GstVideoInfo display_info; | |
| D3D12_BOX crop_rect = { }; | |
| D3D12_BOX prev_crop_rect = { }; | |
| gboolean force_aspect_ratio = TRUE; | |
| std::atomic<gboolean> enable_navigation = { TRUE }; | |
| gboolean first_present = TRUE; | |
| gboolean backbuf_rendered = FALSE; | |
| std::unique_ptr<DeviceContext> ctx; | |
| std::wstring title; | |
| gboolean update_title = FALSE; | |
| GstD3D12MSAAMode msaa = GST_D3D12_MSAA_DISABLED; | |
| /* Win32 window handles */ | |
| std::mutex hwnd_lock; | |
| std::condition_variable hwnd_cond; | |
| HWND hwnd = nullptr; | |
| HWND external_hwnd = nullptr; | |
| std::atomic<GstD3D12WindowState> state = { GST_D3D12_WINDOW_STATE_INIT }; | |
| GThread *main_loop_thread = nullptr; | |
| GThread *internal_hwnd_thread = nullptr; | |
| GMainLoop *loop = nullptr; | |
| GMainContext *main_context = nullptr; | |
| gboolean flushing = FALSE; | |
| }; | |
| /* *INDENT-ON* */ | |
| struct _GstD3D12Window | |
| { | |
| GstObject parent; | |
| GstD3D12Device *device; | |
| GstD3D12WindowPrivate *priv; | |
| }; | |
| static GstFlowReturn gst_d3d12_window_on_resize (GstD3D12Window * self); | |
| #define gst_d3d12_window_parent_class parent_class | |
| G_DEFINE_TYPE (GstD3D12Window, gst_d3d12_window, GST_TYPE_OBJECT); | |
| static void gst_d3d12_window_finalize (GObject * object); | |
| static void | |
| gst_d3d12_window_class_init (GstD3D12WindowClass * klass) | |
| { | |
| auto object_class = G_OBJECT_CLASS (klass); | |
| object_class->finalize = gst_d3d12_window_finalize; | |
| d3d12_window_signals[SIGNAL_KEY_EVENT] = | |
| g_signal_new ("key-event", G_TYPE_FROM_CLASS (klass), | |
| G_SIGNAL_RUN_LAST, 0, nullptr, nullptr, nullptr, | |
| G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING); | |
| d3d12_window_signals[SIGNAL_MOUSE_EVENT] = | |
| g_signal_new ("mouse-event", G_TYPE_FROM_CLASS (klass), | |
| G_SIGNAL_RUN_LAST, 0, nullptr, nullptr, nullptr, | |
| G_TYPE_NONE, 4, G_TYPE_STRING, G_TYPE_INT, G_TYPE_DOUBLE, G_TYPE_DOUBLE); | |
| d3d12_window_signals[SIGNAL_FULLSCREEN] = | |
| g_signal_new ("fullscreen", G_TYPE_FROM_CLASS (klass), | |
| G_SIGNAL_RUN_LAST, 0, nullptr, nullptr, nullptr, | |
| G_TYPE_NONE, 1, G_TYPE_BOOLEAN); | |
| GST_DEBUG_CATEGORY_INIT (gst_d3d12_window_debug, | |
| "d3d12window", 0, "d3d12window"); | |
| } | |
| static void | |
| gst_d3d12_window_init (GstD3D12Window * self) | |
| { | |
| self->priv = new GstD3D12WindowPrivate (); | |
| } | |
| static void | |
| gst_d3d12_window_finalize (GObject * object) | |
| { | |
| auto self = GST_D3D12_WINDOW (object); | |
| delete self->priv; | |
| gst_clear_object (&self->device); | |
| G_OBJECT_CLASS (parent_class)->finalize (object); | |
| } | |
| static void | |
| gst_d3d12_window_on_key_event (GstD3D12Window * self, | |
| UINT msg, WPARAM wparam, LPARAM lparam) | |
| { | |
| gunichar2 keyname[128]; | |
| const gchar *event; | |
| if (!GetKeyNameTextW (lparam, (LPWSTR) keyname, 128)) | |
| return; | |
| gchar *name = g_utf16_to_utf8 (keyname, 128, nullptr, nullptr, nullptr); | |
| if (!name) | |
| return; | |
| if (msg == WM_KEYDOWN) | |
| event = "key-press"; | |
| else | |
| event = "key-release"; | |
| g_signal_emit (self, d3d12_window_signals[SIGNAL_KEY_EVENT], 0, event, name); | |
| g_free (name); | |
| } | |
| static void | |
| gst_d3d12_window_on_mouse_event (GstD3D12Window * self, UINT msg, WPARAM wparam, | |
| LPARAM lparam) | |
| { | |
| auto priv = self->priv; | |
| gint button = 0; | |
| const gchar *event = nullptr; | |
| switch (msg) { | |
| case WM_MOUSEMOVE: | |
| button = 0; | |
| event = "mouse-move"; | |
| break; | |
| case WM_LBUTTONDOWN: | |
| button = 1; | |
| event = "mouse-button-press"; | |
| break; | |
| case WM_LBUTTONUP: | |
| button = 1; | |
| event = "mouse-button-release"; | |
| break; | |
| case WM_RBUTTONDOWN: | |
| button = 2; | |
| event = "mouse-button-press"; | |
| break; | |
| case WM_RBUTTONUP: | |
| button = 2; | |
| event = "mouse-button-release"; | |
| break; | |
| case WM_MBUTTONDOWN: | |
| button = 3; | |
| event = "mouse-button-press"; | |
| break; | |
| case WM_MBUTTONUP: | |
| button = 3; | |
| event = "mouse-button-release"; | |
| break; | |
| default: | |
| return; | |
| } | |
| GstVideoRectangle output_rect = { }; | |
| GstVideoOrientationMethod orientation; | |
| gint in_w, in_h; | |
| { | |
| std::lock_guard < std::recursive_mutex > lk (priv->lock); | |
| orientation = priv->orientation; | |
| output_rect = priv->output_rect; | |
| in_w = priv->input_info.width; | |
| in_h = priv->input_info.height; | |
| } | |
| auto xpos = LOWORD (lparam); | |
| auto ypos = HIWORD (lparam); | |
| if (in_w <= 0 || in_h <= 0 || xpos < output_rect.x || | |
| xpos >= output_rect.x + output_rect.w || ypos < output_rect.y || | |
| ypos >= output_rect.y + output_rect.h) { | |
| return; | |
| } | |
| gint src_w, src_h; | |
| switch (orientation) { | |
| case GST_VIDEO_ORIENTATION_90R: | |
| case GST_VIDEO_ORIENTATION_90L: | |
| case GST_VIDEO_ORIENTATION_UL_LR: | |
| case GST_VIDEO_ORIENTATION_UR_LL: | |
| src_w = in_h; | |
| src_h = in_w; | |
| break; | |
| default: | |
| src_w = in_w; | |
| src_h = in_h; | |
| break; | |
| } | |
| xpos = ((xpos - output_rect.x) / (double) output_rect.w) * src_w; | |
| ypos = ((ypos - output_rect.y) / (double) output_rect.h) * src_h; | |
| xpos = CLAMP (xpos, 0, (LONG) (src_w - 1)); | |
| ypos = CLAMP (ypos, 0, (LONG) (src_h - 1)); | |
| double final_x = 0; | |
| double final_y = 0; | |
| switch (orientation) { | |
| case GST_VIDEO_ORIENTATION_90R: | |
| final_x = ypos; | |
| final_y = src_w - xpos; | |
| break; | |
| case GST_VIDEO_ORIENTATION_90L: | |
| final_x = src_h - ypos; | |
| final_y = xpos; | |
| break; | |
| case GST_VIDEO_ORIENTATION_UR_LL: | |
| final_x = src_h - ypos; | |
| final_y = src_w - xpos; | |
| break; | |
| case GST_VIDEO_ORIENTATION_UL_LR: | |
| final_x = ypos; | |
| final_y = xpos; | |
| break; | |
| case GST_VIDEO_ORIENTATION_180: | |
| final_x = src_w - xpos; | |
| final_y = src_h - ypos; | |
| break; | |
| case GST_VIDEO_ORIENTATION_HORIZ: | |
| final_x = src_w - xpos; | |
| final_y = ypos; | |
| break; | |
| case GST_VIDEO_ORIENTATION_VERT: | |
| final_x = xpos; | |
| final_y = src_h - ypos; | |
| break; | |
| default: | |
| final_x = xpos; | |
| final_y = ypos; | |
| break; | |
| } | |
| g_signal_emit (self, d3d12_window_signals[SIGNAL_MOUSE_EVENT], 0, | |
| event, button, final_x, final_y); | |
| } | |
| static void | |
| gst_d3d12_window_toggle_fullscreen_mode (GstD3D12Window * self, | |
| gboolean emit_signal) | |
| { | |
| auto priv = self->priv; | |
| HWND hwnd = nullptr; | |
| gboolean is_fullscreen; | |
| ComPtr < IDXGISwapChain > swapchain; | |
| { | |
| std::lock_guard < std::mutex > hlk (priv->hwnd_lock); | |
| hwnd = priv->external_hwnd ? priv->external_hwnd : priv->hwnd; | |
| if (!hwnd) | |
| return; | |
| if (priv->requested_fullscreen == priv->applied_fullscreen) | |
| return; | |
| { | |
| std::lock_guard < std::recursive_mutex > lk (priv->lock); | |
| if (priv->ctx) | |
| swapchain = priv->ctx->swapchain; | |
| } | |
| if (!swapchain) | |
| return; | |
| GST_DEBUG_OBJECT (self, "Change mode to %s", | |
| priv->requested_fullscreen ? "fullscreen" : "windowed"); | |
| priv->applied_fullscreen = priv->requested_fullscreen; | |
| is_fullscreen = priv->applied_fullscreen; | |
| } | |
| if (!is_fullscreen) { | |
| SetWindowLongW (hwnd, GWL_STYLE, priv->restore_style); | |
| SetWindowPlacement (hwnd, &priv->restore_placement); | |
| } else { | |
| ComPtr < IDXGIOutput > output; | |
| DXGI_OUTPUT_DESC output_desc; | |
| /* remember current placement to restore window later */ | |
| GetWindowPlacement (hwnd, &priv->restore_placement); | |
| /* show window before change style */ | |
| ShowWindow (hwnd, SW_SHOW); | |
| priv->restore_style = GetWindowLong (hwnd, GWL_STYLE); | |
| /* Make the window borderless so that the client area can fill the screen */ | |
| SetWindowLongA (hwnd, GWL_STYLE, | |
| priv->restore_style & | |
| ~(WS_CAPTION | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_SYSMENU | | |
| WS_THICKFRAME | WS_MAXIMIZE)); | |
| swapchain->GetContainingOutput (&output); | |
| output->GetDesc (&output_desc); | |
| SetWindowPos (hwnd, HWND_TOP, | |
| output_desc.DesktopCoordinates.left, | |
| output_desc.DesktopCoordinates.top, | |
| output_desc.DesktopCoordinates.right, | |
| output_desc.DesktopCoordinates.bottom, | |
| SWP_FRAMECHANGED | SWP_NOACTIVATE); | |
| ShowWindow (hwnd, SW_MAXIMIZE); | |
| } | |
| GST_DEBUG_OBJECT (self, "Fullscreen mode change done"); | |
| if (emit_signal) { | |
| g_signal_emit (self, d3d12_window_signals[SIGNAL_FULLSCREEN], | |
| 0, is_fullscreen); | |
| } | |
| } | |
| static GstD3D12Window * | |
| gst_d3d12_window_from_hwnd (HWND hwnd) | |
| { | |
| std::lock_guard < std::mutex > lk (global_hwnd_lock); | |
| auto self = GetPropW (hwnd, D3D12_WINDOW_PROP_NAME); | |
| if (self) | |
| gst_object_ref (self); | |
| return (GstD3D12Window *) self; | |
| } | |
| static LRESULT CALLBACK | |
| gst_d3d12_window_proc (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) | |
| { | |
| switch (msg) { | |
| case WM_CREATE: | |
| { | |
| LPCREATESTRUCTW create_param = (LPCREATESTRUCTW) lparam; | |
| SetPropW (hwnd, D3D12_WINDOW_PROP_NAME, create_param->lpCreateParams); | |
| break; | |
| } | |
| case WM_GST_D3D12_DESTROY_INTERNAL_WINDOW: | |
| { | |
| GST_INFO ("Handle destroy window message"); | |
| DestroyWindow (hwnd); | |
| return 0; | |
| } | |
| case WM_GST_D3D12_UPDATE_RENDER_RECT: | |
| { | |
| auto self = gst_d3d12_window_from_hwnd (hwnd); | |
| if (self) { | |
| GstVideoRectangle render_rect = { }; | |
| { | |
| auto priv = self->priv; | |
| std::lock_guard < std::mutex > lk (priv->hwnd_lock); | |
| render_rect = priv->render_rect; | |
| } | |
| if (render_rect.w > 0 && render_rect.h > 0) { | |
| MoveWindow (hwnd, | |
| render_rect.x, render_rect.y, render_rect.w, render_rect.h, TRUE); | |
| } | |
| gst_object_unref (self); | |
| } | |
| return 0; | |
| } | |
| case WM_GST_D3D12_FULLSCREEN: | |
| { | |
| auto self = gst_d3d12_window_from_hwnd (hwnd); | |
| if (self) { | |
| gst_d3d12_window_toggle_fullscreen_mode (self, FALSE); | |
| gst_object_unref (self); | |
| } | |
| return 0; | |
| } | |
| case WM_SYSKEYDOWN: | |
| { | |
| WORD state = GetKeyState (VK_RETURN); | |
| BYTE high = HIBYTE (state); | |
| if (high & 0x1) { | |
| auto self = gst_d3d12_window_from_hwnd (hwnd); | |
| if (self) { | |
| auto priv = self->priv; | |
| bool do_toggle = false; | |
| { | |
| std::lock_guard < std::mutex > lk (priv->hwnd_lock); | |
| if (priv->fullscreen_on_alt_enter) { | |
| priv->requested_fullscreen = !priv->applied_fullscreen; | |
| do_toggle = true; | |
| } | |
| } | |
| if (do_toggle) | |
| gst_d3d12_window_toggle_fullscreen_mode (self, TRUE); | |
| gst_object_unref (self); | |
| } | |
| } | |
| break; | |
| } | |
| case WM_SIZE: | |
| { | |
| auto self = gst_d3d12_window_from_hwnd (hwnd); | |
| if (self) { | |
| gst_d3d12_window_on_resize (self); | |
| gst_object_unref (self); | |
| } | |
| break; | |
| } | |
| case WM_CLOSE: | |
| { | |
| auto self = gst_d3d12_window_from_hwnd (hwnd); | |
| if (self) { | |
| auto priv = self->priv; | |
| GST_DEBUG_OBJECT (self, "Internal window is being closed"); | |
| { | |
| std::lock_guard < std::mutex > lk (priv->hwnd_lock); | |
| RemovePropW (priv->hwnd, D3D12_WINDOW_PROP_NAME); | |
| priv->hwnd = nullptr; | |
| priv->state = GST_D3D12_WINDOW_STATE_CLOSED; | |
| } | |
| gst_object_unref (self); | |
| } | |
| break; | |
| } | |
| case WM_NCHITTEST: | |
| { | |
| auto self = gst_d3d12_window_from_hwnd (hwnd); | |
| if (self && self->priv->external_hwnd) { | |
| gst_object_unref (self); | |
| /* To passthrough mouse event if external window is used. | |
| * Only hit-test succeeded window can receive/handle some mouse events | |
| * and we want such events to be handled by parent (application) window | |
| */ | |
| return (LRESULT) HTTRANSPARENT; | |
| } | |
| gst_clear_object (&self); | |
| break; | |
| } | |
| case WM_KEYDOWN: | |
| case WM_KEYUP: | |
| { | |
| auto self = gst_d3d12_window_from_hwnd (hwnd); | |
| if (self) { | |
| auto priv = self->priv; | |
| if (priv->enable_navigation) | |
| gst_d3d12_window_on_key_event (self, msg, wparam, lparam); | |
| gst_object_unref (self); | |
| } | |
| break; | |
| } | |
| case WM_LBUTTONDOWN: | |
| case WM_LBUTTONUP: | |
| case WM_RBUTTONDOWN: | |
| case WM_RBUTTONUP: | |
| case WM_MBUTTONDOWN: | |
| case WM_MBUTTONUP: | |
| case WM_MOUSEMOVE: | |
| { | |
| auto self = gst_d3d12_window_from_hwnd (hwnd); | |
| if (self) { | |
| auto priv = self->priv; | |
| if (priv->enable_navigation) | |
| gst_d3d12_window_on_mouse_event (self, msg, wparam, lparam); | |
| gst_object_unref (self); | |
| } | |
| break; | |
| } | |
| default: | |
| break; | |
| } | |
| return DefWindowProcW (hwnd, msg, wparam, lparam); | |
| } | |
| static void | |
| gst_d3d12_window_create_hwnd (GstD3D12Window * self) | |
| { | |
| auto priv = self->priv; | |
| auto inst = GetModuleHandle (nullptr); | |
| { | |
| static std::mutex class_lock; | |
| std::lock_guard < std::mutex > lk (class_lock); | |
| WNDCLASSEXW wc = { }; | |
| if (!GetClassInfoExW (inst, L"GstD3D12Hwnd", &wc)) { | |
| wc.cbSize = sizeof (WNDCLASSEXW); | |
| wc.lpfnWndProc = gst_d3d12_window_proc; | |
| wc.hInstance = inst; | |
| wc.hIcon = LoadIcon (nullptr, IDI_WINLOGO); | |
| wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; | |
| wc.hCursor = LoadCursor (nullptr, IDC_ARROW); | |
| wc.hbrBackground = (HBRUSH) GetStockObject (BLACK_BRUSH); | |
| wc.lpszClassName = L"GstD3D12Hwnd"; | |
| RegisterClassExW (&wc); | |
| } | |
| } | |
| int x = CW_USEDEFAULT; | |
| int y = CW_USEDEFAULT; | |
| int w = 0; | |
| int h = 0; | |
| DWORD style = WS_GST_D3D12; | |
| std::wstring title = L"Direct3D12 Renderer"; | |
| if (!priv->title.empty ()) | |
| title = priv->title; | |
| if (!priv->external_hwnd) { | |
| if (priv->render_rect.w > 0 && priv->render_rect.h > 0) { | |
| x = priv->render_rect.x; | |
| y = priv->render_rect.y; | |
| w = priv->render_rect.w; | |
| h = priv->render_rect.h; | |
| } else { | |
| std::lock_guard < std::recursive_mutex > lk (priv->lock); | |
| RECT rect = { }; | |
| switch (priv->orientation) { | |
| case GST_VIDEO_ORIENTATION_90R: | |
| case GST_VIDEO_ORIENTATION_90L: | |
| case GST_VIDEO_ORIENTATION_UL_LR: | |
| case GST_VIDEO_ORIENTATION_UR_LL: | |
| rect.right = GST_VIDEO_INFO_HEIGHT (&priv->display_info); | |
| rect.bottom = GST_VIDEO_INFO_WIDTH (&priv->display_info); | |
| break; | |
| default: | |
| rect.right = GST_VIDEO_INFO_WIDTH (&priv->display_info); | |
| rect.bottom = GST_VIDEO_INFO_HEIGHT (&priv->display_info); | |
| break; | |
| } | |
| AdjustWindowRect (&rect, WS_GST_D3D12, FALSE); | |
| w = rect.right - rect.left; | |
| h = rect.bottom - rect.top; | |
| } | |
| style |= WS_VISIBLE; | |
| } | |
| priv->hwnd = CreateWindowExW (0, L"GstD3D12Hwnd", title.c_str (), | |
| style, x, y, w, h, (HWND) nullptr, (HMENU) nullptr, inst, self); | |
| priv->applied_fullscreen = FALSE; | |
| priv->internal_hwnd_thread = g_thread_self (); | |
| } | |
| static void | |
| gst_d3d12_window_release_external_hwnd (GstD3D12Window * self) | |
| { | |
| auto priv = self->priv; | |
| if (!priv->external_hwnd) | |
| return; | |
| auto hwnd = priv->external_hwnd; | |
| priv->external_hwnd = nullptr; | |
| auto external_proc = (WNDPROC) GetPropW (hwnd, EXTERNAL_PROC_PROP_NAME); | |
| if (!external_proc) { | |
| GST_WARNING_OBJECT (self, "Failed to get original window procedure"); | |
| return; | |
| } | |
| GST_DEBUG_OBJECT (self, "release external window %" G_GUINTPTR_FORMAT | |
| ", original window procedure %p", (guintptr) hwnd, external_proc); | |
| RemovePropW (hwnd, EXTERNAL_PROC_PROP_NAME); | |
| RemovePropW (hwnd, D3D12_WINDOW_PROP_NAME); | |
| if (!SetWindowLongPtrW (hwnd, GWLP_WNDPROC, (LONG_PTR) external_proc)) | |
| GST_WARNING_OBJECT (self, "Couldn't restore original window procedure"); | |
| } | |
| static LRESULT FAR PASCAL | |
| sub_class_proc (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) | |
| { | |
| auto external_window_proc = | |
| (WNDPROC) GetPropW (hwnd, EXTERNAL_PROC_PROP_NAME); | |
| auto self = gst_d3d12_window_from_hwnd (hwnd); | |
| if (!self) { | |
| GST_DEBUG ("No object attached to the window, chain up to default"); | |
| return CallWindowProcW (external_window_proc, hwnd, msg, wparam, lparam); | |
| } | |
| auto priv = self->priv; | |
| switch (msg) { | |
| case WM_GST_D3D12_CONSTRUCT_INTERNAL_WINDOW: | |
| { | |
| GST_DEBUG_OBJECT (self, "Create internal window"); | |
| std::lock_guard < std::mutex > lk (priv->hwnd_lock); | |
| if (priv->hwnd) { | |
| GST_WARNING_OBJECT (self, | |
| "Window already created, probably we have received 2 creation messages"); | |
| gst_object_unref (self); | |
| return 0; | |
| } | |
| gst_d3d12_window_create_hwnd (self); | |
| SetWindowLongPtrW (priv->hwnd, GWL_STYLE, WS_CHILD | WS_MAXIMIZE); | |
| SetParent (priv->hwnd, priv->external_hwnd); | |
| RECT rect; | |
| GetClientRect (priv->external_hwnd, &rect); | |
| if (priv->render_rect.w > 0 && priv->render_rect.h > 0) { | |
| rect.left = priv->render_rect.x; | |
| rect.top = priv->render_rect.y; | |
| rect.right = priv->render_rect.x + priv->render_rect.w; | |
| rect.bottom = priv->render_rect.y + priv->render_rect.h; | |
| } | |
| SetWindowPos (priv->hwnd, HWND_TOP, rect.left, rect.top, | |
| rect.right - rect.left, rect.bottom - rect.top, | |
| SWP_ASYNCWINDOWPOS | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | | |
| SWP_FRAMECHANGED | SWP_NOACTIVATE); | |
| MoveWindow (priv->hwnd, rect.left, rect.top, | |
| rect.right - rect.left, rect.bottom - rect.top, FALSE); | |
| ShowWindow (priv->hwnd, SW_SHOW); | |
| priv->state = GST_D3D12_WINDOW_STATE_OPENED; | |
| priv->hwnd_cond.notify_all (); | |
| /* don't need to be chained up to parent window procedure, | |
| * as this is our custom message */ | |
| gst_object_unref (self); | |
| return 0; | |
| } | |
| case WM_SYSKEYDOWN: | |
| { | |
| WORD state = GetKeyState (VK_RETURN); | |
| BYTE high = HIBYTE (state); | |
| if (high & 0x1) { | |
| bool do_toggle = false; | |
| { | |
| std::lock_guard < std::mutex > lk (priv->hwnd_lock); | |
| if (priv->fullscreen_on_alt_enter) { | |
| priv->requested_fullscreen = !priv->applied_fullscreen; | |
| do_toggle = true; | |
| } | |
| } | |
| if (do_toggle) | |
| gst_d3d12_window_toggle_fullscreen_mode (self, TRUE); | |
| } | |
| break; | |
| } | |
| case WM_SIZE: | |
| if (priv->render_rect.w > 0 || priv->render_rect.h > 0) { | |
| MoveWindow (priv->hwnd, | |
| priv->render_rect.x, priv->render_rect.y, | |
| priv->render_rect.w, priv->render_rect.h, FALSE); | |
| } else { | |
| MoveWindow (priv->hwnd, 0, 0, LOWORD (lparam), HIWORD (lparam), FALSE); | |
| } | |
| break; | |
| case WM_DESTROY: | |
| { | |
| std::lock_guard < std::mutex > lk (priv->hwnd_lock); | |
| GST_WARNING_OBJECT (self, "external window is closing"); | |
| gst_d3d12_window_release_external_hwnd (self); | |
| if (priv->hwnd) | |
| DestroyWindow (priv->hwnd); | |
| priv->hwnd = nullptr; | |
| priv->state = GST_D3D12_WINDOW_STATE_CLOSED; | |
| priv->hwnd_cond.notify_all (); | |
| break; | |
| } | |
| case WM_KEYDOWN: | |
| case WM_KEYUP: | |
| if (priv->enable_navigation) | |
| gst_d3d12_window_on_key_event (self, msg, wparam, lparam); | |
| break; | |
| case WM_LBUTTONDOWN: | |
| case WM_LBUTTONUP: | |
| case WM_RBUTTONDOWN: | |
| case WM_RBUTTONUP: | |
| case WM_MBUTTONDOWN: | |
| case WM_MBUTTONUP: | |
| case WM_MOUSEMOVE: | |
| if (priv->enable_navigation) | |
| gst_d3d12_window_on_mouse_event (self, msg, wparam, lparam); | |
| break; | |
| default: | |
| break; | |
| } | |
| gst_object_unref (self); | |
| return CallWindowProcW (external_window_proc, hwnd, msg, wparam, lparam); | |
| } | |
| static gboolean | |
| msg_io_cb (GIOChannel * source, GIOCondition condition, gpointer data) | |
| { | |
| MSG msg; | |
| if (!PeekMessage (&msg, nullptr, 0, 0, PM_REMOVE)) | |
| return G_SOURCE_CONTINUE; | |
| TranslateMessage (&msg); | |
| DispatchMessage (&msg); | |
| return G_SOURCE_CONTINUE; | |
| } | |
| static gpointer | |
| gst_d3d12_window_hwnd_thread_func (GstD3D12Window * self) | |
| { | |
| auto priv = self->priv; | |
| g_main_context_push_thread_default (priv->main_context); | |
| gst_d3d12_window_create_hwnd (self); | |
| priv->state = GST_D3D12_WINDOW_STATE_OPENED; | |
| auto msg_io_ch = g_io_channel_win32_new_messages (0); | |
| auto msg_source = g_io_create_watch (msg_io_ch, G_IO_IN); | |
| g_source_set_callback (msg_source, (GSourceFunc) msg_io_cb, nullptr, nullptr); | |
| g_source_attach (msg_source, priv->main_context); | |
| auto idle_source = g_idle_source_new (); | |
| g_source_set_callback (idle_source,[](gpointer data)->gboolean { | |
| auto self = GST_D3D12_WINDOW (data); | |
| auto priv = self->priv; | |
| std::lock_guard < std::mutex > lk (priv->hwnd_lock); | |
| priv->hwnd_cond.notify_all (); | |
| return G_SOURCE_REMOVE; | |
| } | |
| , self, nullptr); | |
| g_source_attach (idle_source, priv->main_context); | |
| g_source_unref (idle_source); | |
| g_main_loop_run (priv->loop); | |
| g_clear_pointer (&priv->hwnd, DestroyWindow); | |
| g_source_destroy (msg_source); | |
| g_source_unref (msg_source); | |
| g_io_channel_unref (msg_io_ch); | |
| g_main_context_pop_thread_default (priv->main_context); | |
| return nullptr; | |
| } | |
| static GstFlowReturn | |
| gst_d3d12_window_prepare_hwnd (GstD3D12Window * self, guintptr window_handle) | |
| { | |
| auto priv = self->priv; | |
| switch (priv->state) { | |
| case GST_D3D12_WINDOW_STATE_OPENED: | |
| return GST_FLOW_OK; | |
| case GST_D3D12_WINDOW_STATE_CLOSED: | |
| return GST_FLOW_ERROR; | |
| default: | |
| break; | |
| } | |
| std::unique_lock < std::mutex > lk (priv->hwnd_lock); | |
| priv->external_hwnd = (HWND) window_handle; | |
| if (!priv->external_hwnd) { | |
| priv->main_loop_thread = g_thread_new ("GstD3D12Window", | |
| (GThreadFunc) gst_d3d12_window_hwnd_thread_func, self); | |
| while (!g_main_loop_is_running (priv->loop)) | |
| priv->hwnd_cond.wait (lk); | |
| return GST_FLOW_OK; | |
| } | |
| if (!IsWindow (priv->external_hwnd)) { | |
| GST_ERROR_OBJECT (self, "Invalid window handle"); | |
| return GST_FLOW_ERROR; | |
| } | |
| { | |
| std::lock_guard < std::mutex > glk (global_hwnd_lock); | |
| auto external_proc = | |
| (WNDPROC) GetWindowLongPtrA (priv->external_hwnd, GWLP_WNDPROC); | |
| SetPropW (priv->external_hwnd, EXTERNAL_PROC_PROP_NAME, | |
| (HANDLE) external_proc); | |
| SetPropW (priv->external_hwnd, D3D12_WINDOW_PROP_NAME, self); | |
| SetWindowLongPtrW (priv->external_hwnd, GWLP_WNDPROC, | |
| (LONG_PTR) sub_class_proc); | |
| } | |
| PostMessageW (priv->external_hwnd, | |
| WM_GST_D3D12_CONSTRUCT_INTERNAL_WINDOW, 0, 0); | |
| while (priv->external_hwnd && | |
| priv->state == GST_D3D12_WINDOW_STATE_INIT && !priv->flushing) { | |
| priv->hwnd_cond.wait (lk); | |
| } | |
| if (priv->state != GST_D3D12_WINDOW_STATE_OPENED) { | |
| if (priv->flushing) { | |
| GST_DEBUG_OBJECT (self, "We are flushing"); | |
| return GST_FLOW_FLUSHING; | |
| } | |
| return GST_FLOW_ERROR; | |
| } | |
| return GST_FLOW_OK; | |
| } | |
| void | |
| gst_d3d12_window_unprepare (GstD3D12Window * window) | |
| { | |
| GST_DEBUG_OBJECT (window, "Unprepare"); | |
| auto priv = window->priv; | |
| { | |
| std::lock_guard < std::recursive_mutex > lk (priv->lock); | |
| priv->ctx = nullptr; | |
| gst_clear_object (&window->device); | |
| } | |
| if (priv->external_hwnd) { | |
| { | |
| std::lock_guard < std::mutex > lk (global_hwnd_lock); | |
| gst_d3d12_window_release_external_hwnd (window); | |
| } | |
| std::lock_guard < std::mutex > lk (priv->hwnd_lock); | |
| if (priv->hwnd) { | |
| if (priv->internal_hwnd_thread == g_thread_self ()) { | |
| /* State changing thread is identical to internal window thread. | |
| * window can be closed here */ | |
| GST_INFO_OBJECT (window, "Closing internal window immediately"); | |
| DestroyWindow (priv->hwnd); | |
| } else { | |
| /* We cannot destroy internal window from non-window thread. | |
| * and we cannot use synchronously SendMessage() method at this point | |
| * since window thread might be waiting for current thread and SendMessage() | |
| * will be blocked until it's called from window thread. | |
| * Instead, posts message so that it can be closed from window thread | |
| * asynchronously */ | |
| GST_INFO_OBJECT (window, "Posting custom destory message"); | |
| PostMessageW (priv->hwnd, WM_GST_D3D12_DESTROY_INTERNAL_WINDOW, 0, 0); | |
| } | |
| } | |
| } | |
| g_main_loop_quit (priv->loop); | |
| g_clear_pointer (&priv->main_loop_thread, g_thread_join); | |
| std::lock_guard < std::mutex > lk (priv->hwnd_lock); | |
| priv->hwnd = nullptr; | |
| priv->external_hwnd = nullptr; | |
| priv->internal_hwnd_thread = nullptr; | |
| priv->state = GST_D3D12_WINDOW_STATE_INIT; | |
| priv->applied_fullscreen = FALSE; | |
| } | |
| void | |
| gst_d3d12_window_unlock (GstD3D12Window * window) | |
| { | |
| GST_DEBUG_OBJECT (window, "Unlock"); | |
| auto priv = window->priv; | |
| std::lock_guard < std::mutex > lk (priv->hwnd_lock); | |
| priv->flushing = TRUE; | |
| priv->hwnd_cond.notify_all (); | |
| } | |
| void | |
| gst_d3d12_window_unlock_stop (GstD3D12Window * window) | |
| { | |
| GST_DEBUG_OBJECT (window, "Unlock stop"); | |
| auto priv = window->priv; | |
| std::lock_guard < std::mutex > lk (priv->hwnd_lock); | |
| priv->flushing = FALSE; | |
| priv->hwnd_cond.notify_all (); | |
| } | |
| static GstFlowReturn | |
| gst_d3d12_window_on_resize (GstD3D12Window * self) | |
| { | |
| auto priv = self->priv; | |
| std::lock_guard < std::recursive_mutex > lk (priv->lock); | |
| /* resize might be called before swapchain configuration */ | |
| if (!priv->ctx) | |
| return GST_FLOW_OK; | |
| if (priv->ctx->fence_val != 0) | |
| priv->ctx->WaitGpu (); | |
| priv->ctx->swap_buffers.clear (); | |
| gst_clear_buffer (&priv->ctx->msaa_buf); | |
| DXGI_SWAP_CHAIN_DESC desc = { }; | |
| priv->ctx->swapchain->GetDesc (&desc); | |
| auto hr = priv->ctx->swapchain->ResizeBuffers (BACK_BUFFER_COUNT, | |
| 0, 0, priv->display_format, desc.Flags); | |
| if (!gst_d3d12_result (hr, self->device)) { | |
| GST_ERROR_OBJECT (self, "Couldn't resize buffers"); | |
| return GST_FLOW_ERROR; | |
| } | |
| for (guint i = 0; i < BACK_BUFFER_COUNT; i++) { | |
| ComPtr < ID3D12Resource > backbuf; | |
| hr = priv->ctx->swapchain->GetBuffer (i, IID_PPV_ARGS (&backbuf)); | |
| if (!gst_d3d12_result (hr, self->device)) { | |
| GST_ERROR_OBJECT (self, "Couldn't get backbuffer"); | |
| return GST_FLOW_ERROR; | |
| } | |
| if (i == 0) | |
| priv->ctx->buffer_desc = backbuf->GetDesc (); | |
| auto mem = gst_d3d12_allocator_alloc_wrapped (nullptr, self->device, | |
| backbuf.Get (), 0); | |
| auto buf = gst_buffer_new (); | |
| gst_buffer_append_memory (buf, mem); | |
| priv->ctx->swap_buffers.push_back (std::make_shared < SwapBuffer > (buf)); | |
| } | |
| guint sample_count = 1; | |
| switch (priv->msaa) { | |
| case GST_D3D12_MSAA_2X: | |
| sample_count = 2; | |
| break; | |
| case GST_D3D12_MSAA_4X: | |
| sample_count = 4; | |
| break; | |
| case GST_D3D12_MSAA_8X: | |
| sample_count = 8; | |
| break; | |
| default: | |
| break; | |
| } | |
| auto device = gst_d3d12_device_get_device_handle (self->device); | |
| D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS feature_data = { }; | |
| feature_data.Format = priv->ctx->buffer_desc.Format; | |
| feature_data.SampleCount = sample_count; | |
| while (feature_data.SampleCount > 1) { | |
| hr = device->CheckFeatureSupport (D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS, | |
| &feature_data, sizeof (feature_data)); | |
| if (SUCCEEDED (hr) && feature_data.NumQualityLevels > 0) | |
| break; | |
| feature_data.SampleCount /= 2; | |
| } | |
| if (feature_data.SampleCount > 1 && feature_data.NumQualityLevels > 0) { | |
| GST_DEBUG_OBJECT (self, "Enable MSAA x%d with quality level %d", | |
| feature_data.SampleCount, feature_data.NumQualityLevels - 1); | |
| D3D12_HEAP_PROPERTIES heap_prop = | |
| CD3DX12_HEAP_PROPERTIES (D3D12_HEAP_TYPE_DEFAULT); | |
| D3D12_RESOURCE_DESC resource_desc = | |
| CD3DX12_RESOURCE_DESC::Tex2D (priv->ctx->buffer_desc.Format, | |
| priv->ctx->buffer_desc.Width, priv->ctx->buffer_desc.Height, | |
| 1, 1, feature_data.SampleCount, feature_data.NumQualityLevels - 1, | |
| D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET); | |
| D3D12_CLEAR_VALUE clear_value = { }; | |
| clear_value.Format = priv->ctx->buffer_desc.Format; | |
| clear_value.Color[0] = 0.0f; | |
| clear_value.Color[1] = 0.0f; | |
| clear_value.Color[2] = 0.0f; | |
| clear_value.Color[3] = 1.0f; | |
| ComPtr < ID3D12Resource > msaa_texture; | |
| hr = device->CreateCommittedResource (&heap_prop, D3D12_HEAP_FLAG_NONE, | |
| &resource_desc, D3D12_RESOURCE_STATE_RENDER_TARGET, &clear_value, | |
| IID_PPV_ARGS (&msaa_texture)); | |
| if (!gst_d3d12_result (hr, self->device)) { | |
| GST_ERROR_OBJECT (self, "Couldn't create MSAA texture"); | |
| return GST_FLOW_ERROR; | |
| } | |
| auto mem = gst_d3d12_allocator_alloc_wrapped (nullptr, self->device, | |
| msaa_texture.Get (), 0); | |
| priv->ctx->msaa_buf = gst_buffer_new (); | |
| gst_buffer_append_memory (priv->ctx->msaa_buf, mem); | |
| } | |
| priv->first_present = TRUE; | |
| priv->backbuf_rendered = FALSE; | |
| /* redraw the last scene if cached buffer exits */ | |
| GstFlowReturn ret = GST_FLOW_OK; | |
| if (priv->ctx->cached_buf) { | |
| ret = gst_d3d12_window_set_buffer (self, priv->ctx->cached_buf); | |
| if (ret == GST_FLOW_OK) | |
| ret = gst_d3d12_window_present (self); | |
| } | |
| return ret; | |
| } | |
| GstFlowReturn | |
| gst_d3d12_window_prepare (GstD3D12Window * window, GstD3D12Device * device, | |
| guintptr window_handle, guint display_width, guint display_height, | |
| GstCaps * caps, GstStructure * config) | |
| { | |
| auto priv = window->priv; | |
| GstVideoInfo in_info; | |
| GstVideoFormat format = GST_VIDEO_FORMAT_RGBA; | |
| priv->display_format = DXGI_FORMAT_R8G8B8A8_UNORM; | |
| gst_video_info_from_caps (&in_info, caps); | |
| if (GST_VIDEO_INFO_COMP_DEPTH (&in_info, 0) > 8) { | |
| auto device_handle = gst_d3d12_device_get_device_handle (device); | |
| D3D12_FEATURE_DATA_FORMAT_SUPPORT format_support = { }; | |
| const D3D12_FORMAT_SUPPORT1 support_flags = | |
| D3D12_FORMAT_SUPPORT1_RENDER_TARGET | D3D12_FORMAT_SUPPORT1_DISPLAY; | |
| format_support.Format = DXGI_FORMAT_R10G10B10A2_UNORM; | |
| format_support.Support1 = support_flags; | |
| auto hr = device_handle->CheckFeatureSupport (D3D12_FEATURE_FORMAT_SUPPORT, | |
| &format_support, sizeof (format_support)); | |
| if (SUCCEEDED (hr) && (format_support.Support1 & support_flags) | |
| == support_flags) { | |
| priv->display_format = DXGI_FORMAT_R10G10B10A2_UNORM; | |
| format = GST_VIDEO_FORMAT_RGB10A2_LE; | |
| } | |
| } | |
| { | |
| std::lock_guard < std::recursive_mutex > lk (priv->lock); | |
| gst_video_info_set_format (&priv->display_info, format, | |
| display_width, display_height); | |
| } | |
| auto ret = gst_d3d12_window_prepare_hwnd (window, window_handle); | |
| if (ret != GST_FLOW_OK) { | |
| GST_WARNING_OBJECT (window, "Couldn't setup window handle"); | |
| if (config) | |
| gst_structure_free (config); | |
| return ret; | |
| } | |
| std::unique_lock < std::recursive_mutex > lk (priv->lock); | |
| HRESULT hr; | |
| if (window->device != device) { | |
| priv->ctx = nullptr; | |
| gst_clear_object (&window->device); | |
| window->device = (GstD3D12Device *) gst_object_ref (device); | |
| } | |
| if (!priv->ctx) { | |
| auto ctx = std::make_unique < DeviceContext > (device); | |
| if (!ctx->initialized) { | |
| GST_ERROR_OBJECT (window, "Couldn't initialize device context"); | |
| if (config) | |
| gst_structure_free (config); | |
| return GST_FLOW_ERROR; | |
| } | |
| DXGI_SWAP_CHAIN_DESC1 desc = { }; | |
| desc.Format = priv->display_format; | |
| desc.SampleDesc.Count = 1; | |
| desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; | |
| desc.BufferCount = BACK_BUFFER_COUNT; | |
| desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; | |
| auto factory = gst_d3d12_device_get_factory_handle (device); | |
| ComPtr < ID3D12CommandQueue > cq; | |
| gst_d3d12_command_queue_get_handle (ctx->queue, &cq); | |
| ComPtr < IDXGISwapChain1 > swapchain; | |
| hr = factory->CreateSwapChainForHwnd (cq.Get (), priv->hwnd, | |
| &desc, nullptr, nullptr, &swapchain); | |
| if (!gst_d3d12_result (hr, window->device)) { | |
| GST_ERROR_OBJECT (window, "Couldn't create swapchain"); | |
| if (config) | |
| gst_structure_free (config); | |
| return GST_FLOW_ERROR; | |
| } | |
| ComPtr < IDXGIFactory1 > parent_factory; | |
| hr = swapchain->GetParent (IID_PPV_ARGS (&parent_factory)); | |
| if (!gst_d3d12_result (hr, window->device)) { | |
| GST_WARNING_OBJECT (window, "Couldn't get parent factory"); | |
| } else { | |
| hr = parent_factory->MakeWindowAssociation (priv->hwnd, | |
| DXGI_MWA_NO_ALT_ENTER); | |
| if (!gst_d3d12_result (hr, window->device)) { | |
| GST_WARNING_OBJECT (window, "MakeWindowAssociation failed, hr: 0x%x", | |
| (guint) hr); | |
| } | |
| } | |
| hr = swapchain.As (&ctx->swapchain); | |
| if (!gst_d3d12_result (hr, window->device)) { | |
| GST_ERROR_OBJECT (window, "IDXGISwapChain4 interface is unavailable"); | |
| if (config) | |
| gst_structure_free (config); | |
| return GST_FLOW_ERROR; | |
| } | |
| priv->ctx = std::move (ctx); | |
| } else { | |
| priv->ctx->WaitGpu (); | |
| } | |
| priv->input_info = in_info; | |
| priv->crop_rect = CD3DX12_BOX (0, 0, in_info.width, in_info.height); | |
| priv->prev_crop_rect = priv->crop_rect; | |
| gst_clear_object (&priv->ctx->conv); | |
| gst_clear_object (&priv->ctx->comp); | |
| gst_clear_buffer (&priv->ctx->cached_buf); | |
| if (GST_VIDEO_INFO_HAS_ALPHA (&in_info)) { | |
| gst_structure_set (config, GST_D3D12_CONVERTER_OPT_DEST_ALPHA_MODE, | |
| GST_TYPE_D3D12_CONVERTER_ALPHA_MODE, | |
| GST_D3D12_CONVERTER_ALPHA_MODE_PREMULTIPLIED, nullptr); | |
| } | |
| priv->ctx->conv = gst_d3d12_converter_new (window->device, | |
| &priv->input_info, &priv->display_info, nullptr, nullptr, config); | |
| if (!priv->ctx->conv) { | |
| GST_ERROR_OBJECT (window, "Couldn't create converter"); | |
| priv->ctx = nullptr; | |
| return GST_FLOW_ERROR; | |
| } | |
| priv->ctx->comp = gst_d3d12_overlay_compositor_new (window->device, | |
| &priv->display_info); | |
| if (!priv->ctx->comp) { | |
| GST_ERROR_OBJECT (window, "Couldn't create overlay compositor"); | |
| priv->ctx = nullptr; | |
| return GST_FLOW_ERROR; | |
| } | |
| ret = gst_d3d12_window_on_resize (window); | |
| if (ret != GST_FLOW_OK) | |
| return ret; | |
| lk.unlock (); | |
| std::lock_guard < std::mutex > hlk (priv->hwnd_lock); | |
| if (priv->requested_fullscreen != priv->applied_fullscreen) | |
| PostMessageW (priv->hwnd, WM_GST_D3D12_FULLSCREEN, 0, 0); | |
| return GST_FLOW_OK; | |
| } | |
| GstFlowReturn | |
| gst_d3d12_window_set_buffer (GstD3D12Window * window, GstBuffer * buffer) | |
| { | |
| auto priv = window->priv; | |
| if (buffer && priv->state != GST_D3D12_WINDOW_STATE_OPENED) { | |
| GST_ERROR_OBJECT (window, "Window was closed"); | |
| return GST_D3D12_WINDOW_FLOW_CLOSED; | |
| } | |
| std::lock_guard < std::recursive_mutex > lk (priv->lock); | |
| if (!priv->ctx) { | |
| if (!buffer) { | |
| GST_DEBUG_OBJECT (window, "Swapchain was not configured"); | |
| return GST_FLOW_OK; | |
| } | |
| GST_ERROR_OBJECT (window, "Swapchain was not configured"); | |
| return GST_FLOW_ERROR; | |
| } | |
| if (buffer) | |
| gst_buffer_replace (&priv->ctx->cached_buf, buffer); | |
| if (!priv->ctx->cached_buf) | |
| return GST_FLOW_OK; | |
| auto cur_idx = priv->ctx->swapchain->GetCurrentBackBufferIndex (); | |
| auto swapbuf = priv->ctx->swap_buffers[cur_idx]; | |
| auto crop_rect = priv->crop_rect; | |
| auto crop_meta = gst_buffer_get_video_crop_meta (priv->ctx->cached_buf); | |
| if (crop_meta) { | |
| crop_rect = CD3DX12_BOX (crop_meta->x, crop_meta->y, | |
| crop_meta->x + crop_meta->width, crop_meta->y + crop_meta->height); | |
| } | |
| if (crop_rect != priv->prev_crop_rect) { | |
| g_object_set (priv->ctx->conv, "src-x", (gint) crop_rect.left, | |
| "src-y", (gint) crop_rect.top, | |
| "src-width", (gint) (crop_rect.right - crop_rect.left), | |
| "src-height", (gint) (crop_rect.bottom - crop_rect.top), nullptr); | |
| priv->prev_crop_rect = crop_rect; | |
| } | |
| if (priv->first_present) { | |
| GstVideoRectangle dst_rect = { }; | |
| GstVideoRectangle rst_rect = { }; | |
| dst_rect.w = (gint) priv->ctx->buffer_desc.Width; | |
| dst_rect.h = (gint) priv->ctx->buffer_desc.Height; | |
| for (size_t i = 0; i < priv->ctx->swap_buffers.size (); i++) | |
| priv->ctx->swap_buffers[i]->first = true; | |
| if (priv->force_aspect_ratio) { | |
| GstVideoRectangle src_rect = { }; | |
| switch (priv->orientation) { | |
| case GST_VIDEO_ORIENTATION_90R: | |
| case GST_VIDEO_ORIENTATION_90L: | |
| case GST_VIDEO_ORIENTATION_UL_LR: | |
| case GST_VIDEO_ORIENTATION_UR_LL: | |
| src_rect.w = GST_VIDEO_INFO_HEIGHT (&priv->display_info); | |
| src_rect.h = GST_VIDEO_INFO_WIDTH (&priv->display_info); | |
| break; | |
| default: | |
| src_rect.w = GST_VIDEO_INFO_WIDTH (&priv->display_info); | |
| src_rect.h = GST_VIDEO_INFO_HEIGHT (&priv->display_info); | |
| break; | |
| } | |
| gst_video_sink_center_rect (src_rect, dst_rect, &rst_rect, TRUE); | |
| } else { | |
| rst_rect = dst_rect; | |
| } | |
| priv->output_rect = rst_rect; | |
| priv->dirty_rect.left = rst_rect.x; | |
| priv->dirty_rect.top = rst_rect.y; | |
| priv->dirty_rect.right = rst_rect.x + rst_rect.w; | |
| priv->dirty_rect.bottom = rst_rect.y + rst_rect.h; | |
| g_object_set (priv->ctx->conv, "dest-x", priv->output_rect.x, | |
| "dest-y", priv->output_rect.y, "dest-width", priv->output_rect.w, | |
| "dest-height", priv->output_rect.h, nullptr); | |
| if (gst_d3d12_need_transform (priv->rotation_x, priv->rotation_y, | |
| priv->rotation_z, priv->scale_x, priv->scale_y)) { | |
| gst_d3d12_converter_apply_transform (priv->ctx->conv, priv->orientation, | |
| priv->output_rect.w, priv->output_rect.h, priv->fov, priv->ortho, | |
| priv->rotation_x, priv->rotation_y, priv->rotation_z, | |
| priv->scale_x, priv->scale_y); | |
| } else { | |
| g_object_set (priv->ctx->conv, | |
| "video-direction", priv->orientation, nullptr); | |
| } | |
| gst_d3d12_overlay_compositor_update_viewport (priv->ctx->comp, | |
| &priv->output_rect); | |
| } | |
| g_object_set (priv->ctx->conv, "fill-border", swapbuf->first, nullptr); | |
| swapbuf->first = FALSE; | |
| gst_d3d12_overlay_compositor_upload (priv->ctx->comp, priv->ctx->cached_buf); | |
| GstD3D12CommandAllocator *gst_ca; | |
| if (!gst_d3d12_command_allocator_pool_acquire (priv->ctx->ca_pool, &gst_ca)) { | |
| GST_ERROR_OBJECT (window, "Couldn't acquire command allocator"); | |
| return GST_FLOW_ERROR; | |
| } | |
| ComPtr < ID3D12CommandAllocator > ca; | |
| gst_d3d12_command_allocator_get_handle (gst_ca, &ca); | |
| auto hr = ca->Reset (); | |
| if (!gst_d3d12_result (hr, window->device)) { | |
| GST_ERROR_OBJECT (window, "Couldn't reset command list"); | |
| gst_d3d12_command_allocator_unref (gst_ca); | |
| return GST_FLOW_ERROR; | |
| } | |
| ComPtr < ID3D12GraphicsCommandList > cl; | |
| if (!priv->ctx->cl) { | |
| auto device = gst_d3d12_device_get_device_handle (priv->ctx->device); | |
| hr = device->CreateCommandList (0, D3D12_COMMAND_LIST_TYPE_DIRECT, | |
| ca.Get (), nullptr, IID_PPV_ARGS (&cl)); | |
| if (!gst_d3d12_result (hr, priv->ctx->device)) { | |
| GST_ERROR_OBJECT (window, "Couldn't create command list"); | |
| gst_d3d12_command_allocator_unref (gst_ca); | |
| return GST_FLOW_ERROR; | |
| } | |
| priv->ctx->cl = cl; | |
| } else { | |
| cl = priv->ctx->cl; | |
| hr = cl->Reset (ca.Get (), nullptr); | |
| if (!gst_d3d12_result (hr, priv->ctx->device)) { | |
| GST_ERROR_OBJECT (window, "Couldn't reset command list"); | |
| gst_d3d12_command_allocator_unref (gst_ca); | |
| return GST_FLOW_ERROR; | |
| } | |
| } | |
| GstD3D12FenceData *fence_data; | |
| gst_d3d12_fence_data_pool_acquire (priv->fence_data_pool, &fence_data); | |
| gst_d3d12_fence_data_add_notify_mini_object (fence_data, gst_ca); | |
| auto mem = (GstD3D12Memory *) gst_buffer_peek_memory (swapbuf->backbuf, 0); | |
| auto backbuf_texture = gst_d3d12_memory_get_resource_handle (mem); | |
| ID3D12Resource *msaa_resource = nullptr; | |
| GstBuffer *conv_outbuf = swapbuf->backbuf; | |
| if (priv->ctx->msaa_buf) { | |
| conv_outbuf = priv->ctx->msaa_buf; | |
| mem = (GstD3D12Memory *) gst_buffer_peek_memory (priv->ctx->msaa_buf, 0); | |
| msaa_resource = gst_d3d12_memory_get_resource_handle (mem); | |
| /* MSAA resource must be render target state here already */ | |
| } else { | |
| D3D12_RESOURCE_BARRIER barrier = | |
| CD3DX12_RESOURCE_BARRIER::Transition (backbuf_texture, | |
| D3D12_RESOURCE_STATE_COMMON, | |
| D3D12_RESOURCE_STATE_RENDER_TARGET); | |
| cl->ResourceBarrier (1, &barrier); | |
| } | |
| if (!gst_d3d12_converter_convert_buffer (priv->ctx->conv, | |
| priv->ctx->cached_buf, conv_outbuf, fence_data, cl.Get ())) { | |
| GST_ERROR_OBJECT (window, "Couldn't build convert command"); | |
| gst_d3d12_fence_data_unref (fence_data); | |
| return GST_FLOW_ERROR; | |
| } | |
| if (!gst_d3d12_overlay_compositor_draw (priv->ctx->comp, | |
| conv_outbuf, fence_data, cl.Get ())) { | |
| GST_ERROR_OBJECT (window, "Couldn't build overlay command"); | |
| gst_d3d12_fence_data_unref (fence_data); | |
| return GST_FLOW_ERROR; | |
| } | |
| if (msaa_resource) { | |
| std::vector < D3D12_RESOURCE_BARRIER > barriers; | |
| barriers.push_back (CD3DX12_RESOURCE_BARRIER::Transition (msaa_resource, | |
| D3D12_RESOURCE_STATE_RENDER_TARGET, | |
| D3D12_RESOURCE_STATE_RESOLVE_SOURCE)); | |
| barriers.push_back (CD3DX12_RESOURCE_BARRIER::Transition (backbuf_texture, | |
| D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_RESOLVE_DEST)); | |
| cl->ResourceBarrier (barriers.size (), barriers.data ()); | |
| cl->ResolveSubresource (backbuf_texture, 0, msaa_resource, 0, | |
| priv->display_format); | |
| barriers.clear (); | |
| barriers.push_back (CD3DX12_RESOURCE_BARRIER::Transition (msaa_resource, | |
| D3D12_RESOURCE_STATE_RESOLVE_SOURCE, | |
| D3D12_RESOURCE_STATE_RENDER_TARGET)); | |
| barriers.push_back (CD3DX12_RESOURCE_BARRIER::Transition (backbuf_texture, | |
| D3D12_RESOURCE_STATE_RESOLVE_DEST, D3D12_RESOURCE_STATE_COMMON)); | |
| cl->ResourceBarrier (barriers.size (), barriers.data ()); | |
| } else { | |
| D3D12_RESOURCE_BARRIER barrier = | |
| CD3DX12_RESOURCE_BARRIER::Transition (backbuf_texture, | |
| D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_COMMON); | |
| cl->ResourceBarrier (1, &barrier); | |
| } | |
| hr = cl->Close (); | |
| if (!gst_d3d12_result (hr, priv->ctx->device)) { | |
| GST_ERROR_OBJECT (window, "Couldn't close command list"); | |
| gst_d3d12_fence_data_unref (fence_data); | |
| return GST_FLOW_ERROR; | |
| } | |
| guint64 max_fence_val = 0; | |
| auto num_mem = gst_buffer_n_memory (priv->ctx->cached_buf); | |
| for (guint i = 0; i < num_mem; i++) { | |
| mem = (GstD3D12Memory *) gst_buffer_peek_memory (priv->ctx->cached_buf, 0); | |
| if (mem->fence_value > max_fence_val) | |
| max_fence_val = mem->fence_value; | |
| } | |
| auto completed = gst_d3d12_device_get_completed_value (priv->ctx->device, | |
| D3D12_COMMAND_LIST_TYPE_DIRECT); | |
| if (completed < max_fence_val) { | |
| auto device_queue = gst_d3d12_device_get_command_queue (priv->ctx->device, | |
| D3D12_COMMAND_LIST_TYPE_DIRECT); | |
| ComPtr < ID3D12Fence > fence; | |
| gst_d3d12_command_queue_get_fence (device_queue, &fence); | |
| gst_d3d12_command_queue_execute_wait (priv->ctx->queue, fence.Get (), | |
| max_fence_val); | |
| } | |
| ID3D12CommandList *cmd_list[] = { cl.Get () }; | |
| hr = gst_d3d12_command_queue_execute_command_lists (priv->ctx->queue, | |
| 1, cmd_list, &priv->ctx->fence_val); | |
| if (!gst_d3d12_result (hr, priv->ctx->device)) { | |
| GST_ERROR_OBJECT (window, "Signal failed"); | |
| gst_d3d12_fence_data_unref (fence_data); | |
| return GST_FLOW_ERROR; | |
| } | |
| gst_d3d12_command_queue_set_notify (priv->ctx->queue, priv->ctx->fence_val, | |
| fence_data, (GDestroyNotify) gst_d3d12_fence_data_unref); | |
| priv->backbuf_rendered = TRUE; | |
| return GST_FLOW_OK; | |
| } | |
| GstFlowReturn | |
| gst_d3d12_window_present (GstD3D12Window * window) | |
| { | |
| auto priv = window->priv; | |
| if (priv->state != GST_D3D12_WINDOW_STATE_OPENED) { | |
| GST_ERROR_OBJECT (window, "Window was closed"); | |
| return GST_D3D12_WINDOW_FLOW_CLOSED; | |
| } | |
| std::lock_guard < std::recursive_mutex > lk (priv->lock); | |
| if (!priv->ctx) { | |
| GST_ERROR_OBJECT (window, "Swapchain was not configured"); | |
| return GST_FLOW_ERROR; | |
| } | |
| if (priv->backbuf_rendered) { | |
| DXGI_PRESENT_PARAMETERS params = { }; | |
| if (!priv->first_present) { | |
| params.DirtyRectsCount = 1; | |
| params.pDirtyRects = &priv->dirty_rect; | |
| } | |
| auto hr = priv->ctx->swapchain->Present1 (0, | |
| DXGI_PRESENT_DO_NOT_WAIT, ¶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); | |
| } | |
| } |