Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gles: Sync texture uploads for shared contexts #1616

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
111 changes: 78 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, RwLock,
},
};
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 @@ -678,6 +685,13 @@ impl GlesRenderer {
}
}

/// Warning: If your context only supports OpenGL ES 2.0 and
/// you are sharing EGLContexts, using [`import_shm_buffer`]
/// will insert a `glFinish()` call for every buffer import
/// to synchronize texture access.
///
/// As a compositor developer consider not sharing
/// contexts, if OpenGL ES 3.0 is unavailable.
#[cfg(feature = "wayland_frontend")]
impl ImportMemWl for GlesRenderer {
#[instrument(level = "trace", parent = &self.span, skip(self))]
Expand All @@ -694,6 +708,14 @@ 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
.get_or_insert_threadsafe(|| Arc::new(Mutex::new(CacheMap::new())))
.lock()
.unwrap()
});

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

Expand Down Expand Up @@ -734,20 +756,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 +767,7 @@ impl ImportMemWl for GlesRenderer {
upload_full = true;
let new = Arc::new(GlesTextureInternal {
texture: tex,
sync: RwLock::default(),
format: Some(internal_format),
has_alpha,
is_external: false,
Expand All @@ -764,21 +776,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.sync.write().unwrap();
unsafe {
sync_lock.wait_for_all(&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 +830,14 @@ 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) {
sync_lock.update_write(&self.gl);
} else if self.egl.is_shared() {
self.gl.Finish();
}
}
std::mem::drop(sync_lock);

Ok(texture)
})
Expand Down Expand Up @@ -910,9 +924,20 @@ impl ImportMem for GlesRenderer {
);
self.gl.BindTexture(ffi::TEXTURE_2D, 0);
}

let mut sync = RwLock::<TextureSync>::default();
if self.capabilities.contains(&Capability::Fencing) {
sync.get_mut().unwrap().update_write(&self.gl);
} else if self.egl.is_shared() {
unsafe {
self.gl.Finish();
}
};

// new texture, upload in full
GlesTextureInternal {
texture: tex,
sync,
format: Some(internal),
has_alpha,
is_external: false,
Expand Down Expand Up @@ -952,7 +977,9 @@ impl ImportMem for GlesRenderer {
return Err(GlesError::UnexpectedSize);
}

let mut sync_lock = texture.0.sync.write().unwrap();
unsafe {
sync_lock.wait_for_all(&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 +1003,12 @@ 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) {
sync_lock.update_write(&self.gl);
} else if self.egl.is_shared() {
self.gl.Finish();
}
}

Ok(())
Expand Down Expand Up @@ -1048,6 +1081,7 @@ impl ImportEgl for GlesRenderer {

let texture = GlesTexture(Arc::new(GlesTextureInternal {
texture: tex,
sync: RwLock::default(),
format: match egl.format {
EGLFormat::RGB | EGLFormat::RGBA => Some(ffi::RGBA8),
EGLFormat::External => None,
Expand Down Expand Up @@ -1094,6 +1128,7 @@ impl ImportDma for GlesRenderer {
let has_alpha = has_alpha(buffer.format().code);
let texture = GlesTexture(Arc::new(GlesTextureInternal {
texture: tex,
sync: RwLock::default(),
format: Some(format),
has_alpha,
is_external,
Expand Down Expand Up @@ -1451,6 +1486,8 @@ impl Bind<GlesTexture> for GlesRenderer {
let bind = || {
let mut fbo = 0;
unsafe {
// TODO: we should keep the lock, while the Target is active
texture.0.sync.read().unwrap().wait_for_upload(&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 +2334,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 @@ -2675,7 +2712,9 @@ impl GlesFrame<'_> {

// render
let gl = &self.renderer.gl;
let sync_lock = tex.0.sync.read().unwrap();
unsafe {
sync_lock.wait_for_upload(gl);
gl.ActiveTexture(ffi::TEXTURE0);
gl.BindTexture(target, tex.0.texture);
gl.TexParameteri(
Expand Down Expand Up @@ -2755,6 +2794,12 @@ impl GlesFrame<'_> {
gl.BindTexture(target, 0);
gl.DisableVertexAttribArray(program.attrib_vert as u32);
gl.DisableVertexAttribArray(program.attrib_vert_position as u32);

if self.renderer.capabilities.contains(&Capability::Fencing) {
sync_lock.update_read(gl);
} else if self.renderer.egl.is_shared() {
gl.Finish();
};
}

Ok(())
Expand Down
73 changes: 73 additions & 0 deletions src/backend/renderer/gles/texture.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use ffi::Gles2;

use super::*;
use std::sync::Arc;

Expand Down Expand Up @@ -27,6 +29,7 @@ impl GlesTexture {
) -> GlesTexture {
GlesTexture(Arc::new(GlesTextureInternal {
texture: tex,
sync: RwLock::default(),
format: internal_format,
has_alpha: !opaque,
is_external: false,
Expand All @@ -50,9 +53,68 @@ impl GlesTexture {
}
}

#[derive(Debug, Default)]
pub(super) struct TextureSync {
read_sync: Mutex<Option<ffi::types::GLsync>>,
write_sync: Mutex<Option<ffi::types::GLsync>>,
}

unsafe fn wait_for_syncpoint(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 TextureSync {
pub(super) fn wait_for_upload(&self, gl: &Gles2) {
unsafe {
wait_for_syncpoint(&mut self.write_sync.lock().unwrap(), gl);
}
}

pub(super) fn update_read(&self, gl: &Gles2) {
let mut read_sync = self.read_sync.lock().unwrap();
if let Some(old) = read_sync.take() {
unsafe {
gl.WaitSync(old, 0, ffi::TIMEOUT_IGNORED);
gl.DeleteSync(old);
};
}
*read_sync = Some(unsafe { gl.FenceSync(ffi::SYNC_GPU_COMMANDS_COMPLETE, 0) });
}

pub(super) fn wait_for_all(&mut self, gl: &Gles2) {
unsafe {
wait_for_syncpoint(&mut self.read_sync.get_mut().unwrap(), gl);
wait_for_syncpoint(&mut self.write_sync.get_mut().unwrap(), gl);
}
}

pub(super) fn update_write(&mut self, gl: &Gles2) {
let write_sync = self.write_sync.get_mut().unwrap();
if let Some(old) = write_sync.take() {
unsafe {
gl.WaitSync(old, 0, ffi::TIMEOUT_IGNORED);
gl.DeleteSync(old);
};
}

*write_sync = Some(unsafe { gl.FenceSync(ffi::SYNC_GPU_COMMANDS_COMPLETE, 0) });
}
}

#[derive(Debug)]
pub(super) struct GlesTextureInternal {
pub(super) texture: ffi::types::GLuint,
pub(super) sync: RwLock<TextureSync>,
pub(super) format: Option<ffi::types::GLenum>,
pub(super) has_alpha: bool,
pub(super) is_external: bool,
Expand All @@ -69,6 +131,17 @@ impl Drop for GlesTextureInternal {
let _ = self
.destruction_callback_sender
.send(CleanupResource::Texture(self.texture));
let mut sync = self.sync.write().unwrap();
if let Some(sync) = sync.read_sync.get_mut().unwrap().take() {
let _ = self
.destruction_callback_sender
.send(CleanupResource::Sync(sync as *const _));
}
if let Some(sync) = sync.write_sync.get_mut().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
Loading