From ded003ce7ec5df7d6cad76759ae7debd1d55658a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Sun, 4 Feb 2024 15:54:54 +0100 Subject: [PATCH 1/2] gst-sink: Fix indent Gbp-Dch: Ignore --- src/livi-gst-sink.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/livi-gst-sink.c b/src/livi-gst-sink.c index 76f7573..39c4683 100644 --- a/src/livi-gst-sink.c +++ b/src/livi-gst-sink.c @@ -84,7 +84,7 @@ livi_gst_sink_get_times (GstBaseSink *bsink, *start = GST_BUFFER_TIMESTAMP (buf); if (GST_BUFFER_DURATION_IS_VALID (buf)) *end = *start + GST_BUFFER_DURATION (buf); - else{ + else { if (GST_VIDEO_INFO_FPS_N (&livi_sink->v_info) > 0) { *end = *start + gst_util_uint64_scale_int (GST_SECOND, @@ -349,11 +349,11 @@ livi_gst_sink_initialize_gl (LiviGstSink *self) wayland_display = gdk_wayland_display_get_wl_display (display); self->gst_display = GST_GL_DISPLAY (gst_gl_display_wayland_new_with_display (wayland_display)); self->gst_app_context = gst_gl_context_new_wrapped (self->gst_display, gl_handle, platform, gl_api); - }else { + } else { GST_ERROR_OBJECT (self, "Failed to get handle from GdkGLContext, not using Wayland EGL"); return FALSE; } - }else { + } else { GST_INFO_OBJECT (self, "Unsupported GDK display %s for GL", G_OBJECT_TYPE_NAME (display)); return FALSE; } @@ -369,7 +369,7 @@ livi_gst_sink_initialize_gl (LiviGstSink *self) g_clear_object (&self->gst_display); HANDLE_EXTERNAL_WGL_MAKE_CURRENT (self->gdk_context); return FALSE; - }else { + } else { DEACTIVATE_WGL_CONTEXT (self->gdk_context); gst_gl_context_activate (self->gst_app_context, FALSE); } -- GitLab From 58b38235685d004ec04514f047a1c1c2521d9c1e Mon Sep 17 00:00:00 2001 From: Robert Mader Date: Sat, 20 Jan 2024 23:47:51 +0100 Subject: [PATCH 2/2] sink: Add support for DMABuf import and graphics offload By implementing support for `GdkDmabufTextureBuilder` and `GstVideoInfoDmaDrm`. This allows zero-copy video playback on Wayland when paired with hardware video decoding. Mostly a straigh forward copy from `GtkGstSink`. Note that Gstreamer 1.23.1/1.24 is required. We bump the required GTK version due to the change in the UI file, which is hard to make conditional. --- meson.build | 16 +++- meson_options.txt | 3 + src/livi-gst-paintable.c | 51 +++++++++---- src/livi-gst-paintable.h | 8 +- src/livi-gst-sink.c | 161 ++++++++++++++++++++++++++++++++++++--- src/livi-window.ui | 16 ++-- src/meson.build | 15 +++- 7 files changed, 229 insertions(+), 41 deletions(-) create mode 100644 meson_options.txt diff --git a/meson.build b/meson.build index 63bdf5e..9ee31c9 100644 --- a/meson.build +++ b/meson.build @@ -18,10 +18,6 @@ config_h.set_quoted('PROJECT_NAME', meson.project_name()) config_h.set_quoted('PACKAGE_VERSION', meson.project_version()) config_h.set_quoted('GETTEXT_PACKAGE', 'livi') config_h.set_quoted('LOCALEDIR', join_paths(get_option('prefix'), get_option('localedir'))) -configure_file( - output: 'livi-config.h', - configuration: config_h, -) global_c_args = [ '-I' + meson.project_build_root() ] test_c_args = [ @@ -89,6 +85,18 @@ configure_file( output: 'run', configuration: run_data) +configure_file( + output: 'livi-config.h', + configuration: config_h, +) + +summary({ + 'Gst dmabuf passthrough support': dmabuf_passthrough, + }, + bool_yn: true, + section: 'Build', +) + gnome.post_install( glib_compile_schemas: true, gtk_update_icon_cache: true, diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..6664e9c --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,3 @@ +option('dmabuf-passthrough', + type: 'feature', value: 'auto', + description: 'Whether to enable dmabuf passhthrough') diff --git a/src/livi-gst-paintable.c b/src/livi-gst-paintable.c index 44d84b1..e4b4ad3 100644 --- a/src/livi-gst-paintable.c +++ b/src/livi-gst-paintable.c @@ -22,6 +22,7 @@ struct _LiviGstPaintable { GdkPaintable *image; double pixel_aspect_ratio; + graphene_rect_t viewport; GdkGLContext *context; }; @@ -44,7 +45,24 @@ livi_gst_paintable_paintable_snapshot (GdkPaintable *paintable, LiviGstPaintable *self = LIVI_GST_PAINTABLE (paintable); if (self->image) - gdk_paintable_snapshot (self->image, snapshot, width, height); + { + float sx, sy; + + gtk_snapshot_save (snapshot); + + sx = gdk_paintable_get_intrinsic_width (self->image) / self->viewport.size.width; + sy = gdk_paintable_get_intrinsic_height (self->image) / self->viewport.size.height; + + gtk_snapshot_push_clip (snapshot, &GRAPHENE_RECT_INIT (0, 0, width, height)); + + gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (-self->viewport.origin.x * width / self->viewport.size.width, + -self->viewport.origin.y * height / self->viewport.size.height)); + + gdk_paintable_snapshot (self->image, snapshot, width * sx, height * sy); + + gtk_snapshot_pop (snapshot); + gtk_snapshot_restore (snapshot); + } } static GdkPaintable * @@ -64,8 +82,7 @@ livi_gst_paintable_paintable_get_intrinsic_width (GdkPaintable *paintable) LiviGstPaintable *self = LIVI_GST_PAINTABLE (paintable); if (self->image) - return round (self->pixel_aspect_ratio * - gdk_paintable_get_intrinsic_width (self->image)); + return round (self->pixel_aspect_ratio * self->viewport.size.width); return 0; } @@ -76,7 +93,7 @@ livi_gst_paintable_paintable_get_intrinsic_height (GdkPaintable *paintable) LiviGstPaintable *self = LIVI_GST_PAINTABLE (paintable); if (self->image) - return gdk_paintable_get_intrinsic_height (self->image); + return ceil (self->viewport.size.height); return 0; } @@ -87,8 +104,7 @@ livi_gst_paintable_paintable_get_intrinsic_aspect_ratio (GdkPaintable *paintable LiviGstPaintable *self = LIVI_GST_PAINTABLE (paintable); if (self->image) - return self->pixel_aspect_ratio * - gdk_paintable_get_intrinsic_aspect_ratio (self->image); + return self->viewport.size.width / self->viewport.size.height; return 0.0; }; @@ -201,9 +217,10 @@ livi_gst_paintable_unrealize (LiviGstPaintable *self, } static void -livi_gst_paintable_set_paintable (LiviGstPaintable *self, - GdkPaintable *paintable, - double pixel_aspect_ratio) +livi_gst_paintable_set_paintable (LiviGstPaintable *self, + GdkPaintable *paintable, + double pixel_aspect_ratio, + const graphene_rect_t *viewport) { gboolean size_changed; @@ -217,7 +234,8 @@ livi_gst_paintable_set_paintable (LiviGstPaintable *self, FLT_EPSILON) || !G_APPROX_VALUE (gdk_paintable_get_intrinsic_aspect_ratio (self->image), gdk_paintable_get_intrinsic_aspect_ratio (paintable), - FLT_EPSILON)) { + FLT_EPSILON) || + !graphene_rect_equal (viewport, &self->viewport)) { size_changed = TRUE; } else { size_changed = FALSE; @@ -225,6 +243,7 @@ livi_gst_paintable_set_paintable (LiviGstPaintable *self, g_set_object (&self->image, paintable); self->pixel_aspect_ratio = pixel_aspect_ratio; + self->viewport = *viewport; if (size_changed) gdk_paintable_invalidate_size (GDK_PAINTABLE (self)); @@ -236,6 +255,7 @@ typedef struct _SetTextureInvocation { LiviGstPaintable *paintable; GdkTexture *texture; double pixel_aspect_ratio; + graphene_rect_t viewport; } SetTextureInvocation; static void @@ -254,15 +274,17 @@ livi_gst_paintable_set_texture_invoke (gpointer data) livi_gst_paintable_set_paintable (invoke->paintable, GDK_PAINTABLE (invoke->texture), - invoke->pixel_aspect_ratio); + invoke->pixel_aspect_ratio, + &invoke->viewport); return G_SOURCE_REMOVE; } void -livi_gst_paintable_queue_set_texture (LiviGstPaintable *self, - GdkTexture *texture, - double pixel_aspect_ratio) +livi_gst_paintable_queue_set_texture (LiviGstPaintable *self, + GdkTexture *texture, + double pixel_aspect_ratio, + const graphene_rect_t *viewport) { SetTextureInvocation *invoke; @@ -270,6 +292,7 @@ livi_gst_paintable_queue_set_texture (LiviGstPaintable *self, invoke->paintable = g_object_ref (self); invoke->texture = g_object_ref (texture); invoke->pixel_aspect_ratio = pixel_aspect_ratio; + invoke->viewport = *viewport; g_main_context_invoke_full (NULL, G_PRIORITY_DEFAULT, diff --git a/src/livi-gst-paintable.h b/src/livi-gst-paintable.h index 4d450ba..7a01085 100644 --- a/src/livi-gst-paintable.h +++ b/src/livi-gst-paintable.h @@ -11,6 +11,7 @@ #pragma once #include +#include G_BEGIN_DECLS @@ -24,8 +25,9 @@ void livi_gst_paintable_realize (LiviGstPaintable *self, GdkSurface *surface); void livi_gst_paintable_unrealize (LiviGstPaintable *self, GdkSurface *surface); -void livi_gst_paintable_queue_set_texture (LiviGstPaintable *self, - GdkTexture *texture, - double pixel_aspect_ratio); +void livi_gst_paintable_queue_set_texture (LiviGstPaintable *self, + GdkTexture *texture, + double pixel_aspect_ratio, + const graphene_rect_t *viewport); G_END_DECLS diff --git a/src/livi-gst-sink.c b/src/livi-gst-sink.c index 39c4683..ec7ee54 100644 --- a/src/livi-gst-sink.c +++ b/src/livi-gst-sink.c @@ -12,6 +12,7 @@ * Copyright (C) 2015 Matthew Waters */ +#include "livi-config.h" #include "livi-gst-sink.h" #include "livi-gst-paintable.h" @@ -21,6 +22,11 @@ #include +#ifdef HAVE_GSTREAMER_DRM +#include +#include +#endif + enum { PROP_0, PROP_PAINTABLE, @@ -39,6 +45,10 @@ struct _LiviGstSink { GstGLDisplay *gst_display; GstGLContext *gst_app_context; GstGLContext *gst_context; + +#ifdef HAVE_GSTREAMER_DRM + GstVideoInfoDmaDrm drm_info; +#endif }; @@ -53,7 +63,11 @@ static GstStaticPadTemplate livi_gst_sink_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, - GST_STATIC_CAPS ("video/x-raw(" GST_CAPS_FEATURE_MEMORY_GL_MEMORY "), " + GST_STATIC_CAPS ( +#ifdef HAVE_GSTREAMER_DRM + GST_VIDEO_DMA_DRM_CAPS_MAKE "; " +#endif + "video/x-raw(" GST_CAPS_FEATURE_MEMORY_GL_MEMORY "), " "format = (string) RGBA, " "width = " GST_VIDEO_SIZE_RANGE ", " "height = " GST_VIDEO_SIZE_RANGE ", " @@ -95,6 +109,41 @@ livi_gst_sink_get_times (GstBaseSink *bsink, } } +#ifdef HAVE_GSTREAMER_DRM +static void +add_drm_formats_and_modifiers (GstCaps *caps, + GdkDmabufFormats *dmabuf_formats) +{ + GValue dmabuf_list = G_VALUE_INIT; + size_t i; + + g_value_init (&dmabuf_list, GST_TYPE_LIST); + + for (i = 0; i < gdk_dmabuf_formats_get_n_formats (dmabuf_formats); i++) { + GValue value = G_VALUE_INIT; + gchar *drm_format_string; + guint32 fmt; + guint64 mod; + + gdk_dmabuf_formats_get_format (dmabuf_formats, i, &fmt, &mod); + + if (mod == DRM_FORMAT_MOD_INVALID) + continue; + + drm_format_string = gst_video_dma_drm_fourcc_to_string (fmt, mod); + if (!drm_format_string) + continue; + + g_value_init (&value, G_TYPE_STRING); + g_value_take_string (&value, drm_format_string); + gst_value_list_append_and_take_value (&dmabuf_list, &value); + } + + gst_structure_take_value (gst_caps_get_structure (caps, 0), "drm-format", + &dmabuf_list); +} +#endif + static GstCaps * livi_gst_sink_get_caps (GstBaseSink *bsink, GstCaps *filter) @@ -105,7 +154,16 @@ livi_gst_sink_get_caps (GstBaseSink *bsink, if (self->gst_context) { tmp = gst_pad_get_pad_template_caps (GST_BASE_SINK_PAD (bsink)); - }else { +#ifdef HAVE_GSTREAMER_DRM + { + GdkDisplay *display = gdk_gl_context_get_display (self->gdk_context); + GdkDmabufFormats *formats = gdk_display_get_dmabuf_formats (display); + + tmp = gst_caps_make_writable (tmp); + add_drm_formats_and_modifiers (tmp, formats); + } +#endif + } else { tmp = gst_caps_from_string (NOGL_CAPS); } GST_DEBUG_OBJECT (self, "advertising own caps %" GST_PTR_FORMAT, tmp); @@ -131,8 +189,22 @@ livi_gst_sink_set_caps (GstBaseSink *bsink, GST_DEBUG_OBJECT (self, "set caps with %" GST_PTR_FORMAT, caps); - if (!gst_video_info_from_caps (&self->v_info, caps)) - return FALSE; +#ifdef HAVE_GSTREAMER_DRM + if (gst_video_is_dma_drm_caps (caps)) { + if (!gst_video_info_dma_drm_from_caps (&self->drm_info, caps)) + return FALSE; + + if (!gst_video_info_dma_drm_to_video_info (&self->drm_info, &self->v_info)) + return FALSE; + } else { + gst_video_info_dma_drm_init (&self->drm_info); +#endif + + if (!gst_video_info_from_caps (&self->v_info, caps)) + return FALSE; +#ifdef HAVE_GSTREAMER_DRM + } +#endif return TRUE; } @@ -173,6 +245,13 @@ livi_gst_sink_propose_allocation (GstBaseSink *bsink, return FALSE; } +#ifdef HAVE_GSTREAMER_DRM + if (gst_caps_features_contains (gst_caps_get_features (caps, 0), GST_CAPS_FEATURE_MEMORY_DMABUF)) { + gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, 0); + return TRUE; + } +#endif + if (!gst_caps_features_contains (gst_caps_get_features (caps, 0), GST_CAPS_FEATURE_MEMORY_GL_MEMORY)) return FALSE; @@ -241,16 +320,77 @@ video_frame_free (GstVideoFrame *frame) } static GdkTexture * -livi_gst_sink_texture_from_buffer (LiviGstSink *self, - GstBuffer *buffer, - double *pixel_aspect_ratio) +livi_gst_sink_texture_from_buffer (LiviGstSink *self, + GstBuffer *buffer, + double *pixel_aspect_ratio, + graphene_rect_t *viewport) { GstVideoFrame *frame = g_new (GstVideoFrame, 1); GdkTexture *texture; - g_autoptr (GdkGLTextureBuilder) builder = NULL; + viewport->origin.x = 0; + viewport->origin.y = 0; + viewport->size.width = GST_VIDEO_INFO_WIDTH (&self->v_info); + viewport->size.height = GST_VIDEO_INFO_HEIGHT (&self->v_info); + +#ifdef HAVE_GSTREAMER_DRM + if (gst_is_dmabuf_memory (gst_buffer_peek_memory (buffer, 0))) { + g_autoptr (GdkDmabufTextureBuilder) builder = NULL; + const GstVideoMeta *vmeta = gst_buffer_get_video_meta (buffer); + GError *error = NULL; + int i; + + /* We don't map dmabufs */ + g_clear_pointer (&frame, g_free); + + g_return_val_if_fail (vmeta, NULL); + g_return_val_if_fail (self->gdk_context, NULL); + g_return_val_if_fail (self->drm_info.drm_fourcc != DRM_FORMAT_INVALID, NULL); + + builder = gdk_dmabuf_texture_builder_new (); + gdk_dmabuf_texture_builder_set_display (builder, gdk_gl_context_get_display (self->gdk_context)); + gdk_dmabuf_texture_builder_set_fourcc (builder, self->drm_info.drm_fourcc); + gdk_dmabuf_texture_builder_set_modifier (builder, self->drm_info.drm_modifier); + gdk_dmabuf_texture_builder_set_width (builder, vmeta->width); + gdk_dmabuf_texture_builder_set_height (builder, vmeta->height); + gdk_dmabuf_texture_builder_set_n_planes (builder, vmeta->n_planes); + + for (i = 0; i < vmeta->n_planes; i++) { + GstMemory *mem; + guint mem_idx, length; + gsize skip; + + if (!gst_buffer_find_memory (buffer, + vmeta->offset[i], + 1, + &mem_idx, + &length, + &skip)) { + GST_ERROR_OBJECT (self, "Buffer data is bogus"); + return NULL; + } + + mem = gst_buffer_peek_memory (buffer, mem_idx); + + gdk_dmabuf_texture_builder_set_fd (builder, i, gst_dmabuf_memory_get_fd (mem)); + gdk_dmabuf_texture_builder_set_offset (builder, i, mem->offset + skip); + gdk_dmabuf_texture_builder_set_stride (builder, i, vmeta->stride[i]); + } + + texture = gdk_dmabuf_texture_builder_build (builder, + (GDestroyNotify) gst_buffer_unref, + gst_buffer_ref (buffer), + &error); + if (!texture) + GST_ERROR_OBJECT (self, "Failed to create dmabuf texture: %s", error->message); + + *pixel_aspect_ratio = ((double) GST_VIDEO_INFO_PAR_N (&self->v_info) / + (double) GST_VIDEO_INFO_PAR_D (&self->v_info)); + } else +#endif if (self->gdk_context && gst_video_frame_map (frame, &self->v_info, buffer, GST_MAP_READ | GST_MAP_GL)) { + g_autoptr (GdkGLTextureBuilder) builder = NULL; GstGLSyncMeta *sync_meta; sync_meta = gst_buffer_get_gl_sync_meta (buffer); @@ -300,6 +440,7 @@ livi_gst_sink_show_frame (GstVideoSink *vsink, LiviGstSink *self; g_autoptr (GdkTexture) texture = NULL; double pixel_aspect_ratio; + graphene_rect_t viewport; GST_TRACE ("rendering buffer:%p", buf); @@ -307,9 +448,9 @@ livi_gst_sink_show_frame (GstVideoSink *vsink, GST_OBJECT_LOCK (self); - texture = livi_gst_sink_texture_from_buffer (self, buf, &pixel_aspect_ratio); + texture = livi_gst_sink_texture_from_buffer (self, buf, &pixel_aspect_ratio, &viewport); if (texture) - livi_gst_paintable_queue_set_texture (self->paintable, texture, pixel_aspect_ratio); + livi_gst_paintable_queue_set_texture (self->paintable, texture, pixel_aspect_ratio, &viewport); GST_OBJECT_UNLOCK (self); diff --git a/src/livi-window.ui b/src/livi-window.ui index 7776e34..0e5ee2f 100644 --- a/src/livi-window.ui +++ b/src/livi-window.ui @@ -107,13 +107,17 @@ + center + True + True - - 300 - center - True - True - + + enabled + + + + + diff --git a/src/meson.build b/src/meson.build index ef87257..3337249 100644 --- a/src/meson.build +++ b/src/meson.build @@ -13,9 +13,15 @@ livi_sources = [ ] + generated_dbus_sources gst_ver = '>= 1.22' +gst_allocators_dep = dependency('gstreamer-allocators-1.0', version: gst_ver) -gstplay_dep = dependency('gstreamer-play-1.0', version: gst_ver) -gstgl_dep = dependency('gstreamer-gl-1.0', version: gst_ver) +dmabuf_passthrough = false +if not get_option('dmabuf-passthrough').disabled() + if gst_allocators_dep.version().version_compare('>= 1.23.1') + dmabuf_passthrough = true + endif +endif +config_h.set('HAVE_GSTREAMER_DRM', dmabuf_passthrough) gtk4_dep = dependency( 'gtk4', @@ -35,11 +41,12 @@ gtk4_dep = dependency( livi_deps = [ dependency('gio-2.0', version: '>= 2.50'), dependency('gstreamer-1.0', version: gst_ver), + gst_allocators_dep, + dependency('gstreamer-gl-1.0', version: gst_ver), + dependency('gstreamer-play-1.0', version: gst_ver), dependency('libadwaita-1', version: '>= 1.4'), gtk4_dep, cc.find_library('m', required: false), - gstplay_dep, - gstgl_dep, ] livi_sources += gnome.compile_resources('livi-resources', -- GitLab