From 5c3879ace6b45cbf75e0289e7bea0ab152222007 Mon Sep 17 00:00:00 2001 From: Seungha Yang Date: Wed, 30 Jan 2019 20:07:29 +0900 Subject: [PATCH] d3d11videosink: Add new Direct3D11 video render plugin Direct3D11 was shipped as part of Windows7 and it's obviously primary graphics API on Windows. This plugin includes HDR10 rendering if following requirements are satisfied * IDXGISwapChain4::SetHDRMetaData is available (decleared in dxgi1_5.h) * Display can support DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020 color space * Upstream provides 10 bitdepth format with smpte-st 2084 static metadata --- meson_options.txt | 1 + sys/d3d11/gstd3d11_fwd.h | 59 +++ sys/d3d11/gstd3d11bufferpool.c | 308 ++++++++++++ sys/d3d11/gstd3d11bufferpool.h | 67 +++ sys/d3d11/gstd3d11device.c | 898 +++++++++++++++++++++++++++++++++ sys/d3d11/gstd3d11device.h | 113 +++++ sys/d3d11/gstd3d11memory.c | 405 +++++++++++++++ sys/d3d11/gstd3d11memory.h | 123 +++++ sys/d3d11/gstd3d11utils.c | 429 ++++++++++++++++ sys/d3d11/gstd3d11utils.h | 57 +++ sys/d3d11/gstd3d11videosink.c | 815 ++++++++++++++++++++++++++++++ sys/d3d11/gstd3d11videosink.h | 77 +++ sys/d3d11/gstd3d11window.c | 1072 ++++++++++++++++++++++++++++++++++++++++ sys/d3d11/gstd3d11window.h | 125 +++++ sys/d3d11/meson.build | 51 ++ sys/d3d11/plugin.c | 38 ++ sys/meson.build | 1 + 17 files changed, 4639 insertions(+) create mode 100644 sys/d3d11/gstd3d11_fwd.h create mode 100644 sys/d3d11/gstd3d11bufferpool.c create mode 100644 sys/d3d11/gstd3d11bufferpool.h create mode 100644 sys/d3d11/gstd3d11device.c create mode 100644 sys/d3d11/gstd3d11device.h create mode 100644 sys/d3d11/gstd3d11memory.c create mode 100644 sys/d3d11/gstd3d11memory.h create mode 100644 sys/d3d11/gstd3d11utils.c create mode 100644 sys/d3d11/gstd3d11utils.h create mode 100644 sys/d3d11/gstd3d11videosink.c create mode 100644 sys/d3d11/gstd3d11videosink.h create mode 100644 sys/d3d11/gstd3d11window.c create mode 100644 sys/d3d11/gstd3d11window.h create mode 100644 sys/d3d11/meson.build create mode 100644 sys/d3d11/plugin.c diff --git a/meson_options.txt b/meson_options.txt index 2a6dca3..d6db86f 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -87,6 +87,7 @@ option('colormanagement', type : 'feature', value : 'auto', description : 'Color option('curl', type : 'feature', value : 'auto', description : 'cURL network source and sink plugin') option('curl-ssh2', type : 'feature', value : 'auto', description : 'cURL network source and sink plugin libssh2 support') option('d3dvideosink', type : 'feature', value : 'auto', description : 'Direct3D video sink plugin') +option('d3d11', type : 'feature', value : 'auto', description : 'Direct3D11 plugin') option('dash', type : 'feature', value : 'auto', description : 'DASH demuxer plugin') option('dc1394', type : 'feature', value : 'auto', description : 'libdc1394 IIDC camera source plugin') option('decklink', type : 'feature', value : 'auto', description : 'DeckLink audio/video source/sink plugin') diff --git a/sys/d3d11/gstd3d11_fwd.h b/sys/d3d11/gstd3d11_fwd.h new file mode 100644 index 0000000..8643fd9 --- /dev/null +++ b/sys/d3d11/gstd3d11_fwd.h @@ -0,0 +1,59 @@ +/* GStreamer + * Copyright (C) 2019 Seungha Yang + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_D3D11_FWD_H__ +#define __GST_D3D11_FWD_H__ + +#include + +/* define COBJMACROS to use d3d11 C APIs */ +#ifndef COBJMACROS +#define COBJMACROS +#endif + +#ifndef INITGUID +#include +#endif + +#include +#ifdef HAVE_DXGI_1_5_H +#include +#else +#include +#endif + +G_BEGIN_DECLS + +typedef struct _GstD3D11Device GstD3D11Device; +typedef struct _GstD3D11DeviceClass GstD3D11DeviceClass; +typedef struct _GstD3D11DevicePrivate GstD3D11DevicePrivate; + +typedef struct _GstD3D11AllocationParams GstD3D11AllocationParams; +typedef struct _GstD3D11Memory GstD3D11Memory; +typedef struct _GstD3D11Allocator GstD3D11Allocator; +typedef struct _GstD3D11AllocatorClass GstD3D11AllocatorClass; +typedef struct _GstD3D11AllocatorPrivate GstD3D11AllocatorPrivate; + +typedef struct _GstD3D11BufferPool GstD3D11BufferPool; +typedef struct _GstD3D11BufferPoolClass GstD3D11BufferPoolClass; +typedef struct _GstD3D11BufferPoolPrivate GstD3D11BufferPoolPrivate; + +G_END_DECLS + +#endif /* __GST_D3D11_FWD_H__ */ diff --git a/sys/d3d11/gstd3d11bufferpool.c b/sys/d3d11/gstd3d11bufferpool.c new file mode 100644 index 0000000..7e1058c --- /dev/null +++ b/sys/d3d11/gstd3d11bufferpool.c @@ -0,0 +1,308 @@ +/* GStreamer + * Copyright (C) 2019 Seungha Yang + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstd3d11bufferpool.h" +#include "gstd3d11memory.h" +#include "gstd3d11device.h" + +GST_DEBUG_CATEGORY_STATIC (gst_d3d11_buffer_pool_debug); +#define GST_CAT_DEFAULT gst_d3d11_buffer_pool_debug + +struct _GstD3D11BufferPoolPrivate +{ + GstD3D11Device *device; + GstD3D11Allocator *allocator; + GstCaps *caps; + + gboolean add_videometa; + GstD3D11AllocationParams *d3d11_params; +}; + +#define gst_d3d11_buffer_pool_parent_class parent_class +G_DEFINE_TYPE_WITH_PRIVATE (GstD3D11BufferPool, + gst_d3d11_buffer_pool, GST_TYPE_BUFFER_POOL); + +static void gst_d3d11_buffer_pool_dispose (GObject * object); +static const gchar **gst_d3d11_buffer_pool_get_options (GstBufferPool * pool); +static gboolean gst_d3d11_buffer_pool_set_config (GstBufferPool * pool, + GstStructure * config); +static GstFlowReturn gst_d3d11_buffer_pool_alloc (GstBufferPool * pool, + GstBuffer ** buffer, GstBufferPoolAcquireParams * params); + +static void +gst_d3d11_buffer_pool_class_init (GstD3D11BufferPoolClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstBufferPoolClass *bufferpool_class = GST_BUFFER_POOL_CLASS (klass); + + gobject_class->dispose = gst_d3d11_buffer_pool_dispose; + + bufferpool_class->get_options = gst_d3d11_buffer_pool_get_options; + bufferpool_class->set_config = gst_d3d11_buffer_pool_set_config; + bufferpool_class->alloc_buffer = gst_d3d11_buffer_pool_alloc; + + GST_DEBUG_CATEGORY_INIT (gst_d3d11_buffer_pool_debug, "d3d11bufferpool", 0, + "d3d11bufferpool object"); +} + +static void +gst_d3d11_buffer_pool_init (GstD3D11BufferPool * self) +{ + self->priv = gst_d3d11_buffer_pool_get_instance_private (self); +} + +static void +gst_d3d11_buffer_pool_dispose (GObject * object) +{ + GstD3D11BufferPool *self = GST_D3D11_BUFFER_POOL (object); + GstD3D11BufferPoolPrivate *priv = self->priv; + + if (priv->d3d11_params) + gst_d3d11_allocation_params_free (priv->d3d11_params); + priv->d3d11_params = NULL; + gst_clear_object (&priv->device); + gst_clear_object (&priv->allocator); + gst_clear_caps (&priv->caps); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static const gchar ** +gst_d3d11_buffer_pool_get_options (GstBufferPool * pool) +{ + static const gchar *options[] = { GST_BUFFER_POOL_OPTION_VIDEO_META, + GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT, + NULL + }; + return options; +} + +static gboolean +gst_d3d11_buffer_pool_set_config (GstBufferPool * pool, GstStructure * config) +{ + GstD3D11BufferPool *self = GST_D3D11_BUFFER_POOL (pool); + GstD3D11BufferPoolPrivate *priv = self->priv; + GstVideoInfo info; + GstCaps *caps = NULL; + guint min_buffers, max_buffers; + guint max_align, n; + GstAllocator *allocator = NULL; + GstAllocationParams alloc_params; + gboolean ret = TRUE; + D3D11_TEXTURE2D_DESC *desc; + + if (!gst_buffer_pool_config_get_params (config, &caps, NULL, &min_buffers, + &max_buffers)) + goto wrong_config; + + if (caps == NULL) + goto no_caps; + + /* now parse the caps from the config */ + if (!gst_video_info_from_caps (&info, caps)) + goto wrong_caps; + + GST_LOG_OBJECT (pool, "%dx%d, caps %" GST_PTR_FORMAT, info.width, info.height, + caps); + + if (!gst_buffer_pool_config_get_allocator (config, &allocator, &alloc_params)) + goto wrong_config; + + gst_caps_replace (&priv->caps, caps); + + if (priv->allocator) + gst_object_unref (priv->allocator); + + if (allocator) { + if (!GST_IS_D3D11_ALLOCATOR (allocator)) { + gst_object_unref (allocator); + goto wrong_allocator; + } else { + priv->allocator = gst_object_ref (allocator); + } + } else { + priv->allocator = gst_d3d11_allocator_new (priv->device); + g_assert (priv->allocator); + } + + priv->add_videometa = gst_buffer_pool_config_has_option (config, + GST_BUFFER_POOL_OPTION_VIDEO_META); + + if (priv->d3d11_params) + gst_d3d11_allocation_params_free (priv->d3d11_params); + priv->d3d11_params = + gst_buffer_pool_config_get_d3d11_allocation_params (config); + if (!priv->d3d11_params) + priv->d3d11_params = gst_d3d11_allocation_params_new (&alloc_params, + &info, NULL); + + desc = &priv->d3d11_params->desc; + + GST_LOG_OBJECT (self, "Direct3D11 Allocation params"); + GST_LOG_OBJECT (self, "\t%dx%d, DXGI format %d", + desc->Width, desc->Height, desc->Format); + GST_LOG_OBJECT (self, "\tMipLevel %d, ArraySize %d", + desc->MipLevels, desc->ArraySize); + GST_LOG_OBJECT (self, "\tSampleDesc.Count %d, SampleDesc.Quality %d", + desc->SampleDesc.Count, desc->SampleDesc.Quality); + GST_LOG_OBJECT (self, "\tUsage %d", desc->Usage); + GST_LOG_OBJECT (self, "\tBindFlags 0x%x", desc->BindFlags); + GST_LOG_OBJECT (self, "\tCPUAccessFlags 0x%x", desc->CPUAccessFlags); + GST_LOG_OBJECT (self, "\tMiscFlags 0x%x", desc->MiscFlags); + + max_align = alloc_params.align; + + if (gst_buffer_pool_config_has_option (config, + GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT)) { + priv->add_videometa = TRUE; + + gst_buffer_pool_config_get_video_alignment (config, + &priv->d3d11_params->align); + + for (n = 0; n < GST_VIDEO_MAX_PLANES; ++n) + max_align |= priv->d3d11_params->align.stride_align[n]; + + for (n = 0; n < GST_VIDEO_MAX_PLANES; ++n) + priv->d3d11_params->align.stride_align[n] = max_align; + + gst_video_info_align (&priv->d3d11_params->info, + &priv->d3d11_params->align); + + gst_buffer_pool_config_set_video_alignment (config, + &priv->d3d11_params->align); + } + + if (alloc_params.align < max_align) { + GST_WARNING_OBJECT (pool, "allocation params alignment %u is smaller " + "than the max specified video stride alignment %u, fixing", + (guint) alloc_params.align, max_align); + + alloc_params.align = priv->d3d11_params->parent.align = max_align; + gst_buffer_pool_config_set_allocator (config, allocator, &alloc_params); + gst_allocation_params_copy (&alloc_params); + } + + gst_buffer_pool_config_set_params (config, + caps, GST_VIDEO_INFO_SIZE (&priv->d3d11_params->info), min_buffers, + max_buffers); + + return GST_BUFFER_POOL_CLASS (parent_class)->set_config (pool, config) && ret; + + /* ERRORS */ +wrong_config: + { + GST_WARNING_OBJECT (pool, "invalid config"); + return FALSE; + } +no_caps: + { + GST_WARNING_OBJECT (pool, "no caps in config"); + return FALSE; + } +wrong_caps: + { + GST_WARNING_OBJECT (pool, + "failed getting geometry from caps %" GST_PTR_FORMAT, caps); + return FALSE; + } +wrong_allocator: + { + GST_WARNING_OBJECT (pool, "Incorrect allocator type for this pool"); + return FALSE; + } +} + +static GstFlowReturn +gst_d3d11_buffer_pool_alloc (GstBufferPool * pool, GstBuffer ** buffer, + GstBufferPoolAcquireParams * params) +{ + GstD3D11BufferPool *self = GST_D3D11_BUFFER_POOL (pool); + GstD3D11BufferPoolPrivate *priv = self->priv; + GstMemory *mem; + GstBuffer *buf; + GstVideoInfo *info = &priv->d3d11_params->info; + + mem = gst_d3d11_allocator_alloc (priv->allocator, priv->d3d11_params); + + if (!mem) { + GST_ERROR_OBJECT (self, "cannot create texture memory"); + return GST_FLOW_ERROR; + } + + buf = gst_buffer_new (); + gst_buffer_append_memory (buf, mem); + + if (priv->add_videometa) { + GstD3D11Memory *dmem = (GstD3D11Memory *) mem; + + GST_DEBUG_OBJECT (self, "adding GstVideoMeta"); + gst_buffer_add_video_meta_full (buf, GST_VIDEO_FRAME_FLAG_NONE, + GST_VIDEO_INFO_FORMAT (info), GST_VIDEO_INFO_WIDTH (info), + GST_VIDEO_INFO_HEIGHT (info), GST_VIDEO_INFO_N_PLANES (info), + dmem->offset, dmem->stride); + } + + *buffer = buf; + + return GST_FLOW_OK; +} + +GstD3D11BufferPool * +gst_d3d11_buffer_pool_new (GstD3D11Device * device) +{ + GstD3D11BufferPool *pool; + GstD3D11Allocator *alloc; + + g_return_val_if_fail (GST_IS_D3D11_DEVICE (device), NULL); + + pool = g_object_new (GST_TYPE_D3D11_BUFFER_POOL, NULL); + alloc = gst_d3d11_allocator_new (device); + + pool->priv->device = gst_object_ref (device); + pool->priv->allocator = alloc; + + return pool; +} + +GstD3D11AllocationParams * +gst_buffer_pool_config_get_d3d11_allocation_params (GstStructure * config) +{ + GstD3D11AllocationParams *ret; + + if (!gst_structure_get (config, "d3d11-allocation-params", + GST_TYPE_D3D11_ALLOCATION_PARAMS, &ret, NULL)) + ret = NULL; + + return ret; +} + +void +gst_buffer_pool_config_set_d3d11_allocation_params (GstStructure * config, + GstD3D11AllocationParams * params) +{ + g_return_if_fail (config != NULL); + g_return_if_fail (params != NULL); + + gst_structure_set (config, "d3d11-allocation-params", + GST_TYPE_D3D11_ALLOCATION_PARAMS, params, NULL); +} diff --git a/sys/d3d11/gstd3d11bufferpool.h b/sys/d3d11/gstd3d11bufferpool.h new file mode 100644 index 0000000..bb86e16 --- /dev/null +++ b/sys/d3d11/gstd3d11bufferpool.h @@ -0,0 +1,67 @@ +/* + * GStreamer + * Copyright (C) 2019 Seungha Yang + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_D3D11_BUFFER_POOL_H__ +#define __GST_D3D11_BUFFER_POOL_H__ + +#include +#include + +#include "gstd3d11_fwd.h" + +G_BEGIN_DECLS + +#define GST_TYPE_D3D11_BUFFER_POOL (gst_d3d11_buffer_pool_get_type()) +#define GST_D3D11_BUFFER_POOL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_D3D11_BUFFER_POOL, GstD3D11BufferPool)) +#define GST_D3D11_BUFFER_POOL_CLASS(klass) (G_TYPE_CHECK_CLASS((klass), GST_TYPE_D3D11_BUFFER_POOL, GstD3D11BufferPoolClass)) +#define GST_IS_D3D11_BUFFER_POOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_D3D11_BUFFER_POOL)) +#define GST_IS_D3D11_BUFFER_POOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_D3D11_BUFFER_POOL)) +#define GST_D3D11_BUFFER_POOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GST_TYPE_D3D11_BUFFER_POOL, GstD3D11BufferPoolClass)) + +struct _GstD3D11BufferPool +{ + GstBufferPool parent; + + /*< private >*/ + GstD3D11BufferPoolPrivate *priv; + + gpointer _gst_reserved[GST_PADDING]; +}; + +struct _GstD3D11BufferPoolClass +{ + GstBufferPoolClass bufferpool_class; + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + +GType gst_d3d11_buffer_pool_get_type (void); + +GstD3D11BufferPool * gst_d3d11_buffer_pool_new (GstD3D11Device *device); + +GstD3D11AllocationParams * gst_buffer_pool_config_get_d3d11_allocation_params (GstStructure * config); + +void gst_buffer_pool_config_set_d3d11_allocation_params (GstStructure * config, + GstD3D11AllocationParams * params); + +G_END_DECLS + +#endif /* __GST_D3D11_BUFFER_POOL_H__ */ diff --git a/sys/d3d11/gstd3d11device.c b/sys/d3d11/gstd3d11device.c new file mode 100644 index 0000000..a586d1c --- /dev/null +++ b/sys/d3d11/gstd3d11device.c @@ -0,0 +1,898 @@ +/* GStreamer + * Copyright (C) 2019 Seungha Yang + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstd3d11device.h" +#include "gmodule.h" + +#ifdef HAVE_D3D11SDKLAYER_H +#include +#endif + +GST_DEBUG_CATEGORY_STATIC (GST_CAT_CONTEXT); +GST_DEBUG_CATEGORY_STATIC (gst_d3d11_device_debug); +#define GST_CAT_DEFAULT gst_d3d11_device_debug + +#ifdef HAVE_D3D11SDKLAYER_H +static GModule *sdk_layer = NULL; +#endif + +enum +{ + PROP_0, + PROP_ADAPTER +}; + +#define DEFAULT_ADAPTER -1 + +struct _GstD3D11DevicePrivate +{ + gint adapter; + + ID3D11Device *device; + ID3D11DeviceContext *device_context; + + IDXGIFactory1 *factory; + GstD3D11DXGIFactoryVersion factory_ver; + + ID3D11VideoDevice *video_device; + ID3D11VideoContext *video_context; + + GMutex lock; + GCond cond; + GThread *thread; + GThread *active_thread; + GMainLoop *loop; + GMainContext *main_context; + +#ifdef HAVE_D3D11SDKLAYER_H + ID3D11Debug *debug; + ID3D11InfoQueue *info_queue; +#endif +}; + +#define gst_d3d11_device_parent_class parent_class +G_DEFINE_TYPE_WITH_PRIVATE (GstD3D11Device, gst_d3d11_device, GST_TYPE_OBJECT); + +static void gst_d3d11_device_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_d3d11_device_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void gst_d3d11_device_constructed (GObject * object); +static void gst_d3d11_device_dispose (GObject * object); +static void gst_d3d11_device_finalize (GObject * object); + +static gpointer gst_d3d11_device_thread_func (gpointer data); + +#ifdef HAVE_D3D11SDKLAYER_H +static gboolean +gst_d3d11_device_enable_debug_layer (void) +{ + static volatile gsize _init = 0; + + if (g_once_init_enter (&_init)) { + sdk_layer = g_module_open ("d3d11sdklayers.dll", G_MODULE_BIND_LAZY); + + if (!sdk_layer) + sdk_layer = g_module_open ("d3d11_1sdklayers.dll", G_MODULE_BIND_LAZY); + + g_once_init_leave (&_init, 1); + } + + return ! !sdk_layer; +} + +static gboolean +gst_d3d11_device_get_message (GstD3D11Device * self) +{ + GstD3D11DevicePrivate *priv = self->priv; + D3D11_MESSAGE *msg; + SIZE_T msg_len = 0; + HRESULT hr; + UINT64 num_msg, i; + + num_msg = ID3D11InfoQueue_GetNumStoredMessages (priv->info_queue); + + for (i = 0; i < num_msg; i++) { + hr = ID3D11InfoQueue_GetMessage (priv->info_queue, i, NULL, &msg_len); + + if (FAILED (hr) || msg_len == 0) { + return G_SOURCE_CONTINUE; + } + + msg = (D3D11_MESSAGE *) g_malloc0 (msg_len); + hr = ID3D11InfoQueue_GetMessage (priv->info_queue, i, msg, &msg_len); + + GST_TRACE_OBJECT (self, "D3D11 Message - %s", msg->pDescription); + g_free (msg); + } + + ID3D11InfoQueue_ClearStoredMessages (priv->info_queue); + + return G_SOURCE_CONTINUE; +} +#endif + +static void +gst_d3d11_device_class_init (GstD3D11DeviceClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->set_property = gst_d3d11_device_set_property; + gobject_class->get_property = gst_d3d11_device_get_property; + gobject_class->constructed = gst_d3d11_device_constructed; + gobject_class->dispose = gst_d3d11_device_dispose; + gobject_class->finalize = gst_d3d11_device_finalize; + + g_object_class_install_property (gobject_class, PROP_ADAPTER, + g_param_spec_int ("adapter", "Adapter", + "Adapter index for creating device (-1 for default)", + -1, G_MAXINT32, DEFAULT_ADAPTER, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + GST_DEBUG_CATEGORY_INIT (gst_d3d11_device_debug, + "d3d11device", 0, "d3d11 device"); + GST_DEBUG_CATEGORY_GET (GST_CAT_CONTEXT, "GST_CONTEXT"); +} + +static void +gst_d3d11_device_init (GstD3D11Device * self) +{ + GstD3D11DevicePrivate *priv; + + priv = gst_d3d11_device_get_instance_private (self); + priv->adapter = DEFAULT_ADAPTER; + + g_mutex_init (&priv->lock); + g_cond_init (&priv->cond); + + priv->main_context = g_main_context_new (); + priv->loop = g_main_loop_new (priv->main_context, FALSE); + + self->priv = priv; +} + +static void +_relase_adapter (IDXGIAdapter1 * adapter) +{ + IDXGIAdapter1_Release (adapter); +} + +static void +gst_d3d11_device_constructed (GObject * object) +{ + GstD3D11Device *self = GST_D3D11_DEVICE (object); + GstD3D11DevicePrivate *priv = self->priv; + IDXGIAdapter1 *adapter = NULL; + GList *adapter_list = NULL; + GList *hw_adapter_list = NULL; + IDXGIFactory1 *factory = NULL; + HRESULT hr; + guint i; + guint num_adapter = 0; + UINT d3d11_flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; + + static const D3D_DRIVER_TYPE driver_types[] = { + D3D_DRIVER_TYPE_HARDWARE, + D3D_DRIVER_TYPE_WARP, + D3D_DRIVER_TYPE_UNKNOWN + }; + static const D3D_FEATURE_LEVEL feature_levels[] = { + D3D_FEATURE_LEVEL_11_1, + D3D_FEATURE_LEVEL_11_0, + D3D_FEATURE_LEVEL_10_1, + D3D_FEATURE_LEVEL_10_0, + D3D_FEATURE_LEVEL_9_3, + D3D_FEATURE_LEVEL_9_2, + D3D_FEATURE_LEVEL_9_1 + }; + D3D_FEATURE_LEVEL selected_level; + +#ifdef HAVE_DXGI_1_5_H + hr = CreateDXGIFactory1 (&IID_IDXGIFactory5, (void **) &factory); + if (FAILED (hr)) { + GST_INFO_OBJECT (self, "IDXGIFactory5 was unavailable"); + factory = NULL; + } + + priv->factory_ver = GST_D3D11_DXGI_FACTORY_5; +#endif + + if (!factory) { + priv->factory_ver = GST_D3D11_DXGI_FACTORY_1; + hr = CreateDXGIFactory1 (&IID_IDXGIFactory1, (void **) &factory); + } + + if (FAILED (hr)) { + GST_ERROR_OBJECT (self, "cannot create dxgi factory, hr: 0x%x", (guint) hr); + goto error; + } + + while (IDXGIFactory1_EnumAdapters1 (factory, num_adapter, + &adapter) != DXGI_ERROR_NOT_FOUND) { + DXGI_ADAPTER_DESC1 desc; + + hr = IDXGIAdapter1_GetDesc1 (adapter, &desc); + if (SUCCEEDED (hr)) { + gchar *vender = NULL; + + vender = g_utf16_to_utf8 (desc.Description, -1, NULL, NULL, NULL); + GST_DEBUG_OBJECT (self, + "adapter index %d: D3D11 device vendor-id: 0x%04x, device-id: 0x%04x, " + "Flags: 0x%x, %s", + num_adapter, desc.VendorId, desc.DeviceId, desc.Flags, vender); + g_free (vender); + + /* DXGI_ADAPTER_FLAG_SOFTWARE is missing in dxgi.h of mingw */ + if ((desc.Flags & 0x2) != 0x2) { + hw_adapter_list = g_list_append (hw_adapter_list, adapter); + IDXGIAdapter1_AddRef (adapter); + } + } + + adapter_list = g_list_append (adapter_list, adapter); + + num_adapter++; + + if (priv->adapter >= 0 && priv->adapter < num_adapter) + break; + } + + adapter = NULL; + if (priv->adapter >= 0) { + if (priv->adapter >= num_adapter) { + GST_WARNING_OBJECT (self, + "Requested index %d is out of scope for adapter", priv->adapter); + } else { + adapter = (IDXGIAdapter1 *) g_list_nth_data (adapter_list, priv->adapter); + } + } else if (hw_adapter_list) { + adapter = (IDXGIAdapter1 *) g_list_nth_data (hw_adapter_list, 0); + } else if (adapter_list) { + adapter = (IDXGIAdapter1 *) g_list_nth_data (adapter_list, 0); + } + + if (adapter) + IDXGIAdapter1_AddRef (adapter); + +#ifdef HAVE_D3D11SDKLAYER_H + if (gst_debug_category_get_threshold (GST_CAT_DEFAULT) >= GST_LEVEL_TRACE) { + /* DirectX SDK should be installed on system for this */ + if (gst_d3d11_device_enable_debug_layer ()) { + GST_INFO_OBJECT (self, "sdk layer library was loaded"); + d3d11_flags |= D3D11_CREATE_DEVICE_DEBUG; + } + } +#endif + + if (adapter) { + hr = D3D11CreateDevice ((IDXGIAdapter *) adapter, D3D_DRIVER_TYPE_UNKNOWN, + NULL, d3d11_flags, feature_levels, G_N_ELEMENTS (feature_levels), + D3D11_SDK_VERSION, &priv->device, &selected_level, + &priv->device_context); + + if (FAILED (hr)) { + /* Retry if the system could not recognize D3D_FEATURE_LEVEL_11_1 */ + hr = D3D11CreateDevice ((IDXGIAdapter *) adapter, D3D_DRIVER_TYPE_UNKNOWN, + NULL, d3d11_flags, &feature_levels[1], + G_N_ELEMENTS (feature_levels) - 1, D3D11_SDK_VERSION, &priv->device, + &selected_level, &priv->device_context); + } + + if (SUCCEEDED (hr)) { + GST_DEBUG_OBJECT (self, "Selected feature level 0x%x", selected_level); + } + } else { + for (i = 0; i < G_N_ELEMENTS (driver_types); i++) { + hr = D3D11CreateDevice (NULL, driver_types[i], NULL, + d3d11_flags, + feature_levels, G_N_ELEMENTS (feature_levels), + D3D11_SDK_VERSION, &priv->device, &selected_level, + &priv->device_context); + + if (FAILED (hr)) { + /* Retry if the system could not recognize D3D_FEATURE_LEVEL_11_1 */ + hr = D3D11CreateDevice (NULL, driver_types[i], NULL, + d3d11_flags, + &feature_levels[1], G_N_ELEMENTS (feature_levels) - 1, + D3D11_SDK_VERSION, &priv->device, &selected_level, + &priv->device_context); + } + + if (SUCCEEDED (hr)) { + GST_DEBUG_OBJECT (self, "Selected driver type 0x%x, feature level 0x%x", + driver_types[i], selected_level); + break; + } + } + } + + if (FAILED (hr)) { + GST_ERROR_OBJECT (self, "cannot create d3d11 device, hr: 0x%x", (guint) hr); + goto error; + } + + priv->factory = factory; + + if (adapter) + IDXGIAdapter1_Release (adapter); + + if (adapter_list) + g_list_free_full (adapter_list, (GDestroyNotify) _relase_adapter); + + if (hw_adapter_list) + g_list_free_full (hw_adapter_list, (GDestroyNotify) _relase_adapter); + +#ifdef HAVE_D3D11SDKLAYER_H + if ((d3d11_flags & D3D11_CREATE_DEVICE_DEBUG) == D3D11_CREATE_DEVICE_DEBUG) { + ID3D11Debug *debug; + ID3D11InfoQueue *info_queue; + + hr = ID3D11Device_QueryInterface (priv->device, + &IID_ID3D11Debug, (void **) &debug); + + if (SUCCEEDED (hr)) { + GST_DEBUG_OBJECT (self, "D3D11Debug interface available"); + ID3D11Debug_ReportLiveDeviceObjects (debug, D3D11_RLDO_DETAIL); + priv->debug = debug; + } + + hr = ID3D11Device_QueryInterface (priv->device, + &IID_ID3D11InfoQueue, (void **) &info_queue); + if (SUCCEEDED (hr)) { + GSource *source; + + GST_DEBUG_OBJECT (self, "D3D11InfoQueue interface available"); + priv->info_queue = info_queue; + + source = g_idle_source_new (); + g_source_set_callback (source, (GSourceFunc) gst_d3d11_device_get_message, + self, NULL); + + g_source_attach (source, priv->main_context); + g_source_unref (source); + } + } +#endif + + g_mutex_lock (&priv->lock); + priv->thread = g_thread_new ("GstD3D11Device", + (GThreadFunc) gst_d3d11_device_thread_func, self); + while (!g_main_loop_is_running (priv->loop)) + g_cond_wait (&priv->cond, &priv->lock); + g_mutex_unlock (&priv->lock); + + G_OBJECT_CLASS (parent_class)->constructed (object); + + return; + +error: + if (factory) + IDXGIFactory1_Release (factory); + + if (adapter) + IDXGIAdapter1_Release (adapter); + + if (adapter_list) + g_list_free_full (adapter_list, (GDestroyNotify) _relase_adapter); + + if (hw_adapter_list) + g_list_free_full (hw_adapter_list, (GDestroyNotify) _relase_adapter); + + G_OBJECT_CLASS (parent_class)->constructed (object); + + return; +} + +static void +gst_d3d11_device_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstD3D11Device *self = GST_D3D11_DEVICE (object); + GstD3D11DevicePrivate *priv = self->priv; + + switch (prop_id) { + case PROP_ADAPTER: + priv->adapter = g_value_get_int (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_d3d11_device_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstD3D11Device *self = GST_D3D11_DEVICE (object); + GstD3D11DevicePrivate *priv = self->priv; + + switch (prop_id) { + case PROP_ADAPTER: + g_value_set_int (value, priv->adapter); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_d3d11_device_dispose (GObject * object) +{ + GstD3D11Device *self = GST_D3D11_DEVICE (object); + GstD3D11DevicePrivate *priv = self->priv; + + GST_LOG_OBJECT (self, "dispose"); + + if (priv->loop) { + g_main_loop_quit (priv->loop); + } + + if (priv->thread) { + g_thread_join (priv->thread); + priv->thread = NULL; + } + + if (priv->loop) { + g_main_loop_unref (priv->loop); + priv->loop = NULL; + } + + if (priv->main_context) { + g_main_context_unref (priv->main_context); + priv->main_context = NULL; + } +#ifdef HAVE_D3D11SDKLAYER_H + if (priv->debug) { + ID3D11Debug_Release (priv->debug); + priv->debug = NULL; + } + + if (priv->info_queue) { + ID3D11InfoQueue_ClearStoredMessages (priv->info_queue); + ID3D11InfoQueue_Release (priv->info_queue); + priv->info_queue = NULL; + } +#endif + + if (priv->device) { + ID3D11Device_Release (priv->device); + priv->device = NULL; + } + + if (priv->device_context) { + ID3D11DeviceContext_Release (priv->device_context); + priv->device_context = NULL; + } + + if (priv->video_device) { + ID3D11VideoDevice_Release (priv->video_device); + priv->video_device = NULL; + } + + if (priv->video_context) { + ID3D11VideoContext_Release (priv->video_context); + priv->video_context = NULL; + } + + if (priv->factory) { + IDXGIFactory1_Release (priv->factory); + priv->factory = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_d3d11_device_finalize (GObject * object) +{ + GstD3D11Device *self = GST_D3D11_DEVICE (object); + GstD3D11DevicePrivate *priv = self->priv; + + GST_LOG_OBJECT (self, "finalize"); + + g_mutex_clear (&priv->lock); + g_cond_clear (&priv->cond); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gboolean +running_cb (gpointer user_data) +{ + GstD3D11Device *self = GST_D3D11_DEVICE (user_data); + GstD3D11DevicePrivate *priv = self->priv; + + GST_TRACE_OBJECT (self, "Main loop running now"); + + g_mutex_lock (&priv->lock); + g_cond_signal (&priv->cond); + g_mutex_unlock (&priv->lock); + + return G_SOURCE_REMOVE; +} + +static gpointer +gst_d3d11_device_thread_func (gpointer data) +{ + GstD3D11Device *self = GST_D3D11_DEVICE (data); + GstD3D11DevicePrivate *priv = self->priv; + GSource *source; + + GST_DEBUG_OBJECT (self, "Enter loop"); + g_main_context_push_thread_default (priv->main_context); + + source = g_idle_source_new (); + g_source_set_callback (source, (GSourceFunc) running_cb, self, NULL); + g_source_attach (source, priv->main_context); + g_source_unref (source); + + priv->active_thread = g_thread_self (); + g_main_loop_run (priv->loop); + + g_main_context_pop_thread_default (priv->main_context); + + GST_DEBUG_OBJECT (self, "Exit loop"); + + return NULL; +} + +/** + * gst_d3d11_device_new: + * @adapter: the index of adapter for creating d3d11 device (-1 for default) + * + * Returns: (transfer full) (nullable): a new #GstD3D11Device for @adapter or %NULL + * when failed to create D3D11 device with given adapter index. + */ +GstD3D11Device * +gst_d3d11_device_new (gint adapter) +{ + GstD3D11Device *device = NULL; + GstD3D11DevicePrivate *priv; + static volatile gsize _init = 0; + + if (g_once_init_enter (&_init)) { + GST_DEBUG_CATEGORY_INIT (gst_d3d11_device_debug, "d3d11device", 0, + "d3d11 device"); + g_once_init_leave (&_init, 1); + } + + device = g_object_new (GST_TYPE_D3D11_DEVICE, "adapter", adapter, NULL); + + priv = device->priv; + + if (!priv->device || !priv->device_context) { + GST_ERROR ("Cannot create d3d11 device"); + g_object_unref (device); + device = NULL; + } + + return device; +} + +/** + * gst_d3d11_device_get_device: + * @device: a #GstD3D11Device + * + * Used for various D3D11 APIs directly. + * Caller must not destroy returned device object. + * + * Returns: (transfer none): the ID3D11Device + */ +ID3D11Device * +gst_d3d11_device_get_device (GstD3D11Device * device) +{ + g_return_val_if_fail (GST_IS_D3D11_DEVICE (device), NULL); + + return device->priv->device; +} + +/** + * gst_d3d11_device_get_device_context: + * @device: a #GstD3D11Device + * + * Used for various D3D11 APIs directly. + * Caller must not destroy returned device object. + * + * Returns: (transfer none): the ID3D11DeviceContext + */ +ID3D11DeviceContext * +gst_d3d11_device_get_device_context (GstD3D11Device * device) +{ + g_return_val_if_fail (GST_IS_D3D11_DEVICE (device), NULL); + + return device->priv->device_context; +} + +GstD3D11DXGIFactoryVersion +gst_d3d11_device_get_chosen_dxgi_factory_version (GstD3D11Device * device) +{ + g_return_val_if_fail (GST_IS_D3D11_DEVICE (device), + GST_D3D11_DXGI_FACTORY_UNKNOWN); + + return device->priv->factory_ver; +} + +typedef struct +{ + GstD3D11Device *device; + GstD3D11DeviceThreadFunc func; + + gpointer data; + gboolean fired; +} MessageData; + +static gboolean +gst_d3d11_device_message_callback (MessageData * msg) +{ + GstD3D11Device *self = msg->device; + GstD3D11DevicePrivate *priv = self->priv; + + msg->func (self, msg->data); + + g_mutex_lock (&priv->lock); + msg->fired = TRUE; + g_cond_signal (&priv->cond); + g_mutex_unlock (&priv->lock); + + return G_SOURCE_REMOVE; +} + +/** + * gst_d3d11_device_thread_add: + * @device: a #GstD3D11Device + * @func: (scope call): a #GstD3D11DeviceThreadFunc + * @data: (closure): user data to call @func with + * + * Execute @func in the D3DDevice thread of @device with @data + * + * MT-safe + */ +void +gst_d3d11_device_thread_add (GstD3D11Device * device, + GstD3D11DeviceThreadFunc func, gpointer data) +{ + GstD3D11DevicePrivate *priv; + MessageData msg = { 0, }; + + g_return_if_fail (GST_IS_D3D11_DEVICE (device)); + g_return_if_fail (func != NULL); + + priv = device->priv; + + if (priv->active_thread == g_thread_self ()) { + func (device, data); + return; + } + + msg.device = gst_object_ref (device); + msg.func = func; + msg.data = data; + msg.fired = FALSE; + + g_main_context_invoke (priv->main_context, + (GSourceFunc) gst_d3d11_device_message_callback, &msg); + + g_mutex_lock (&priv->lock); + while (!msg.fired) + g_cond_wait (&priv->cond, &priv->lock); + g_mutex_unlock (&priv->lock); + + gst_object_unref (device); +} + +typedef struct +{ + IDXGISwapChain *swap_chain; + const DXGI_SWAP_CHAIN_DESC *desc; +} CreateSwapChainData; + +static void +gst_d3d11_device_create_swap_chain_internal (GstD3D11Device * device, + CreateSwapChainData * data) +{ + GstD3D11DevicePrivate *priv = device->priv; + HRESULT hr; + + hr = IDXGIFactory1_CreateSwapChain (priv->factory, (IUnknown *) priv->device, + (DXGI_SWAP_CHAIN_DESC *) data->desc, &data->swap_chain); + + if (FAILED (hr)) { + GST_ERROR_OBJECT (device, "Cannot create SwapChain Object: 0x%x", + (guint) hr); + data->swap_chain = NULL; + } +} + +/** + * gst_d3d11_device_create_swap_chain: + * @device: a #GstD3D11Device + * @desc: a DXGI_SWAP_CHAIN_DESC structure for swapchain + * + * Creat a IDXGISwapChain object. Caller must release returned swap chain object + * via IDXGISwapChain_Release() + * + * Returns: (transfer full) (nullable): a new IDXGISwapChain or %NULL + * when failed to create swap chain with given @desc + */ +IDXGISwapChain * +gst_d3d11_device_create_swap_chain (GstD3D11Device * device, + const DXGI_SWAP_CHAIN_DESC * desc) +{ + CreateSwapChainData data = { 0, }; + + g_return_val_if_fail (GST_IS_D3D11_DEVICE (device), NULL); + + data.swap_chain = NULL; + data.desc = desc; + + gst_d3d11_device_thread_add (device, (GstD3D11DeviceThreadFunc) + gst_d3d11_device_create_swap_chain_internal, &data); + + return data.swap_chain; +} + +static void +gst_d3d11_device_release_swap_chain_internal (GstD3D11Device * device, + IDXGISwapChain * swap_chain) +{ + IDXGISwapChain_Release (swap_chain); +} + +/** + * gst_d3d11_device_release_swap_chain: + * @device: a #GstD3D11Device + * @swap_chain: a IDXGISwapChain + * + * Release a @swap_chain from device thread + * + */ +void +gst_d3d11_device_release_swap_chain (GstD3D11Device * device, + IDXGISwapChain * swap_chain) +{ + g_return_if_fail (GST_IS_D3D11_DEVICE (device)); + + gst_d3d11_device_thread_add (device, + (GstD3D11DeviceThreadFunc) gst_d3d11_device_release_swap_chain_internal, + swap_chain); +} + +typedef struct +{ + ID3D11Texture2D *texture; + const D3D11_TEXTURE2D_DESC *desc; + const D3D11_SUBRESOURCE_DATA *inital_data; +} CreateTextureData; + +static void +gst_d3d11_device_create_texture_internal (GstD3D11Device * device, + CreateTextureData * data) +{ + GstD3D11DevicePrivate *priv = device->priv; + HRESULT hr; + + hr = ID3D11Device_CreateTexture2D (priv->device, data->desc, + data->inital_data, &data->texture); + if (FAILED (hr)) { + GST_ERROR ("Failed to create staging texture (0x%x)", (guint) hr); + data->texture = NULL; + } +} + +ID3D11Texture2D * +gst_d3d11_device_create_texture (GstD3D11Device * device, + const D3D11_TEXTURE2D_DESC * desc, + const D3D11_SUBRESOURCE_DATA * inital_data) +{ + CreateTextureData data; + + g_return_val_if_fail (GST_IS_D3D11_DEVICE (device), NULL); + g_return_val_if_fail (desc != NULL, NULL); + + data.texture = NULL; + data.desc = desc; + data.inital_data = inital_data; + + gst_d3d11_device_thread_add (device, (GstD3D11DeviceThreadFunc) + gst_d3d11_device_create_texture_internal, &data); + + return data.texture; +} + +static void +gst_d3d11_device_release_texture_internal (GstD3D11Device * device, + ID3D11Texture2D * texture) +{ + ID3D11Texture2D_Release (texture); +} + +void +gst_d3d11_device_release_texture (GstD3D11Device * device, + ID3D11Texture2D * texture) +{ + g_return_if_fail (GST_IS_D3D11_DEVICE (device)); + g_return_if_fail (texture != NULL); + + gst_d3d11_device_thread_add (device, (GstD3D11DeviceThreadFunc) + gst_d3d11_device_release_texture_internal, texture); +} + +/** + * gst_context_set_d3d11_device: + * @context: a #GstContext + * @device: (transfer none): resulting #GstD3D11Device + * + * Sets @device on @context + */ +void +gst_context_set_d3d11_device (GstContext * context, GstD3D11Device * device) +{ + GstStructure *s; + const gchar *context_type; + + g_return_if_fail (GST_IS_CONTEXT (context)); + g_return_if_fail (GST_IS_D3D11_DEVICE (device)); + + context_type = gst_context_get_context_type (context); + if (g_strcmp0 (context_type, GST_D3D11_DEVICE_HANDLE_CONTEXT_TYPE) != 0) + return; + + GST_CAT_LOG (GST_CAT_CONTEXT, + "setting GstD3DDevice(%" GST_PTR_FORMAT ") on context(%" GST_PTR_FORMAT + ")", device, context); + + s = gst_context_writable_structure (context); + gst_structure_set (s, "device", GST_TYPE_D3D11_DEVICE, device, NULL); +} + +/** + * gst_context_get_d3d11_device: + * @context: a #GstContext + * @device: (out) (transfer full): resulting #GstD3D11Device + * + * Returns: Whether @device was in @context + */ +gboolean +gst_context_get_d3d11_device (GstContext * context, GstD3D11Device ** device) +{ + const GstStructure *s; + const gchar *context_type; + gboolean ret; + + g_return_val_if_fail (GST_IS_CONTEXT (context), FALSE); + g_return_val_if_fail (device != NULL, FALSE); + + context_type = gst_context_get_context_type (context); + if (g_strcmp0 (context_type, GST_D3D11_DEVICE_HANDLE_CONTEXT_TYPE) != 0) + return FALSE; + + s = gst_context_get_structure (context); + ret = gst_structure_get (s, "device", GST_TYPE_D3D11_DEVICE, device, NULL); + + GST_CAT_LOG (GST_CAT_CONTEXT, "got GstD3DDevice(%p) from context(%p)", + *device, context); + + return ret; +} diff --git a/sys/d3d11/gstd3d11device.h b/sys/d3d11/gstd3d11device.h new file mode 100644 index 0000000..4344903 --- /dev/null +++ b/sys/d3d11/gstd3d11device.h @@ -0,0 +1,113 @@ +/* GStreamer + * Copyright (C) 2019 Seungha Yang + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_D3D11_DEVICE_H__ +#define __GST_D3D11_DEVICE_H__ + +#include +#include + +#include "gstd3d11_fwd.h" + +G_BEGIN_DECLS + +#define GST_TYPE_D3D11_DEVICE (gst_d3d11_device_get_type()) +#define GST_D3D11_DEVICE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_D3D11_DEVICE,GstD3D11Device)) +#define GST_D3D11_DEVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_D3D11_DEVICE,GstD3D11DeviceClass)) +#define GST_D3D11_DEVICE_GET_CLASS(obj) (GST_D3D11_DEVICE_CLASS(G_OBJECT_GET_CLASS(obj))) +#define GST_IS_D3D11_DEVICE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_D3D11_DEVICE)) +#define GST_IS_D3D11_DEVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_D3D11_DEVICE)) +#define GST_D3D11_DEVICE_CAST(obj) ((GstD3D11Device*)(obj)) + +#define GST_D3D11_DEVICE_HANDLE_CONTEXT_TYPE "gst.d3d11.device.handle" + +/** + * GstD3D11DeviceThreadFunc: + * @device: a #GstD3D11Device + * @data: user data + * + * Represents a function to run in the D3D11 device thread with @device and @data + */ +typedef void (*GstD3D11DeviceThreadFunc) (GstD3D11Device * device, gpointer data); + +typedef enum +{ + GST_D3D11_DXGI_FACTORY_UNKNOWN = 0, + GST_D3D11_DXGI_FACTORY_1, + GST_D3D11_DXGI_FACTORY_2, + GST_D3D11_DXGI_FACTORY_3, + GST_D3D11_DXGI_FACTORY_4, + GST_D3D11_DXGI_FACTORY_5, +} GstD3D11DXGIFactoryVersion; + + +struct _GstD3D11Device +{ + GstObject parent; + + GstD3D11DevicePrivate *priv; + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + +struct _GstD3D11DeviceClass +{ + GstObjectClass parent_class; + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + +GType gst_d3d11_device_get_type (void); + +GstD3D11Device * gst_d3d11_device_new (gint adapter); + +ID3D11Device * gst_d3d11_device_get_device (GstD3D11Device * device); + +ID3D11DeviceContext * gst_d3d11_device_get_device_context (GstD3D11Device * device); + +GstD3D11DXGIFactoryVersion gst_d3d11_device_get_chosen_dxgi_factory_version (GstD3D11Device * device); + +IDXGISwapChain * gst_d3d11_device_create_swap_chain (GstD3D11Device * device, + const DXGI_SWAP_CHAIN_DESC * desc); + +void gst_d3d11_device_release_swap_chain (GstD3D11Device * device, + IDXGISwapChain * swap_chain); + +void gst_d3d11_device_thread_add (GstD3D11Device * device, + GstD3D11DeviceThreadFunc func, + gpointer data); + +ID3D11Texture2D * gst_d3d11_device_create_texture (GstD3D11Device * device, + const D3D11_TEXTURE2D_DESC * desc, + const D3D11_SUBRESOURCE_DATA *inital_data); + +void gst_d3d11_device_release_texture (GstD3D11Device * device, + ID3D11Texture2D * texture); + +void gst_context_set_d3d11_device (GstContext * context, + GstD3D11Device * device); + +gboolean gst_context_get_d3d11_device (GstContext * context, + GstD3D11Device ** device); + +G_END_DECLS + +#endif /* __GST_D3D11_DEVICE_H__ */ diff --git a/sys/d3d11/gstd3d11memory.c b/sys/d3d11/gstd3d11memory.c new file mode 100644 index 0000000..6dcc52c --- /dev/null +++ b/sys/d3d11/gstd3d11memory.c @@ -0,0 +1,405 @@ +/* GStreamer + * Copyright (C) 2019 Seungha Yang + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "gstd3d11memory.h" +#include "gstd3d11device.h" +#include "gstd3d11utils.h" + +GST_DEBUG_CATEGORY_STATIC (gst_d3d11_allocator_debug); +#define GST_CAT_DEFAULT gst_d3d11_allocator_debug + +GstD3D11AllocationParams * +gst_d3d11_allocation_params_new (GstAllocationParams * alloc_params, + GstVideoInfo * info, GstVideoAlignment * align) +{ + GstD3D11AllocationParams *ret; + + g_return_val_if_fail (alloc_params != NULL, NULL); + g_return_val_if_fail (info != NULL, NULL); + + ret = g_new0 (GstD3D11AllocationParams, 1); + + memcpy (&ret->info, info, sizeof (GstVideoInfo)); + if (align) { + ret->align = *align; + } else { + gst_video_alignment_reset (&ret->align); + } + + ret->desc.Width = GST_VIDEO_INFO_WIDTH (info); + ret->desc.Height = GST_VIDEO_INFO_HEIGHT (info); + ret->desc.MipLevels = 1; + ret->desc.ArraySize = 1; + ret->desc.Format = + gst_d3d11_dxgi_format_from_gst (GST_VIDEO_INFO_FORMAT (info)); + ret->desc.SampleDesc.Count = 1; + ret->desc.SampleDesc.Quality = 0; + ret->desc.Usage = D3D11_USAGE_DEFAULT; + /* User must set proper BindFlags and MiscFlags manually */ + + return ret; +} + +GstD3D11AllocationParams * +gst_d3d11_allocation_params_copy (GstD3D11AllocationParams * src) +{ + GstD3D11AllocationParams *dst; + + g_return_val_if_fail (src != NULL, NULL); + + dst = g_new0 (GstD3D11AllocationParams, 1); + memcpy (dst, src, sizeof (GstD3D11AllocationParams)); + + return dst; +} + +void +gst_d3d11_allocation_params_free (GstD3D11AllocationParams * params) +{ + g_free (params); +} + +G_DEFINE_BOXED_TYPE (GstD3D11AllocationParams, gst_d3d11_allocation_params, + (GBoxedCopyFunc) gst_d3d11_allocation_params_copy, + (GBoxedFreeFunc) gst_d3d11_allocation_params_free); + +#define gst_d3d11_allocator_parent_class parent_class +G_DEFINE_TYPE (GstD3D11Allocator, gst_d3d11_allocator, GST_TYPE_ALLOCATOR); + +static inline D3D11_MAP +_gst_map_flags_to_d3d11 (GstMapFlags flags) +{ + if ((flags & GST_MAP_READWRITE) == GST_MAP_READWRITE) + return D3D11_MAP_READ_WRITE; + else if ((flags & GST_MAP_WRITE) == GST_MAP_WRITE) + return D3D11_MAP_WRITE; + else if ((flags & GST_MAP_READ) == GST_MAP_READ) + return D3D11_MAP_READ; + else + g_assert_not_reached (); + + return D3D11_MAP_READ; +} + +static ID3D11Texture2D * +_create_staging_texture (GstD3D11Device * device, + const D3D11_TEXTURE2D_DESC * ref) +{ + D3D11_TEXTURE2D_DESC desc = { 0, }; + + desc.Width = ref->Width; + desc.Height = ref->Height; + desc.MipLevels = 1; + desc.Format = ref->Format; + desc.SampleDesc.Count = 1; + desc.ArraySize = 1; + desc.Usage = D3D11_USAGE_STAGING; + desc.CPUAccessFlags = (D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE); + + return gst_d3d11_device_create_texture (device, &desc, NULL); +} + +typedef struct +{ + GstD3D11Memory *mem; + D3D11_MAP map_flag; + + gboolean ret; +} D3D11MapData; + +static void +_map_cpu_access_data (GstD3D11Device * device, gpointer data) +{ + D3D11MapData *map_data = (D3D11MapData *) data; + GstD3D11Memory *dmem = map_data->mem; + HRESULT hr; + ID3D11Resource *texture = (ID3D11Resource *) dmem->texture; + ID3D11Resource *staging = (ID3D11Resource *) dmem->staging; + ID3D11DeviceContext *device_context = + gst_d3d11_device_get_device_context (device); + + ID3D11DeviceContext_CopySubresourceRegion (device_context, + staging, 0, 0, 0, 0, texture, 0, NULL); + + hr = ID3D11DeviceContext_Map (device_context, + staging, 0, map_data->map_flag, 0, &dmem->map); + + if (FAILED (hr)) { + GST_ERROR ("Failed to map staging texture (0x%x)", (guint) hr); + map_data->ret = FALSE; + } + + map_data->ret = TRUE; +} + +static gpointer +gst_d3d11_memory_map (GstMemory * mem, gsize maxsize, GstMapFlags flags) +{ + GstD3D11Memory *dmem = (GstD3D11Memory *) mem; + + if ((flags & GST_MAP_D3D11) == GST_MAP_D3D11) + return dmem->texture; + + if (dmem->cpu_map_count == 0) { + D3D11MapData map_data; + GstD3D11Device *device = GST_D3D11_ALLOCATOR (mem->allocator)->device; + + map_data.mem = dmem; + map_data.map_flag = _gst_map_flags_to_d3d11 (flags); + + gst_d3d11_device_thread_add (device, (GstD3D11DeviceThreadFunc) + _map_cpu_access_data, &map_data); + + if (!map_data.ret) + return NULL; + } + + if ((flags & GST_MAP_WRITE) == GST_MAP_WRITE) + dmem->need_upload = TRUE; + + dmem->cpu_map_count++; + + return dmem->map.pData; +} + +static void +_unmap_cpu_access_data (GstD3D11Device * device, gpointer data) +{ + GstD3D11Memory *dmem = (GstD3D11Memory *) data; + ID3D11Resource *texture = (ID3D11Resource *) dmem->texture; + ID3D11Resource *staging = (ID3D11Resource *) dmem->staging; + ID3D11DeviceContext *device_context = + gst_d3d11_device_get_device_context (device); + + ID3D11DeviceContext_Unmap (device_context, staging, 0); + + if (dmem->need_upload) { + ID3D11DeviceContext_CopySubresourceRegion (device_context, texture, + 0, 0, 0, 0, staging, 0, NULL); + } + dmem->need_upload = FALSE; +} + +static void +gst_d3d11_memory_unmap_full (GstMemory * mem, GstMapInfo * info) +{ + GstD3D11Memory *dmem = (GstD3D11Memory *) mem; + GstD3D11Device *device = GST_D3D11_ALLOCATOR (mem->allocator)->device; + + if ((info->flags & GST_MAP_D3D11) == GST_MAP_D3D11) + return; + + dmem->cpu_map_count--; + if (dmem->cpu_map_count > 0) + return; + + gst_d3d11_device_thread_add (device, (GstD3D11DeviceThreadFunc) + _unmap_cpu_access_data, dmem); +} + +static GstMemory * +gst_d3d11_memory_share (GstMemory * mem, gssize offset, gssize size) +{ + /* TODO: impl. */ + return NULL; +} + +static GstMemory * +gst_d3d11_allocator_dummy_alloc (GstAllocator * allocator, gsize size, + GstAllocationParams * params) +{ + g_return_val_if_reached (NULL); +} + +static void +gst_d3d11_allocator_free (GstAllocator * allocator, GstMemory * mem) +{ + GstD3D11Memory *dmem = (GstD3D11Memory *) mem; + GstD3D11Device *device = GST_D3D11_ALLOCATOR (allocator)->device; + + if (dmem->texture) + gst_d3d11_device_release_texture (device, dmem->texture); + + if (dmem->staging) + gst_d3d11_device_release_texture (device, dmem->staging); + + g_slice_free (GstD3D11Memory, dmem); +} + +static void +gst_d3d11_allocator_dispose (GObject * object) +{ + GstD3D11Allocator *alloc = GST_D3D11_ALLOCATOR (object); + + gst_clear_object (&alloc->device); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_d3d11_allocator_class_init (GstD3D11AllocatorClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstAllocatorClass *allocator_class = GST_ALLOCATOR_CLASS (klass); + + gobject_class->dispose = gst_d3d11_allocator_dispose; + + allocator_class->alloc = gst_d3d11_allocator_dummy_alloc; + allocator_class->free = gst_d3d11_allocator_free; + + GST_DEBUG_CATEGORY_INIT (gst_d3d11_allocator_debug, "d3d11allocator", 0, + "d3d11allocator object"); +} + +static void +gst_d3d11_allocator_init (GstD3D11Allocator * allocator) +{ + GstAllocator *alloc = GST_ALLOCATOR_CAST (allocator); + + alloc->mem_type = GST_D3D11_MEMORY_NAME; + alloc->mem_map = gst_d3d11_memory_map; + alloc->mem_unmap_full = gst_d3d11_memory_unmap_full; + alloc->mem_share = gst_d3d11_memory_share; + /* fallback copy */ + + GST_OBJECT_FLAG_SET (alloc, GST_ALLOCATOR_FLAG_CUSTOM_ALLOC); +} + +GstD3D11Allocator * +gst_d3d11_allocator_new (GstD3D11Device * device) +{ + GstD3D11Allocator *allocator; + + g_return_val_if_fail (GST_IS_D3D11_DEVICE (device), NULL); + + allocator = g_object_new (GST_TYPE_D3D11_ALLOCATOR, NULL); + allocator->device = gst_object_ref (device); + + return allocator; +} + +typedef struct _CalculateSizeData +{ + ID3D11Texture2D *staging; + GstVideoInfo *info; + gsize offset[GST_VIDEO_MAX_PLANES]; + gint stride[GST_VIDEO_MAX_PLANES]; + gsize size; + gboolean ret; +} CalculateSizeData; + +static void +_calculate_buffer_size (GstD3D11Device * device, CalculateSizeData * data) +{ + HRESULT hr; + D3D11_MAPPED_SUBRESOURCE map; + ID3D11DeviceContext *device_context = + gst_d3d11_device_get_device_context (device); + + hr = ID3D11DeviceContext_Map (device_context, + (ID3D11Resource *) data->staging, 0, GST_MAP_READWRITE, 0, &map); + + if (FAILED (hr)) { + GST_ERROR ("Failed to map staging texture (0x%x)", (guint) hr); + data->ret = FALSE; + return; + } + + ID3D11DeviceContext_Unmap (device_context, (ID3D11Resource *) data->staging, + 0); + + data->ret = gst_d3d11_calculate_buffer_size (data->info, + map.RowPitch, data->offset, data->stride, &data->size); +} + +GstMemory * +gst_d3d11_allocator_alloc (GstD3D11Allocator * allocator, + GstD3D11AllocationParams * params) +{ + GstD3D11Memory *mem; + GstD3D11Device *device; + GstAllocationParams *alloc_params; + gsize size, maxsize; + ID3D11Texture2D *texture = NULL; + ID3D11Texture2D *staging = NULL; + CalculateSizeData data; + gint i; + + g_return_val_if_fail (GST_IS_D3D11_ALLOCATOR (allocator), NULL); + g_return_val_if_fail (params != NULL, NULL); + + device = allocator->device; + + texture = gst_d3d11_device_create_texture (device, ¶ms->desc, NULL); + if (!texture) { + GST_ERROR_OBJECT (allocator, "Couldn't create texture"); + goto error; + } + + staging = _create_staging_texture (device, ¶ms->desc); + if (!staging) { + GST_ERROR_OBJECT (allocator, "Couldn't create staging texture"); + goto error; + } + + /* try map staging texture to get actual stride and size */ + memset (&data, 0, sizeof (CalculateSizeData)); + data.staging = staging; + data.info = ¶ms->info; + gst_d3d11_device_thread_add (device, (GstD3D11DeviceThreadFunc) + _calculate_buffer_size, &data); + + if (!data.ret) { + GST_ERROR_OBJECT (allocator, "Couldn't calculate stride"); + goto error; + } + + alloc_params = (GstAllocationParams *) params; + maxsize = size = data.size; + maxsize += alloc_params->prefix + alloc_params->padding; + + mem = g_slice_new0 (GstD3D11Memory); + + gst_memory_init (GST_MEMORY_CAST (mem), + alloc_params->flags, GST_ALLOCATOR_CAST (allocator), NULL, maxsize, + alloc_params->align, 0, size); + + mem->desc = params->desc; + mem->texture = texture; + mem->staging = staging; + + for (i = 0; i < GST_VIDEO_MAX_PLANES; i++) { + mem->offset[i] = data.offset[i]; + mem->stride[i] = data.stride[i]; + } + + return GST_MEMORY_CAST (mem); + +error: + if (texture) + gst_d3d11_device_release_texture (device, texture); + if (staging) + gst_d3d11_device_release_texture (device, staging); + return NULL; +} diff --git a/sys/d3d11/gstd3d11memory.h b/sys/d3d11/gstd3d11memory.h new file mode 100644 index 0000000..406dd1ec --- /dev/null +++ b/sys/d3d11/gstd3d11memory.h @@ -0,0 +1,123 @@ +/* + * GStreamer + * Copyright (C) 2019 Seungha Yang + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_D3D11_MEMORY_H__ +#define __GST_D3D11_MEMORY_H__ + +#include +#include + +#include "gstd3d11_fwd.h" + +G_BEGIN_DECLS + +#define GST_TYPE_D3D11_ALLOCATION_PARAMS (gst_d3d11_allocation_params_get_type()) +#define GST_TYPE_D3D11_ALLOCATOR (gst_d3d11_allocator_get_type()) +#define GST_D3D11_ALLOCATOR(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_D3D11_ALLOCATOR, GstD3D11Allocator)) +#define GST_D3D11_ALLOCATOR_CLASS(klass) (G_TYPE_CHECK_CLASS((klass), GST_TYPE_D3D11_ALLOCATOR, GstD3D11AllocatorClass)) +#define GST_IS_D3D11_ALLOCATOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_D3D11_ALLOCATOR)) +#define GST_IS_D3D11_ALLOCATOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_D3D11_ALLOCATOR)) +#define GST_D3D11_ALLOCATOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GST_TYPE_D3D11_ALLOCATOR, GstD3D11AllocatorClass)) + +#define GST_D3D11_MEMORY_NAME "D3D11Memory" + +/** + * GST_CAPS_FEATURE_MEMORY_D3D11_MEMORY: + * + * Name of the caps feature for indicating the use of #GstD3D11Memory + */ +#define GST_CAPS_FEATURE_MEMORY_D3D11_MEMORY "memory:D3D11Memory" + +/** + * GST_MAP_D3D11: + * + * Flag indicating that we should map the D3D11 resource instead of to system memory. + */ +#define GST_MAP_D3D11 (GST_MAP_FLAG_LAST << 1) + +struct _GstD3D11Memory +{ + GstMemory mem; + + GstMapFlags map_flags; + gint cpu_map_count; + + ID3D11Texture2D *texture; + ID3D11Texture2D *staging; + + D3D11_TEXTURE2D_DESC desc; + D3D11_MAPPED_SUBRESOURCE map; + gboolean need_upload; + + gsize offset[GST_VIDEO_MAX_PLANES]; + gint stride[GST_VIDEO_MAX_PLANES]; +}; + +struct _GstD3D11AllocationParams +{ + GstAllocationParams parent; + + D3D11_TEXTURE2D_DESC desc; + + GstVideoInfo info; + GstVideoAlignment align; + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING_LARGE]; +}; + +struct _GstD3D11Allocator +{ + GstAllocator parent; + + /*< private >*/ + GstD3D11Device *device; + + gpointer _gst_reserved[GST_PADDING]; +}; + +struct _GstD3D11AllocatorClass +{ + GstAllocatorClass allocator_class; + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + +GType gst_d3d11_allocation_params_get_type (void); + +GstD3D11AllocationParams * gst_d3d11_allocation_params_new (GstAllocationParams * alloc_params, + GstVideoInfo * info, + GstVideoAlignment * align); + +GstD3D11AllocationParams * gst_d3d11_allocation_params_copy (GstD3D11AllocationParams * src); + +void gst_d3d11_allocation_params_free (GstD3D11AllocationParams * parms); + +GType gst_d3d11_allocator_get_type (void); + +GstD3D11Allocator * gst_d3d11_allocator_new (GstD3D11Device *device); + +GstMemory * gst_d3d11_allocator_alloc (GstD3D11Allocator * allocator, + GstD3D11AllocationParams * params); + +G_END_DECLS + +#endif /* __GST_D3D11_MEMORY_H__ */ diff --git a/sys/d3d11/gstd3d11utils.c b/sys/d3d11/gstd3d11utils.c new file mode 100644 index 0000000..0c8d49a --- /dev/null +++ b/sys/d3d11/gstd3d11utils.c @@ -0,0 +1,429 @@ +/* GStreamer + * Copyright (C) 2019 Seungha Yang + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstd3d11utils.h" +#include "gstd3d11device.h" + +GST_DEBUG_CATEGORY_STATIC (gst_d3d11_utils_debug); +GST_DEBUG_CATEGORY_STATIC (GST_CAT_CONTEXT); + +static GstDebugCategory * +_init_d3d11_utils_debug (void) +{ + static volatile gsize _init = 0; + + if (g_once_init_enter (&_init)) { + GST_DEBUG_CATEGORY_INIT (gst_d3d11_utils_debug, "d3d11utils", 0, + "Direct3D11 Utilities"); + g_once_init_leave (&_init, 1); + } + + return gst_d3d11_utils_debug; +} + +static void +_init_context_debug (void) +{ + static volatile gsize _init = 0; + + if (g_once_init_enter (&_init)) { + GST_DEBUG_CATEGORY_GET (GST_CAT_CONTEXT, "GST_CONTEXT"); + g_once_init_leave (&_init, 1); + } +} + +#define GST_CAT_DEFAULT _init_d3d11_utils_debug() + +static const struct +{ + GstVideoFormat gst_format; + DXGI_FORMAT dxgi_format; +} gst_dxgi_format_map[] = { + /* TODO: add more formats */ + { + GST_VIDEO_FORMAT_BGRA, DXGI_FORMAT_B8G8R8A8_UNORM}, { + GST_VIDEO_FORMAT_RGBA, DXGI_FORMAT_R8G8B8A8_UNORM}, { + GST_VIDEO_FORMAT_RGB10A2_LE, DXGI_FORMAT_R10G10B10A2_UNORM} +}; + +GstVideoFormat +gst_d3d11_dxgi_format_to_gst (DXGI_FORMAT format) +{ + gint i; + + for (i = 0; i < G_N_ELEMENTS (gst_dxgi_format_map); i++) { + if (gst_dxgi_format_map[i].dxgi_format == format) + return gst_dxgi_format_map[i].gst_format; + } + + return GST_VIDEO_FORMAT_UNKNOWN; +} + +DXGI_FORMAT +gst_d3d11_dxgi_format_from_gst (GstVideoFormat format) +{ + gint i; + + for (i = 0; i < G_N_ELEMENTS (gst_dxgi_format_map); i++) { + if (gst_dxgi_format_map[i].gst_format == format) + return gst_dxgi_format_map[i].dxgi_format; + } + + return DXGI_FORMAT_UNKNOWN; +} + +typedef struct +{ + GstCaps *caps; + D3D11_FORMAT_SUPPORT flags; +} SupportCapsData; + +static void +gst_d3d11_device_get_supported_caps_internal (GstD3D11Device * device, + SupportCapsData * data) +{ + ID3D11Device *d3d11_device; + HRESULT hr; + gint i; + GValue v_list = G_VALUE_INIT; + GstCaps *supported_caps; + + d3d11_device = gst_d3d11_device_get_device (device); + g_value_init (&v_list, GST_TYPE_LIST); + + for (i = 0; i < G_N_ELEMENTS (gst_dxgi_format_map); i++) { + UINT format_support = 0; + GstVideoFormat format = gst_dxgi_format_map[i].gst_format; + + hr = ID3D11Device_CheckFormatSupport (d3d11_device, + gst_dxgi_format_map[i].dxgi_format, &format_support); + + if (SUCCEEDED (hr) && ((format_support & data->flags) == data->flags)) { + GValue v_str = G_VALUE_INIT; + g_value_init (&v_str, G_TYPE_STRING); + + GST_LOG_OBJECT (device, "d3d11 device can support %s with flags 0x%x", + gst_video_format_to_string (format), data->flags); + g_value_set_string (&v_str, gst_video_format_to_string (format)); + gst_value_list_append_and_take_value (&v_list, &v_str); + } + } + + supported_caps = gst_caps_new_simple ("video/x-raw", + "width", GST_TYPE_INT_RANGE, 1, G_MAXINT, + "height", GST_TYPE_INT_RANGE, 1, G_MAXINT, + "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, NULL); + gst_caps_set_value (supported_caps, "format", &v_list); + g_value_unset (&v_list); + + data->caps = supported_caps; +} + +/** + * gst_d3d11_device_get_supported_caps: + * @device: a #GstD3DDevice + * @flags: D3D11_FORMAT_SUPPORT flags + * + * Check supported format with given flags + * + * Returns: a #GstCaps representing supported format + */ +GstCaps * +gst_d3d11_device_get_supported_caps (GstD3D11Device * device, + D3D11_FORMAT_SUPPORT flags) +{ + SupportCapsData data; + + g_return_val_if_fail (GST_IS_D3D11_DEVICE (device), NULL); + + data.caps = NULL; + data.flags = flags; + + gst_d3d11_device_thread_add (device, (GstD3D11DeviceThreadFunc) + gst_d3d11_device_get_supported_caps_internal, &data); + + return data.caps; +} + +gboolean +gst_d3d11_calculate_buffer_size (GstVideoInfo * info, guint pitch, + gsize offset[GST_VIDEO_MAX_PLANES], gint stride[GST_VIDEO_MAX_PLANES], + gsize * size) +{ + g_return_val_if_fail (info != NULL, FALSE); + + switch (GST_VIDEO_INFO_FORMAT (info)) { + case GST_VIDEO_FORMAT_BGRA: + case GST_VIDEO_FORMAT_RGBA: + case GST_VIDEO_FORMAT_RGB10A2_LE: + offset[0] = 0; + stride[0] = pitch; + *size = pitch * GST_VIDEO_INFO_HEIGHT (info); + break; + case GST_VIDEO_FORMAT_NV12: + offset[0] = 0; + stride[0] = pitch; + offset[1] = offset[0] + stride[0] * GST_VIDEO_INFO_COMP_HEIGHT (info, 0); + stride[1] = pitch; + *size = offset[1] + stride[1] * GST_VIDEO_INFO_COMP_HEIGHT (info, 1); + break; + default: + return FALSE; + } + + GST_LOG ("Calculated buffer size: %" G_GSIZE_FORMAT + " (%s %dx%d, Pitch %d)", *size, + gst_video_format_to_string (GST_VIDEO_INFO_FORMAT (info)), + GST_VIDEO_INFO_WIDTH (info), GST_VIDEO_INFO_HEIGHT (info), pitch); + + return TRUE; +} + +/** + * gst_d3d11_handle_set_context: + * @element: a #GstElement + * @context: a #GstContext + * @device: (inout) (transfer full): location of a #GstD3DDevice + * + * Helper function for implementing #GstElementClass.set_context() in + * D3D11 capable elements. + * + * Retrieve's the #GstD3D11Device in @context and places the result in @device. + * + * Returns: whether the @device could be set successfully + */ +gboolean +gst_d3d11_handle_set_context (GstElement * element, GstContext * context, + GstD3D11Device ** device) +{ + GstD3D11Device *device_replacement = NULL; + + g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE); + g_return_val_if_fail (device != NULL, FALSE); + + if (!context) + return FALSE; + + if (!gst_context_get_d3d11_device (context, &device_replacement)) + return FALSE; + + if (*device) + gst_object_unref (*device); + *device = device_replacement; + + return TRUE; +} + +/** + * gst_d3d11_handle_context_query: + * @element: a #GstElement + * @query: a #GstQuery of type %GST_QUERY_CONTEXT + * @device: (transfer none) (nullable): a #GstD3D11Device + * + * Returns: Whether the @query was successfully responded to from the passed + * @device. + */ +gboolean +gst_d3d11_handle_context_query (GstElement * element, GstQuery * query, + GstD3D11Device * device) +{ + const gchar *context_type; + GstContext *context, *old_context; + + g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE); + g_return_val_if_fail (GST_IS_QUERY (query), FALSE); + + GST_LOG_OBJECT (element, "handle context query %" GST_PTR_FORMAT, query); + + if (!device) + return FALSE; + + gst_query_parse_context_type (query, &context_type); + if (g_strcmp0 (context_type, GST_D3D11_DEVICE_HANDLE_CONTEXT_TYPE) != 0) + return FALSE; + + gst_query_parse_context (query, &old_context); + if (old_context) + context = gst_context_copy (old_context); + else + context = gst_context_new (GST_D3D11_DEVICE_HANDLE_CONTEXT_TYPE, TRUE); + + gst_context_set_d3d11_device (context, device); + gst_query_set_context (query, context); + gst_context_unref (context); + + GST_DEBUG_OBJECT (element, "successfully set %" GST_PTR_FORMAT + " on %" GST_PTR_FORMAT, device, query); + + return TRUE; +} + +static gboolean +pad_query (const GValue * item, GValue * value, gpointer user_data) +{ + GstPad *pad = g_value_get_object (item); + GstQuery *query = user_data; + gboolean res; + + res = gst_pad_peer_query (pad, query); + + if (res) { + g_value_set_boolean (value, TRUE); + return FALSE; + } + + GST_CAT_INFO_OBJECT (GST_CAT_CONTEXT, pad, "pad peer query failed"); + return TRUE; +} + +static gboolean +run_query (GstElement * element, GstQuery * query, GstPadDirection direction) +{ + GstIterator *it; + GstIteratorFoldFunction func = pad_query; + GValue res = { 0 }; + + g_value_init (&res, G_TYPE_BOOLEAN); + g_value_set_boolean (&res, FALSE); + + /* Ask neighbor */ + if (direction == GST_PAD_SRC) + it = gst_element_iterate_src_pads (element); + else + it = gst_element_iterate_sink_pads (element); + + while (gst_iterator_fold (it, func, &res, query) == GST_ITERATOR_RESYNC) + gst_iterator_resync (it); + + gst_iterator_free (it); + + return g_value_get_boolean (&res); +} + +static void +run_d3d11_context_query (GstElement * element) +{ + GstQuery *query; + GstContext *ctxt; + + /* 2a) Query downstream with GST_QUERY_CONTEXT for the context and + * check if downstream already has a context of the specific type + * 2b) Query upstream as above. + */ + query = gst_query_new_context (GST_D3D11_DEVICE_HANDLE_CONTEXT_TYPE); + if (run_query (element, query, GST_PAD_SRC)) { + gst_query_parse_context (query, &ctxt); + GST_CAT_INFO_OBJECT (GST_CAT_CONTEXT, element, + "found context (%" GST_PTR_FORMAT ") in downstream query", ctxt); + gst_element_set_context (element, ctxt); + } else if (run_query (element, query, GST_PAD_SINK)) { + gst_query_parse_context (query, &ctxt); + GST_CAT_INFO_OBJECT (GST_CAT_CONTEXT, element, + "found context (%" GST_PTR_FORMAT ") in upstream query", ctxt); + gst_element_set_context (element, ctxt); + } else { + /* 3) Post a GST_MESSAGE_NEED_CONTEXT message on the bus with + * the required context type and afterwards check if a + * usable context was set now as in 1). The message could + * be handled by the parent bins of the element and the + * application. + */ + GstMessage *msg; + + GST_CAT_INFO_OBJECT (GST_CAT_CONTEXT, element, + "posting need context message"); + msg = gst_message_new_need_context (GST_OBJECT_CAST (element), + GST_D3D11_DEVICE_HANDLE_CONTEXT_TYPE); + gst_element_post_message (element, msg); + } + + /* + * Whomever responds to the need-context message performs a + * GstElement::set_context() with the required context in which the element + * is required to update the display_ptr or call gst_gl_handle_set_context(). + */ + + gst_query_unref (query); +} + +/** + * gst_d3d11_ensure_element_data: + * @element: the #GstElement running the query + * @device: (inout): the resulting #GstD3D11Device + * @preferred_adapter: the index of preferred adapter + * + * Perform the steps necessary for retrieving a #GstD3D11Device + * from the surrounding elements or from the application using the #GstContext mechanism. + * + * If the contents of @device is not %NULL, then no #GstContext query is + * necessary for #GstD3D11Device retrieval is performed. + * + * Returns: whether a #GstD3D11Device exists in @device + */ +gboolean +gst_d3d11_ensure_element_data (GstElement * element, GstD3D11Device ** device, + gint preferred_adapter) +{ + GstD3D11Device *new_device; + GstContext *context; + + g_return_val_if_fail (element != NULL, FALSE); + g_return_val_if_fail (device != NULL, FALSE); + _init_context_debug (); + + if (*device) { + GST_LOG_OBJECT (element, "already have a device %" GST_PTR_FORMAT, *device); + return TRUE; + } + + run_d3d11_context_query (element); + + /* Neighbour found and it updated the devicey */ + if (*device) { + return TRUE; + } + + new_device = gst_d3d11_device_new (preferred_adapter); + + if (!new_device) { + GST_ERROR_OBJECT (element, + "Couldn't create new device with adapter index %d", preferred_adapter); + return FALSE; + } + + *device = new_device; + + context = gst_context_new (GST_D3D11_DEVICE_HANDLE_CONTEXT_TYPE, TRUE); + gst_context_set_d3d11_device (context, new_device); + + gst_element_set_context (element, context); + + GST_CAT_INFO_OBJECT (GST_CAT_CONTEXT, element, + "posting have context (%" GST_PTR_FORMAT + ") message with device (%" GST_PTR_FORMAT ")", context, device); + + gst_element_post_message (GST_ELEMENT_CAST (element), + gst_message_new_have_context (GST_OBJECT_CAST (element), context)); + + return TRUE; +} diff --git a/sys/d3d11/gstd3d11utils.h b/sys/d3d11/gstd3d11utils.h new file mode 100644 index 0000000..2fcd0f3 --- /dev/null +++ b/sys/d3d11/gstd3d11utils.h @@ -0,0 +1,57 @@ +/* GStreamer + * Copyright (C) 2019 Seungha Yang + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_D3D11_UTILS_H__ +#define __GST_D3D11_UTILS_H__ + +#include +#include + +#include "gstd3d11_fwd.h" + +G_BEGIN_DECLS + +GstVideoFormat gst_d3d11_dxgi_format_to_gst (DXGI_FORMAT format); + +DXGI_FORMAT gst_d3d11_dxgi_format_from_gst (GstVideoFormat format); + +GstCaps * gst_d3d11_device_get_supported_caps (GstD3D11Device * device, + D3D11_FORMAT_SUPPORT flags); + +gboolean gst_d3d11_calculate_buffer_size (GstVideoInfo * info, + guint pitch, + gsize offset[GST_VIDEO_MAX_PLANES], + gint stride[GST_VIDEO_MAX_PLANES], + gsize *size); + +gboolean gst_d3d11_handle_set_context (GstElement * element, + GstContext * context, + GstD3D11Device ** device); + +gboolean gst_d3d11_handle_context_query (GstElement * element, + GstQuery * query, + GstD3D11Device * device); + +gboolean gst_d3d11_ensure_element_data (GstElement * element, + GstD3D11Device ** device, + gint preferred_adapter); + +G_END_DECLS + +#endif /* __GST_D3D11_UTILS_H__ */ diff --git a/sys/d3d11/gstd3d11videosink.c b/sys/d3d11/gstd3d11videosink.c new file mode 100644 index 0000000..976c2fd --- /dev/null +++ b/sys/d3d11/gstd3d11videosink.c @@ -0,0 +1,815 @@ +/* GStreamer + * Copyright (C) 2019 Seungha Yang + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstd3d11videosink.h" +#include "gstd3d11memory.h" +#include "gstd3d11utils.h" +#include "gstd3d11device.h" +#include "gstd3d11bufferpool.h" + +enum +{ + PROP_0, + PROP_ADAPTER, + PROP_FORCE_ASPECT_RATIO, + PROP_ENABLE_NAVIGATION_EVENTS +}; + +#define DEFAULT_ADAPTER -1 +#define DEFAULT_FORCE_ASPECT_RATIO TRUE +#define DEFAULT_ENABLE_NAVIGATION_EVENTS TRUE + +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-raw, " + "format = (string) { BGRA, RGBA, RGB10A2_LE }, " + "framerate = (fraction) [ 0, MAX ], " + "width = (int) [ 1, MAX ], " "height = (int) [ 1, MAX ]") + ); + +GST_DEBUG_CATEGORY (d3d11_video_sink_debug); +#define GST_CAT_DEFAULT d3d11_video_sink_debug + +static void gst_d3d11_videosink_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_d3d11_videosink_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static void +gst_d3d11_video_sink_video_overlay_init (GstVideoOverlayInterface * iface); +static void +gst_d3d11_video_sink_navigation_init (GstNavigationInterface * iface); + +static void gst_d3d11_video_sink_set_context (GstElement * element, + GstContext * context); +static GstCaps *gst_d3d11_video_sink_get_caps (GstBaseSink * sink, + GstCaps * filter); +static gboolean gst_d3d11_video_sink_set_caps (GstBaseSink * sink, + GstCaps * caps); + +static gboolean gst_d3d11_video_sink_start (GstBaseSink * sink); +static gboolean gst_d3d11_video_sink_stop (GstBaseSink * sink); +static gboolean gst_d3d11_video_sink_propose_allocation (GstBaseSink * sink, + GstQuery * query); + +static GstFlowReturn +gst_d3d11_video_sink_show_frame (GstVideoSink * sink, GstBuffer * buf); + +#define gst_d3d11_video_sink_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GstD3D11VideoSink, gst_d3d11_video_sink, + GST_TYPE_VIDEO_SINK, + G_IMPLEMENT_INTERFACE (GST_TYPE_VIDEO_OVERLAY, + gst_d3d11_video_sink_video_overlay_init); + G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION, + gst_d3d11_video_sink_navigation_init); + GST_DEBUG_CATEGORY_INIT (d3d11_video_sink_debug, + "d3d11videosink", 0, "Direct3D11 Video Sink")); + +static void +gst_d3d11_video_sink_class_init (GstD3D11VideoSinkClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + GstBaseSinkClass *basesink_class = GST_BASE_SINK_CLASS (klass); + GstVideoSinkClass *videosink_class = GST_VIDEO_SINK_CLASS (klass); + + gobject_class->set_property = gst_d3d11_videosink_set_property; + gobject_class->get_property = gst_d3d11_videosink_get_property; + + g_object_class_install_property (gobject_class, PROP_ADAPTER, + g_param_spec_int ("adapter", "Adapter", + "Adapter index for creating device (-1 for default)", + -1, G_MAXINT32, DEFAULT_ADAPTER, + G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY | + 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, navigation events are sent upstream", + DEFAULT_ENABLE_NAVIGATION_EVENTS, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + element_class->set_context = + GST_DEBUG_FUNCPTR (gst_d3d11_video_sink_set_context); + + gst_element_class_set_static_metadata (element_class, + "Direct3D11 video sink", "Sink/Video", + "A Direct3D11 based videosink", + "Seungha Yang "); + + gst_element_class_add_static_pad_template (element_class, &sink_template); + + basesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_d3d11_video_sink_get_caps); + basesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_d3d11_video_sink_set_caps); + basesink_class->start = GST_DEBUG_FUNCPTR (gst_d3d11_video_sink_start); + basesink_class->stop = GST_DEBUG_FUNCPTR (gst_d3d11_video_sink_stop); + basesink_class->propose_allocation = + GST_DEBUG_FUNCPTR (gst_d3d11_video_sink_propose_allocation); + + videosink_class->show_frame = + GST_DEBUG_FUNCPTR (gst_d3d11_video_sink_show_frame); +} + +static void +gst_d3d11_video_sink_init (GstD3D11VideoSink * self) +{ + self->adapter = DEFAULT_ADAPTER; + self->force_aspect_ratio = DEFAULT_FORCE_ASPECT_RATIO; + self->enable_navigation_events = DEFAULT_ENABLE_NAVIGATION_EVENTS; +} + +static void +gst_d3d11_videosink_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstD3D11VideoSink *self = GST_D3D11_VIDEO_SINK (object); + + GST_OBJECT_LOCK (self); + switch (prop_id) { + case PROP_ADAPTER: + self->adapter = g_value_get_int (value); + break; + case PROP_FORCE_ASPECT_RATIO: + self->force_aspect_ratio = g_value_get_boolean (value); + if (self->window) + g_object_set (self->window, + "force-aspect-ratio", self->force_aspect_ratio, NULL); + break; + case PROP_ENABLE_NAVIGATION_EVENTS: + self->enable_navigation_events = g_value_get_boolean (value); + if (self->window) { + g_object_set (self->window, + "enable-navigation-events", self->enable_navigation_events, NULL); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + GST_OBJECT_UNLOCK (self); +} + +static void +gst_d3d11_videosink_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstD3D11VideoSink *self = GST_D3D11_VIDEO_SINK (object); + + switch (prop_id) { + case PROP_ADAPTER: + g_value_set_int (value, self->adapter); + break; + case PROP_FORCE_ASPECT_RATIO: + g_value_set_boolean (value, self->force_aspect_ratio); + break; + case PROP_ENABLE_NAVIGATION_EVENTS: + g_value_set_boolean (value, self->enable_navigation_events); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_d3d11_video_sink_set_context (GstElement * element, GstContext * context) +{ + GstD3D11VideoSink *self = GST_D3D11_VIDEO_SINK (element); + + gst_d3d11_handle_set_context (element, context, &self->device); + + GST_ELEMENT_CLASS (parent_class)->set_context (element, context); +} + +static GstCaps * +gst_d3d11_video_sink_get_caps (GstBaseSink * sink, GstCaps * filter) +{ + GstD3D11VideoSink *self = GST_D3D11_VIDEO_SINK (sink); + GstCaps *caps = NULL; + + if (self->device) + caps = gst_d3d11_device_get_supported_caps (self->device, + D3D11_FORMAT_SUPPORT_TEXTURE2D | D3D11_FORMAT_SUPPORT_DISPLAY | + D3D11_FORMAT_SUPPORT_RENDER_TARGET); + + if (!caps) + caps = gst_pad_get_pad_template_caps (GST_VIDEO_SINK_PAD (sink)); + + if (caps && filter) { + GstCaps *isect; + isect = gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST); + gst_caps_unref (caps); + caps = isect; + } + + return caps; +} + +static gboolean +gst_d3d11_video_sink_set_caps (GstBaseSink * sink, GstCaps * caps) +{ + GstD3D11VideoSink *self = GST_D3D11_VIDEO_SINK (sink); + GstCaps *sink_caps = NULL; + gint video_width, video_height; + gint video_par_n, video_par_d; /* video's PAR */ + gint display_par_n = 1, display_par_d = 1; /* display's PAR */ + guint num, den; + D3D11_TEXTURE2D_DESC desc = { 0, }; + ID3D11Texture2D *staging; + + GST_DEBUG_OBJECT (self, "set caps %" GST_PTR_FORMAT, caps); + + sink_caps = gst_d3d11_device_get_supported_caps (self->device, + D3D11_FORMAT_SUPPORT_TEXTURE2D | D3D11_FORMAT_SUPPORT_DISPLAY | + D3D11_FORMAT_SUPPORT_RENDER_TARGET); + + GST_DEBUG_OBJECT (self, "supported caps %" GST_PTR_FORMAT, sink_caps); + + if (!gst_caps_can_intersect (sink_caps, caps)) + goto incompatible_caps; + + gst_clear_caps (&sink_caps); + + if (!gst_video_info_from_caps (&self->info, caps)) + goto invalid_format; + + video_width = GST_VIDEO_INFO_WIDTH (&self->info); + video_height = GST_VIDEO_INFO_HEIGHT (&self->info); + video_par_n = GST_VIDEO_INFO_PAR_N (&self->info); + video_par_d = GST_VIDEO_INFO_PAR_D (&self->info); + + /* get aspect ratio from caps if it's present, and + * convert video width and height to a display width and height + * using wd / hd = wv / hv * PARv / PARd */ + + /* TODO: Get display PAR */ + + if (!gst_video_calculate_display_ratio (&num, &den, video_width, + video_height, video_par_n, video_par_d, display_par_n, display_par_d)) + goto no_disp_ratio; + + GST_DEBUG_OBJECT (sink, + "video width/height: %dx%d, calculated display ratio: %d/%d format: %s", + video_width, video_height, num, den, + gst_video_format_to_string (GST_VIDEO_INFO_FORMAT (&self->info))); + + /* now find a width x height that respects this display ratio. + * prefer those that have one of w/h the same as the incoming video + * using wd / hd = num / den + */ + + /* start with same height, because of interlaced video + * check hd / den is an integer scale factor, and scale wd with the PAR + */ + if (video_height % den == 0) { + GST_DEBUG_OBJECT (self, "keeping video height"); + GST_VIDEO_SINK_WIDTH (self) = (guint) + gst_util_uint64_scale_int (video_height, num, den); + GST_VIDEO_SINK_HEIGHT (self) = video_height; + } else if (video_width % num == 0) { + GST_DEBUG_OBJECT (self, "keeping video width"); + GST_VIDEO_SINK_WIDTH (self) = video_width; + GST_VIDEO_SINK_HEIGHT (self) = (guint) + gst_util_uint64_scale_int (video_width, den, num); + } else { + GST_DEBUG_OBJECT (self, "approximating while keeping video height"); + GST_VIDEO_SINK_WIDTH (self) = (guint) + gst_util_uint64_scale_int (video_height, num, den); + GST_VIDEO_SINK_HEIGHT (self) = video_height; + } + + GST_DEBUG_OBJECT (self, "scaling to %dx%d", + GST_VIDEO_SINK_WIDTH (self), GST_VIDEO_SINK_HEIGHT (self)); + + if (GST_VIDEO_SINK_WIDTH (self) <= 0 || GST_VIDEO_SINK_HEIGHT (self) <= 0) + goto no_display_size; + + self->dxgi_format = + gst_d3d11_dxgi_format_from_gst (GST_VIDEO_INFO_FORMAT (&self->info)); + + if (!self->window_id) + gst_video_overlay_prepare_window_handle (GST_VIDEO_OVERLAY (self)); + + if (self->window_id) { + GST_DEBUG_OBJECT (self, "Set external window %" G_GUINTPTR_FORMAT, + (guintptr) self->window_id); + gst_d3d11_window_set_window_handle (self->window, self->window_id); + } + + GST_OBJECT_LOCK (self); + if (!self->pending_render_rect) { + self->render_rect.x = 0; + self->render_rect.y = 0; + self->render_rect.w = GST_VIDEO_SINK_WIDTH (self); + self->render_rect.h = GST_VIDEO_SINK_HEIGHT (self); + } + + gst_d3d11_window_set_render_rectangle (self->window, + self->render_rect.x, self->render_rect.y, self->render_rect.w, + self->render_rect.h); + self->pending_render_rect = FALSE; + + if (!self->force_aspect_ratio) { + g_object_set (self->window, + "force-aspect-ratio", self->force_aspect_ratio, NULL); + } + + GST_OBJECT_UNLOCK (self); + + if (!gst_d3d11_window_prepare (self->window, GST_VIDEO_SINK_WIDTH (self), + GST_VIDEO_SINK_HEIGHT (self), self->dxgi_format, caps)) { + GST_ERROR_OBJECT (self, "cannot create swapchain"); + return FALSE; + } + + if (self->fallback_staging) { + gst_d3d11_device_release_texture (self->device, self->fallback_staging); + self->fallback_staging = NULL; + } + + desc.Width = GST_VIDEO_SINK_WIDTH (self); + desc.Height = GST_VIDEO_SINK_HEIGHT (self); + desc.MipLevels = 1; + desc.Format = self->dxgi_format; + desc.SampleDesc.Count = 1; + desc.ArraySize = 1; + desc.Usage = D3D11_USAGE_STAGING; + desc.CPUAccessFlags = (D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE); + + staging = gst_d3d11_device_create_texture (self->device, &desc, NULL); + if (!staging) { + GST_ERROR_OBJECT (self, "cannot create fallback staging texture"); + return FALSE; + } + + self->fallback_staging = staging; + + return TRUE; + + /* ERRORS */ +incompatible_caps: + { + GST_ERROR_OBJECT (sink, "caps incompatible"); + gst_clear_caps (&sink_caps); + return FALSE; + } +invalid_format: + { + GST_DEBUG_OBJECT (sink, + "Could not locate image format from caps %" GST_PTR_FORMAT, caps); + return FALSE; + } +no_disp_ratio: + { + GST_ELEMENT_ERROR (sink, CORE, NEGOTIATION, (NULL), + ("Error calculating the output display ratio of the video.")); + return FALSE; + } +no_display_size: + { + GST_ELEMENT_ERROR (sink, CORE, NEGOTIATION, (NULL), + ("Error calculating the output display ratio of the video.")); + return FALSE; + } +} + +static void +gst_d3d11_video_sink_key_event (GstD3D11Window * window, const gchar * event, + const gchar * key, GstD3D11VideoSink * self) +{ + if (self->enable_navigation_events) { + GST_LOG_OBJECT (self, "send key event %s, key %s", event, key); + gst_navigation_send_key_event (GST_NAVIGATION (self), event, key); + } +} + +static void +gst_d3d11_video_mouse_key_event (GstD3D11Window * window, const gchar * event, + gint button, gdouble x, gdouble y, GstD3D11VideoSink * self) +{ + if (self->enable_navigation_events) { + GST_LOG_OBJECT (self, + "send mouse event %s, button %d (%.1f, %.1f)", event, button, x, y); + gst_navigation_send_mouse_event (GST_NAVIGATION (self), event, button, x, + y); + } +} + +static void +gst_d3d11_video_sink_got_window_handle (GstD3D11Window * window, + gpointer window_handle, GstD3D11VideoSink * self) +{ + GST_LOG_OBJECT (self, + "got window handle %" G_GUINTPTR_FORMAT, (guintptr) window_handle); + gst_video_overlay_got_window_handle (GST_VIDEO_OVERLAY (self), + (guintptr) window_handle); +} + +static gboolean +gst_d3d11_video_sink_start (GstBaseSink * sink) +{ + GstD3D11VideoSink *self = GST_D3D11_VIDEO_SINK (sink); + + GST_DEBUG_OBJECT (self, "Start"); + + if (!gst_d3d11_ensure_element_data (GST_ELEMENT_CAST (self), &self->device, + self->adapter) || !self->device) { + GST_ERROR_OBJECT (sink, "Cannot create d3d11device"); + return FALSE; + } + + self->window = gst_d3d11_window_new (self->device); + if (!self->window) { + GST_ERROR_OBJECT (sink, "Cannot create d3d11window"); + return FALSE; + } + + g_object_set (self->window, + "enable-navigation-events", self->enable_navigation_events, NULL); + + g_signal_connect (self->window, "key-event", + G_CALLBACK (gst_d3d11_video_sink_key_event), self); + g_signal_connect (self->window, "mouse-event", + G_CALLBACK (gst_d3d11_video_mouse_key_event), self); + g_signal_connect (self->window, "got-window-handle", + G_CALLBACK (gst_d3d11_video_sink_got_window_handle), self); + + return TRUE; +} + +static gboolean +gst_d3d11_video_sink_stop (GstBaseSink * sink) +{ + GstD3D11VideoSink *self = GST_D3D11_VIDEO_SINK (sink); + + GST_DEBUG_OBJECT (self, "Stop"); + + if (self->fallback_staging) { + ID3D11Texture2D_Release (self->fallback_staging); + self->fallback_staging = NULL; + } + + gst_clear_object (&self->device); + gst_clear_object (&self->window); + + return TRUE; +} + +static gboolean +gst_d3d11_video_sink_propose_allocation (GstBaseSink * sink, GstQuery * query) +{ + GstD3D11VideoSink *self = GST_D3D11_VIDEO_SINK (sink); + GstStructure *config; + GstCaps *caps; + GstBufferPool *pool = NULL; + GstVideoInfo info; + guint size; + gboolean need_pool; + + if (!self->device || !self->window) + return FALSE; + + gst_query_parse_allocation (query, &caps, &need_pool); + + if (caps == NULL) + goto no_caps; + + if (!gst_video_info_from_caps (&info, caps)) + goto invalid_caps; + + /* the normal size of a frame */ + size = info.size; + + if (need_pool) { + GST_DEBUG_OBJECT (self, "create new pool"); + + pool = (GstBufferPool *) gst_d3d11_buffer_pool_new (self->device); + config = gst_buffer_pool_get_config (pool); + gst_buffer_pool_config_set_params (config, caps, size, 2, + DXGI_MAX_SWAP_CHAIN_BUFFERS); + + if (!gst_buffer_pool_set_config (pool, config)) { + g_object_unref (pool); + goto config_failed; + } + } + + gst_query_add_allocation_pool (query, pool, size, 2, + DXGI_MAX_SWAP_CHAIN_BUFFERS); + if (pool) + g_object_unref (pool); + + gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL); + gst_query_add_allocation_meta (query, GST_VIDEO_CROP_META_API_TYPE, NULL); + + return TRUE; + + /* ERRORS */ +no_caps: + { + GST_WARNING_OBJECT (self, "no caps specified"); + return FALSE; + } +invalid_caps: + { + GST_WARNING_OBJECT (self, "invalid caps specified"); + return FALSE; + } +config_failed: + { + GST_WARNING_OBJECT (self, "failed setting config"); + return FALSE; + } + + return TRUE; +} + +typedef struct +{ + GstD3D11VideoSink *sink; + + GstVideoFrame *frame; + ID3D11Resource *resource; + + GstFlowReturn ret; +} FrameUploadData; + +static void +_upload_frame (GstD3D11Device * device, gpointer data) +{ + GstD3D11VideoSink *self; + HRESULT hr; + ID3D11DeviceContext *device_context; + FrameUploadData *upload_data = (FrameUploadData *) data; + D3D11_MAPPED_SUBRESOURCE d3d11_map; + guint i; + guint8 *dst; + + self = upload_data->sink; + + device_context = gst_d3d11_device_get_device_context (device); + + hr = ID3D11DeviceContext_Map (device_context, + upload_data->resource, 0, D3D11_MAP_WRITE, 0, &d3d11_map); + + if (FAILED (hr)) { + GST_ERROR_OBJECT (self, "cannot map d3d11 staging texture"); + upload_data->ret = GST_FLOW_ERROR; + return; + } + + dst = d3d11_map.pData; + for (i = 0; i < GST_VIDEO_FRAME_N_PLANES (upload_data->frame); i++) { + guint w, h; + guint j; + guint8 *src; + gint src_stride; + + w = GST_VIDEO_FRAME_COMP_WIDTH (upload_data->frame, i) * + GST_VIDEO_FRAME_COMP_PSTRIDE (upload_data->frame, i); + h = GST_VIDEO_FRAME_COMP_HEIGHT (upload_data->frame, i); + src = GST_VIDEO_FRAME_PLANE_DATA (upload_data->frame, i); + src_stride = GST_VIDEO_FRAME_PLANE_STRIDE (upload_data->frame, i); + + for (j = 0; j < h; j++) { + memcpy (dst, src, w); + dst += d3d11_map.RowPitch; + src += src_stride; + } + } + + ID3D11DeviceContext_Unmap (device_context, upload_data->resource, 0); +} + +static GstFlowReturn +gst_d3d11_video_sink_show_frame (GstVideoSink * sink, GstBuffer * buf) +{ + GstD3D11VideoSink *self = GST_D3D11_VIDEO_SINK (sink); + GstVideoFrame frame; + FrameUploadData data; + ID3D11Texture2D *texture; + GstMapInfo map; + GstFlowReturn ret; + gboolean need_unmap = FALSE; + GstMemory *mem; + GstVideoRectangle rect = { 0, }; + GstVideoCropMeta *crop; + + if (gst_buffer_n_memory (buf) == 1 && (mem = gst_buffer_peek_memory (buf, 0)) + && gst_memory_is_type (mem, GST_D3D11_MEMORY_NAME)) { + /* If this buffer has been allocated using our buffer management we simply + put the ximage which is in the PRIVATE pointer */ + GST_TRACE_OBJECT (self, "buffer %p from our pool, writing directly", buf); + if (!gst_memory_map (mem, &map, (GST_MAP_READ | GST_MAP_D3D11))) { + GST_ERROR_OBJECT (self, "cannot map d3d11 memory"); + return GST_FLOW_ERROR; + } + + texture = (ID3D11Texture2D *) map.data; + need_unmap = TRUE; + } else { + if (!gst_video_frame_map (&frame, &self->info, buf, GST_MAP_READ)) { + GST_ERROR_OBJECT (self, "cannot map video frame"); + return GST_FLOW_ERROR; + } + + GST_TRACE_OBJECT (self, + "buffer %p out of our pool, write to stage buffer", buf); + + data.sink = self; + data.frame = &frame; + data.resource = (ID3D11Resource *) self->fallback_staging; + data.ret = GST_FLOW_OK; + + gst_d3d11_device_thread_add (self->device, (GstD3D11DeviceThreadFunc) + _upload_frame, &data); + + if (data.ret != GST_FLOW_OK) + return data.ret; + + gst_video_frame_unmap (&frame); + + texture = self->fallback_staging; + } + + gst_d3d11_window_show (self->window); + + crop = gst_buffer_get_video_crop_meta (buf); + if (crop) { + rect.x = crop->x; + rect.y = crop->y; + rect.w = crop->width; + rect.h = crop->height; + } else { + rect.w = GST_VIDEO_SINK_WIDTH (self); + rect.h = GST_VIDEO_SINK_HEIGHT (self); + } + + ret = gst_d3d11_window_render (self->window, texture, &rect); + + if (need_unmap) + gst_memory_unmap (mem, &map); + + if (ret == GST_D3D11_WINDOW_FLOW_CLOSED) { + GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND, + ("Output window was closed"), (NULL)); + + ret = GST_FLOW_ERROR; + } + + return ret; +} + +/* VideoOverlay interface */ +static void +gst_d3d11_video_sink_set_window_handle (GstVideoOverlay * overlay, + guintptr window_id) +{ + GstD3D11VideoSink *self = GST_D3D11_VIDEO_SINK (overlay); + + GST_DEBUG ("set window handle %" G_GUINTPTR_FORMAT, window_id); + + self->window_id = window_id; +} + +static void +gst_d3d11_video_sink_set_render_rectangle (GstVideoOverlay * overlay, gint x, + gint y, gint width, gint height) +{ + GstD3D11VideoSink *self = GST_D3D11_VIDEO_SINK (overlay); + + GST_DEBUG_OBJECT (self, + "render rect x: %d, y: %d, width: %d, height %d", x, y, width, height); + + GST_OBJECT_LOCK (self); + if (self->window) { + gst_d3d11_window_set_render_rectangle (self->window, x, y, width, height); + } else { + self->render_rect.x = x; + self->render_rect.y = y; + self->render_rect.w = width; + self->render_rect.h = height; + self->pending_render_rect = TRUE; + } + GST_OBJECT_UNLOCK (self); +} + +static void +gst_d3d11_video_sink_expose (GstVideoOverlay * overlay) +{ + GstD3D11VideoSink *self = GST_D3D11_VIDEO_SINK (overlay); + + if (self->window && self->window->swap_chain) { + GstVideoRectangle rect = { 0, }; + rect.w = GST_VIDEO_SINK_WIDTH (self); + rect.h = GST_VIDEO_SINK_HEIGHT (self); + + gst_d3d11_window_render (self->window, NULL, &rect); + } +} + +static void +gst_d3d11_video_sink_video_overlay_init (GstVideoOverlayInterface * iface) +{ + iface->set_window_handle = gst_d3d11_video_sink_set_window_handle; + iface->set_render_rectangle = gst_d3d11_video_sink_set_render_rectangle; + iface->expose = gst_d3d11_video_sink_expose; +} + +/* Navigation interface */ +static void +gst_d3d11_video_sink_navigation_send_event (GstNavigation * navigation, + GstStructure * structure) +{ + GstD3D11VideoSink *self = GST_D3D11_VIDEO_SINK (navigation); + gboolean handled = FALSE; + GstEvent *event = NULL; + GstVideoRectangle src = { 0, }; + GstVideoRectangle dst = { 0, }; + GstVideoRectangle result; + gdouble x, y, xscale = 1.0, yscale = 1.0; + + if (!self->window) { + gst_structure_free (structure); + return; + } + + if (self->force_aspect_ratio) { + /* We get the frame position using the calculated geometry from _setcaps + that respect pixel aspect ratios */ + src.w = GST_VIDEO_SINK_WIDTH (self); + src.h = GST_VIDEO_SINK_HEIGHT (self); + dst.w = self->render_rect.w; + dst.h = self->render_rect.h; + + gst_video_sink_center_rect (src, dst, &result, TRUE); + result.x += self->render_rect.x; + result.y += self->render_rect.y; + } else { + memcpy (&result, &self->render_rect, sizeof (GstVideoRectangle)); + } + + xscale = (gdouble) GST_VIDEO_INFO_WIDTH (&self->info) / result.w; + yscale = (gdouble) GST_VIDEO_INFO_HEIGHT (&self->info) / result.h; + + /* Converting pointer coordinates to the non scaled geometry */ + if (gst_structure_get_double (structure, "pointer_x", &x)) { + x = MIN (x, result.x + result.w); + x = MAX (x - result.x, 0); + gst_structure_set (structure, "pointer_x", G_TYPE_DOUBLE, + (gdouble) x * xscale, NULL); + } + if (gst_structure_get_double (structure, "pointer_y", &y)) { + y = MIN (y, result.y + result.h); + y = MAX (y - result.y, 0); + gst_structure_set (structure, "pointer_y", G_TYPE_DOUBLE, + (gdouble) y * yscale, NULL); + } + + event = gst_event_new_navigation (structure); + if (event) { + gst_event_ref (event); + handled = gst_pad_push_event (GST_VIDEO_SINK_PAD (self), event); + + if (!handled) + gst_element_post_message (GST_ELEMENT_CAST (self), + gst_navigation_message_new_event (GST_OBJECT_CAST (self), event)); + + gst_event_unref (event); + } +} + +static void +gst_d3d11_video_sink_navigation_init (GstNavigationInterface * iface) +{ + iface->send_event = gst_d3d11_video_sink_navigation_send_event; +} diff --git a/sys/d3d11/gstd3d11videosink.h b/sys/d3d11/gstd3d11videosink.h new file mode 100644 index 0000000..2fd81fc --- /dev/null +++ b/sys/d3d11/gstd3d11videosink.h @@ -0,0 +1,77 @@ +/* GStreamer + * Copyright (C) 2019 Seungha Yang + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_D3D11_VIDEO_SINK_H__ +#define __GST_D3D11_VIDEO_SINK_H__ + +#include +#include +#include +#include +#include + +#include "gstd3d11_fwd.h" +#include "gstd3d11window.h" + +G_BEGIN_DECLS + +#define GST_TYPE_D3D11_VIDEO_SINK (gst_d3d11_video_sink_get_type()) +#define GST_D3D11_VIDEO_SINK(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_D3D11_VIDEO_SINK,GstD3D11VideoSink)) +#define GST_D3D11_VIDEO_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_D3D11_VIDEO_SINK,GstD3D11VideoSinkClass)) +#define GST_D3D11_VIDEO_SINK_GET_CLASS(obj) (GST_D3D11_VIDEO_SINK_CLASS(G_OBJECT_GET_CLASS(obj))) +#define GST_IS_D3D11_VIDEO_SINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_D3D11_VIDEO_SINK)) +#define GST_IS_D3D11_VIDEO_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_D3D11_VIDEO_SINK)) + +typedef struct _GstD3D11VideoSink GstD3D11VideoSink; +typedef struct _GstD3D11VideoSinkClass GstD3D11VideoSinkClass; + +struct _GstD3D11VideoSink +{ + GstVideoSink sink; + GstD3D11Device *device; + GstD3D11Window *window; + + GstVideoInfo info; + DXGI_FORMAT dxgi_format; + + guintptr window_id; + + /* properties */ + gint adapter; + gboolean force_aspect_ratio; + gboolean enable_navigation_events; + + /* saved render rectangle until we have a window */ + GstVideoRectangle render_rect; + gboolean pending_render_rect; + + ID3D11Texture2D *fallback_staging; +}; + +struct _GstD3D11VideoSinkClass +{ + GstVideoSinkClass parent_class; +}; + +GType gst_d3d11_video_sink_get_type (void); + +G_END_DECLS + + +#endif /* __GST_D3D11_VIDEO_SINK_H__ */ diff --git a/sys/d3d11/gstd3d11window.c b/sys/d3d11/gstd3d11window.c new file mode 100644 index 0000000..e34e47c --- /dev/null +++ b/sys/d3d11/gstd3d11window.c @@ -0,0 +1,1072 @@ +/* + * GStreamer + * Copyright (C) 2008 Julien Isorce + * Copyright (C) 2012 Matthew Waters + * Copyright (C) 2019 Seungha Yang + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstd3d11window.h" +#include "gstd3d11device.h" + +#include + +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; + + 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; + + src_ratio = (gdouble) width / height; + dst_ratio = (gdouble) window->surface_width / window->surface_height; + + 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, + 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); + + 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->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; +} diff --git a/sys/d3d11/gstd3d11window.h b/sys/d3d11/gstd3d11window.h new file mode 100644 index 0000000..3e39e7a --- /dev/null +++ b/sys/d3d11/gstd3d11window.h @@ -0,0 +1,125 @@ +/* + * GStreamer + * Copyright (C) 2012 Matthew Waters + * Copyright (C) 2019 Seungha Yang + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_D3D11_WINDOW_H__ +#define __GST_D3D11_WINDOW_H__ + +#include +#include +#include "gstd3d11_fwd.h" + +G_BEGIN_DECLS + +#define GST_TYPE_D3D11_WINDOW (gst_d3d11_window_get_type()) +#define GST_D3D11_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_D3D11_WINDOW, GstD3D11Window)) +#define GST_D3D11_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS((klass), GST_TYPE_D3D11_WINDOW, GstD3D11WindowClass)) +#define GST_IS_D3D11_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_D3D11_WINDOW)) +#define GST_IS_D3D11_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_D3D11_WINDOW)) +#define GST_D3D11_WINDOW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GST_TYPE_D3D11_WINDOW, GstD3D11WindowClass)) + +typedef struct _GstD3D11Window GstD3D11Window; +typedef struct _GstD3D11WindowClass GstD3D11WindowClass; + +#define GST_D3D11_WINDOW_FLOW_CLOSED GST_FLOW_CUSTOM_ERROR + +struct _GstD3D11Window +{ + GstObject parent; + + GstVideoInfo info; + GstVideoMasteringDisplayInfo mastering_display_info; + GstVideoContentLightLevel content_light_level; + + GstVideoRectangle render_rect; + + GMutex lock; + GCond cond; + + GMainContext *main_context; + GMainLoop *loop; + + guint width; + guint height; + + guint surface_width; + guint surface_height; + + gboolean visible; + + GSource *msg_source; + GIOChannel *msg_io_channel; + + GThread *thread; + + gboolean created; + + HWND internal_win_id; + HWND external_win_id; + + HDC device_handle; + IDXGISwapChain *swap_chain; + ID3D11Texture2D *backbuffer; + ID3D11RenderTargetView *rtv; + DXGI_FORMAT format; + + GstD3D11Device *device; + + gboolean pending_resize; + + gboolean force_aspect_ratio; + gboolean enable_navigation_events; +}; + +struct _GstD3D11WindowClass +{ + GstObjectClass object_class; +}; + +GType gst_d3d11_window_get_type (void); + +GstD3D11Window * gst_d3d11_window_new (GstD3D11Device * device); + +void gst_d3d11_window_show (GstD3D11Window * window); + +void gst_d3d11_window_set_window_handle (GstD3D11Window * window, + guintptr id); + +void gst_d3d11_window_set_render_rectangle (GstD3D11Window * window, + gint x, gint y, + gint width, gint height); + +void gst_d3d11_window_get_surface_dimensions (GstD3D11Window * window, + guint * width, + guint * height); + +gboolean gst_d3d11_window_prepare (GstD3D11Window * window, + guint width, + guint height, + DXGI_FORMAT format, + GstCaps * caps); + +GstFlowReturn gst_d3d11_window_render (GstD3D11Window * window, + ID3D11Texture2D * texture, + GstVideoRectangle * src_rect); + +G_END_DECLS + +#endif /* __GST_D3D11_WINDOW_H__ */ diff --git a/sys/d3d11/meson.build b/sys/d3d11/meson.build new file mode 100644 index 0000000..bd080d1 --- /dev/null +++ b/sys/d3d11/meson.build @@ -0,0 +1,51 @@ +d3d11_sources = [ + 'gstd3d11bufferpool.c', + 'gstd3d11device.c', + 'gstd3d11memory.c', + 'gstd3d11utils.c', + 'gstd3d11videosink.c', + 'gstd3d11window.c', + 'plugin.c', +] + +have_d3d11 = false +extra_c_args = [] +extra_dep = [] + +d3d11_option = get_option('d3d11') +if host_system != 'windows' or d3d11_option.disabled() + subdir_done() +endif + +d3d11_lib = cc.find_library('d3d11', required : d3d11_option) +dxgi_lib = cc.find_library('dxgi', required : d3d11_option) + +have_d3d11 = d3d11_lib.found() and dxgi_lib.found() and cc.has_header('d3d11.h') and cc.has_header('dxgi.h') +if not have_d3d11 + if d3d11_option.enabled() + error('The d3d11 plugin was enabled explicitly, but required dependencies were not found.') + endif + subdir_done() +endif + +# required for HDR meatadata +if cc.has_header('dxgi1_5.h') + extra_c_args += ['-DHAVE_DXGI_1_5_H'] +endif + +# for enabling debug layer +if cc.has_header('d3d11sdklayers.h') + extra_c_args += ['-DHAVE_D3D11SDKLAYER_H'] + extra_dep += [gmodule_dep] +endif + +gstd3d11 = library('gstd3d11', + d3d11_sources, + c_args : gst_plugins_bad_args + extra_c_args, + include_directories : [configinc], + dependencies : [gstbase_dep, gstvideo_dep, gstallocators_dep, d3d11_lib, dxgi_lib] + extra_dep, + install : true, + install_dir : plugins_install_dir, +) +pkgconfig.generate(gstd3d11, install_dir : plugins_pkgconfig_install_dir) +plugins += [gstd3d11] diff --git a/sys/d3d11/plugin.c b/sys/d3d11/plugin.c new file mode 100644 index 0000000..11d95a6 --- /dev/null +++ b/sys/d3d11/plugin.c @@ -0,0 +1,38 @@ +/* GStreamer + * Copyright (C) 2019 Seungha Yang + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "gstd3d11videosink.h" + +static gboolean +plugin_init (GstPlugin * plugin) +{ + return gst_element_register (plugin, + "d3d11videosink", GST_RANK_SECONDARY - 1, GST_TYPE_D3D11_VIDEO_SINK); +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + d3d11, + "Direct3D11 plugin", + plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/sys/meson.build b/sys/meson.build index a42e872..0b69ad0 100644 --- a/sys/meson.build +++ b/sys/meson.build @@ -1,6 +1,7 @@ subdir('androidmedia') subdir('applemedia') subdir('bluez') +subdir('d3d11') subdir('d3dvideosink') subdir('decklink') subdir('directsound') -- 2.7.4