// // Copyright (C) 2021 Bilal Elmoussaoui // Copyright (C) 2021 Jordan Petridis // Copyright (C) 2021-2024 Sebastian Dröge // // This Source Code Form is subject to the terms of the Mozilla Public License, v2.0. // If a copy of the MPL was not distributed with this file, You can obtain one at // . // // SPDX-License-Identifier: MPL-2.0 use super::SinkEvent; use crate::sink::frame::Frame; use crate::sink::paintable::Paintable; use glib::thread_guard::ThreadGuard; use gtk::prelude::*; use gtk::{gdk, glib}; #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] use gst_gl::prelude::GLContextExt as GstGLContextExt; #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] use gst_gl::prelude::*; #[allow(unused_imports)] use gst::prelude::*; use gst::subclass::prelude::*; use gst_base::subclass::prelude::*; use gst_video::subclass::prelude::*; use once_cell::sync::Lazy; use std::sync::{Mutex, MutexGuard}; use crate::utils; // Global GL context that is created by the first sink and kept around until the end of the // process. This is provided to other elements in the pipeline to make sure they create GL contexts // that are sharing with the GTK GL context. #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] enum GLContext { Uninitialized, Unsupported, Initialized { display: gst_gl::GLDisplay, wrapped_context: gst_gl::GLContext, gdk_context: ThreadGuard, }, } #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] static GL_CONTEXT: Mutex = Mutex::new(GLContext::Uninitialized); pub(crate) static CAT: Lazy = Lazy::new(|| { gst::DebugCategory::new( "gtk4paintablesink", gst::DebugColorFlags::empty(), Some("GTK4 Paintable sink"), ) }); #[derive(Default)] pub struct PaintableSink { paintable: Mutex>>, window: Mutex>>, info: Mutex>, sender: Mutex>>, pending_frame: Mutex>, cached_caps: Mutex>, } impl Drop for PaintableSink { fn drop(&mut self) { let mut paintable = self.paintable.lock().unwrap(); if let Some(paintable) = paintable.take() { glib::MainContext::default().invoke(|| drop(paintable)) } } } #[glib::object_subclass] impl ObjectSubclass for PaintableSink { const NAME: &'static str = "GstGtk4PaintableSink"; type Type = super::PaintableSink; type ParentType = gst_video::VideoSink; type Interfaces = (gst::ChildProxy,); } impl ObjectImpl for PaintableSink { fn properties() -> &'static [glib::ParamSpec] { static PROPERTIES: Lazy> = Lazy::new(|| { vec![ glib::ParamSpecObject::builder::("paintable") .nick("Paintable") .blurb("The Paintable the sink renders to") .read_only() .build(), ] }); PROPERTIES.as_ref() } fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { match pspec.name() { "paintable" => { // Fix segfault when GTK3 and GTK4 are loaded (e.g. `gst-inspect-1.0 -a`) // checking if GtkBin is registered to know if libgtk3.so is already present // GtkBin was dropped for GTK4 https://gitlab.gnome.org/GNOME/gtk/-/commit/3c165b3b77 if glib::types::Type::from_name("GtkBin").is_some() { gst::error!(CAT, imp: self, "Skipping the creation of paintable to avoid segfault between GTK3 and GTK4"); return None::<&gdk::Paintable>.to_value(); } let mut paintable_guard = self.paintable.lock().unwrap(); let mut created = false; if paintable_guard.is_none() { created = true; self.create_paintable(&mut paintable_guard); } let paintable = match &*paintable_guard { Some(ref paintable) => paintable, None => { gst::error!(CAT, imp: self, "Failed to create paintable"); return None::<&gdk::Paintable>.to_value(); } }; // Getter must be called from the main thread if !paintable.is_owner() { gst::error!( CAT, imp: self, "Can't retrieve Paintable from non-main thread" ); return None::<&gdk::Paintable>.to_value(); } let paintable = paintable.get_ref().clone(); drop(paintable_guard); if created { let self_ = self.to_owned(); glib::MainContext::default().invoke(move || { let paintable_guard = self_.paintable.lock().unwrap(); if let Some(paintable) = &*paintable_guard { let paintable_clone = paintable.get_ref().clone(); drop(paintable_guard); self_.obj().child_added(&paintable_clone, "paintable"); } }); } paintable.to_value() } _ => unimplemented!(), } } } impl GstObjectImpl for PaintableSink {} impl ElementImpl for PaintableSink { fn metadata() -> Option<&'static gst::subclass::ElementMetadata> { static ELEMENT_METADATA: Lazy = Lazy::new(|| { gst::subclass::ElementMetadata::new( "GTK 4 Paintable Sink", "Sink/Video", "A GTK 4 Paintable sink", "Bilal Elmoussaoui , Jordan Petridis , Sebastian Dröge ", ) }); Some(&*ELEMENT_METADATA) } fn pad_templates() -> &'static [gst::PadTemplate] { static PAD_TEMPLATES: Lazy> = Lazy::new(|| { // Those are the supported formats by a gdk::Texture let mut caps = gst::Caps::new_empty(); { let caps = caps.get_mut().unwrap(); #[cfg(all(target_os = "linux", feature = "dmabuf"))] { for features in [ [ gst_allocators::CAPS_FEATURE_MEMORY_DMABUF, gst_video::CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, ] .as_slice(), [gst_allocators::CAPS_FEATURE_MEMORY_DMABUF].as_slice(), ] { let c = gst_video::VideoCapsBuilder::new() .format(gst_video::VideoFormat::DmaDrm) .features(features.iter().copied()) .build(); caps.append(c); } } for features in [ #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] Some(gst::CapsFeatures::new([ gst_gl::CAPS_FEATURE_MEMORY_GL_MEMORY, gst_video::CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, ])), #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] Some(gst::CapsFeatures::new([ gst_gl::CAPS_FEATURE_MEMORY_GL_MEMORY, ])), Some(gst::CapsFeatures::new([ "memory:SystemMemory", gst_video::CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, ])), Some(gst::CapsFeatures::new([ gst_video::CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, ])), None, ] { #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] { const GL_FORMATS: &[gst_video::VideoFormat] = &[gst_video::VideoFormat::Rgba, gst_video::VideoFormat::Rgb]; const NON_GL_FORMATS: &[gst_video::VideoFormat] = &[ gst_video::VideoFormat::Bgra, gst_video::VideoFormat::Argb, gst_video::VideoFormat::Rgba, gst_video::VideoFormat::Abgr, gst_video::VideoFormat::Rgb, gst_video::VideoFormat::Bgr, ]; let formats = if features.as_ref().is_some_and(|features| { features.contains(gst_gl::CAPS_FEATURE_MEMORY_GL_MEMORY) }) { GL_FORMATS } else { NON_GL_FORMATS }; let mut c = gst_video::video_make_raw_caps(formats).build(); if let Some(features) = features { let c = c.get_mut().unwrap(); if features.contains(gst_gl::CAPS_FEATURE_MEMORY_GL_MEMORY) { c.set("texture-target", "2D") } c.set_features_simple(Some(features)); } caps.append(c); } #[cfg(not(any( target_os = "macos", target_os = "windows", feature = "gst-gl" )))] { const FORMATS: &[gst_video::VideoFormat] = &[ gst_video::VideoFormat::Bgra, gst_video::VideoFormat::Argb, gst_video::VideoFormat::Rgba, gst_video::VideoFormat::Abgr, gst_video::VideoFormat::Rgb, gst_video::VideoFormat::Bgr, ]; let mut c = gst_video::video_make_raw_caps(FORMATS).build(); if let Some(features) = features { let c = c.get_mut().unwrap(); c.set_features_simple(Some(features)); } caps.append(c); } } } vec![gst::PadTemplate::new( "sink", gst::PadDirection::Sink, gst::PadPresence::Always, &caps, ) .unwrap()] }); PAD_TEMPLATES.as_ref() } #[allow(clippy::single_match)] fn change_state( &self, transition: gst::StateChange, ) -> Result { match transition { gst::StateChange::NullToReady => { let create_window = glib::program_name().as_deref() == Some("gst-launch-1.0") || std::env::var("GST_GTK4_WINDOW").as_deref() == Ok("1"); if create_window { let res = utils::invoke_on_main_thread(gtk::init); if let Err(err) = res { gst::error!(CAT, imp: self, "Failed to create initialize GTK: {err}"); return Err(gst::StateChangeError); } } let mut paintable_guard = self.paintable.lock().unwrap(); let mut created = false; if paintable_guard.is_none() { created = true; self.create_paintable(&mut paintable_guard); } if paintable_guard.is_none() { gst::error!(CAT, imp: self, "Failed to create paintable"); return Err(gst::StateChangeError); } drop(paintable_guard); if created { let self_ = self.to_owned(); glib::MainContext::default().invoke(move || { let paintable_guard = self_.paintable.lock().unwrap(); if let Some(paintable) = &*paintable_guard { let paintable_clone = paintable.get_ref().clone(); drop(paintable_guard); self_.obj().child_added(&paintable_clone, "paintable"); } }); } // Notify the pipeline about the GL display and wrapped context so that any other // elements in the pipeline ideally use the same / create GL contexts that are // sharing with this one. #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] { let gl_context = GL_CONTEXT.lock().unwrap(); if let GLContext::Initialized { display, wrapped_context, .. } = &*gl_context { let display = display.clone(); let wrapped_context = wrapped_context.clone(); drop(gl_context); gst_gl::gl_element_propagate_display_context(&*self.obj(), &display); let mut ctx = gst::Context::new("gst.gl.app_context", true); { let ctx = ctx.get_mut().unwrap(); ctx.structure_mut().set("context", &wrapped_context); } let _ = self.obj().post_message( gst::message::HaveContext::builder(ctx) .src(&*self.obj()) .build(), ); } } if create_window { self.create_window(); } } _ => (), } let res = self.parent_change_state(transition); match transition { gst::StateChange::PausedToReady => { let _ = self.info.lock().unwrap().take(); let _ = self.pending_frame.lock().unwrap().take(); // Flush frames from the GDK paintable but don't wait // for this to finish as this can other deadlock. let self_ = self.to_owned(); glib::MainContext::default().invoke(move || { let paintable = self_.paintable.lock().unwrap(); if let Some(paintable) = &*paintable { paintable.get_ref().handle_flush_frames(); } }); } gst::StateChange::ReadyToNull => { let mut window_guard = self.window.lock().unwrap(); if let Some(window) = window_guard.take() { drop(window_guard); glib::MainContext::default().invoke(move || { let window = window.get_ref(); window.close(); }); } } _ => (), } res } } impl BaseSinkImpl for PaintableSink { fn caps(&self, filter: Option<&gst::Caps>) -> Option { let cached_caps = self .cached_caps .lock() .expect("Failed to lock cached caps mutex") .clone(); let mut tmp_caps = cached_caps.unwrap_or_else(|| { let templ = Self::pad_templates(); templ[0].caps().clone() }); gst::debug!(CAT, imp: self, "Advertising our own caps: {tmp_caps:?}"); if let Some(filter_caps) = filter { gst::debug!( CAT, imp: self, "Intersecting with filter caps: {filter_caps:?}", ); tmp_caps = filter_caps.intersect_with_mode(&tmp_caps, gst::CapsIntersectMode::First); }; gst::debug!(CAT, imp: self, "Returning caps: {tmp_caps:?}"); Some(tmp_caps) } fn set_caps(&self, caps: &gst::Caps) -> Result<(), gst::LoggableError> { gst::debug!(CAT, imp: self, "Setting caps {caps:?}"); #[allow(unused_mut)] let mut video_info = None; #[cfg(all(target_os = "linux", feature = "dmabuf"))] { if let Ok(info) = gst_video::VideoInfoDmaDrm::from_caps(caps) { video_info = Some(info.into()); } } let video_info = match video_info { Some(info) => info, None => gst_video::VideoInfo::from_caps(caps) .map_err(|_| gst::loggable_error!(CAT, "Invalid caps"))? .into(), }; self.info.lock().unwrap().replace(video_info); Ok(()) } fn propose_allocation( &self, query: &mut gst::query::Allocation, ) -> Result<(), gst::LoggableError> { gst::debug!(CAT, imp: self, "Proposing Allocation query"); self.parent_propose_allocation(query)?; query.add_allocation_meta::(None); // TODO: Provide a preferred "window size" here for higher-resolution rendering query.add_allocation_meta::(None); #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] { if let GLContext::Initialized { wrapped_context, .. } = &*GL_CONTEXT.lock().unwrap() { if wrapped_context.check_feature("GL_ARB_sync") || wrapped_context.check_feature("GL_EXT_EGL_sync") { query.add_allocation_meta::(None) } } } Ok(()) } fn query(&self, query: &mut gst::QueryRef) -> bool { gst::log!(CAT, imp: self, "Handling query {:?}", query); match query.view_mut() { #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] gst::QueryViewMut::Context(q) => { // Avoid holding the locks while we respond to the query // The objects are ref-counted anyway. let mut display_clone = None; let mut wrapped_context_clone = None; if let GLContext::Initialized { display, wrapped_context, .. } = &*GL_CONTEXT.lock().unwrap() { display_clone = Some(display.clone()); wrapped_context_clone = Some(wrapped_context.clone()); } if let (Some(display), Some(wrapped_context)) = (display_clone, wrapped_context_clone) { return gst_gl::functions::gl_handle_context_query( &*self.obj(), q, Some(&display), None::<&gst_gl::GLContext>, Some(&wrapped_context), ); } BaseSinkImplExt::parent_query(self, query) } _ => BaseSinkImplExt::parent_query(self, query), } } } impl VideoSinkImpl for PaintableSink { fn show_frame(&self, buffer: &gst::Buffer) -> Result { gst::trace!(CAT, imp: self, "Rendering buffer {:?}", buffer); // Empty buffer, nothing to render if buffer.n_memory() == 0 { gst::trace!( CAT, imp: self, "Empty buffer, nothing to render. Returning." ); return Ok(gst::FlowSuccess::Ok); }; let info = self.info.lock().unwrap(); let info = info.as_ref().ok_or_else(|| { gst::error!(CAT, imp: self, "Received no caps yet"); gst::FlowError::NotNegotiated })?; let wrapped_context = { #[cfg(not(any(target_os = "macos", target_os = "windows", feature = "gst-gl")))] { None } #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] { let gl_context = GL_CONTEXT.lock().unwrap(); if let GLContext::Initialized { wrapped_context, .. } = &*gl_context { Some(wrapped_context.clone()) } else { None } } }; let frame = Frame::new(buffer, info, wrapped_context.as_ref()).map_err(|err| { gst::error!(CAT, imp: self, "Failed to map video frame"); err })?; self.pending_frame.lock().unwrap().replace(frame); let sender = self.sender.lock().unwrap(); let sender = sender.as_ref().ok_or_else(|| { gst::error!(CAT, imp: self, "Have no main thread sender"); gst::FlowError::Flushing })?; match sender.try_send(SinkEvent::FrameChanged) { Ok(_) => (), Err(async_channel::TrySendError::Full(_)) => { gst::warning!(CAT, imp: self, "Have too many pending frames"); } Err(async_channel::TrySendError::Closed(_)) => { gst::error!(CAT, imp: self, "Have main thread receiver shut down"); return Err(gst::FlowError::Flushing); } } Ok(gst::FlowSuccess::Ok) } } impl PaintableSink { fn pending_frame(&self) -> Option { self.pending_frame.lock().unwrap().take() } fn do_action(&self, action: SinkEvent) -> glib::ControlFlow { let paintable = self.paintable.lock().unwrap(); let paintable = match &*paintable { Some(paintable) => paintable, None => return glib::ControlFlow::Break, }; match action { SinkEvent::FrameChanged => { let Some(frame) = self.pending_frame() else { return glib::ControlFlow::Continue; }; gst::trace!(CAT, imp: self, "Frame changed"); paintable.get_ref().handle_frame_changed(&self.obj(), frame); } } glib::ControlFlow::Continue } fn configure_caps(&self) { #[allow(unused_mut)] let mut tmp_caps = Self::pad_templates()[0].caps().clone(); #[cfg(all(target_os = "linux", feature = "dmabuf"))] { let formats = utils::invoke_on_main_thread(move || { let Some(display) = gdk::Display::default() else { return vec![]; }; let dmabuf_formats = display.dmabuf_formats(); let mut formats = vec![]; let n_formats = dmabuf_formats.n_formats(); for i in 0..n_formats { let (fourcc, modifier) = dmabuf_formats.format(i); if fourcc == 0 || modifier == (u64::MAX >> 8) { continue; } formats.push(gst_video::dma_drm_fourcc_to_string(fourcc, modifier)); } formats }); if formats.is_empty() { // Filter out dmabufs caps from the template pads if we have no supported formats if !matches!(&*GL_CONTEXT.lock().unwrap(), GLContext::Initialized { .. }) { tmp_caps = tmp_caps .iter_with_features() .filter(|(_, features)| { !features.contains(gst_allocators::CAPS_FEATURE_MEMORY_DMABUF) }) .map(|(s, c)| (s.to_owned(), c.to_owned())) .collect::(); } } else { let tmp_caps = tmp_caps.make_mut(); for (s, f) in tmp_caps.iter_with_features_mut() { if f.contains(gst_allocators::CAPS_FEATURE_MEMORY_DMABUF) { s.set("drm-format", gst::List::new(&formats)); } } } } #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] { // Filter out GL caps from the template pads if we have no context if !matches!(&*GL_CONTEXT.lock().unwrap(), GLContext::Initialized { .. }) { tmp_caps = tmp_caps .iter_with_features() .filter(|(_, features)| { !features.contains(gst_gl::CAPS_FEATURE_MEMORY_GL_MEMORY) }) .map(|(s, c)| (s.to_owned(), c.to_owned())) .collect::(); } } self.cached_caps .lock() .expect("Failed to lock Mutex") .replace(tmp_caps); } fn create_window(&self) { let self_ = self.to_owned(); glib::MainContext::default().invoke(move || { let mut window_guard = self_.window.lock().unwrap(); if window_guard.is_some() { return; } let paintable = match &*self_.paintable.lock().unwrap() { Some(paintable) => paintable.get_ref().clone(), None => return, }; let window = gtk::Window::new(); let picture = gtk::Picture::new(); picture.set_paintable(Some(&paintable)); #[cfg(feature = "gtk_v4_14")] { let offload = gtk::GraphicsOffload::new(Some(&picture)); offload.set_enabled(gtk::GraphicsOffloadEnabled::Enabled); window.set_child(Some(&offload)); } #[cfg(not(feature = "gtk_v4_14"))] { window.set_child(Some(&picture)); } window.set_default_size(640, 480); window.connect_close_request({ let self_ = self_.clone(); move |_window| { if self_.window.lock().unwrap().is_some() { gst::element_imp_error!( self_, gst::ResourceError::NotFound, ("Output window was closed") ); } glib::Propagation::Proceed } }); window.show(); *window_guard = Some(ThreadGuard::new(window)); }); } fn create_paintable(&self, paintable_storage: &mut MutexGuard>>) { #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] { self.initialize_gl_context(); } self.configure_caps(); self.initialize_paintable(paintable_storage); } fn initialize_paintable( &self, paintable_storage: &mut MutexGuard>>, ) { gst::debug!(CAT, imp: self, "Initializing paintable"); // The channel for the SinkEvents let (sender, receiver) = async_channel::bounded(3); // Spawn an async task on the main context to handle the channel messages let main_context = glib::MainContext::default(); let self_ = self.downgrade(); main_context.spawn(async move { while let Ok(action) = receiver.recv().await { let Some(self_) = self_.upgrade() else { break; }; self_.do_action(action); } }); // Create the paintable from the main thread let paintable = utils::invoke_on_main_thread(move || { #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] { let gdk_context = if let GLContext::Initialized { gdk_context, .. } = &*GL_CONTEXT.lock().unwrap() { Some(gdk_context.get_ref().clone()) } else { None }; ThreadGuard::new(Paintable::new(gdk_context)) } #[cfg(not(any(target_os = "macos", target_os = "windows", feature = "gst-gl")))] { ThreadGuard::new(Paintable::new(None)) } }); **paintable_storage = Some(paintable); *self.sender.lock().unwrap() = Some(sender); } #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] fn initialize_gl_context(&self) { gst::debug!(CAT, imp: self, "Realizing GDK GL Context"); let self_ = self.to_owned(); utils::invoke_on_main_thread(move || { self_.initialize_gl_context_main(); }); } #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] fn initialize_gl_context_main(&self) { gst::debug!(CAT, imp: self, "Realizing GDK GL Context from main thread"); let mut gl_context_guard = GL_CONTEXT.lock().unwrap(); if !matches!(&*gl_context_guard, GLContext::Uninitialized) { gst::debug!(CAT, imp: self, "Already initialized GL context before"); return; } *gl_context_guard = GLContext::Unsupported; // This can return NULL but only happens in 2 situations: // * If the function is called before gtk_init // * If the function is called after gdk_display_close(default_display) // Both of which are treated as programming errors. // // However, when we are building the docs, gtk_init doesn't get called // and this would cause the documentation generation to error. // Thus its okayish to return None here and fallback to software // rendering, since this path isn't going to be used by applications // anyway. // // FIXME: add a couple more gtk_init checks across the codebase where // applicable since this is no longer going to panic. let gdk_display = match gdk::Display::default() { Some(display) => display, None => { gst::warning!(CAT, imp: self, "Failed to retrieve GDK display"); return; } }; let gdk_context = match gdk_display.create_gl_context() { Ok(gdk_context) => gdk_context, Err(err) => { gst::warning!(CAT, imp: self, "Failed to create GDK GL Context: {err}"); return; } }; match gdk_context.type_().name() { #[cfg(feature = "x11egl")] "GdkX11GLContextEGL" => (), #[cfg(feature = "x11glx")] "GdkX11GLContextGLX" => (), #[cfg(feature = "wayland")] "GdkWaylandGLContext" => (), #[cfg(target_os = "macos")] "GdkMacosGLContext" => (), #[cfg(target_os = "windows")] "GdkWin32GLContextWGL" => (), #[cfg(all(windows, feature = "winegl"))] "GdkWin32GLContextEGL" => (), display => { gst::error!(CAT, imp: self, "Unsupported GDK display {display} for GL"); return; } } gst::info!(CAT, imp: self, "Realizing GDK GL Context",); if let Err(err) = gdk_context.realize() { gst::warning!(CAT, imp: self, "Failed to realize GDK GL Context: {err}"); return; } gst::info!(CAT, imp: self, "Successfully realized GDK GL Context"); gdk_context.make_current(); let res = match gdk_context.type_().name() { #[cfg(feature = "x11egl")] "GdkX11GLContextEGL" => self.initialize_x11egl(gdk_display), #[cfg(feature = "x11glx")] "GdkX11GLContextGLX" => self.initialize_x11glx(gdk_display), #[cfg(feature = "wayland")] "GdkWaylandGLContext" => self.initialize_waylandegl(gdk_display), #[cfg(target_os = "macos")] "GdkMacosGLContext" => self.initialize_macosgl(gdk_display), #[cfg(target_os = "windows")] "GdkWin32GLContextWGL" => self.initialize_wgl(gdk_display, &gdk_context), #[cfg(all(target_os = "windows", feature = "winegl"))] "GdkWin32GLContextEGL" => self.initialize_winegl(gdk_display), display_type => { unreachable!("Unsupported GDK display {display_type} for GL"); } }; let (display, wrapped_context) = res.unwrap(); match wrapped_context.activate(true) { Ok(_) => gst::info!(CAT, imp: self, "Successfully activated GL Context"), Err(_) => { gst::error!(CAT, imp: self, "Failed to activate GL context",); return; } }; if let Err(err) = wrapped_context.fill_info() { gst::error!( CAT, imp: self, "Failed to fill info on the GL Context: {err}", ); // Deactivate the context upon failure if wrapped_context.activate(false).is_err() { gst::error!( CAT, imp: self, "Failed to deactivate the context after failing fill info", ); } return; } gst::info!(CAT, imp: self, "Successfully initialized GL Context"); *gl_context_guard = GLContext::Initialized { display, wrapped_context, gdk_context: ThreadGuard::new(gdk_context), }; } #[cfg(feature = "x11egl")] fn initialize_x11egl( &self, display: gdk::Display, ) -> Option<(gst_gl::GLDisplay, gst_gl::GLContext)> { gst::info!( CAT, imp: self, "Initializing GL for x11 EGL backend and display" ); let platform = gst_gl::GLPlatform::EGL; let (gl_api, _, _) = gst_gl::GLContext::current_gl_api(platform); let gl_ctx = gst_gl::GLContext::current_gl_context(platform); if gl_ctx == 0 { gst::error!(CAT, imp: self, "Failed to get handle from GdkGLContext"); return None; } // FIXME: bindings unsafe { use glib::translate::*; let display = display.downcast::().unwrap(); let x11_display = gdk_x11::ffi::gdk_x11_display_get_egl_display(display.to_glib_none().0); if x11_display.is_null() { gst::error!(CAT, imp: self, "Failed to get EGL display"); return None; } let gst_display = gst_gl_egl::ffi::gst_gl_display_egl_new_with_egl_display(x11_display); let gst_display = gst_gl::GLDisplay::from_glib_full(gst_display as *mut gst_gl::ffi::GstGLDisplay); let wrapped_context = gst_gl::GLContext::new_wrapped(&gst_display, gl_ctx, platform, gl_api); let wrapped_context = match wrapped_context { None => { gst::error!(CAT, imp: self, "Failed to create wrapped GL context"); return None; } Some(wrapped_context) => wrapped_context, }; Some((gst_display, wrapped_context)) } } #[cfg(feature = "x11glx")] fn initialize_x11glx( &self, display: gdk::Display, ) -> Option<(gst_gl::GLDisplay, gst_gl::GLContext)> { gst::info!( CAT, imp: self, "Initializing GL for x11 GLX backend and display" ); let platform = gst_gl::GLPlatform::GLX; let (gl_api, _, _) = gst_gl::GLContext::current_gl_api(platform); let gl_ctx = gst_gl::GLContext::current_gl_context(platform); if gl_ctx == 0 { gst::error!(CAT, imp: self, "Failed to get handle from GdkGLContext"); return None; } // FIXME: bindings unsafe { use glib::translate::*; let display = display.downcast::().unwrap(); let x11_display = gdk_x11::ffi::gdk_x11_display_get_xdisplay(display.to_glib_none().0); if x11_display.is_null() { gst::error!(CAT, imp: self, "Failed to get X11 display"); return None; } let gst_display = gst_gl_x11::ffi::gst_gl_display_x11_new_with_display(x11_display); let gst_display = gst_gl::GLDisplay::from_glib_full(gst_display as *mut gst_gl::ffi::GstGLDisplay); let wrapped_context = gst_gl::GLContext::new_wrapped(&gst_display, gl_ctx, platform, gl_api); let wrapped_context = match wrapped_context { None => { gst::error!(CAT, imp: self, "Failed to create wrapped GL context"); return None; } Some(wrapped_context) => wrapped_context, }; Some((gst_display, wrapped_context)) } } #[cfg(feature = "wayland")] fn initialize_waylandegl( &self, display: gdk::Display, ) -> Option<(gst_gl::GLDisplay, gst_gl::GLContext)> { gst::info!( CAT, imp: self, "Initializing GL for Wayland EGL backend and display" ); let platform = gst_gl::GLPlatform::EGL; let (gl_api, _, _) = gst_gl::GLContext::current_gl_api(platform); let gl_ctx = gst_gl::GLContext::current_gl_context(platform); if gl_ctx == 0 { gst::error!(CAT, imp: self, "Failed to get handle from GdkGLContext"); return None; } // FIXME: bindings unsafe { use glib::translate::*; // let wayland_display = gdk_wayland::WaylandDisplay::wl_display(display.downcast()); // get the ptr directly since we are going to use it raw let display = display.downcast::().unwrap(); let wayland_display = gdk_wayland::ffi::gdk_wayland_display_get_wl_display(display.to_glib_none().0); if wayland_display.is_null() { gst::error!(CAT, imp: self, "Failed to get Wayland display"); return None; } let gst_display = gst_gl_wayland::ffi::gst_gl_display_wayland_new_with_display(wayland_display); let gst_display = gst_gl::GLDisplay::from_glib_full(gst_display as *mut gst_gl::ffi::GstGLDisplay); let wrapped_context = gst_gl::GLContext::new_wrapped(&gst_display, gl_ctx, platform, gl_api); let wrapped_context = match wrapped_context { None => { gst::error!(CAT, imp: self, "Failed to create wrapped GL context"); return None; } Some(wrapped_context) => wrapped_context, }; Some((gst_display, wrapped_context)) } } #[cfg(target_os = "macos")] fn initialize_macosgl( &self, display: gdk::Display, ) -> Option<(gst_gl::GLDisplay, gst_gl::GLContext)> { gst::info!( CAT, imp: self, "Initializing GL for macOS backend and display" ); let platform = gst_gl::GLPlatform::CGL; let (gl_api, _, _) = gst_gl::GLContext::current_gl_api(platform); let gl_ctx = gst_gl::GLContext::current_gl_context(platform); if gl_ctx == 0 { gst::error!(CAT, imp: self, "Failed to get handle from GdkGLContext"); return None; } let gst_display = gst_gl::GLDisplay::new(); unsafe { let wrapped_context = gst_gl::GLContext::new_wrapped(&gst_display, gl_ctx, platform, gl_api); let wrapped_context = match wrapped_context { None => { gst::error!(CAT, imp: self, "Failed to create wrapped GL context"); return None; } Some(wrapped_context) => wrapped_context, }; Some((gst_display, wrapped_context)) } } #[cfg(target_os = "windows")] fn initialize_wgl( &self, _display: gdk::Display, context: &gdk::GLContext, ) -> Option<(gst_gl::GLDisplay, gst_gl::GLContext)> { gst::info!( CAT, imp: self, "Initializing GL with for Windows WGL backend and display." ); let platform = gst_gl::GLPlatform::WGL; let gl_api = if context.is_legacy() { gst_gl::GLAPI::OPENGL } else { gst_gl::GLAPI::OPENGL3 }; let gl_ctx = gst_gl::GLContext::current_gl_context(platform); if gl_ctx == 0 { gst::error!(CAT, imp: self, "Failed to get handle from GdkGLContext",); return None; } unsafe { let gst_display = if let Some(display) = gst_gl::GLDisplay::with_type(gst_gl::GLDisplayType::WIN32) { display } else { gst::error!(CAT, imp: self, "Failed to get GL display"); return None; }; gst_display.filter_gl_api(gl_api); let wrapped_context = gst_gl::GLContext::new_wrapped(&gst_display, gl_ctx, platform, gl_api); let wrapped_context = match wrapped_context { None => { gst::error!(CAT, imp: self, "Failed to create wrapped GL context"); return None; } Some(wrapped_context) => wrapped_context, }; Some((gst_display, wrapped_context)) } } #[cfg(all(target_os = "windows", feature = "winegl"))] fn initialize_winegl( &self, display: gdk::Display, ) -> Option<(gst_gl::GLDisplay, gst_gl::GLContext)> { gst::info!( CAT, imp: self, "Initializing GL with for Windows EGL backend and display." ); let platform = gst_gl::GLPlatform::EGL; let (gl_api, _, _) = gst_gl::GLContext::current_gl_api(platform); let gl_ctx = gst_gl::GLContext::current_gl_context(platform); if gl_ctx == 0 { gst::error!(CAT, imp: self, "Failed to get handle from GdkGLContext",); return None; } // FIXME: bindings unsafe { use gdk_win32::prelude::*; use glib::translate::*; let d = display.downcast::().unwrap(); let egl_display = d.egl_display().unwrap().as_ptr(); // TODO: On the binary distribution of GStreamer for Windows, this symbol is not there let gst_display = gst_gl_egl::ffi::gst_gl_display_egl_from_gl_display(egl_display.cast()); if gst_display.is_null() { gst::error!(CAT, imp: self, "Failed to get EGL display"); return None; } let gst_display = gst_gl::GLDisplay::from_glib_full(gst_display as *mut gst_gl::ffi::GstGLDisplay); gst_display.filter_gl_api(gl_api); let wrapped_context = gst_gl::GLContext::new_wrapped(&gst_display, gl_ctx, platform, gl_api); let wrapped_context = match wrapped_context { None => { gst::error!(CAT, imp: self, "Failed to create wrapped GL context"); return None; } Some(wrapped_context) => wrapped_context, }; Some((gst_display, wrapped_context)) } } } impl ChildProxyImpl for PaintableSink { fn child_by_index(&self, index: u32) -> Option { if index != 0 { return None; } let paintable = self.paintable.lock().unwrap(); paintable .as_ref() .filter(|p| p.is_owner()) .map(|p| p.get_ref().upcast_ref::().clone()) } fn child_by_name(&self, name: &str) -> Option { if name == "paintable" { return self.child_by_index(0); } None } fn children_count(&self) -> u32 { let paintable = self.paintable.lock().unwrap(); if paintable.is_some() { 1 } else { 0 } } }