Skip to content

Commit

Permalink
gles: Sync texture uploads for shared contexts
Browse files Browse the repository at this point in the history
  • Loading branch information
Drakulix committed Dec 20, 2024
1 parent 68555cf commit c79e229
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 34 deletions.
8 changes: 8 additions & 0 deletions src/backend/egl/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
110 changes: 77 additions & 33 deletions src/backend/renderer/gles/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {}

Expand Down Expand Up @@ -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,
}
Expand Down Expand Up @@ -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") {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -668,6 +672,9 @@ impl GlesRenderer {
CleanupResource::Program(program) => unsafe {
self.gl.DeleteProgram(program);
},
CleanupResource::Sync(sync) => unsafe {
self.gl.DeleteSync(sync);
},
}
}
}
Expand All @@ -694,6 +701,18 @@ impl ImportMemWl for GlesRenderer {
// this is guaranteed a non-public internal type, so we are good.
type CacheMap = HashMap<usize, Arc<GlesTextureInternal>>;

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::<Arc<Mutex<CacheMap>>>()
.unwrap()
.lock()
.unwrap()
});

with_buffer_contents(buffer, |ptr, len, data| {
self.make_current()?;

Expand Down Expand Up @@ -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::<Arc<Mutex<CacheMap>>>()
.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;
Expand All @@ -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,
Expand All @@ -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::<Arc<Mutex<CacheMap>>>()
.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);
Expand Down Expand Up @@ -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)
})
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand All @@ -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(())
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -1451,6 +1493,7 @@ impl Bind<GlesTexture> 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(
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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(
Expand Down
25 changes: 24 additions & 1 deletion src/backend/renderer/gles/texture.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use ffi::Gles2;

use super::*;
use std::sync::Arc;
use std::sync::{Arc, Mutex};

/// A handle to a GLES texture
///
Expand Down Expand Up @@ -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,
Expand All @@ -53,6 +56,7 @@ impl GlesTexture {
#[derive(Debug)]
pub(super) struct GlesTextureInternal {
pub(super) texture: ffi::types::GLuint,
pub(super) upload_sync: Mutex<Option<ffi::types::GLsync>>,
pub(super) format: Option<ffi::types::GLenum>,
pub(super) has_alpha: bool,
pub(super) is_external: bool,
Expand All @@ -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<ffi::types::GLsync>, 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
Expand Down

0 comments on commit c79e229

Please sign in to comment.