gstreamer/subprojects/gst-plugins-bad/sys/d3d11/gstd3d11window_win32.cpp
Seungha Yang 26dd6c5c26 d3d11videosink: Fix rendering on external handle
Partial revert of the commit 068a5c1053.
That introduced size mismatch between internal and external HWND

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/4006>
2023-02-20 22:35:27 +00:00

1245 lines
38 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);
#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)
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 (HWND hwnd);
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);
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_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;
}
self->overlay_state = GST_D3D11_WINDOW_WIN32_OVERLAY_STATE_NONE;
self->external_hwnd = hwnd;
GST_DEBUG_OBJECT (self, "Preparing external handle");
ret = gst_d3d11_window_win32_set_external_handle (self);
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);
if (self->external_hwnd) {
gst_d3d11_window_win32_release_external_handle (self->external_hwnd);
RemovePropA (self->internal_hwnd, D3D11_WINDOW_PROP_NAME);
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 {
/* We cannot destroy internal window from non-window thread.
* and we cannot use synchronously SendMessage() method at this point
* since window thread might be wait 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 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;
SetParent (hwnd, NULL);
GST_INFO ("Destroying internal window %" G_GUINTPTR_FORMAT, (guintptr) hwnd);
if (!DestroyWindow (hwnd))
GST_WARNING ("failed to destroy window %" G_GUINTPTR_FORMAT
", 0x%x", (guintptr) hwnd, (guint) GetLastError ());
}
static GstFlowReturn
gst_d3d11_window_win32_set_external_handle (GstD3D11WindowWin32 * self)
{
WNDPROC external_window_proc;
GstFlowReturn ret = GST_FLOW_OK;
external_window_proc =
(WNDPROC) GetWindowLongPtrA (self->external_hwnd, GWLP_WNDPROC);
GST_DEBUG_OBJECT (self, "set external window %" G_GUINTPTR_FORMAT
", original window procedure %p", (guintptr) self->external_hwnd,
external_window_proc);
SetPropA (self->external_hwnd, EXTERNAL_PROC_PROP_NAME,
(HANDLE) external_window_proc);
SetPropA (self->external_hwnd, D3D11_WINDOW_PROP_NAME, self);
SetWindowLongPtrA (self->external_hwnd, GWLP_WNDPROC,
(LONG_PTR) sub_class_proc);
/* 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 (self->external_hwnd, WM_GST_D3D11_CONSTRUCT_INTERNAL_WINDOW,
0, 0);
GstD3D11SRWLockGuard lk (&self->lock);
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 (HWND hwnd)
{
WNDPROC external_proc;
if (!hwnd)
return;
external_proc = (WNDPROC) GetPropA (hwnd, EXTERNAL_PROC_PROP_NAME);
if (!external_proc) {
GST_WARNING ("Failed to get original window procedure");
return;
}
GST_DEBUG ("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 ("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_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW,
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);
ShowWindow (self->internal_hwnd, SW_HIDE);
gst_d3d11_window_win32_destroy_internal_window (self->internal_hwnd);
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_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 (GetPropA (hWnd, D3D11_WINDOW_PROP_NAME)) {
HANDLE handle = GetPropA (hWnd, D3D11_WINDOW_PROP_NAME);
if (!GST_IS_D3D11_WINDOW_WIN32 (handle)) {
GST_WARNING ("%p is not d3d11window object", handle);
return DefWindowProcA (hWnd, uMsg, wParam, lParam);
}
self = GST_D3D11_WINDOW_WIN32 (handle);
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 */
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)
return (LRESULT) HTTRANSPARENT;
break;
default:
break;
}
} else if (uMsg == WM_GST_D3D11_DESTROY_INTERNAL_WINDOW) {
GST_INFO ("Handle destroy window message");
gst_d3d11_window_win32_destroy_internal_window (hWnd);
return 0;
}
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 =
(GstD3D11WindowWin32 *) GetPropA (hWnd, D3D11_WINDOW_PROP_NAME);
if (uMsg == WM_GST_D3D11_CONSTRUCT_INTERNAL_WINDOW) {
GstD3D11Window *window = GST_D3D11_WINDOW (self);
RECT rect;
GST_DEBUG_OBJECT (self, "Create internal window");
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);
GstD3D11SRWLockGuard lk (&self->lock);
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 */
return 0;
} else if (self) {
if (uMsg == WM_SIZE) {
MoveWindow (self->internal_hwnd, 0, 0, LOWORD (lParam), HIWORD (lParam),
FALSE);
} else if (uMsg == WM_CLOSE || uMsg == WM_DESTROY) {
GstD3D11SRWLockGuard lk (&self->lock);
GST_WARNING_OBJECT (self, "external window is closing");
gst_d3d11_window_win32_release_external_handle (self->external_hwnd);
self->external_hwnd = NULL;
if (self->internal_hwnd) {
RemovePropA (self->internal_hwnd, D3D11_WINDOW_PROP_NAME);
ShowWindow (self->internal_hwnd, SW_HIDE);
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);
} else {
gst_d3d11_window_win32_handle_window_proc (self, hWnd, uMsg, wParam,
lParam);
}
}
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;
GetClientRect (self->internal_hwnd, &rect);
width += 2 * GetSystemMetrics (SM_CXSIZEFRAME);
height +=
2 * GetSystemMetrics (SM_CYSIZEFRAME) +
GetSystemMetrics (SM_CYCAPTION);
MoveWindow (self->internal_hwnd, rect.left, rect.top, 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;
}