diff --git a/meson_options.txt b/meson_options.txt index 2a6dca3060..d6db86f3a3 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 0000000000..8643fd97cd --- /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 0000000000..7e1058cf48 --- /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 0000000000..bb86e16455 --- /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 0000000000..a586d1cd4e --- /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 0000000000..43449039ba --- /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 0000000000..6dcc52c953 --- /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 0000000000..406dd1ec44 --- /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 0000000000..0c8d49a763 --- /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 0000000000..2fcd0f3cd2 --- /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 0000000000..976c2fd69e --- /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 0000000000..2fd81fc118 --- /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 0000000000..e34e47ccd6 --- /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 0000000000..3e39e7a873 --- /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 0000000000..bd080d1a3a --- /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 0000000000..11d95a644f --- /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 a42e872fd2..0b69ad069a 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')