diff --git a/src/backend/egl/context.rs b/src/backend/egl/context.rs index 70906421b3dc..e4ed995d69ae 100644 --- a/src/backend/egl/context.rs +++ b/src/backend/egl/context.rs @@ -418,6 +418,14 @@ impl EGLContext { unsafe { ffi::egl::GetCurrentContext() == self.context as *const _ } } + /// Returns true if the OpenGL context is (possibly) shared with another. + /// + /// Externally managed contexts created with `EGLContext::from_raw` + /// are always considered shared by this function. + pub fn is_shared(&self) -> bool { + self.externally_managed || Arc::strong_count(&self.user_data) > 1 + } + /// Returns the egl config for this context pub fn config_id(&self) -> ffi::egl::types::EGLConfig { self.config_id diff --git a/src/backend/renderer/gles/mod.rs b/src/backend/renderer/gles/mod.rs index 305ba6cb5cd6..873ebed140d5 100644 --- a/src/backend/renderer/gles/mod.rs +++ b/src/backend/renderer/gles/mod.rs @@ -12,14 +12,11 @@ use std::{ sync::{ atomic::{AtomicBool, AtomicPtr, Ordering}, mpsc::{channel, Receiver, Sender}, - Arc, + Arc, Mutex, }, }; use tracing::{debug, error, info, info_span, instrument, span, span::EnteredSpan, trace, warn, Level}; -#[cfg(feature = "wayland_frontend")] -use std::sync::Mutex; - pub mod element; mod error; pub mod format; @@ -88,6 +85,7 @@ enum CleanupResource { EGLImage(EGLImage), Mapping(ffi::types::GLuint, *const std::ffi::c_void), Program(ffi::types::GLuint), + Sync(ffi::types::GLsync), } unsafe impl Send for CleanupResource {} @@ -240,8 +238,10 @@ pub enum Capability { _10Bit, /// GlesRenderer supports creating of Renderbuffers with usable formats Renderbuffer, - /// GlesRenderer supports fencing, + /// GlesRenderer supports fencing Fencing, + /// GlesRenderer supports fencing and exporting it to EGL + ExportFence, /// GlesRenderer supports GL debug Debug, } @@ -409,11 +409,13 @@ impl GlesRenderer { debug!("Blitting is supported"); capabilities.push(Capability::_10Bit); debug!("10-bit formats are supported"); + capabilities.push(Capability::Fencing); + debug!("Fencing is supported"); } if exts.iter().any(|ext| ext == "GL_OES_EGL_sync") { - debug!("Fencing is supported"); - capabilities.push(Capability::Fencing); + debug!("EGL Fencing is supported"); + capabilities.push(Capability::ExportFence); } if exts.iter().any(|ext| ext == "GL_KHR_debug") { @@ -480,9 +482,11 @@ impl GlesRenderer { Capability::Instancing => { GlesError::GLExtensionNotSupported(&["GL_EXT_instanced_arrays", "GL_EXT_draw_instanced"]) } - Capability::Blit | Capability::_10Bit => GlesError::GLVersionNotSupported(version::GLES_3_0), + Capability::Blit | Capability::_10Bit | Capability::Fencing => { + GlesError::GLVersionNotSupported(version::GLES_3_0) + } Capability::Renderbuffer => GlesError::GLExtensionNotSupported(&["GL_OES_rgb8_rgba8"]), - Capability::Fencing => GlesError::GLExtensionNotSupported(&["GL_OES_EGL_sync"]), + Capability::ExportFence => GlesError::GLExtensionNotSupported(&["GL_OES_EGL_sync"]), Capability::Debug => GlesError::GLExtensionNotSupported(&["GL_KHR_debug"]), }; return Err(err); @@ -668,6 +672,9 @@ impl GlesRenderer { CleanupResource::Program(program) => unsafe { self.gl.DeleteProgram(program); }, + CleanupResource::Sync(sync) => unsafe { + self.gl.DeleteSync(sync); + }, } } } @@ -694,6 +701,18 @@ impl ImportMemWl for GlesRenderer { // this is guaranteed a non-public internal type, so we are good. type CacheMap = HashMap>; + let mut surface_lock = surface.as_ref().map(|surface_data| { + surface_data + .data_map + .insert_if_missing_threadsafe(|| Arc::new(Mutex::new(CacheMap::new()))); + surface_data + .data_map + .get::>>() + .unwrap() + .lock() + .unwrap() + }); + with_buffer_contents(buffer, |ptr, len, data| { self.make_current()?; @@ -734,20 +753,9 @@ impl ImportMemWl for GlesRenderer { let id = self.id(); let texture = GlesTexture( - surface - .and_then(|surface| { - surface - .data_map - .insert_if_missing_threadsafe(|| Arc::new(Mutex::new(CacheMap::new()))); - surface - .data_map - .get::>>() - .unwrap() - .lock() - .unwrap() - .get(&id) - .cloned() - }) + surface_lock + .as_ref() + .and_then(|cache| cache.get(&id).cloned()) .filter(|texture| texture.size == (width, height).into()) .unwrap_or_else(|| { let mut tex = 0; @@ -756,6 +764,7 @@ impl ImportMemWl for GlesRenderer { upload_full = true; let new = Arc::new(GlesTextureInternal { texture: tex, + upload_sync: Mutex::new(None), format: Some(internal_format), has_alpha, is_external: false, @@ -764,21 +773,16 @@ impl ImportMemWl for GlesRenderer { egl_images: None, destruction_callback_sender: self.destruction_callback_sender.clone(), }); - if let Some(surface) = surface { - let copy = new.clone(); - surface - .data_map - .get::>>() - .unwrap() - .lock() - .unwrap() - .insert(id, copy); + if let Some(cache) = surface_lock.as_mut() { + cache.insert(id, new.clone()); } new }), ); + let mut sync_lock = texture.0.upload_sync.lock().unwrap(); unsafe { + wait_for_upload(&mut sync_lock, &self.gl); self.gl.BindTexture(ffi::TEXTURE_2D, texture.0.texture); self.gl .TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_S, ffi::CLAMP_TO_EDGE as i32); @@ -823,7 +827,18 @@ impl ImportMemWl for GlesRenderer { self.gl.PixelStorei(ffi::UNPACK_ROW_LENGTH, 0); self.gl.BindTexture(ffi::TEXTURE_2D, 0); + + if self.capabilities.contains(&Capability::Fencing) { + if let Some(old_sync) = + sync_lock.replace(self.gl.FenceSync(ffi::SYNC_GPU_COMMANDS_COMPLETE, 0) as *mut _) + { + self.gl.DeleteSync(old_sync); + } + } else if self.egl.is_shared() { + self.gl.Finish(); + } } + std::mem::drop(sync_lock); Ok(texture) }) @@ -910,9 +925,22 @@ impl ImportMem for GlesRenderer { ); self.gl.BindTexture(ffi::TEXTURE_2D, 0); } + + let upload_sync = Mutex::new(if self.capabilities.contains(&Capability::Fencing) { + Some(unsafe { self.gl.FenceSync(ffi::SYNC_GPU_COMMANDS_COMPLETE, 0) }) + } else if self.egl.is_shared() { + unsafe { + self.gl.Finish(); + } + None + } else { + None + }); + // new texture, upload in full GlesTextureInternal { texture: tex, + upload_sync, format: Some(internal), has_alpha, is_external: false, @@ -952,7 +980,9 @@ impl ImportMem for GlesRenderer { return Err(GlesError::UnexpectedSize); } + let mut sync_lock = texture.0.upload_sync.lock().unwrap(); unsafe { + wait_for_upload(&mut sync_lock, &self.gl); self.gl.BindTexture(ffi::TEXTURE_2D, texture.0.texture); self.gl .TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_S, ffi::CLAMP_TO_EDGE as i32); @@ -976,6 +1006,16 @@ impl ImportMem for GlesRenderer { self.gl.PixelStorei(ffi::UNPACK_SKIP_PIXELS, 0); self.gl.PixelStorei(ffi::UNPACK_SKIP_ROWS, 0); self.gl.BindTexture(ffi::TEXTURE_2D, 0); + + if self.capabilities.contains(&Capability::Fencing) { + if let Some(old_sync) = + sync_lock.replace(self.gl.FenceSync(ffi::SYNC_GPU_COMMANDS_COMPLETE, 0)) + { + self.gl.DeleteSync(old_sync); + } + } else if self.egl.is_shared() { + self.gl.Finish(); + } } Ok(()) @@ -1048,6 +1088,7 @@ impl ImportEgl for GlesRenderer { let texture = GlesTexture(Arc::new(GlesTextureInternal { texture: tex, + upload_sync: Mutex::new(None), format: match egl.format { EGLFormat::RGB | EGLFormat::RGBA => Some(ffi::RGBA8), EGLFormat::External => None, @@ -1094,6 +1135,7 @@ impl ImportDma for GlesRenderer { let has_alpha = has_alpha(buffer.format().code); let texture = GlesTexture(Arc::new(GlesTextureInternal { texture: tex, + upload_sync: Mutex::new(None), format: Some(format), has_alpha, is_external, @@ -1451,6 +1493,7 @@ impl Bind for GlesRenderer { let bind = || { let mut fbo = 0; unsafe { + wait_for_upload(&mut texture.0.upload_sync.lock().unwrap(), &self.gl); self.gl.GenFramebuffers(1, &mut fbo as *mut _); self.gl.BindFramebuffer(ffi::FRAMEBUFFER, fbo); self.gl.FramebufferTexture2D( @@ -2297,7 +2340,7 @@ impl GlesFrame<'_> { self.renderer.cleanup(); // if we support egl fences we should use it - if self.renderer.capabilities.contains(&Capability::Fencing) { + if self.renderer.capabilities.contains(&Capability::ExportFence) { if let Ok(fence) = EGLFence::create(self.renderer.egl.display()) { unsafe { self.renderer.gl.Flush(); @@ -2676,6 +2719,7 @@ impl GlesFrame<'_> { // render let gl = &self.renderer.gl; unsafe { + wait_for_upload(&mut tex.0.upload_sync.lock().unwrap(), gl); gl.ActiveTexture(ffi::TEXTURE0); gl.BindTexture(target, tex.0.texture); gl.TexParameteri( diff --git a/src/backend/renderer/gles/texture.rs b/src/backend/renderer/gles/texture.rs index 0fbaa802138b..95c8ff58859a 100644 --- a/src/backend/renderer/gles/texture.rs +++ b/src/backend/renderer/gles/texture.rs @@ -1,5 +1,7 @@ +use ffi::Gles2; + use super::*; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; /// A handle to a GLES texture /// @@ -27,6 +29,7 @@ impl GlesTexture { ) -> GlesTexture { GlesTexture(Arc::new(GlesTextureInternal { texture: tex, + upload_sync: Mutex::new(None), format: internal_format, has_alpha: !opaque, is_external: false, @@ -53,6 +56,7 @@ impl GlesTexture { #[derive(Debug)] pub(super) struct GlesTextureInternal { pub(super) texture: ffi::types::GLuint, + pub(super) upload_sync: Mutex>, pub(super) format: Option, pub(super) has_alpha: bool, pub(super) is_external: bool, @@ -64,11 +68,30 @@ pub(super) struct GlesTextureInternal { unsafe impl Send for GlesTextureInternal {} unsafe impl Sync for GlesTextureInternal {} +pub(super) unsafe fn wait_for_upload(sync: &mut Option, gl: &Gles2) { + if let Some(sync_obj) = *sync { + match gl.ClientWaitSync(sync_obj, 0, 0) { + ffi::ALREADY_SIGNALED | ffi::CONDITION_SATISFIED => { + let _ = sync.take(); + gl.DeleteSync(sync_obj); + } + _ => { + gl.WaitSync(sync_obj, 0, ffi::TIMEOUT_IGNORED); + } + }; + } +} + impl Drop for GlesTextureInternal { fn drop(&mut self) { let _ = self .destruction_callback_sender .send(CleanupResource::Texture(self.texture)); + if let Some(sync) = self.upload_sync.lock().unwrap().take() { + let _ = self + .destruction_callback_sender + .send(CleanupResource::Sync(sync as *const _)); + } if let Some(images) = self.egl_images.take() { for image in images { let _ = self