From eac069b46d7d53359b1a6059652794d45aa4b3e7 Mon Sep 17 00:00:00 2001 From: Seungha Yang <seungha@centricular.com> Date: Sun, 7 Apr 2024 19:23:52 +0900 Subject: [PATCH] d3d12: Add d3d12deinterlace element Adding D3D12 compute shader based deinterlace element with YADIF filtering Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/8140> --- .../gst/d3d12/gstd3d12device-private.h | 3 + .../gst-libs/gst/d3d12/gstd3d12device.cpp | 8 + .../sys/d3d12/gstd3d12deinterlace.cpp | 1101 +++++++++ .../sys/d3d12/gstd3d12deinterlace.h | 31 + .../sys/d3d12/gstd3d12yadif.cpp | 2061 +++++++++++++++++ .../gst-plugins-bad/sys/d3d12/gstd3d12yadif.h | 61 + .../gst-plugins-bad/sys/d3d12/meson.build | 2 + .../gst-plugins-bad/sys/d3d12/plugin.cpp | 3 + 8 files changed, 3270 insertions(+) create mode 100644 subprojects/gst-plugins-bad/sys/d3d12/gstd3d12deinterlace.cpp create mode 100644 subprojects/gst-plugins-bad/sys/d3d12/gstd3d12deinterlace.h create mode 100644 subprojects/gst-plugins-bad/sys/d3d12/gstd3d12yadif.cpp create mode 100644 subprojects/gst-plugins-bad/sys/d3d12/gstd3d12yadif.h diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/d3d12/gstd3d12device-private.h b/subprojects/gst-plugins-bad/gst-libs/gst/d3d12/gstd3d12device-private.h index a4a3aef397..010b10e292 100644 --- a/subprojects/gst-plugins-bad/gst-libs/gst/d3d12/gstd3d12device-private.h +++ b/subprojects/gst-plugins-bad/gst-libs/gst/d3d12/gstd3d12device-private.h @@ -100,6 +100,9 @@ HRESULT gst_d3d12_device_get_sampler_state (GstD3D12Device * device, GST_D3D12_API gboolean gst_d3d12_device_non_zeroed_supported (GstD3D12Device * device); +GST_D3D12_API +gboolean gst_d3d12_device_is_uma (GstD3D12Device * device); + GST_D3D12_API HRESULT gst_d3d12_device_get_converter_resources (GstD3D12Device * device, ID3D12Resource * index_buf, diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/d3d12/gstd3d12device.cpp b/subprojects/gst-plugins-bad/gst-libs/gst/d3d12/gstd3d12device.cpp index 411ca979be..02392a5451 100644 --- a/subprojects/gst-plugins-bad/gst-libs/gst/d3d12/gstd3d12device.cpp +++ b/subprojects/gst-plugins-bad/gst-libs/gst/d3d12/gstd3d12device.cpp @@ -2253,6 +2253,14 @@ gst_d3d12_device_non_zeroed_supported (GstD3D12Device * device) return device->priv->inner->non_zeroed_supported; } +gboolean +gst_d3d12_device_is_uma (GstD3D12Device * device) +{ + g_return_val_if_fail (GST_IS_D3D12_DEVICE (device), FALSE); + + return device->priv->inner->feature_support.UMA (); +} + /** * gst_d3d12_flush_all_devices: * diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12deinterlace.cpp b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12deinterlace.cpp new file mode 100644 index 0000000000..b5146e8360 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12deinterlace.cpp @@ -0,0 +1,1101 @@ +/* GStreamer + * Copyright (C) 2024 Seungha Yang <seungha@centricular.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstd3d12deinterlace.h" +#include "gstd3d12pluginutils.h" +#include "gstd3d12yadif.h" +#include <directx/d3dx12.h> +#include <mutex> +#include <memory> +#include <wrl.h> + +/* *INDENT-OFF* */ +using namespace Microsoft::WRL; +/* *INDENT-ON* */ + +GST_DEBUG_CATEGORY_STATIC (gst_d3d12_deinterlace_debug); +#define GST_CAT_DEFAULT gst_d3d12_deinterlace_debug + +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES + (GST_CAPS_FEATURE_MEMORY_D3D12_MEMORY, GST_D3D12_ALL_FORMATS) "; " + GST_VIDEO_CAPS_MAKE_WITH_FEATURES + (GST_CAPS_FEATURE_MEMORY_D3D12_MEMORY "," + GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, + GST_D3D12_ALL_FORMATS))); + +static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES + (GST_CAPS_FEATURE_MEMORY_D3D12_MEMORY, GST_D3D12_ALL_FORMATS) "; " + GST_VIDEO_CAPS_MAKE_WITH_FEATURES + (GST_CAPS_FEATURE_MEMORY_D3D12_MEMORY "," + GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, + GST_D3D12_ALL_FORMATS))); + + +enum GstD3D12DeinterlaceFields +{ + GST_D3D12_DEINTERLACE_FIELDS_ALL, + GST_D3D12_DEINTERLACE_FIELDS_TOP, + GST_D3D12_DEINTERLACE_FIELDS_BOTTOM, +}; + +#define GST_TYPE_D3D12_DEINTERLACE_FIELDS (gst_d3d12_deinterlace_fields_get_type()) +static GType +gst_d3d12_deinterlace_fields_get_type (void) +{ + static GType type = 0; + + GST_D3D12_CALL_ONCE_BEGIN { + static const GEnumValue types[] = { + {GST_D3D12_DEINTERLACE_FIELDS_ALL, "All fields", "all"}, + {GST_D3D12_DEINTERLACE_FIELDS_TOP, "Top fields only", "top"}, + {GST_D3D12_DEINTERLACE_FIELDS_BOTTOM, "Bottom fields only", "bottom"}, + {0, nullptr, nullptr}, + }; + + type = g_enum_register_static ("GstD3D12DeinterlaceFields", types); + } GST_D3D12_CALL_ONCE_END; + + return type; +} + +enum GstD3D12DeinterlaceEngine +{ + GST_D3D12_DEINTERLACE_ENGINE_AUTO, + GST_D3D12_DEINTERLACE_ENGINE_3D, + GST_D3D12_DEINTERLACE_ENGINE_COMPUTE, +}; + +#define GST_TYPE_D3D12_DEINTERLACE_ENGINE (gst_d3d12_deinterlace_engine_get_type()) +static GType +gst_d3d12_deinterlace_engine_get_type (void) +{ + static GType type = 0; + + GST_D3D12_CALL_ONCE_BEGIN { + static const GEnumValue types[] = { + {GST_D3D12_DEINTERLACE_ENGINE_AUTO, + "iGPU uses 3D engine, dGPU uses compute engine", "auto"}, + {GST_D3D12_DEINTERLACE_ENGINE_3D, "3D", "3d"}, + {GST_D3D12_DEINTERLACE_ENGINE_COMPUTE, "Compute", "compute"}, + {0, nullptr, nullptr}, + }; + + type = g_enum_register_static ("GstD3D12DeinterlaceEngine", types); + } GST_D3D12_CALL_ONCE_END; + + return type; +} + +enum +{ + PROP_0, + PROP_FIELDS, + PROP_ENGINE, +}; + +#define DEFAULT_FIELDS GST_D3D12_DEINTERLACE_FIELDS_ALL +#define DEFAULT_ENGINE GST_D3D12_DEINTERLACE_ENGINE_AUTO + + +/* *INDENT-OFF* */ +struct DeinterlaceConvCtx +{ + DeinterlaceConvCtx (GstD3D12Device * dev) + { + device = (GstD3D12Device *) gst_object_ref (dev); + auto device_handle = gst_d3d12_device_get_device_handle (device); + ca_pool = gst_d3d12_cmd_alloc_pool_new (device_handle, + D3D12_COMMAND_LIST_TYPE_DIRECT); + } + + ~DeinterlaceConvCtx () + { + gst_d3d12_device_fence_wait (device, D3D12_COMMAND_LIST_TYPE_DIRECT, + fence_val); + + if (pre_pool) + gst_buffer_pool_set_active (pre_pool, FALSE); + if (post_pool) + gst_buffer_pool_set_active (post_pool, FALSE); + cl = nullptr; + gst_clear_object (&pre_pool); + gst_clear_object (&post_pool); + gst_clear_object (&pre_conv); + gst_clear_object (&post_conv); + gst_clear_object (&ca_pool); + gst_clear_object (&device); + } + + GstD3D12Device *device = nullptr; + GstD3D12Converter *pre_conv = nullptr; + GstD3D12Converter *post_conv = nullptr; + GstBufferPool *pre_pool = nullptr; + GstBufferPool *post_pool = nullptr; + ComPtr<ID3D12GraphicsCommandList> cl; + GstD3D12CmdAllocPool *ca_pool = nullptr; + guint64 fence_val = 0; +}; + +struct GstD3D12DeinterlacePrivate +{ + GstD3D12DeinterlacePrivate () + { + fence_pool = gst_d3d12_fence_data_pool_new (); + } + + ~GstD3D12DeinterlacePrivate () + { + gst_clear_object (&yadif); + gst_clear_object (&fence_pool); + } + + std::mutex lock; + GstD3D12Yadif *yadif = nullptr; + GstD3D12FenceDataPool *fence_pool = nullptr; + std::shared_ptr<DeinterlaceConvCtx> conv_ctx; + GstVideoInfo in_info; + GstVideoInfo yadif_info; + GstClockTime latency = 0; + gboolean use_compute = FALSE; + GstD3D12DeinterlaceFields fields = DEFAULT_FIELDS; + GstD3D12DeinterlaceEngine engine = DEFAULT_ENGINE; +}; +/* *INDENT-ON* */ + +struct _GstD3D12Deinterlace +{ + GstD3D12BaseFilter parent; + + GstD3D12DeinterlacePrivate *priv; +}; + +static void gst_d3d12_deinterlace_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_d3d12_deinterlace_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void gst_d3d12_deinterlace_finalize (GObject * object); +static gboolean gst_d3d12_deinterlace_start (GstBaseTransform * trans); +static gboolean gst_d3d12_deinterlace_stop (GstBaseTransform * trans); +static GstCaps *gst_d3d12_deinterlace_transform_caps (GstBaseTransform * + trans, GstPadDirection direction, GstCaps * caps, GstCaps * filter); +static GstCaps *gst_d3d12_deinterlace_fixate_caps (GstBaseTransform * + base, GstPadDirection direction, GstCaps * caps, GstCaps * othercaps); +static gboolean gst_d3d12_deinterlace_propose_allocation (GstBaseTransform * + trans, GstQuery * decide_query, GstQuery * query); +static gboolean gst_d3d12_deinterlace_decide_allocation (GstBaseTransform * + trans, GstQuery * query); +static gboolean gst_d3d12_deinterlace_sink_event (GstBaseTransform * trans, + GstEvent * event); +static gboolean gst_d3d12_deinterlace_query (GstBaseTransform * trans, + GstPadDirection direction, GstQuery * query); +static gboolean gst_d3d12_deinterlace_set_info (GstD3D12BaseFilter * filter, + GstCaps * incaps, GstVideoInfo * in_info, GstCaps * outcaps, + GstVideoInfo * out_info); +static GstFlowReturn gst_d3d12_deinterlace_generate_output (GstBaseTransform * + trans, GstBuffer ** buffer); +static GstFlowReturn gst_d3d12_deinterlace_transform (GstBaseTransform * trans, + GstBuffer * inbuf, GstBuffer * outbuf); +static GstFlowReturn gst_d3d12_deinterlace_submit_input_buffer (GstBaseTransform + * trans, gboolean is_discont, GstBuffer * input); + +#define gst_d3d12_deinterlace_parent_class parent_class +G_DEFINE_TYPE (GstD3D12Deinterlace, gst_d3d12_deinterlace, + GST_TYPE_D3D12_BASE_FILTER); + +static void +gst_d3d12_deinterlace_class_init (GstD3D12DeinterlaceClass * klass) +{ + auto object_class = G_OBJECT_CLASS (klass); + auto element_class = GST_ELEMENT_CLASS (klass); + auto trans_class = GST_BASE_TRANSFORM_CLASS (klass); + auto filter_class = GST_D3D12_BASE_FILTER_CLASS (klass); + + object_class->set_property = gst_d3d12_deinterlace_set_property; + object_class->get_property = gst_d3d12_deinterlace_get_property; + object_class->finalize = gst_d3d12_deinterlace_finalize; + + g_object_class_install_property (object_class, PROP_FIELDS, + g_param_spec_enum ("fields", "Fields", "Fields to use for deinterlacing", + GST_TYPE_D3D12_DEINTERLACE_FIELDS, DEFAULT_FIELDS, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property (object_class, PROP_ENGINE, + g_param_spec_enum ("engine", "Engine", "Engine to use", + GST_TYPE_D3D12_DEINTERLACE_ENGINE, DEFAULT_ENGINE, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + + gst_element_class_add_static_pad_template (element_class, &sink_template); + gst_element_class_add_static_pad_template (element_class, &src_template); + + gst_element_class_set_static_metadata (element_class, + "Direct3D12 Deinterlacer", + "Filter/Deinterlace/Effect/Video/Hardware", + "A Direct3D12 deinterlacer element", + "Seungha Yang <seungha@centricular.com>"); + + trans_class->passthrough_on_same_caps = TRUE; + + trans_class->start = GST_DEBUG_FUNCPTR (gst_d3d12_deinterlace_start); + trans_class->stop = GST_DEBUG_FUNCPTR (gst_d3d12_deinterlace_stop); + trans_class->transform_caps = + GST_DEBUG_FUNCPTR (gst_d3d12_deinterlace_transform_caps); + trans_class->fixate_caps = + GST_DEBUG_FUNCPTR (gst_d3d12_deinterlace_fixate_caps); + trans_class->propose_allocation = + GST_DEBUG_FUNCPTR (gst_d3d12_deinterlace_propose_allocation); + trans_class->decide_allocation = + GST_DEBUG_FUNCPTR (gst_d3d12_deinterlace_decide_allocation); + trans_class->sink_event = + GST_DEBUG_FUNCPTR (gst_d3d12_deinterlace_sink_event); + trans_class->query = GST_DEBUG_FUNCPTR (gst_d3d12_deinterlace_query); + trans_class->submit_input_buffer = + GST_DEBUG_FUNCPTR (gst_d3d12_deinterlace_submit_input_buffer); + trans_class->generate_output = + GST_DEBUG_FUNCPTR (gst_d3d12_deinterlace_generate_output); + trans_class->transform = GST_DEBUG_FUNCPTR (gst_d3d12_deinterlace_transform); + + filter_class->set_info = GST_DEBUG_FUNCPTR (gst_d3d12_deinterlace_set_info); + + gst_type_mark_as_plugin_api (GST_TYPE_D3D12_SAMPLING_METHOD, + (GstPluginAPIFlags) 0); + + GST_DEBUG_CATEGORY_INIT (gst_d3d12_deinterlace_debug, "d3d12deinterlace", 0, + "d3d12deinterlace"); +} + +static void +gst_d3d12_deinterlace_init (GstD3D12Deinterlace * self) +{ + self->priv = new GstD3D12DeinterlacePrivate (); +} + +static void +gst_d3d12_deinterlace_finalize (GObject * object) +{ + auto self = GST_D3D12_DEINTERLACE (object); + + delete self->priv; + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gboolean +is_double_framerate (GstD3D12DeinterlaceFields fields) +{ + if (fields == GST_D3D12_DEINTERLACE_FIELDS_ALL) + return TRUE; + + return FALSE; +} + +static void +gst_d3d12_deinterlace_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + auto self = GST_D3D12_DEINTERLACE (object); + auto priv = self->priv; + + std::lock_guard < std::mutex > lk (priv->lock); + switch (prop_id) { + case PROP_FIELDS: + { + auto fields = (GstD3D12DeinterlaceFields) g_value_get_enum (value); + + if (priv->fields != fields) { + gboolean reconfig = FALSE; + if (is_double_framerate (priv->fields) != is_double_framerate (fields)) + reconfig = TRUE; + + priv->fields = fields; + if (priv->yadif) { + gst_d3d12_yadif_set_fields (priv->yadif, + (GstD3D12YadifFields) fields); + } + + if (reconfig) + gst_base_transform_reconfigure_src (GST_BASE_TRANSFORM (self)); + } + break; + } + case PROP_ENGINE: + priv->engine = (GstD3D12DeinterlaceEngine) g_value_get_enum (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (self, prop_id, pspec); + break; + } +} + +static void +gst_d3d12_deinterlace_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + auto self = GST_D3D12_DEINTERLACE (object); + auto priv = self->priv; + + std::lock_guard < std::mutex > lk (priv->lock); + switch (prop_id) { + case PROP_FIELDS: + g_value_set_enum (value, priv->fields); + break; + case PROP_ENGINE: + g_value_set_enum (value, priv->engine); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (self, prop_id, pspec); + break; + } +} + +static gboolean +gst_d3d12_deinterlace_start (GstBaseTransform * trans) +{ + auto self = GST_D3D12_DEINTERLACE (trans); + auto priv = self->priv; + + priv->latency = 0; + + return GST_BASE_TRANSFORM_CLASS (parent_class)->start (trans); +} + +static gboolean +gst_d3d12_deinterlace_stop (GstBaseTransform * trans) +{ + auto self = GST_D3D12_DEINTERLACE (trans); + auto priv = self->priv; + + { + std::lock_guard < std::mutex > lk (priv->lock); + gst_clear_object (&priv->yadif); + priv->conv_ctx = nullptr; + } + + return GST_BASE_TRANSFORM_CLASS (parent_class)->stop (trans); +} + +static GstCaps * +gst_d3d12_deinterlace_remove_interlace_info (GstCaps * caps, + gboolean remove_framerate) +{ + auto res = gst_caps_new_empty (); + auto n = gst_caps_get_size (caps); + for (guint i = 0; i < n; i++) { + auto s = gst_caps_get_structure (caps, i); + auto f = gst_caps_get_features (caps, i); + + /* If this is already expressed by the existing caps + * skip this structure */ + if (i > 0 && gst_caps_is_subset_structure_full (res, s, f)) + continue; + + s = gst_structure_copy (s); + /* Only remove format info for the cases when we can actually convert */ + if (!gst_caps_features_is_any (f) + && gst_caps_features_contains (f, GST_CAPS_FEATURE_MEMORY_D3D12_MEMORY)) { + if (remove_framerate) { + gst_structure_remove_fields (s, + "interlace-mode", "field-order", "framerate", nullptr); + } else { + gst_structure_remove_fields (s, + "interlace-mode", "field-order", nullptr); + } + } + + gst_caps_append_structure_full (res, s, gst_caps_features_copy (f)); + } + + return res; +} + +static GstCaps * +gst_d3d12_deinterlace_transform_caps (GstBaseTransform * trans, + GstPadDirection direction, GstCaps * caps, GstCaps * filter) +{ + auto self = GST_D3D12_DEINTERLACE (trans); + auto priv = self->priv; + + /* Get all possible caps that we can transform to */ + auto ret = gst_d3d12_deinterlace_remove_interlace_info (caps, + is_double_framerate (priv->fields)); + + if (filter) { + auto tmp = gst_caps_intersect_full (filter, ret, GST_CAPS_INTERSECT_FIRST); + gst_caps_unref (ret); + ret = tmp; + } + + GST_DEBUG_OBJECT (trans, "transformed %" GST_PTR_FORMAT " into %" + GST_PTR_FORMAT, caps, ret); + + return ret; +} + +static GstCaps * +gst_d3d12_deinterlace_fixate_caps (GstBaseTransform * trans, + GstPadDirection direction, GstCaps * caps, GstCaps * othercaps) +{ + auto self = GST_D3D12_DEINTERLACE (trans); + auto priv = self->priv; + + std::lock_guard < std::mutex > lk (priv->lock); + + GST_DEBUG_OBJECT (self, + "trying to fixate othercaps %" GST_PTR_FORMAT " based on caps %" + GST_PTR_FORMAT, othercaps, caps); + + othercaps = gst_caps_truncate (othercaps); + othercaps = gst_caps_make_writable (othercaps); + + if (direction == GST_PAD_SRC) + return gst_caps_fixate (othercaps); + + auto tmp = gst_caps_copy (caps); + tmp = gst_caps_fixate (tmp); + + GstVideoInfo info; + if (!gst_video_info_from_caps (&info, tmp)) { + GST_WARNING_OBJECT (self, "Invalid caps %" GST_PTR_FORMAT, caps); + gst_caps_unref (tmp); + + return gst_caps_fixate (othercaps); + } + + auto s = gst_caps_get_structure (tmp, 0); + + gint fps_n, fps_d; + if (is_double_framerate (priv->fields) && + gst_structure_get_fraction (s, "framerate", &fps_n, &fps_d) && + fps_n > 0 && fps_d > 0) { + /* for non-blend method, output framerate will be doubled */ + if (GST_VIDEO_INFO_IS_INTERLACED (&info)) { + fps_n *= 2; + } + + gst_caps_set_simple (othercaps, + "framerate", GST_TYPE_FRACTION, fps_n, fps_d, nullptr); + } + + auto interlace_mode = gst_structure_get_string (s, "interlace-mode"); + if (g_strcmp0 ("progressive", interlace_mode) == 0) { + /* Just forward interlace-mode=progressive. + * By this way, basetransform will enable passthrough for non-interlaced + * stream*/ + gst_caps_set_simple (othercaps, + "interlace-mode", G_TYPE_STRING, "progressive", nullptr); + } + + gst_caps_unref (tmp); + + return gst_caps_fixate (othercaps); +} + +static gboolean +gst_d3d12_deinterlace_propose_allocation (GstBaseTransform * trans, + GstQuery * decide_query, GstQuery * query) +{ + auto filter = GST_D3D12_BASE_FILTER (trans); + GstVideoInfo info; + GstBufferPool *pool; + GstCaps *caps; + guint size; + gboolean is_d3d12 = FALSE; + + if (!GST_BASE_TRANSFORM_CLASS (parent_class)->propose_allocation (trans, + decide_query, query)) + return FALSE; + + /* passthrough, we're done */ + if (!decide_query) + return TRUE; + + gst_query_parse_allocation (query, &caps, nullptr); + + if (!caps) { + GST_WARNING_OBJECT (filter, "Allocation query without caps"); + return FALSE; + } + + if (!gst_video_info_from_caps (&info, caps)) + return FALSE; + + if (gst_query_get_n_allocation_pools (query) == 0) { + GstCapsFeatures *features; + GstStructure *config; + + features = gst_caps_get_features (caps, 0); + + if (features && gst_caps_features_contains (features, + GST_CAPS_FEATURE_MEMORY_D3D12_MEMORY)) { + GST_DEBUG_OBJECT (filter, "upstream support d3d12 memory"); + pool = gst_d3d12_buffer_pool_new (filter->device); + is_d3d12 = TRUE; + } else { + pool = gst_video_buffer_pool_new (); + } + + config = gst_buffer_pool_get_config (pool); + + gst_buffer_pool_config_add_option (config, + GST_BUFFER_POOL_OPTION_VIDEO_META); + if (!is_d3d12) { + gst_buffer_pool_config_add_option (config, + GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT); + } + + size = GST_VIDEO_INFO_SIZE (&info); + gst_buffer_pool_config_set_params (config, caps, size, 0, 0); + + if (!gst_buffer_pool_set_config (pool, config)) { + GST_ERROR_OBJECT (filter, "Bufferpool config failed"); + gst_object_unref (pool); + return FALSE; + } + + /* d3d12 buffer pool will update buffer size based on allocated texture, + * get size from config again */ + config = gst_buffer_pool_get_config (pool); + gst_buffer_pool_config_get_params (config, + nullptr, &size, nullptr, nullptr); + gst_structure_free (config); + + gst_query_add_allocation_pool (query, pool, size, 0, 0); + gst_object_unref (pool); + } + + gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, nullptr); + gst_query_add_allocation_meta (query, + GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, nullptr); + + return TRUE; +} + +static gboolean +gst_d3d12_deinterlace_decide_allocation (GstBaseTransform * trans, + GstQuery * query) +{ + auto filter = GST_D3D12_BASE_FILTER (trans); + GstCaps *outcaps = nullptr; + GstBufferPool *pool = nullptr; + guint size, min = 0, max = 0; + GstStructure *config; + gboolean update_pool = FALSE; + GstVideoInfo info; + + gst_query_parse_allocation (query, &outcaps, nullptr); + + if (!outcaps) + return FALSE; + + if (!gst_video_info_from_caps (&info, outcaps)) { + GST_ERROR_OBJECT (filter, "Invalid caps %" GST_PTR_FORMAT, outcaps); + return FALSE; + } + + size = GST_VIDEO_INFO_SIZE (&info); + if (gst_query_get_n_allocation_pools (query) > 0) { + gst_query_parse_nth_allocation_pool (query, 0, &pool, &size, &min, &max); + if (pool) { + if (!GST_IS_D3D12_BUFFER_POOL (pool)) { + gst_clear_object (&pool); + } else { + auto dpool = GST_D3D12_BUFFER_POOL (pool); + if (!gst_d3d12_device_is_equal (dpool->device, filter->device)) + gst_clear_object (&pool); + } + } + + update_pool = TRUE; + } + + if (!pool) + pool = gst_d3d12_buffer_pool_new (filter->device); + + config = gst_buffer_pool_get_config (pool); + gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META); + gst_buffer_pool_config_set_params (config, outcaps, size, min, max); + gst_buffer_pool_set_config (pool, config); + + /* d3d12 buffer pool will update buffer size based on allocated texture, + * get size from config again */ + config = gst_buffer_pool_get_config (pool); + gst_buffer_pool_config_get_params (config, nullptr, &size, nullptr, nullptr); + gst_structure_free (config); + + if (update_pool) + gst_query_set_nth_allocation_pool (query, 0, pool, size, min, max); + else + gst_query_add_allocation_pool (query, pool, size, min, max); + + gst_object_unref (pool); + + return GST_BASE_TRANSFORM_CLASS (parent_class)->decide_allocation (trans, + query); +} + +static void +gst_d3d12_deinterlace_drain (GstD3D12Deinterlace * self) +{ + auto trans = GST_BASE_TRANSFORM (self); + auto priv = self->priv; + GstFlowReturn ret = GST_FLOW_OK; + GstBuffer *outbuf = nullptr; + + if (!priv->yadif) + return; + + if (gst_base_transform_is_passthrough (trans)) { + gst_d3d12_yadif_flush (priv->yadif); + return; + } + + gst_d3d12_yadif_drain (priv->yadif); + do { + outbuf = nullptr; + ret = gst_d3d12_yadif_pop (priv->yadif, &outbuf); + if (ret == GST_D3D12_YADIF_FLOW_NEED_DATA) + ret = GST_FLOW_OK; + + if (outbuf) + ret = gst_pad_push (GST_BASE_TRANSFORM_SRC_PAD (trans), outbuf); + } while (ret == GST_FLOW_OK && outbuf); +} + +static gboolean +gst_d3d12_deinterlace_prepare_convert (GstD3D12Deinterlace * self, + GstCaps * in_caps, const GstVideoInfo * in_info, GstVideoInfo * yadif_info) +{ + auto filter = GST_D3D12_BASE_FILTER (self); + auto priv = self->priv; + + auto format = GST_VIDEO_INFO_FORMAT (in_info); + switch (format) { + case GST_VIDEO_FORMAT_RGB16: + case GST_VIDEO_FORMAT_BGR16: + case GST_VIDEO_FORMAT_RGB15: + case GST_VIDEO_FORMAT_BGR15: + break; + default: + *yadif_info = *in_info; + return TRUE; + } + + gst_video_info_set_interlaced_format (yadif_info, GST_VIDEO_FORMAT_RGBA, + in_info->interlace_mode, in_info->width, in_info->height); + GST_VIDEO_INFO_FIELD_ORDER (yadif_info) = + GST_VIDEO_INFO_FIELD_ORDER (in_info); + + GstCaps *caps = gst_video_info_to_caps (yadif_info); + + auto ctx = std::make_shared < DeinterlaceConvCtx > (filter->device); + ctx->pre_pool = gst_d3d12_buffer_pool_new (filter->device); + ctx->post_pool = gst_d3d12_buffer_pool_new (filter->device); + + auto config = gst_buffer_pool_get_config (ctx->pre_pool); + gst_buffer_pool_config_set_params (config, caps, yadif_info->size, 0, 0); + gst_caps_unref (caps); + if (!gst_buffer_pool_set_config (ctx->pre_pool, config)) { + GST_ERROR_OBJECT (self, "Couldn't set pool config"); + return FALSE; + } + + if (!gst_buffer_pool_set_active (ctx->pre_pool, TRUE)) { + GST_ERROR_OBJECT (self, "Pool active failed"); + return FALSE; + } + + config = gst_buffer_pool_get_config (ctx->post_pool); + gst_buffer_pool_config_set_params (config, in_caps, in_info->size, 0, 0); + + if (!gst_buffer_pool_set_config (ctx->post_pool, config)) { + GST_ERROR_OBJECT (self, "Couldn't set pool config"); + return FALSE; + } + + if (!gst_buffer_pool_set_active (ctx->post_pool, TRUE)) { + GST_ERROR_OBJECT (self, "Pool active failed"); + return FALSE; + } + + config = gst_structure_new ("convert-config", + GST_D3D12_CONVERTER_OPT_SAMPLER_FILTER, + GST_TYPE_D3D12_CONVERTER_SAMPLER_FILTER, + D3D12_FILTER_MIN_MAG_MIP_POINT, nullptr); + + ctx->pre_conv = gst_d3d12_converter_new (filter->device, + nullptr, in_info, yadif_info, nullptr, nullptr, + gst_structure_copy (config)); + if (!ctx->pre_conv) { + GST_ERROR_OBJECT (self, "Couldn't create pre converter"); + gst_structure_free (config); + return FALSE; + } + + ctx->post_conv = gst_d3d12_converter_new (filter->device, + nullptr, yadif_info, in_info, nullptr, nullptr, config); + if (!ctx->post_conv) { + GST_ERROR_OBJECT (self, "Couldn't create post converter"); + return FALSE; + } + + priv->conv_ctx = ctx; + + return TRUE; +} + +static gboolean +gst_d3d12_deinterlace_set_info (GstD3D12BaseFilter * filter, + GstCaps * incaps, GstVideoInfo * in_info, GstCaps * outcaps, + GstVideoInfo * out_info) +{ + auto trans = GST_BASE_TRANSFORM (filter); + auto self = GST_D3D12_DEINTERLACE (filter); + auto priv = self->priv; + + gboolean post_msg = FALSE; + + { + std::lock_guard < std::mutex > lk (priv->lock); + auto fps_n = in_info->fps_n; + auto fps_d = in_info->fps_d; + if (fps_n <= 0 || fps_d <= 0) { + fps_n = 25; + fps_d = 1; + } + GstClockTime latency = gst_util_uint64_scale (GST_SECOND, fps_d, fps_n); + if (latency != priv->latency) { + priv->latency = latency; + post_msg = TRUE; + } + + gst_clear_object (&priv->yadif); + priv->conv_ctx = nullptr; + + if (!GST_VIDEO_INFO_IS_INTERLACED (in_info)) { + gst_base_transform_set_passthrough (trans, TRUE); + } else { + gst_base_transform_set_passthrough (trans, FALSE); + } + + if (gst_base_transform_is_passthrough (trans)) + return TRUE; + + priv->in_info = *in_info; + if (!gst_d3d12_deinterlace_prepare_convert (self, incaps, &priv->in_info, + &priv->yadif_info)) { + return FALSE; + } + + priv->use_compute = FALSE; + if (priv->engine == GST_D3D12_DEINTERLACE_ENGINE_COMPUTE) { + priv->use_compute = TRUE; + } else if (priv->engine == GST_D3D12_DEINTERLACE_ENGINE_AUTO && + !gst_d3d12_device_is_uma (filter->device) && !priv->conv_ctx) { + /* Since yadif shader is full compute shader, in case of dGPU, + * prefer compute queue so that task can be overlapped with other 3D tasks + */ + priv->use_compute = TRUE; + } + + GST_DEBUG_OBJECT (self, "Use compute engine: %d", priv->use_compute); + + priv->yadif = gst_d3d12_yadif_new (filter->device, &priv->yadif_info, + priv->use_compute); + if (!priv->yadif) { + GST_ERROR_OBJECT (self, "Couldn't create yadif object"); + priv->conv_ctx = nullptr; + return FALSE; + } + + gst_d3d12_yadif_set_direction (priv->yadif, trans->segment.rate >= 0); + gst_d3d12_yadif_set_fields (priv->yadif, + (GstD3D12YadifFields) priv->fields); + } + + if (post_msg) { + gst_element_post_message (GST_ELEMENT_CAST (self), + gst_message_new_latency (GST_OBJECT_CAST (self))); + } + + return TRUE; +} + +static gboolean +gst_d3d12_deinterlace_sink_event (GstBaseTransform * trans, GstEvent * event) +{ + auto self = GST_D3D12_DEINTERLACE (trans); + auto priv = self->priv; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_EOS: + gst_d3d12_deinterlace_drain (self); + break; + case GST_EVENT_FLUSH_STOP: + if (priv->yadif) + gst_d3d12_yadif_flush (priv->yadif); + break; + case GST_EVENT_SEGMENT: + if (priv->yadif) { + const GstSegment *segment; + gst_event_parse_segment (event, &segment); + if (segment->format == GST_FORMAT_TIME) { + std::lock_guard < std::mutex > lk (priv->lock); + gst_d3d12_yadif_set_direction (priv->yadif, segment->rate >= 0); + } + } + break; + default: + break; + } + + return GST_BASE_TRANSFORM_CLASS (parent_class)->sink_event (trans, event); +} + +static gboolean +gst_d3d12_deinterlace_query (GstBaseTransform * trans, + GstPadDirection direction, GstQuery * query) +{ + auto self = GST_D3D12_DEINTERLACE (trans); + auto priv = self->priv; + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_LATENCY: + { + GstClockTime latency; + + { + std::lock_guard < std::mutex > lk (priv->lock); + latency = priv->latency; + } + + if (latency != 0 && GST_CLOCK_TIME_IS_VALID (latency) && + !gst_base_transform_is_passthrough (trans)) { + auto otherpad = (direction == GST_PAD_SRC) ? + GST_BASE_TRANSFORM_SINK_PAD (trans) : + GST_BASE_TRANSFORM_SRC_PAD (trans); + + auto ret = gst_pad_peer_query (otherpad, query); + if (ret) { + gboolean live; + GstClockTime min_latency, max_latency; + gst_query_parse_latency (query, &live, &min_latency, &max_latency); + + GST_DEBUG_OBJECT (self, "peer latency: min %" + GST_TIME_FORMAT " max %" GST_TIME_FORMAT + ", our latency: %" GST_TIME_FORMAT, + GST_TIME_ARGS (min_latency), GST_TIME_ARGS (max_latency), + GST_TIME_ARGS (latency)); + + min_latency += latency; + if (GST_CLOCK_TIME_IS_VALID (max_latency)) + max_latency += latency; + + gst_query_set_latency (query, live, min_latency, max_latency); + } + + return ret; + } + break; + } + default: + break; + } + + return GST_BASE_TRANSFORM_CLASS (parent_class)->query (trans, direction, + query); +} + +static GstBuffer * +gst_d3d12_deinterlace_convert (GstD3D12Deinterlace * self, GstBuffer * buffer, + gboolean is_preproc) +{ + auto priv = self->priv; + if (!priv->conv_ctx) + return buffer; + + GstBuffer *outbuf = nullptr; + auto ctx = priv->conv_ctx; + GstBufferPool *pool; + GstD3D12Converter *conv; + if (is_preproc) { + pool = ctx->pre_pool; + conv = ctx->pre_conv; + } else { + pool = ctx->post_pool; + conv = ctx->post_conv; + } + + gst_buffer_pool_acquire_buffer (pool, &outbuf, nullptr); + if (!outbuf) { + GST_ERROR_OBJECT (self, "Couldn't acquire buffer"); + gst_buffer_unref (buffer); + return nullptr; + } + + gst_buffer_copy_into (outbuf, buffer, GST_BUFFER_COPY_METADATA, 0, -1); + + GstD3D12FenceData *fence_data; + gst_d3d12_fence_data_pool_acquire (priv->fence_pool, &fence_data); + gst_d3d12_fence_data_push (fence_data, FENCE_NOTIFY_MINI_OBJECT (buffer)); + + GstD3D12CmdAlloc *gst_ca; + if (!gst_d3d12_cmd_alloc_pool_acquire (ctx->ca_pool, &gst_ca)) { + GST_ERROR_OBJECT (self, "Couldn't acquire command allocator"); + gst_d3d12_fence_data_unref (fence_data); + gst_buffer_unref (outbuf); + return nullptr; + } + + auto ca = gst_d3d12_cmd_alloc_get_handle (gst_ca); + gst_d3d12_fence_data_push (fence_data, FENCE_NOTIFY_MINI_OBJECT (gst_ca)); + + auto hr = ca->Reset (); + if (!gst_d3d12_result (hr, ctx->device)) { + GST_ERROR_OBJECT (self, "Couldn't reset command allocator"); + gst_d3d12_fence_data_unref (fence_data); + gst_buffer_unref (outbuf); + return nullptr; + } + + auto device = gst_d3d12_device_get_device_handle (ctx->device); + if (!ctx->cl) { + hr = device->CreateCommandList (0, D3D12_COMMAND_LIST_TYPE_DIRECT, + ca, nullptr, IID_PPV_ARGS (&ctx->cl)); + } else { + hr = ctx->cl->Reset (ca, nullptr); + } + + if (!gst_d3d12_result (hr, ctx->device)) { + GST_ERROR_OBJECT (self, "Couldn't reset command list"); + gst_d3d12_fence_data_unref (fence_data); + gst_buffer_unref (outbuf); + return nullptr; + } + + if (!gst_d3d12_converter_convert_buffer (conv, buffer, outbuf, + fence_data, ctx->cl.Get (), is_preproc ? TRUE : priv->use_compute)) { + GST_ERROR_OBJECT (self, "Couldn't convert buffer"); + gst_d3d12_fence_data_unref (fence_data); + gst_buffer_unref (outbuf); + return nullptr; + } + + hr = ctx->cl->Close (); + if (!gst_d3d12_result (hr, ctx->device)) { + GST_ERROR_OBJECT (self, "Couldn't execute command list"); + gst_d3d12_fence_data_unref (fence_data); + gst_buffer_unref (outbuf); + return nullptr; + } + + ID3D12CommandList *cmd_list[] = { ctx->cl.Get () }; + hr = gst_d3d12_device_execute_command_lists (ctx->device, + D3D12_COMMAND_LIST_TYPE_DIRECT, 1, cmd_list, &ctx->fence_val); + + if (!gst_d3d12_result (hr, ctx->device)) { + GST_ERROR_OBJECT (self, "Couldn't execute command list"); + gst_d3d12_fence_data_unref (fence_data); + gst_buffer_unref (outbuf); + return nullptr; + } + + gst_d3d12_device_set_fence_notify (ctx->device, + D3D12_COMMAND_LIST_TYPE_DIRECT, ctx->fence_val, + FENCE_NOTIFY_MINI_OBJECT (fence_data)); + + auto fence = gst_d3d12_device_get_fence_handle (ctx->device, + D3D12_COMMAND_LIST_TYPE_DIRECT); + gst_d3d12_buffer_set_fence (outbuf, fence, ctx->fence_val, FALSE); + + return outbuf; +} + +static GstFlowReturn +gst_d3d12_deinterlace_submit_input_buffer (GstBaseTransform * trans, + gboolean is_discont, GstBuffer * input) +{ + auto self = GST_D3D12_DEINTERLACE (trans); + auto priv = self->priv; + + /* Let baseclass handle QoS first */ + auto ret = GST_BASE_TRANSFORM_CLASS (parent_class)->submit_input_buffer + (trans, is_discont, input); + if (ret != GST_FLOW_OK) + return ret; + + /* at this moment, baseclass must hold queued_buf */ + g_assert (trans->queued_buf != NULL); + auto buf = trans->queued_buf; + trans->queued_buf = nullptr; + + buf = gst_d3d12_deinterlace_convert (self, buf, TRUE); + if (!buf) + return GST_FLOW_ERROR; + + ret = gst_d3d12_yadif_push (priv->yadif, buf); + if (ret == GST_D3D12_YADIF_FLOW_NEED_DATA) + ret = GST_FLOW_OK; + + return ret; +} + +static GstFlowReturn +gst_d3d12_deinterlace_generate_output (GstBaseTransform * trans, + GstBuffer ** buffer) +{ + auto self = GST_D3D12_DEINTERLACE (trans); + auto priv = self->priv; + + if (gst_base_transform_is_passthrough (trans)) { + return GST_BASE_TRANSFORM_CLASS (parent_class)->generate_output (trans, + buffer); + } + + GstBuffer *outbuf = nullptr; + auto ret = gst_d3d12_yadif_pop (priv->yadif, &outbuf); + if (ret == GST_D3D12_YADIF_FLOW_NEED_DATA) + ret = GST_FLOW_OK; + + if (outbuf) { + outbuf = gst_d3d12_deinterlace_convert (self, outbuf, FALSE); + if (!outbuf) + ret = GST_FLOW_ERROR; + } + + *buffer = outbuf; + + return ret; +} + +static GstFlowReturn +gst_d3d12_deinterlace_transform (GstBaseTransform * trans, GstBuffer * inbuf, + GstBuffer * outbuf) +{ + /* generate_output() will do actual process */ + return GST_FLOW_OK; +} diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12deinterlace.h b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12deinterlace.h new file mode 100644 index 0000000000..2309870e01 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12deinterlace.h @@ -0,0 +1,31 @@ +/* GStreamer + * Copyright (C) 2024 Seungha Yang <seungha@centricular.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <gst/gst.h> +#include "gstd3d12basefilter.h" + +G_BEGIN_DECLS + +#define GST_TYPE_D3D12_DEINTERLACE (gst_d3d12_deinterlace_get_type()) +G_DECLARE_FINAL_TYPE (GstD3D12Deinterlace, gst_d3d12_deinterlace, + GST, D3D12_DEINTERLACE, GstD3D12BaseFilter) + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12yadif.cpp b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12yadif.cpp new file mode 100644 index 0000000000..c2a0fe81c4 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12yadif.cpp @@ -0,0 +1,2061 @@ +/* GStreamer + * Copyright (C) 2024 Seungha Yang <seungha@centricular.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstd3d12yadif.h" +#include "gstd3d12pluginutils.h" +#include <gst/d3dshader/gstd3dshader.h> +#include <directx/d3dx12.h> +#include <wrl.h> +#include <vector> +#include <math.h> +#include <memory> +#include <mutex> + +/* *INDENT-OFF* */ +using namespace Microsoft::WRL; +/* *INDENT-ON* */ + +#ifndef GST_DISABLE_GST_DEBUG +#define GST_CAT_DEFAULT ensure_debug_category() +static GstDebugCategory * +ensure_debug_category (void) +{ + static GstDebugCategory *cat = nullptr; + + GST_D3D12_CALL_ONCE_BEGIN { + cat = _gst_debug_category_new ("d3d12yadif", 0, "d3d12yadif"); + } GST_D3D12_CALL_ONCE_END; + + return cat; +} +#endif /* GST_DISABLE_GST_DEBUG */ + +/* *INDENT-OFF* */ +struct YadifCBData +{ + UINT width; + UINT height; + UINT primary_line = 0; + UINT is_second = 0; +}; + +struct YadifContext +{ + ComPtr<ID3D12PipelineState> pso; + YadifCBData cb_data = { }; + guint dispatch_x; + guint dispatch_y; +}; + +struct YadifConvertContext +{ + ComPtr<ID3D12PipelineState> pso; + guint dispatch_x; + guint dispatch_y; +}; + +struct GstD3D12YadifPrivate +{ + GstD3D12YadifPrivate () + { + fence_pool = gst_d3d12_fence_data_pool_new (); + output_queue = gst_vec_deque_new (2); + current_queue = gst_vec_deque_new (2); + gst_vec_deque_set_clear_func (output_queue, + (GDestroyNotify) gst_buffer_unref); + gst_vec_deque_set_clear_func (current_queue, + (GDestroyNotify) gst_buffer_unref); + } + + ~GstD3D12YadifPrivate () + { + if (device) { + gst_d3d12_device_fence_wait (device, queue_type, + fence_val); + } + + contexts.clear (); + pre_context = nullptr; + post_context = nullptr; + rs = nullptr; + cl = nullptr; + fence = nullptr; + Flush (); + gst_vec_deque_free (output_queue); + if (output_pool) + gst_buffer_pool_set_active (output_pool, FALSE); + gst_clear_object (&output_pool); + if (convert_pool) + gst_buffer_pool_set_active (convert_pool, FALSE); + gst_clear_object (&convert_pool); + gst_clear_object (&desc_pool); + gst_clear_object (&ca_pool); + gst_clear_object (&fence_pool); + gst_clear_object (&cq); + gst_clear_object (&device); + } + + void Flush () + { + gst_clear_buffer (&prev_buf); + gst_clear_buffer (&cur_buf); + gst_clear_buffer (&next_buf); + } + + std::vector<std::shared_ptr<YadifContext>> contexts; + std::shared_ptr<YadifConvertContext> pre_context; + std::shared_ptr<YadifConvertContext> post_context; + GstVecDeque *output_queue = nullptr; + GstVecDeque *current_queue = nullptr; + ComPtr<ID3D12GraphicsCommandList> cl; + ComPtr<ID3D12RootSignature> rs; + ComPtr<ID3D12RootSignature> convert_rs; + GstD3D12Device *device = nullptr; + GstD3D12CmdQueue *cq = nullptr; + ComPtr<ID3D12Fence> fence; + GstD3D12FenceDataPool *fence_pool = nullptr; + GstD3D12DescHeapPool *desc_pool = nullptr; + GstD3D12CmdAllocPool *ca_pool = nullptr; + GstBuffer *prev_buf = nullptr; + GstBuffer *cur_buf = nullptr; + GstBuffer *next_buf = nullptr; + GstBufferPool *output_pool = nullptr; + GstBufferPool *convert_pool = nullptr; + GstVideoInfo info; + GstVideoInfo origin_info; + guint64 fence_val = 0; + guint desc_inc_size; + gboolean is_forward = TRUE; + GstD3D12YadifFields fields = GST_D3D12_YADIF_FIELDS_ALL; + D3D12_COMMAND_LIST_TYPE queue_type = D3D12_COMMAND_LIST_TYPE_DIRECT; + std::mutex lock; +}; +/* *INDENT-ON* */ + +struct _GstD3D12Yadif +{ + GstObject parent; + + GstD3D12YadifPrivate *priv; +}; + +static void gst_d3d12_yadif_finalize (GObject * object); + +#define gst_d3d12_yadif_parent_class parent_class +G_DEFINE_TYPE (GstD3D12Yadif, gst_d3d12_yadif, GST_TYPE_OBJECT); + +static void +gst_d3d12_yadif_class_init (GstD3D12YadifClass * klass) +{ + auto object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gst_d3d12_yadif_finalize; +} + +static void +gst_d3d12_yadif_init (GstD3D12Yadif * self) +{ + self->priv = new GstD3D12YadifPrivate (); +} + +static void +gst_d3d12_yadif_finalize (GObject * object) +{ + auto self = GST_D3D12_YADIF (object); + + delete self->priv; + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gboolean +gst_d3d12_yadif_get_rs_blob (GstD3D12Device * device, ID3DBlob ** blob) +{ + static ID3DBlob *rs_blob_ = nullptr; + + GST_D3D12_CALL_ONCE_BEGIN { + std::vector < D3D12_DESCRIPTOR_RANGE > ranges; + std::vector < D3D12_ROOT_PARAMETER > params; + + for (guint i = 0; i < 3; i++) { + ranges.push_back (CD3DX12_DESCRIPTOR_RANGE + (D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, i)); + } + + ranges.push_back (CD3DX12_DESCRIPTOR_RANGE (D3D12_DESCRIPTOR_RANGE_TYPE_UAV, + 1, 0)); + + CD3DX12_ROOT_PARAMETER param; + param.InitAsDescriptorTable (ranges.size (), ranges.data ()); + params.push_back (param); + + param.InitAsConstants (4, 0); + params.push_back (param); + + D3D12_VERSIONED_ROOT_SIGNATURE_DESC desc = { }; + CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC::Init_1_0 (desc, params.size (), + params.data (), 0, nullptr, + D3D12_ROOT_SIGNATURE_FLAG_DENY_VERTEX_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_HULL_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_DOMAIN_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_AMPLIFICATION_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_PIXEL_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_MESH_SHADER_ROOT_ACCESS); + + ComPtr < ID3DBlob > rs_blob; + ComPtr < ID3DBlob > error_blob; + auto hr = D3DX12SerializeVersionedRootSignature (&desc, + D3D_ROOT_SIGNATURE_VERSION_1_0, &rs_blob, &error_blob); + if (!gst_d3d12_result (hr, device)) { + const gchar *error_msg = nullptr; + if (error_blob) + error_msg = (const gchar *) error_blob->GetBufferPointer (); + + GST_ERROR_OBJECT (device, + "Couldn't serialize rs, hr: 0x%x, error detail: %s", + (guint) hr, GST_STR_NULL (error_msg)); + } else { + rs_blob_ = rs_blob.Detach (); + } + } + GST_D3D12_CALL_ONCE_END; + + if (rs_blob_) { + *blob = rs_blob_; + rs_blob_->AddRef (); + return TRUE; + } + + return FALSE; +} + +static gboolean +gst_d3d12_yadif_get_convert_rs_blob (GstD3D12Device * device, ID3DBlob ** blob) +{ + static ID3DBlob *rs_blob_ = nullptr; + + GST_D3D12_CALL_ONCE_BEGIN { + CD3DX12_ROOT_PARAMETER param; + CD3DX12_DESCRIPTOR_RANGE range[2]; + range[0].Init (D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0, 0); + range[1].Init (D3D12_DESCRIPTOR_RANGE_TYPE_UAV, 1, 0, 0); + + param.InitAsDescriptorTable (2, range); + + D3D12_VERSIONED_ROOT_SIGNATURE_DESC desc = { }; + CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC::Init_1_0 (desc, 1, ¶m, 0, + nullptr, + D3D12_ROOT_SIGNATURE_FLAG_DENY_VERTEX_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_HULL_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_DOMAIN_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_AMPLIFICATION_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_PIXEL_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_MESH_SHADER_ROOT_ACCESS); + + ComPtr < ID3DBlob > rs_blob; + ComPtr < ID3DBlob > error_blob; + auto hr = D3DX12SerializeVersionedRootSignature (&desc, + D3D_ROOT_SIGNATURE_VERSION_1_0, &rs_blob, &error_blob); + if (!gst_d3d12_result (hr, device)) { + const gchar *error_msg = nullptr; + if (error_blob) + error_msg = (const gchar *) error_blob->GetBufferPointer (); + + GST_ERROR_OBJECT (device, + "Couldn't serialize rs, hr: 0x%x, error detail: %s", + (guint) hr, GST_STR_NULL (error_msg)); + } else { + rs_blob_ = rs_blob.Detach (); + } + } + GST_D3D12_CALL_ONCE_END; + + if (rs_blob_) { + *blob = rs_blob_; + rs_blob_->AddRef (); + return TRUE; + } + + return FALSE; +} + +static gboolean +gst_d3d12_yadif_prepare_convert (GstD3D12Yadif * self) +{ + auto priv = self->priv; + + GstVideoFormat conv_format = GST_VIDEO_FORMAT_UNKNOWN; + auto format = GST_VIDEO_INFO_FORMAT (&priv->origin_info); + switch (format) { + case GST_VIDEO_FORMAT_YUY2: + case GST_VIDEO_FORMAT_UYVY: + case GST_VIDEO_FORMAT_VYUY: + case GST_VIDEO_FORMAT_YVYU: + case GST_VIDEO_FORMAT_v308: + case GST_VIDEO_FORMAT_IYU2: + conv_format = GST_VIDEO_FORMAT_AYUV; + break; + case GST_VIDEO_FORMAT_Y210: + case GST_VIDEO_FORMAT_Y212_LE: + case GST_VIDEO_FORMAT_Y216_LE: + case GST_VIDEO_FORMAT_v210: + case GST_VIDEO_FORMAT_v216: + conv_format = GST_VIDEO_FORMAT_AYUV64; + break; + case GST_VIDEO_FORMAT_RGB: + case GST_VIDEO_FORMAT_BGR: + conv_format = GST_VIDEO_FORMAT_RGBA; + break; + case GST_VIDEO_FORMAT_r210: + conv_format = GST_VIDEO_FORMAT_RGB10A2_LE; + break; + default: + return TRUE; + } + + GstD3DConverterCSByteCode pre_byte_code = { }; + GstD3DConverterCSByteCode post_byte_code = { }; + if (!gst_d3d_converter_shader_get_cs_blob (format, conv_format, + GST_D3D_SM_5_0, &pre_byte_code) || + !gst_d3d_converter_shader_get_cs_blob (conv_format, format, + GST_D3D_SM_5_0, &post_byte_code)) { + GST_ERROR_OBJECT (self, "Couldn't get convert shader blob"); + return FALSE; + } + + gst_video_info_set_interlaced_format (&priv->info, conv_format, + priv->origin_info.interlace_mode, + priv->origin_info.width, priv->origin_info.height); + GST_VIDEO_INFO_FIELD_ORDER (&priv->info) = + GST_VIDEO_INFO_FIELD_ORDER (&priv->origin_info); + + ComPtr < ID3DBlob > rs_blob; + if (!gst_d3d12_yadif_get_convert_rs_blob (priv->device, &rs_blob)) { + GST_ERROR_OBJECT (self, "Couldn't get rs blob"); + return FALSE; + } + + auto device = gst_d3d12_device_get_device_handle (priv->device); + auto hr = device->CreateRootSignature (0, rs_blob->GetBufferPointer (), + rs_blob->GetBufferSize (), IID_PPV_ARGS (&priv->convert_rs)); + if (!gst_d3d12_result (hr, priv->device)) { + GST_ERROR_OBJECT (self, "Couldn't create rs"); + return FALSE; + } + + D3D12_COMPUTE_PIPELINE_STATE_DESC pso_desc = { }; + pso_desc.pRootSignature = priv->convert_rs.Get (); + auto pre_context = std::make_shared < YadifConvertContext > (); + pso_desc.CS.pShaderBytecode = pre_byte_code.byte_code.byte_code; + pso_desc.CS.BytecodeLength = pre_byte_code.byte_code.byte_code_len; + hr = device->CreateComputePipelineState (&pso_desc, + IID_PPV_ARGS (&pre_context->pso)); + if (!gst_d3d12_result (hr, priv->device)) { + GST_ERROR_OBJECT (self, "Couldn't create pso"); + return FALSE; + } + + pre_context->dispatch_x = (guint) ceil (priv->info.width / + (float) pre_byte_code.x_unit); + pre_context->dispatch_y = (guint) ceil (priv->info.height / + (float) pre_byte_code.y_unit); + + auto post_context = std::make_shared < YadifConvertContext > (); + pso_desc.CS.pShaderBytecode = post_byte_code.byte_code.byte_code; + pso_desc.CS.BytecodeLength = post_byte_code.byte_code.byte_code_len; + hr = device->CreateComputePipelineState (&pso_desc, + IID_PPV_ARGS (&post_context->pso)); + if (!gst_d3d12_result (hr, priv->device)) { + GST_ERROR_OBJECT (self, "Couldn't create pso"); + return FALSE; + } + + post_context->dispatch_x = (guint) ceil (priv->info.width / + (float) post_byte_code.x_unit); + post_context->dispatch_y = (guint) ceil (priv->info.height / + (float) post_byte_code.y_unit); + + priv->pre_context = pre_context; + priv->post_context = post_context; + + priv->convert_pool = gst_d3d12_buffer_pool_new (priv->device); + auto config = gst_buffer_pool_get_config (priv->convert_pool); + gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META); + auto caps = gst_video_info_to_caps (&priv->origin_info); + gst_buffer_pool_config_set_params (config, + caps, priv->origin_info.size, 0, 0); + gst_caps_unref (caps); + + GstD3D12Format d3d12_format; + gst_d3d12_device_get_format (priv->device, format, &d3d12_format); + + D3D12_RESOURCE_FLAGS resource_flags = + D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS | + D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; + if ((d3d12_format.support1 & D3D12_FORMAT_SUPPORT1_RENDER_TARGET) == + D3D12_FORMAT_SUPPORT1_RENDER_TARGET) { + resource_flags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; + } + + auto params = gst_d3d12_allocation_params_new (priv->device, + &priv->origin_info, GST_D3D12_ALLOCATION_FLAG_DEFAULT, resource_flags, + D3D12_HEAP_FLAG_SHARED); + gst_buffer_pool_config_set_d3d12_allocation_params (config, params); + gst_d3d12_allocation_params_free (params); + + if (!gst_buffer_pool_set_config (priv->convert_pool, config)) { + GST_ERROR_OBJECT (self, "Couldn't set pool config"); + return FALSE; + } + + if (!gst_buffer_pool_set_active (priv->convert_pool, TRUE)) { + GST_ERROR_OBJECT (self, "Pool active failed"); + return FALSE; + } + + return TRUE; +} + +static gboolean +gst_d3d12_yadif_prepare_context (GstD3D12Yadif * self, + const GstVideoInfo * info) +{ + auto priv = self->priv; + + ComPtr < ID3DBlob > rs_blob; + if (!gst_d3d12_yadif_get_rs_blob (priv->device, &rs_blob)) { + GST_ERROR_OBJECT (self, "Couldn't get rs blob"); + return FALSE; + } + + auto device = gst_d3d12_device_get_device_handle (priv->device); + auto hr = device->CreateRootSignature (0, rs_blob->GetBufferPointer (), + rs_blob->GetBufferSize (), IID_PPV_ARGS (&priv->rs)); + if (!gst_d3d12_result (hr, priv->device)) { + GST_ERROR_OBJECT (self, "Couldn't create rs"); + return FALSE; + } + + auto format = GST_VIDEO_INFO_FORMAT (info); + switch (format) { + case GST_VIDEO_FORMAT_NV12: + case GST_VIDEO_FORMAT_NV21: + case GST_VIDEO_FORMAT_P010_10LE: + case GST_VIDEO_FORMAT_P012_LE: + case GST_VIDEO_FORMAT_P016_LE: + case GST_VIDEO_FORMAT_AV12: + case GST_VIDEO_FORMAT_NV16: + case GST_VIDEO_FORMAT_NV61: + case GST_VIDEO_FORMAT_NV24: + { + D3D12_COMPUTE_PIPELINE_STATE_DESC pso_desc = { }; + pso_desc.pRootSignature = priv->rs.Get (); + + GstD3DShaderByteCode bytecode_luma = { }; + GstD3DShaderByteCode bytecode_chroma = { }; + if (!gst_d3d_plugin_shader_get_cs_blob (GST_D3D_PLUGIN_CS_YADIF_1, + GST_D3D_SM_5_0, &bytecode_luma) || + !gst_d3d_plugin_shader_get_cs_blob (GST_D3D_PLUGIN_CS_YADIF_2, + GST_D3D_SM_5_0, &bytecode_chroma)) { + GST_ERROR_OBJECT (self, "Couldn't get cs blob"); + return FALSE; + } + + pso_desc.CS.pShaderBytecode = bytecode_luma.byte_code; + pso_desc.CS.BytecodeLength = bytecode_luma.byte_code_len; + + auto context = std::make_shared < YadifContext > (); + + hr = device->CreateComputePipelineState (&pso_desc, + IID_PPV_ARGS (&context->pso)); + if (!gst_d3d12_result (hr, priv->device)) { + GST_ERROR_OBJECT (self, "Couldn't create pso"); + return FALSE; + } + + guint width = GST_ROUND_UP_2 (info->width); + guint height = GST_ROUND_UP_2 (info->height); + + context->cb_data.width = width; + context->cb_data.height = height; + context->cb_data.primary_line = 0; + context->cb_data.is_second = 0; + context->dispatch_x = (guint) ceil (width / 8.0); + context->dispatch_y = (guint) ceil (height / 8.0); + + priv->contexts.push_back (context); + + context = std::make_shared < YadifContext > (); + + pso_desc.CS.pShaderBytecode = bytecode_chroma.byte_code; + pso_desc.CS.BytecodeLength = bytecode_chroma.byte_code_len; + hr = device->CreateComputePipelineState (&pso_desc, + IID_PPV_ARGS (&context->pso)); + if (!gst_d3d12_result (hr, priv->device)) { + GST_ERROR_OBJECT (self, "Couldn't create pso"); + return FALSE; + } + + switch (format) { + case GST_VIDEO_FORMAT_NV16: + case GST_VIDEO_FORMAT_NV61: + context->cb_data.width = width / 2; + context->cb_data.height = height; + context->cb_data.primary_line = 0; + context->cb_data.is_second = 0; + context->dispatch_x = (guint) ceil (width / 16.0); + context->dispatch_y = (guint) ceil (height / 8.0); + break; + case GST_VIDEO_FORMAT_NV24: + context->cb_data.width = width; + context->cb_data.height = height; + context->cb_data.primary_line = 0; + context->cb_data.is_second = 0; + context->dispatch_x = (guint) ceil (width / 8.0); + context->dispatch_y = (guint) ceil (height / 8.0); + break; + default: + context->cb_data.width = width / 2; + context->cb_data.height = height / 2; + context->cb_data.primary_line = 0; + context->cb_data.is_second = 0; + context->dispatch_x = (guint) ceil (width / 16.0); + context->dispatch_y = (guint) ceil (height / 16.0); + break; + } + + priv->contexts.push_back (context); + + if (format == GST_VIDEO_FORMAT_AV12) { + context = std::make_shared < YadifContext > (); + context->pso = priv->contexts[0]->pso; + context->cb_data.width = width; + context->cb_data.height = height; + context->cb_data.primary_line = 0; + context->cb_data.is_second = 0; + context->dispatch_x = (guint) ceil (width / 8.0); + context->dispatch_y = (guint) ceil (height / 8.0); + + priv->contexts.push_back (context); + } + break; + } + case GST_VIDEO_FORMAT_I420: + case GST_VIDEO_FORMAT_YV12: + case GST_VIDEO_FORMAT_I420_10LE: + case GST_VIDEO_FORMAT_I420_12LE: + { + D3D12_COMPUTE_PIPELINE_STATE_DESC pso_desc = { }; + pso_desc.pRootSignature = priv->rs.Get (); + + GstD3DShaderByteCode bytecode = { }; + GstD3DPluginCS cs = GST_D3D_PLUGIN_CS_YADIF_1; + switch (format) { + case GST_VIDEO_FORMAT_I420_10LE: + cs = GST_D3D_PLUGIN_CS_YADIF_1_10; + break; + case GST_VIDEO_FORMAT_I420_12LE: + cs = GST_D3D_PLUGIN_CS_YADIF_1_12; + break; + default: + break; + } + + if (!gst_d3d_plugin_shader_get_cs_blob (cs, GST_D3D_SM_5_0, &bytecode)) { + GST_ERROR_OBJECT (self, "Couldn't get cs blob"); + return FALSE; + } + + pso_desc.CS.pShaderBytecode = bytecode.byte_code; + pso_desc.CS.BytecodeLength = bytecode.byte_code_len; + + auto context = std::make_shared < YadifContext > (); + + hr = device->CreateComputePipelineState (&pso_desc, + IID_PPV_ARGS (&context->pso)); + if (!gst_d3d12_result (hr, priv->device)) { + GST_ERROR_OBJECT (self, "Couldn't create pso"); + return FALSE; + } + + guint width = GST_ROUND_UP_2 (info->width); + guint height = GST_ROUND_UP_2 (info->height); + + context->cb_data.width = width; + context->cb_data.height = height; + context->cb_data.primary_line = 0; + context->cb_data.is_second = 0; + context->dispatch_x = (guint) ceil (width / 8.0); + context->dispatch_y = (guint) ceil (height / 8.0); + + priv->contexts.push_back (context); + + for (guint i = 0; i < 2; i++) { + context = std::make_shared < YadifContext > (); + context->cb_data.width = width / 2; + context->cb_data.height = height / 2; + context->cb_data.primary_line = 0; + context->cb_data.is_second = 0; + context->dispatch_x = (guint) ceil (width / 16.0); + context->dispatch_y = (guint) ceil (height / 16.0); + + priv->contexts.push_back (context); + } + break; + } + case GST_VIDEO_FORMAT_Y41B: + { + D3D12_COMPUTE_PIPELINE_STATE_DESC pso_desc = { }; + pso_desc.pRootSignature = priv->rs.Get (); + + GstD3DShaderByteCode bytecode = { }; + GstD3DPluginCS cs = GST_D3D_PLUGIN_CS_YADIF_1; + + if (!gst_d3d_plugin_shader_get_cs_blob (cs, GST_D3D_SM_5_0, &bytecode)) { + GST_ERROR_OBJECT (self, "Couldn't get cs blob"); + return FALSE; + } + + pso_desc.CS.pShaderBytecode = bytecode.byte_code; + pso_desc.CS.BytecodeLength = bytecode.byte_code_len; + + auto context = std::make_shared < YadifContext > (); + + hr = device->CreateComputePipelineState (&pso_desc, + IID_PPV_ARGS (&context->pso)); + if (!gst_d3d12_result (hr, priv->device)) { + GST_ERROR_OBJECT (self, "Couldn't create pso"); + return FALSE; + } + + guint width = GST_ROUND_UP_4 (info->width); + guint height = GST_ROUND_UP_4 (info->height); + + context->cb_data.width = width; + context->cb_data.height = height; + context->cb_data.primary_line = 0; + context->cb_data.is_second = 0; + context->dispatch_x = (guint) ceil (width / 8.0); + context->dispatch_y = (guint) ceil (height / 8.0); + + priv->contexts.push_back (context); + + for (guint i = 0; i < 2; i++) { + context = std::make_shared < YadifContext > (); + context->cb_data.width = width / 4; + context->cb_data.height = height; + context->cb_data.primary_line = 0; + context->cb_data.is_second = 0; + context->dispatch_x = (guint) ceil (width / 32.0); + context->dispatch_y = (guint) ceil (height / 8.0); + + priv->contexts.push_back (context); + } + break; + } + case GST_VIDEO_FORMAT_Y42B: + case GST_VIDEO_FORMAT_I422_10LE: + case GST_VIDEO_FORMAT_I422_12LE: + { + D3D12_COMPUTE_PIPELINE_STATE_DESC pso_desc = { }; + pso_desc.pRootSignature = priv->rs.Get (); + + GstD3DShaderByteCode bytecode = { }; + GstD3DPluginCS cs = GST_D3D_PLUGIN_CS_YADIF_1; + switch (format) { + case GST_VIDEO_FORMAT_I422_10LE: + cs = GST_D3D_PLUGIN_CS_YADIF_1_10; + break; + case GST_VIDEO_FORMAT_I422_12LE: + cs = GST_D3D_PLUGIN_CS_YADIF_1_12; + break; + default: + break; + } + + if (!gst_d3d_plugin_shader_get_cs_blob (cs, GST_D3D_SM_5_0, &bytecode)) { + GST_ERROR_OBJECT (self, "Couldn't get cs blob"); + return FALSE; + } + + pso_desc.CS.pShaderBytecode = bytecode.byte_code; + pso_desc.CS.BytecodeLength = bytecode.byte_code_len; + + auto context = std::make_shared < YadifContext > (); + + hr = device->CreateComputePipelineState (&pso_desc, + IID_PPV_ARGS (&context->pso)); + if (!gst_d3d12_result (hr, priv->device)) { + GST_ERROR_OBJECT (self, "Couldn't create pso"); + return FALSE; + } + + guint width = GST_ROUND_UP_2 (info->width); + guint height = GST_ROUND_UP_2 (info->height); + + context->cb_data.width = width; + context->cb_data.height = height; + context->cb_data.primary_line = 0; + context->cb_data.is_second = 0; + context->dispatch_x = (guint) ceil (width / 8.0); + context->dispatch_y = (guint) ceil (height / 8.0); + + priv->contexts.push_back (context); + + for (guint i = 0; i < 2; i++) { + context = std::make_shared < YadifContext > (); + context->cb_data.width = width / 2; + context->cb_data.height = height; + context->cb_data.primary_line = 0; + context->cb_data.is_second = 0; + context->dispatch_x = (guint) ceil (width / 16.0); + context->dispatch_y = (guint) ceil (height / 8.0); + + priv->contexts.push_back (context); + } + break; + } + case GST_VIDEO_FORMAT_YUV9: + case GST_VIDEO_FORMAT_YVU9: + { + D3D12_COMPUTE_PIPELINE_STATE_DESC pso_desc = { }; + pso_desc.pRootSignature = priv->rs.Get (); + + GstD3DShaderByteCode bytecode = { }; + GstD3DPluginCS cs = GST_D3D_PLUGIN_CS_YADIF_1; + if (!gst_d3d_plugin_shader_get_cs_blob (cs, GST_D3D_SM_5_0, &bytecode)) { + GST_ERROR_OBJECT (self, "Couldn't get cs blob"); + return FALSE; + } + + pso_desc.CS.pShaderBytecode = bytecode.byte_code; + pso_desc.CS.BytecodeLength = bytecode.byte_code_len; + + auto context = std::make_shared < YadifContext > (); + + hr = device->CreateComputePipelineState (&pso_desc, + IID_PPV_ARGS (&context->pso)); + if (!gst_d3d12_result (hr, priv->device)) { + GST_ERROR_OBJECT (self, "Couldn't create pso"); + return FALSE; + } + + guint width = GST_ROUND_UP_4 (info->width); + guint height = GST_ROUND_UP_4 (info->height); + + context->cb_data.width = width; + context->cb_data.height = height; + context->cb_data.primary_line = 0; + context->cb_data.is_second = 0; + context->dispatch_x = (guint) ceil (width / 8.0); + context->dispatch_y = (guint) ceil (height / 8.0); + + priv->contexts.push_back (context); + + for (guint i = 0; i < 2; i++) { + context = std::make_shared < YadifContext > (); + context->cb_data.width = width / 4; + context->cb_data.height = height / 4; + context->cb_data.primary_line = 0; + context->cb_data.is_second = 0; + context->dispatch_x = (guint) ceil (width / 32.0); + context->dispatch_y = (guint) ceil (height / 32.0); + + priv->contexts.push_back (context); + } + break; + } + case GST_VIDEO_FORMAT_Y444: + case GST_VIDEO_FORMAT_Y444_10LE: + case GST_VIDEO_FORMAT_Y444_12LE: + case GST_VIDEO_FORMAT_Y444_16LE: + case GST_VIDEO_FORMAT_GBR: + case GST_VIDEO_FORMAT_GBR_10LE: + case GST_VIDEO_FORMAT_GBR_12LE: + case GST_VIDEO_FORMAT_GBR_16LE: + case GST_VIDEO_FORMAT_BGRP: + case GST_VIDEO_FORMAT_RGBP: + { + D3D12_COMPUTE_PIPELINE_STATE_DESC pso_desc = { }; + pso_desc.pRootSignature = priv->rs.Get (); + + GstD3DShaderByteCode bytecode = { }; + GstD3DPluginCS cs = GST_D3D_PLUGIN_CS_YADIF_1; + switch (format) { + case GST_VIDEO_FORMAT_Y444_10LE: + case GST_VIDEO_FORMAT_GBR_10LE: + cs = GST_D3D_PLUGIN_CS_YADIF_1_10; + break; + case GST_VIDEO_FORMAT_Y444_12LE: + case GST_VIDEO_FORMAT_GBR_12LE: + cs = GST_D3D_PLUGIN_CS_YADIF_1_12; + break; + default: + break; + } + + if (!gst_d3d_plugin_shader_get_cs_blob (cs, GST_D3D_SM_5_0, &bytecode)) { + GST_ERROR_OBJECT (self, "Couldn't get cs blob"); + return FALSE; + } + + pso_desc.CS.pShaderBytecode = bytecode.byte_code; + pso_desc.CS.BytecodeLength = bytecode.byte_code_len; + + auto context = std::make_shared < YadifContext > (); + hr = device->CreateComputePipelineState (&pso_desc, + IID_PPV_ARGS (&context->pso)); + if (!gst_d3d12_result (hr, priv->device)) { + GST_ERROR_OBJECT (self, "Couldn't create pso"); + return FALSE; + } + + guint width = GST_ROUND_UP_2 (info->width); + guint height = GST_ROUND_UP_2 (info->height); + + context->cb_data.width = width; + context->cb_data.height = height; + context->cb_data.primary_line = 0; + context->cb_data.is_second = 0; + context->dispatch_x = (guint) ceil (width / 8.0); + context->dispatch_y = (guint) ceil (height / 8.0); + + priv->contexts.push_back (context); + + for (guint i = 0; i < 2; i++) { + context = std::make_shared < YadifContext > (); + context->cb_data.width = width; + context->cb_data.height = height; + context->cb_data.primary_line = 0; + context->cb_data.is_second = 0; + context->dispatch_x = (guint) ceil (width / 8.0); + context->dispatch_y = (guint) ceil (height / 8.0); + + priv->contexts.push_back (context); + } + break; + } + case GST_VIDEO_FORMAT_RGBA64_LE: + case GST_VIDEO_FORMAT_BGRA64_LE: + case GST_VIDEO_FORMAT_Y412_LE: + case GST_VIDEO_FORMAT_Y416_LE: + case GST_VIDEO_FORMAT_RGB10A2_LE: + case GST_VIDEO_FORMAT_Y410: + case GST_VIDEO_FORMAT_BGR10A2_LE: + case GST_VIDEO_FORMAT_VUYA: + case GST_VIDEO_FORMAT_RGBA: + case GST_VIDEO_FORMAT_BGRA: + case GST_VIDEO_FORMAT_RGBx: + case GST_VIDEO_FORMAT_BGRx: + case GST_VIDEO_FORMAT_ARGB64_LE: + case GST_VIDEO_FORMAT_AYUV64: + case GST_VIDEO_FORMAT_AYUV: + case GST_VIDEO_FORMAT_ABGR: + case GST_VIDEO_FORMAT_ARGB: + case GST_VIDEO_FORMAT_xBGR: + case GST_VIDEO_FORMAT_xRGB: + case GST_VIDEO_FORMAT_GRAY16_LE: + case GST_VIDEO_FORMAT_GRAY8: + { + D3D12_COMPUTE_PIPELINE_STATE_DESC pso_desc = { }; + pso_desc.pRootSignature = priv->rs.Get (); + + GstD3DShaderByteCode bytecode = { }; + GstD3DPluginCS cs = GST_D3D_PLUGIN_CS_YADIF_4; + switch (format) { + case GST_VIDEO_FORMAT_GRAY16_LE: + case GST_VIDEO_FORMAT_GRAY8: + cs = GST_D3D_PLUGIN_CS_YADIF_1; + break; + default: + break; + } + + if (!gst_d3d_plugin_shader_get_cs_blob (cs, GST_D3D_SM_5_0, &bytecode)) { + GST_ERROR_OBJECT (self, "Couldn't get cs blob"); + return FALSE; + } + + pso_desc.CS.pShaderBytecode = bytecode.byte_code; + pso_desc.CS.BytecodeLength = bytecode.byte_code_len; + + auto context = std::make_shared < YadifContext > (); + hr = device->CreateComputePipelineState (&pso_desc, + IID_PPV_ARGS (&context->pso)); + if (!gst_d3d12_result (hr, priv->device)) { + GST_ERROR_OBJECT (self, "Couldn't create pso"); + return FALSE; + } + + guint width = GST_ROUND_UP_2 (info->width); + guint height = GST_ROUND_UP_2 (info->height); + + context->cb_data.width = width; + context->cb_data.height = height; + context->cb_data.primary_line = 0; + context->cb_data.is_second = 0; + context->dispatch_x = (guint) ceil (width / 8.0); + context->dispatch_y = (guint) ceil (height / 8.0); + + priv->contexts.push_back (context); + break; + } + case GST_VIDEO_FORMAT_A420: + case GST_VIDEO_FORMAT_A420_10LE: + case GST_VIDEO_FORMAT_A420_12LE: + case GST_VIDEO_FORMAT_A420_16LE: + { + D3D12_COMPUTE_PIPELINE_STATE_DESC pso_desc = { }; + pso_desc.pRootSignature = priv->rs.Get (); + + GstD3DShaderByteCode bytecode = { }; + GstD3DPluginCS cs = GST_D3D_PLUGIN_CS_YADIF_1; + switch (format) { + case GST_VIDEO_FORMAT_A420_10LE: + cs = GST_D3D_PLUGIN_CS_YADIF_1_10; + break; + case GST_VIDEO_FORMAT_A420_12LE: + cs = GST_D3D_PLUGIN_CS_YADIF_1_12; + default: + break; + } + + if (!gst_d3d_plugin_shader_get_cs_blob (cs, GST_D3D_SM_5_0, &bytecode)) { + GST_ERROR_OBJECT (self, "Couldn't get cs blob"); + return FALSE; + } + + pso_desc.CS.pShaderBytecode = bytecode.byte_code; + pso_desc.CS.BytecodeLength = bytecode.byte_code_len; + + auto context = std::make_shared < YadifContext > (); + + hr = device->CreateComputePipelineState (&pso_desc, + IID_PPV_ARGS (&context->pso)); + if (!gst_d3d12_result (hr, priv->device)) { + GST_ERROR_OBJECT (self, "Couldn't create pso"); + return FALSE; + } + + guint width = GST_ROUND_UP_2 (info->width); + guint height = GST_ROUND_UP_2 (info->height); + + context->cb_data.width = width; + context->cb_data.height = height; + context->cb_data.primary_line = 0; + context->cb_data.is_second = 0; + context->dispatch_x = (guint) ceil (width / 8.0); + context->dispatch_y = (guint) ceil (height / 8.0); + + priv->contexts.push_back (context); + + for (guint i = 0; i < 2; i++) { + context = std::make_shared < YadifContext > (); + context->cb_data.width = width / 2; + context->cb_data.height = height / 2; + context->cb_data.primary_line = 0; + context->cb_data.is_second = 0; + context->dispatch_x = (guint) ceil (width / 16.0); + context->dispatch_y = (guint) ceil (height / 16.0); + + priv->contexts.push_back (context); + } + + context = std::make_shared < YadifContext > (); + context->cb_data.width = width; + context->cb_data.height = height; + context->cb_data.primary_line = 0; + context->cb_data.is_second = 0; + context->dispatch_x = (guint) ceil (width / 8.0); + context->dispatch_y = (guint) ceil (height / 8.0); + + priv->contexts.push_back (context); + break; + } + case GST_VIDEO_FORMAT_A422: + case GST_VIDEO_FORMAT_A422_10LE: + case GST_VIDEO_FORMAT_A422_12LE: + case GST_VIDEO_FORMAT_A422_16LE: + { + D3D12_COMPUTE_PIPELINE_STATE_DESC pso_desc = { }; + pso_desc.pRootSignature = priv->rs.Get (); + + GstD3DShaderByteCode bytecode = { }; + GstD3DPluginCS cs = GST_D3D_PLUGIN_CS_YADIF_1; + switch (format) { + case GST_VIDEO_FORMAT_A422_10LE: + cs = GST_D3D_PLUGIN_CS_YADIF_1_10; + break; + case GST_VIDEO_FORMAT_A422_12LE: + cs = GST_D3D_PLUGIN_CS_YADIF_1_12; + default: + break; + } + + if (!gst_d3d_plugin_shader_get_cs_blob (cs, GST_D3D_SM_5_0, &bytecode)) { + GST_ERROR_OBJECT (self, "Couldn't get cs blob"); + return FALSE; + } + + pso_desc.CS.pShaderBytecode = bytecode.byte_code; + pso_desc.CS.BytecodeLength = bytecode.byte_code_len; + + auto context = std::make_shared < YadifContext > (); + + hr = device->CreateComputePipelineState (&pso_desc, + IID_PPV_ARGS (&context->pso)); + if (!gst_d3d12_result (hr, priv->device)) { + GST_ERROR_OBJECT (self, "Couldn't create pso"); + return FALSE; + } + + guint width = GST_ROUND_UP_2 (info->width); + guint height = GST_ROUND_UP_2 (info->height); + + context->cb_data.width = width; + context->cb_data.height = height; + context->cb_data.primary_line = 0; + context->cb_data.is_second = 0; + context->dispatch_x = (guint) ceil (width / 8.0); + context->dispatch_y = (guint) ceil (height / 8.0); + + priv->contexts.push_back (context); + + for (guint i = 0; i < 2; i++) { + context = std::make_shared < YadifContext > (); + context->cb_data.width = width / 2; + context->cb_data.height = height; + context->cb_data.primary_line = 0; + context->cb_data.is_second = 0; + context->dispatch_x = (guint) ceil (width / 16.0); + context->dispatch_y = (guint) ceil (height / 8.0); + + priv->contexts.push_back (context); + } + + context = std::make_shared < YadifContext > (); + context->cb_data.width = width; + context->cb_data.height = height; + context->cb_data.primary_line = 0; + context->cb_data.is_second = 0; + context->dispatch_x = (guint) ceil (width / 8.0); + context->dispatch_y = (guint) ceil (height / 8.0); + + priv->contexts.push_back (context); + break; + } + case GST_VIDEO_FORMAT_GBRA: + case GST_VIDEO_FORMAT_GBRA_10LE: + case GST_VIDEO_FORMAT_GBRA_12LE: + case GST_VIDEO_FORMAT_A444: + case GST_VIDEO_FORMAT_A444_10LE: + case GST_VIDEO_FORMAT_A444_12LE: + case GST_VIDEO_FORMAT_A444_16LE: + { + D3D12_COMPUTE_PIPELINE_STATE_DESC pso_desc = { }; + pso_desc.pRootSignature = priv->rs.Get (); + + GstD3DShaderByteCode bytecode = { }; + GstD3DPluginCS cs = GST_D3D_PLUGIN_CS_YADIF_1; + switch (format) { + case GST_VIDEO_FORMAT_GBRA_10LE: + case GST_VIDEO_FORMAT_A444_10LE: + cs = GST_D3D_PLUGIN_CS_YADIF_1_10; + break; + case GST_VIDEO_FORMAT_GBRA_12LE: + case GST_VIDEO_FORMAT_A444_12LE: + cs = GST_D3D_PLUGIN_CS_YADIF_1_12; + break; + default: + break; + } + + if (!gst_d3d_plugin_shader_get_cs_blob (cs, GST_D3D_SM_5_0, &bytecode)) { + GST_ERROR_OBJECT (self, "Couldn't get cs blob"); + return FALSE; + } + + pso_desc.CS.pShaderBytecode = bytecode.byte_code; + pso_desc.CS.BytecodeLength = bytecode.byte_code_len; + + auto context = std::make_shared < YadifContext > (); + + hr = device->CreateComputePipelineState (&pso_desc, + IID_PPV_ARGS (&context->pso)); + if (!gst_d3d12_result (hr, priv->device)) { + GST_ERROR_OBJECT (self, "Couldn't create pso"); + return FALSE; + } + + guint width = GST_ROUND_UP_2 (info->width); + guint height = GST_ROUND_UP_2 (info->height); + + context->cb_data.width = width; + context->cb_data.height = height; + context->cb_data.primary_line = 0; + context->cb_data.is_second = 0; + context->dispatch_x = (guint) ceil (width / 8.0); + context->dispatch_y = (guint) ceil (height / 8.0); + + priv->contexts.push_back (context); + + for (guint i = 0; i < 3; i++) { + context = std::make_shared < YadifContext > (); + context->cb_data.width = width; + context->cb_data.height = height; + context->cb_data.primary_line = 0; + context->cb_data.is_second = 0; + context->dispatch_x = (guint) ceil (width / 8.0); + context->dispatch_y = (guint) ceil (height / 8.0); + + priv->contexts.push_back (context); + } + break; + } + default: + GST_ERROR_OBJECT (self, "Not supported format %s", + gst_video_format_to_string (GST_VIDEO_INFO_FORMAT (info))); + return FALSE; + } + + D3D12_DESCRIPTOR_HEAP_DESC heap_desc = { }; + heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; + /* 4 descriptors per Dispatch (3 SRV and 1 UAV) + * 2x for top and bottom fields */ + heap_desc.NumDescriptors = 4 * 2 * GST_VIDEO_INFO_N_PLANES (info); + heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; + priv->desc_pool = gst_d3d12_desc_heap_pool_new (device, &heap_desc); + + priv->desc_inc_size = device->GetDescriptorHandleIncrementSize + (D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + + priv->output_pool = gst_d3d12_buffer_pool_new (priv->device); + auto config = gst_buffer_pool_get_config (priv->output_pool); + gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META); + auto caps = gst_video_info_to_caps (info); + gst_buffer_pool_config_set_params (config, caps, info->size, 0, 0); + gst_caps_unref (caps); + + GstD3D12Format d3d12_format; + gst_d3d12_device_get_format (priv->device, GST_VIDEO_INFO_FORMAT (info), + &d3d12_format); + + D3D12_RESOURCE_FLAGS resource_flags = + D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS | + D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; + if ((d3d12_format.support1 & D3D12_FORMAT_SUPPORT1_RENDER_TARGET) == + D3D12_FORMAT_SUPPORT1_RENDER_TARGET) { + resource_flags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; + } + + auto params = gst_d3d12_allocation_params_new (priv->device, info, + GST_D3D12_ALLOCATION_FLAG_DEFAULT, resource_flags, + D3D12_HEAP_FLAG_SHARED); + gst_buffer_pool_config_set_d3d12_allocation_params (config, params); + gst_d3d12_allocation_params_free (params); + + if (!gst_buffer_pool_set_config (priv->output_pool, config)) { + GST_ERROR_OBJECT (self, "Couldn't set pool config"); + return FALSE; + } + + if (!gst_buffer_pool_set_active (priv->output_pool, TRUE)) { + GST_ERROR_OBJECT (self, "Pool active failed"); + return FALSE; + } + + return TRUE; +} + +GstD3D12Yadif * +gst_d3d12_yadif_new (GstD3D12Device * device, const GstVideoInfo * info, + gboolean use_compute) +{ + g_return_val_if_fail (GST_IS_D3D12_DEVICE (device), nullptr); + g_return_val_if_fail (info, nullptr); + + auto self = (GstD3D12Yadif *) g_object_new (GST_TYPE_D3D12_YADIF, nullptr); + gst_object_ref_sink (self); + + auto priv = self->priv; + priv->info = *info; + priv->origin_info = *info; + priv->device = (GstD3D12Device *) gst_object_ref (device); + priv->queue_type = use_compute ? + D3D12_COMMAND_LIST_TYPE_COMPUTE : D3D12_COMMAND_LIST_TYPE_DIRECT; + + switch (info->interlace_mode) { + case GST_VIDEO_INTERLACE_MODE_PROGRESSIVE: + case GST_VIDEO_INTERLACE_MODE_INTERLEAVED: + case GST_VIDEO_INTERLACE_MODE_MIXED: + break; + default: + GST_ERROR_OBJECT (self, "Interlaced mode not supported"); + gst_object_unref (self); + return nullptr; + } + + if (!gst_d3d12_yadif_prepare_convert (self)) { + gst_object_unref (self); + return nullptr; + } + + if (!gst_d3d12_yadif_prepare_context (self, &priv->info)) { + gst_object_unref (self); + return nullptr; + } + + auto device_handle = gst_d3d12_device_get_device_handle (device); + priv->ca_pool = gst_d3d12_cmd_alloc_pool_new (device_handle, + priv->queue_type); + priv->cq = gst_d3d12_device_get_cmd_queue (priv->device, priv->queue_type); + gst_object_ref (priv->cq); + priv->fence = gst_d3d12_cmd_queue_get_fence_handle (priv->cq); + + return self; +} + +void +gst_d3d12_yadif_set_fields (GstD3D12Yadif * yadif, GstD3D12YadifFields fields) +{ + g_return_if_fail (GST_IS_D3D12_YADIF (yadif)); + + auto priv = yadif->priv; + std::lock_guard < std::mutex > lk (priv->lock); + priv->fields = fields; +} + +void +gst_d3d12_yadif_set_direction (GstD3D12Yadif * yadif, gboolean is_forward) +{ + g_return_if_fail (GST_IS_D3D12_YADIF (yadif)); + + auto priv = yadif->priv; + std::lock_guard < std::mutex > lk (priv->lock); + priv->is_forward = is_forward; +} + +struct GstD3D12YadifFrameCtx +{ + GstD3D12Frame prev; + GstD3D12Frame cur; + GstD3D12Frame next; + GstD3D12Frame out_frames[2]; + GstD3D12Frame conv_frames[2]; + UINT is_second[2]; +}; + +static void +gst_d3d12_yadif_unmap_frame_ctx (GstD3D12YadifFrameCtx * ctx) +{ + gst_d3d12_frame_unmap (&ctx->prev); + gst_d3d12_frame_unmap (&ctx->cur); + gst_d3d12_frame_unmap (&ctx->next); + for (guint i = 0; i < 2; i++) { + gst_d3d12_frame_unmap (&ctx->out_frames[i]); + gst_d3d12_frame_unmap (&ctx->conv_frames[i]); + } +} + +struct CopyMetaData +{ + GstBuffer *outbuf; + gboolean copy_cc; +}; + +static gboolean +foreach_metadata (GstBuffer * inbuf, GstMeta ** meta, gpointer user_data) +{ + auto data = (CopyMetaData *) user_data; + auto info = (*meta)->info; + bool do_copy = false; + + if (gst_meta_api_type_has_tag (info->api, _gst_meta_tag_memory) + || gst_meta_api_type_has_tag (info->api, _gst_meta_tag_memory_reference)) { + /* never call the transform_meta with memory specific metadata */ + do_copy = false; + } else if (info->api == GST_VIDEO_CAPTION_META_API_TYPE && !data->copy_cc) { + do_copy = false; + } else if (!gst_meta_api_type_get_tags (info->api)) { + do_copy = true; + } + + if (do_copy && info->transform_func) { + GstMetaTransformCopy copy_data; + copy_data.region = FALSE; + copy_data.offset = 0; + copy_data.size = (gsize) - 1; + info->transform_func (data->outbuf, *meta, inbuf, _gst_meta_transform_copy, + ©_data); + } + + return TRUE; +} + +static gboolean +gst_d3d12_yadif_map_frames (GstD3D12Yadif * self, guint tff, + GstD3D12YadifFrameCtx * ctx, GstD3D12FenceData * fence_data, + std::vector < ID3D12Fence * >&fences_to_wait, + std::vector < guint64 > &fence_values_to_wait) +{ + auto priv = self->priv; + GstClockTime first_pts = GST_CLOCK_TIME_NONE; + GstClockTime second_pts = GST_CLOCK_TIME_NONE; + GstClockTime dur = GST_CLOCK_TIME_NONE; + GstBuffer *first_buf = nullptr; + GstBuffer *second_buf = nullptr; + GstBuffer *first_conv_buf = nullptr; + GstBuffer *second_conv_buf = nullptr; + GstBuffer *first_target = nullptr; + GstBuffer *second_target = nullptr; + GstD3D12FrameMapFlags out_map_flags = GST_D3D12_FRAME_MAP_FLAG_UAV; + + if (priv->post_context) + out_map_flags |= GST_D3D12_FRAME_MAP_FLAG_SRV; + + gst_vec_deque_clear (priv->current_queue); + memset (ctx, 0, sizeof (GstD3D12YadifFrameCtx)); + + if (!gst_d3d12_frame_map (&ctx->prev, &priv->info, priv->prev_buf, + GST_MAP_READ, GST_D3D12_FRAME_MAP_FLAG_SRV)) { + GST_ERROR_OBJECT (self, "Couldn't map prev frame"); + goto error; + } + + if (!gst_d3d12_frame_map (&ctx->cur, &priv->info, priv->cur_buf, + GST_MAP_READ, GST_D3D12_FRAME_MAP_FLAG_SRV)) { + GST_ERROR_OBJECT (self, "Couldn't map cur frame"); + goto error; + } + + if (!gst_d3d12_frame_map (&ctx->next, &priv->info, priv->next_buf, + GST_MAP_READ, GST_D3D12_FRAME_MAP_FLAG_SRV)) { + GST_ERROR_OBJECT (self, "Couldn't map next frame"); + goto error; + } + + gst_buffer_pool_acquire_buffer (priv->output_pool, &first_buf, nullptr); + if (!first_buf) { + GST_ERROR_OBJECT (self, "Couldn't acquire first field buffer"); + goto error; + } + + if (priv->post_context) { + gst_buffer_pool_acquire_buffer (priv->convert_pool, + &first_conv_buf, nullptr); + if (!first_conv_buf) { + GST_ERROR_OBJECT (self, "Couldn't acquire first field output buffer"); + gst_clear_buffer (&first_buf); + goto error; + } + + gst_d3d12_fence_data_push (fence_data, + FENCE_NOTIFY_MINI_OBJECT (first_buf)); + gst_vec_deque_push_tail (priv->current_queue, first_conv_buf); + first_target = first_conv_buf; + } else { + gst_vec_deque_push_tail (priv->current_queue, first_buf); + first_target = first_buf; + } + + /* Copy buffer flags except for interlace related ones */ + gst_buffer_copy_into (first_target, priv->cur_buf, GST_BUFFER_COPY_FLAGS, 0, + -1); + GST_BUFFER_FLAG_UNSET (first_target, GST_VIDEO_BUFFER_FLAG_INTERLACED); + GST_BUFFER_FLAG_UNSET (first_target, GST_VIDEO_BUFFER_FLAG_TFF); + + if (priv->fields == GST_D3D12_YADIF_FIELDS_ALL) { + gst_buffer_pool_acquire_buffer (priv->output_pool, &second_buf, nullptr); + if (!second_buf) { + GST_ERROR_OBJECT (self, "Couldn't acquire second field buffer"); + goto error; + } + + if (priv->post_context) { + gst_buffer_pool_acquire_buffer (priv->convert_pool, + &second_conv_buf, nullptr); + if (!second_conv_buf) { + GST_ERROR_OBJECT (self, "Couldn't acquire second field output buffer"); + gst_clear_buffer (&second_buf); + goto error; + } + gst_d3d12_fence_data_push (fence_data, + FENCE_NOTIFY_MINI_OBJECT (second_buf)); + gst_vec_deque_push_tail (priv->current_queue, second_conv_buf); + second_target = second_conv_buf; + } else { + gst_vec_deque_push_tail (priv->current_queue, second_buf); + second_target = second_buf; + } + + gst_buffer_copy_into (second_target, priv->cur_buf, GST_BUFFER_COPY_FLAGS, + 0, -1); + GST_BUFFER_FLAG_UNSET (second_target, GST_VIDEO_BUFFER_FLAG_INTERLACED); + GST_BUFFER_FLAG_UNSET (second_target, GST_VIDEO_BUFFER_FLAG_TFF); + + if (GST_BUFFER_PTS_IS_VALID (priv->cur_buf)) { + first_pts = GST_BUFFER_PTS (priv->cur_buf); + if (GST_BUFFER_DURATION_IS_VALID (priv->cur_buf)) { + dur = GST_BUFFER_DURATION (priv->cur_buf) / 2; + } else if (GST_BUFFER_PTS_IS_VALID (priv->next_buf)) { + auto next_pts = GST_BUFFER_PTS (priv->next_buf); + if (priv->is_forward && first_pts <= next_pts) + dur = (next_pts - first_pts) / 2; + else if (!priv->is_forward && first_pts >= next_pts) + dur = (first_pts - next_pts) / 2; + } + + if (GST_CLOCK_TIME_IS_VALID (dur)) + second_pts = first_pts + dur; + } + + if (priv->is_forward) { + GST_BUFFER_PTS (first_target) = first_pts; + GST_BUFFER_PTS (second_target) = second_pts; + } else { + GST_BUFFER_PTS (first_target) = second_pts; + GST_BUFFER_PTS (second_target) = first_pts; + } + + GST_BUFFER_DURATION (first_target) = dur; + GST_BUFFER_DURATION (second_target) = dur; + } else { + GST_BUFFER_PTS (first_target) = GST_BUFFER_PTS (priv->cur_buf); + GST_BUFFER_DURATION (first_target) = GST_BUFFER_DURATION (priv->cur_buf); + } + + CopyMetaData copy_meta; + copy_meta.copy_cc = TRUE; + copy_meta.outbuf = first_target; + gst_buffer_foreach_meta (priv->cur_buf, foreach_metadata, ©_meta); + + if (second_target) { + copy_meta.copy_cc = FALSE; + copy_meta.outbuf = second_target; + gst_buffer_foreach_meta (priv->cur_buf, foreach_metadata, ©_meta); + } + + switch (priv->fields) { + case GST_D3D12_YADIF_FIELDS_TOP: + ctx->is_second[0] = tff ? 0 : 1; + break; + case GST_D3D12_YADIF_FIELDS_BOTTOM: + ctx->is_second[0] = tff ? 1 : 0; + break; + case GST_D3D12_YADIF_FIELDS_ALL: + if (priv->is_forward) { + ctx->is_second[0] = 0; + ctx->is_second[1] = 1; + } else { + ctx->is_second[0] = 1; + ctx->is_second[1] = 0; + } + break; + default: + g_assert_not_reached (); + break; + } + + if (!gst_d3d12_frame_map (&ctx->out_frames[0], &priv->info, first_buf, + GST_MAP_D3D12, out_map_flags)) { + GST_ERROR_OBJECT (self, "Couldn't map first field output"); + goto error; + } + + if (first_conv_buf && !gst_d3d12_frame_map (&ctx->conv_frames[0], + &priv->origin_info, first_conv_buf, + GST_MAP_D3D12, GST_D3D12_FRAME_MAP_FLAG_UAV)) { + GST_ERROR_OBJECT (self, "Couldn't map first field convert output"); + goto error; + } + + if (second_buf && + !gst_d3d12_frame_map (&ctx->out_frames[1], &priv->info, second_buf, + GST_MAP_D3D12, out_map_flags)) { + GST_ERROR_OBJECT (self, "Couldn't map second field output"); + goto error; + } + + if (second_conv_buf && !gst_d3d12_frame_map (&ctx->conv_frames[1], + &priv->origin_info, second_conv_buf, + GST_MAP_D3D12, GST_D3D12_FRAME_MAP_FLAG_UAV)) { + GST_ERROR_OBJECT (self, "Couldn't map second field convert output"); + goto error; + } + + gst_d3d12_fence_data_push (fence_data, + FENCE_NOTIFY_MINI_OBJECT (gst_buffer_ref (priv->prev_buf))); + gst_d3d12_fence_data_push (fence_data, + FENCE_NOTIFY_MINI_OBJECT (gst_buffer_ref (priv->cur_buf))); + gst_d3d12_fence_data_push (fence_data, + FENCE_NOTIFY_MINI_OBJECT (gst_buffer_ref (priv->next_buf))); + + for (guint i = 0; i < GST_VIDEO_INFO_N_PLANES (&priv->info); i++) { + if (ctx->prev.fence[i].fence && + ctx->prev.fence[i].fence != priv->fence.Get ()) { + fences_to_wait.push_back (ctx->prev.fence[i].fence); + fence_values_to_wait.push_back (ctx->prev.fence[i].fence_value); + } + + if (ctx->cur.fence[i].fence && + ctx->cur.fence[i].fence != priv->fence.Get ()) { + fences_to_wait.push_back (ctx->cur.fence[i].fence); + fence_values_to_wait.push_back (ctx->cur.fence[i].fence_value); + } + + if (ctx->next.fence[i].fence && + ctx->next.fence[i].fence != priv->fence.Get ()) { + fences_to_wait.push_back (ctx->next.fence[i].fence); + fence_values_to_wait.push_back (ctx->next.fence[i].fence_value); + } + } + + return TRUE; + +error: + gst_d3d12_yadif_unmap_frame_ctx (ctx); + gst_vec_deque_clear (priv->current_queue); + return FALSE; +} + +static GstFlowReturn +gst_d3d12_yadif_process_frame (GstD3D12Yadif * self) +{ + auto priv = self->priv; + UINT tff = 0; + + g_return_val_if_fail (priv->prev_buf, GST_FLOW_ERROR); + g_return_val_if_fail (priv->cur_buf, GST_FLOW_ERROR); + g_return_val_if_fail (priv->next_buf, GST_FLOW_ERROR); + + switch (GST_VIDEO_INFO_INTERLACE_MODE (&priv->info)) { + case GST_VIDEO_INTERLACE_MODE_PROGRESSIVE: + gst_vec_deque_push_tail (priv->output_queue, + gst_buffer_ref (priv->cur_buf)); + return GST_FLOW_OK; + case GST_VIDEO_INTERLACE_MODE_MIXED: + if (!GST_BUFFER_FLAG_IS_SET (priv->cur_buf, + GST_VIDEO_BUFFER_FLAG_INTERLACED)) { + gst_vec_deque_push_tail (priv->output_queue, + gst_buffer_ref (priv->cur_buf)); + return GST_FLOW_OK; + } + + if (GST_BUFFER_FLAG_IS_SET (priv->cur_buf, GST_VIDEO_BUFFER_FLAG_TFF)) + tff = 1; + break; + case GST_VIDEO_INTERLACE_MODE_INTERLEAVED: + if (GST_VIDEO_INFO_FIELD_ORDER (&priv->info) == + GST_VIDEO_FIELD_ORDER_TOP_FIELD_FIRST) { + tff = 1; + } else if (GST_VIDEO_INFO_FIELD_ORDER (&priv->info) == + GST_VIDEO_FIELD_ORDER_UNKNOWN && + GST_BUFFER_FLAG_IS_SET (priv->cur_buf, GST_VIDEO_BUFFER_FLAG_TFF)) { + tff = 1; + } + break; + default: + GST_ERROR_OBJECT (self, "Not supported interlace mode"); + return GST_FLOW_ERROR; + } + + auto device = gst_d3d12_device_get_device_handle (priv->device); + GstD3D12FenceData *fence_data; + gst_d3d12_fence_data_pool_acquire (priv->fence_pool, &fence_data); + + GstD3D12YadifFrameCtx frame_ctx; + std::vector < ID3D12Fence * >fences_to_wait; + std::vector < guint64 > fence_values_to_wait; + + if (!gst_d3d12_yadif_map_frames (self, tff, &frame_ctx, fence_data, + fences_to_wait, fence_values_to_wait)) { + GST_ERROR_OBJECT (self, "Couldn't map frame context"); + gst_d3d12_fence_data_unref (fence_data); + return GST_FLOW_ERROR; + } + + GstD3D12DescHeap *desc_heap; + if (!gst_d3d12_desc_heap_pool_acquire (priv->desc_pool, &desc_heap)) { + GST_ERROR_OBJECT (self, "Couldn't acquire descriptor heap"); + gst_d3d12_yadif_unmap_frame_ctx (&frame_ctx); + gst_d3d12_fence_data_unref (fence_data); + return GST_FLOW_ERROR; + } + + gst_d3d12_fence_data_push (fence_data, FENCE_NOTIFY_MINI_OBJECT (desc_heap)); + + GstD3D12DescHeap *conv_desc_heap = nullptr; + ID3D12DescriptorHeap *conv_desc_handle = nullptr; + if (priv->post_context) { + if (!gst_d3d12_desc_heap_pool_acquire (priv->desc_pool, &conv_desc_heap)) { + GST_ERROR_OBJECT (self, "Couldn't acquire descriptor heap"); + gst_d3d12_yadif_unmap_frame_ctx (&frame_ctx); + gst_d3d12_fence_data_unref (fence_data); + return GST_FLOW_ERROR; + } + + gst_d3d12_fence_data_push (fence_data, + FENCE_NOTIFY_MINI_OBJECT (conv_desc_heap)); + } + + auto desc_handle = gst_d3d12_desc_heap_get_handle (desc_heap); + auto cpu_handle = CD3DX12_CPU_DESCRIPTOR_HANDLE + (GetCPUDescriptorHandleForHeapStart (desc_handle)); + + guint num_fields = priv->fields == GST_D3D12_YADIF_FIELDS_ALL ? 2 : 1; + for (guint field = 0; field < num_fields; field++) { + for (guint i = 0; i < GST_VIDEO_INFO_N_PLANES (&priv->info); i++) { + device->CopyDescriptorsSimple (1, cpu_handle, + frame_ctx.prev.srv_desc_handle[i], + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + cpu_handle.Offset (priv->desc_inc_size); + + device->CopyDescriptorsSimple (1, cpu_handle, + frame_ctx.cur.srv_desc_handle[i], + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + cpu_handle.Offset (priv->desc_inc_size); + + device->CopyDescriptorsSimple (1, cpu_handle, + frame_ctx.next.srv_desc_handle[i], + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + cpu_handle.Offset (priv->desc_inc_size); + + device->CopyDescriptorsSimple (1, cpu_handle, + frame_ctx.out_frames[field].uav_desc_handle[i], + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + cpu_handle.Offset (priv->desc_inc_size); + } + } + + if (conv_desc_heap) { + conv_desc_handle = gst_d3d12_desc_heap_get_handle (conv_desc_heap); + auto conv_cpu_handle = CD3DX12_CPU_DESCRIPTOR_HANDLE + (GetCPUDescriptorHandleForHeapStart (conv_desc_handle)); + + for (guint field = 0; field < num_fields; field++) { + device->CopyDescriptorsSimple (1, conv_cpu_handle, + frame_ctx.out_frames[field].srv_desc_handle[0], + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + conv_cpu_handle.Offset (priv->desc_inc_size); + + device->CopyDescriptorsSimple (1, conv_cpu_handle, + frame_ctx.conv_frames[field].uav_desc_handle[0], + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + conv_cpu_handle.Offset (priv->desc_inc_size); + } + } + + GstD3D12CmdAlloc *gst_ca; + if (!gst_d3d12_cmd_alloc_pool_acquire (priv->ca_pool, &gst_ca)) { + GST_ERROR_OBJECT (self, "Couldn't acquire command allocator"); + gst_d3d12_yadif_unmap_frame_ctx (&frame_ctx); + gst_d3d12_fence_data_unref (fence_data); + return GST_FLOW_ERROR; + } + + auto ca = gst_d3d12_cmd_alloc_get_handle (gst_ca); + gst_d3d12_fence_data_push (fence_data, FENCE_NOTIFY_MINI_OBJECT (gst_ca)); + + HRESULT hr = ca->Reset (); + if (!gst_d3d12_result (hr, priv->device)) { + GST_ERROR_OBJECT (self, "Couldn't reset command allocator"); + gst_d3d12_yadif_unmap_frame_ctx (&frame_ctx); + gst_d3d12_fence_data_unref (fence_data); + return GST_FLOW_ERROR; + } + + if (!priv->cl) { + hr = device->CreateCommandList (0, priv->queue_type, + ca, nullptr, IID_PPV_ARGS (&priv->cl)); + } else { + hr = priv->cl->Reset (ca, nullptr); + } + + if (!gst_d3d12_result (hr, priv->device)) { + GST_ERROR_OBJECT (self, "Couldn't reset command list"); + gst_d3d12_yadif_unmap_frame_ctx (&frame_ctx); + gst_d3d12_fence_data_unref (fence_data); + return GST_FLOW_ERROR; + } + + auto gpu_handle = CD3DX12_GPU_DESCRIPTOR_HANDLE + (GetGPUDescriptorHandleForHeapStart (desc_handle)); + + priv->cl->SetComputeRootSignature (priv->rs.Get ()); + ID3D12DescriptorHeap *heaps[] = { desc_handle }; + priv->cl->SetDescriptorHeaps (1, heaps); + + for (guint field = 0; field < num_fields; field++) { + for (size_t i = 0; i < priv->contexts.size (); i++) { + auto ctx = priv->contexts[i]; + ctx->cb_data.primary_line = tff ? 0 : 1; + ctx->cb_data.is_second = frame_ctx.is_second[field]; + + if (ctx->pso) + priv->cl->SetPipelineState (ctx->pso.Get ()); + + priv->cl->SetComputeRootDescriptorTable (0, gpu_handle); + gpu_handle.Offset (priv->desc_inc_size * 4); + + priv->cl->SetComputeRoot32BitConstants (1, 4, &ctx->cb_data, 0); + priv->cl->Dispatch (ctx->dispatch_x, ctx->dispatch_y, 1); + + if (priv->post_context) { + auto barrier = + CD3DX12_RESOURCE_BARRIER::Transition + (frame_ctx.out_frames[field].data[0], + D3D12_RESOURCE_STATE_UNORDERED_ACCESS, + D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, + D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, + D3D12_RESOURCE_BARRIER_FLAG_BEGIN_ONLY); + priv->cl->ResourceBarrier (1, &barrier); + } + } + } + + if (priv->post_context) { + auto conv_gpu_handle = CD3DX12_GPU_DESCRIPTOR_HANDLE + (GetGPUDescriptorHandleForHeapStart (conv_desc_handle)); + auto ctx = priv->post_context; + + priv->cl->SetComputeRootSignature (priv->convert_rs.Get ()); + ID3D12DescriptorHeap *conv_heaps[] = { conv_desc_handle }; + priv->cl->SetDescriptorHeaps (1, conv_heaps); + priv->cl->SetPipelineState (ctx->pso.Get ()); + + for (guint field = 0; field < num_fields; field++) { + auto barrier = CD3DX12_RESOURCE_BARRIER::Transition + (frame_ctx.out_frames[field].data[0], + D3D12_RESOURCE_STATE_UNORDERED_ACCESS, + D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, + D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, + D3D12_RESOURCE_BARRIER_FLAG_END_ONLY); + priv->cl->ResourceBarrier (1, &barrier); + + priv->cl->SetComputeRootDescriptorTable (0, conv_gpu_handle); + conv_gpu_handle.Offset (priv->desc_inc_size * 2); + priv->cl->Dispatch (ctx->dispatch_x, ctx->dispatch_y, 1); + } + } + + hr = priv->cl->Close (); + + if (!gst_d3d12_result (hr, priv->device)) { + GST_ERROR_OBJECT (self, "Couldn't close command list"); + gst_d3d12_yadif_unmap_frame_ctx (&frame_ctx); + gst_d3d12_fence_data_unref (fence_data); + gst_vec_deque_clear (priv->current_queue); + return GST_FLOW_ERROR; + } + + ID3D12CommandList *cmd_list[] = { priv->cl.Get () }; + if (fences_to_wait.empty ()) { + hr = gst_d3d12_cmd_queue_execute_command_lists (priv->cq, + 1, cmd_list, &priv->fence_val); + } else { + hr = gst_d3d12_cmd_queue_execute_command_lists_full (priv->cq, + fences_to_wait.size (), fences_to_wait.data (), + fence_values_to_wait.data (), 1, cmd_list, &priv->fence_val); + } + + gst_d3d12_yadif_unmap_frame_ctx (&frame_ctx); + + if (!gst_d3d12_result (hr, priv->device)) { + GST_ERROR_OBJECT (self, "Couldn't execute command list"); + gst_d3d12_fence_data_unref (fence_data); + gst_vec_deque_clear (priv->current_queue); + return GST_FLOW_ERROR; + } + + gst_d3d12_cmd_queue_set_notify (priv->cq, priv->fence_val, + FENCE_NOTIFY_MINI_OBJECT (fence_data)); + + while (!gst_vec_deque_is_empty (priv->current_queue)) { + auto buf = (GstBuffer *) gst_vec_deque_pop_head (priv->current_queue); + gst_d3d12_buffer_set_fence (buf, priv->fence.Get (), + priv->fence_val, FALSE); + gst_vec_deque_push_tail (priv->output_queue, buf); + } + + return GST_FLOW_OK; +} + +static GstFlowReturn +gst_d3d12_yadif_push_unlocked (GstD3D12Yadif * self, GstBuffer * buffer) +{ + auto priv = self->priv; + + gst_clear_buffer (&priv->prev_buf); + + priv->prev_buf = priv->cur_buf; + priv->cur_buf = priv->next_buf; + priv->next_buf = buffer; + + if (!priv->cur_buf) + priv->cur_buf = gst_buffer_ref (priv->next_buf); + + if (!priv->prev_buf) + return GST_D3D12_YADIF_FLOW_NEED_DATA; + + return gst_d3d12_yadif_process_frame (self); +} + +static GstBuffer * +gst_d3d12_yadif_preproc (GstD3D12Yadif * self, GstBuffer * buffer) +{ + auto priv = self->priv; + + if (!priv->pre_context) + return buffer; + + GstD3D12FenceData *fence_data; + gst_d3d12_fence_data_pool_acquire (priv->fence_pool, &fence_data); + gst_d3d12_fence_data_push (fence_data, FENCE_NOTIFY_MINI_OBJECT (buffer)); + + GstD3D12CmdAlloc *gst_ca; + if (!gst_d3d12_cmd_alloc_pool_acquire (priv->ca_pool, &gst_ca)) { + GST_ERROR_OBJECT (self, "Couldn't acquire command allocator"); + gst_d3d12_fence_data_unref (fence_data); + return nullptr; + } + + auto ca = gst_d3d12_cmd_alloc_get_handle (gst_ca); + gst_d3d12_fence_data_push (fence_data, FENCE_NOTIFY_MINI_OBJECT (gst_ca)); + + auto hr = ca->Reset (); + if (!gst_d3d12_result (hr, priv->device)) { + GST_ERROR_OBJECT (self, "Couldn't reset command allocator"); + gst_d3d12_fence_data_unref (fence_data); + return nullptr; + } + + auto device = gst_d3d12_device_get_device_handle (priv->device); + if (!priv->cl) { + hr = device->CreateCommandList (0, priv->queue_type, + ca, nullptr, IID_PPV_ARGS (&priv->cl)); + } else { + hr = priv->cl->Reset (ca, nullptr); + } + + if (!gst_d3d12_result (hr, priv->device)) { + GST_ERROR_OBJECT (self, "Couldn't reset command list"); + gst_d3d12_fence_data_unref (fence_data); + return nullptr; + } + + GstD3D12DescHeap *desc_heap; + if (!gst_d3d12_desc_heap_pool_acquire (priv->desc_pool, &desc_heap)) { + GST_ERROR_OBJECT (self, "Couldn't acquire descriptor heap"); + gst_d3d12_fence_data_unref (fence_data); + return nullptr; + } + + gst_d3d12_fence_data_push (fence_data, FENCE_NOTIFY_MINI_OBJECT (desc_heap)); + + GstBuffer *outbuf = nullptr; + gst_buffer_pool_acquire_buffer (priv->output_pool, &outbuf, nullptr); + if (!outbuf) { + GST_ERROR_OBJECT (self, "Couldn't acquire output buffer"); + gst_d3d12_fence_data_unref (fence_data); + return nullptr; + } + + gst_buffer_copy_into (outbuf, buffer, GST_BUFFER_COPY_METADATA, 0, -1); + GstD3D12Frame in_frame, out_frame; + if (!gst_d3d12_frame_map (&in_frame, &priv->origin_info, buffer, + GST_MAP_READ, GST_D3D12_FRAME_MAP_FLAG_SRV)) { + GST_ERROR_OBJECT (self, "Couldn't map frame"); + gst_d3d12_fence_data_unref (fence_data); + return nullptr; + } + + if (!gst_d3d12_frame_map (&out_frame, &priv->info, outbuf, + GST_MAP_D3D12, GST_D3D12_FRAME_MAP_FLAG_UAV)) { + GST_ERROR_OBJECT (self, "Couldn't map frame"); + gst_d3d12_frame_unmap (&in_frame); + gst_d3d12_fence_data_unref (fence_data); + return nullptr; + } + + auto desc_handle = gst_d3d12_desc_heap_get_handle (desc_heap); + auto cpu_handle = CD3DX12_CPU_DESCRIPTOR_HANDLE + (GetCPUDescriptorHandleForHeapStart (desc_handle)); + + device->CopyDescriptorsSimple (1, cpu_handle, in_frame.srv_desc_handle[0], + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + cpu_handle.Offset (priv->desc_inc_size); + device->CopyDescriptorsSimple (1, cpu_handle, out_frame.uav_desc_handle[0], + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + + auto gpu_handle = CD3DX12_GPU_DESCRIPTOR_HANDLE + (GetGPUDescriptorHandleForHeapStart (desc_handle)); + priv->cl->SetComputeRootSignature (priv->rs.Get ()); + + ID3D12DescriptorHeap *heaps[] = { desc_handle }; + priv->cl->SetDescriptorHeaps (1, heaps); + + auto ctx = priv->pre_context; + priv->cl->SetPipelineState (ctx->pso.Get ()); + priv->cl->SetComputeRootDescriptorTable (0, gpu_handle); + priv->cl->Dispatch (ctx->dispatch_x, ctx->dispatch_y, 1); + hr = priv->cl->Close (); + + if (!gst_d3d12_result (hr, priv->device)) { + GST_ERROR_OBJECT (self, "Couldn't close command list"); + gst_d3d12_frame_unmap (&in_frame); + gst_d3d12_frame_unmap (&out_frame); + gst_d3d12_fence_data_unref (fence_data); + gst_buffer_unref (outbuf); + return nullptr; + } + + ID3D12CommandList *cmd_list[] = { priv->cl.Get () }; + if (in_frame.fence->fence) { + hr = gst_d3d12_cmd_queue_execute_command_lists_full (priv->cq, + 1, &in_frame.fence->fence, &in_frame.fence->fence_value, + 1, cmd_list, &priv->fence_val); + } else { + hr = gst_d3d12_cmd_queue_execute_command_lists (priv->cq, + 1, cmd_list, &priv->fence_val); + } + + gst_d3d12_frame_unmap (&in_frame); + gst_d3d12_frame_unmap (&out_frame); + + if (!gst_d3d12_result (hr, priv->device)) { + GST_ERROR_OBJECT (self, "Couldn't execute command list"); + gst_d3d12_fence_data_unref (fence_data); + gst_buffer_unref (outbuf); + return nullptr; + } + + gst_d3d12_cmd_queue_set_notify (priv->cq, priv->fence_val, + FENCE_NOTIFY_MINI_OBJECT (fence_data)); + gst_d3d12_buffer_set_fence (outbuf, priv->fence.Get (), + priv->fence_val, FALSE); + + return outbuf; +} + +GstFlowReturn +gst_d3d12_yadif_push (GstD3D12Yadif * yadif, GstBuffer * buffer) +{ + g_return_val_if_fail (GST_IS_D3D12_YADIF (yadif), GST_FLOW_ERROR); + g_return_val_if_fail (GST_IS_BUFFER (buffer), GST_FLOW_ERROR); + + auto priv = yadif->priv; + + std::lock_guard < std::mutex > lk (priv->lock); + buffer = gst_d3d12_yadif_preproc (yadif, buffer); + + if (!buffer) + return GST_FLOW_ERROR; + + return gst_d3d12_yadif_push_unlocked (yadif, buffer); +} + +GstFlowReturn +gst_d3d12_yadif_pop (GstD3D12Yadif * yadif, GstBuffer ** buffer) +{ + g_return_val_if_fail (GST_IS_D3D12_YADIF (yadif), GST_FLOW_ERROR); + g_return_val_if_fail (buffer, GST_FLOW_ERROR); + + auto priv = yadif->priv; + + std::lock_guard < std::mutex > lk (priv->lock); + + *buffer = nullptr; + if (gst_vec_deque_is_empty (priv->output_queue)) + return GST_D3D12_YADIF_FLOW_NEED_DATA; + + *buffer = (GstBuffer *) gst_vec_deque_pop_head (priv->output_queue); + + return GST_FLOW_OK; +} + +GstFlowReturn +gst_d3d12_yadif_drain (GstD3D12Yadif * yadif) +{ + g_return_val_if_fail (GST_IS_D3D12_YADIF (yadif), GST_FLOW_ERROR); + + auto priv = yadif->priv; + + std::lock_guard < std::mutex > lk (priv->lock); + if (!priv->next_buf) { + priv->Flush (); + + return GST_D3D12_YADIF_FLOW_NEED_DATA; + } + + auto next = gst_buffer_copy (priv->next_buf); + GstClockTime pts = GST_CLOCK_TIME_NONE; + GstClockTime dur = GST_CLOCK_TIME_NONE; + if (GST_BUFFER_PTS_IS_VALID (priv->next_buf)) { + pts = GST_BUFFER_PTS (priv->next_buf); + if (GST_BUFFER_DURATION_IS_VALID (priv->next_buf)) { + dur = GST_BUFFER_DURATION (priv->next_buf); + } else { + gint fps_n = 30; + gint fps_d = 1; + if (priv->info.fps_n > 0 && priv->info.fps_d > 0) { + fps_n = priv->info.fps_n; + fps_d = priv->info.fps_d; + } + + dur = gst_util_uint64_scale (GST_SECOND, fps_d, fps_n); + } + + if (!priv->is_forward) { + if (pts >= dur) { + pts -= dur; + } else { + dur -= pts; + pts = 0; + } + } else { + pts += dur; + } + } + + GST_BUFFER_PTS (next) = pts; + GST_BUFFER_DURATION (next) = dur; + + auto ret = gst_d3d12_yadif_push_unlocked (yadif, next); + priv->Flush (); + + return ret; +} + +void +gst_d3d12_yadif_flush (GstD3D12Yadif * yadif) +{ + g_return_if_fail (GST_IS_D3D12_YADIF (yadif)); + + auto priv = yadif->priv; + + std::lock_guard < std::mutex > lk (priv->lock); + priv->Flush (); + gst_vec_deque_clear (priv->output_queue); +} diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12yadif.h b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12yadif.h new file mode 100644 index 0000000000..ffbbdf48f8 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12yadif.h @@ -0,0 +1,61 @@ +/* GStreamer + * Copyright (C) 2024 Seungha Yang <seungha@centricular.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <gst/gst.h> +#include <gst/video/video.h> +#include <gst/d3d12/gstd3d12.h> + +G_BEGIN_DECLS + +#define GST_TYPE_D3D12_YADIF (gst_d3d12_yadif_get_type()) +G_DECLARE_FINAL_TYPE (GstD3D12Yadif, gst_d3d12_yadif, + GST, D3D12_YADIF, GstObject) + +#define GST_D3D12_YADIF_FLOW_NEED_DATA GST_FLOW_CUSTOM_SUCCESS + +typedef enum +{ + GST_D3D12_YADIF_FIELDS_ALL, + GST_D3D12_YADIF_FIELDS_TOP, + GST_D3D12_YADIF_FIELDS_BOTTOM, +} GstD3D12YadifFields; + +GstD3D12Yadif * gst_d3d12_yadif_new (GstD3D12Device * device, + const GstVideoInfo * info, + gboolean use_compute); + +void gst_d3d12_yadif_set_fields (GstD3D12Yadif * yadif, + GstD3D12YadifFields fields); + +void gst_d3d12_yadif_set_direction (GstD3D12Yadif * yadif, + gboolean is_forward); + +GstFlowReturn gst_d3d12_yadif_push (GstD3D12Yadif * yadif, + GstBuffer * buffer); + +GstFlowReturn gst_d3d12_yadif_pop (GstD3D12Yadif * yadif, + GstBuffer ** buffer); + +GstFlowReturn gst_d3d12_yadif_drain (GstD3D12Yadif * yadif); + +void gst_d3d12_yadif_flush (GstD3D12Yadif * yadif); + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/sys/d3d12/meson.build b/subprojects/gst-plugins-bad/sys/d3d12/meson.build index a4b71d46e8..715fe5e29b 100644 --- a/subprojects/gst-plugins-bad/sys/d3d12/meson.build +++ b/subprojects/gst-plugins-bad/sys/d3d12/meson.build @@ -5,6 +5,8 @@ d3d12_sources = [ 'gstd3d12convert.cpp', 'gstd3d12decoder.cpp', 'gstd3d12decodercpbpool.cpp', + 'gstd3d12deinterlace.cpp', + 'gstd3d12yadif.cpp', 'gstd3d12dpbstorage.cpp', 'gstd3d12dxgicapture.cpp', 'gstd3d12encoder.cpp', diff --git a/subprojects/gst-plugins-bad/sys/d3d12/plugin.cpp b/subprojects/gst-plugins-bad/sys/d3d12/plugin.cpp index 475ab6cd7d..85476e81c9 100644 --- a/subprojects/gst-plugins-bad/sys/d3d12/plugin.cpp +++ b/subprojects/gst-plugins-bad/sys/d3d12/plugin.cpp @@ -50,6 +50,7 @@ #include "gstd3d12ipcsink.h" #include "gstd3d12swapchainsink.h" #include "gstd3d12mipmapping.h" +#include "gstd3d12deinterlace.h" #include <windows.h> #include <versionhelpers.h> #include <wrl.h> @@ -189,6 +190,8 @@ plugin_init (GstPlugin * plugin) "d3d12swapchainsink", GST_RANK_NONE, GST_TYPE_D3D12_SWAPCHAIN_SINK); gst_element_register (plugin, "d3d12mipmapping", GST_RANK_NONE, GST_TYPE_D3D12_MIP_MAPPING); + gst_element_register (plugin, + "d3d12deinterlace", GST_RANK_NONE, GST_TYPE_D3D12_DEINTERLACE); g_object_set_data_full (G_OBJECT (plugin), "plugin-d3d12-shutdown", (gpointer) "shutdown-data",