diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12swapchainsink.cpp b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12swapchainsink.cpp new file mode 100644 index 0000000000..57909ea1a5 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12swapchainsink.cpp @@ -0,0 +1,1082 @@ +/* GStreamer + * Copyright (C) 2024 Seungha Yang + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstd3d12swapchainsink.h" +#include "gstd3d12pluginutils.h" +#include "gstd3d12overlaycompositor.h" +#include +#include +#include +#include +#include + +/* *INDENT-OFF* */ +using namespace Microsoft::WRL; +/* *INDENT-ON* */ + + +enum +{ + PROP_0, + PROP_ADAPTER, + PROP_FORCE_ASPECT_RATIO, + PROP_WIDTH, + PROP_HEIGHT, + PROP_BORDER_COLOR, + PROP_SWAPCHAIN, +}; + +#define DEFAULT_ADAPTER -1 +#define DEFAULT_FORCE_ASPECT_RATIO TRUE +#define DEFAULT_WIDTH 1280 +#define DEFAULT_HEIGHT 720 +#define DEFAULT_BORDER_COLOR (G_GUINT64_CONSTANT(0xffff000000000000)) + +#define BACK_BUFFER_COUNT 2 + +enum +{ + SIGNAL_RESIZE, + SIGNAL_LAST +}; + +static guint d3d12_swapchain_sink_signals[SIGNAL_LAST] = { 0, }; + +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) ";" + GST_VIDEO_CAPS_MAKE (GST_D3D12_ALL_FORMATS) "; " + GST_VIDEO_CAPS_MAKE_WITH_FEATURES + (GST_CAPS_FEATURE_MEMORY_SYSTEM_MEMORY "," + GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, + GST_D3D12_ALL_FORMATS))); + +GST_DEBUG_CATEGORY_STATIC (gst_d3d12_swapchain_sink_debug); +#define GST_CAT_DEFAULT gst_d3d12_swapchain_sink_debug + +/* *INDENT-OFF* */ +struct BackBuffer +{ + BackBuffer (GstBuffer * buffer, ID3D12Resource * res) + { + backbuf = buffer; + resource = res; + } + + ~BackBuffer () + { + gst_clear_buffer (&backbuf); + } + + GstBuffer *backbuf = nullptr; + ComPtr resource; +}; + +struct GstD3D12SwapChainSinkPrivate +{ + GstD3D12SwapChainSinkPrivate () + { + convert_config = gst_structure_new_empty ("convert-config"); + fence_data_pool = gst_d3d12_fence_data_pool_new (); + gst_video_info_init (&info); + gst_video_info_set_format (&display_info, GST_VIDEO_FORMAT_RGBA, + width, height); + update_border_color (); + } + + ~GstD3D12SwapChainSinkPrivate () + { + stop (); + + gst_structure_free (convert_config); + gst_clear_object (&comp); + gst_clear_object (&ca_pool); + gst_clear_object (&fence_data_pool); + } + + void stop () + { + if (cq && swapchain && fence_val > 0) + gst_d3d12_command_queue_idle_for_swapchain (cq, fence_val); + if (pool) { + gst_buffer_pool_set_active (pool, FALSE); + gst_clear_object (&pool); + } + gst_clear_caps (&caps); + gst_clear_buffer (&cached_buf); + gst_clear_object (&conv); + backbuf.clear (); + convert_format = GST_VIDEO_FORMAT_UNKNOWN; + caps_updated = false; + first_present = true; + } + + void update_border_color () + { + border_color_val[0] = ((border_color & + G_GUINT64_CONSTANT(0x0000ffff00000000)) >> 32) / (FLOAT) G_MAXUINT16; + border_color_val[1] = ((border_color & + G_GUINT64_CONSTANT(0x00000000ffff0000)) >> 16) / (FLOAT) G_MAXUINT16; + border_color_val[2] = (border_color & + G_GUINT64_CONSTANT(0x000000000000ffff)) / (FLOAT) G_MAXUINT16; + border_color_val[3] = ((border_color & + G_GUINT64_CONSTANT(0xffff000000000000)) >> 48) / (FLOAT) G_MAXUINT16; + } + + std::recursive_mutex lock; + GstVideoInfo info; + GstVideoInfo display_info; + guint display_width; + guint display_height; + GstVideoFormat convert_format = GST_VIDEO_FORMAT_UNKNOWN; + ComPtr swapchain; + ComPtr cl; + std::vector> backbuf; + GstStructure *convert_config = nullptr; + GstD3D12FenceDataPool *fence_data_pool = nullptr; + GstBufferPool *pool = nullptr; + GstD3D12CommandQueue *cq = nullptr; + GstD3D12CommandAllocatorPool *ca_pool = nullptr; + GstBuffer *cached_buf = nullptr; + GstCaps *caps = nullptr; + GstD3D12Converter *conv = nullptr; + GstD3D12OverlayCompositor *comp = nullptr; + guint64 fence_val = 0; + bool caps_updated = false; + bool first_present = true; + D3D12_BOX crop_rect = { }; + D3D12_BOX prev_crop_rect = { }; + FLOAT border_color_val[4]; + GstVideoRectangle viewport = { }; + + gint adapter = DEFAULT_ADAPTER; + gint force_aspect_ratio = DEFAULT_FORCE_ASPECT_RATIO; + guint width = DEFAULT_WIDTH; + guint height = DEFAULT_HEIGHT; + guint64 border_color = DEFAULT_BORDER_COLOR; +}; +/* *INDENT-ON* */ + +struct _GstD3D12SwapChainSink +{ + GstVideoSink parent; + + GstD3D12Device *device; + + GstD3D12SwapChainSinkPrivate *priv; +}; + +static void gst_d3d12_swapchain_sink_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec); +static void gst_d3d12_swapchain_sink_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec); +static void gst_d3d12_swapchain_sink_finalize (GObject * object); +static void gst_d3d12_swapchain_sink_set_context (GstElement * element, + GstContext * context); +static gboolean gst_d3d12_swapchain_sink_start (GstBaseSink * sink); +static gboolean gst_d3d12_swapchain_sink_stop (GstBaseSink * sink); +static gboolean gst_d3d12_swapchain_sink_propose_allocation (GstBaseSink * sink, + GstQuery * query); +static gboolean gst_d3d12_swapchain_sink_query (GstBaseSink * sink, + GstQuery * query); +static GstFlowReturn gst_d3d12_swapchain_sink_prepare (GstBaseSink * sink, + GstBuffer * buf); +static gboolean gst_d3d12_swapchain_sink_set_info (GstVideoSink * sink, + GstCaps * caps, const GstVideoInfo * info); +static GstFlowReturn gst_d3d12_swapchain_sink_show_frame (GstVideoSink * sink, + GstBuffer * buf); +static void gst_d3d12_swapchain_sink_resize (GstD3D12SwapChainSink * self, + guint width, guint height); + +#define gst_d3d12_swapchain_sink_parent_class parent_class +G_DEFINE_TYPE (GstD3D12SwapChainSink, gst_d3d12_swapchain_sink, + GST_TYPE_VIDEO_SINK); + +static void +gst_d3d12_swapchain_sink_class_init (GstD3D12SwapChainSinkClass * klass) +{ + auto object_class = G_OBJECT_CLASS (klass); + auto element_class = GST_ELEMENT_CLASS (klass); + auto basesink_class = GST_BASE_SINK_CLASS (klass); + auto videosink_class = GST_VIDEO_SINK_CLASS (klass); + + object_class->set_property = gst_d3d12_swapchain_sink_set_property; + object_class->get_property = gst_d3d12_swapchain_sink_get_property; + object_class->finalize = gst_d3d12_swapchain_sink_finalize; + + g_object_class_install_property (object_class, PROP_ADAPTER, + g_param_spec_int ("adapter", "Adapter", + "Adapter index for creating device (-1 for default)", + -1, G_MAXINT32, DEFAULT_ADAPTER, + (GParamFlags) (G_PARAM_READWRITE | GST_PARAM_MUTABLE_READY | + G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property (object_class, PROP_FORCE_ASPECT_RATIO, + g_param_spec_boolean ("force-aspect-ratio", + "Force aspect ratio", + "When enabled, scaling will respect original aspect ratio", + DEFAULT_FORCE_ASPECT_RATIO, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property (object_class, PROP_WIDTH, + g_param_spec_uint ("swapchain-width", "Swapchain Width", + "Width of swapchain buffers", + 1, D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION, DEFAULT_WIDTH, + (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property (object_class, PROP_HEIGHT, + g_param_spec_uint ("swapchain-height", "Swapchain Height", + "Height of swapchain buffers", + 1, D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION, DEFAULT_HEIGHT, + (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property (object_class, PROP_BORDER_COLOR, + g_param_spec_uint64 ("border-color", "Border Color", + "ARGB64 representation of the border color to use", + 0, G_MAXUINT64, DEFAULT_BORDER_COLOR, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property (object_class, PROP_SWAPCHAIN, + g_param_spec_pointer ("swapchain", "SwapChain", + "DXGI swapchain handle", + (GParamFlags) (GST_PARAM_DOC_SHOW_DEFAULT | G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS))); + + d3d12_swapchain_sink_signals[SIGNAL_RESIZE] = + g_signal_new_class_handler ("resize", G_TYPE_FROM_CLASS (klass), + (GSignalFlags) (G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION), + G_CALLBACK (gst_d3d12_swapchain_sink_resize), nullptr, nullptr, nullptr, + G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT); + + element_class->set_context = + GST_DEBUG_FUNCPTR (gst_d3d12_swapchain_sink_set_context); + + gst_element_class_set_static_metadata (element_class, + "Direct3D12 SwapChain Sink", "Sink/Video", + "DXGI composition swapchain sink", + "Seungha Yang "); + + gst_element_class_add_static_pad_template (element_class, &sink_template); + + basesink_class->start = GST_DEBUG_FUNCPTR (gst_d3d12_swapchain_sink_start); + basesink_class->stop = GST_DEBUG_FUNCPTR (gst_d3d12_swapchain_sink_stop); + basesink_class->propose_allocation = + GST_DEBUG_FUNCPTR (gst_d3d12_swapchain_sink_propose_allocation); + basesink_class->query = GST_DEBUG_FUNCPTR (gst_d3d12_swapchain_sink_query); + basesink_class->prepare = + GST_DEBUG_FUNCPTR (gst_d3d12_swapchain_sink_prepare); + + videosink_class->set_info = + GST_DEBUG_FUNCPTR (gst_d3d12_swapchain_sink_set_info); + videosink_class->show_frame = + GST_DEBUG_FUNCPTR (gst_d3d12_swapchain_sink_show_frame); + + GST_DEBUG_CATEGORY_INIT (gst_d3d12_swapchain_sink_debug, + "d3d12swapchainsink", 0, "d3d12swapchainsink"); +} + +static void +gst_d3d12_swapchain_sink_init (GstD3D12SwapChainSink * self) +{ + self->priv = new GstD3D12SwapChainSinkPrivate (); +} + +static void +gst_d3d12_swapchain_sink_finalize (GObject * object) +{ + auto self = GST_D3D12_SWAPCHAIN_SINK (object); + + delete self->priv; + + gst_clear_object (&self->device); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_d3d12_swapchain_sink_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + auto self = GST_D3D12_SWAPCHAIN_SINK (object); + auto priv = self->priv; + + std::lock_guard < std::recursive_mutex > lk (priv->lock); + switch (prop_id) { + case PROP_ADAPTER: + priv->adapter = g_value_get_int (value); + break; + case PROP_FORCE_ASPECT_RATIO: + { + auto val = g_value_get_boolean (value); + if (val != priv->force_aspect_ratio) { + priv->force_aspect_ratio = val; + gst_d3d12_swapchain_sink_resize (self, priv->width, priv->height); + } + break; + } + case PROP_BORDER_COLOR: + priv->border_color = g_value_get_uint64 (value); + priv->update_border_color (); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +gst_d3d12_swapchain_sink_resize_unlocked (GstD3D12SwapChainSink * self, + guint width, guint height) +{ + auto priv = self->priv; + + if (width != priv->width || height != priv->height) { + GST_DEBUG_OBJECT (self, "Resizing swapchain, %ux%u -> %ux%u", + priv->width, priv->height, width, height); + if (priv->cq && priv->swapchain && priv->fence_val > 0) + gst_d3d12_command_queue_idle_for_swapchain (priv->cq, priv->fence_val); + + priv->backbuf.clear (); + priv->width = width; + priv->height = height; + priv->first_present = true; + gst_video_info_set_format (&priv->display_info, GST_VIDEO_FORMAT_RGBA, + width, height); + } + + if (priv->swapchain && priv->backbuf.empty ()) { + auto hr = priv->swapchain->ResizeBuffers (BACK_BUFFER_COUNT, + priv->width, priv->height, DXGI_FORMAT_R8G8B8A8_UNORM, 0); + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Resize failed"); + return FALSE; + } + + for (guint i = 0; i < BACK_BUFFER_COUNT; i++) { + ComPtr < ID3D12Resource > backbuf; + hr = priv->swapchain->GetBuffer (i, IID_PPV_ARGS (&backbuf)); + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Couldn't get backbuffer"); + priv->backbuf.clear (); + return FALSE; + } + + auto mem = gst_d3d12_allocator_alloc_wrapped (nullptr, self->device, + backbuf.Get (), 0, nullptr, nullptr); + auto buf = gst_buffer_new (); + gst_buffer_append_memory (buf, mem); + auto swapbuf = std::make_shared < BackBuffer > (buf, backbuf.Get ()); + priv->backbuf.push_back (swapbuf); + } + } + + return TRUE; +} + +static gboolean +gst_d3d12_swapchain_sink_ensure_swapchain (GstD3D12SwapChainSink * self) +{ + auto priv = self->priv; + if (priv->swapchain) + return TRUE; + + if (!gst_d3d12_ensure_element_data (GST_ELEMENT_CAST (self), priv->adapter, + &self->device)) { + GST_ERROR_OBJECT (self, "Cannot create device"); + return FALSE; + } + + priv->cq = gst_d3d12_device_get_command_queue (self->device, + D3D12_COMMAND_LIST_TYPE_DIRECT); + + auto cq = gst_d3d12_command_queue_get_handle (priv->cq); + auto factory = gst_d3d12_device_get_factory_handle (self->device); + + DXGI_SWAP_CHAIN_DESC1 desc = { }; + desc.Width = priv->width; + desc.Height = priv->height; + desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + desc.SampleDesc.Count = 1; + desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + desc.BufferCount = BACK_BUFFER_COUNT; + desc.Scaling = DXGI_SCALING_STRETCH; + desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; + desc.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED; + + ComPtr < IDXGISwapChain1 > swapchain; + auto hr = factory->CreateSwapChainForComposition (cq, &desc, nullptr, + &swapchain); + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Couldn't create swapchain"); + return FALSE; + } + + hr = swapchain.As (&priv->swapchain); + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Couldn't get IDXGISwapChain4 interface"); + return FALSE; + } + + auto device = gst_d3d12_device_get_device_handle (self->device); + priv->ca_pool = gst_d3d12_command_allocator_pool_new (device, + D3D12_COMMAND_LIST_TYPE_DIRECT); + + GstVideoInfo info; + gst_video_info_set_format (&info, + GST_VIDEO_FORMAT_RGBA, priv->width, priv->height); + priv->comp = gst_d3d12_overlay_compositor_new (self->device, &info); + + return gst_d3d12_swapchain_sink_resize_unlocked (self, + priv->width, priv->height); +} + +static void +gst_d3d12_swapchain_sink_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + auto self = GST_D3D12_SWAPCHAIN_SINK (object); + auto priv = self->priv; + + std::lock_guard < std::recursive_mutex > lk (priv->lock); + switch (prop_id) { + case PROP_ADAPTER: + g_value_set_int (value, priv->adapter); + break; + case PROP_FORCE_ASPECT_RATIO: + g_value_set_boolean (value, priv->force_aspect_ratio); + break; + case PROP_WIDTH: + g_value_set_uint (value, priv->width); + break; + case PROP_HEIGHT: + g_value_set_uint (value, priv->height); + break; + case PROP_BORDER_COLOR: + g_value_set_uint64 (value, priv->border_color); + break; + case PROP_SWAPCHAIN: + gst_d3d12_swapchain_sink_ensure_swapchain (self); + g_value_set_pointer (value, priv->swapchain.Get ()); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_d3d12_swapchain_sink_set_context (GstElement * element, + GstContext * context) +{ + auto self = GST_D3D12_SWAPCHAIN_SINK (element); + auto priv = self->priv; + + gst_d3d12_handle_set_context (element, context, priv->adapter, &self->device); + + GST_ELEMENT_CLASS (parent_class)->set_context (element, context); +} + +static gboolean +gst_d3d12_swapchain_sink_set_info (GstVideoSink * sink, GstCaps * caps, + const GstVideoInfo * info) +{ + auto self = GST_D3D12_SWAPCHAIN_SINK (sink); + auto priv = self->priv; + + GST_DEBUG_OBJECT (self, "set caps %" GST_PTR_FORMAT, caps); + + std::lock_guard < std::recursive_mutex > lk (priv->lock); + gst_caps_replace (&priv->caps, caps); + priv->info = *info; + priv->caps_updated = true; + + auto video_width = GST_VIDEO_INFO_WIDTH (&priv->info); + auto video_height = GST_VIDEO_INFO_HEIGHT (&priv->info); + auto video_par_n = GST_VIDEO_INFO_PAR_N (&priv->info); + auto video_par_d = GST_VIDEO_INFO_PAR_D (&priv->info); + gint display_par_n = 1; + gint display_par_d = 1; + guint num, den; + + if (!gst_video_calculate_display_ratio (&num, &den, video_width, + video_height, video_par_n, video_par_d, display_par_n, + display_par_d)) { + GST_ELEMENT_WARNING (self, CORE, NEGOTIATION, (nullptr), + ("Error calculating the output display ratio of the video.")); + GST_VIDEO_SINK_WIDTH (self) = video_width; + GST_VIDEO_SINK_HEIGHT (self) = video_height; + } else { + GST_DEBUG_OBJECT (self, + "video width/height: %dx%d, calculated display ratio: %d/%d format: %s", + video_width, video_height, num, den, + gst_video_format_to_string (GST_VIDEO_INFO_FORMAT (&priv->info))); + + if (video_height % den == 0) { + GST_DEBUG_OBJECT (self, "keeping video height"); + GST_VIDEO_SINK_WIDTH (self) = (guint) + gst_util_uint64_scale_int (video_height, num, den); + GST_VIDEO_SINK_HEIGHT (self) = video_height; + } else if (video_width % num == 0) { + GST_DEBUG_OBJECT (self, "keeping video width"); + GST_VIDEO_SINK_WIDTH (self) = video_width; + GST_VIDEO_SINK_HEIGHT (self) = (guint) + gst_util_uint64_scale_int (video_width, den, num); + } else { + GST_DEBUG_OBJECT (self, "approximating while keeping video height"); + GST_VIDEO_SINK_WIDTH (self) = (guint) + gst_util_uint64_scale_int (video_height, num, den); + GST_VIDEO_SINK_HEIGHT (self) = video_height; + } + } + + if (GST_VIDEO_SINK_WIDTH (self) <= 0) { + GST_WARNING_OBJECT (self, "Invalid display width %d", + GST_VIDEO_SINK_WIDTH (self)); + GST_VIDEO_SINK_WIDTH (self) = 8; + } + + if (GST_VIDEO_SINK_HEIGHT (self) <= 0) { + GST_WARNING_OBJECT (self, "Invalid display height %d", + GST_VIDEO_SINK_HEIGHT (self)); + GST_VIDEO_SINK_HEIGHT (self) = 8; + } + + GST_DEBUG_OBJECT (self, "scaling to %dx%d", + GST_VIDEO_SINK_WIDTH (self), GST_VIDEO_SINK_HEIGHT (self)); + + if (priv->pool) { + gst_buffer_pool_set_active (priv->pool, FALSE); + gst_clear_object (&priv->pool); + } + + priv->pool = gst_d3d12_buffer_pool_new (self->device); + auto config = gst_buffer_pool_get_config (priv->pool); + + gst_buffer_pool_config_set_params (config, priv->caps, priv->info.size, 0, 0); + if (!gst_buffer_pool_set_config (priv->pool, config) || + !gst_buffer_pool_set_active (priv->pool, TRUE)) { + GST_ELEMENT_ERROR (self, RESOURCE, FAILED, (nullptr), + ("Couldn't setup buffer pool")); + gst_clear_object (&priv->pool); + return FALSE; + } + + return TRUE; +} + +static gboolean +gst_d3d12_swapchain_sink_foreach_meta (GstBuffer * buffer, GstMeta ** meta, + GstBuffer * uploaded) +{ + if ((*meta)->info->api != GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE) + return TRUE; + + auto cmeta = (GstVideoOverlayCompositionMeta *) (*meta); + if (!cmeta->overlay) + return TRUE; + + if (gst_video_overlay_composition_n_rectangles (cmeta->overlay) == 0) + return TRUE; + + gst_buffer_add_video_overlay_composition_meta (uploaded, cmeta->overlay); + + return TRUE; +} + +static gboolean +gst_d3d12_swapchain_sink_render (GstD3D12SwapChainSink * self) +{ + auto priv = self->priv; + if (!priv->cached_buf) { + GST_DEBUG_OBJECT (self, "No cached buffer"); + return TRUE; + } + + auto crop_rect = priv->crop_rect; + auto crop_meta = gst_buffer_get_video_crop_meta (priv->cached_buf); + if (crop_meta) { + crop_rect = CD3DX12_BOX (crop_meta->x, crop_meta->y, + crop_meta->x + crop_meta->width, crop_meta->y + crop_meta->height); + } + + if (crop_rect != priv->prev_crop_rect) { + g_object_set (priv->conv, "src-x", (gint) crop_rect.left, + "src-y", (gint) crop_rect.top, + "src-width", (gint) (crop_rect.right - crop_rect.left), + "src-height", (gint) (crop_rect.bottom - crop_rect.top), nullptr); + priv->prev_crop_rect = crop_rect; + } + + if (priv->first_present) { + GstVideoRectangle dst_rect = { }; + dst_rect.w = priv->width; + dst_rect.h = priv->height; + + if (priv->force_aspect_ratio) { + GstVideoRectangle src_rect = { }; + src_rect.w = priv->display_width; + src_rect.h = priv->display_height; + + gst_video_sink_center_rect (src_rect, dst_rect, &priv->viewport, TRUE); + } else { + priv->viewport = dst_rect; + } + + g_object_set (priv->conv, "dest-x", priv->viewport.x, + "dest-y", priv->viewport.y, "dest-width", priv->viewport.w, + "dest-height", priv->viewport.h, nullptr); + gst_d3d12_overlay_compositor_update_viewport (priv->comp, &priv->viewport); + + priv->first_present = false; + } + + gst_d3d12_overlay_compositor_upload (priv->comp, priv->cached_buf); + + GstD3D12CommandAllocator *gst_ca; + if (!gst_d3d12_command_allocator_pool_acquire (priv->ca_pool, &gst_ca)) { + GST_ERROR_OBJECT (self, "Couldn't acquire command allocator"); + return FALSE; + } + + auto ca = gst_d3d12_command_allocator_get_handle (gst_ca); + auto hr = ca->Reset (); + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Couldn't reset command list"); + gst_d3d12_command_allocator_unref (gst_ca); + return FALSE; + } + + ComPtr < ID3D12GraphicsCommandList > cl; + if (!priv->cl) { + auto device_handle = gst_d3d12_device_get_device_handle (self->device); + hr = device_handle->CreateCommandList (0, D3D12_COMMAND_LIST_TYPE_DIRECT, + ca, nullptr, IID_PPV_ARGS (&cl)); + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Couldn't create command list"); + gst_d3d12_command_allocator_unref (gst_ca); + return FALSE; + } + + priv->cl = cl; + } else { + cl = priv->cl; + hr = cl->Reset (ca, nullptr); + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Couldn't reset command list"); + gst_d3d12_command_allocator_unref (gst_ca); + return FALSE; + } + } + + auto cur_idx = priv->swapchain->GetCurrentBackBufferIndex (); + auto backbuf = priv->backbuf[cur_idx]->backbuf; + + GstD3D12FenceData *fence_data; + gst_d3d12_fence_data_pool_acquire (priv->fence_data_pool, &fence_data); + gst_d3d12_fence_data_push (fence_data, FENCE_NOTIFY_MINI_OBJECT (gst_ca)); + + auto mem = (GstD3D12Memory *) gst_buffer_peek_memory (backbuf, 0); + auto backbuf_texture = gst_d3d12_memory_get_resource_handle (mem); + D3D12_RESOURCE_BARRIER barrier = + CD3DX12_RESOURCE_BARRIER::Transition (backbuf_texture, + D3D12_RESOURCE_STATE_COMMON, + D3D12_RESOURCE_STATE_RENDER_TARGET); + cl->ResourceBarrier (1, &barrier); + + if (priv->viewport.x != 0 || priv->viewport.y != 0 || + (guint) priv->viewport.w != priv->width || + (guint) priv->viewport.h != priv->height) { + auto rtv_heap = gst_d3d12_memory_get_render_target_view_heap (mem); + auto cpu_handle = GetCPUDescriptorHandleForHeapStart (rtv_heap); + cl->ClearRenderTargetView (cpu_handle, priv->border_color_val, 0, nullptr); + } + + if (!gst_d3d12_converter_convert_buffer (priv->conv, + priv->cached_buf, backbuf, fence_data, cl.Get (), TRUE)) { + GST_ERROR_OBJECT (self, "Couldn't build convert command"); + gst_d3d12_fence_data_unref (fence_data); + return FALSE; + } + + if (!gst_d3d12_overlay_compositor_draw (priv->comp, + backbuf, fence_data, cl.Get ())) { + GST_ERROR_OBJECT (self, "Couldn't build overlay command"); + gst_d3d12_fence_data_unref (fence_data); + return FALSE; + } + + barrier = + CD3DX12_RESOURCE_BARRIER::Transition (backbuf_texture, + D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_COMMON); + cl->ResourceBarrier (1, &barrier); + hr = cl->Close (); + + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Couldn't close command list"); + gst_d3d12_fence_data_unref (fence_data); + return FALSE; + } + + ID3D12CommandList *cmd_list[] = { cl.Get () }; + hr = gst_d3d12_command_queue_execute_command_lists (priv->cq, + 1, cmd_list, &priv->fence_val); + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Signal failed"); + gst_d3d12_fence_data_unref (fence_data); + return FALSE; + } + + gst_d3d12_command_queue_set_notify (priv->cq, priv->fence_val, + fence_data, (GDestroyNotify) gst_d3d12_fence_data_unref); + + return TRUE; +} + +static gboolean +gst_d3d12_swapchain_sink_set_buffer (GstD3D12SwapChainSink * self, + GstBuffer * buffer, gboolean is_prepare) +{ + auto priv = self->priv; + bool need_render = false; + bool update_converter = false; + + std::lock_guard < std::recursive_mutex > lk (priv->lock); + if (!priv->swapchain || priv->backbuf.empty ()) { + GST_ERROR_OBJECT (self, "Swapchain was not configured"); + return FALSE; + } + + if (is_prepare) { + if (priv->caps_updated) { + need_render = false; + update_converter = false; + } else { + need_render = true; + update_converter = false; + } + } else { + if (priv->caps_updated) { + need_render = true; + update_converter = true; + priv->caps_updated = false; + } else { + need_render = false; + update_converter = false; + } + } + + if (update_converter) { + gst_d3d12_command_queue_idle_for_swapchain (priv->cq, priv->fence_val); + + auto format = GST_VIDEO_INFO_FORMAT (&priv->info); + if (priv->convert_format != format) + gst_clear_object (&priv->conv); + + + priv->display_width = GST_VIDEO_SINK_WIDTH (self); + priv->display_height = GST_VIDEO_SINK_HEIGHT (self); + priv->convert_format = format; + priv->crop_rect = CD3DX12_BOX (0, 0, priv->info.width, priv->info.height); + priv->prev_crop_rect = priv->crop_rect; + priv->first_present = true; + gst_clear_buffer (&priv->cached_buf); + + if (!priv->conv) { + gst_structure_set (priv->convert_config, + GST_D3D12_CONVERTER_OPT_DEST_ALPHA_MODE, + GST_TYPE_D3D12_CONVERTER_ALPHA_MODE, + GST_VIDEO_INFO_HAS_ALPHA (&priv->info) ? + GST_D3D12_CONVERTER_ALPHA_MODE_PREMULTIPLIED : + GST_D3D12_CONVERTER_ALPHA_MODE_UNSPECIFIED, nullptr); + + priv->conv = gst_d3d12_converter_new (self->device, nullptr, &priv->info, + &priv->display_info, nullptr, nullptr, + gst_structure_copy (priv->convert_config)); + if (!priv->conv) { + GST_ERROR_OBJECT (self, "Couldn't create converter"); + return FALSE; + } + } else { + g_object_set (priv->conv, "src-x", 0, "src-y", 0, + "src-width", priv->info.width, "src-height", priv->info.height, + nullptr); + } + } + + if (!need_render) + return TRUE; + + auto mem = gst_buffer_peek_memory (buffer, 0); + if (!gst_is_d3d12_memory (mem)) { + GstBuffer *upload = nullptr; + gst_buffer_pool_acquire_buffer (priv->pool, &upload, nullptr); + if (!upload) { + GST_ERROR_OBJECT (self, "Couldn't allocate upload buffer"); + return FALSE; + } + + GstVideoFrame in_frame, out_frame; + if (!gst_video_frame_map (&in_frame, &priv->info, buffer, GST_MAP_READ)) { + GST_ERROR_OBJECT (self, "Couldn't map input frame"); + gst_buffer_unref (upload); + return FALSE; + } + + if (!gst_video_frame_map (&out_frame, &priv->info, upload, GST_MAP_WRITE)) { + GST_ERROR_OBJECT (self, "Couldn't map upload frame"); + gst_video_frame_unmap (&in_frame); + gst_buffer_unref (upload); + return FALSE; + } + + auto copy_ret = gst_video_frame_copy (&out_frame, &in_frame); + gst_video_frame_unmap (&out_frame); + gst_video_frame_unmap (&in_frame); + if (!copy_ret) { + GST_ERROR_OBJECT (self, "Couldn't copy frame"); + gst_buffer_unref (upload); + return FALSE; + } + + gst_buffer_foreach_meta (buffer, + (GstBufferForeachMetaFunc) gst_d3d12_swapchain_sink_foreach_meta, + upload); + + gst_clear_buffer (&priv->cached_buf); + priv->cached_buf = upload; + } else { + gst_buffer_replace (&priv->cached_buf, buffer); + } + + return gst_d3d12_swapchain_sink_render (self); +} + +static void +gst_d3d12_swapchain_sink_resize (GstD3D12SwapChainSink * self, guint width, + guint height) +{ + auto priv = self->priv; + + if (width == 0 || width > D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION) { + GST_WARNING_OBJECT (self, "Invalid width value %u", width); + return; + } + + if (height == 0 || height > D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION) { + GST_WARNING_OBJECT (self, "Invalid height value %u", height); + return; + } + + std::lock_guard < std::recursive_mutex > lk (priv->lock); + if (!gst_d3d12_swapchain_sink_resize_unlocked (self, width, height)) { + GST_ERROR_OBJECT (self, "Couldn't resize swapchain"); + return; + } + + if (priv->swapchain && priv->cached_buf && + gst_d3d12_swapchain_sink_render (self)) { + GST_ERROR_OBJECT (self, "resize %ux%u", width, height); + auto hr = priv->swapchain->Present (0, 0); + if (!gst_d3d12_result (hr, self->device)) + GST_ERROR_OBJECT (self, "Present failed"); + + gst_d3d12_command_queue_execute_command_lists (priv->cq, + 0, nullptr, &priv->fence_val); + } +} + +static gboolean +gst_d3d12_swapchain_sink_start (GstBaseSink * sink) +{ + auto self = GST_D3D12_SWAPCHAIN_SINK (sink); + auto priv = self->priv; + + GST_DEBUG_OBJECT (self, "Start"); + + std::lock_guard < std::recursive_mutex > lk (priv->lock); + if (!gst_d3d12_swapchain_sink_ensure_swapchain (self)) { + GST_ERROR_OBJECT (self, "Couldn't create swapchain"); + return FALSE; + } + + return TRUE; +} + +static gboolean +gst_d3d12_swapchain_sink_stop (GstBaseSink * sink) +{ + auto self = GST_D3D12_SWAPCHAIN_SINK (sink); + auto priv = self->priv; + + GST_DEBUG_OBJECT (self, "Stop"); + + priv->stop (); + + return TRUE; +} + +static gboolean +gst_d3d12_swapchain_sink_propose_allocation (GstBaseSink * sink, + GstQuery * query) +{ + auto self = GST_D3D12_SWAPCHAIN_SINK (sink); + GstCaps *caps; + GstBufferPool *pool = nullptr; + GstVideoInfo info; + guint size; + gboolean need_pool; + + if (!self->device) { + GST_WARNING_OBJECT (self, "No configured device"); + return FALSE; + } + + gst_query_parse_allocation (query, &caps, &need_pool); + if (!caps) { + GST_WARNING_OBJECT (self, "no caps specified"); + return FALSE; + } + + if (!gst_video_info_from_caps (&info, caps)) { + GST_ERROR_OBJECT (self, "Invalid caps %" GST_PTR_FORMAT, caps); + return FALSE; + } + + size = info.size; + + bool is_d3d12 = false; + auto features = gst_caps_get_features (caps, 0); + if (gst_caps_features_contains (features, + GST_CAPS_FEATURE_MEMORY_D3D12_MEMORY)) { + is_d3d12 = true; + GST_DEBUG_OBJECT (self, "upstream support d3d12 memory"); + } + + if (need_pool) { + if (is_d3d12) + pool = gst_d3d12_buffer_pool_new (self->device); + else + pool = gst_video_buffer_pool_new (); + + auto 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); + } + + gst_buffer_pool_config_set_params (config, caps, size, 2, 0); + + if (!gst_buffer_pool_set_config (pool, config)) { + GST_ERROR_OBJECT (pool, "Couldn't set config"); + gst_object_unref (pool); + + return FALSE; + } + + if (is_d3d12) { + 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, 2, 0); + gst_clear_object (&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); + if (is_d3d12) { + gst_query_add_allocation_meta (query, + GST_VIDEO_CROP_META_API_TYPE, nullptr); + } + + return TRUE; +} + +static gboolean +gst_d3d12_swapchain_sink_query (GstBaseSink * sink, GstQuery * query) +{ + auto self = GST_D3D12_SWAPCHAIN_SINK (sink); + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_CONTEXT: + if (gst_d3d12_handle_context_query (GST_ELEMENT (self), query, + self->device)) { + return TRUE; + } + break; + default: + break; + } + + return GST_BASE_SINK_CLASS (parent_class)->query (sink, query); +} + +static GstFlowReturn +gst_d3d12_swapchain_sink_prepare (GstBaseSink * sink, GstBuffer * buffer) +{ + auto self = GST_D3D12_SWAPCHAIN_SINK (sink); + auto priv = self->priv; + + std::lock_guard < std::recursive_mutex > lk (priv->lock); + if (!gst_d3d12_swapchain_sink_set_buffer (self, buffer, TRUE)) { + GST_ERROR_OBJECT (self, "Set buffer failed"); + return GST_FLOW_ERROR; + } + + return GST_FLOW_OK; +} + +static GstFlowReturn +gst_d3d12_swapchain_sink_show_frame (GstVideoSink * sink, GstBuffer * buf) +{ + auto self = GST_D3D12_SWAPCHAIN_SINK (sink); + auto priv = self->priv; + + std::lock_guard < std::recursive_mutex > lk (priv->lock); + if (!gst_d3d12_swapchain_sink_set_buffer (self, buf, FALSE)) { + GST_ERROR_OBJECT (self, "Set buffer failed"); + return GST_FLOW_ERROR; + } + + auto hr = priv->swapchain->Present (0, 0); + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Present failed"); + return GST_FLOW_ERROR; + } + + /* To update fence value */ + gst_d3d12_command_queue_execute_command_lists (priv->cq, + 0, nullptr, &priv->fence_val); + + return GST_FLOW_OK; +} diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12swapchainsink.h b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12swapchainsink.h new file mode 100644 index 0000000000..b1381f2cfc --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12swapchainsink.h @@ -0,0 +1,33 @@ +/* GStreamer + * Copyright (C) 2024 Seungha Yang + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_D3D12_SWAPCHAIN_SINK (gst_d3d12_swapchain_sink_get_type()) +G_DECLARE_FINAL_TYPE (GstD3D12SwapChainSink, gst_d3d12_swapchain_sink, + GST, D3D12_SWAPCHAIN_SINK, GstVideoSink) + +G_END_DECLS + diff --git a/subprojects/gst-plugins-bad/sys/d3d12/meson.build b/subprojects/gst-plugins-bad/sys/d3d12/meson.build index d931ea250b..bf0e09e46e 100644 --- a/subprojects/gst-plugins-bad/sys/d3d12/meson.build +++ b/subprojects/gst-plugins-bad/sys/d3d12/meson.build @@ -23,6 +23,7 @@ d3d12_sources = [ 'gstd3d12screencapture.cpp', 'gstd3d12screencapturedevice.cpp', 'gstd3d12screencapturesrc.cpp', + 'gstd3d12swapchainsink.cpp', 'gstd3d12testsrc.cpp', 'gstd3d12videosink.cpp', 'gstd3d12vp8dec.cpp', diff --git a/subprojects/gst-plugins-bad/sys/d3d12/plugin.cpp b/subprojects/gst-plugins-bad/sys/d3d12/plugin.cpp index 8e69b5dd9f..00299adf87 100644 --- a/subprojects/gst-plugins-bad/sys/d3d12/plugin.cpp +++ b/subprojects/gst-plugins-bad/sys/d3d12/plugin.cpp @@ -48,6 +48,7 @@ #include "gstd3d12ipcclient.h" #include "gstd3d12ipcsrc.h" #include "gstd3d12ipcsink.h" +#include "gstd3d12swapchainsink.h" #include #include #include @@ -178,6 +179,8 @@ plugin_init (GstPlugin * plugin) "d3d12ipcsrc", GST_RANK_NONE, GST_TYPE_D3D12_IPC_SRC); gst_element_register (plugin, "d3d12ipcsink", GST_RANK_NONE, GST_TYPE_D3D12_IPC_SINK); + gst_element_register (plugin, + "d3d12swapchainsink", GST_RANK_NONE, GST_TYPE_D3D12_SWAPCHAIN_SINK); g_object_set_data_full (G_OBJECT (plugin), "plugin-d3d12-shutdown", (gpointer) "shutdown-data", diff --git a/subprojects/gst-plugins-bad/tests/examples/d3d12/d3d12swapchainsink-win32.cpp b/subprojects/gst-plugins-bad/tests/examples/d3d12/d3d12swapchainsink-win32.cpp new file mode 100644 index 0000000000..a134accabd --- /dev/null +++ b/subprojects/gst-plugins-bad/tests/examples/d3d12/d3d12swapchainsink-win32.cpp @@ -0,0 +1,448 @@ +/* GStreamer + * Copyright (C) 2024 Seungha Yang + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace Microsoft::WRL; + +static GMainLoop *loop_ = nullptr; +static HWND hwnd_ = nullptr; + +struct GpuResource +{ + ComPtr dcomp_device; + ComPtr target; + ComPtr visual; + ComPtr bg_surface; + ComPtr swapchain_visual; + ComPtr device11; + ComPtr context11; +}; + +struct AppData +{ + GstElement *pipeline = nullptr; + std::shared_ptr resource; +}; + +#define APP_DATA_PROP_NAME L"EXAMPLE-APP-DATA" + +static LRESULT CALLBACK +window_proc (HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) +{ + switch (message) { + case WM_NCCREATE: + { + LPCREATESTRUCTW lpcs = (LPCREATESTRUCTW) lparam; + auto data = (AppData *) lpcs->lpCreateParams; + SetPropW (hwnd, APP_DATA_PROP_NAME, data); + break; + } + case WM_DESTROY: + gst_println ("Destroy window"); + if (loop_) + g_main_loop_quit (loop_); + break; + case WM_SIZE: + { + auto data = (AppData *) GetPropW (hwnd, APP_DATA_PROP_NAME); + if (!data) + break; + + auto resource = data->resource; + if (!resource) + break; + + RECT rect = { }; + GetClientRect (hwnd, &rect); + gint width = (rect.right - rect.left); + gint height = (rect.bottom - rect.top); + + if (width > 0 && height > 0) { + POINT offset; + ComPtr texture; + ComPtr rtv; + auto hr = resource->bg_surface->Resize (width, height); + if (SUCCEEDED (hr)) { + hr = resource->bg_surface->BeginDraw (nullptr, + IID_PPV_ARGS (&texture), &offset); + } + + if (SUCCEEDED (hr)) { + hr = resource->device11->CreateRenderTargetView (texture.Get (), nullptr, + &rtv); + } + + if (SUCCEEDED (hr)) { + FLOAT bg_color[] = { 0.5, 0.5, 0.5, 0.5 }; + resource->context11->ClearRenderTargetView (rtv.Get (), bg_color); + hr = resource->bg_surface->EndDraw (); + } + + if (SUCCEEDED (hr)) { + if (width > 320) { + FLOAT offset_x = ((FLOAT) (width - 320)) / 2.0; + resource->swapchain_visual->SetOffsetX (offset_x); + } else { + resource->swapchain_visual->SetOffsetX (0.0); + } + + if (height > 240) { + FLOAT offset_y = ((FLOAT) (height - 240)) / 2.0; + resource->swapchain_visual->SetOffsetY (offset_y); + } else { + resource->swapchain_visual->SetOffsetY (0.0); + } + + resource->dcomp_device->Commit (); + } + } + break; + } + default: + break; + } + + return DefWindowProcW (hwnd, message, wparam, lparam); +} + +static gboolean +bus_msg (GstBus * bus, GstMessage * msg, AppData * data) +{ + switch (GST_MESSAGE_TYPE (msg)) { + case GST_MESSAGE_ERROR: + { + GError *err; + gchar *dbg; + + gst_message_parse_error (msg, &err, &dbg); + gst_printerrln ("ERROR %s", err->message); + if (dbg != nullptr) + gst_printerrln ("ERROR debug information: %s", dbg); + g_clear_error (&err); + g_free (dbg); + + g_main_loop_quit (loop_); + break; + } + case GST_MESSAGE_EOS: + { + gst_println ("Got EOS"); + g_main_loop_quit (loop_); + break; + } + default: + break; + } + + return TRUE; +} + +static gboolean +msg_cb (GIOChannel * source, GIOCondition condition, gpointer data) +{ + MSG msg; + + if (!PeekMessageW (&msg, nullptr, 0, 0, PM_REMOVE)) + return G_SOURCE_CONTINUE; + + TranslateMessage (&msg); + DispatchMessage (&msg); + + return G_SOURCE_CONTINUE; +} + +int +main (int argc, char ** argv) +{ + GIOChannel *msg_io_channel = nullptr; + AppData app_data = { }; + HRESULT hr; + gchar *uri = nullptr; + GOptionEntry options[] = { + {"uri", 0, 0, G_OPTION_ARG_STRING, &uri, "URI to play"}, + {nullptr} + }; + + auto opt_ctx = g_option_context_new ("D3D12 swapchainsink"); + g_option_context_add_main_entries (opt_ctx, options, nullptr); + g_option_context_set_help_enabled (opt_ctx, TRUE); + g_option_context_add_group (opt_ctx, gst_init_get_option_group ()); + + if (!g_option_context_parse (opt_ctx, &argc, &argv, nullptr)) { + gst_printerrln ("option parsing failed"); + return 1; + } + + loop_ = g_main_loop_new (nullptr, FALSE); + + /* Creates pipeline */ + GstElement *sink; + if (uri) { + app_data.pipeline = gst_element_factory_make ("playbin3", nullptr); + if (!app_data.pipeline) { + gst_printerrln ("Couldn't create pipeline"); + return 1; + } + + sink = gst_element_factory_make ("d3d12swapchainsink", nullptr); + if (!sink) { + gst_printerrln ("Couldn't create sink"); + return 1; + } + + g_object_set (app_data.pipeline, "video-sink", sink, "uri", uri, nullptr); + } else { + app_data.pipeline = gst_parse_launch ("d3d12testsrc ! " + "video/x-raw(memory:D3D12Memory),format=RGBA,width=240,height=240 ! " + "dwritetimeoverlay font-size=50 ! queue ! d3d12swapchainsink name=sink", + nullptr); + + if (!app_data.pipeline) { + gst_printerrln ("Couldn't create pipeline"); + return 1; + } + + sink = gst_bin_get_by_name (GST_BIN (app_data.pipeline), "sink"); + g_assert (sink); + } + + gst_bus_add_watch (GST_ELEMENT_BUS (app_data.pipeline), (GstBusFunc) bus_msg, + &app_data); + + /* Set swapchain resolution and border color */ + g_signal_emit_by_name (sink, "resize", 320, 240); + + guint64 border_color = 0; + /* alpha */ + border_color |= ((guint64) (G_MAXUINT16 / 2)) << 48; + /* red */ + border_color |= ((guint64) (G_MAXUINT16 / 2)) << 32; + g_object_set (sink, "border-color", border_color, nullptr); + + /* Gets swapchain handle. This swapchain will be bound to a dcomp visual node */ + IUnknown *swapchain = nullptr; + g_object_get (sink, "swapchain", &swapchain, nullptr); + if (!swapchain) { + gst_printerrln ("Couldn't get swapchain"); + return 1; + } + + /* playbin will take floating refcount */ + if (!uri) + gst_object_unref (sink); + + /* Creates d3d11 device to initialize dcomp device. + * Note that d3d11 (or d2d) device will not be required if swapchain is + * the only visual node (i.e., root node) which needs to be composed. + * In that case, an application can pass nullptr device to + * DCompositionCreateDevice2() */ + auto resource = std::make_shared (); + ComPtr factory; + ComPtr adapter; + + hr = CreateDXGIFactory1 (IID_PPV_ARGS (&factory)); + if (FAILED (hr)) { + gst_printerrln ("CreateDXGIFactory1 failed"); + return 1; + } + + hr = factory->EnumAdapters (0, &adapter); + if (FAILED (hr)) { + gst_printerrln ("EnumAdapters failed"); + return 1; + } + + static const D3D_FEATURE_LEVEL feature_levels[] = { + D3D_FEATURE_LEVEL_11_1, + }; + hr = D3D11CreateDevice (adapter.Get (), D3D_DRIVER_TYPE_UNKNOWN, nullptr, + D3D11_CREATE_DEVICE_BGRA_SUPPORT, feature_levels, 1, D3D11_SDK_VERSION, + &resource->device11, nullptr, &resource->context11); + if (FAILED (hr)) { + gst_printerrln ("D3D11CreateDevice failed"); + return 1; + } + + /* Prepare main window */ + WNDCLASSEXW wc = { }; + RECT wr = { 0, 0, 640, 480 }; + HINSTANCE hinstance = GetModuleHandle (nullptr); + wc.cbSize = sizeof (WNDCLASSEXW); + wc.lpfnWndProc = (WNDPROC) window_proc; + wc.hInstance = hinstance; + wc.hIcon = LoadIcon (nullptr, IDI_WINLOGO); + wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; + wc.hCursor = LoadCursor (nullptr, IDC_ARROW); + wc.hbrBackground = (HBRUSH) GetStockObject (BLACK_BRUSH); + wc.lpszClassName = L"GstD3D12SwapChainSinkExample"; + + RegisterClassExW (&wc); + AdjustWindowRect (&wr, WS_OVERLAPPEDWINDOW, FALSE); + + hwnd_ = CreateWindowExW (WS_EX_NOREDIRECTIONBITMAP, wc.lpszClassName, + L"D3D12SwapChainSink Example - Win32", + WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW | WS_VISIBLE, + CW_USEDEFAULT, CW_USEDEFAULT, + wr.right - wr.left, wr.bottom - wr.top, (HWND) nullptr, (HMENU) nullptr, + hinstance, &app_data); + + msg_io_channel = g_io_channel_win32_new_messages (0); + g_io_add_watch (msg_io_channel, G_IO_IN, msg_cb, nullptr); + + /* Create DComp resources */ + hr = DCompositionCreateDevice2 (resource->device11.Get (), + IID_PPV_ARGS (&resource->dcomp_device)); + if (FAILED (hr)) { + gst_printerrln ("Couldn't create composition device"); + return 1; + } + + hr = resource->dcomp_device->CreateTargetForHwnd (hwnd_, TRUE, + &resource->target); + if (FAILED (hr)) { + gst_printerrln ("CreateTargetForHwnd failed"); + return 1; + } + + hr = resource->dcomp_device->CreateVisual (&resource->visual); + if (FAILED (hr)) { + gst_printerrln ("CreateVisual failed"); + return 1; + } + + hr = resource->target->SetRoot (resource->visual.Get ()); + if (FAILED (hr)) { + gst_printerrln ("SetRoot failed"); + return 1; + } + + /* Create background visual, and clear color using d3d11 API */ + hr = resource->dcomp_device->CreateVirtualSurface (640, 480, + DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ALPHA_MODE_PREMULTIPLIED, + &resource->bg_surface); + if (FAILED (hr)) { + gst_printerrln ("CreateVirtualSurface failed"); + return 1; + } + + hr = resource->visual->SetContent (resource->bg_surface.Get ()); + if (FAILED (hr)) { + gst_printerrln ("SetContent failed"); + return 1; + } + + { + POINT offset; + ComPtr texture; + ComPtr rtv; + hr = resource->bg_surface->BeginDraw (nullptr, IID_PPV_ARGS (&texture), + &offset); + if (FAILED (hr)) { + gst_printerrln ("BeginDraw failed"); + return 1; + } + + hr = resource->device11->CreateRenderTargetView (texture.Get (), + nullptr, &rtv); + if (FAILED (hr)) { + gst_printerrln ("CreateRenderTargetView failed"); + return 1; + } + + /* Draw semi-transparent background */ + FLOAT bg_color[] = { 0.5, 0.5, 0.5, 0.5 }; + resource->context11->ClearRenderTargetView (rtv.Get (), bg_color); + hr = resource->bg_surface->EndDraw (); + if (FAILED (hr)) { + gst_printerrln ("EndDraw failed"); + return 1; + } + } + + hr = resource->dcomp_device->CreateVisual (&resource->swapchain_visual); + if (FAILED (hr)) { + gst_printerrln ("CreateVisual failed"); + return 1; + } + + hr = resource->visual->AddVisual (resource->swapchain_visual.Get (), TRUE, nullptr); + if (FAILED (hr)) { + gst_printerrln ("AddVisual failed"); + return 1; + } + + hr = resource->swapchain_visual->SetOffsetX (160.0); + if (FAILED (hr)) { + gst_printerrln ("SetOffsetX failed"); + return 1; + } + + hr = resource->swapchain_visual->SetOffsetY (120.0); + if (FAILED (hr)) { + gst_printerrln ("SetOffsetY failed"); + return 1; + } + + hr = resource->swapchain_visual->SetContent (swapchain); + if (FAILED (hr)) { + gst_printerrln ("SetContent failed"); + return 1; + } + + hr = resource->dcomp_device->Commit (); + if (FAILED (hr)) { + gst_printerrln ("Commit failed"); + return 1; + } + + app_data.resource = std::move (resource); + + gst_element_set_state (app_data.pipeline, GST_STATE_PLAYING); + g_main_loop_run (loop_); + + gst_element_set_state (app_data.pipeline, GST_STATE_NULL); + gst_bus_remove_watch (GST_ELEMENT_BUS (app_data.pipeline)); + + app_data.resource = nullptr; + gst_object_unref (app_data.pipeline); + + if (hwnd_) + DestroyWindow (hwnd_); + + g_io_channel_unref (msg_io_channel); + g_main_loop_unref (loop_); + g_free (uri); + + gst_deinit (); + + return 0; +} diff --git a/subprojects/gst-plugins-bad/tests/examples/d3d12/d3d12swapchainsink-winrt.cpp b/subprojects/gst-plugins-bad/tests/examples/d3d12/d3d12swapchainsink-winrt.cpp new file mode 100644 index 0000000000..e58b57b7be --- /dev/null +++ b/subprojects/gst-plugins-bad/tests/examples/d3d12/d3d12swapchainsink-winrt.cpp @@ -0,0 +1,333 @@ +/* GStreamer + * Copyright (C) 2024 Seungha Yang + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Microsoft::WRL; +using namespace ABI::Windows::System; +using namespace ABI::Windows::UI::Composition; +using namespace ABI::Windows::UI::Composition::Desktop; +using namespace ABI::Windows::Foundation; + +static LRESULT CALLBACK +window_proc (HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) +{ + switch (message) { + case WM_DESTROY: + PostQuitMessage(0); + return 0; + default: + break; + } + + return DefWindowProcW (hwnd, message, wparam, lparam); +} + +static void +app_main (void) +{ + auto pipeline = gst_parse_launch ("d3d12testsrc ! " + "video/x-raw(memory:D3D12Memory),format=RGBA,width=240,height=240 ! " + "dwritetimeoverlay font-size=50 ! queue ! d3d12swapchainsink name=sink", + nullptr); + if (!pipeline) { + gst_printerrln ("Couldn't create pipeline"); + return; + } + + auto sink = gst_bin_get_by_name (GST_BIN (pipeline), "sink"); + g_assert (sink); + + /* Set swapchain resolution and border color */ + g_signal_emit_by_name (sink, "resize", 320, 240); + + guint64 border_color = 0; + /* alpha */ + border_color |= ((guint64) (G_MAXUINT16 / 2)) << 48; + /* red */ + border_color |= ((guint64) (G_MAXUINT16 / 2)) << 32; + g_object_set (sink, "border-color", border_color, nullptr); + + IUnknown *swapchain = nullptr; + g_object_get (sink, "swapchain", &swapchain, nullptr); + if (!swapchain) { + gst_printerrln ("Couldn't get swapchain"); + return; + } + gst_object_unref (sink); + + /* Prepare main window */ + WNDCLASSEXW wc = { }; + RECT wr = { 0, 0, 640, 480 }; + HINSTANCE hinstance = GetModuleHandle (nullptr); + wc.cbSize = sizeof (WNDCLASSEXW); + wc.lpfnWndProc = (WNDPROC) window_proc; + wc.hInstance = hinstance; + wc.hIcon = LoadIcon (nullptr, IDI_WINLOGO); + wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; + wc.hCursor = LoadCursor (nullptr, IDC_ARROW); + wc.hbrBackground = (HBRUSH) GetStockObject (BLACK_BRUSH); + wc.lpszClassName = L"GstD3D12SwapChainSinkExample"; + + RegisterClassExW (&wc); + AdjustWindowRect (&wr, WS_OVERLAPPEDWINDOW, FALSE); + + HWND hwnd = CreateWindowExW (WS_EX_NOREDIRECTIONBITMAP, wc.lpszClassName, + L"D3D12SwapChainSink Example - WinRT", + WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW | WS_VISIBLE, + CW_USEDEFAULT, CW_USEDEFAULT, + wr.right - wr.left, wr.bottom - wr.top, (HWND) nullptr, (HMENU) nullptr, + hinstance, nullptr); + + /* compositor requires dispatcher queue. Creates one for the current main thread */ + DispatcherQueueOptions queue_opt = { }; + queue_opt.dwSize = sizeof (DispatcherQueueOptions); + queue_opt.threadType = DQTYPE_THREAD_CURRENT; + queue_opt.apartmentType = DQTAT_COM_NONE; + + ComPtr < IDispatcherQueueController > queue_ctrl; + HRESULT hr = CreateDispatcherQueueController (queue_opt, &queue_ctrl); + g_assert (SUCCEEDED (hr)); + + ComPtr dqueue; + hr = queue_ctrl->get_DispatcherQueue (&dqueue); + g_assert (SUCCEEDED (hr)); + + /* Creates compositor */ + ComPtr insp; + HSTRING class_id_hstring; + WindowsCreateString (RuntimeClass_Windows_UI_Composition_Compositor, + wcslen (RuntimeClass_Windows_UI_Composition_Compositor), &class_id_hstring); + hr = RoActivateInstance (class_id_hstring, &insp); + WindowsDeleteString (class_id_hstring); + g_assert (SUCCEEDED (hr)); + + ComPtr compositor; + hr = insp.As (&compositor); + g_assert (SUCCEEDED (hr)); + + ComPtr compositor_desktop_interop; + hr = compositor.As (&compositor_desktop_interop); + g_assert (SUCCEEDED (hr)); + + ComPtr compositor_interop; + hr = compositor.As (&compositor_interop); + g_assert (SUCCEEDED (hr)); + + /* Creates compositor target for the main HWND */ + ComPtr desktop_target; + hr = compositor_desktop_interop->CreateDesktopWindowTarget (hwnd, + TRUE, &desktop_target); + g_assert (SUCCEEDED (hr)); + + ComPtr target; + hr = desktop_target.As (&target); + g_assert (SUCCEEDED (hr)); + + /* Creates container visual and put background static color visual */ + ComPtr root; + hr = compositor->CreateContainerVisual (&root); + g_assert (SUCCEEDED (hr)); + + ComPtr root_visual; + hr = root.As (&root_visual); + g_assert (SUCCEEDED (hr)); + + ComPtr root_visual2; + hr = root.As (&root_visual2); + g_assert (SUCCEEDED (hr)); + + Numerics::Vector2 vec2 = { 1.0, 1.0 }; + hr = root_visual2->put_RelativeSizeAdjustment (vec2); + g_assert (SUCCEEDED (hr)); + + hr = target->put_Root (root_visual.Get ()); + g_assert (SUCCEEDED (hr)); + + ABI::Windows::UI::Color bg_color = { }; + bg_color.R = 128; + bg_color.G = 128; + bg_color.B = 128; + bg_color.A = 128; + + ComPtr bg_color_brush; + hr = compositor->CreateColorBrushWithColor (bg_color, &bg_color_brush); + g_assert (SUCCEEDED (hr)); + + ComPtr bg_brush; + hr = bg_color_brush.As (&bg_brush); + g_assert (SUCCEEDED (hr)); + + ComPtr bg_sprite_visual; + hr = compositor->CreateSpriteVisual (&bg_sprite_visual); + g_assert (SUCCEEDED (hr)); + + hr = bg_sprite_visual->put_Brush (bg_brush.Get ()); + g_assert (SUCCEEDED (hr)); + + ComPtr bg_visual; + hr = bg_sprite_visual.As (&bg_visual); + g_assert (SUCCEEDED (hr)); + + ComPtr bg_visual2; + hr = bg_sprite_visual.As (&bg_visual2); + g_assert (SUCCEEDED (hr)); + + hr = bg_visual2->put_RelativeSizeAdjustment (vec2); + g_assert (SUCCEEDED (hr)); + + ComPtr children; + hr = root->get_Children (&children); + g_assert (SUCCEEDED (hr)); + + hr = children->InsertAtBottom (bg_visual.Get ()); + g_assert (SUCCEEDED (hr)); + + /* Creates swapchain visual */ + ComPtr swapchain_surface; + hr = compositor_interop->CreateCompositionSurfaceForSwapChain (swapchain, + &swapchain_surface); + g_assert (SUCCEEDED (hr)); + + ComPtr swapchain_surface_brush; + hr = compositor->CreateSurfaceBrushWithSurface (swapchain_surface.Get (), + &swapchain_surface_brush); + g_assert (SUCCEEDED (hr)); + + ComPtr swapchain_brush; + hr = swapchain_surface_brush.As (&swapchain_brush); + g_assert (SUCCEEDED (hr)); + + /* Place swapchain visual at center */ + hr = swapchain_surface_brush->put_HorizontalAlignmentRatio (0.5); + g_assert (SUCCEEDED (hr)); + + hr = swapchain_surface_brush->put_VerticalAlignmentRatio (0.5); + g_assert (SUCCEEDED (hr)); + + /* Scale swapchain visual with aspect-ratio preserved */ + hr = swapchain_surface_brush->put_Stretch (CompositionStretch_Uniform); + g_assert (SUCCEEDED (hr)); + + ComPtr swapchain_sprite_visual; + hr = compositor->CreateSpriteVisual (&swapchain_sprite_visual); + g_assert (SUCCEEDED (hr)); + + ComPtr swapchain_visual; + hr = swapchain_sprite_visual.As (&swapchain_visual); + g_assert (SUCCEEDED (hr)); + + vec2.X = 0.5; + vec2.Y = 0.5; + hr = swapchain_visual->put_AnchorPoint (vec2); + g_assert (SUCCEEDED (hr)); + + ComPtr swapchain_visual2; + hr = swapchain_sprite_visual.As (&swapchain_visual2); + g_assert (SUCCEEDED (hr)); + + hr = swapchain_visual2->put_RelativeSizeAdjustment (vec2); + g_assert (SUCCEEDED (hr)); + + Numerics::Vector3 vec3 = { 0.5, 0.5, 0.0 }; + hr = swapchain_visual2->put_RelativeOffsetAdjustment (vec3); + g_assert (SUCCEEDED (hr)); + + hr = swapchain_sprite_visual->put_Brush (swapchain_brush.Get ()); + g_assert (SUCCEEDED (hr)); + + hr = children->InsertAtTop (swapchain_visual.Get ()); + g_assert (SUCCEEDED (hr)); + + /* Compositor and visual tree are configured, run pipeline */ + gst_element_set_state (pipeline, GST_STATE_PLAYING); + auto bus = gst_element_get_bus (pipeline); + + MSG msg = { }; + while (msg.message != WM_QUIT) { + if (PeekMessage (&msg, nullptr, 0, 0, PM_REMOVE)) { + TranslateMessage (&msg); + DispatchMessage (&msg); + } + + auto gst_msg = gst_bus_pop (bus); + if (gst_msg) { + switch (GST_MESSAGE_TYPE (gst_msg)) { + case GST_MESSAGE_ERROR: + { + GError *err; + gchar *dbg; + + gst_message_parse_error (gst_msg, &err, &dbg); + gst_printerrln ("ERROR %s", err->message); + if (dbg != nullptr) + gst_printerrln ("ERROR debug information: %s", dbg); + g_clear_error (&err); + g_free (dbg); + PostQuitMessage (0); + break; + } + case GST_MESSAGE_EOS: + { + gst_println ("Got EOS"); + PostQuitMessage (0); + break; + } + default: + break; + } + + gst_message_unref (gst_msg); + } + } + + gst_object_unref (bus); + gst_element_set_state (pipeline, GST_STATE_NULL); + gst_object_unref (pipeline); +} + +int +main (int argc, char ** argv) +{ + gst_init (nullptr, nullptr); + + RoInitialize (RO_INIT_SINGLETHREADED); + app_main (); + RoUninitialize (); + + gst_deinit (); + + return 0; +} diff --git a/subprojects/gst-plugins-bad/tests/examples/d3d12/meson.build b/subprojects/gst-plugins-bad/tests/examples/d3d12/meson.build index 1f51d2a702..1a0b19bc73 100644 --- a/subprojects/gst-plugins-bad/tests/examples/d3d12/meson.build +++ b/subprojects/gst-plugins-bad/tests/examples/d3d12/meson.build @@ -5,7 +5,15 @@ endif have_d2d_h = cc.has_header('d2d1_3.h') have_dwrite_h = cc.has_header('dwrite.h') have_d3d12video_h = cc.has_header('d3d12video.h') +have_dcomp_h = cc.has_header('dcomp.h') +have_d3d11_h = cc.has_header('d3d11.h') +have_dxgi_h = cc.has_header('dxgi.h') dwrite_dep = cc.find_library('dwrite', required: false) +dcomp_dep = cc.find_library('dcomp', required: false) +d3d11_dep = cc.find_library('d3d11', required: false) +dxgi_dep = cc.find_library('dxgi', required: false) +runtimeobject_dep = cc.find_library('runtimeobject', required: false) +coremessaging_lib = cc.find_library('coremessaging', required: false) executable('d3d12enc-dynamic-reconfigure', ['d3d12enc-dynamic-reconfigure.c', '../key-handler.c'], @@ -34,3 +42,42 @@ if gstd3d12_dep.found() ) endif endif + +if cc.get_id() == 'msvc' and have_dcomp_h and dcomp_dep.found() and \ + have_d3d11_h and d3d11_dep.found() and have_dxgi_h and dxgi_dep.found() + executable('d3d12swapchainsink-win32', 'd3d12swapchainsink-win32.cpp', + c_args : gst_plugins_bad_args + ['-DGST_USE_UNSTABLE_API'], + cpp_args : gst_plugins_bad_args + ['-DGST_USE_UNSTABLE_API'], + include_directories : [configinc, libsinc], + dependencies: [gst_dep, gstvideo_dep, dcomp_dep, d3d11_dep, dxgi_dep], + install: false, + ) +endif + +have_winrt_comp_headers = true +winrt_comp_headers = [ + 'winstring.h', + 'roapi.h', + 'dispatcherqueue.h', + 'windows.system.h', + 'windows.ui.composition.h', + 'windows.ui.composition.interop.h', + 'windows.ui.composition.desktop.h', +] + +foreach h: winrt_comp_headers + if not cc.has_header(h) + have_winrt_comp_headers = false + endif +endforeach + +if cc.get_id() == 'msvc' and have_winrt_comp_headers and \ + runtimeobject_dep.found() and coremessaging_lib.found() + executable('d3d12swapchainsink-winrt', 'd3d12swapchainsink-winrt.cpp', + c_args : gst_plugins_bad_args + ['-DGST_USE_UNSTABLE_API'], + cpp_args : gst_plugins_bad_args + ['-DGST_USE_UNSTABLE_API'], + include_directories : [configinc, libsinc], + dependencies: [gst_dep, gstvideo_dep, runtimeobject_dep, coremessaging_lib], + install: false, + ) +endif