/* * GStreamer * Copyright (C) 2008 Julien Isorce * Copyright (C) 2012 Matthew Waters * Copyright (C) 2019 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 "gstd3d11window_win32.h" #include /* *INDENT-OFF* */ using namespace Microsoft::WRL; /* *INDENT-ON* */ GST_DEBUG_CATEGORY_EXTERN (gst_d3d11_window_debug); #define GST_CAT_DEFAULT gst_d3d11_window_debug G_LOCK_DEFINE_STATIC (create_lock); G_LOCK_DEFINE_STATIC (get_instance_lock); #define EXTERNAL_PROC_PROP_NAME "d3d11_window_external_proc" #define D3D11_WINDOW_PROP_NAME "gst_d3d11_window_win32_object" #define WM_GST_D3D11_FULLSCREEN (WM_USER + 1) #define WM_GST_D3D11_CONSTRUCT_INTERNAL_WINDOW (WM_USER + 2) #define WM_GST_D3D11_DESTROY_INTERNAL_WINDOW (WM_USER + 3) #define WM_GST_D3D11_MOVE_WINDOW (WM_USER + 4) #define WM_GST_D3D11_SHOW_WINDOW (WM_USER + 5) #define WS_GST_D3D11 (WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW) static LRESULT CALLBACK window_proc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); static LRESULT FAR PASCAL sub_class_proc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); typedef enum { GST_D3D11_WINDOW_WIN32_OVERLAY_STATE_NONE = 0, GST_D3D11_WINDOW_WIN32_OVERLAY_STATE_OPENED, GST_D3D11_WINDOW_WIN32_OVERLAY_STATE_CLOSED, } GstD3D11WindowWin32OverlayState; struct _GstD3D11WindowWin32 { GstD3D11Window parent; SRWLOCK lock; CONDITION_VARIABLE cond; GMainContext *main_context; GMainLoop *loop; gboolean visible; GSource *msg_source; GIOChannel *msg_io_channel; GThread *thread; GThread *internal_hwnd_thread; HWND internal_hwnd; HWND external_hwnd; GstD3D11WindowWin32OverlayState overlay_state; HDC device_handle; gboolean have_swapchain1; /* atomic */ gint pending_fullscreen_count; gint pending_move_window; /* fullscreen related */ LONG restore_style; WINDOWPLACEMENT restore_placement; /* Handle set_render_rectangle */ GstVideoRectangle render_rect; gboolean flushing; gboolean setup_external_hwnd; }; #define gst_d3d11_window_win32_parent_class parent_class G_DEFINE_TYPE (GstD3D11WindowWin32, gst_d3d11_window_win32, GST_TYPE_D3D11_WINDOW); static void gst_d3d11_window_win32_constructed (GObject * object); static void gst_d3d11_window_win32_dispose (GObject * object); static void gst_d3d11_window_win32_show (GstD3D11Window * window); static void gst_d3d11_window_win32_update_swap_chain (GstD3D11Window * window); static void gst_d3d11_window_win32_change_fullscreen_mode (GstD3D11Window * window); static gboolean gst_d3d11_window_win32_create_swap_chain (GstD3D11Window * window, DXGI_FORMAT format, guint width, guint height, guint swapchain_flags, IDXGISwapChain ** swap_chain); static GstFlowReturn gst_d3d11_window_win32_present (GstD3D11Window * window, guint present_flags); static gpointer gst_d3d11_window_win32_thread_func (gpointer data); static gboolean gst_d3d11_window_win32_create_internal_window (GstD3D11WindowWin32 * self); static void gst_d3d11_window_win32_destroy_internal_window (HWND hwnd); static void gst_d3d11_window_win32_release_external_handle (GstD3D11WindowWin32 * self); static void gst_d3d11_window_win32_on_resize (GstD3D11Window * window, guint width, guint height); static GstFlowReturn gst_d3d11_window_win32_prepare (GstD3D11Window * window, guint display_width, guint display_height, GstCaps * caps, GstStructure * config, DXGI_FORMAT display_format, GError ** error); static void gst_d3d11_window_win32_unprepare (GstD3D11Window * window); static void gst_d3d11_window_win32_set_render_rectangle (GstD3D11Window * window, const GstVideoRectangle * rect); static void gst_d3d11_window_win32_set_title (GstD3D11Window * window, const gchar * title); static gboolean gst_d3d11_window_win32_unlock (GstD3D11Window * window); static gboolean gst_d3d11_window_win32_unlock_stop (GstD3D11Window * window); static GstFlowReturn gst_d3d11_window_win32_set_external_handle (GstD3D11WindowWin32 * self, HWND hwnd); static void gst_d3d11_window_win32_class_init (GstD3D11WindowWin32Class * klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GstD3D11WindowClass *window_class = GST_D3D11_WINDOW_CLASS (klass); gobject_class->constructed = gst_d3d11_window_win32_constructed; gobject_class->dispose = gst_d3d11_window_win32_dispose; window_class->show = GST_DEBUG_FUNCPTR (gst_d3d11_window_win32_show); window_class->update_swap_chain = GST_DEBUG_FUNCPTR (gst_d3d11_window_win32_update_swap_chain); window_class->change_fullscreen_mode = GST_DEBUG_FUNCPTR (gst_d3d11_window_win32_change_fullscreen_mode); window_class->create_swap_chain = GST_DEBUG_FUNCPTR (gst_d3d11_window_win32_create_swap_chain); window_class->present = GST_DEBUG_FUNCPTR (gst_d3d11_window_win32_present); window_class->on_resize = GST_DEBUG_FUNCPTR (gst_d3d11_window_win32_on_resize); window_class->prepare = GST_DEBUG_FUNCPTR (gst_d3d11_window_win32_prepare); window_class->unprepare = GST_DEBUG_FUNCPTR (gst_d3d11_window_win32_unprepare); window_class->set_render_rectangle = GST_DEBUG_FUNCPTR (gst_d3d11_window_win32_set_render_rectangle); window_class->set_title = GST_DEBUG_FUNCPTR (gst_d3d11_window_win32_set_title); window_class->unlock = GST_DEBUG_FUNCPTR (gst_d3d11_window_win32_unlock); window_class->unlock_stop = GST_DEBUG_FUNCPTR (gst_d3d11_window_win32_unlock_stop); } static void gst_d3d11_window_win32_init (GstD3D11WindowWin32 * self) { self->main_context = g_main_context_new (); self->restore_placement.length = sizeof (WINDOWPLACEMENT); } static void gst_d3d11_window_win32_constructed (GObject * object) { GstD3D11Window *window = GST_D3D11_WINDOW (object); GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (object); if (window->external_handle) { /* Will setup internal child window on ::prepare() */ self->setup_external_hwnd = TRUE; window->initialized = TRUE; goto done; } AcquireSRWLockExclusive (&self->lock); self->loop = g_main_loop_new (self->main_context, FALSE); self->thread = g_thread_new ("GstD3D11WindowWin32", (GThreadFunc) gst_d3d11_window_win32_thread_func, self); while (!g_main_loop_is_running (self->loop)) SleepConditionVariableSRW (&self->cond, &self->lock, INFINITE, 0); ReleaseSRWLockExclusive (&self->lock); done: G_OBJECT_CLASS (parent_class)->constructed (object); } static void gst_d3d11_window_win32_dispose (GObject * object) { GST_DEBUG_OBJECT (object, "dispose"); gst_d3d11_window_win32_unprepare (GST_D3D11_WINDOW (object)); G_OBJECT_CLASS (parent_class)->dispose (object); } static GstFlowReturn gst_d3d11_window_win32_prepare (GstD3D11Window * window, guint display_width, guint display_height, GstCaps * caps, GstStructure * config, DXGI_FORMAT display_format, GError ** error) { GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (window); HWND hwnd; GstFlowReturn ret; if (!self->setup_external_hwnd) goto done; hwnd = (HWND) window->external_handle; if (!IsWindow (hwnd)) { gst_structure_free (config); GST_ERROR_OBJECT (self, "Invalid window handle"); g_set_error (error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_FAILED, "Invalid window handle"); return GST_FLOW_ERROR; } GST_DEBUG_OBJECT (self, "Preparing external handle"); ret = gst_d3d11_window_win32_set_external_handle (self, hwnd); if (ret != GST_FLOW_OK) { gst_structure_free (config); if (ret == GST_FLOW_FLUSHING) { GST_WARNING_OBJECT (self, "Flushing"); return GST_FLOW_FLUSHING; } GST_ERROR_OBJECT (self, "Couldn't configure internal window"); g_set_error (error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_FAILED, "Window handle configuration failed"); return GST_FLOW_ERROR; } GST_DEBUG_OBJECT (self, "External handle got prepared"); self->setup_external_hwnd = FALSE; done: return GST_D3D11_WINDOW_CLASS (parent_class)->prepare (window, display_width, display_height, caps, config, display_format, error); } static void gst_d3d11_window_win32_unprepare (GstD3D11Window * window) { GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (window); GST_DEBUG_OBJECT (self, "unprepare"); if (self->external_hwnd) { G_LOCK (get_instance_lock); gst_d3d11_window_win32_release_external_handle (self); RemovePropA (self->internal_hwnd, D3D11_WINDOW_PROP_NAME); G_UNLOCK (get_instance_lock); if (self->internal_hwnd_thread == g_thread_self ()) { /* State changing thread is identical to internal window thread. * window can be closed here */ GST_INFO_OBJECT (self, "Closing internal window immediately"); gst_d3d11_window_win32_destroy_internal_window (self->internal_hwnd); } else if (self->internal_hwnd) { /* 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 (self, "Posting custom destory message"); PostMessageA (self->internal_hwnd, WM_GST_D3D11_DESTROY_INTERNAL_WINDOW, 0, 0); } self->external_hwnd = NULL; self->internal_hwnd = NULL; self->internal_hwnd_thread = NULL; } if (self->loop) { g_main_loop_quit (self->loop); } if (self->thread) { g_thread_join (self->thread); self->thread = NULL; } if (self->loop) { g_main_loop_unref (self->loop); self->loop = NULL; } if (self->main_context) { g_main_context_unref (self->main_context); self->main_context = NULL; } } static GstD3D11WindowWin32 * gst_d3d11_window_win32_hwnd_get_instance (HWND hwnd) { HANDLE handle; G_LOCK (get_instance_lock); handle = GetPropA (hwnd, D3D11_WINDOW_PROP_NAME); if (handle) handle = gst_object_ref (handle); G_UNLOCK (get_instance_lock); return (GstD3D11WindowWin32 *) handle; } static void gst_d3d11_window_win32_set_render_rectangle (GstD3D11Window * window, const GstVideoRectangle * rect) { GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (window); self->render_rect = *rect; if (self->external_hwnd && self->internal_hwnd) { g_atomic_int_add (&self->pending_move_window, 1); if (self->internal_hwnd_thread == g_thread_self ()) { /* We are on message pumping thread already, handle this synchroniously */ SendMessageA (self->internal_hwnd, WM_GST_D3D11_MOVE_WINDOW, 0, 0); } else { /* Post message to message pumping thread. Handling HWND specific message * on message pumping thread is not a worst idea in generall */ PostMessageA (self->internal_hwnd, WM_GST_D3D11_MOVE_WINDOW, 0, 0); } } else { /* XXX: Not sure what's expected behavior if we are drawing on internal * HWND but user wants to specify rectangle. * * - Should we move window to corresponding desktop coordinates ? * - Or should crop correspondingly by modifying viewport of * render target view of swapchian's backbuffer or so ? * - Or should we ignore set_render_rectangle if we are drawing on * internal HWND without external HWND ? */ } } static void gst_d3d11_window_win32_set_title (GstD3D11Window * window, const gchar * title) { GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (window); /* Do this only when we are rendring on our own HWND */ if (!self->external_hwnd && self->internal_hwnd) { gunichar2 *str = g_utf8_to_utf16 (title, -1, nullptr, nullptr, nullptr); if (str) { SetWindowTextW (self->internal_hwnd, (LPCWSTR) str); g_free (str); } } } static gboolean gst_d3d11_window_win32_unlock (GstD3D11Window * window) { GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (window); GstD3D11SRWLockGuard lk (&self->lock); GST_DEBUG_OBJECT (self, "Unlock"); self->flushing = TRUE; WakeAllConditionVariable (&self->cond); return TRUE; } static gboolean gst_d3d11_window_win32_unlock_stop (GstD3D11Window * window) { GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (window); GstD3D11SRWLockGuard lk (&self->lock); GST_DEBUG_OBJECT (self, "Unlock stop"); self->flushing = FALSE; WakeAllConditionVariable (&self->cond); return TRUE; } static gboolean running_cb (gpointer user_data) { GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (user_data); GST_TRACE_OBJECT (self, "Main loop running now"); AcquireSRWLockExclusive (&self->lock); WakeConditionVariable (&self->cond); ReleaseSRWLockExclusive (&self->lock); return G_SOURCE_REMOVE; } static gboolean msg_cb (GIOChannel * source, GIOCondition condition, gpointer data) { MSG msg; if (!PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) return G_SOURCE_CONTINUE; TranslateMessage (&msg); DispatchMessage (&msg); return G_SOURCE_CONTINUE; } static gpointer gst_d3d11_window_win32_thread_func (gpointer data) { GstD3D11Window *window = GST_D3D11_WINDOW (data); GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (data); GSource *source; GST_DEBUG_OBJECT (self, "Enter loop"); g_main_context_push_thread_default (self->main_context); window->initialized = gst_d3d11_window_win32_create_internal_window (self); /* Watching and dispatching all messages on this thread */ self->msg_io_channel = g_io_channel_win32_new_messages (0); self->msg_source = g_io_create_watch (self->msg_io_channel, G_IO_IN); g_source_set_callback (self->msg_source, (GSourceFunc) msg_cb, self, NULL); g_source_attach (self->msg_source, self->main_context); source = g_idle_source_new (); g_source_set_callback (source, (GSourceFunc) running_cb, self, NULL); g_source_attach (source, self->main_context); g_source_unref (source); g_main_loop_run (self->loop); RemovePropA (self->internal_hwnd, D3D11_WINDOW_PROP_NAME); gst_d3d11_window_win32_destroy_internal_window (self->internal_hwnd); self->internal_hwnd = NULL; self->internal_hwnd_thread = NULL; if (self->msg_source) { g_source_destroy (self->msg_source); g_source_unref (self->msg_source); self->msg_source = NULL; } if (self->msg_io_channel) { g_io_channel_unref (self->msg_io_channel); self->msg_io_channel = NULL; } g_main_context_pop_thread_default (self->main_context); GST_DEBUG_OBJECT (self, "Exit loop"); return NULL; } static void gst_d3d11_window_win32_destroy_internal_window (HWND hwnd) { if (!hwnd) return; ShowWindow (hwnd, SW_HIDE); SetParent (hwnd, NULL); GST_INFO ("Destroying internal window %" G_GUINTPTR_FORMAT, (guintptr) hwnd); if (!DestroyWindow (hwnd)) g_critical ("failed to destroy window %" G_GUINTPTR_FORMAT ", 0x%x", (guintptr) hwnd, (guint) GetLastError ()); } static GstFlowReturn gst_d3d11_window_win32_set_external_handle (GstD3D11WindowWin32 * self, HWND hwnd) { WNDPROC external_window_proc; GstFlowReturn ret = GST_FLOW_OK; GstD3D11SRWLockGuard lk (&self->lock); self->overlay_state = GST_D3D11_WINDOW_WIN32_OVERLAY_STATE_NONE; self->external_hwnd = hwnd; G_LOCK (get_instance_lock); external_window_proc = (WNDPROC) GetWindowLongPtrA (hwnd, GWLP_WNDPROC); GST_DEBUG_OBJECT (self, "set external window %" G_GUINTPTR_FORMAT ", original window procedure %p", (guintptr) hwnd, external_window_proc); g_assert (external_window_proc != sub_class_proc); g_warn_if_fail (GetPropA (hwnd, EXTERNAL_PROC_PROP_NAME) == NULL); g_warn_if_fail (GetPropA (hwnd, D3D11_WINDOW_PROP_NAME) == NULL); SetPropA (hwnd, EXTERNAL_PROC_PROP_NAME, (HANDLE) external_window_proc); SetPropA (hwnd, D3D11_WINDOW_PROP_NAME, self); SetWindowLongPtrA (hwnd, GWLP_WNDPROC, (LONG_PTR) sub_class_proc); G_UNLOCK (get_instance_lock); /* SendMessage() may cause deadlock if parent window thread is busy * for changing pipeline's state. Post our message instead, and wait for * the parent window's thread or flushing */ PostMessageA (hwnd, WM_GST_D3D11_CONSTRUCT_INTERNAL_WINDOW, 0, 0); while (self->external_hwnd && self->overlay_state == GST_D3D11_WINDOW_WIN32_OVERLAY_STATE_NONE && !self->flushing) { SleepConditionVariableSRW (&self->cond, &self->lock, INFINITE, 0); } if (self->overlay_state != GST_D3D11_WINDOW_WIN32_OVERLAY_STATE_OPENED) { if (self->flushing) ret = GST_FLOW_FLUSHING; else ret = GST_FLOW_ERROR; } return ret; } static void gst_d3d11_window_win32_release_external_handle (GstD3D11WindowWin32 * self) { WNDPROC external_proc; HWND hwnd = self->external_hwnd; if (!hwnd) return; self->external_hwnd = NULL; external_proc = (WNDPROC) GetPropA (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); RemovePropA (hwnd, EXTERNAL_PROC_PROP_NAME); RemovePropA (hwnd, D3D11_WINDOW_PROP_NAME); if (!SetWindowLongPtrA (hwnd, GWLP_WNDPROC, (LONG_PTR) external_proc)) GST_WARNING_OBJECT (self, "Couldn't restore original window procedure"); } static gboolean gst_d3d11_window_win32_create_internal_window (GstD3D11WindowWin32 * self) { WNDCLASSEXA wc; ATOM atom = 0; HINSTANCE hinstance = GetModuleHandleA (NULL); GST_LOG_OBJECT (self, "Attempting to create a win32 window"); G_LOCK (create_lock); atom = GetClassInfoExA (hinstance, "GSTD3D11", &wc); if (atom == 0) { GST_LOG_OBJECT (self, "Register internal window class"); ZeroMemory (&wc, sizeof (WNDCLASSEXA)); wc.cbSize = sizeof (WNDCLASSEXA); wc.lpfnWndProc = window_proc; wc.hInstance = hinstance; wc.hIcon = LoadIcon (NULL, IDI_WINLOGO); wc.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; wc.hCursor = LoadCursor (NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH) GetStockObject (BLACK_BRUSH); wc.lpszClassName = "GSTD3D11"; atom = RegisterClassExA (&wc); if (atom == 0) { G_UNLOCK (create_lock); GST_ERROR_OBJECT (self, "Failed to register window class 0x%x", (unsigned int) GetLastError ()); return FALSE; } } else { GST_LOG_OBJECT (self, "window class was already registered"); } self->device_handle = 0; self->internal_hwnd = 0; self->visible = FALSE; self->internal_hwnd = CreateWindowExA (0, "GSTD3D11", "Direct3D11 renderer", WS_GST_D3D11, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, (HWND) NULL, (HMENU) NULL, hinstance, self); G_UNLOCK (create_lock); if (!self->internal_hwnd) { GST_ERROR_OBJECT (self, "Failed to create d3d11 window"); return FALSE; } GST_DEBUG_OBJECT (self, "d3d11 window created: %" G_GUINTPTR_FORMAT, (guintptr) self->internal_hwnd); /* device_handle is set in the window_proc */ if (!self->device_handle) { GST_ERROR_OBJECT (self, "device handle is not available"); return FALSE; } GST_LOG_OBJECT (self, "Created a internal d3d11 window %p", self->internal_hwnd); self->internal_hwnd_thread = g_thread_self (); return TRUE; } /* always called from window thread */ static void gst_d3d11_window_win32_change_fullscreen_mode_internal (GstD3D11WindowWin32 * self) { GstD3D11Window *window = GST_D3D11_WINDOW (self); HWND hwnd = self->external_hwnd ? self->external_hwnd : self->internal_hwnd; if (!window->swap_chain) return; if (window->requested_fullscreen == window->fullscreen) return; GST_DEBUG_OBJECT (self, "Change mode to %s", window->requested_fullscreen ? "fullscreen" : "windowed"); window->fullscreen = !window->fullscreen; if (!window->fullscreen) { /* Restore the window's attributes and size */ SetWindowLongA (hwnd, GWL_STYLE, self->restore_style); SetWindowPlacement (hwnd, &self->restore_placement); } else { ComPtr < IDXGIOutput > output; DXGI_OUTPUT_DESC output_desc; IDXGISwapChain *swap_chain = window->swap_chain; /* remember current placement to restore window later */ GetWindowPlacement (hwnd, &self->restore_placement); /* show window before change style */ ShowWindow (hwnd, SW_SHOW); self->restore_style = GetWindowLong (hwnd, GWL_STYLE); /* Make the window borderless so that the client area can fill the screen */ SetWindowLongA (hwnd, GWL_STYLE, self->restore_style & ~(WS_CAPTION | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_SYSMENU | WS_THICKFRAME | WS_MAXIMIZE)); swap_chain->GetContainingOutput (&output); output->GetDesc (&output_desc); SetWindowPos (hwnd, HWND_TOPMOST, 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"); } static void gst_d3d11_window_win32_on_key_event (GstD3D11WindowWin32 * self, HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { GstD3D11Window *window = GST_D3D11_WINDOW (self); gunichar2 wcrep[128]; const gchar *event; if (!window->enable_navigation_events) return; if (GetKeyNameTextW (lParam, (LPWSTR) wcrep, 128)) { gchar *utfrep = g_utf16_to_utf8 (wcrep, 128, NULL, NULL, NULL); if (utfrep) { if (uMsg == WM_KEYDOWN) event = "key-press"; else event = "key-release"; gst_d3d11_window_on_key_event (window, event, utfrep); g_free (utfrep); } } } static void gst_d3d11_window_win32_on_mouse_event (GstD3D11WindowWin32 * self, HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { GstD3D11Window *window = GST_D3D11_WINDOW (self); gint button; const gchar *event = NULL; if (!window->enable_navigation_events) return; switch (uMsg) { 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: break; } if (event) gst_d3d11_window_on_mouse_event (window, event, button, (gdouble) LOWORD (lParam), (gdouble) HIWORD (lParam)); } static void gst_d3d11_window_win32_handle_window_proc (GstD3D11WindowWin32 * self, HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { GstD3D11Window *window = GST_D3D11_WINDOW (self); switch (uMsg) { case WM_SIZE: gst_d3d11_window_win32_on_resize (window, 0, 0); break; case WM_CLOSE: if (self->internal_hwnd) { RemovePropA (self->internal_hwnd, D3D11_WINDOW_PROP_NAME); gst_d3d11_window_win32_destroy_internal_window (hWnd); self->overlay_state = GST_D3D11_WINDOW_WIN32_OVERLAY_STATE_CLOSED; self->internal_hwnd = NULL; self->internal_hwnd_thread = NULL; } break; case WM_KEYDOWN: case WM_KEYUP: gst_d3d11_window_win32_on_key_event (self, hWnd, uMsg, wParam, lParam); break; case WM_LBUTTONDOWN: case WM_LBUTTONUP: case WM_RBUTTONDOWN: case WM_RBUTTONUP: case WM_MBUTTONDOWN: case WM_MBUTTONUP: case WM_MOUSEMOVE: gst_d3d11_window_win32_on_mouse_event (self, hWnd, uMsg, wParam, lParam); break; case WM_SYSKEYDOWN: if ((window->fullscreen_toggle_mode & GST_D3D11_WINDOW_FULLSCREEN_TOGGLE_MODE_ALT_ENTER) == GST_D3D11_WINDOW_FULLSCREEN_TOGGLE_MODE_ALT_ENTER) { WORD state = GetKeyState (VK_RETURN); BYTE high = HIBYTE (state); if (high & 0x1) { window->requested_fullscreen = !window->fullscreen; gst_d3d11_window_win32_change_fullscreen_mode_internal (self); } } break; case WM_GST_D3D11_FULLSCREEN: if (g_atomic_int_get (&self->pending_fullscreen_count)) { g_atomic_int_dec_and_test (&self->pending_fullscreen_count); if ((window->fullscreen_toggle_mode & GST_D3D11_WINDOW_FULLSCREEN_TOGGLE_MODE_PROPERTY) == GST_D3D11_WINDOW_FULLSCREEN_TOGGLE_MODE_PROPERTY) gst_d3d11_window_win32_change_fullscreen_mode_internal (self); } break; case WM_GST_D3D11_MOVE_WINDOW: if (g_atomic_int_get (&self->pending_move_window)) { g_atomic_int_set (&self->pending_move_window, 0); if (self->internal_hwnd && self->external_hwnd) { if (self->render_rect.w < 0 || self->render_rect.h < 0) { RECT rect; /* Reset render rect and back to full-size window */ if (GetClientRect (self->external_hwnd, &rect)) { MoveWindow (self->internal_hwnd, 0, 0, rect.right - rect.left, rect.bottom - rect.top, FALSE); } } else { MoveWindow (self->internal_hwnd, self->render_rect.x, self->render_rect.y, self->render_rect.w, self->render_rect.h, FALSE); } } } break; case WM_GST_D3D11_SHOW_WINDOW: ShowWindow (self->internal_hwnd, SW_SHOW); break; default: break; } return; } static LRESULT CALLBACK window_proc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { GstD3D11WindowWin32 *self; if (uMsg == WM_GST_D3D11_DESTROY_INTERNAL_WINDOW) { GST_INFO ("Handle destroy window message"); gst_d3d11_window_win32_destroy_internal_window (hWnd); return 0; } if (uMsg == WM_CREATE) { self = GST_D3D11_WINDOW_WIN32 (((LPCREATESTRUCT) lParam)->lpCreateParams); GST_LOG_OBJECT (self, "WM_CREATE"); self->device_handle = GetDC (hWnd); /* Do this, otherwise we hang on exit. We can still use it (due to the * CS_OWNDC flag in the WindowClass) after we have Released. */ ReleaseDC (hWnd, self->device_handle); SetPropA (hWnd, D3D11_WINDOW_PROP_NAME, self); } else if ((self = gst_d3d11_window_win32_hwnd_get_instance (hWnd))) { g_assert (self->internal_hwnd == hWnd); gst_d3d11_window_win32_handle_window_proc (self, hWnd, uMsg, wParam, lParam); switch (uMsg) { case WM_SIZE: /* We handled this event already */ gst_object_unref (self); return 0; case WM_NCHITTEST: /* 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 */ if (self->external_hwnd) { gst_object_unref (self); return (LRESULT) HTTRANSPARENT; } break; default: break; } gst_object_unref (self); } return DefWindowProcA (hWnd, uMsg, wParam, lParam); } static LRESULT FAR PASCAL sub_class_proc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { WNDPROC external_window_proc = (WNDPROC) GetPropA (hWnd, EXTERNAL_PROC_PROP_NAME); GstD3D11WindowWin32 *self = gst_d3d11_window_win32_hwnd_get_instance (hWnd); if (self == NULL || self->flushing) { GST_DEBUG ("No object attached to the window, chain up to default"); gst_clear_object (&self); return CallWindowProcA (external_window_proc, hWnd, uMsg, wParam, lParam); } switch (uMsg) { case WM_GST_D3D11_CONSTRUCT_INTERNAL_WINDOW:{ GstD3D11Window *window = GST_D3D11_WINDOW (self); RECT rect; GST_DEBUG_OBJECT (self, "Create internal window"); GstD3D11SRWLockGuard lk (&self->lock); if (self->internal_hwnd) { GST_WARNING_OBJECT (self, "Window already created, probably we have received 2 creation messages"); g_warn_if_fail (self->overlay_state == GST_D3D11_WINDOW_WIN32_OVERLAY_STATE_OPENED); gst_object_unref (self); return 0; } if (self->flushing) { GST_DEBUG_OBJECT (self, "Flushing"); gst_object_unref (self); return 0; } window->initialized = gst_d3d11_window_win32_create_internal_window (self); SetWindowLongPtrA (self->internal_hwnd, GWL_STYLE, WS_CHILD | WS_MAXIMIZE); SetParent (self->internal_hwnd, self->external_hwnd); /* take changes into account: SWP_FRAMECHANGED */ GetClientRect (self->external_hwnd, &rect); if (self->render_rect.x != 0 || self->render_rect.y != 0 || self->render_rect.w != 0 || self->render_rect.h != 0) { rect.left = self->render_rect.x; rect.top = self->render_rect.y; rect.right = self->render_rect.x + self->render_rect.w; rect.bottom = self->render_rect.y + self->render_rect.h; } SetWindowPos (self->internal_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 (self->internal_hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, FALSE); self->overlay_state = GST_D3D11_WINDOW_WIN32_OVERLAY_STATE_OPENED; WakeAllConditionVariable (&self->cond); /* 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_SIZE: if (self->render_rect.x != 0 || self->render_rect.y != 0 || self->render_rect.w != 0 || self->render_rect.h != 0) { MoveWindow (self->internal_hwnd, self->render_rect.x, self->render_rect.y, self->render_rect.w, self->render_rect.h, FALSE); } else { MoveWindow (self->internal_hwnd, 0, 0, LOWORD (lParam), HIWORD (lParam), FALSE); } break; case WM_CLOSE: case WM_DESTROY:{ GstD3D11SRWLockGuard lk (&self->lock); GST_WARNING_OBJECT (self, "external window is closing"); gst_d3d11_window_win32_release_external_handle (self); if (self->internal_hwnd) { RemovePropA (self->internal_hwnd, D3D11_WINDOW_PROP_NAME); gst_d3d11_window_win32_destroy_internal_window (self->internal_hwnd); } self->internal_hwnd = NULL; self->internal_hwnd_thread = NULL; self->overlay_state = GST_D3D11_WINDOW_WIN32_OVERLAY_STATE_CLOSED; WakeAllConditionVariable (&self->cond); break; } default: gst_d3d11_window_win32_handle_window_proc (self, hWnd, uMsg, wParam, lParam); break; } gst_object_unref (self); return CallWindowProcA (external_window_proc, hWnd, uMsg, wParam, lParam); } static void gst_d3d11_window_win32_disable_alt_enter (GstD3D11WindowWin32 * self, GstD3D11Device * device, IDXGISwapChain * swap_chain, HWND hwnd) { ComPtr < IDXGIFactory1 > factory; HRESULT hr; hr = swap_chain->GetParent (IID_PPV_ARGS (&factory)); if (!gst_d3d11_result (hr, device) || !factory) { GST_WARNING_OBJECT (self, "Cannot get parent dxgi factory for swapchain %p, hr: 0x%x", swap_chain, (guint) hr); return; } hr = factory->MakeWindowAssociation (hwnd, DXGI_MWA_NO_ALT_ENTER); if (!gst_d3d11_result (hr, device)) { GST_WARNING_OBJECT (self, "MakeWindowAssociation failure, hr: 0x%x", (guint) hr); } } static IDXGISwapChain * create_swap_chain (GstD3D11WindowWin32 * self, GstD3D11Device * device, DXGI_SWAP_CHAIN_DESC * desc) { HRESULT hr; IDXGISwapChain *swap_chain = NULL; ID3D11Device *device_handle = gst_d3d11_device_get_device_handle (device); IDXGIFactory1 *factory = gst_d3d11_device_get_dxgi_factory_handle (device); GstD3D11DeviceLockGuard lk (device); hr = factory->CreateSwapChain (device_handle, desc, &swap_chain); if (!gst_d3d11_result (hr, device)) { GST_WARNING_OBJECT (self, "Cannot create SwapChain Object: 0x%x", (guint) hr); swap_chain = NULL; } return swap_chain; } static IDXGISwapChain1 * create_swap_chain_for_hwnd (GstD3D11WindowWin32 * self, GstD3D11Device * device, HWND hwnd, DXGI_SWAP_CHAIN_DESC1 * desc, DXGI_SWAP_CHAIN_FULLSCREEN_DESC * fullscreen_desc, IDXGIOutput * output) { HRESULT hr; IDXGISwapChain1 *swap_chain = NULL; ID3D11Device *device_handle = gst_d3d11_device_get_device_handle (device); IDXGIFactory1 *factory = gst_d3d11_device_get_dxgi_factory_handle (device); ComPtr < IDXGIFactory2 > factory2; hr = factory->QueryInterface (IID_PPV_ARGS (&factory2)); if (!gst_d3d11_result (hr, device)) { GST_WARNING_OBJECT (self, "IDXGIFactory2 interface is unavailable"); return NULL; } GstD3D11DeviceLockGuard lk (device); hr = factory2->CreateSwapChainForHwnd (device_handle, hwnd, desc, fullscreen_desc, output, &swap_chain); if (!gst_d3d11_result (hr, device)) { GST_WARNING_OBJECT (self, "Cannot create SwapChain Object: 0x%x", (guint) hr); swap_chain = NULL; } return swap_chain; } static gboolean gst_d3d11_window_win32_create_swap_chain (GstD3D11Window * window, DXGI_FORMAT format, guint width, guint height, guint swapchain_flags, IDXGISwapChain ** swap_chain) { GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (window); DXGI_SWAP_CHAIN_DESC desc = { 0, }; IDXGISwapChain *new_swapchain = NULL; GstD3D11Device *device = window->device; self->have_swapchain1 = FALSE; { DXGI_SWAP_CHAIN_DESC1 desc1 = { 0, }; desc1.Width = 0; desc1.Height = 0; desc1.Format = format; /* FIXME: add support stereo */ desc1.Stereo = FALSE; desc1.SampleDesc.Count = 1; desc1.SampleDesc.Quality = 0; desc1.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; desc1.BufferCount = 2; desc1.Scaling = DXGI_SCALING_STRETCH; /* scaling-stretch would break aspect-ratio so we prefer to use scaling-none, * but Windows7 does not support this method */ if (gst_d3d11_is_windows_8_or_greater ()) desc1.Scaling = DXGI_SCALING_NONE; desc1.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; desc1.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; desc1.Flags = swapchain_flags; new_swapchain = create_swap_chain_for_hwnd (self, device, self->internal_hwnd, &desc1, NULL, NULL); if (!new_swapchain) { GST_WARNING_OBJECT (self, "Failed to create swapchain1"); } else { self->have_swapchain1 = TRUE; } } if (!new_swapchain) { DXGI_SWAP_EFFECT swap_effect = DXGI_SWAP_EFFECT_DISCARD; if (gst_d3d11_is_windows_8_or_greater ()) swap_effect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; /* we will get client area at on_resize */ desc.BufferDesc.Width = 0; desc.BufferDesc.Height = 0; /* don't care refresh rate */ desc.BufferDesc.RefreshRate.Numerator = 0; desc.BufferDesc.RefreshRate.Denominator = 1; desc.BufferDesc.Format = format; desc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; desc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; desc.SampleDesc.Count = 1; desc.SampleDesc.Quality = 0; desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; desc.BufferCount = 2; desc.SwapEffect = swap_effect; desc.OutputWindow = self->internal_hwnd; desc.Windowed = TRUE; desc.Flags = swapchain_flags; new_swapchain = create_swap_chain (self, device, &desc); } if (!new_swapchain) { GST_ERROR_OBJECT (self, "Cannot create swapchain"); return FALSE; } /* disable alt+enter here. It should be manually handled */ GstD3D11DeviceLockGuard lk (device); gst_d3d11_window_win32_disable_alt_enter (self, device, new_swapchain, desc.OutputWindow); *swap_chain = new_swapchain; return TRUE; } static void gst_d3d11_window_win32_show (GstD3D11Window * window) { GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (window); gint width, height; switch (window->method) { case GST_VIDEO_ORIENTATION_90R: case GST_VIDEO_ORIENTATION_90L: case GST_VIDEO_ORIENTATION_UL_LR: case GST_VIDEO_ORIENTATION_UR_LL: width = GST_VIDEO_INFO_HEIGHT (&window->render_info); height = GST_VIDEO_INFO_WIDTH (&window->render_info); break; default: width = GST_VIDEO_INFO_WIDTH (&window->render_info); height = GST_VIDEO_INFO_HEIGHT (&window->render_info); break; } if (!self->visible) { /* if no parent the real size has to be set now because this has not been done * when at window creation */ if (!self->external_hwnd) { RECT rect = { 0, }; rect.right = width; rect.bottom = height; if (AdjustWindowRect (&rect, WS_GST_D3D11, FALSE)) { width = rect.right - rect.left; height = rect.bottom - rect.top; } else { width += 2 * GetSystemMetrics (SM_CXSIZEFRAME); height += 2 * GetSystemMetrics (SM_CYSIZEFRAME) + GetSystemMetrics (SM_CYCAPTION); } MoveWindow (self->internal_hwnd, 0, 0, width, height, FALSE); ShowWindow (self->internal_hwnd, SW_SHOW); } else if (self->internal_hwnd) { /* ShowWindow will throw message to message pumping thread (app thread) * synchroniously, which can be blocked at the moment. * Post message to internal hwnd and do that from message pumping thread */ PostMessageA (self->internal_hwnd, WM_GST_D3D11_SHOW_WINDOW, 0, 0); } if (g_atomic_int_get (&self->pending_fullscreen_count) > 0) PostMessageA (self->internal_hwnd, WM_GST_D3D11_FULLSCREEN, 0, 0); self->visible = TRUE; } } static GstFlowReturn gst_d3d11_window_win32_present (GstD3D11Window * window, guint present_flags) { GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (window); HRESULT hr; if ((!self->external_hwnd && self->overlay_state == GST_D3D11_WINDOW_WIN32_OVERLAY_STATE_CLOSED) || !self->internal_hwnd) { GST_ERROR_OBJECT (self, "Output window was closed"); return GST_D3D11_WINDOW_FLOW_CLOSED; } if (self->have_swapchain1) { IDXGISwapChain1 *swap_chain1 = (IDXGISwapChain1 *) window->swap_chain; DXGI_PRESENT_PARAMETERS present_params = { 0, }; /* the first present should not specify dirty-rect */ if (!window->first_present && !window->emit_present) { present_params.DirtyRectsCount = 1; present_params.pDirtyRects = &window->render_rect; } hr = swap_chain1->Present1 (0, present_flags, &present_params); } else { hr = window->swap_chain->Present (0, present_flags); } if (!gst_d3d11_result (hr, window->device)) { GST_WARNING_OBJECT (self, "Direct3D cannot present texture, hr: 0x%x", (guint) hr); } return GST_FLOW_OK; } static void gst_d3d11_window_win32_on_resize (GstD3D11Window * window, guint width, guint height) { /* Set zero width and height here. dxgi will decide client area by itself */ GST_D3D11_WINDOW_CLASS (parent_class)->on_resize (window, 0, 0); } static void gst_d3d11_window_win32_update_swap_chain (GstD3D11Window * window) { GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (window); if (self->internal_hwnd) PostMessageA (self->internal_hwnd, WM_SIZE, 0, 0); return; } static void gst_d3d11_window_win32_change_fullscreen_mode (GstD3D11Window * window) { GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (window); if (self->internal_hwnd) { g_atomic_int_add (&self->pending_fullscreen_count, 1); if (self->visible) PostMessageA (self->internal_hwnd, WM_GST_D3D11_FULLSCREEN, 0, 0); } } GstD3D11Window * gst_d3d11_window_win32_new (GstD3D11Device * device, guintptr handle) { GstD3D11Window *window; g_return_val_if_fail (GST_IS_D3D11_DEVICE (device), NULL); window = (GstD3D11Window *) g_object_new (GST_TYPE_D3D11_WINDOW_WIN32, "d3d11device", device, "window-handle", handle, NULL); if (!window->initialized) { gst_object_unref (window); return NULL; } gst_object_ref_sink (window); return window; }