renderer: allow caching resources on WlBuffer
- renderer: Change the renderer api to not import different buffer types directly, but import any supported wl_buffer - renderer: Remove destroy_texture call and move responsibility into implementation - gles2: Cache egl images as well as textures on wl_buffer userdata - gles2: Implement delayed destruction of textures to avoid leaking or changing global state on drop
This commit is contained in:
parent
2200d09841
commit
75b2a2d801
|
@ -11,11 +11,9 @@ use wayland_server::protocol::{wl_buffer, wl_shm};
|
||||||
|
|
||||||
use super::{device::DevPath, surface::DrmSurfaceInternal, DrmError, DrmSurface};
|
use super::{device::DevPath, surface::DrmSurfaceInternal, DrmError, DrmSurface};
|
||||||
use crate::backend::{
|
use crate::backend::{
|
||||||
allocator::{
|
allocator::{dmabuf::{AsDmabuf, Dmabuf}, Allocator, Buffer, Format, Fourcc, Modifier, Slot, Swapchain},
|
||||||
dmabuf::{AsDmabuf, Dmabuf}, Allocator, Buffer, Format, Fourcc, Modifier, Slot, Swapchain,
|
egl::display::EGLBufferReader,
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use crate::backend::egl::EGLBuffer;
|
|
||||||
use crate::backend::renderer::{Bind, Renderer, Texture, Transform};
|
use crate::backend::renderer::{Bind, Renderer, Texture, Transform};
|
||||||
use crate::backend::SwapBuffersError;
|
use crate::backend::SwapBuffersError;
|
||||||
|
|
||||||
|
@ -354,19 +352,10 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "wayland_frontend")]
|
#[cfg(feature = "wayland_frontend")]
|
||||||
fn import_shm(&mut self, buffer: &wl_buffer::WlBuffer) -> Result<Self::TextureId, Self::Error> {
|
fn import_buffer(&mut self, buffer: &wl_buffer::WlBuffer, egl: Option<&EGLBufferReader>) -> Result<Self::TextureId, Self::Error> {
|
||||||
self.renderer.import_shm(buffer).map_err(Error::RenderError)
|
self.renderer.import_buffer(buffer, egl).map_err(Error::RenderError)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "wayland_frontend")]
|
|
||||||
fn import_egl(&mut self, buffer: &EGLBuffer) -> Result<Self::TextureId, Self::Error> {
|
|
||||||
self.renderer.import_egl(buffer).map_err(Error::RenderError)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn destroy_texture(&mut self, texture: Self::TextureId) -> Result<(), Self::Error> {
|
|
||||||
self.renderer.destroy_texture(texture).map_err(Error::RenderError)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn begin(&mut self, width: u32, height: u32, _transform: Transform) -> Result<(), Error<E1, E2, E3>> {
|
fn begin(&mut self, width: u32, height: u32, _transform: Transform) -> Result<(), Error<E1, E2, E3>> {
|
||||||
if self.current_buffer.is_some() {
|
if self.current_buffer.is_some() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
//! Implementation of the rendering traits using OpenGL ES 2
|
//! Implementation of the rendering traits using OpenGL ES 2
|
||||||
|
|
||||||
use std::{collections::HashSet, os::raw::c_char};
|
use std::{collections::HashSet, os::raw::c_char};
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
use std::sync::{
|
||||||
|
atomic::{Ordering, AtomicUsize},
|
||||||
|
mpsc::{Sender, Receiver, channel}
|
||||||
|
};
|
||||||
|
|
||||||
use cgmath::{prelude::*, Matrix3};
|
use cgmath::{prelude::*, Matrix3};
|
||||||
|
|
||||||
|
@ -15,7 +20,7 @@ use crate::backend::allocator::{
|
||||||
Format,
|
Format,
|
||||||
};
|
};
|
||||||
use crate::backend::egl::{
|
use crate::backend::egl::{
|
||||||
ffi::egl::types::EGLImage, EGLBuffer, EGLContext, EGLSurface, Format as EGLFormat, MakeCurrentError,
|
ffi::egl::types::EGLImage, EGLBuffer, EGLContext, EGLSurface, Format as EGLFormat, MakeCurrentError, display::EGLBufferReader,
|
||||||
};
|
};
|
||||||
use crate::backend::SwapBuffersError;
|
use crate::backend::SwapBuffersError;
|
||||||
|
|
||||||
|
@ -27,6 +32,8 @@ pub mod ffi {
|
||||||
include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs"));
|
include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static RENDERER_COUNTER: AtomicUsize = AtomicUsize::new(0);
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Gles2Program {
|
struct Gles2Program {
|
||||||
program: ffi::types::GLuint,
|
program: ffi::types::GLuint,
|
||||||
|
@ -39,22 +46,32 @@ struct Gles2Program {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A handle to a GLES2 texture
|
/// A handle to a GLES2 texture
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Gles2Texture(Rc<Gles2TextureInternal>);
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Gles2Texture {
|
struct Gles2TextureInternal {
|
||||||
texture: ffi::types::GLuint,
|
texture: ffi::types::GLuint,
|
||||||
texture_kind: usize,
|
texture_kind: usize,
|
||||||
is_external: bool,
|
is_external: bool,
|
||||||
y_inverted: bool,
|
y_inverted: bool,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
|
destruction_callback_sender: Sender<ffi::types::GLuint>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Gles2TextureInternal {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let _ = self.destruction_callback_sender.send(self.texture);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Texture for Gles2Texture {
|
impl Texture for Gles2Texture {
|
||||||
fn width(&self) -> u32 {
|
fn width(&self) -> u32 {
|
||||||
self.width
|
self.0.width
|
||||||
}
|
}
|
||||||
fn height(&self) -> u32 {
|
fn height(&self) -> u32 {
|
||||||
self.height
|
self.0.height
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,6 +91,7 @@ struct Gles2Buffer {
|
||||||
|
|
||||||
/// A renderer utilizing OpenGL ES 2
|
/// A renderer utilizing OpenGL ES 2
|
||||||
pub struct Gles2Renderer {
|
pub struct Gles2Renderer {
|
||||||
|
id: usize,
|
||||||
buffers: Vec<WeakGles2Buffer>,
|
buffers: Vec<WeakGles2Buffer>,
|
||||||
target_buffer: Option<Gles2Buffer>,
|
target_buffer: Option<Gles2Buffer>,
|
||||||
target_surface: Option<Rc<EGLSurface>>,
|
target_surface: Option<Rc<EGLSurface>>,
|
||||||
|
@ -82,6 +100,8 @@ pub struct Gles2Renderer {
|
||||||
programs: [Gles2Program; shaders::FRAGMENT_COUNT],
|
programs: [Gles2Program; shaders::FRAGMENT_COUNT],
|
||||||
gl: ffi::Gles2,
|
gl: ffi::Gles2,
|
||||||
egl: EGLContext,
|
egl: EGLContext,
|
||||||
|
destruction_callback: Receiver<ffi::types::GLuint>,
|
||||||
|
destruction_callback_sender: Sender<ffi::types::GLuint>,
|
||||||
logger: Option<*mut ::slog::Logger>,
|
logger: Option<*mut ::slog::Logger>,
|
||||||
_not_send: *mut (),
|
_not_send: *mut (),
|
||||||
}
|
}
|
||||||
|
@ -89,6 +109,7 @@ pub struct Gles2Renderer {
|
||||||
impl fmt::Debug for Gles2Renderer {
|
impl fmt::Debug for Gles2Renderer {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.debug_struct("Gles2Renderer")
|
f.debug_struct("Gles2Renderer")
|
||||||
|
.field("id", &self.id)
|
||||||
.field("buffers", &self.buffers)
|
.field("buffers", &self.buffers)
|
||||||
.field("target_buffer", &self.target_buffer)
|
.field("target_buffer", &self.target_buffer)
|
||||||
.field("target_surface", &self.target_surface)
|
.field("target_surface", &self.target_surface)
|
||||||
|
@ -134,6 +155,10 @@ pub enum Gles2Error {
|
||||||
#[error("Error accessing the buffer ({0:?})")]
|
#[error("Error accessing the buffer ({0:?})")]
|
||||||
#[cfg(feature = "wayland_frontend")]
|
#[cfg(feature = "wayland_frontend")]
|
||||||
BufferAccessError(crate::wayland::shm::BufferAccessError),
|
BufferAccessError(crate::wayland::shm::BufferAccessError),
|
||||||
|
/// The given egl buffer was not accessible
|
||||||
|
#[error("Error accessing the buffer ({0:?})")]
|
||||||
|
#[cfg(feature = "wayland_frontend")]
|
||||||
|
EGLBufferAccessError(crate::backend::egl::BufferAccessError),
|
||||||
/// This rendering operation was called without a previous `begin`-call
|
/// This rendering operation was called without a previous `begin`-call
|
||||||
#[error("Call begin before doing any rendering operations")]
|
#[error("Call begin before doing any rendering operations")]
|
||||||
UnconstraintRenderingOperation,
|
UnconstraintRenderingOperation,
|
||||||
|
@ -151,7 +176,8 @@ impl From<Gles2Error> for SwapBuffersError {
|
||||||
x @ Gles2Error::FramebufferBindingError
|
x @ Gles2Error::FramebufferBindingError
|
||||||
| x @ Gles2Error::BindBufferEGLError(_)
|
| x @ Gles2Error::BindBufferEGLError(_)
|
||||||
| x @ Gles2Error::UnsupportedPixelFormat(_)
|
| x @ Gles2Error::UnsupportedPixelFormat(_)
|
||||||
| x @ Gles2Error::BufferAccessError(_) => SwapBuffersError::TemporaryFailure(Box::new(x)),
|
| x @ Gles2Error::BufferAccessError(_)
|
||||||
|
| x @ Gles2Error::EGLBufferAccessError(_) => SwapBuffersError::TemporaryFailure(Box::new(x)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -336,16 +362,20 @@ impl Gles2Renderer {
|
||||||
texture_program(&gl, shaders::FRAGMENT_SHADER_EXTERNAL)?,
|
texture_program(&gl, shaders::FRAGMENT_SHADER_EXTERNAL)?,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
let (tx, rx) = channel();
|
||||||
let renderer = Gles2Renderer {
|
let renderer = Gles2Renderer {
|
||||||
|
id: RENDERER_COUNTER.fetch_add(1, Ordering::SeqCst),
|
||||||
gl,
|
gl,
|
||||||
egl: context,
|
egl: context,
|
||||||
extensions: exts,
|
extensions: exts,
|
||||||
programs,
|
programs,
|
||||||
logger,
|
|
||||||
target_buffer: None,
|
target_buffer: None,
|
||||||
target_surface: None,
|
target_surface: None,
|
||||||
buffers: Vec::new(),
|
buffers: Vec::new(),
|
||||||
current_projection: None,
|
current_projection: None,
|
||||||
|
destruction_callback: rx,
|
||||||
|
destruction_callback_sender: tx,
|
||||||
|
logger,
|
||||||
_not_send: std::ptr::null_mut(),
|
_not_send: std::ptr::null_mut(),
|
||||||
};
|
};
|
||||||
renderer.egl.unbind()?;
|
renderer.egl.unbind()?;
|
||||||
|
@ -362,6 +392,176 @@ impl Gles2Renderer {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn cleanup(&mut self) -> Result<(), Gles2Error> {
|
||||||
|
self.make_current()?;
|
||||||
|
for texture in self.destruction_callback.try_iter() {
|
||||||
|
unsafe {
|
||||||
|
self.gl.DeleteTextures(1, &texture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BufferCache {
|
||||||
|
cache: Vec<Option<BufferCacheVariant>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum BufferCacheVariant {
|
||||||
|
Egl(Option<EglCache>),
|
||||||
|
Shm(Option<ShmCache>),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct EglCache {
|
||||||
|
buf: Option<EGLBuffer>,
|
||||||
|
texture: Gles2Texture,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ShmCache {
|
||||||
|
texture: Gles2Texture,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Gles2Renderer {
|
||||||
|
#[cfg(feature = "wayland_frontend")]
|
||||||
|
fn import_shm(&mut self, buffer: &wl_buffer::WlBuffer, cache: &mut Option<ShmCache>) -> Result<Gles2Texture, Gles2Error> {
|
||||||
|
use crate::wayland::shm::with_buffer_contents;
|
||||||
|
|
||||||
|
with_buffer_contents(&buffer, |slice, data| {
|
||||||
|
self.make_current()?;
|
||||||
|
|
||||||
|
let offset = data.offset as i32;
|
||||||
|
let width = data.width as i32;
|
||||||
|
let height = data.height as i32;
|
||||||
|
let stride = data.stride as i32;
|
||||||
|
|
||||||
|
// number of bytes per pixel
|
||||||
|
// TODO: compute from data.format
|
||||||
|
let pixelsize = 4i32;
|
||||||
|
|
||||||
|
// ensure consistency, the SHM handler of smithay should ensure this
|
||||||
|
assert!((offset + (height - 1) * stride + width * pixelsize) as usize <= slice.len());
|
||||||
|
|
||||||
|
let (gl_format, shader_idx) = match data.format {
|
||||||
|
wl_shm::Format::Abgr8888 => (ffi::RGBA, 0),
|
||||||
|
wl_shm::Format::Xbgr8888 => (ffi::RGBA, 1),
|
||||||
|
wl_shm::Format::Argb8888 => (ffi::BGRA_EXT, 2),
|
||||||
|
wl_shm::Format::Xrgb8888 => (ffi::BGRA_EXT, 3),
|
||||||
|
format => return Err(Gles2Error::UnsupportedPixelFormat(format)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let texture = cache.as_ref().map(|x| x.texture.clone()).unwrap_or_else(|| {
|
||||||
|
let mut tex = 0;
|
||||||
|
unsafe { self.gl.GenTextures(1, &mut tex) };
|
||||||
|
Gles2Texture(Rc::new(Gles2TextureInternal {
|
||||||
|
texture: tex,
|
||||||
|
texture_kind: shader_idx,
|
||||||
|
is_external: false,
|
||||||
|
y_inverted: false,
|
||||||
|
width: width as u32,
|
||||||
|
height: height as u32,
|
||||||
|
destruction_callback_sender: self.destruction_callback_sender.clone(),
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
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);
|
||||||
|
self.gl
|
||||||
|
.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_T, ffi::CLAMP_TO_EDGE as i32);
|
||||||
|
self.gl.PixelStorei(ffi::UNPACK_ROW_LENGTH, stride / pixelsize);
|
||||||
|
self.gl.TexImage2D(
|
||||||
|
ffi::TEXTURE_2D,
|
||||||
|
0,
|
||||||
|
gl_format as i32,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
0,
|
||||||
|
ffi::RGBA,
|
||||||
|
ffi::UNSIGNED_BYTE as u32,
|
||||||
|
slice.as_ptr() as *const _,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.gl.PixelStorei(ffi::UNPACK_ROW_LENGTH, 0);
|
||||||
|
self.gl.BindTexture(ffi::TEXTURE_2D, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.egl.unbind()?;
|
||||||
|
|
||||||
|
*cache = Some(ShmCache {
|
||||||
|
texture: texture.clone(),
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(texture)
|
||||||
|
})
|
||||||
|
.map_err(Gles2Error::BufferAccessError)?
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "wayland_frontend")]
|
||||||
|
fn import_egl(&mut self, buffer: &wl_buffer::WlBuffer, reader: &EGLBufferReader, cache: &mut Option<EglCache>) -> Result<Gles2Texture, Gles2Error> {
|
||||||
|
if !self.extensions.iter().any(|ext| ext == "GL_OES_EGL_image") {
|
||||||
|
return Err(Gles2Error::GLExtensionNotSupported(&["GL_OES_EGL_image"]));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.make_current()?;
|
||||||
|
let egl_buffer = cache.as_mut().and_then(|x| x.buf.take()).map(Ok).unwrap_or_else(|| {
|
||||||
|
reader.egl_buffer_contents(&buffer)
|
||||||
|
}).map_err(Gles2Error::EGLBufferAccessError)?;
|
||||||
|
|
||||||
|
// we do not need to re-import external textures
|
||||||
|
if egl_buffer.format == EGLFormat::External && cache.is_some() {
|
||||||
|
return Ok(cache.as_ref().map(|x| x.texture.clone()).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
let tex = self.import_egl_image(egl_buffer.image(0).unwrap(), egl_buffer.format == EGLFormat::External, cache.as_ref().map(|x| x.texture.0.texture))?;
|
||||||
|
let texture = cache.as_ref().map(|x| x.texture.clone()).unwrap_or_else(|| {
|
||||||
|
Gles2Texture(Rc::new(Gles2TextureInternal {
|
||||||
|
texture: tex,
|
||||||
|
texture_kind: match egl_buffer.format {
|
||||||
|
EGLFormat::RGB => 3,
|
||||||
|
EGLFormat::RGBA => 2,
|
||||||
|
EGLFormat::External => 4,
|
||||||
|
_ => unreachable!("EGLBuffer currenly does not expose multi-planar buffers to us"),
|
||||||
|
},
|
||||||
|
is_external: egl_buffer.format == EGLFormat::External,
|
||||||
|
y_inverted: egl_buffer.y_inverted,
|
||||||
|
width: egl_buffer.width,
|
||||||
|
height: egl_buffer.height,
|
||||||
|
destruction_callback_sender: self.destruction_callback_sender.clone(),
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
self.egl.unbind()?;
|
||||||
|
|
||||||
|
*cache = Some(EglCache {
|
||||||
|
buf: Some(egl_buffer),
|
||||||
|
texture: texture.clone(),
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(texture)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn import_egl_image(&self, image: EGLImage, is_external: bool, tex: Option<u32>) -> Result<u32, Gles2Error> {
|
||||||
|
let tex = tex.unwrap_or_else(|| unsafe {
|
||||||
|
let mut tex = 0;
|
||||||
|
self.gl.GenTextures(1, &mut tex);
|
||||||
|
tex
|
||||||
|
});
|
||||||
|
let target = if is_external {
|
||||||
|
ffi::TEXTURE_EXTERNAL_OES
|
||||||
|
} else {
|
||||||
|
ffi::TEXTURE_2D
|
||||||
|
};
|
||||||
|
unsafe {
|
||||||
|
self.gl.BindTexture(target, tex);
|
||||||
|
|
||||||
|
self.gl
|
||||||
|
.EGLImageTargetTexture2DOES(target, image);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(tex)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Bind<Rc<EGLSurface>> for Gles2Renderer {
|
impl Bind<Rc<EGLSurface>> for Gles2Renderer {
|
||||||
|
@ -393,7 +593,7 @@ impl Bind<Dmabuf> for Gles2Renderer {
|
||||||
let buffer = self
|
let buffer = self
|
||||||
.buffers
|
.buffers
|
||||||
.iter()
|
.iter()
|
||||||
.find(|buffer| dmabuf == buffer.dmabuf)
|
.find(|buffer| if let Some(dma) = buffer.dmabuf.upgrade() { dma == dmabuf } else { false })
|
||||||
.map(|buf| {
|
.map(|buf| {
|
||||||
let dmabuf = buf
|
let dmabuf = buf
|
||||||
.dmabuf
|
.dmabuf
|
||||||
|
@ -559,140 +759,69 @@ impl Renderer for Gles2Renderer {
|
||||||
self.gl.BindTexture(ffi::TEXTURE_2D, 0);
|
self.gl.BindTexture(ffi::TEXTURE_2D, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let texture = Gles2Texture {
|
let texture = Gles2Texture(Rc::new(Gles2TextureInternal {
|
||||||
texture: tex,
|
texture: tex,
|
||||||
texture_kind: 2,
|
texture_kind: 2,
|
||||||
is_external: false,
|
is_external: false,
|
||||||
y_inverted: false,
|
y_inverted: false,
|
||||||
width: image.width(),
|
width: image.width(),
|
||||||
height: image.height(),
|
height: image.height(),
|
||||||
};
|
destruction_callback_sender: self.destruction_callback_sender.clone(),
|
||||||
|
}));
|
||||||
self.egl.unbind()?;
|
self.egl.unbind()?;
|
||||||
|
|
||||||
Ok(texture)
|
Ok(texture)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(feature = "wayland_frontend")]
|
#[cfg(feature = "wayland_frontend")]
|
||||||
fn import_shm(&mut self, buffer: &wl_buffer::WlBuffer) -> Result<Self::TextureId, Self::Error> {
|
fn import_buffer(&mut self, buffer: &wl_buffer::WlBuffer, egl: Option<&EGLBufferReader>) -> Result<Self::TextureId, Self::Error> {
|
||||||
use crate::wayland::shm::with_buffer_contents;
|
// init cache if not existing
|
||||||
|
let cache_cell = match buffer.as_ref().user_data().get::<Rc<RefCell<BufferCache>>>() {
|
||||||
|
Some(cache) => cache.clone(),
|
||||||
|
None => {
|
||||||
|
let cache = BufferCache {
|
||||||
|
cache: Vec::with_capacity(self.id+1),
|
||||||
|
};
|
||||||
|
|
||||||
with_buffer_contents(&buffer, |slice, data| {
|
let data: Rc<RefCell<BufferCache>> = Rc::new(RefCell::new(cache));
|
||||||
self.make_current()?;
|
let result = data.clone();
|
||||||
|
buffer.as_ref().user_data().set(|| data);
|
||||||
let offset = data.offset as i32;
|
result
|
||||||
let width = data.width as i32;
|
|
||||||
let height = data.height as i32;
|
|
||||||
let stride = data.stride as i32;
|
|
||||||
|
|
||||||
// number of bytes per pixel
|
|
||||||
// TODO: compute from data.format
|
|
||||||
let pixelsize = 4i32;
|
|
||||||
|
|
||||||
// ensure consistency, the SHM handler of smithay should ensure this
|
|
||||||
assert!((offset + (height - 1) * stride + width * pixelsize) as usize <= slice.len());
|
|
||||||
|
|
||||||
let (gl_format, shader_idx) = match data.format {
|
|
||||||
wl_shm::Format::Abgr8888 => (ffi::RGBA, 0),
|
|
||||||
wl_shm::Format::Xbgr8888 => (ffi::RGBA, 1),
|
|
||||||
wl_shm::Format::Argb8888 => (ffi::BGRA_EXT, 2),
|
|
||||||
wl_shm::Format::Xrgb8888 => (ffi::BGRA_EXT, 3),
|
|
||||||
format => return Err(Gles2Error::UnsupportedPixelFormat(format)),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut tex = 0;
|
|
||||||
unsafe {
|
|
||||||
self.gl.GenTextures(1, &mut tex);
|
|
||||||
self.gl.BindTexture(ffi::TEXTURE_2D, tex);
|
|
||||||
|
|
||||||
self.gl
|
|
||||||
.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_S, ffi::CLAMP_TO_EDGE as i32);
|
|
||||||
self.gl
|
|
||||||
.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_T, ffi::CLAMP_TO_EDGE as i32);
|
|
||||||
self.gl.PixelStorei(ffi::UNPACK_ROW_LENGTH, stride / pixelsize);
|
|
||||||
self.gl.TexImage2D(
|
|
||||||
ffi::TEXTURE_2D,
|
|
||||||
0,
|
|
||||||
gl_format as i32,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
0,
|
|
||||||
gl_format,
|
|
||||||
ffi::UNSIGNED_BYTE as u32,
|
|
||||||
slice.as_ptr() as *const _,
|
|
||||||
);
|
|
||||||
|
|
||||||
self.gl.PixelStorei(ffi::UNPACK_ROW_LENGTH, 0);
|
|
||||||
self.gl.BindTexture(ffi::TEXTURE_2D, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let texture = Gles2Texture {
|
|
||||||
texture: tex,
|
|
||||||
texture_kind: shader_idx,
|
|
||||||
is_external: false,
|
|
||||||
y_inverted: false,
|
|
||||||
width: width as u32,
|
|
||||||
height: height as u32,
|
|
||||||
};
|
|
||||||
self.egl.unbind()?;
|
|
||||||
|
|
||||||
Ok(texture)
|
|
||||||
})
|
|
||||||
.map_err(Gles2Error::BufferAccessError)?
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "wayland_frontend")]
|
|
||||||
fn import_egl(&mut self, buffer: &EGLBuffer) -> Result<Self::TextureId, Self::Error> {
|
|
||||||
if !self.extensions.iter().any(|ext| ext == "GL_OES_EGL_image") {
|
|
||||||
return Err(Gles2Error::GLExtensionNotSupported(&["GL_OES_EGL_image"]));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.make_current()?;
|
|
||||||
|
|
||||||
let mut tex = 0;
|
|
||||||
let target = if buffer.format == EGLFormat::External {
|
|
||||||
ffi::TEXTURE_EXTERNAL_OES
|
|
||||||
} else {
|
|
||||||
ffi::TEXTURE_2D
|
|
||||||
};
|
};
|
||||||
unsafe {
|
|
||||||
self.gl.GenTextures(1, &mut tex);
|
// init cache
|
||||||
self.gl.BindTexture(target, tex);
|
let mut cache = cache_cell.borrow_mut();
|
||||||
|
while cache.cache.len() < self.id {
|
||||||
self.gl
|
cache.cache.push(None);
|
||||||
.EGLImageTargetTexture2DOES(target, buffer.image(0).unwrap());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let texture = Gles2Texture {
|
if cache.cache.len() == self.id {
|
||||||
texture: tex,
|
cache.cache.push(Some(
|
||||||
texture_kind: match buffer.format {
|
if egl.and_then(|egl| egl.egl_buffer_dimensions(&buffer)).is_some() {
|
||||||
EGLFormat::RGB => 3,
|
BufferCacheVariant::Egl(None)
|
||||||
EGLFormat::RGBA => 2,
|
} else if crate::wayland::shm::with_buffer_contents(&buffer, |_, _| ()).is_ok() {
|
||||||
EGLFormat::External => 4,
|
BufferCacheVariant::Shm(None)
|
||||||
_ => unreachable!("EGLBuffer currenly does not expose multi-planar buffers to us"),
|
} else {
|
||||||
},
|
unreachable!("Completely unknown buffer format. How did we got here?");
|
||||||
is_external: buffer.format == EGLFormat::External,
|
}
|
||||||
y_inverted: buffer.y_inverted,
|
));
|
||||||
width: buffer.width,
|
}
|
||||||
height: buffer.height,
|
|
||||||
};
|
// delegate for different buffer types
|
||||||
self.egl.unbind()?;
|
match cache.cache[self.id].as_mut() {
|
||||||
|
Some(BufferCacheVariant::Egl(cache)) => self.import_egl(&buffer, egl.unwrap(), cache),
|
||||||
Ok(texture)
|
Some(BufferCacheVariant::Shm(cache)) => self.import_shm(&buffer, cache),
|
||||||
}
|
_ => unreachable!(),
|
||||||
|
|
||||||
fn destroy_texture(&mut self, texture: Self::TextureId) -> Result<(), Self::Error> {
|
|
||||||
self.make_current()?;
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
self.gl.DeleteTextures(1, &texture.texture);
|
|
||||||
}
|
}
|
||||||
self.egl.unbind()?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn begin(&mut self, width: u32, height: u32, transform: Transform) -> Result<(), Gles2Error> {
|
fn begin(&mut self, width: u32, height: u32, transform: Transform) -> Result<(), Gles2Error> {
|
||||||
self.make_current()?;
|
self.make_current()?;
|
||||||
|
// delayed destruction until the next frame rendering.
|
||||||
|
self.cleanup()?;
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
self.gl.Viewport(0, 0, width as i32, height as i32);
|
self.gl.Viewport(0, 0, width as i32, height as i32);
|
||||||
|
|
||||||
|
@ -746,7 +875,7 @@ impl Renderer for Gles2Renderer {
|
||||||
//apply output transformation
|
//apply output transformation
|
||||||
matrix = self.current_projection.as_ref().unwrap() * matrix;
|
matrix = self.current_projection.as_ref().unwrap() * matrix;
|
||||||
|
|
||||||
let target = if tex.is_external {
|
let target = if tex.0.is_external {
|
||||||
ffi::TEXTURE_EXTERNAL_OES
|
ffi::TEXTURE_EXTERNAL_OES
|
||||||
} else {
|
} else {
|
||||||
ffi::TEXTURE_2D
|
ffi::TEXTURE_2D
|
||||||
|
@ -755,27 +884,27 @@ impl Renderer for Gles2Renderer {
|
||||||
// render
|
// render
|
||||||
unsafe {
|
unsafe {
|
||||||
self.gl.ActiveTexture(ffi::TEXTURE0);
|
self.gl.ActiveTexture(ffi::TEXTURE0);
|
||||||
self.gl.BindTexture(target, tex.texture);
|
self.gl.BindTexture(target, tex.0.texture);
|
||||||
self.gl
|
self.gl
|
||||||
.TexParameteri(target, ffi::TEXTURE_MIN_FILTER, ffi::LINEAR as i32);
|
.TexParameteri(target, ffi::TEXTURE_MIN_FILTER, ffi::LINEAR as i32);
|
||||||
self.gl.UseProgram(self.programs[tex.texture_kind].program);
|
self.gl.UseProgram(self.programs[tex.0.texture_kind].program);
|
||||||
|
|
||||||
self.gl.Uniform1i(self.programs[tex.texture_kind].uniform_tex, 0);
|
self.gl.Uniform1i(self.programs[tex.0.texture_kind].uniform_tex, 0);
|
||||||
self.gl.UniformMatrix3fv(
|
self.gl.UniformMatrix3fv(
|
||||||
self.programs[tex.texture_kind].uniform_matrix,
|
self.programs[tex.0.texture_kind].uniform_matrix,
|
||||||
1,
|
1,
|
||||||
ffi::FALSE,
|
ffi::FALSE,
|
||||||
matrix.as_ptr(),
|
matrix.as_ptr(),
|
||||||
);
|
);
|
||||||
self.gl.Uniform1i(
|
self.gl.Uniform1i(
|
||||||
self.programs[tex.texture_kind].uniform_invert_y,
|
self.programs[tex.0.texture_kind].uniform_invert_y,
|
||||||
if tex.y_inverted { 1 } else { 0 },
|
if tex.0.y_inverted { 1 } else { 0 },
|
||||||
);
|
);
|
||||||
self.gl
|
self.gl
|
||||||
.Uniform1f(self.programs[tex.texture_kind].uniform_alpha, alpha);
|
.Uniform1f(self.programs[tex.0.texture_kind].uniform_alpha, alpha);
|
||||||
|
|
||||||
self.gl.VertexAttribPointer(
|
self.gl.VertexAttribPointer(
|
||||||
self.programs[tex.texture_kind].attrib_position as u32,
|
self.programs[tex.0.texture_kind].attrib_position as u32,
|
||||||
2,
|
2,
|
||||||
ffi::FLOAT,
|
ffi::FLOAT,
|
||||||
ffi::FALSE,
|
ffi::FALSE,
|
||||||
|
@ -783,7 +912,7 @@ impl Renderer for Gles2Renderer {
|
||||||
VERTS.as_ptr() as *const _,
|
VERTS.as_ptr() as *const _,
|
||||||
);
|
);
|
||||||
self.gl.VertexAttribPointer(
|
self.gl.VertexAttribPointer(
|
||||||
self.programs[tex.texture_kind].attrib_tex_coords as u32,
|
self.programs[tex.0.texture_kind].attrib_tex_coords as u32,
|
||||||
2,
|
2,
|
||||||
ffi::FLOAT,
|
ffi::FLOAT,
|
||||||
ffi::FALSE,
|
ffi::FALSE,
|
||||||
|
@ -792,16 +921,16 @@ impl Renderer for Gles2Renderer {
|
||||||
);
|
);
|
||||||
|
|
||||||
self.gl
|
self.gl
|
||||||
.EnableVertexAttribArray(self.programs[tex.texture_kind].attrib_position as u32);
|
.EnableVertexAttribArray(self.programs[tex.0.texture_kind].attrib_position as u32);
|
||||||
self.gl
|
self.gl
|
||||||
.EnableVertexAttribArray(self.programs[tex.texture_kind].attrib_tex_coords as u32);
|
.EnableVertexAttribArray(self.programs[tex.0.texture_kind].attrib_tex_coords as u32);
|
||||||
|
|
||||||
self.gl.DrawArrays(ffi::TRIANGLE_STRIP, 0, 4);
|
self.gl.DrawArrays(ffi::TRIANGLE_STRIP, 0, 4);
|
||||||
|
|
||||||
self.gl
|
self.gl
|
||||||
.DisableVertexAttribArray(self.programs[tex.texture_kind].attrib_position as u32);
|
.DisableVertexAttribArray(self.programs[tex.0.texture_kind].attrib_position as u32);
|
||||||
self.gl
|
self.gl
|
||||||
.DisableVertexAttribArray(self.programs[tex.texture_kind].attrib_tex_coords as u32);
|
.DisableVertexAttribArray(self.programs[tex.0.texture_kind].attrib_tex_coords as u32);
|
||||||
|
|
||||||
self.gl.BindTexture(target, 0);
|
self.gl.BindTexture(target, 0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,8 @@ use wayland_server::protocol::{wl_buffer, wl_shm};
|
||||||
use crate::backend::SwapBuffersError;
|
use crate::backend::SwapBuffersError;
|
||||||
#[cfg(feature = "renderer_gl")]
|
#[cfg(feature = "renderer_gl")]
|
||||||
pub mod gles2;
|
pub mod gles2;
|
||||||
#[cfg(feature = "wayland_frontend")]
|
#[cfg(all(feature = "wayland_frontend", feature = "backend_egl"))]
|
||||||
use crate::backend::egl::EGLBuffer;
|
use crate::backend::egl::display::EGLBufferReader;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
||||||
/// Possible transformations to two-dimensional planes
|
/// Possible transformations to two-dimensional planes
|
||||||
|
@ -164,39 +164,19 @@ pub trait Renderer {
|
||||||
&[wl_shm::Format::Argb8888, wl_shm::Format::Xrgb8888]
|
&[wl_shm::Format::Argb8888, wl_shm::Format::Xrgb8888]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Import a given shared memory buffer into the renderer.
|
|
||||||
|
/// Import a given buffer into the renderer.
|
||||||
///
|
///
|
||||||
/// Returns a texture_id, which can be used with `render_texture(_at)` or implementation-specific functions.
|
/// Returns a texture_id, which can be used with `render_texture(_at)` or implementation-specific functions.
|
||||||
///
|
///
|
||||||
/// If not otherwise defined by the implementation, this texture id is only valid for the renderer, that created it,
|
/// If not otherwise defined by the implementation, this texture id is only valid for the renderer, that created it.
|
||||||
/// and needs to be freed by calling `destroy_texture` on this renderer to avoid a resource leak.
|
|
||||||
///
|
///
|
||||||
/// This operation needs no bound or default rendering target.
|
/// This operation needs no bound or default rendering target.
|
||||||
///
|
///
|
||||||
/// The implementation defines, if the id keeps being valid, if the buffer is released,
|
/// The implementation defines, if the id keeps being valid, if the buffer is released,
|
||||||
/// to avoid relying on implementation details, keep the buffer alive, until you destroyed this texture again.
|
/// to avoid relying on implementation details, keep the buffer alive, until you destroyed this texture again.
|
||||||
#[cfg(feature = "wayland_frontend")]
|
#[cfg(feature = "wayland_frontend")]
|
||||||
fn import_shm(&mut self, buffer: &wl_buffer::WlBuffer) -> Result<Self::TextureId, Self::Error>;
|
fn import_buffer(&mut self, buffer: &wl_buffer::WlBuffer, egl: Option<&EGLBufferReader>) -> Result<Self::TextureId, Self::Error>;
|
||||||
|
|
||||||
/// Import a given egl-backed memory buffer into the renderer.
|
|
||||||
///
|
|
||||||
/// Returns a texture_id, which can be used with `render_texture(_at)` or implementation-specific functions.
|
|
||||||
///
|
|
||||||
/// If not otherwise defined by the implementation, this texture id is only valid for the renderer, that created it,
|
|
||||||
/// and needs to be freed by calling `destroy_texture` on this renderer to avoid a resource leak.
|
|
||||||
///
|
|
||||||
/// This operation needs no bound or default rendering target.
|
|
||||||
///
|
|
||||||
/// The implementation defines, if the id keeps being valid, if the buffer is released,
|
|
||||||
/// to avoid relying on implementation details, keep the buffer alive, until you destroyed this texture again.
|
|
||||||
#[cfg(feature = "wayland_frontend")]
|
|
||||||
fn import_egl(&mut self, buffer: &EGLBuffer) -> Result<Self::TextureId, Self::Error>;
|
|
||||||
|
|
||||||
/// Deallocate the given texture.
|
|
||||||
///
|
|
||||||
/// In case the texture type of this renderer is cloneable or copyable, those handles will also become invalid
|
|
||||||
/// and destroy calls with one of these handles might error out as the texture is already freed.
|
|
||||||
fn destroy_texture(&mut self, texture: Self::TextureId) -> Result<(), Self::Error>;
|
|
||||||
|
|
||||||
/// Initialize a rendering context on the current rendering target with given dimensions and transformation.
|
/// Initialize a rendering context on the current rendering target with given dimensions and transformation.
|
||||||
///
|
///
|
||||||
|
@ -265,3 +245,25 @@ pub trait Renderer {
|
||||||
self.render_texture(texture, mat, alpha)
|
self.render_texture(texture, mat, alpha)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "wayland_frontend", feature = "backend_egl"))]
|
||||||
|
pub fn buffer_dimensions(buffer: &wl_buffer::WlBuffer, egl_buffer_reader: Option<&EGLBufferReader>) -> Option<(i32, i32)> {
|
||||||
|
if let Some((w, h)) = egl_buffer_reader.as_ref().and_then(|x| x.egl_buffer_dimensions(&buffer)) {
|
||||||
|
Some((w, h))
|
||||||
|
} else if let Ok((w, h)) = crate::wayland::shm::with_buffer_contents(&buffer, |_, data| (data.width, data.height)) {
|
||||||
|
Some((w, h))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "wayland_frontend", not(feature = "backend_egl")))]
|
||||||
|
pub fn buffer_dimensions(buffer: &wl_buffer::WlBuffer) -> Option<(i32, i32)> {
|
||||||
|
use crate::backend::allocator::Buffer;
|
||||||
|
|
||||||
|
if let Ok((w, h)) = crate::wayland::shm::with_buffer_contents(&buffer, |_, data| (data.width, data.height)) {
|
||||||
|
Some((w, h))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
use crate::backend::egl::display::EGLDisplay;
|
use crate::backend::egl::display::EGLDisplay;
|
||||||
use crate::backend::{
|
use crate::backend::{
|
||||||
egl::{context::GlAttributes, native, EGLBuffer, EGLContext, EGLSurface, Error as EGLError},
|
egl::{context::GlAttributes, native, EGLContext, EGLSurface, Error as EGLError},
|
||||||
input::{
|
input::{
|
||||||
Axis, AxisSource, Event as BackendEvent, InputBackend, InputEvent, KeyState, KeyboardKeyEvent,
|
Axis, AxisSource, Event as BackendEvent, InputBackend, InputEvent, KeyState, KeyboardKeyEvent,
|
||||||
MouseButton, MouseButtonState, PointerAxisEvent, PointerButtonEvent, PointerMotionAbsoluteEvent,
|
MouseButton, MouseButtonState, PointerAxisEvent, PointerButtonEvent, PointerMotionAbsoluteEvent,
|
||||||
|
@ -291,17 +291,8 @@ impl Renderer for WinitGraphicsBackend {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "wayland_frontend")]
|
#[cfg(feature = "wayland_frontend")]
|
||||||
fn import_shm(&mut self, buffer: &wl_buffer::WlBuffer) -> Result<Self::TextureId, Self::Error> {
|
fn import_buffer(&mut self, buffer: &wl_buffer::WlBuffer, egl: Option<&EGLBufferReader>) -> Result<Self::TextureId, Self::Error> {
|
||||||
self.renderer.import_shm(buffer)
|
self.renderer.import_buffer(buffer, egl)
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "wayland_frontend")]
|
|
||||||
fn import_egl(&mut self, buffer: &EGLBuffer) -> Result<Self::TextureId, Self::Error> {
|
|
||||||
self.renderer.import_egl(buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn destroy_texture(&mut self, texture: Self::TextureId) -> Result<(), Self::Error> {
|
|
||||||
self.renderer.destroy_texture(texture)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn begin(
|
fn begin(
|
||||||
|
|
Loading…
Reference in New Issue