// // Copyright (C) 2021 Bilal Elmoussaoui // Copyright (C) 2021 Jordan Petridis // Copyright (C) 2021 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, Sender}; use gtk::prelude::*; use gtk::{gdk, glib}; 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; #[cfg(any(target_os = "macos", feature = "gst_gl"))] use gst_gl::prelude::GLContextExt as GstGLContextExt; #[cfg(any(target_os = "macos", feature = "gst_gl"))] use gst_gl::prelude::*; #[cfg(any(target_os = "macos", feature = "gst_gl"))] use std::sync::atomic::{AtomicBool, Ordering}; static CAT: Lazy = Lazy::new(|| { gst::DebugCategory::new( "gtk4paintablesink", gst::DebugColorFlags::empty(), Some("GTK4 Paintable sink"), ) }); #[derive(Default)] pub struct PaintableSink { paintable: Mutex>>, info: Mutex>, sender: Mutex>>, pending_frame: Mutex>, #[cfg(any(target_os = "macos", feature = "gst_gl"))] gst_display: Mutex>, #[cfg(any(target_os = "macos", feature = "gst_gl"))] gst_app_context: Mutex>, #[cfg(any(target_os = "macos", feature = "gst_gl"))] gst_context: Mutex>, cached_caps: Mutex>, #[cfg(any(target_os = "macos", feature = "gst_gl"))] have_gl_context: AtomicBool, } 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; } 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" => { let mut paintable = self.paintable.lock().unwrap(); if paintable.is_none() { self.create_paintable(&mut paintable); } let paintable = match &*paintable { 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() { paintable.get_ref().to_value() } else { gst::error!( CAT, imp: self, "Can't retrieve Paintable from non-main thread" ); None::<&gdk::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(); for features in [ None, Some(gst::CapsFeatures::new([ "memory:GLMemory", "meta:GstVideoOverlayComposition", ])), Some(gst::CapsFeatures::new(["memory:GLMemory"])), Some(gst::CapsFeatures::new([ "memory:SystemMemory", "meta:GstVideoOverlayComposition", ])), Some(gst::CapsFeatures::new(["meta:GstVideoOverlayComposition"])), ] { let mut c = gst_video::video_make_raw_caps(&[ gst_video::VideoFormat::Bgra, gst_video::VideoFormat::Argb, gst_video::VideoFormat::Rgba, gst_video::VideoFormat::Abgr, gst_video::VideoFormat::Rgb, gst_video::VideoFormat::Bgr, ]) .build(); if let Some(features) = features { let c = c.get_mut().unwrap(); if features.contains("memory:GLMemory") { c.set("texture-target", "2D") } 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 mut paintable = self.paintable.lock().unwrap(); if paintable.is_none() { self.create_paintable(&mut paintable); } if paintable.is_none() { gst::error!(CAT, imp: self, "Failed to create paintable"); return Err(gst::StateChangeError); } drop(paintable); #[cfg(any(target_os = "macos", feature = "gst_gl"))] { if self.have_gl_context.load(Ordering::Relaxed) { if self.initialize_gl_wrapper() { // We must have a display at this point. let display = self.gst_display.lock().unwrap().clone().unwrap(); gst_gl::gl_element_propagate_display_context(&*self.obj(), &display); } else { self.have_gl_context.store(false, Ordering::Relaxed); } } } } _ => (), } 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(); let self_ = self.to_owned(); utils::invoke_on_main_thread(move || { let paintable = self_.paintable.lock().unwrap(); if let Some(paintable) = &*paintable { paintable.get_ref().handle_flush_frames(); } }); } #[cfg(any(target_os = "macos", feature = "gst_gl"))] gst::StateChange::ReadyToNull => { let _ = self.gst_context.lock().unwrap().take(); let _ = self.gst_app_context.lock().unwrap().take(); let _ = self.gst_display.lock().unwrap().take(); } _ => (), } 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); let video_info = gst_video::VideoInfo::from_caps(caps) .map_err(|_| gst::loggable_error!(CAT, "Invalid caps"))?; 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(not(any(target_os = "macos", feature = "gst_gl")))] { Ok(()) } #[cfg(any(target_os = "macos", feature = "gst_gl"))] { // Early return if there is no context initialized let gst_context = match &*self.gst_context.lock().unwrap() { None => { gst::debug!( CAT, imp: self, "Found no GL Context during propose_allocation." ); return Ok(()); } Some(gst_context) => gst_context.clone(), }; // GL specific things let (caps, need_pool) = query.get_owned(); if caps.is_empty() { return Err(gst::loggable_error!(CAT, "No caps where specified.")); } if let Some(f) = caps.features(0) { if !f.contains("memory:GLMemory") { gst::debug!( CAT, imp: self, "No 'memory:GLMemory' feature in caps: {}", caps ) } } let info = gst_video::VideoInfo::from_caps(&caps) .map_err(|_| gst::loggable_error!(CAT, "Failed to get VideoInfo from caps"))?; let size = info.size() as u32; let buffer_pool = gst_gl::GLBufferPool::new(&gst_context); if need_pool { gst::debug!(CAT, imp: self, "Creating new Pool"); let mut config = buffer_pool.config(); config.set_params(Some(&caps), size, 0, 0); config.add_option("GstBufferPoolOptionGLSyncMeta"); if let Err(err) = buffer_pool.set_config(config) { return Err(gst::loggable_error!( CAT, format!("Failed to set config in the GL BufferPool.: {}", err) )); } } // we need at least 2 buffer because we hold on to the last one query.add_allocation_pool(Some(&buffer_pool), size, 2, 0); if gst_context.check_feature("GL_ARB_sync") || gst_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", feature = "gst_gl"))] gst::QueryViewMut::Context(q) => { // Avoid holding the locks while we respond to the query // The objects are ref-counted anyway. let (gst_display, app_ctx, gst_ctx) = ( self.gst_display.lock().unwrap().clone(), self.gst_app_context.lock().unwrap().clone(), self.gst_context.lock().unwrap().clone(), ); if let (Some(gst_display), Some(app_ctx), Some(gst_ctx)) = (gst_display, app_ctx, gst_ctx) { return gst_gl::functions::gl_handle_context_query( &*self.obj(), q, Some(&gst_display), Some(&gst_ctx), Some(&app_ctx), ); } 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 have_gl_context = { #[cfg(not(any(target_os = "macos", feature = "gst_gl")))] { false } #[cfg(any(target_os = "macos", feature = "gst_gl"))] { self.have_gl_context.load(Ordering::Relaxed) } }; let frame = Frame::new(buffer, info, have_gl_context).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 })?; sender.send(SinkEvent::FrameChanged).map_err(|_| { gst::error!(CAT, imp: self, "Have main thread receiver shut down"); 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::Continue { let paintable = self.paintable.lock().unwrap(); let paintable = match &*paintable { Some(paintable) => paintable, None => return glib::Continue(false), }; match action { SinkEvent::FrameChanged => { gst::trace!(CAT, imp: self, "Frame changed"); paintable .get_ref() .handle_frame_changed(self.pending_frame()) } } glib::Continue(true) } fn configure_caps(&self) { #[allow(unused_mut)] let mut tmp_caps = Self::pad_templates()[0].caps().clone(); #[cfg(any(target_os = "macos", feature = "gst_gl"))] { // Filter out GL caps from the template pads if we have no context if !self.have_gl_context.load(Ordering::Relaxed) { tmp_caps = tmp_caps .iter_with_features() .filter(|(_, features)| !features.contains("memory:GLMemory")) .map(|(s, c)| (s.to_owned(), c.to_owned())) .collect::(); } } self.cached_caps .lock() .expect("Failed to lock Mutex") .replace(tmp_caps); } fn create_paintable(&self, paintable_storage: &mut MutexGuard>>) { #[allow(unused_mut)] let mut ctx = None; #[cfg(any(target_os = "macos", feature = "gst_gl"))] { if let Some(c) = self.realize_context() { self.have_gl_context.store(true, Ordering::Relaxed); ctx = Some(c); } } self.configure_caps(); self.initialize_paintable(ctx, paintable_storage); } fn initialize_paintable( &self, gl_context: Option>, paintable_storage: &mut MutexGuard>>, ) { gst::debug!(CAT, imp: self, "Initializing paintable"); let paintable = utils::invoke_on_main_thread(|| { // grab the context out of the fragile let ctx = gl_context.map(|f| f.into_inner()); ThreadGuard::new(Paintable::new(ctx)) }); // The channel for the SinkEvents let (sender, receiver) = glib::MainContext::channel(glib::PRIORITY_DEFAULT); let self_ = self.to_owned(); receiver.attach( None, glib::clone!( @weak self_ => @default-return glib::Continue(false), move |action| self_.do_action(action) ), ); **paintable_storage = Some(paintable); *self.sender.lock().unwrap() = Some(sender); } #[cfg(any(target_os = "macos", feature = "gst_gl"))] fn realize_context(&self) -> Option> { gst::debug!(CAT, imp: self, "Realizing GDK GL Context"); let self_ = self.to_owned(); utils::invoke_on_main_thread(move || -> Option> { gst::debug!( CAT, imp: self_, "Realizing GDK GL Context from main context" ); // 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 display = gdk::Display::default()?; let ctx = match display.create_gl_context() { Ok(ctx) => ctx, Err(err) => { gst::warning!(CAT, imp: self_, "Failed to create GDK GL Context: {err}"); return None; } }; match ctx.type_().name() { #[cfg(all(target_os = "linux", feature = "x11egl"))] "GdkX11GLContextEGL" => (), #[cfg(all(target_os = "linux", feature = "x11glx"))] "GdkX11GLContextGLX" => (), #[cfg(all(target_os = "linux", feature = "wayland"))] "GdkWaylandGLContext" => (), #[cfg(target_os = "macos")] "GdkMacosGLContext" => (), display => { gst::error!(CAT, imp: self_, "Unsupported GDK display {display} for GL"); return None; } } gst::info!(CAT, imp: &self_, "Realizing GDK GL Context",); match ctx.realize() { Ok(_) => { gst::info!(CAT, imp: self_, "Successfully realized GDK GL Context",); Some(ThreadGuard::new(ctx)) } Err(err) => { gst::warning!(CAT, imp: self_, "Failed to realize GDK GL Context: {err}",); None } } }) } #[cfg(any(target_os = "macos", feature = "gst_gl"))] fn initialize_gl_wrapper(&self) -> bool { gst::info!(CAT, imp: self, "Initializing GDK GL Context"); let self_ = self.to_owned(); utils::invoke_on_main_thread(move || self_.initialize_gl()) } #[cfg(any(target_os = "macos", feature = "gst_gl"))] fn initialize_gl(&self) -> bool { let ctx = { let paintable = self.paintable.lock().unwrap(); // Impossible to not have a paintable and GL context at this point paintable.as_ref().unwrap().get_ref().context().unwrap() }; let display = gtk::prelude::GLContextExt::display(&ctx) .expect("Failed to get GDK Display from GDK Context."); ctx.make_current(); let mut app_ctx_guard = self.gst_app_context.lock().unwrap(); let mut display_guard = self.gst_display.lock().unwrap(); match ctx.type_().name() { #[cfg(all(target_os = "linux", feature = "x11egl"))] "GdkX11GLContextEGL" => { self.initialize_x11egl(display, &mut display_guard, &mut app_ctx_guard); } #[cfg(all(target_os = "linux", feature = "x11glx"))] "GdkX11GLContextGLX" => { self.initialize_x11glx(display, &mut display_guard, &mut app_ctx_guard); } #[cfg(all(target_os = "linux", feature = "wayland"))] "GdkWaylandGLContext" => { self.initialize_waylandegl(display, &mut display_guard, &mut app_ctx_guard); } #[cfg(target_os = "macos")] "GdkMacosGLContext" => { self.initialize_macosgl(display, &mut display_guard, &mut app_ctx_guard); } _ => { unreachable!("Unsupported GDK display {display} for GL"); } }; // This should have been initialized once we are done with the platform checks let app_ctx = match &*app_ctx_guard { None => { assert!(display_guard.is_none()); return false; } Some(app_ctx) => app_ctx, }; let display = match &*display_guard { None => return false, Some(display) => display, }; match app_ctx.activate(true) { Ok(_) => gst::info!(CAT, imp: self, "Successfully activated GL Context."), Err(_) => { gst::error!(CAT, imp: self, "Failed to activate GL context",); *app_ctx_guard = None; *display_guard = None; return false; } }; if let Err(err) = app_ctx.fill_info() { gst::error!( CAT, imp: self, "Failed to fill info on the GL Context: {err}", ); // Deactivate the context upon failure if app_ctx.activate(false).is_err() { gst::error!( CAT, imp: self, "Failed to deactivate the context after failing fill info", ); } *app_ctx_guard = None; *display_guard = None; return false; } if app_ctx.activate(false).is_err() { gst::error!(CAT, imp: self, "Failed to deactivate GL context",); *app_ctx_guard = None; *display_guard = None; return false; } gst::info!( CAT, imp: self, "Successfully deactivated GL Context after fill_info" ); let gst_context = match display.create_context(app_ctx) { Ok(gst_context) => gst_context, Err(err) => { gst::error!(CAT, imp: self, "Could not create GL context: {err}"); *app_ctx_guard = None; *display_guard = None; return false; } }; match display.add_context(&gst_context) { Ok(_) => { let mut gst_ctx_guard = self.gst_context.lock().unwrap(); gst::info!(CAT, imp: self, "Successfully initialized GL Context"); gst_ctx_guard.replace(gst_context); true } Err(_) => { gst::error!(CAT, imp: self, "Could not add GL context to display"); *app_ctx_guard = None; *display_guard = None; false } } } #[cfg(all(target_os = "linux", feature = "x11egl"))] fn initialize_x11egl( &self, display: gdk::Display, display_guard: &mut Option, app_ctx_guard: &mut Option, ) { 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; } // FIXME: bindings unsafe { use glib::translate::*; let d = display.downcast::().unwrap(); let x11_display = gdk_x11::ffi::gdk_x11_display_get_egl_display(d.to_glib_none().0); if x11_display.is_null() { gst::error!(CAT, imp: self, "Failed to get EGL display"); return; } 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 gst_app_context = gst_gl::GLContext::new_wrapped(&gst_display, gl_ctx, platform, gl_api); let gst_app_context = match gst_app_context { None => { gst::error!(CAT, imp: self, "Failed to create wrapped GL context"); return; } Some(gst_app_context) => gst_app_context, }; display_guard.replace(gst_display); app_ctx_guard.replace(gst_app_context); } } #[cfg(all(target_os = "linux", feature = "x11glx"))] fn initialize_x11glx( &self, display: gdk::Display, display_guard: &mut Option, app_ctx_guard: &mut Option, ) { 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; } // FIXME: bindings unsafe { use glib::translate::*; let d = display.downcast::().unwrap(); let x11_display = gdk_x11::ffi::gdk_x11_display_get_xdisplay(d.to_glib_none().0); if x11_display.is_null() { gst::error!(CAT, imp: self, "Failed to get X11 display"); return; } 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 gst_app_context = gst_gl::GLContext::new_wrapped(&gst_display, gl_ctx, platform, gl_api); let gst_app_context = match gst_app_context { None => { gst::error!(CAT, imp: self, "Failed to create wrapped GL context"); return; } Some(gst_app_context) => gst_app_context, }; display_guard.replace(gst_display); app_ctx_guard.replace(gst_app_context); } } #[cfg(all(target_os = "linux", feature = "wayland"))] fn initialize_waylandegl( &self, display: gdk::Display, display_guard: &mut Option, app_ctx_guard: &mut Option, ) { 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; } // 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 d = display.downcast::().unwrap(); let wayland_display = gdk_wayland::ffi::gdk_wayland_display_get_wl_display(d.to_glib_none().0); if wayland_display.is_null() { gst::error!(CAT, imp: self, "Failed to get Wayland display"); return; } 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 gst_app_context = gst_gl::GLContext::new_wrapped(&gst_display, gl_ctx, platform, gl_api); let gst_app_context = match gst_app_context { None => { gst::error!(CAT, imp: self, "Failed to create wrapped GL context"); return; } Some(gst_app_context) => gst_app_context, }; display_guard.replace(gst_display); app_ctx_guard.replace(gst_app_context); } } #[cfg(target_os = "macos")] fn initialize_macosgl( &self, display: gdk::Display, display_guard: &mut Option, app_ctx_guard: &mut Option, ) { 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; } let gst_display = gst_gl::GLDisplay::new(); unsafe { let gst_app_context = gst_gl::GLContext::new_wrapped(&gst_display, gl_ctx, platform, gl_api); let gst_app_context = match gst_app_context { None => { gst::error!(CAT, imp: self, "Failed to create wrapped GL context"); return; } Some(gst_app_context) => gst_app_context, }; display_guard.replace(gst_display); app_ctx_guard.replace(gst_app_context); } } }