diff --git a/.indent_cpp_list b/.indent_cpp_list index a2024e3de8..45d57cd500 100644 --- a/.indent_cpp_list +++ b/.indent_cpp_list @@ -1,3 +1,4 @@ +subprojects/gst-plugins-bad/ext/qt6d3d11 subprojects/gst-plugins-bad/gst-libs/gst/cuda subprojects/gst-plugins-bad/gst-libs/gst/d3d11 subprojects/gst-plugins-bad/gst-libs/gst/va diff --git a/subprojects/gst-plugins-bad/ext/meson.build b/subprojects/gst-plugins-bad/ext/meson.build index f4fbbaeb67..169aa059cb 100644 --- a/subprojects/gst-plugins-bad/ext/meson.build +++ b/subprojects/gst-plugins-bad/ext/meson.build @@ -48,6 +48,7 @@ subdir('openmpt') subdir('openni2') subdir('opus') subdir('qroverlay') +subdir('qt6d3d11') subdir('resindvd') subdir('rsvg') subdir('rtmp') diff --git a/subprojects/gst-plugins-bad/ext/qt6d3d11/gstqml6d3d11sink.cpp b/subprojects/gst-plugins-bad/ext/qt6d3d11/gstqml6d3d11sink.cpp new file mode 100644 index 0000000000..e3e7c53fbc --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/qt6d3d11/gstqml6d3d11sink.cpp @@ -0,0 +1,664 @@ +/* GStreamer + * Copyright (C) 2023 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 "gstqml6d3d11sink.h" +#include "gstqt6d3d11videoitem.h" +#include + +GST_DEBUG_CATEGORY_STATIC (gst_qml6_d3d11_sink_debug); +#define GST_CAT_DEFAULT gst_qml6_d3d11_sink_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_D3D11_MEMORY, GST_D3D11_SINK_FORMATS) "; " + GST_VIDEO_CAPS_MAKE (GST_D3D11_SINK_FORMATS))); + +enum +{ + PROP_0, + PROP_WIDGET, + PROP_FORCE_ASPECT_RATIO, +}; + +#define DEFAULT_FORCE_ASPECT_RATIO TRUE + +struct GstQml6D3D11SinkPrivate +{ + QSharedPointer < GstQt6D3D11VideoItemProxy > widget; + + std::recursive_mutex lock; + + GstVideoInfo info; + + GstD3D11Device *device = nullptr; + gint64 adapter_luid = 0; + GstBuffer *prepared_buffer = nullptr; + GstBufferPool *pool = nullptr; + + gboolean force_aspect_ratio = DEFAULT_FORCE_ASPECT_RATIO; +}; + +struct _GstQml6D3D11Sink +{ + GstVideoSink parent; + + GstQml6D3D11SinkPrivate *priv; +}; + +static void +gst_qml6_d3d11_sink_navigation_init (GstNavigationInterface * iface); +static void gst_qml6_d3d11_sink_dispose (GObject * object); +static void gst_qml6_d3d11_sink_finalize (GObject * object); +static void gst_qml6_d3d11_sink_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec); +static void gst_qml6_d3d11_sink_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec); +static void gst_qml6_d3d11_sink_set_context (GstElement * element, + GstContext * context); +static gboolean gst_qml6_d3d11_sink_set_caps (GstBaseSink * sink, + GstCaps * caps); +static gboolean gst_qml6_d3d11_sink_start (GstBaseSink * sink); +static gboolean gst_qml6_d3d11_sink_stop (GstBaseSink * sink); +static gboolean gst_qml6_d3d11_sink_propose_allocation (GstBaseSink * sink, + GstQuery * query); +static gboolean gst_qml6_d3d11_sink_query (GstBaseSink * sink, + GstQuery * query); +static GstFlowReturn gst_qml6_d3d11_sink_prepare (GstBaseSink * sink, + GstBuffer * buf); +static GstFlowReturn gst_qml6_d3d11_sink_show_frame (GstVideoSink * sink, + GstBuffer * buf); + +#define gst_qml6_d3d11_sink_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GstQml6D3D11Sink, gst_qml6_d3d11_sink, + GST_TYPE_VIDEO_SINK, GST_DEBUG_CATEGORY_INIT (gst_qml6_d3d11_sink_debug, + "qml6d3d11sink", 0, "qml6d3d11sink"); + G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION, + gst_qml6_d3d11_sink_navigation_init); + gst_qt6_d3d11_video_item_init_once ()); + +static void +gst_qml6_d3d11_sink_class_init (GstQml6D3D11SinkClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + GstBaseSinkClass *sink_class = GST_BASE_SINK_CLASS (klass); + GstVideoSinkClass *videosink_class = GST_VIDEO_SINK_CLASS (klass); + + object_class->dispose = gst_qml6_d3d11_sink_dispose; + object_class->finalize = gst_qml6_d3d11_sink_finalize; + object_class->set_property = gst_qml6_d3d11_sink_set_property; + object_class->get_property = gst_qml6_d3d11_sink_get_property; + + g_object_class_install_property (object_class, PROP_WIDGET, + g_param_spec_pointer ("widget", "QQuickItem", + "The QQuickItem to place in the object hierarchy", + (GParamFlags) (GST_PARAM_MUTABLE_READY | G_PARAM_READWRITE | + 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))); + + gst_element_class_set_static_metadata (element_class, + "Qt6 Direct3D11 Video Sink", "Sink/Video", + "A video sink that renders to a QQuickItem for Qt6 using Direct3D11", + "Seungha Yang "); + + gst_element_class_add_static_pad_template (element_class, &sink_template); + + element_class->set_context = + GST_DEBUG_FUNCPTR (gst_qml6_d3d11_sink_set_context); + + sink_class->set_caps = GST_DEBUG_FUNCPTR (gst_qml6_d3d11_sink_set_caps); + sink_class->start = GST_DEBUG_FUNCPTR (gst_qml6_d3d11_sink_start); + sink_class->stop = GST_DEBUG_FUNCPTR (gst_qml6_d3d11_sink_stop); + sink_class->propose_allocation = + GST_DEBUG_FUNCPTR (gst_qml6_d3d11_sink_propose_allocation); + sink_class->query = GST_DEBUG_FUNCPTR (gst_qml6_d3d11_sink_query); + sink_class->prepare = GST_DEBUG_FUNCPTR (gst_qml6_d3d11_sink_prepare); + + videosink_class->show_frame = + GST_DEBUG_FUNCPTR (gst_qml6_d3d11_sink_show_frame); +} + +static void +gst_qml6_d3d11_sink_init (GstQml6D3D11Sink * self) +{ + self->priv = new GstQml6D3D11SinkPrivate (); +} + +static void +gst_qml6_d3d11_sink_dispose (GObject * object) +{ + GstQml6D3D11Sink *self = GST_QML6_D3D11_SINK (object); + GstQml6D3D11SinkPrivate *priv = self->priv; + + gst_clear_object (&priv->device); + self->priv->widget.clear (); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_qml6_d3d11_sink_finalize (GObject * object) +{ + GstQml6D3D11Sink *self = GST_QML6_D3D11_SINK (object); + + delete self->priv; + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_qml6_d3d11_sink_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstQml6D3D11Sink *self = GST_QML6_D3D11_SINK (object); + GstQml6D3D11SinkPrivate *priv = self->priv; + std::lock_guard < std::recursive_mutex > lk (priv->lock); + + switch (prop_id) { + case PROP_FORCE_ASPECT_RATIO: + priv->force_aspect_ratio = g_value_get_boolean (value); + if (priv->widget) + priv->widget->SetForceAspectRatio (priv->force_aspect_ratio); + break; + case PROP_WIDGET: + { + GstQt6D3D11VideoItem *item = + (GstQt6D3D11VideoItem *) g_value_get_pointer (value); + if (item) { + priv->widget = item->Proxy (); + if (priv->widget) { + priv->widget->SetSink (GST_ELEMENT_CAST (self)); + priv->widget->SetForceAspectRatio (priv->force_aspect_ratio); + } + } else { + priv->widget.clear (); + } + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_qml6_d3d11_sink_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstQml6D3D11Sink *self = GST_QML6_D3D11_SINK (object); + GstQml6D3D11SinkPrivate *priv = self->priv; + std::lock_guard < std::recursive_mutex > lk (priv->lock); + + switch (prop_id) { + case PROP_FORCE_ASPECT_RATIO: + g_value_set_boolean (value, priv->force_aspect_ratio); + break; + case PROP_WIDGET: + if (priv->widget) { + g_value_set_pointer (value, priv->widget->Item ()); + } else { + g_value_set_pointer (value, nullptr); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_qml6_d3d11_sink_set_context (GstElement * element, GstContext * context) +{ + GstQml6D3D11Sink *self = GST_QML6_D3D11_SINK (element); + GstQml6D3D11SinkPrivate *priv = self->priv; + std::lock_guard < std::recursive_mutex > lk (priv->lock); + + if (priv->widget) { + gint64 adapter_luid = priv->widget->AdapterLuid (); + gst_d3d11_handle_set_context_for_adapter_luid (element, + context, adapter_luid, &priv->device); + } + + GST_ELEMENT_CLASS (parent_class)->set_context (element, context); +} + +static gboolean +gst_qml6_d3d11_sink_set_caps (GstBaseSink * sink, GstCaps * caps) +{ + GstQml6D3D11Sink *self = GST_QML6_D3D11_SINK (sink); + GstQml6D3D11SinkPrivate *priv = self->priv; + std::lock_guard < std::recursive_mutex > lk (priv->lock); + GstBufferPool *pool; + GstStructure *config; + GstD3D11AllocationParams *params; + + if (!priv->widget) { + GST_ERROR_OBJECT (self, "Widget is not configured"); + return FALSE; + } + + if (!gst_video_info_from_caps (&priv->info, caps)) { + GST_ERROR_OBJECT (self, "Invalid caps"); + return FALSE; + } + + if (priv->pool) { + gst_buffer_pool_set_active (priv->pool, FALSE); + gst_clear_object (&priv->pool); + } + + priv->widget->SetCaps (caps); + + pool = gst_d3d11_buffer_pool_new (priv->device); + config = gst_buffer_pool_get_config (pool); + params = gst_d3d11_allocation_params_new (priv->device, &priv->info, + GST_D3D11_ALLOCATION_FLAG_DEFAULT, D3D11_BIND_SHADER_RESOURCE, 0); + gst_buffer_pool_config_set_d3d11_allocation_params (config, params); + gst_d3d11_allocation_params_free (params); + + gst_buffer_pool_config_set_params (config, caps, priv->info.size, 0, 0); + + if (!gst_buffer_pool_set_config (pool, config)) { + GST_ERROR_OBJECT (self, "Couldn't set pool config"); + goto error; + } + + if (!gst_buffer_pool_set_active (pool, TRUE)) { + GST_ERROR_OBJECT (self, "Couldn't set active"); + goto error; + } + + priv->pool = pool; + + return TRUE; + +error: + gst_clear_object (&pool); + return FALSE; +} + +static gboolean +gst_qml6_d3d11_sink_start (GstBaseSink * sink) +{ + GstQml6D3D11Sink *self = GST_QML6_D3D11_SINK (sink); + GstQml6D3D11SinkPrivate *priv = self->priv; + std::lock_guard < std::recursive_mutex > lk (priv->lock); + gint64 adapter_luid; + + GST_DEBUG_OBJECT (self, "Start"); + + if (!priv->widget) { + GST_ERROR_OBJECT (self, "Widget is not configured"); + return FALSE; + } + + adapter_luid = priv->widget->AdapterLuid (); + if (!gst_d3d11_ensure_element_data_for_adapter_luid (GST_ELEMENT_CAST (self), + adapter_luid, &priv->device)) { + GST_ERROR_OBJECT (self, "Couldn't create d3d11 device for luid %" + G_GINT64_FORMAT, adapter_luid); + return FALSE; + } + + priv->adapter_luid = adapter_luid; + + return TRUE; +} + +static gboolean +gst_qml6_d3d11_sink_stop (GstBaseSink * sink) +{ + GstQml6D3D11Sink *self = GST_QML6_D3D11_SINK (sink); + GstQml6D3D11SinkPrivate *priv = self->priv; + + GST_DEBUG_OBJECT (self, "Stop"); + + if (priv->pool) { + gst_buffer_pool_set_active (priv->pool, FALSE); + gst_clear_object (&priv->pool); + } + + gst_clear_buffer (&priv->prepared_buffer); + gst_clear_object (&priv->device); + + return TRUE; +} + +static gboolean +gst_qml6_d3d11_sink_propose_allocation (GstBaseSink * sink, GstQuery * query) +{ + GstQml6D3D11Sink *self = GST_QML6_D3D11_SINK (sink); + GstQml6D3D11SinkPrivate *priv = self->priv; + GstCaps *caps; + GstBufferPool *pool = nullptr; + GstVideoInfo info; + guint size; + gboolean need_pool; + + if (!priv->device) { + GST_ERROR_OBJECT (self, "No d3d11 device configured"); + 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_WARNING_OBJECT (self, "Invalid caps"); + return FALSE; + } + + /* the normal size of a frame */ + size = info.size; + + if (need_pool) { + GstCapsFeatures *features; + GstStructure *config; + gboolean is_d3d11 = FALSE; + + features = gst_caps_get_features (caps, 0); + if (features + && gst_caps_features_contains (features, + GST_CAPS_FEATURE_MEMORY_D3D11_MEMORY)) { + GST_DEBUG_OBJECT (self, "upstream support d3d11 memory"); + pool = gst_d3d11_buffer_pool_new (priv->device); + is_d3d11 = 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_d3d11) { + gst_buffer_pool_config_add_option (config, + GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT); + } + + size = GST_VIDEO_INFO_SIZE (&info); + if (is_d3d11) { + GstD3D11AllocationParams *d3d11_params = + gst_d3d11_allocation_params_new (priv->device, &info, + GST_D3D11_ALLOCATION_FLAG_DEFAULT, D3D11_BIND_SHADER_RESOURCE, 0); + + gst_buffer_pool_config_set_d3d11_allocation_params (config, d3d11_params); + gst_d3d11_allocation_params_free (d3d11_params); + } + + gst_buffer_pool_config_set_params (config, caps, (guint) 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; + } + + /* d3d11 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, 2, 0); + if (pool) + gst_object_unref (pool); + + gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL); + + return TRUE; +} + +static gboolean +gst_qml6_d3d11_sink_query (GstBaseSink * sink, GstQuery * query) +{ + GstQml6D3D11Sink *self = GST_QML6_D3D11_SINK (sink); + GstQml6D3D11SinkPrivate *priv = self->priv; + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_CONTEXT: + if (gst_d3d11_handle_context_query (GST_ELEMENT (self), query, + priv->device)) { + return TRUE; + } + break; + default: + break; + } + + return GST_BASE_SINK_CLASS (parent_class)->query (sink, query); +} + +static GstBuffer * +gst_qml6_d3d11_sink_do_system_copy (GstQml6D3D11Sink * self, GstBuffer * in_buf) +{ + GstQml6D3D11SinkPrivate *priv = self->priv; + GstBuffer *out_buf; + GstVideoFrame in_frame, out_frame; + + if (!gst_video_frame_map (&in_frame, &priv->info, in_buf, GST_MAP_READ)) { + GST_ERROR_OBJECT (self, "Couldn't map input buffer"); + + return nullptr; + } + + if (gst_buffer_pool_acquire_buffer (priv->pool, &out_buf, nullptr) + != GST_FLOW_OK) { + GST_ERROR_OBJECT (self, "Couldn't acquire buffer"); + gst_video_frame_unmap (&in_frame); + + return nullptr; + } + + if (!gst_video_frame_map (&out_frame, &priv->info, out_buf, GST_MAP_WRITE)) { + GST_ERROR_OBJECT (self, "Couldn't map output buffer"); + gst_video_frame_unmap (&in_frame); + gst_buffer_unref (out_buf); + + return nullptr; + } + + gst_video_frame_copy (&out_frame, &in_frame); + gst_video_frame_unmap (&in_frame); + gst_video_frame_unmap (&out_frame); + + /* Do upload staging to default texture */ + for (guint i = 0; i < gst_buffer_n_memory (out_buf); i++) { + GstMemory *mem = gst_buffer_peek_memory (out_buf, i); + GstMapInfo info; + + gst_memory_map (mem, &info, (GstMapFlags) (GST_MAP_READ | GST_MAP_D3D11)); + gst_memory_unmap (mem, &info); + } + + return out_buf; +} + +static GstBuffer * +gst_qml6_d3d11_sink_do_gpu_copy (GstQml6D3D11Sink * self, GstBuffer * in_buf) +{ + GstQml6D3D11SinkPrivate *priv = self->priv; + GstBuffer *out_buf; + ID3D11DeviceContext *context; + + if (gst_buffer_pool_acquire_buffer (priv->pool, &out_buf, nullptr) + != GST_FLOW_OK) { + GST_ERROR_OBJECT (self, "Couldn't acquire buffer"); + return nullptr; + } + + GstD3D11DeviceLockGuard lk (priv->device); + context = gst_d3d11_device_get_device_context_handle (priv->device); + + for (guint i = 0; i < gst_buffer_n_memory (in_buf); i++) { + GstMapInfo src_info, dst_info; + GstMemory *src_mem, *dst_mem; + D3D11_TEXTURE2D_DESC src_desc, dst_desc; + D3D11_BOX src_box; + guint subresource_idx; + ID3D11Texture2D *src_tex, *dst_tex; + + src_mem = gst_buffer_peek_memory (in_buf, i); + dst_mem = gst_buffer_peek_memory (out_buf, i); + + gst_memory_map (src_mem, &src_info, + (GstMapFlags) (GST_MAP_READ | GST_MAP_D3D11)); + gst_memory_map (dst_mem, + &dst_info, (GstMapFlags) (GST_MAP_WRITE | GST_MAP_D3D11)); + + src_tex = (ID3D11Texture2D *) src_info.data; + dst_tex = (ID3D11Texture2D *) dst_info.data; + + src_tex->GetDesc (&src_desc); + dst_tex->GetDesc (&dst_desc); + + subresource_idx = GPOINTER_TO_UINT (src_info.user_data[0]); + + src_box.left = 0; + src_box.top = 0; + src_box.front = 0; + src_box.back = 1; + src_box.right = MIN (src_desc.Width, dst_desc.Width); + src_box.bottom = MIN (src_desc.Height, dst_desc.Height); + + context->CopySubresourceRegion (dst_tex, 0, + 0, 0, 0, src_tex, subresource_idx, &src_box); + gst_memory_unmap (src_mem, &src_info); + gst_memory_unmap (dst_mem, &dst_info); + } + + return out_buf; +} + +static GstFlowReturn +gst_qml6_d3d11_sink_prepare (GstBaseSink * sink, GstBuffer * buf) +{ + GstQml6D3D11Sink *self = GST_QML6_D3D11_SINK (sink); + GstQml6D3D11SinkPrivate *priv = self->priv; + std::lock_guard < std::recursive_mutex > lk (priv->lock); + GstBuffer *render_buf = nullptr; + GstMemory *mem; + GstD3D11Device *other_device = nullptr; + + if (!priv->widget) { + GST_ERROR_OBJECT (self, "Widget is not configured"); + return GST_FLOW_ERROR; + } + + gst_clear_buffer (&priv->prepared_buffer); + mem = gst_buffer_peek_memory (buf, 0); + if (!gst_is_d3d11_memory (mem)) { + render_buf = gst_qml6_d3d11_sink_do_system_copy (self, buf); + } else { + GstD3D11Memory *dmem = GST_D3D11_MEMORY_CAST (mem); + + other_device = dmem->device; + if (dmem->device != priv->device) { + gint64 luid; + + /* different device, check if it's the same GPU */ + g_object_get (dmem->device, "adapter-luid", &luid, nullptr); + if (luid != priv->adapter_luid) { + /* From other GPU, system copy */ + render_buf = gst_qml6_d3d11_sink_do_system_copy (self, buf); + } else { + /* same GPU and sharable, increase ref */ + render_buf = gst_buffer_ref (buf); + } + } else { + render_buf = gst_buffer_ref (buf); + } + } + + if (!render_buf) { + GST_ERROR_OBJECT (self, "Couldn't prepare output buffer"); + return GST_FLOW_ERROR; + } + + priv->prepared_buffer = render_buf; + + return GST_FLOW_OK; +} + +static GstFlowReturn +gst_qml6_d3d11_sink_show_frame (GstVideoSink * sink, GstBuffer * buf) +{ + GstQml6D3D11Sink *self = GST_QML6_D3D11_SINK (sink); + GstQml6D3D11SinkPrivate *priv = self->priv; + std::lock_guard < std::recursive_mutex > lk (priv->lock); + + if (!priv->widget) { + GST_ERROR_OBJECT (self, "Widget is not configured"); + return GST_FLOW_ERROR; + } + + if (!priv->prepared_buffer) { + GST_ERROR_OBJECT (self, "No prepared sample"); + return GST_FLOW_ERROR; + } + + priv->widget->SetBuffer (priv->prepared_buffer); + + return GST_FLOW_OK; +} + +static void +gst_qml6_d3d11_sink_navigation_send_event (GstNavigation * navigation, + GstEvent * event) +{ + GstQml6D3D11Sink *self = GST_QML6_D3D11_SINK (navigation); + + if (event) { + gboolean handled; + + gst_event_ref (event); + handled = gst_pad_push_event (GST_VIDEO_SINK_PAD (self), event); + if (!handled) { + gst_element_post_message (GST_ELEMENT_CAST (self), + gst_navigation_message_new_event (GST_OBJECT_CAST (self), event)); + } + + gst_event_unref (event); + } +} + +static void +gst_qml6_d3d11_sink_navigation_init (GstNavigationInterface * iface) +{ + iface->send_event_simple = gst_qml6_d3d11_sink_navigation_send_event; +} diff --git a/subprojects/gst-plugins-bad/ext/qt6d3d11/gstqml6d3d11sink.h b/subprojects/gst-plugins-bad/ext/qt6d3d11/gstqml6d3d11sink.h new file mode 100644 index 0000000000..61be5bdbf6 --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/qt6d3d11/gstqml6d3d11sink.h @@ -0,0 +1,31 @@ +/* GStreamer + * Copyright (C) 2023 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 + +G_BEGIN_DECLS + +#define GST_TYPE_QML6_D3D11_SINK (gst_qml6_d3d11_sink_get_type()) +G_DECLARE_FINAL_TYPE (GstQml6D3D11Sink, gst_qml6_d3d11_sink, + GST, QML6_D3D11_SINK, GstVideoSink); + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/ext/qt6d3d11/gstqsg6d3d11node.cpp b/subprojects/gst-plugins-bad/ext/qt6d3d11/gstqsg6d3d11node.cpp new file mode 100644 index 0000000000..34dc36d47d --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/qt6d3d11/gstqsg6d3d11node.cpp @@ -0,0 +1,257 @@ +/* GStreamer + * Copyright (C) 2022 Matthew Waters + * Copyright (C) 2023 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 "gstqsg6d3d11node.h" +#include + +GST_DEBUG_CATEGORY_EXTERN (gst_qt6_d3d11_debug); +#define GST_CAT_DEFAULT gst_qt6_d3d11_debug + +/* *INDENT-OFF* */ +using namespace Microsoft::WRL; +/* *INDENT-ON* */ + +GstQSG6D3D11Node::GstQSG6D3D11Node (QQuickItem * item, GstD3D11Device * device) +{ + window_ = item->window (); + qt_device_ = (GstD3D11Device *) gst_object_ref (device); + + frame_.buffer = nullptr; + + gst_video_info_init (&render_info_); + gst_video_info_init (&info_); + + setOwnsTexture (true); + resize (8, 8); + + mapTexture (); +} + +GstQSG6D3D11Node::~GstQSG6D3D11Node () +{ + GST_DEBUG ("Destroying %p", this); + + unmapTexture (); + gst_clear_buffer (&sharable_); + gst_clear_buffer (&backbuffer_); + gst_clear_buffer (&buffer_); + gst_clear_caps (&caps_); + + if (pool_) { + gst_buffer_pool_set_active (pool_, FALSE); + gst_object_unref (pool_); + } + + gst_clear_object (&conv_); + gst_clear_object (&device_); + gst_clear_object (&qt_device_); +} + +QSGTexture * +GstQSG6D3D11Node::texture () const +{ + return QSGSimpleTextureNode::texture (); +} + +void +GstQSG6D3D11Node::mapTexture () +{ + if (backbuffer_) { + gst_video_frame_map (&frame_, &render_info_, backbuffer_, + (GstMapFlags) (GST_MAP_READ | GST_MAP_D3D11)); + } +} + +void +GstQSG6D3D11Node::unmapTexture () +{ + if (frame_.buffer) + gst_video_frame_unmap (&frame_); + frame_.buffer = nullptr; +} + +void +GstQSG6D3D11Node::resize (guint width, guint height) +{ + GstStructure *config; + GstD3D11AllocationParams *params; + GstCaps *caps; + GstD3D11Memory *dmem; + ID3D11Texture2D *tex; + + if (pool_ && width == render_info_.width && height == render_info_.height) + return; + + gst_clear_buffer (&sharable_); + gst_clear_buffer (&backbuffer_); + + if (pool_) { + gst_buffer_pool_set_active (pool_, FALSE); + gst_clear_object (&pool_); + } + + pool_ = gst_d3d11_buffer_pool_new (qt_device_); + + gst_video_info_set_format (&render_info_, GST_VIDEO_FORMAT_RGBA, + width, height); + render_info_.colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT709; + caps = gst_video_info_to_caps (&render_info_); + + params = gst_d3d11_allocation_params_new (qt_device_, &render_info_, + GST_D3D11_ALLOCATION_FLAG_DEFAULT, + D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET, + D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX); + + config = gst_buffer_pool_get_config (pool_); + gst_buffer_pool_config_set_d3d11_allocation_params (config, params); + gst_d3d11_allocation_params_free (params); + + gst_buffer_pool_config_set_params (config, caps, render_info_.size, 0, 0); + gst_caps_unref (caps); + gst_buffer_pool_set_config (pool_, config); + gst_buffer_pool_set_active (pool_, TRUE); + + gst_buffer_pool_acquire_buffer (pool_, &backbuffer_, nullptr); + + dmem = (GstD3D11Memory *) gst_buffer_peek_memory (backbuffer_, 0); + tex = (ID3D11Texture2D *) gst_d3d11_memory_get_resource_handle (dmem); + auto qt_texture = QNativeInterface::QSGD3D11Texture::fromNative (tex, window_, + QSize (render_info_.width, render_info_.height), + QQuickWindow::TextureHasAlphaChannel); + setTexture (qt_texture); +} + +/* only called from QT rendering thread */ +void +GstQSG6D3D11Node::SetCaps (GstCaps * caps) +{ + if (caps_ && caps && gst_caps_is_equal (caps_, caps)) + return; + + gst_clear_object (&conv_); + gst_clear_buffer (&sharable_); + gst_clear_buffer (&buffer_); + + if (caps) + gst_video_info_from_caps (&info_, caps); + else + gst_video_info_init (&info_); + + gst_caps_replace (&caps_, caps); +} + +/* only called from QT rendering thread */ +void +GstQSG6D3D11Node::SetBuffer (GstBuffer * buffer) +{ + gboolean buffer_changed; + + buffer_changed = gst_buffer_replace (&buffer_, buffer); + + unmapTexture (); + + if (buffer_ && buffer_changed) { + GstD3D11Memory *dmem; + + resize (info_.width, info_.height); + + dmem = (GstD3D11Memory *) gst_buffer_peek_memory (buffer, 0); + if (dmem->device != device_) { + gst_clear_object (&conv_); + gst_clear_buffer (&sharable_); + gst_clear_object (&device_); + device_ = (GstD3D11Device *) gst_object_ref (dmem->device); + } + + gst_d3d11_device_lock (device_); + if (!conv_) + conv_ = gst_d3d11_converter_new (device_, &info_, &render_info_, nullptr); + + if (!sharable_) { + GstMemory *mem_wrapped; + ID3D11Resource *resource; + ComPtr < IDXGIResource > dxgi_resource; + ComPtr < ID3D11Texture2D > shared_texture; + ID3D11Device *device_handle; + HANDLE handle; + + device_handle = gst_d3d11_device_get_device_handle (dmem->device); + dmem = (GstD3D11Memory *) gst_buffer_peek_memory (backbuffer_, 0); + resource = gst_d3d11_memory_get_resource_handle (dmem); + resource->QueryInterface (IID_PPV_ARGS (&dxgi_resource)); + dxgi_resource->GetSharedHandle (&handle); + device_handle->OpenSharedResource (handle, + IID_PPV_ARGS (&shared_texture)); + + mem_wrapped = gst_d3d11_allocator_alloc_wrapped (nullptr, device_, + shared_texture.Get (), render_info_.size, nullptr, nullptr); + + sharable_ = gst_buffer_new (); + gst_buffer_append_memory (sharable_, mem_wrapped); + } + + if (GST_VIDEO_INFO_FORMAT (&info_) == GST_VIDEO_FORMAT_RGBA) { + GstMapInfo src_info, dst_info; + GstMemory *src_mem, *dst_mem; + D3D11_TEXTURE2D_DESC src_desc, dst_desc; + D3D11_BOX src_box; + ID3D11Texture2D *src_tex, *dst_tex; + ID3D11DeviceContext *context = + gst_d3d11_device_get_device_context_handle (device_); + + src_mem = gst_buffer_peek_memory (buffer_, 0); + dst_mem = gst_buffer_peek_memory (sharable_, 0); + + gst_memory_map (src_mem, &src_info, + (GstMapFlags) (GST_MAP_READ | GST_MAP_D3D11)); + gst_memory_map (dst_mem, + &dst_info, (GstMapFlags) (GST_MAP_WRITE | GST_MAP_D3D11)); + + src_tex = (ID3D11Texture2D *) src_info.data; + dst_tex = (ID3D11Texture2D *) dst_info.data; + + src_tex->GetDesc (&src_desc); + dst_tex->GetDesc (&dst_desc); + + src_box.left = 0; + src_box.top = 0; + src_box.front = 0; + src_box.back = 1; + src_box.right = MIN (src_desc.Width, dst_desc.Width); + src_box.bottom = MIN (src_desc.Height, dst_desc.Height); + + context->CopySubresourceRegion (dst_tex, 0, + 0, 0, 0, src_tex, 0, &src_box); + gst_memory_unmap (src_mem, &src_info); + gst_memory_unmap (dst_mem, &dst_info); + } else { + gst_d3d11_converter_convert_buffer_unlocked (conv_, buffer_, sharable_); + } + gst_d3d11_device_unlock (device_); + + markDirty (QSGNode::DirtyMaterial); + } + + mapTexture (); +} diff --git a/subprojects/gst-plugins-bad/ext/qt6d3d11/gstqsg6d3d11node.h b/subprojects/gst-plugins-bad/ext/qt6d3d11/gstqsg6d3d11node.h new file mode 100644 index 0000000000..4fee3a59aa --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/qt6d3d11/gstqsg6d3d11node.h @@ -0,0 +1,62 @@ +/* GStreamer + * Copyright (C) 2022 Matthew Waters + * Copyright (C) 2023 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 +#include +#include +#include +#include + +class GstQSG6D3D11Node : public QSGTextureProvider, public QSGSimpleTextureNode +{ + Q_OBJECT + +public: + GstQSG6D3D11Node (QQuickItem * item, GstD3D11Device * device); + ~GstQSG6D3D11Node (); + + QSGTexture *texture() const override; + + void SetCaps (GstCaps * caps); + void SetBuffer (GstBuffer * buffer); + +private: + void resize (guint width, guint height); + void mapTexture (); + void unmapTexture (); + +private: + QQuickWindow *window_; + GstD3D11Device *qt_device_; + GstD3D11Device *device_ = nullptr; + GstBuffer *buffer_ = nullptr; + GstCaps *caps_ = nullptr; + GstBuffer *sharable_ = nullptr; + GstBuffer *backbuffer_ = nullptr; + GstVideoFrame frame_; + GstVideoInfo info_; + GstVideoInfo render_info_; + GstBufferPool *pool_ = nullptr; + GstD3D11Converter *conv_ = nullptr; +}; diff --git a/subprojects/gst-plugins-bad/ext/qt6d3d11/gstqt6d3d11videoitem.cpp b/subprojects/gst-plugins-bad/ext/qt6d3d11/gstqt6d3d11videoitem.cpp new file mode 100644 index 0000000000..3229e91cf0 --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/qt6d3d11/gstqt6d3d11videoitem.cpp @@ -0,0 +1,630 @@ +/* GStreamer + * Copyright (C) 2015 Matthew Waters + * Copyright (C) 2023 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 "gstqt6d3d11videoitem.h" +#include "gstqsg6d3d11node.h" + +GST_DEBUG_CATEGORY_EXTERN (gst_qt6_d3d11_debug); +#define GST_CAT_DEFAULT gst_qt6_d3d11_debug + +void +GstQt6D3D11VideoItemProxy::InvalidateRef (void) +{ + std::lock_guard < std::mutex > lk (lock); + item = nullptr; +} + +void +GstQt6D3D11VideoItemProxy::SetSink (GstElement * sink) +{ + std::lock_guard < std::mutex > lk (lock); + if (!item) + return; + + item->setSink (sink); +} + +void +GstQt6D3D11VideoItemProxy::SetCaps (GstCaps * caps) +{ + std::lock_guard < std::mutex > lk (lock); + if (!item) + return; + + item->setCaps (caps); +} + +void +GstQt6D3D11VideoItemProxy::SetBuffer (GstBuffer * buffer) +{ + std::lock_guard < std::mutex > lk (lock); + if (!item) + return; + + item->setBuffer (buffer); +} + +void +GstQt6D3D11VideoItemProxy::SetForceAspectRatio (bool force) +{ + std::lock_guard < std::mutex > lk (lock); + if (!item) + return item->setForceAspectRatio (force); +} + +gint64 +GstQt6D3D11VideoItemProxy::AdapterLuid () +{ + std::lock_guard < std::mutex > lk (lock); + if (!item) + return 0; + + return item->AdapterLuid (); +} + +GstQt6D3D11VideoItem * +GstQt6D3D11VideoItemProxy::Item (void) +{ + std::lock_guard < std::mutex > lk (lock); + + return item; +} + +GstQt6D3D11VideoItem::GstQt6D3D11VideoItem () +{ + g_weak_ref_init (&sink_, nullptr); + + connect (this, &QQuickItem::windowChanged, this, + &GstQt6D3D11VideoItem::handleWindowChanged); + + proxy_ = QSharedPointer < GstQt6D3D11VideoItemProxy > + (new GstQt6D3D11VideoItemProxy (this)); + + setFlag (ItemHasContents, true); + setAcceptedMouseButtons (Qt::AllButtons); + setAcceptHoverEvents (true); + setAcceptTouchEvents (true); + + gst_video_info_init (&info_); + + GST_DEBUG ("%p New Qt6 Video Item", this); +} + +GstQt6D3D11VideoItem::~GstQt6D3D11VideoItem () +{ + GST_DEBUG ("%p Destroying Qt6 Video Item", this); + + proxy_->InvalidateRef (); + proxy_.clear (); + + gst_clear_buffer (&buffer_); + gst_clear_caps (&caps_); + + g_weak_ref_clear (&sink_); +} + +QSharedPointer < GstQt6D3D11VideoItemProxy > GstQt6D3D11VideoItem::Proxy (void) +{ + return proxy_; +} + +void +GstQt6D3D11VideoItem::setSink (GstElement * sink) +{ + std::lock_guard < std::mutex > lk (lock_); + g_weak_ref_set (&sink_, sink); +} + +void +GstQt6D3D11VideoItem::setCaps (GstCaps * caps) +{ + std::lock_guard < std::mutex > lk (lock_); + guint num, den; + + gst_caps_replace (&caps_, caps); + gst_video_info_from_caps (&info_, caps); + + gst_video_calculate_display_ratio (&num, &den, info_.width, info_.height, + info_.par_n, info_.par_d, 1, 1); + + display_width_ = info_.width; + display_height_ = info_.height; + + if (display_width_ % num == 0) { + display_height_ = gst_util_uint64_scale_int (display_width_, den, num); + } else { + display_width_ = gst_util_uint64_scale_int (display_height_, num, den); + } + + update_par_ = true; +} + +void +GstQt6D3D11VideoItem::setBuffer (GstBuffer * buffer) +{ + std::lock_guard < std::mutex > lk (lock_); + + gst_buffer_replace (&buffer_, buffer); + + if (this->window_) + QMetaObject::invokeMethod (this->window_, "update", Qt::QueuedConnection); +} + +void +GstQt6D3D11VideoItem::setForceAspectRatio (bool force) +{ + std::lock_guard < std::mutex > lk (lock_); + + if (force != force_aspect_ratio_) + update_par_ = true; + + force_aspect_ratio_ = force; +} + +gint64 +GstQt6D3D11VideoItem::AdapterLuid (void) +{ + std::lock_guard < std::mutex > lk (lock_); + + if (!qt_device_) + return 0; + + return luid_; +} + +void +GstQt6D3D11VideoItem::handleWindowChanged (QQuickWindow * window) +{ + if (window) { + connect (window, &QQuickWindow::beforeSynchronizing, this, + &GstQt6D3D11VideoItem::onBeforeSynchronizing, Qt::DirectConnection); + connect (window, &QQuickWindow::sceneGraphInvalidated, this, + &GstQt6D3D11VideoItem::onSceneGraphInvalidated, Qt::DirectConnection); + } else { + gst_clear_object (&qt_device_); + window_ = nullptr; + } + node_ = nullptr; +} + +QSGNode * +GstQt6D3D11VideoItem::updatePaintNode (QSGNode * old_node, + UpdatePaintNodeData * data) +{ + if (!qt_device_) + return old_node; + + GstQSG6D3D11Node *new_node = static_cast < GstQSG6D3D11Node * >(old_node); + GstVideoRectangle src, dst, result; + + std::lock_guard < std::mutex > lk (lock_); + + GST_TRACE ("%p updatePaintNode", this); + + if (!new_node) + new_node = new GstQSG6D3D11Node (this, qt_device_); + + if (force_aspect_ratio_ && caps_) { + src.w = display_width_; + src.h = display_height_; + + dst.x = boundingRect ().x (); + dst.y = boundingRect ().y (); + dst.w = boundingRect ().width (); + dst.h = boundingRect ().height (); + + gst_video_sink_center_rect (src, dst, &result, TRUE); + } else { + result.x = boundingRect ().x (); + result.y = boundingRect ().y (); + result.w = boundingRect ().width (); + result.h = boundingRect ().height (); + } + + new_node->setRect (QRectF (result.x, result.y, result.w, result.h)); + node_ = new_node; + if (caps_ || !force_aspect_ratio_) + update_par_ = false; + else + update_par_ = true; + + return new_node; +} + +void +GstQt6D3D11VideoItem::onBeforeSynchronizing (void) +{ + QSGRendererInterface *iface; + QQuickWindow *window; + ID3D11Device *device; + std::unique_lock < std::mutex > lk (lock_); + + if (window_) + return; + + GST_DEBUG ("%p Scene graph initialized", this); + + window = this->window (); + + if (!window) { + GST_WARNING ("Initialized scene graph has no associated window"); + return; + } + + iface = window->rendererInterface (); + if (!iface) { + GST_WARNING ("Couldn't get renderer interface"); + return; + } + + if (iface->graphicsApi () != QSGRendererInterface::Direct3D11) { + GST_WARNING ("Not a d3d11 api"); + return; + } + + device = reinterpret_cast < ID3D11Device * >(iface->getResource (window, + QSGRendererInterface::DeviceResource)); + if (!device) { + GST_WARNING ("Couldn't get d3d11 device"); + return; + } + + qt_device_ = gst_d3d11_device_new_wrapped (device); + g_object_get (qt_device_, "adapter-luid", &luid_, nullptr); + window_ = window; + + lk.unlock (); + + connect (window, &QQuickWindow::beforeRendering, this, + &GstQt6D3D11VideoItem::onBeforeRendering, Qt::DirectConnection); +} + +void +GstQt6D3D11VideoItem::onSceneGraphInvalidated (void) +{ + std::unique_lock < std::mutex > lk (lock_); + + GST_DEBUG ("%p Scene graph invalidated", this); + + gst_clear_object (&qt_device_); + window_ = nullptr; + node_ = nullptr; +} + +void +GstQt6D3D11VideoItem::onBeforeRendering (void) +{ + std::lock_guard < std::mutex > lk (lock_); + + if (update_par_ && caps_ && node_) { + GstVideoRectangle src, dst, result; + + GST_DEBUG ("Updating node rect"); + + if (force_aspect_ratio_ && caps_) { + src.w = display_width_; + src.h = display_height_; + + dst.x = boundingRect ().x (); + dst.y = boundingRect ().y (); + dst.w = boundingRect ().width (); + dst.h = boundingRect ().height (); + + gst_video_sink_center_rect (src, dst, &result, TRUE); + } else { + result.x = boundingRect ().x (); + result.y = boundingRect ().y (); + result.w = boundingRect ().width (); + result.h = boundingRect ().height (); + } + + node_->setRect (QRectF (result.x, result.y, result.w, result.h)); + update_par_ = false; + } + + if (node_) { + node_->SetCaps (caps_); + node_->SetBuffer (buffer_); + } + + return; +} + +void +GstQt6D3D11VideoItem::hoverEnterEvent (QHoverEvent * event) +{ + mouse_hovering_ = true; +} + +void +GstQt6D3D11VideoItem::hoverLeaveEvent (QHoverEvent * event) +{ + mouse_hovering_ = false; +} + +void +GstQt6D3D11VideoItem::fitStreamToAllocatedSize (GstVideoRectangle * result) +{ + if (force_aspect_ratio_) { + GstVideoRectangle src, dst; + + src.x = 0; + src.y = 0; + src.w = display_width_; + src.h = display_height_; + + dst.x = 0; + dst.y = 0; + dst.w = width (); + dst.h = height (); + + gst_video_sink_center_rect (src, dst, result, TRUE); + } else { + result->x = 0; + result->y = 0; + result->w = width (); + result->h = height (); + } +} + +QPointF +GstQt6D3D11VideoItem::mapPointToStreamSize (QPointF pos) +{ + gdouble stream_width, stream_height; + GstVideoRectangle result; + double stream_x, stream_y; + double x, y; + + fitStreamToAllocatedSize (&result); + + stream_width = (gdouble) GST_VIDEO_INFO_WIDTH (&info_); + stream_height = (gdouble) GST_VIDEO_INFO_HEIGHT (&info_); + x = pos.x (); + y = pos.y (); + + /* from display coordinates to stream coordinates */ + if (result.w > 0) + stream_x = (x - result.x) / result.w * stream_width; + else + stream_x = 0.; + + /* clip to stream size */ + stream_x = CLAMP (stream_x, 0., stream_width); + + /* same for y-axis */ + if (result.h > 0) + stream_y = (y - result.y) / result.h * stream_height; + else + stream_y = 0.; + + stream_y = CLAMP (stream_y, 0., stream_height); + + return QPointF (stream_x, stream_y); +} + +static GstNavigationModifierType +translateModifiers (Qt::KeyboardModifiers modifiers) +{ + return (GstNavigationModifierType) ( + ((modifiers & Qt::KeyboardModifier::ShiftModifier) ? + GST_NAVIGATION_MODIFIER_SHIFT_MASK : 0) | ((modifiers & + Qt::KeyboardModifier::ControlModifier) ? + GST_NAVIGATION_MODIFIER_CONTROL_MASK : 0) | ((modifiers & + Qt:: + KeyboardModifier::AltModifier) ? GST_NAVIGATION_MODIFIER_MOD1_MASK + : 0) | ((modifiers & Qt:: + KeyboardModifier::MetaModifier) ? + GST_NAVIGATION_MODIFIER_META_MASK : 0)); +} + +static GstNavigationModifierType +translateMouseButtons (Qt::MouseButtons buttons) +{ + return (GstNavigationModifierType) ( + ((buttons & Qt::LeftButton) ? GST_NAVIGATION_MODIFIER_BUTTON1_MASK : 0) | + ((buttons & Qt::RightButton) ? GST_NAVIGATION_MODIFIER_BUTTON2_MASK : 0) | + ((buttons & Qt::MiddleButton) ? GST_NAVIGATION_MODIFIER_BUTTON3_MASK : 0) + | ((buttons & Qt::BackButton) ? GST_NAVIGATION_MODIFIER_BUTTON4_MASK : 0) + | ((buttons & Qt::ForwardButton) ? GST_NAVIGATION_MODIFIER_BUTTON5_MASK : + 0)); +} + +void +GstQt6D3D11VideoItem::hoverMoveEvent (QHoverEvent * event) +{ + if (!mouse_hovering_) + return; + + if (event->position () == event->oldPos ()) + return; + + std::lock_guard < std::mutex > lk (lock_); + if (GST_VIDEO_INFO_FORMAT (&info_) == GST_VIDEO_FORMAT_UNKNOWN) + return; + + GstElement *sink = (GstElement *) g_weak_ref_get (&sink_); + if (!sink) + return; + + auto pos = mapPointToStreamSize (event->position ()); + gst_navigation_send_event_simple (GST_NAVIGATION (sink), + gst_navigation_event_new_mouse_move (pos.x (), pos.y (), + translateModifiers (event->modifiers ()))); + + gst_object_unref (sink); +} + +void +GstQt6D3D11VideoItem::touchEvent (QTouchEvent * event) +{ + std::lock_guard < std::mutex > lk (lock_); + if (GST_VIDEO_INFO_FORMAT (&info_) == GST_VIDEO_FORMAT_UNKNOWN) + return; + + GstElement *sink = (GstElement *) g_weak_ref_get (&sink_); + if (!sink) + return; + + if (event->type () == QEvent::TouchCancel) { + gst_navigation_send_event_simple (GST_NAVIGATION (sink), + gst_navigation_event_new_touch_cancel (translateModifiers + (event->modifiers ()))); + } else { + const QList < QTouchEvent::TouchPoint > points = event->points (); + gboolean sent_event = FALSE; + + for (int i = 0; i < points.count (); i++) { + GstEvent *nav_event; + QPointF pos = mapPointToStreamSize (points[i].position ()); + + switch (points[i].state ()) { + case QEventPoint::Pressed: + nav_event = + gst_navigation_event_new_touch_down ((guint) points[i].id (), + pos.x (), pos.y (), (gdouble) points[i].pressure (), + translateModifiers (event->modifiers ())); + break; + case QEventPoint::Updated: + nav_event = + gst_navigation_event_new_touch_motion ((guint) points[i].id (), + pos.x (), pos.y (), (gdouble) points[i].pressure (), + translateModifiers (event->modifiers ())); + break; + case QEventPoint::Released: + nav_event = + gst_navigation_event_new_touch_up ((guint) points[i].id (), + pos.x (), pos.y (), translateModifiers (event->modifiers ())); + break; + /* Don't send an event if the point did not change */ + default: + nav_event = nullptr; + break; + } + + if (nav_event) { + gst_navigation_send_event_simple (GST_NAVIGATION (sink), nav_event); + sent_event = TRUE; + } + } + + /* Group simultaneous touch events with a frame event */ + if (sent_event) { + gst_navigation_send_event_simple (GST_NAVIGATION (sink), + gst_navigation_event_new_touch_frame (translateModifiers + (event->modifiers ()))); + } + } + + gst_object_unref (sink); +} + +void +GstQt6D3D11VideoItem::sendMouseEvent (QMouseEvent * event, gboolean is_press) +{ + std::lock_guard < std::mutex > lk (lock_); + if (GST_VIDEO_INFO_FORMAT (&info_) == GST_VIDEO_FORMAT_UNKNOWN) + return; + + GstElement *sink = (GstElement *) g_weak_ref_get (&sink_); + if (!sink) + return; + + gint button = 0; + switch (event->button ()) { + case Qt::LeftButton: + button = 1; + break; + case Qt::RightButton: + button = 2; + break; + case Qt::MiddleButton: + button = 3; + default: + break; + } + + auto pos = mapPointToStreamSize (event->pos ()); + GstEvent *navi; + GstNavigationModifierType type = (GstNavigationModifierType) + (translateModifiers (event->modifiers ()) | + translateMouseButtons (event->buttons ())); + if (is_press) { + navi = gst_navigation_event_new_mouse_button_press (button, + pos.x (), pos.y (), type); + } else { + navi = gst_navigation_event_new_mouse_button_release (button, + pos.x (), pos.y (), type); + } + + gst_navigation_send_event_simple (GST_NAVIGATION (sink), navi); + gst_object_unref (sink); +} + +void +GstQt6D3D11VideoItem::mousePressEvent (QMouseEvent * event) +{ + sendMouseEvent (event, TRUE); +} + +void +GstQt6D3D11VideoItem::mouseReleaseEvent (QMouseEvent * event) +{ + sendMouseEvent (event, FALSE); +} + +void +GstQt6D3D11VideoItem::wheelEvent (QWheelEvent * event) +{ + std::lock_guard < std::mutex > lk (lock_); + if (GST_VIDEO_INFO_FORMAT (&info_) == GST_VIDEO_FORMAT_UNKNOWN) + return; + + GstElement *sink = (GstElement *) g_weak_ref_get (&sink_); + if (!sink) + return; + + auto pos = mapPointToStreamSize (event->position ()); + auto delta = event->angleDelta (); + GstNavigationModifierType type = (GstNavigationModifierType) + (translateModifiers (event->modifiers ()) | + translateMouseButtons (event->buttons ())); + + gst_navigation_send_event_simple (GST_NAVIGATION (sink), + gst_navigation_event_new_mouse_scroll (pos.x (), pos.y (), + delta.x (), delta.y (), type)); + gst_object_unref (sink); +} + +void +gst_qt6_d3d11_video_item_init_once (void) +{ + GST_D3D11_CALL_ONCE_BEGIN { + qmlRegisterType < GstQt6D3D11VideoItem > + ("org.freedesktop.gstreamer.Qt6D3D11VideoItem", + 1, 0, "GstD3D11Qt6VideoItem"); + } GST_D3D11_CALL_ONCE_END; +} diff --git a/subprojects/gst-plugins-bad/ext/qt6d3d11/gstqt6d3d11videoitem.h b/subprojects/gst-plugins-bad/ext/qt6d3d11/gstqt6d3d11videoitem.h new file mode 100644 index 0000000000..cba789fc45 --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/qt6d3d11/gstqt6d3d11videoitem.h @@ -0,0 +1,119 @@ +/* GStreamer + * Copyright (C) 2015 Matthew Waters + * Copyright (C) 2023 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 +#include +#include +#include +#include "gstqsg6d3d11node.h" + +class GstQt6D3D11VideoItem; + +class GstQt6D3D11VideoItemProxy : public QObject +{ + Q_OBJECT + QML_ELEMENT + +public: + GstQt6D3D11VideoItemProxy (GstQt6D3D11VideoItem * videoItem) + : item (videoItem) + { + } + + void InvalidateRef (void); + + void SetSink (GstElement * sink); + void SetCaps (GstCaps * caps); + void SetBuffer (GstBuffer * buffer); + void SetForceAspectRatio (bool force); + + gint64 AdapterLuid (void); + GstQt6D3D11VideoItem * Item (void); + +private: + GstQt6D3D11VideoItem *item; + std::mutex lock; +}; + +class GstQt6D3D11VideoItem : public QQuickItem +{ + friend class GstQt6D3D11VideoItemProxy; + + Q_OBJECT + QML_ELEMENT + +public: + GstQt6D3D11VideoItem (); + ~GstQt6D3D11VideoItem (); + + QSharedPointer Proxy (void); + +protected: + QSGNode * updatePaintNode (QSGNode * oldNode, UpdatePaintNodeData * data); + void hoverEnterEvent (QHoverEvent * event); + void hoverLeaveEvent (QHoverEvent * event); + void hoverMoveEvent (QHoverEvent * event); + void touchEvent (QTouchEvent * event); + void mousePressEvent (QMouseEvent * event); + void mouseReleaseEvent (QMouseEvent * event); + void wheelEvent (QWheelEvent * event); + +private: + void setSink (GstElement * sink); + void setCaps (GstCaps * caps); + void setBuffer (GstBuffer * buffer); + void setForceAspectRatio (bool force); + gint64 AdapterLuid (); + void fitStreamToAllocatedSize (GstVideoRectangle * result); + QPointF mapPointToStreamSize (QPointF point); + void sendMouseEvent (QMouseEvent * event, gboolean is_press); + void clearResource (); + void clearFrameResource (); + bool setupShader (); + +private slots: + void handleWindowChanged (QQuickWindow * window); + void onBeforeSynchronizing (void); + void onSceneGraphInvalidated (void); + void onBeforeRendering (void); + +private: + QSharedPointer proxy_; + QQuickWindow *window_ = nullptr; + GstCaps *caps_ = nullptr; + GstBuffer *buffer_ = nullptr; + bool force_aspect_ratio_ = true; + gint64 luid_ = 0; + GstVideoInfo info_; + int display_width_ = 0; + int display_height_ = 0; + GWeakRef sink_; + std::mutex lock_; + bool mouse_hovering_ = false; + bool update_par_ = false; + GstD3D11Device *qt_device_ = nullptr; + GstQSG6D3D11Node *node_ = nullptr; +}; + +void gst_qt6_d3d11_video_item_init_once (void); diff --git a/subprojects/gst-plugins-bad/ext/qt6d3d11/meson.build b/subprojects/gst-plugins-bad/ext/qt6d3d11/meson.build new file mode 100644 index 0000000000..d94bfdf522 --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/qt6d3d11/meson.build @@ -0,0 +1,53 @@ +gstqt6d3d11_sources = [ + 'gstqml6d3d11sink.cpp', + 'gstqsg6d3d11node.cpp', + 'gstqt6d3d11videoitem.cpp', + 'plugin.cpp', +] + +moc_headers = [ + 'gstqsg6d3d11node.h', + 'gstqt6d3d11videoitem.h' +] + +have_qt6d3d11 = false + +qt6_option = get_option('qt6d3d11') +if qt6_option.disabled() + subdir_done() +endif + +if not gstd3d11_dep.found() + if qt6_option.enabled() + error('qt6 plugin was enabled explicitly, but required d3d11 dep was not found') + else + subdir_done() + endif +endif + +qt6_mod = import('qt6') +if not qt6_mod.has_tools() + if qt6_option.enabled() + error('qt6 plugin was enabled, but qt specific tools were not found') + endif + subdir_done() +endif + +qt6qml_dep = dependency('qt6', modules : ['Core', 'Gui', 'Qml', 'Quick'], + required: qt6_option) +if not qt6qml_dep.found() + subdir_done() +endif + +have_qt6d3d11 = true +moc_files = qt6_mod.preprocess(moc_headers : moc_headers) +gstqt6d3d11 = library('gstqt6d3d11', gstqt6d3d11_sources, moc_files, + 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, gstd3d11_dep, qt6qml_dep], + override_options : ['cpp_std=c++17'], + install: true, + install_dir : plugins_install_dir +) +plugins += [gstqt6d3d11] diff --git a/subprojects/gst-plugins-bad/ext/qt6d3d11/plugin.cpp b/subprojects/gst-plugins-bad/ext/qt6d3d11/plugin.cpp new file mode 100644 index 0000000000..fa8b661e87 --- /dev/null +++ b/subprojects/gst-plugins-bad/ext/qt6d3d11/plugin.cpp @@ -0,0 +1,44 @@ +/* GStreamer + * Copyright (C) 2023 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 + +GST_DEBUG_CATEGORY (gst_qt6_d3d11_debug); + +static gboolean +plugin_init (GstPlugin * plugin) +{ + GST_DEBUG_CATEGORY_INIT (gst_qt6_d3d11_debug, "qt6d3d11", 0, "qt6d3d11"); + + gst_element_register (plugin, "qml6d3d11sink", GST_RANK_NONE, + GST_TYPE_QML6_D3D11_SINK); + + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + qt6d3d11, + "Qt6 Direct3D11 plugin", + plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/subprojects/gst-plugins-bad/meson_options.txt b/subprojects/gst-plugins-bad/meson_options.txt index 1cedb2222c..a29d89d3db 100644 --- a/subprojects/gst-plugins-bad/meson_options.txt +++ b/subprojects/gst-plugins-bad/meson_options.txt @@ -149,6 +149,7 @@ option('opensles', type : 'feature', value : 'auto', description : 'OpenSL ES au option('opus', type : 'feature', value : 'auto', description : 'OPUS audio parser plugin') option('qroverlay', type : 'feature', value : 'auto', description : 'Element to set random data on a qroverlay') option('qsv', type : 'feature', value : 'auto', description : 'Intel Quick Sync Video plugin') +option('qt6d3d11', type : 'feature', value : 'auto', description : 'Qt6 Direct3D11 plugin') option('resindvd', type : 'feature', value : 'auto', description : 'Resin DVD playback plugin (GPL - only built if gpl option is also enabled!)') option('rsvg', type : 'feature', value : 'auto', description : 'SVG overlayer and image decoder plugin') option('rtmp', type : 'feature', value : 'auto', description : 'RTMP video network source and sink plugin') diff --git a/subprojects/gst-plugins-bad/tests/examples/meson.build b/subprojects/gst-plugins-bad/tests/examples/meson.build index 29838f7167..d6a1e1ade9 100644 --- a/subprojects/gst-plugins-bad/tests/examples/meson.build +++ b/subprojects/gst-plugins-bad/tests/examples/meson.build @@ -14,6 +14,7 @@ subdir('mxf') subdir('nvcodec') subdir('opencv', if_found: opencv_dep) subdir('qsv') +subdir('qt6d3d11') subdir('uvch264') subdir('va') subdir('waylandsink') diff --git a/subprojects/gst-plugins-bad/tests/examples/qt6d3d11/meson.build b/subprojects/gst-plugins-bad/tests/examples/qt6d3d11/meson.build new file mode 100644 index 0000000000..c4f95d0ff1 --- /dev/null +++ b/subprojects/gst-plugins-bad/tests/examples/qt6d3d11/meson.build @@ -0,0 +1,14 @@ +if not have_qt6d3d11 + subdir_done() +endif + +qt6_example_deps = dependency('qt6', + modules : ['Core', 'Gui', 'Widgets', 'Qml', 'Quick'], + required: get_option('examples')) + +if not qt6_example_deps.found() + subdir_done() +endif + +subdir('qml6d3d11sink') +subdir('qml6d3d11sink-dyn-add') \ No newline at end of file diff --git a/subprojects/gst-plugins-bad/tests/examples/qt6d3d11/qml6d3d11sink-dyn-add/meson.build b/subprojects/gst-plugins-bad/tests/examples/qt6d3d11/qml6d3d11sink-dyn-add/meson.build new file mode 100644 index 0000000000..45fc332a82 --- /dev/null +++ b/subprojects/gst-plugins-bad/tests/examples/qt6d3d11/qml6d3d11sink-dyn-add/meson.build @@ -0,0 +1,8 @@ +qt_preprocessed = qt6_mod.preprocess(qresources : 'qml6d3d11sink-dyn-add.qrc') +executable('qml6d3d11sink-dyn-add', 'qml6d3d11sink-dyn-add.cpp', qt_preprocessed, + dependencies : [gst_dep, qt6_example_deps], + override_options : ['cpp_std=c++17'], + c_args : gst_plugins_bad_args, + cpp_args : gst_plugins_bad_args, + include_directories : [configinc], + install: false) diff --git a/subprojects/gst-plugins-bad/tests/examples/qt6d3d11/qml6d3d11sink-dyn-add/qml6d3d11sink-dyn-add.cpp b/subprojects/gst-plugins-bad/tests/examples/qt6d3d11/qml6d3d11sink-dyn-add/qml6d3d11sink-dyn-add.cpp new file mode 100644 index 0000000000..8946a332a5 --- /dev/null +++ b/subprojects/gst-plugins-bad/tests/examples/qt6d3d11/qml6d3d11sink-dyn-add/qml6d3d11sink-dyn-add.cpp @@ -0,0 +1,101 @@ +/* GStreamer + * Copyright (C) 2023 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. + */ + +#include +#include +#include +#include +#include +#include + +static int +run_application (int argc, char ** argv, GstElement * pipeline) +{ + QGuiApplication app(argc, argv); + int ret; + + /* NOTE: Default API on Windows is D3D11 already */ + QQuickWindow::setGraphicsApi(QSGRendererInterface::Direct3D11); + + QQmlApplicationEngine engine; + engine.load (QUrl (QStringLiteral ("qrc:/qml6d3d11sink-dyn-add.qml"))); + + QQuickWindow *rootObject; + + rootObject = static_cast (engine.rootObjects().first()); + + gst_element_set_state (pipeline, GST_STATE_PLAYING); + + GstElement *tee = gst_bin_get_by_name (GST_BIN (pipeline), "t"); + /* Add the qml6d3d11sink element on timer callback */ + QTimer::singleShot(5000, [&]() + { + GstElement *queue, *sink; + QQuickItem *videoItem = rootObject->findChild ("videoItem"); + + queue = gst_element_factory_make ("queue", nullptr); + sink = gst_element_factory_make ("qml6d3d11sink", nullptr); + + g_object_set (sink, "widget", videoItem, nullptr); + + gst_println ("Adding new qml6d3d11sink to pipeline"); + + gst_bin_add_many (GST_BIN (pipeline), queue, sink, nullptr); + + gst_element_sync_state_with_parent (sink); + gst_element_sync_state_with_parent (queue); + + gst_element_link_many (tee, queue, sink, nullptr); + + gst_object_unref (tee); + }); + + ret = app.exec(); + + gst_element_set_state (pipeline, GST_STATE_NULL); + + return ret; +} + +int +main(int argc, char *argv[]) +{ + GstElement *pipeline = nullptr; + GstElement *sink = nullptr; + int exit_code; + + gst_init (&argc, &argv); + + pipeline = gst_parse_launch ("d3d11testsrc ! " + "video/x-raw(memory:D3D11Memory),format=RGBA ! " + "tee name=t allow-not-linked=true ! queue ! fakesink sync=true", nullptr); + + /* the plugin must be loaded before loading the qml file to register the + * GstD3D11Qt6VideoItem */ + sink = gst_element_factory_make ("qml6d3d11sink", nullptr); + gst_object_unref (sink); + + exit_code = run_application (argc, argv, pipeline); + + gst_object_unref (pipeline); + + gst_deinit (); + + return exit_code; +} diff --git a/subprojects/gst-plugins-bad/tests/examples/qt6d3d11/qml6d3d11sink-dyn-add/qml6d3d11sink-dyn-add.qml b/subprojects/gst-plugins-bad/tests/examples/qt6d3d11/qml6d3d11sink-dyn-add/qml6d3d11sink-dyn-add.qml new file mode 100644 index 0000000000..81c02543c2 --- /dev/null +++ b/subprojects/gst-plugins-bad/tests/examples/qt6d3d11/qml6d3d11sink-dyn-add/qml6d3d11sink-dyn-add.qml @@ -0,0 +1,59 @@ +import QtQuick 6.0 +import QtQuick.Controls 6.0 +import QtQuick.Dialogs 6.0 +import QtQuick.Window 6.0 + +import org.freedesktop.gstreamer.Qt6D3D11VideoItem 1.0 + +ApplicationWindow { + id: window + visible: true + width: 640 + height: 480 + x: 30 + y: 30 + color: "black" + + Item { + anchors.fill: parent + + GstD3D11Qt6VideoItem { + id: video + objectName: "videoItem" + anchors.centerIn: parent + width: parent.width + height: parent.height + } + + Rectangle { + color: Qt.rgba(1, 1, 1, 0.3) + border.width: 1 + border.color: "white" + anchors.bottom: video.bottom + anchors.bottomMargin: 15 + anchors.horizontalCenter: parent.horizontalCenter + width : parent.width - 30 + height: parent.height - 30 + radius: 8 + + MouseArea { + id: mousearea + anchors.fill: parent + hoverEnabled: true + onEntered: { + parent.opacity = 1.0 + hidetimer.start() + } + } + + Timer { + id: hidetimer + interval: 5000 + onTriggered: { + parent.opacity = 0.0 + stop() + } + } + } + } +} diff --git a/subprojects/gst-plugins-bad/tests/examples/qt6d3d11/qml6d3d11sink-dyn-add/qml6d3d11sink-dyn-add.qrc b/subprojects/gst-plugins-bad/tests/examples/qt6d3d11/qml6d3d11sink-dyn-add/qml6d3d11sink-dyn-add.qrc new file mode 100644 index 0000000000..ef1a833d06 --- /dev/null +++ b/subprojects/gst-plugins-bad/tests/examples/qt6d3d11/qml6d3d11sink-dyn-add/qml6d3d11sink-dyn-add.qrc @@ -0,0 +1,5 @@ + + + qml6d3d11sink-dyn-add.qml + + diff --git a/subprojects/gst-plugins-bad/tests/examples/qt6d3d11/qml6d3d11sink/meson.build b/subprojects/gst-plugins-bad/tests/examples/qt6d3d11/qml6d3d11sink/meson.build new file mode 100644 index 0000000000..e63c211109 --- /dev/null +++ b/subprojects/gst-plugins-bad/tests/examples/qt6d3d11/qml6d3d11sink/meson.build @@ -0,0 +1,8 @@ +qt_preprocessed = qt6_mod.preprocess(qresources : 'qml6d3d11sink.qrc') +executable('qml6d3d11sink', 'qml6d3d11sink.cpp', qt_preprocessed, + dependencies : [gst_dep, qt6_example_deps], + override_options : ['cpp_std=c++17'], + c_args : gst_plugins_bad_args, + cpp_args : gst_plugins_bad_args, + include_directories : [configinc], + install: false) diff --git a/subprojects/gst-plugins-bad/tests/examples/qt6d3d11/qml6d3d11sink/qml6d3d11sink.cpp b/subprojects/gst-plugins-bad/tests/examples/qt6d3d11/qml6d3d11sink/qml6d3d11sink.cpp new file mode 100644 index 0000000000..2c09e6ebe0 --- /dev/null +++ b/subprojects/gst-plugins-bad/tests/examples/qt6d3d11/qml6d3d11sink/qml6d3d11sink.cpp @@ -0,0 +1,215 @@ +/* GStreamer + * Copyright (C) 2023 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. + */ + +#include +#include +#include +#include +#include + +#include + +class PipelineRunner : public QRunnable +{ +public: + PipelineRunner(GstElement * p); + ~PipelineRunner(); + + void run (); + +private: + GstElement *pipeline; +}; + +PipelineRunner::PipelineRunner (GstElement * p) +{ + pipeline = (GstElement *) gst_object_ref (p); +} + +PipelineRunner::~PipelineRunner () +{ + gst_object_unref (this->pipeline); +} + +void +PipelineRunner::run () +{ + gst_element_set_state (this->pipeline, GST_STATE_PLAYING); +} + +struct AppData +{ + QGuiApplication *app; + GstElement *pipeline; + GMainLoop *loop; + GMainContext *context; +}; + +static gboolean +message_cb (GstBus * bus, GstMessage * msg, AppData * data) +{ + gboolean do_exit = FALSE; + + switch (GST_MESSAGE_TYPE (msg)) { + case GST_MESSAGE_ERROR: + gst_println ("Got pipeline error"); + do_exit = TRUE; + break; + case GST_MESSAGE_EOS: + gst_println ("Got pipeline EOS"); + do_exit = TRUE; + break; + default: + break; + } + + if (do_exit) { + g_main_loop_quit (data->loop); + data->app->quit (); + } + + return G_SOURCE_CONTINUE; +} + +static gpointer +pipeline_watch_thread (AppData * data) +{ + GstBus *bus; + + g_main_context_push_thread_default (data->context); + + bus = gst_pipeline_get_bus (GST_PIPELINE (data->pipeline)); + gst_bus_add_watch (bus, (GstBusFunc) message_cb, data); + + g_main_loop_run (data->loop); + + gst_bus_remove_watch (bus); + gst_object_unref (bus); + g_main_context_pop_thread_default (data->context); + + return nullptr; +} + +static int +run_application (int argc, char ** argv, GstElement * pipeline, + GstElement * sink) +{ + QGuiApplication app (argc, argv); + GThread *bus_thread; + int ret; + + /* NOTE: Default API on Windows is D3D11 already */ + QQuickWindow::setGraphicsApi (QSGRendererInterface::Direct3D11); + + QQmlApplicationEngine engine; + engine.load (QUrl (QStringLiteral ("qrc:/qml6d3d11sink.qml"))); + + QQuickItem *videoItem; + QQuickWindow *rootObject; + AppData app_data; + + app_data.app = &app; + app_data.pipeline = pipeline; + app_data.context = g_main_context_new (); + app_data.loop = g_main_loop_new (app_data.context, FALSE); + + /* find and set the videoItem on the sink */ + rootObject = static_cast (engine.rootObjects().first()); + videoItem = rootObject->findChild ("videoItem"); + g_assert (videoItem); + g_object_set (sink, "widget", videoItem, nullptr); + + rootObject->scheduleRenderJob (new PipelineRunner (pipeline), + QQuickWindow::BeforeSynchronizingStage); + + bus_thread = g_thread_new ("pipeline-watch-thread", + (GThreadFunc) pipeline_watch_thread, &app_data); + + ret = app.exec(); + + g_main_loop_quit (app_data.loop); + g_thread_join (bus_thread); + g_main_loop_unref (app_data.loop); + g_main_context_unref (app_data.context); + + gst_element_set_state (pipeline, GST_STATE_NULL); + + return ret; +} + +int +main(int argc, char *argv[]) +{ + GOptionContext *option_ctx; + gchar *uri = nullptr; + GError *err = nullptr; + gboolean ret; + GOptionEntry options[] = { + {"uri", 0, 0, G_OPTION_ARG_STRING, &uri, "URI to play", nullptr}, + {nullptr, } + }; + GstElement *pipeline = nullptr; + GstElement *sink = nullptr; + int exit_code; + + option_ctx = g_option_context_new ("Qt6 Direct3D11 QML render"); + g_option_context_add_main_entries (option_ctx, options, nullptr); + g_option_context_add_group (option_ctx, gst_init_get_option_group ()); + ret = g_option_context_parse (option_ctx, &argc, &argv, &err); + g_option_context_free (option_ctx); + + if (!ret) { + gst_printerrln ("option parsing failed: %s", err->message); + g_clear_error (&err); + return 1; + } + + /* the plugin must be loaded before loading the qml file to register the + * GstD3D11Qt6VideoItem */ + sink = gst_element_factory_make ("qml6d3d11sink", nullptr); + + if (!uri) { + pipeline = gst_pipeline_new (nullptr); + + GstElement *src = gst_element_factory_make ("videotestsrc", nullptr); + GstElement *upload = gst_element_factory_make ("d3d11upload", nullptr); + GstElement *capsfilter = gst_element_factory_make ("capsfilter", nullptr); + GstCaps *caps; + + caps = gst_caps_from_string ("video/x-raw(memory:D3D11Memory),format=RGBA"); + g_object_set (capsfilter, "caps", caps, nullptr); + gst_caps_unref (caps); + + gst_bin_add_many (GST_BIN (pipeline), + src, upload, capsfilter, sink, nullptr); + gst_element_link_many (src, upload, capsfilter, sink, nullptr); + } else { + pipeline = gst_element_factory_make ("playbin", nullptr); + + g_object_set (pipeline, "uri", uri, "video-sink", sink, nullptr); + } + + exit_code = run_application (argc, argv, pipeline, sink); + + gst_object_unref (pipeline); + + gst_deinit (); + + return exit_code; +} diff --git a/subprojects/gst-plugins-bad/tests/examples/qt6d3d11/qml6d3d11sink/qml6d3d11sink.qml b/subprojects/gst-plugins-bad/tests/examples/qt6d3d11/qml6d3d11sink/qml6d3d11sink.qml new file mode 100644 index 0000000000..81c02543c2 --- /dev/null +++ b/subprojects/gst-plugins-bad/tests/examples/qt6d3d11/qml6d3d11sink/qml6d3d11sink.qml @@ -0,0 +1,59 @@ +import QtQuick 6.0 +import QtQuick.Controls 6.0 +import QtQuick.Dialogs 6.0 +import QtQuick.Window 6.0 + +import org.freedesktop.gstreamer.Qt6D3D11VideoItem 1.0 + +ApplicationWindow { + id: window + visible: true + width: 640 + height: 480 + x: 30 + y: 30 + color: "black" + + Item { + anchors.fill: parent + + GstD3D11Qt6VideoItem { + id: video + objectName: "videoItem" + anchors.centerIn: parent + width: parent.width + height: parent.height + } + + Rectangle { + color: Qt.rgba(1, 1, 1, 0.3) + border.width: 1 + border.color: "white" + anchors.bottom: video.bottom + anchors.bottomMargin: 15 + anchors.horizontalCenter: parent.horizontalCenter + width : parent.width - 30 + height: parent.height - 30 + radius: 8 + + MouseArea { + id: mousearea + anchors.fill: parent + hoverEnabled: true + onEntered: { + parent.opacity = 1.0 + hidetimer.start() + } + } + + Timer { + id: hidetimer + interval: 5000 + onTriggered: { + parent.opacity = 0.0 + stop() + } + } + } + } +} diff --git a/subprojects/gst-plugins-bad/tests/examples/qt6d3d11/qml6d3d11sink/qml6d3d11sink.qrc b/subprojects/gst-plugins-bad/tests/examples/qt6d3d11/qml6d3d11sink/qml6d3d11sink.qrc new file mode 100644 index 0000000000..179e18bb04 --- /dev/null +++ b/subprojects/gst-plugins-bad/tests/examples/qt6d3d11/qml6d3d11sink/qml6d3d11sink.qrc @@ -0,0 +1,5 @@ + + + qml6d3d11sink.qml + +