mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-02 14:36:41 +00:00
5909a495a7
Fix unexpected cropping with non 1:1 pixel aspect-ratio. The actual buffer width/height should be passed to gst_d3d11_window_render(), instead of the calculated resolution. The width/height values are parameters for copying d3d11 video memory. Also, aspect-ratio should be considered on resize callback to decide render rectangle size.
1084 lines
30 KiB
C
1084 lines
30 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.h"
|
|
#include "gstd3d11device.h"
|
|
|
|
#include <windows.h>
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_D3D11_DEVICE,
|
|
PROP_FORCE_ASPECT_RATIO,
|
|
PROP_ENABLE_NAVIGATION_EVENTS,
|
|
};
|
|
|
|
#define DEFAULT_ENABLE_NAVIGATION_EVENTS TRUE
|
|
#define DEFAULT_FORCE_ASPECT_RATIO TRUE
|
|
|
|
enum
|
|
{
|
|
SIGNAL_KEY_EVENT,
|
|
SIGNAL_MOUSE_EVENT,
|
|
SIGNAL_GOT_WINDOW_HANDLE,
|
|
SIGNAL_LAST
|
|
};
|
|
|
|
static guint d3d11_window_signals[SIGNAL_LAST] = { 0, };
|
|
|
|
#define EXTERNAL_PROC_PROP_NAME "d3d11_window_external_proc"
|
|
#define D3D11_WINDOW_PROP_NAME "gst_d3d11_window_object"
|
|
|
|
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);
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (gst_d3d11_window_debug);
|
|
#define GST_CAT_DEFAULT gst_d3d11_window_debug
|
|
|
|
#define gst_d3d11_window_parent_class parent_class
|
|
G_DEFINE_TYPE (GstD3D11Window, gst_d3d11_window, GST_TYPE_OBJECT);
|
|
|
|
static void gst_d3d11_window_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec);
|
|
static void gst_d3d11_window_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec);
|
|
static void gst_d3d11_window_dispose (GObject * object);
|
|
static void gst_d3d11_window_finalize (GObject * object);
|
|
static gpointer gst_d3d11_window_thread_func (gpointer data);
|
|
static gboolean _create_window (GstD3D11Window * self);
|
|
static void _open_window (GstD3D11Window * self);
|
|
static void _close_window (GstD3D11Window * self);
|
|
static void release_external_win_id (GstD3D11Window * self);
|
|
|
|
static void
|
|
gst_d3d11_window_class_init (GstD3D11WindowClass * klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
|
|
gobject_class->set_property = gst_d3d11_window_set_property;
|
|
gobject_class->get_property = gst_d3d11_window_get_property;
|
|
gobject_class->dispose = gst_d3d11_window_dispose;
|
|
gobject_class->finalize = gst_d3d11_window_finalize;
|
|
|
|
g_object_class_install_property (gobject_class, PROP_D3D11_DEVICE,
|
|
g_param_spec_object ("d3d11device", "D3D11 Device",
|
|
"GstD3D11Device object for creating swapchain",
|
|
GST_TYPE_D3D11_DEVICE,
|
|
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_FORCE_ASPECT_RATIO,
|
|
g_param_spec_boolean ("force-aspect-ratio",
|
|
"Force aspect ratio",
|
|
"When enabled, scaling will respect original aspect ratio",
|
|
DEFAULT_FORCE_ASPECT_RATIO,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_ENABLE_NAVIGATION_EVENTS,
|
|
g_param_spec_boolean ("enable-navigation-events",
|
|
"Enable navigation events",
|
|
"When enabled, signals for navigation events are emitted",
|
|
DEFAULT_ENABLE_NAVIGATION_EVENTS,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
d3d11_window_signals[SIGNAL_KEY_EVENT] =
|
|
g_signal_new ("key-event", G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic,
|
|
G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING);
|
|
|
|
d3d11_window_signals[SIGNAL_MOUSE_EVENT] =
|
|
g_signal_new ("mouse-event", G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic,
|
|
G_TYPE_NONE, 4, G_TYPE_STRING, G_TYPE_INT, G_TYPE_DOUBLE, G_TYPE_DOUBLE);
|
|
|
|
d3d11_window_signals[SIGNAL_GOT_WINDOW_HANDLE] =
|
|
g_signal_new ("got-window-handle", G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic,
|
|
G_TYPE_NONE, 1, G_TYPE_POINTER);
|
|
|
|
GST_DEBUG_CATEGORY_INIT (gst_d3d11_window_debug, "d3d11window", 0,
|
|
"d3d11 window");
|
|
}
|
|
|
|
static void
|
|
gst_d3d11_window_init (GstD3D11Window * self)
|
|
{
|
|
g_mutex_init (&self->lock);
|
|
g_cond_init (&self->cond);
|
|
|
|
self->main_context = g_main_context_new ();
|
|
self->loop = g_main_loop_new (self->main_context, FALSE);
|
|
|
|
self->force_aspect_ratio = DEFAULT_FORCE_ASPECT_RATIO;
|
|
self->enable_navigation_events = DEFAULT_ENABLE_NAVIGATION_EVENTS;
|
|
|
|
self->aspect_ratio_n = 1;
|
|
self->aspect_ratio_d = 1;
|
|
|
|
GST_TRACE_OBJECT (self, "Initialized");
|
|
}
|
|
|
|
static void
|
|
gst_d3d11_window_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstD3D11Window *self = GST_D3D11_WINDOW (object);
|
|
|
|
GST_OBJECT_LOCK (self);
|
|
switch (prop_id) {
|
|
case PROP_D3D11_DEVICE:
|
|
self->device = g_value_dup_object (value);
|
|
break;
|
|
case PROP_FORCE_ASPECT_RATIO:
|
|
{
|
|
gboolean force_aspect_ratio;
|
|
|
|
force_aspect_ratio = g_value_get_boolean (value);
|
|
if (force_aspect_ratio != self->force_aspect_ratio)
|
|
self->pending_resize = TRUE;
|
|
|
|
self->force_aspect_ratio = force_aspect_ratio;
|
|
break;
|
|
}
|
|
case PROP_ENABLE_NAVIGATION_EVENTS:
|
|
self->enable_navigation_events = g_value_get_boolean (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
|
|
GST_OBJECT_UNLOCK (self);
|
|
}
|
|
|
|
static void
|
|
gst_d3d11_window_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstD3D11Window *self = GST_D3D11_WINDOW (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_ENABLE_NAVIGATION_EVENTS:
|
|
g_value_set_boolean (value, self->enable_navigation_events);
|
|
break;
|
|
case PROP_FORCE_ASPECT_RATIO:
|
|
g_value_set_boolean (value, self->force_aspect_ratio);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_d3d11_window_release_resources (GstD3D11Device * device,
|
|
GstD3D11Window * window)
|
|
{
|
|
if (window->backbuffer) {
|
|
ID3D11Texture2D_Release (window->backbuffer);
|
|
window->backbuffer = NULL;
|
|
}
|
|
|
|
if (window->rtv) {
|
|
ID3D11RenderTargetView_Release (window->rtv);
|
|
window->rtv = NULL;
|
|
}
|
|
|
|
if (window->swap_chain) {
|
|
IDXGISwapChain_Release (window->swap_chain);
|
|
window->swap_chain = NULL;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_d3d11_window_dispose (GObject * object)
|
|
{
|
|
GstD3D11Window *self = GST_D3D11_WINDOW (object);
|
|
|
|
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;
|
|
}
|
|
|
|
if (self->device) {
|
|
gst_d3d11_device_thread_add (self->device,
|
|
(GstD3D11DeviceThreadFunc) gst_d3d11_window_release_resources, self);
|
|
}
|
|
|
|
gst_clear_object (&self->device);
|
|
|
|
G_OBJECT_CLASS (parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
gst_d3d11_window_finalize (GObject * object)
|
|
{
|
|
GstD3D11Window *self = GST_D3D11_WINDOW (object);
|
|
|
|
g_mutex_clear (&self->lock);
|
|
g_cond_clear (&self->cond);
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static gboolean
|
|
running_cb (gpointer user_data)
|
|
{
|
|
GstD3D11Window *self = GST_D3D11_WINDOW (user_data);
|
|
|
|
GST_TRACE_OBJECT (self, "Main loop running now");
|
|
|
|
g_mutex_lock (&self->lock);
|
|
g_cond_signal (&self->cond);
|
|
g_mutex_unlock (&self->lock);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static gpointer
|
|
gst_d3d11_window_thread_func (gpointer data)
|
|
{
|
|
GstD3D11Window *self = GST_D3D11_WINDOW (data);
|
|
GSource *source;
|
|
|
|
GST_DEBUG_OBJECT (self, "Enter loop");
|
|
g_main_context_push_thread_default (self->main_context);
|
|
|
|
self->created = _create_window (self);
|
|
|
|
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);
|
|
|
|
if (self->created)
|
|
_open_window (self);
|
|
|
|
g_main_loop_run (self->loop);
|
|
|
|
if (self->created)
|
|
_close_window (self);
|
|
|
|
g_main_context_pop_thread_default (self->main_context);
|
|
|
|
GST_DEBUG_OBJECT (self, "Exit loop");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
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 void
|
|
_open_window (GstD3D11Window * self)
|
|
{
|
|
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);
|
|
}
|
|
|
|
static void
|
|
_close_window (GstD3D11Window * self)
|
|
{
|
|
if (self->internal_win_id) {
|
|
RemoveProp (self->internal_win_id, D3D11_WINDOW_PROP_NAME);
|
|
ShowWindow (self->internal_win_id, SW_HIDE);
|
|
SetParent (self->internal_win_id, NULL);
|
|
if (!DestroyWindow (self->internal_win_id))
|
|
GST_WARNING ("failed to destroy window %" G_GUINTPTR_FORMAT
|
|
", 0x%x", (guintptr) self->internal_win_id, (guint) GetLastError ());
|
|
self->internal_win_id = 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;
|
|
}
|
|
}
|
|
|
|
static void
|
|
set_external_win_id (GstD3D11Window * self)
|
|
{
|
|
WNDPROC external_window_proc;
|
|
if (!self->external_win_id)
|
|
return;
|
|
|
|
external_window_proc =
|
|
(WNDPROC) GetWindowLongPtr (self->external_win_id, GWLP_WNDPROC);
|
|
|
|
GST_DEBUG ("set external window %" G_GUINTPTR_FORMAT,
|
|
(guintptr) self->external_win_id);
|
|
|
|
SetProp (self->external_win_id, EXTERNAL_PROC_PROP_NAME,
|
|
(WNDPROC) external_window_proc);
|
|
SetProp (self->external_win_id, D3D11_WINDOW_PROP_NAME, self);
|
|
SetWindowLongPtr (self->external_win_id, GWLP_WNDPROC,
|
|
(LONG_PTR) sub_class_proc);
|
|
}
|
|
|
|
static void
|
|
release_external_win_id (GstD3D11Window * self)
|
|
{
|
|
WNDPROC external_proc;
|
|
|
|
if (!self->external_win_id)
|
|
return;
|
|
|
|
external_proc = GetProp (self->external_win_id, EXTERNAL_PROC_PROP_NAME);
|
|
if (!external_proc)
|
|
return;
|
|
|
|
GST_DEBUG ("release external window %" G_GUINTPTR_FORMAT,
|
|
(guintptr) self->external_win_id);
|
|
|
|
SetWindowLongPtr (self->external_win_id,
|
|
GWLP_WNDPROC, (LONG_PTR) external_proc);
|
|
|
|
RemoveProp (self->external_win_id, EXTERNAL_PROC_PROP_NAME);
|
|
RemoveProp (self->external_win_id, D3D11_WINDOW_PROP_NAME);
|
|
self->external_win_id = NULL;
|
|
}
|
|
|
|
static gboolean
|
|
_create_window (GstD3D11Window * self)
|
|
{
|
|
WNDCLASSEX wc;
|
|
ATOM atom = 0;
|
|
HINSTANCE hinstance = GetModuleHandle (NULL);
|
|
|
|
GST_LOG_OBJECT (self, "Attempting to create a win32 window");
|
|
|
|
atom = GetClassInfoEx (hinstance, "GSTD3D11", &wc);
|
|
if (atom == 0) {
|
|
GST_LOG_OBJECT (self, "Register internal window class");
|
|
ZeroMemory (&wc, sizeof (WNDCLASSEX));
|
|
|
|
wc.cbSize = sizeof (WNDCLASSEX);
|
|
wc.lpfnWndProc = window_proc;
|
|
wc.hInstance = hinstance;
|
|
wc.hIcon = LoadIcon (NULL, IDI_WINLOGO);
|
|
wc.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
|
|
wc.hCursor = LoadCursor (NULL, IDC_ARROW);
|
|
wc.hbrBackground = (HBRUSH) GetStockObject (BLACK_BRUSH);
|
|
wc.lpszClassName = "GSTD3D11";
|
|
|
|
atom = RegisterClassEx (&wc);
|
|
|
|
if (atom == 0) {
|
|
GST_ELEMENT_ERROR (self, RESOURCE, FAILED, (NULL),
|
|
("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_win_id = 0;
|
|
self->visible = FALSE;
|
|
|
|
self->internal_win_id = CreateWindowEx (0,
|
|
"GSTD3D11",
|
|
"Direct3D11 renderer",
|
|
WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW,
|
|
CW_USEDEFAULT, CW_USEDEFAULT,
|
|
0, 0, (HWND) NULL, (HMENU) NULL, hinstance, self);
|
|
|
|
if (!self->internal_win_id) {
|
|
GST_ELEMENT_ERROR (self, RESOURCE, FAILED, (NULL),
|
|
("Failed to create d3d11 window"));
|
|
return FALSE;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (self, "d3d11 window created: %" G_GUINTPTR_FORMAT,
|
|
(guintptr) self->internal_win_id);
|
|
|
|
g_signal_emit (self,
|
|
d3d11_window_signals[SIGNAL_GOT_WINDOW_HANDLE], 0, self->internal_win_id);
|
|
|
|
/* device_handle is set in the window_proc */
|
|
if (!self->device_handle) {
|
|
GST_ELEMENT_ERROR (self, RESOURCE, FAILED, (NULL),
|
|
("Failed to create device_handle"));
|
|
return FALSE;
|
|
}
|
|
|
|
GST_LOG_OBJECT (self,
|
|
"Created a internal d3d11 window %p", self->internal_win_id);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gst_d3d11_window_on_resize (GstD3D11Device * device, GstD3D11Window * window)
|
|
{
|
|
HRESULT hr;
|
|
ID3D11Device *d3d11_dev;
|
|
ID3D11DeviceContext *d3d11_context;
|
|
guint width, height;
|
|
|
|
if (!window->swap_chain)
|
|
return;
|
|
|
|
d3d11_dev = gst_d3d11_device_get_device (device);
|
|
d3d11_context = gst_d3d11_device_get_device_context (device);
|
|
|
|
if (window->backbuffer) {
|
|
ID3D11Texture2D_Release (window->backbuffer);
|
|
window->backbuffer = NULL;
|
|
}
|
|
|
|
if (window->rtv) {
|
|
ID3D11RenderTargetView_Release (window->rtv);
|
|
window->rtv = NULL;
|
|
}
|
|
|
|
/* NOTE: there can be various way to resize texture, but
|
|
* we just copy incoming texture toward resized swap chain buffer in order to
|
|
* avoid shader coding.
|
|
* To keep aspect ratio, required vertical or horizontal padding area
|
|
* will be calculated in here.
|
|
*/
|
|
width = window->width;
|
|
height = window->height;
|
|
|
|
if (width != window->surface_width || height != window->surface_height) {
|
|
GstVideoRectangle src_rect, dst_rect;
|
|
gdouble src_ratio, dst_ratio;
|
|
gdouble aspect_ratio =
|
|
(gdouble) window->aspect_ratio_n / (gdouble) window->aspect_ratio_d;
|
|
|
|
src_ratio = (gdouble) width / height;
|
|
dst_ratio =
|
|
(gdouble) window->surface_width / window->surface_height * aspect_ratio;
|
|
|
|
src_rect.x = 0;
|
|
src_rect.y = 0;
|
|
src_rect.w = width;
|
|
src_rect.h = height;
|
|
|
|
dst_rect.x = 0;
|
|
dst_rect.y = 0;
|
|
|
|
if (window->force_aspect_ratio) {
|
|
if (src_ratio > dst_ratio) {
|
|
/* padding top and bottom */
|
|
dst_rect.w = width;
|
|
dst_rect.h = width / dst_ratio;
|
|
} else {
|
|
/* padding left and right */
|
|
dst_rect.w = height * dst_ratio;
|
|
dst_rect.h = height;
|
|
}
|
|
} else {
|
|
dst_rect.w = width;
|
|
dst_rect.h = height;
|
|
}
|
|
|
|
gst_video_sink_center_rect (src_rect, dst_rect, &window->render_rect, TRUE);
|
|
|
|
width = dst_rect.w;
|
|
height = dst_rect.h;
|
|
}
|
|
|
|
hr = IDXGISwapChain_ResizeBuffers (window->swap_chain,
|
|
0, width, height, DXGI_FORMAT_UNKNOWN, 0);
|
|
if (FAILED (hr)) {
|
|
GST_ERROR_OBJECT (window, "Couldn't resize buffers, hr: 0x%x", (guint) hr);
|
|
return;
|
|
}
|
|
|
|
hr = IDXGISwapChain_GetBuffer (window->swap_chain,
|
|
0, &IID_ID3D11Texture2D, (void **) &window->backbuffer);
|
|
if (FAILED (hr)) {
|
|
GST_ERROR_OBJECT (window,
|
|
"Cannot get backbuffer from swapchain, hr: 0x%x", (guint) hr);
|
|
return;
|
|
}
|
|
|
|
hr = ID3D11Device_CreateRenderTargetView (d3d11_dev,
|
|
(ID3D11Resource *) window->backbuffer, NULL, &window->rtv);
|
|
if (FAILED (hr)) {
|
|
GST_ERROR_OBJECT (window, "Cannot create render target view, hr: 0x%x",
|
|
(guint) hr);
|
|
return;
|
|
}
|
|
|
|
ID3D11DeviceContext_OMSetRenderTargets (d3d11_context, 1, &window->rtv, NULL);
|
|
}
|
|
|
|
static void
|
|
gst_d3d11_window_on_size (GstD3D11Window * self,
|
|
HWND hWnd, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
RECT clientRect = { 0, };
|
|
|
|
GetClientRect (hWnd, &clientRect);
|
|
|
|
self->surface_width = clientRect.right - clientRect.left;
|
|
self->surface_height = clientRect.bottom - clientRect.top;
|
|
|
|
GST_LOG_OBJECT (self, "WM_PAINT, surface %ux%u",
|
|
self->surface_width, self->surface_height);
|
|
|
|
gst_d3d11_device_thread_add (self->device,
|
|
(GstD3D11DeviceThreadFunc) gst_d3d11_window_on_resize, self);
|
|
}
|
|
|
|
static void
|
|
gst_d3d11_window_on_keyboard_event (GstD3D11Window * self,
|
|
HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
gunichar2 wcrep[128];
|
|
const gchar *event;
|
|
|
|
if (!self->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";
|
|
|
|
g_signal_emit (self, d3d11_window_signals[SIGNAL_KEY_EVENT], 0,
|
|
event, utfrep);
|
|
g_free (utfrep);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_d3d11_window_on_mouse_event (GstD3D11Window * self,
|
|
HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
gint button;
|
|
const gchar *event = NULL;
|
|
|
|
if (!self->enable_navigation_events)
|
|
return;
|
|
|
|
/* FIXME: convert to render coordinate */
|
|
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)
|
|
g_signal_emit (self, d3d11_window_signals[SIGNAL_MOUSE_EVENT], 0,
|
|
event, button, (gdouble) LOWORD (lParam), (gdouble) HIWORD (lParam));
|
|
}
|
|
|
|
static void
|
|
gst_d3d11_window_handle_window_proc (GstD3D11Window * self,
|
|
HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (uMsg) {
|
|
case WM_SIZE:
|
|
gst_d3d11_window_on_size (self, hWnd, wParam, lParam);
|
|
break;
|
|
case WM_CLOSE:
|
|
if (self->internal_win_id) {
|
|
ShowWindow (self->internal_win_id, SW_HIDE);
|
|
_close_window (self);
|
|
}
|
|
break;
|
|
case WM_KEYDOWN:
|
|
case WM_KEYUP:
|
|
gst_d3d11_window_on_keyboard_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_on_mouse_event (self, hWnd, uMsg, wParam, lParam);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static LRESULT CALLBACK
|
|
window_proc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
GstD3D11Window *self;
|
|
|
|
if (uMsg == WM_CREATE) {
|
|
self = GST_D3D11_WINDOW (((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);
|
|
|
|
SetProp (hWnd, D3D11_WINDOW_PROP_NAME, self);
|
|
} else if (GetProp (hWnd, D3D11_WINDOW_PROP_NAME)) {
|
|
self = GST_D3D11_WINDOW (GetProp (hWnd, D3D11_WINDOW_PROP_NAME));
|
|
|
|
g_assert (self->internal_win_id == hWnd);
|
|
|
|
gst_d3d11_window_handle_window_proc (self, hWnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
return DefWindowProc (hWnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
static LRESULT FAR PASCAL
|
|
sub_class_proc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
WNDPROC external_window_proc = GetProp (hWnd, EXTERNAL_PROC_PROP_NAME);
|
|
GstD3D11Window *self =
|
|
(GstD3D11Window *) GetProp (hWnd, D3D11_WINDOW_PROP_NAME);
|
|
LRESULT ret = 0;
|
|
|
|
gst_d3d11_window_handle_window_proc (self, hWnd, uMsg, wParam, lParam);
|
|
|
|
ret = CallWindowProc (external_window_proc, hWnd, uMsg, wParam, lParam);
|
|
|
|
if (uMsg == WM_CLOSE)
|
|
release_external_win_id (self);
|
|
|
|
return ret;
|
|
}
|
|
|
|
GstD3D11Window *
|
|
gst_d3d11_window_new (GstD3D11Device * device)
|
|
{
|
|
GstD3D11Window *window;
|
|
|
|
g_return_val_if_fail (GST_IS_D3D11_DEVICE (device), NULL);
|
|
|
|
window = g_object_new (GST_TYPE_D3D11_WINDOW, "d3d11device", device, NULL);
|
|
g_object_ref_sink (window);
|
|
|
|
return window;
|
|
}
|
|
|
|
#ifdef HAVE_DXGI_1_5_H
|
|
static inline UINT16
|
|
fraction_to_uint (guint num, guint den, guint scale)
|
|
{
|
|
gdouble val;
|
|
gst_util_fraction_to_double (num, den, &val);
|
|
|
|
return (UINT16) val *scale;
|
|
}
|
|
|
|
static void
|
|
mastering_display_gst_to_dxgi (GstVideoMasteringDisplayInfo * m,
|
|
GstVideoContentLightLevel * c, DXGI_HDR_METADATA_HDR10 * meta)
|
|
{
|
|
meta->RedPrimary[0] = fraction_to_uint (m->Rx_n, m->Rx_d, 50000);
|
|
meta->RedPrimary[1] = fraction_to_uint (m->Ry_n, m->Ry_d, 50000);
|
|
meta->GreenPrimary[0] = fraction_to_uint (m->Gx_n, m->Gx_d, 50000);
|
|
meta->GreenPrimary[1] = fraction_to_uint (m->Gy_n, m->Gy_d, 50000);
|
|
meta->BluePrimary[0] = fraction_to_uint (m->Bx_n, m->Bx_d, 50000);
|
|
meta->BluePrimary[1] = fraction_to_uint (m->By_n, m->By_d, 50000);
|
|
meta->WhitePoint[0] = fraction_to_uint (m->Wx_n, m->Wx_d, 50000);
|
|
meta->WhitePoint[1] = fraction_to_uint (m->Wy_n, m->Wy_d, 50000);
|
|
meta->MaxMasteringLuminance =
|
|
fraction_to_uint (m->max_luma_n, m->max_luma_d, 1);
|
|
meta->MinMasteringLuminance =
|
|
fraction_to_uint (m->min_luma_n, m->min_luma_d, 1);
|
|
meta->MaxContentLightLevel = fraction_to_uint (c->maxCLL_n, c->maxCLL_d, 1);
|
|
meta->MaxFrameAverageLightLevel =
|
|
fraction_to_uint (c->maxFALL_n, c->maxFALL_d, 1);
|
|
}
|
|
#endif
|
|
|
|
gboolean
|
|
gst_d3d11_window_prepare (GstD3D11Window * window, guint width, guint height,
|
|
guint aspect_ratio_n, guint aspect_ratio_d, DXGI_FORMAT format,
|
|
GstCaps * caps)
|
|
{
|
|
DXGI_SWAP_CHAIN_DESC desc = { 0, };
|
|
gboolean have_cll = FALSE;
|
|
gboolean have_mastering = FALSE;
|
|
gboolean hdr_api_available = FALSE;
|
|
|
|
g_return_val_if_fail (GST_IS_D3D11_WINDOW (window), FALSE);
|
|
g_return_val_if_fail (aspect_ratio_n > 0, FALSE);
|
|
g_return_val_if_fail (aspect_ratio_d > 0, FALSE);
|
|
|
|
GST_DEBUG_OBJECT (window, "Prepare window with %dx%d format %d",
|
|
width, height, format);
|
|
|
|
g_mutex_lock (&window->lock);
|
|
if (!window->external_win_id) {
|
|
window->thread = g_thread_new ("GstD3D11Window",
|
|
(GThreadFunc) gst_d3d11_window_thread_func, window);
|
|
while (!g_main_loop_is_running (window->loop))
|
|
g_cond_wait (&window->cond, &window->lock);
|
|
}
|
|
g_mutex_unlock (&window->lock);
|
|
|
|
gst_video_info_from_caps (&window->info, caps);
|
|
if (!gst_video_content_light_level_from_caps (&window->content_light_level,
|
|
caps)) {
|
|
gst_video_content_light_level_init (&window->content_light_level);
|
|
} else {
|
|
have_cll = TRUE;
|
|
}
|
|
|
|
if (!gst_video_mastering_display_info_from_caps
|
|
(&window->mastering_display_info, caps)) {
|
|
gst_video_mastering_display_info_init (&window->mastering_display_info);
|
|
} else {
|
|
have_mastering = TRUE;
|
|
}
|
|
|
|
#ifdef HAVE_DXGI_1_5_H
|
|
if (gst_d3d11_device_get_chosen_dxgi_factory_version (window->device) >=
|
|
GST_D3D11_DXGI_FACTORY_5) {
|
|
GST_DEBUG_OBJECT (window, "DXGI 1.5 interface is available");
|
|
hdr_api_available = TRUE;
|
|
}
|
|
#endif
|
|
|
|
window->aspect_ratio_n = aspect_ratio_n;
|
|
window->aspect_ratio_d = aspect_ratio_d;
|
|
|
|
window->render_rect.x = 0;
|
|
window->render_rect.y = 0;
|
|
window->render_rect.w = width;
|
|
window->render_rect.h = height;
|
|
|
|
desc.BufferDesc.Width = window->width = window->surface_width = width;
|
|
desc.BufferDesc.Height = window->height = window->surface_height = height;
|
|
/* 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 = DXGI_SWAP_EFFECT_DISCARD;
|
|
#ifdef HAVE_DXGI_1_5_H
|
|
/* For non-DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709 color space support,
|
|
* DXGI_SWAP_EFFECT_FLIP_DISCARD instead of DXGI_SWAP_EFFECT_DISCARD */
|
|
if (hdr_api_available)
|
|
desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
|
|
#endif
|
|
desc.OutputWindow =
|
|
window->external_win_id ? window->external_win_id : window->
|
|
internal_win_id;
|
|
desc.Windowed = TRUE;
|
|
desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
|
|
|
|
window->swap_chain =
|
|
gst_d3d11_device_create_swap_chain (window->device, &desc);
|
|
|
|
if (!window->swap_chain) {
|
|
GST_ERROR_OBJECT (window, "Cannot create swapchain");
|
|
return FALSE;
|
|
}
|
|
#ifdef HAVE_DXGI_1_5_H
|
|
if (hdr_api_available && format == DXGI_FORMAT_R10G10B10A2_UNORM &&
|
|
have_cll && have_mastering) {
|
|
UINT can_support = 0;
|
|
HRESULT hr;
|
|
|
|
hr = IDXGISwapChain4_CheckColorSpaceSupport ((IDXGISwapChain4 *)
|
|
window->swap_chain, DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020,
|
|
&can_support);
|
|
|
|
if (SUCCEEDED (hr) &&
|
|
(can_support & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT) ==
|
|
DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT) {
|
|
DXGI_HDR_METADATA_HDR10 metadata = { 0, };
|
|
|
|
GST_DEBUG_OBJECT (window,
|
|
"Swapchain support BT2084 color space, set HDR metadata");
|
|
|
|
mastering_display_gst_to_dxgi (&window->mastering_display_info,
|
|
&window->content_light_level, &metadata);
|
|
|
|
hr = IDXGISwapChain4_SetColorSpace1 ((IDXGISwapChain4 *)
|
|
window->swap_chain, DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020);
|
|
|
|
if (SUCCEEDED (hr)) {
|
|
hr = IDXGISwapChain4_SetHDRMetaData ((IDXGISwapChain4 *)
|
|
window->swap_chain, DXGI_HDR_METADATA_TYPE_HDR10,
|
|
sizeof (DXGI_HDR_METADATA_HDR10), &metadata);
|
|
if (FAILED (hr)) {
|
|
GST_WARNING_OBJECT (window, "Couldn't set HDR metadata, hr 0x%x",
|
|
(guint) hr);
|
|
}
|
|
} else {
|
|
GST_WARNING_OBJECT (window, "Couldn't set colorspace, hr 0x%x",
|
|
(guint) hr);
|
|
}
|
|
} else {
|
|
GST_DEBUG_OBJECT (window,
|
|
"Swapchain couldn't support BT2084 color space, hr 0x%x", (guint) hr);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
gst_d3d11_device_thread_add (window->device,
|
|
(GstD3D11DeviceThreadFunc) gst_d3d11_window_on_resize, window);
|
|
|
|
if (!window->rtv) {
|
|
gst_d3d11_device_thread_add (window->device,
|
|
(GstD3D11DeviceThreadFunc) gst_d3d11_window_release_resources, window);
|
|
return FALSE;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (window, "New swap chain 0x%p created", window->swap_chain);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
gst_d3d11_window_set_window_handle (GstD3D11Window * window, guintptr id)
|
|
{
|
|
g_return_if_fail (GST_IS_D3D11_WINDOW (window));
|
|
|
|
if (window->visible) {
|
|
ShowWindow (window->internal_win_id, SW_HIDE);
|
|
window->visible = FALSE;
|
|
}
|
|
|
|
release_external_win_id (window);
|
|
window->external_win_id = (HWND) id;
|
|
set_external_win_id (window);
|
|
}
|
|
|
|
void
|
|
gst_d3d11_window_show (GstD3D11Window * window)
|
|
{
|
|
gint width, height;
|
|
|
|
g_return_if_fail (GST_IS_D3D11_WINDOW (window));
|
|
|
|
width = window->width;
|
|
height = window->height;
|
|
|
|
if (!window->visible) {
|
|
/* if no parent the real size has to be set now because this has not been done
|
|
* when at window creation */
|
|
if (!window->external_win_id) {
|
|
RECT rect;
|
|
GetClientRect (window->internal_win_id, &rect);
|
|
width += 2 * GetSystemMetrics (SM_CXSIZEFRAME);
|
|
height +=
|
|
2 * GetSystemMetrics (SM_CYSIZEFRAME) +
|
|
GetSystemMetrics (SM_CYCAPTION);
|
|
MoveWindow (window->internal_win_id, rect.left, rect.top, width,
|
|
height, FALSE);
|
|
}
|
|
|
|
ShowWindow (window->internal_win_id, SW_SHOW);
|
|
window->visible = TRUE;
|
|
}
|
|
}
|
|
|
|
void
|
|
gst_d3d11_window_set_render_rectangle (GstD3D11Window * window, gint x, gint y,
|
|
gint width, gint height)
|
|
{
|
|
g_return_if_fail (GST_IS_D3D11_WINDOW (window));
|
|
|
|
if (x < 0 || y < 0) {
|
|
x = y = 0;
|
|
width = window->surface_width;
|
|
height = window->surface_height;
|
|
}
|
|
|
|
if (x < 0 || y < 0 || width <= 0 || height <= 0)
|
|
return;
|
|
|
|
/* TODO: resize window and view */
|
|
}
|
|
|
|
void
|
|
gst_d3d11_window_get_surface_dimensions (GstD3D11Window * window,
|
|
guint * width, guint * height)
|
|
{
|
|
g_return_if_fail (GST_IS_D3D11_WINDOW (window));
|
|
|
|
if (width)
|
|
*width = window->surface_width;
|
|
if (height)
|
|
*height = window->surface_height;
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
GstD3D11Window *window;
|
|
ID3D11Resource *resource;
|
|
GstVideoRectangle *rect;
|
|
|
|
GstFlowReturn ret;
|
|
} FramePresentData;
|
|
|
|
static void
|
|
_present_on_device_thread (GstD3D11Device * device, FramePresentData * data)
|
|
{
|
|
GstD3D11Window *self = data->window;
|
|
ID3D11DeviceContext *device_context;
|
|
HRESULT hr;
|
|
float black[] = { 0.0f, 0.0f, 0.0f, 0.0f };
|
|
D3D11_BOX src_box;
|
|
|
|
src_box.left = data->rect->x;
|
|
src_box.right = data->rect->x + data->rect->w;
|
|
src_box.top = data->rect->y;
|
|
src_box.bottom = data->rect->y + data->rect->h;
|
|
src_box.front = 0;
|
|
src_box.back = 1;
|
|
|
|
device_context = gst_d3d11_device_get_device_context (device);
|
|
|
|
if (data->resource) {
|
|
ID3D11DeviceContext_ClearRenderTargetView (device_context, self->rtv,
|
|
black);
|
|
ID3D11DeviceContext_CopySubresourceRegion (device_context,
|
|
(ID3D11Resource *) self->backbuffer, 0, self->render_rect.x,
|
|
self->render_rect.y, 0, data->resource, 0, &src_box);
|
|
}
|
|
|
|
hr = IDXGISwapChain_Present (self->swap_chain, 0, DXGI_PRESENT_DO_NOT_WAIT);
|
|
|
|
if (FAILED (hr)) {
|
|
GST_WARNING_OBJECT (self, "Direct3D cannot present texture, hr: 0x%x",
|
|
(guint) hr);
|
|
}
|
|
|
|
data->ret = GST_FLOW_OK;
|
|
}
|
|
|
|
GstFlowReturn
|
|
gst_d3d11_window_render (GstD3D11Window * window, ID3D11Texture2D * texture,
|
|
GstVideoRectangle * rect)
|
|
{
|
|
FramePresentData data;
|
|
|
|
g_return_val_if_fail (GST_IS_D3D11_WINDOW (window), GST_FLOW_ERROR);
|
|
g_return_val_if_fail (rect != NULL, GST_FLOW_ERROR);
|
|
|
|
if (!window->external_win_id && !window->internal_win_id) {
|
|
GST_ERROR_OBJECT (window, "Output window was closed");
|
|
return GST_D3D11_WINDOW_FLOW_CLOSED;
|
|
}
|
|
|
|
GST_OBJECT_LOCK (window);
|
|
if (rect->w != window->width || rect->h != window->height ||
|
|
window->pending_resize) {
|
|
window->width = rect->w;
|
|
window->height = rect->h;
|
|
|
|
gst_d3d11_device_thread_add (window->device,
|
|
(GstD3D11DeviceThreadFunc) gst_d3d11_window_on_resize, window);
|
|
}
|
|
GST_OBJECT_UNLOCK (window);
|
|
|
|
data.window = window;
|
|
data.resource = (ID3D11Resource *) texture;
|
|
data.rect = rect;
|
|
data.ret = GST_FLOW_OK;
|
|
|
|
gst_d3d11_device_thread_add (window->device,
|
|
(GstD3D11DeviceThreadFunc) _present_on_device_thread, &data);
|
|
|
|
return data.ret;
|
|
}
|