gstreamer/subprojects/gst-plugins-bad/sys/d3d11/gstd3d11window_win32.cpp
Aleksandr Slobodeniuk e683000573 d3d11videosink: fix race conditions in win32 window
One race condition is the fact that the window object
can be destroyed while running some routine in the UI
thread (such as resizing). To avoid that situation we make
UI thread hold a reference on the window object while it's
running.
Other probpematic case is when the window handle is reused:
if we stop and start the pipeline very fast,
so the sink creates a new window object that is going to use
the same window handle as the previous one.
And finally the case when the pipeline is stopped immediatelly
right after starting, this one is also handled in this commit.

NOTE: a unit test that reproduces this cases have been added
in the previous commit.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/4260>
2023-04-03 11:19:11 +00:00

1300 lines
40 KiB
C++

/*
* GStreamer
* Copyright (C) 2008 Julien Isorce <julien.isorce@gmail.com>
* Copyright (C) 2012 Matthew Waters <ystreet00@gmail.com>
* Copyright (C) 2019 Seungha Yang <seungha.yang@navercorp.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 "gstd3d11window_win32.h"
#include <wrl.h>
/* *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 */
RECT restore_rect;
LONG restore_style;
/* 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 ();
}
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_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) {
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);
if (self->external_hwnd && self->internal_hwnd) {
g_atomic_int_add (&self->pending_move_window, 1);
self->render_rect = *rect;
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);
SetWindowPos (hwnd, HWND_NOTOPMOST,
self->restore_rect.left,
self->restore_rect.top,
self->restore_rect.right - self->restore_rect.left,
self->restore_rect.bottom - self->restore_rect.top,
SWP_FRAMECHANGED | SWP_NOACTIVATE);
ShowWindow (hwnd, SW_NORMAL);
} else {
ComPtr < IDXGIOutput > output;
DXGI_OUTPUT_DESC output_desc;
IDXGISwapChain *swap_chain = window->swap_chain;
/* show window before change style */
ShowWindow (hwnd, SW_SHOW);
/* Save the old window rect so we can restore it when exiting
* fullscreen mode */
GetWindowRect (hwnd, &self->restore_rect);
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));
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);
SetWindowPos (self->internal_hwnd, HWND_TOP, rect.left, rect.top,
rect.right, rect.bottom,
SWP_ASYNCWINDOWPOS | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
SWP_FRAMECHANGED | SWP_NOACTIVATE);
MoveWindow (self->internal_hwnd, rect.left, rect.top, rect.right,
rect.bottom, 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:
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);
}
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);
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;
}