From 4d5d7afb5ab115ff375980ae5735d52d7f1fe768 Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Wed, 7 Apr 2021 00:42:14 +0200 Subject: [PATCH] Introduce a new OpenGL renderer This pulls a lot of code from anvil/src/glium_drawer and replaces glium as glium has too many assumptions about the backend. (Mostly that a fixed framebuffer exists, see fix bullet point.) Depending on how picky glium is, we could try to re-introduce glium support later with a some workarounds, but for now this is actually more straight-forward to support and test. - Add a new GL renderer, that only depends on egl as an initialization platform. In particular do not depend on a surface being available. Renderers share some basic drawing functions and may bind objects to render upon. E.g. surfaces or buffers, textures, etc for the gles2 renderer. - Be explicit about extensions we require and use. Use some very very common ones to make our lives easier (e.g. BGRA and unpack to read in manditory shm formats). - Enable GL debug output - Allow more flexible rendering (e.g. 3D compositors) by allowing user-provided projection matrices. Also provide helper functions to allow easy-ish handling of surface and output transformations. - Add API for renderers to tell the wayland-frontend about supported buffer-types. - Also incoperate code from anvil/src/shm_load to handle buffer loading in the renderer (as this is a renderer dependant operation). --- src/backend/graphics/gl.rs | 51 --- src/backend/graphics/glium.rs | 267 ------------ src/backend/graphics/software.rs | 17 - src/backend/renderer/gles2/mod.rs | 568 ++++++++++++++++++++++++++ src/backend/renderer/gles2/shaders.rs | 92 +++++ src/backend/renderer/mod.rs | 173 ++++++++ 6 files changed, 833 insertions(+), 335 deletions(-) delete mode 100644 src/backend/graphics/gl.rs delete mode 100644 src/backend/graphics/glium.rs delete mode 100644 src/backend/graphics/software.rs create mode 100644 src/backend/renderer/gles2/mod.rs create mode 100644 src/backend/renderer/gles2/shaders.rs create mode 100644 src/backend/renderer/mod.rs diff --git a/src/backend/graphics/gl.rs b/src/backend/graphics/gl.rs deleted file mode 100644 index 8aa1fc0..0000000 --- a/src/backend/graphics/gl.rs +++ /dev/null @@ -1,51 +0,0 @@ -//! OpenGL rendering types - -use nix::libc::c_void; - -use super::{PixelFormat, SwapBuffersError}; - -#[allow(clippy::all, missing_docs)] -pub(crate) mod ffi { - include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs")); -} - -pub use self::ffi::Gles2; - -/// Trait that describes objects that have an OpenGL context -/// and can be used to render upon -pub trait GLGraphicsBackend { - /// Swaps buffers at the end of a frame. - fn swap_buffers(&self) -> Result<(), SwapBuffersError>; - - /// Returns the address of an OpenGL function. - fn get_proc_address(&self, symbol: &str) -> *const c_void; - - /// Returns the dimensions of the window, or screen, etc in points. - /// - /// These are the actual pixels of the underlying graphics backend. - /// For nested compositors you will need to handle the scaling - /// of the root compositor yourself, if you want to. - fn get_framebuffer_dimensions(&self) -> (u32, u32); - - /// Returns true if the OpenGL context is the current one in the thread. - fn is_current(&self) -> bool; - - /// Makes the OpenGL context the current context in the current thread. - /// - /// # Safety - /// - /// The context cannot be made current on multiple threads. - unsafe fn make_current(&self) -> Result<(), SwapBuffersError>; - - /// Returns the pixel format of the main framebuffer of the context. - fn get_pixel_format(&self) -> PixelFormat; -} - -/// Loads a Raw GLES Interface for a given [`GLGraphicsBackend`] -/// -/// This remains valid as long as the underlying [`GLGraphicsBackend`] is alive -/// and may only be used in combination with the backend. Using this with any -/// other gl context or after the backend was dropped *may* cause undefined behavior. -pub fn load_raw_gl(backend: &B) -> Gles2 { - Gles2::load_with(|s| backend.get_proc_address(s) as *const _) -} diff --git a/src/backend/graphics/glium.rs b/src/backend/graphics/glium.rs deleted file mode 100644 index 96f2226..0000000 --- a/src/backend/graphics/glium.rs +++ /dev/null @@ -1,267 +0,0 @@ -//! Glium compatibility module - -use crate::backend::graphics::{gl::GLGraphicsBackend, SwapBuffersError}; -use glium::{ - backend::{Backend, Context, Facade}, - debug::DebugCallbackBehavior, - SwapBuffersError as GliumSwapBuffersError, -}; -use std::{ - cell::{Cell, Ref, RefCell, RefMut}, - fmt, - os::raw::c_void, - rc::Rc, -}; - -/// Wrapper to expose `Glium` compatibility -pub struct GliumGraphicsBackend { - context: Rc, - backend: Rc>, - // at least this type is not `Send` or even `Sync`. - // while there can be multiple Frames, they cannot in parallel call `set_finish`. - // so a buffer of the last error is sufficient, if always cleared... - error_channel: Rc>>>, -} - -// GLGraphicsBackend is a trait, so we have to impl Debug manually -impl fmt::Debug for GliumGraphicsBackend { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - struct BackendDebug<'a, T: GLGraphicsBackend>(&'a Rc>); - impl<'a, T: GLGraphicsBackend> fmt::Debug for BackendDebug<'a, T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let b = &self.0 .0.borrow(); - f.debug_struct("GLGraphicsBackend") - .field("framebuffer_dimensions", &b.get_framebuffer_dimensions()) - .field("is_current", &b.is_current()) - .field("pixel_format", &b.get_pixel_format()) - .finish() - } - } - - f.debug_struct("GliumGraphicsBackend") - .field("context", &"...") - .field("backend", &BackendDebug(&self.backend)) - .field("error_channel", &"...") - .finish() - } -} - -struct InternalBackend(RefCell, Rc>>>); - -impl GliumGraphicsBackend { - fn new(backend: T) -> GliumGraphicsBackend { - let error_channel = Rc::new(Cell::new(None)); - let internal = Rc::new(InternalBackend(RefCell::new(backend), error_channel.clone())); - - GliumGraphicsBackend { - // cannot fail - context: unsafe { - Context::new(internal.clone(), true, DebugCallbackBehavior::default()).unwrap() - }, - backend: internal, - error_channel, - } - } - - /// Start drawing on the backbuffer. - /// - /// This function returns a [`Frame`], which can be used to draw on it. - /// When the [`Frame`] is destroyed, the buffers are swapped. - /// - /// Note that destroying a [`Frame`] is immediate, even if vsync is enabled. - #[inline] - pub fn draw(&self) -> Frame { - Frame( - glium::Frame::new(self.context.clone(), self.backend.get_framebuffer_dimensions()), - self.error_channel.clone(), - ) - } - - /// Borrow the underlying backend. - /// - /// This follows the same semantics as [`std::cell::RefCell`](RefCell::borrow). - /// Multiple read-only borrows are possible. Borrowing the - /// backend while there is a mutable reference will panic. - pub fn borrow(&self) -> Ref<'_, T> { - self.backend.0.borrow() - } - - /// Borrow the underlying backend mutably. - /// - /// This follows the same semantics as [`std::cell::RefCell`](RefCell::borrow_mut). - /// Holding any other borrow while trying to borrow the backend - /// mutably will panic. Note that Glium will borrow the backend - /// (not mutably) during rendering. - pub fn borrow_mut(&self) -> RefMut<'_, T> { - self.backend.0.borrow_mut() - } -} - -impl Facade for GliumGraphicsBackend { - fn get_context(&self) -> &Rc { - &self.context - } -} - -impl From for GliumGraphicsBackend { - fn from(backend: T) -> Self { - GliumGraphicsBackend::new(backend) - } -} - -unsafe impl Backend for InternalBackend { - fn swap_buffers(&self) -> Result<(), GliumSwapBuffersError> { - if let Err(err) = self.0.borrow().swap_buffers() { - Err(match err { - SwapBuffersError::ContextLost(err) => { - self.1.set(Some(err)); - GliumSwapBuffersError::ContextLost - } - SwapBuffersError::TemporaryFailure(err) => { - self.1.set(Some(err)); - GliumSwapBuffersError::AlreadySwapped - } - // I do not think, this may happen, but why not - SwapBuffersError::AlreadySwapped => GliumSwapBuffersError::AlreadySwapped, - }) - } else { - Ok(()) - } - } - - unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void { - self.0.borrow().get_proc_address(symbol) as *const c_void - } - - fn get_framebuffer_dimensions(&self) -> (u32, u32) { - self.0.borrow().get_framebuffer_dimensions() - } - - fn is_current(&self) -> bool { - self.0.borrow().is_current() - } - - unsafe fn make_current(&self) { - // TODO, if this ever blows up anvil, we should probably silently ignore this. - // But I have no idea, if that may happen or what glium does, if the context is not current... - // So lets leave this in to do some real world testing - self.0.borrow().make_current().expect("Context was lost") - } -} - -/// Implementation of `glium::Surface`, targeting the default framebuffer. -/// -/// The back- and front-buffers are swapped when you call `finish`. -/// -/// You **must** call either `finish` or `set_finish` or else the destructor will panic. -pub struct Frame(glium::Frame, Rc>>>); - -impl Frame { - /// Stop drawing, swap the buffers, and consume the Frame. - /// - /// See the documentation of [`SwapBuffersError`] about what is being returned. - pub fn finish(mut self) -> Result<(), SwapBuffersError> { - self.set_finish() - } - - /// Stop drawing, swap the buffers. - /// - /// The Frame can now be dropped regularly. Calling `finish()` or `set_finish()` again will cause `Err(SwapBuffersError::AlreadySwapped)` to be returned. - pub fn set_finish(&mut self) -> Result<(), SwapBuffersError> { - let res = self.0.set_finish(); - let err = self.1.take(); - match (res, err) { - (Ok(()), _) => Ok(()), - (Err(GliumSwapBuffersError::AlreadySwapped), Some(err)) => { - Err(SwapBuffersError::TemporaryFailure(err)) - } - (Err(GliumSwapBuffersError::AlreadySwapped), None) => Err(SwapBuffersError::AlreadySwapped), - (Err(GliumSwapBuffersError::ContextLost), Some(err)) => Err(SwapBuffersError::ContextLost(err)), - _ => unreachable!(), - } - } -} - -impl glium::Surface for Frame { - fn clear( - &mut self, - rect: Option<&glium::Rect>, - color: Option<(f32, f32, f32, f32)>, - color_srgb: bool, - depth: Option, - stencil: Option, - ) { - self.0.clear(rect, color, color_srgb, depth, stencil) - } - - fn get_dimensions(&self) -> (u32, u32) { - self.0.get_dimensions() - } - - fn get_depth_buffer_bits(&self) -> Option { - self.0.get_depth_buffer_bits() - } - - fn get_stencil_buffer_bits(&self) -> Option { - self.0.get_stencil_buffer_bits() - } - - fn draw<'a, 'b, V, I, U>( - &mut self, - v: V, - i: I, - program: &glium::Program, - uniforms: &U, - draw_parameters: &glium::draw_parameters::DrawParameters<'_>, - ) -> Result<(), glium::DrawError> - where - V: glium::vertex::MultiVerticesSource<'b>, - I: Into>, - U: glium::uniforms::Uniforms, - { - self.0.draw(v, i, program, uniforms, draw_parameters) - } - - fn blit_from_frame( - &self, - source_rect: &glium::Rect, - target_rect: &glium::BlitTarget, - filter: glium::uniforms::MagnifySamplerFilter, - ) { - self.0.blit_from_frame(source_rect, target_rect, filter); - } - - fn blit_from_simple_framebuffer( - &self, - source: &glium::framebuffer::SimpleFrameBuffer<'_>, - source_rect: &glium::Rect, - target_rect: &glium::BlitTarget, - filter: glium::uniforms::MagnifySamplerFilter, - ) { - self.0 - .blit_from_simple_framebuffer(source, source_rect, target_rect, filter) - } - - fn blit_from_multioutput_framebuffer( - &self, - source: &glium::framebuffer::MultiOutputFrameBuffer<'_>, - source_rect: &glium::Rect, - target_rect: &glium::BlitTarget, - filter: glium::uniforms::MagnifySamplerFilter, - ) { - self.0 - .blit_from_multioutput_framebuffer(source, source_rect, target_rect, filter) - } - - fn blit_color( - &self, - source_rect: &glium::Rect, - target: &S, - target_rect: &glium::BlitTarget, - filter: glium::uniforms::MagnifySamplerFilter, - ) where - S: glium::Surface, - { - self.0.blit_color(source_rect, target, target_rect, filter) - } -} diff --git a/src/backend/graphics/software.rs b/src/backend/graphics/software.rs deleted file mode 100644 index 700f292..0000000 --- a/src/backend/graphics/software.rs +++ /dev/null @@ -1,17 +0,0 @@ -//! Common traits and types used for software rendering on graphics backends - -use std::error::Error; -use wayland_server::protocol::wl_shm::Format; - -/// Trait that describes objects providing a software rendering implementation -pub trait CpuGraphicsBackend { - /// Render a given buffer of a given format at a specified place in the framebuffer - /// - /// # Error - /// Returns an error if the buffer size does not match the required amount of pixels - /// for the given size or if the position and size is out of scope of the framebuffer. - fn render(&mut self, buffer: &[u8], format: Format, at: (u32, u32), size: (u32, u32)) -> Result<(), E>; - - /// Returns the dimensions of the framebuffer - fn get_framebuffer_dimensions(&self) -> (u32, u32); -} diff --git a/src/backend/renderer/gles2/mod.rs b/src/backend/renderer/gles2/mod.rs new file mode 100644 index 0000000..e4b2534 --- /dev/null +++ b/src/backend/renderer/gles2/mod.rs @@ -0,0 +1,568 @@ +use std::ffi::CStr; +use std::ptr; +use std::sync::Arc; + +use cgmath::{prelude::*, Matrix3, Vector2}; + +mod shaders; +use crate::backend::allocator::dmabuf::{Dmabuf, WeakDmabuf}; +use crate::backend::egl::{EGLContext, EGLSurface, ffi::egl::types::EGLImage}; +use super::{Renderer, Frame, Bind, Unbind, Transform, Texture}; + +#[cfg(feature = "wayland_frontend")] +use wayland_server::protocol::{wl_shm, wl_buffer}; + +#[allow(clippy::all, missing_docs)] +pub mod ffi { + include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs")); +} + +#[derive(Debug)] +struct Gles2Program { + program: ffi::types::GLuint, + uniform_tex: ffi::types::GLint, + uniform_matrix: ffi::types::GLint, + uniform_invert_y: ffi::types::GLint, + uniform_alpha: ffi::types::GLint, + attrib_position: ffi::types::GLint, + attrib_tex_coords: ffi::types::GLint, +} + +pub struct Gles2Texture { + texture: ffi::types::GLuint, + texture_kind: usize, + is_external: bool, + y_inverted: bool, + width: u32, + height: u32, +} + +impl Texture for Gles2Texture { + fn width(&self) -> u32 { + self.width + } + fn height(&self) -> u32 { + self.height + } +} + +#[derive(Clone)] +struct WeakGles2Buffer { + dmabuf: WeakDmabuf, + image: EGLImage, + rbo: ffi::types::GLuint, + fbo: ffi::types::GLuint, +} + +struct Gles2Buffer { + internal: WeakGles2Buffer, + _dmabuf: Dmabuf, +} + +pub struct Gles2Renderer { + internal: Arc, + buffers: Vec, + current_buffer: Option, +} + +struct Gles2RendererInternal { + gl: ffi::Gles2, + egl: EGLContext, + extensions: Vec, + programs: [Gles2Program; shaders::FRAGMENT_COUNT], + logger: Option<*mut ::slog::Logger>, +} + +pub struct Gles2Frame { + internal: Arc, + projection: Matrix3, +} + +#[derive(thiserror::Error, Debug)] +pub enum Gles2Error { + #[error("Failed to compile Shader: {0}")] + ShaderCompileError(&'static str), + #[error("Failed to link Program")] + ProgramLinkError, + #[error("Failed to bind Framebuffer")] + FramebufferBindingError, + #[error("Failed to load GL functions from EGL")] + GLFunctionLoaderError, + /// The required GL extension is not supported by the underlying implementation + #[error("None of the following GL extensions is supported by the underlying GL implementation, at least one is required: {0:?}")] + GLExtensionNotSupported(&'static [&'static str]), + #[error("Failed to active egl context")] + ContextActivationError(#[from] crate::backend::egl::MakeCurrentError), + #[error("Failed to convert dmabuf to EGLImage")] + BindBufferEGLError(#[source] crate::backend::egl::Error), + #[error("Unsupported pixel format: {0:?}")] + #[cfg(feature = "wayland_frontend")] + UnsupportedPixelFormat(wl_shm::Format), + #[error("Error accessing the buffer ({0:?})")] + #[cfg(feature = "wayland_frontend")] + BufferAccessError(crate::wayland::shm::BufferAccessError), +} + +extern "system" fn gl_debug_log(_source: ffi::types::GLenum, + gltype: ffi::types::GLenum, + _id: ffi::types::GLuint, + _severity: ffi::types::GLenum, + _length: ffi::types::GLsizei, + message: *const ffi::types::GLchar, + user_param: *mut nix::libc::c_void) +{ + let _ = std::panic::catch_unwind(move || { + unsafe { + let msg = CStr::from_ptr(message); + let log = Box::from_raw(user_param as *mut ::slog::Logger); + let message_utf8 = msg.to_string_lossy(); + match gltype { + ffi::DEBUG_TYPE_ERROR | ffi::DEBUG_TYPE_UNDEFINED_BEHAVIOR => error!(log, "[GL] {}", message_utf8), + ffi::DEBUG_TYPE_DEPRECATED_BEHAVIOR => warn!(log, "[GL] {}", message_utf8), + _ => debug!(log, "[GL] {}", message_utf8), + }; + std::mem::forget(log); + } + }); +} + +unsafe fn compile_shader(gl: &ffi::Gles2, variant: ffi::types::GLuint, src: &'static str) -> Result { + let shader = gl.CreateShader(variant); + gl.ShaderSource(shader, 1, &src.as_ptr() as *const *const u8 as *const *const i8, &(src.len() as i32) as *const _); + gl.CompileShader(shader); + + let mut status = ffi::FALSE as i32; + gl.GetShaderiv(shader, ffi::COMPILE_STATUS, &mut status as *mut _); + if status == ffi::FALSE as i32 { + gl.DeleteShader(shader); + return Err(Gles2Error::ShaderCompileError(src)); + } + + Ok(shader) +} + +unsafe fn link_program(gl: &ffi::Gles2, vert_src: &'static str, frag_src: &'static str) -> Result { + let vert = compile_shader(gl, ffi::VERTEX_SHADER, vert_src)?; + let frag = compile_shader(gl, ffi::FRAGMENT_SHADER, frag_src)?; + let program = gl.CreateProgram(); + gl.AttachShader(program, vert); + gl.AttachShader(program, frag); + gl.LinkProgram(program); + gl.DetachShader(program, vert); + gl.DetachShader(program, frag); + gl.DeleteShader(vert); + gl.DeleteShader(frag); + + let mut status = ffi::FALSE as i32; + gl.GetProgramiv(program, ffi::LINK_STATUS, &mut status as *mut _); + if status == ffi::FALSE as i32 { + gl.DeleteProgram(program); + return Err(Gles2Error::ProgramLinkError); + } + + Ok(program) +} + +unsafe fn texture_program(gl: &ffi::Gles2, frag: &'static str) -> Result { + let program = link_program(&gl, shaders::VERTEX_SHADER, frag)?; + + let position = CStr::from_bytes_with_nul(b"position\0").expect("NULL terminated"); + let tex_coords = CStr::from_bytes_with_nul(b"tex_coords\0").expect("NULL terminated"); + let tex = CStr::from_bytes_with_nul(b"tex\0").expect("NULL terminated"); + let matrix = CStr::from_bytes_with_nul(b"matrix\0").expect("NULL terminated"); + let invert_y = CStr::from_bytes_with_nul(b"invert_y\0").expect("NULL terminated"); + let alpha = CStr::from_bytes_with_nul(b"alpha\0").expect("NULL terminated"); + + Ok(Gles2Program { + program, + uniform_tex: gl.GetUniformLocation(program, tex.as_ptr() as *const i8), + uniform_matrix: gl.GetUniformLocation(program, matrix.as_ptr() as *const i8), + uniform_invert_y: gl.GetUniformLocation(program, invert_y.as_ptr() as *const i8), + uniform_alpha: gl.GetUniformLocation(program, alpha.as_ptr() as *const i8), + attrib_position: gl.GetAttribLocation(program, position.as_ptr() as *const i8), + attrib_tex_coords: gl.GetAttribLocation(program, tex_coords.as_ptr() as *const i8), + }) +} + +impl Gles2Renderer { + pub fn new(context: EGLContext, logger: L) -> Result + where + L: Into> + { + let log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "renderer_gles2")); + + unsafe { context.make_current()? }; + + let (gl, exts, logger) = unsafe { + let gl = ffi::Gles2::load_with(|s| crate::backend::egl::get_proc_address(s) as *const _); + let ext_ptr = gl.GetString(ffi::EXTENSIONS) as *const i8; + if ext_ptr.is_null() { + return Err(Gles2Error::GLFunctionLoaderError); + } + + let exts = { + let p = CStr::from_ptr(ext_ptr); + let list = String::from_utf8(p.to_bytes().to_vec()).unwrap_or_else(|_| String::new()); + list.split(' ').map(|e| e.to_string()).collect::>() + }; + + info!(log, "Initializing OpenGL ES Renderer"); + info!(log, "GL Version: {:?}", CStr::from_ptr(gl.GetString(ffi::VERSION) as *const i8)); + info!(log, "GL Vendor: {:?}", CStr::from_ptr(gl.GetString(ffi::VENDOR) as *const i8)); + info!(log, "GL Renderer: {:?}", CStr::from_ptr(gl.GetString(ffi::RENDERER) as *const i8)); + info!(log, "Supported GL Extensions: {:?}", exts); + + // required for the manditory wl_shm formats + if !exts.iter().any(|ext| ext == "GL_EXT_texture_format_BGRA8888") { + return Err(Gles2Error::GLExtensionNotSupported(&["GL_EXT_texture_format_BGRA8888"])); + } + // required for buffers without linear memory layout + if !exts.iter().any(|ext| ext == "GL_EXT_unpack_subimage") { + return Err(Gles2Error::GLExtensionNotSupported(&["GL_EXT_unpack_subimage"])); + } + + let logger = if exts.iter().any(|ext| ext == "GL_KHR_debug") { + let logger = Box::into_raw(Box::new(log.clone())); + gl.Enable(ffi::DEBUG_OUTPUT); + gl.Enable(ffi::DEBUG_OUTPUT_SYNCHRONOUS); + gl.DebugMessageCallback(Some(gl_debug_log), logger as *mut nix::libc::c_void); + Some(logger) + } else { None }; + + (gl, exts, logger) + }; + + let programs = { + unsafe { [ + texture_program(&gl, shaders::FRAGMENT_SHADER_ABGR)?, + texture_program(&gl, shaders::FRAGMENT_SHADER_XBGR)?, + texture_program(&gl, shaders::FRAGMENT_SHADER_BGRA)?, + texture_program(&gl, shaders::FRAGMENT_SHADER_BGRX)?, + texture_program(&gl, shaders::FRAGMENT_SHADER_EXTERNAL)?, + ] } + }; + + Ok(Gles2Renderer { + internal: Arc::new(Gles2RendererInternal { + gl, + egl: context, + extensions: exts, + programs, + logger, + }), + buffers: Vec::new(), + current_buffer: None, + }) + } +} + +impl Bind<&EGLSurface> for Gles2Renderer { + fn bind(&mut self, surface: &EGLSurface) -> Result<(), Gles2Error> { + if self.current_buffer.is_some() { + self.unbind()?; + } + + unsafe { + self.internal.egl.make_current_with_surface(&surface)?; + } + + Ok(()) + } +} + +impl Bind for Gles2Renderer { + fn bind(&mut self, dmabuf: Dmabuf) -> Result<(), Gles2Error> { + if self.current_buffer.is_some() { + self.unbind()?; + } + + unsafe { + self.internal.egl.make_current()?; + } + + // Free outdated buffer resources + // TODO: Replace with `drain_filter` once it lands + let mut i = 0; + while i != self.buffers.len() { + if self.buffers[i].dmabuf.upgrade().is_none() { + self.buffers.remove(i); + } else { + i += 1; + } + } + + let buffer = self.buffers + .iter() + .find(|buffer| dmabuf == buffer.dmabuf) + .map(|buf| { + let dmabuf = buf.dmabuf.upgrade().expect("Dmabuf equal check succeeded for freed buffer"); + Ok(Gles2Buffer { + internal: buf.clone(), + // we keep the dmabuf alive as long as we are bound + _dmabuf: dmabuf + }) + }) + .unwrap_or_else(|| { + let image = self.internal.egl.display.create_image_from_dmabuf(&dmabuf).map_err(Gles2Error::BindBufferEGLError)?; + + unsafe { + let mut rbo = 0; + self.internal.gl.GenRenderbuffers(1, &mut rbo as *mut _); + self.internal.gl.BindRenderbuffer(ffi::RENDERBUFFER, rbo); + self.internal.gl.EGLImageTargetRenderbufferStorageOES(ffi::RENDERBUFFER, image); + self.internal.gl.BindRenderbuffer(ffi::RENDERBUFFER, 0); + + let mut fbo = 0; + self.internal.gl.GenFramebuffers(1, &mut fbo as *mut _); + self.internal.gl.BindFramebuffer(ffi::FRAMEBUFFER, fbo); + self.internal.gl.FramebufferRenderbuffer(ffi::FRAMEBUFFER, ffi::COLOR_ATTACHMENT0, ffi::RENDERBUFFER, rbo); + let status = self.internal.gl.CheckFramebufferStatus(ffi::FRAMEBUFFER); + self.internal.gl.BindFramebuffer(ffi::FRAMEBUFFER, 0); + + if status != ffi::FRAMEBUFFER_COMPLETE { + //TODO wrap image and drop here + return Err(Gles2Error::FramebufferBindingError); + } + + let weak = WeakGles2Buffer { + dmabuf: dmabuf.weak(), + image, + rbo, + fbo, + }; + + self.buffers.push(weak.clone()); + + Ok(Gles2Buffer { + internal: weak, + _dmabuf: dmabuf + }) + } + })?; + + unsafe { + self.internal.gl.BindFramebuffer(ffi::FRAMEBUFFER, buffer.internal.fbo); + } + self.current_buffer = Some(buffer); + Ok(()) + } +} + +impl Unbind for Gles2Renderer { + fn unbind(&mut self) -> Result<(), Gles2Error> { + unsafe { + self.internal.egl.make_current()?; + self.internal.gl.BindFramebuffer(ffi::FRAMEBUFFER, 0); + } + self.current_buffer = None; + let _ = self.internal.egl.unbind(); + Ok(()) + } +} + +impl Renderer for Gles2Renderer { + type Error = Gles2Error; + type Texture = Gles2Texture; + type Frame = Gles2Frame; + + fn begin(&mut self, width: u32, height: u32, transform: Transform) -> Result { + if !self.internal.egl.is_current() { + // Do not call this unconditionally. + // If surfaces are in use (e.g. for winit) this would unbind them. + unsafe { self.internal.egl.make_current()?; } + } + unsafe { + self.internal.gl.Viewport(0, 0, width as i32, height as i32); + + self.internal.gl.Enable(ffi::BLEND); + self.internal.gl.BlendFunc(ffi::ONE, ffi::ONE_MINUS_SRC_ALPHA); + } + + // output transformation passed in by the user + let mut projection = Matrix3::::identity(); + projection = projection * Matrix3::from_translation(Vector2::new(width as f32 / 2.0, height as f32 / 2.0)); + projection = projection * transform.matrix(); + let (transformed_width, transformed_height) = transform.transform_size(width, height); + projection = projection * Matrix3::from_translation(Vector2::new(-(transformed_width as f32) / 2.0, -(transformed_height as f32) / 2.0)); + + // replicate https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glOrtho.xml + // glOrtho(0, width, 0, height, 1, 1); + let mut renderer = Matrix3::::identity(); + let t = Matrix3::::identity(); + let x = 2.0 / (width as f32); + let y = 2.0 / (height as f32); + + // Rotation & Reflection + renderer[0][0] = x * t[0][0]; + renderer[1][0] = x * t[0][1]; + renderer[0][1] = y * -t[1][0]; + renderer[1][1] = y * -t[1][1]; + + //Translation + renderer[2][0] = -(1.0f32.copysign(renderer[0][0] + renderer[1][0])); + renderer[2][1] = -(1.0f32.copysign(renderer[0][1] + renderer[1][1])); + + Ok(Gles2Frame { + internal: self.internal.clone(), + projection: projection * renderer, + }) + } + + #[cfg(feature = "wayland_frontend")] + fn shm_formats(&self) -> &[wl_shm::Format] { + &[ + wl_shm::Format::Abgr8888, + wl_shm::Format::Xbgr8888, + wl_shm::Format::Argb8888, + wl_shm::Format::Xrgb8888, + ] + } + + #[cfg(feature = "wayland_frontend")] + fn import_shm(&self, buffer: &wl_buffer::WlBuffer) -> Result { + use crate::wayland::shm::with_buffer_contents; + + with_buffer_contents(&buffer, |slice, data| { + if !self.internal.egl.is_current() { + unsafe { self.internal.egl.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 mut tex = 0; + unsafe { + self.internal.gl.GenTextures(1, &mut tex); + self.internal.gl.BindTexture(ffi::TEXTURE_2D, tex); + + self.internal.gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_S, ffi::CLAMP_TO_EDGE as i32); + self.internal.gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_T, ffi::CLAMP_TO_EDGE as i32); + self.internal.gl.PixelStorei(ffi::UNPACK_ROW_LENGTH, stride / pixelsize); + self.internal.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.internal.gl.PixelStorei(ffi::UNPACK_ROW_LENGTH, 0); + self.internal.gl.BindTexture(ffi::TEXTURE_2D, 0); + } + + Ok(Gles2Texture { + texture: tex, + texture_kind: shader_idx, + is_external: false, + y_inverted: false, + width: width as u32, + height: height as u32, + }) + }).map_err(Gles2Error::BufferAccessError)? + } +} + +impl Drop for Gles2RendererInternal { + fn drop(&mut self) { + unsafe { + if self.egl.make_current().is_ok() { + for program in &self.programs { + self.gl.DeleteProgram(program.program); + } + + if self.extensions.iter().any(|ext| ext == "GL_KHR_debug") { + self.gl.Disable(ffi::DEBUG_OUTPUT); + self.gl.DebugMessageCallback(None, ptr::null()); + } + if let Some(logger) = self.logger { + let _ = Box::from_raw(logger); + } + + let _ = self.egl.unbind(); + } + } + } +} + +static VERTS: [ffi::types::GLfloat; 8] = [ + 1.0, 0.0, // top right + 0.0, 0.0, // top left + 1.0, 1.0, // bottom right + 0.0, 1.0, // bottom left +]; + +static TEX_COORDS: [ffi::types::GLfloat; 8] = [ + 1.0, 0.0, // top right + 0.0, 0.0, // top left + 1.0, 1.0, // bottom right + 0.0, 1.0, // bottom left +]; + +impl Frame for Gles2Frame { + type Error = Gles2Error; + type Texture = Gles2Texture; + + fn clear(&mut self, color: [f32; 4]) -> Result<(), Self::Error> { + unsafe { + self.internal.gl.ClearColor(color[0], color[1], color[2], color[3]); + self.internal.gl.Clear(ffi::COLOR_BUFFER_BIT); + } + + Ok(()) + } + + fn render_texture(&mut self, tex: &Self::Texture, mut matrix: Matrix3, alpha: f32) -> Result<(), Self::Error> { + //apply output transformation + matrix = self.projection * matrix; + + let target = if tex.is_external { ffi::TEXTURE_EXTERNAL_OES } else { ffi::TEXTURE_2D }; + + // render + unsafe { + self.internal.gl.ActiveTexture(ffi::TEXTURE0); + self.internal.gl.BindTexture(target, tex.texture); + self.internal.gl.TexParameteri(target, ffi::TEXTURE_MIN_FILTER, ffi::LINEAR as i32); + self.internal.gl.UseProgram(self.internal.programs[tex.texture_kind].program); + + self.internal.gl.Uniform1i(self.internal.programs[tex.texture_kind].uniform_tex, 0); + self.internal.gl.UniformMatrix3fv(self.internal.programs[tex.texture_kind].uniform_matrix, 1, ffi::FALSE, matrix.as_ptr()); + self.internal.gl.Uniform1i(self.internal.programs[tex.texture_kind].uniform_invert_y, if tex.y_inverted { 1 } else { 0 }); + self.internal.gl.Uniform1f(self.internal.programs[tex.texture_kind].uniform_alpha, alpha); + + self.internal.gl.VertexAttribPointer(self.internal.programs[tex.texture_kind].attrib_position as u32, 2, ffi::FLOAT, ffi::FALSE, 0, VERTS.as_ptr() as *const _); + self.internal.gl.VertexAttribPointer(self.internal.programs[tex.texture_kind].attrib_tex_coords as u32, 2, ffi::FLOAT, ffi::FALSE, 0, TEX_COORDS.as_ptr() as *const _); + + self.internal.gl.EnableVertexAttribArray(self.internal.programs[tex.texture_kind].attrib_position as u32); + self.internal.gl.EnableVertexAttribArray(self.internal.programs[tex.texture_kind].attrib_tex_coords as u32); + + self.internal.gl.DrawArrays(ffi::TRIANGLE_STRIP, 0, 4); + + self.internal.gl.DisableVertexAttribArray(self.internal.programs[tex.texture_kind].attrib_position as u32); + self.internal.gl.DisableVertexAttribArray(self.internal.programs[tex.texture_kind].attrib_tex_coords as u32); + + self.internal.gl.BindTexture(target, 0); + } + + Ok(()) + } + + fn finish(self) -> Result<(), crate::backend::SwapBuffersError> { + unsafe { + self.internal.gl.Flush(); + self.internal.gl.Disable(ffi::BLEND); + } + + Ok(()) + } +} diff --git a/src/backend/renderer/gles2/shaders.rs b/src/backend/renderer/gles2/shaders.rs new file mode 100644 index 0000000..862e7bc --- /dev/null +++ b/src/backend/renderer/gles2/shaders.rs @@ -0,0 +1,92 @@ +/* + * OpenGL Shaders + */ +pub const VERTEX_SHADER: &str = r#" +#version 100 +uniform mat3 matrix; +uniform bool invert_y; +attribute vec2 position; +attribute vec2 tex_coords; +varying vec2 v_tex_coords; +void main() { + gl_Position = vec4(matrix * vec3(position, 1.0), 1.0); + if (invert_y) { + v_tex_coords = vec2(tex_coords.x, 1.0 - tex_coords.y); + } else { + v_tex_coords = tex_coords; + } +}"#; + +pub const FRAGMENT_COUNT: usize = 5; + +pub const FRAGMENT_SHADER_ABGR: &str = r#" +#version 100 +precision mediump float; +uniform sampler2D tex; +uniform float alpha; +varying vec2 v_tex_coords; +void main() { + vec4 color = texture2D(tex, v_tex_coords); + gl_FragColor.r = color.w; + gl_FragColor.g = color.z; + gl_FragColor.b = color.y; + gl_FragColor.a = color.x * alpha; +} +"#; + +pub const FRAGMENT_SHADER_XBGR: &str = r#" +#version 100 +precision mediump float; +uniform sampler2D tex; +uniform float alpha; +varying vec2 v_tex_coords; +void main() { + vec4 color = texture2D(tex, v_tex_coords); + gl_FragColor.r = color.w; + gl_FragColor.g = color.z; + gl_FragColor.b = color.y; + gl_FragColor.a = alpha; +} +"#; + +pub const FRAGMENT_SHADER_BGRA: &str = r#" +#version 100 +precision mediump float; +uniform sampler2D tex; +uniform float alpha; +varying vec2 v_tex_coords; +void main() { + vec4 color = texture2D(tex, v_tex_coords); + gl_FragColor.r = color.z; + gl_FragColor.g = color.y; + gl_FragColor.b = color.x; + gl_FragColor.a = color.w * alpha; +} +"#; + +pub const FRAGMENT_SHADER_BGRX: &str = r#" +#version 100 +precision mediump float; +uniform sampler2D tex; +uniform float alpha; +varying vec2 v_tex_coords; +void main() { + vec4 color = texture2D(tex, v_tex_coords); + gl_FragColor.r = color.z; + gl_FragColor.g = color.y; + gl_FragColor.b = color.x; + gl_FragColor.a = alpha; +} +"#; + +pub const FRAGMENT_SHADER_EXTERNAL: &str = r#" +#version 100 +#extension GL_OES_EGL_image_external : require +precision mediump float; +uniform samplerExternalOES tex; +uniform float alpha; +varying vec2 v_tex_coords; +void main() { + gl_FragColor = texture2D(tex, v_tex_coords) * alpha; +} +"#; \ No newline at end of file diff --git a/src/backend/renderer/mod.rs b/src/backend/renderer/mod.rs new file mode 100644 index 0000000..12ef623 --- /dev/null +++ b/src/backend/renderer/mod.rs @@ -0,0 +1,173 @@ +use std::error::Error; + +use cgmath::{prelude::*, Matrix3, Vector2}; +#[cfg(feature = "wayland_frontend")] +use wayland_server::protocol::{wl_shm, wl_buffer}; + +use crate::backend::SwapBuffersError; +#[cfg(feature = "renderer_gl")] +pub mod gles2; + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub enum Transform { + Normal, + _90, + _180, + _270, + Flipped, + Flipped90, + Flipped180, + Flipped270, +} + +impl Transform { + pub fn matrix(&self) -> Matrix3 { + match self { + Transform::Normal => Matrix3::new( + 1.0, 0.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 0.0, 1.0, + ), + Transform::_90 => Matrix3::new( + 0.0, -1.0, 0.0, + 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, + ), + Transform::_180 => Matrix3::new( + -1.0, 0.0, 0.0, + 0.0, -1.0, 0.0, + 0.0, 0.0, 1.0, + ), + Transform::_270 => Matrix3::new( + 0.0, 1.0, 0.0, + -1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, + ), + Transform::Flipped => Matrix3::new( + -1.0, 0.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 0.0, 1.0, + ), + Transform::Flipped90 => Matrix3::new( + 0.0, 1.0, 0.0, + 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, + ), + Transform::Flipped180 => Matrix3::new( + 1.0, 0.0, 0.0, + 0.0, -1.0, 0.0, + 0.0, 0.0, 1.0, + ), + Transform::Flipped270 => Matrix3::new( + 0.0, -1.0, 0.0, + -1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, + ), + } + } + + pub fn invert(&self) -> Transform { + match self { + Transform::Normal => Transform::Normal, + Transform::Flipped => Transform::Flipped, + Transform::_90 => Transform::_270, + Transform::_180 => Transform::_180, + Transform::_270 => Transform::_90, + Transform::Flipped90 => Transform::Flipped270, + Transform::Flipped180 => Transform::Flipped180, + Transform::Flipped270 => Transform::Flipped90, + } + } + + pub fn transform_size(&self, width: u32, height: u32) -> (u32, u32) { + if *self == Transform::_90 + || *self == Transform::_270 + || *self == Transform::Flipped90 + || *self == Transform::Flipped270 + { + (height, width) + } else { + (width, height) + } + } +} + +#[cfg(feature = "wayland-frontend")] +impl From for Transform { + fn from(transform: wayland_server::protocol::wl_output::Transform) -> Transform { + use wayland_server::protocol::wl_output::Transform::*; + match transform { + Normal => Transform::Normal, + _90 => Transform::_90, + _180 => Transform::_180, + _270 => Transform::_270, + Flipped => Transform::Flipped, + Flipped90 => Transform::Flipped90, + Flipped180 => Transform::Flipped180, + Flipped270 => Transform::Flipped270, + } + } +} + +pub trait Bind: Unbind { + fn bind(&mut self, target: Target) -> Result<(), ::Error>; +} + +pub trait Unbind: Renderer { + fn unbind(&mut self) -> Result<(), ::Error>; +} + +pub trait Texture { + fn size(&self) -> (u32, u32) { + (self.width(), self.height()) + } + + fn width(&self) -> u32; + fn height(&self) -> u32; +} + +pub trait Renderer { + type Error: Error; + type Texture: Texture; + type Frame: Frame; + + #[must_use] + fn begin(&mut self, width: u32, height: u32, transform: Transform) -> Result; + + #[cfg(feature = "wayland_frontend")] + fn shm_formats(&self) -> &[wl_shm::Format] { + // Mandatory + &[wl_shm::Format::Argb8888, wl_shm::Format::Xrgb8888] + } + #[cfg(feature = "wayland_frontend")] + fn import_shm(&self, buffer: &wl_buffer::WlBuffer) -> Result; +} + +pub trait Frame { + type Error: Error; + type Texture: Texture; + + fn clear(&mut self, color: [f32; 4]) -> Result<(), Self::Error>; + fn render_texture(&mut self, texture: &Self::Texture, matrix: Matrix3, alpha: f32) -> Result<(), Self::Error>; + fn render_texture_at(&mut self, texture: &Self::Texture, pos: (i32, i32), transform: Transform, alpha: f32) -> Result<(), Self::Error> { + let mut mat = Matrix3::::identity(); + + // position and scale + let size = texture.size(); + mat = mat * Matrix3::from_translation(Vector2::new(pos.0 as f32, pos.1 as f32)); + mat = mat * Matrix3::from_nonuniform_scale(size.0 as f32, size.1 as f32); + + //apply surface transformation + mat = mat * Matrix3::from_translation(Vector2::new(0.5, 0.5)); + if transform == Transform::Normal { + assert_eq!(mat, mat * transform.invert().matrix()); + assert_eq!(transform.matrix(), Matrix3::::identity()); + } + mat = mat * transform.invert().matrix(); + mat = mat * Matrix3::from_translation(Vector2::new(-0.5, -0.5)); + + self.render_texture(texture, mat, alpha) + } + + fn finish(self) -> Result<(), SwapBuffersError>; +} \ No newline at end of file