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).
This commit is contained in:
Victor Brekenfeld 2021-04-07 00:42:14 +02:00
parent d99108a8e6
commit 4d5d7afb5a
6 changed files with 833 additions and 335 deletions

View File

@ -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<B: GLGraphicsBackend>(backend: &B) -> Gles2 {
Gles2::load_with(|s| backend.get_proc_address(s) as *const _)
}

View File

@ -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<T: GLGraphicsBackend> {
context: Rc<Context>,
backend: Rc<InternalBackend<T>>,
// 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<Cell<Option<Box<dyn std::error::Error>>>>,
}
// GLGraphicsBackend is a trait, so we have to impl Debug manually
impl<T: GLGraphicsBackend> fmt::Debug for GliumGraphicsBackend<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
struct BackendDebug<'a, T: GLGraphicsBackend>(&'a Rc<InternalBackend<T>>);
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<T: GLGraphicsBackend>(RefCell<T>, Rc<Cell<Option<Box<dyn std::error::Error>>>>);
impl<T: GLGraphicsBackend + 'static> GliumGraphicsBackend<T> {
fn new(backend: T) -> GliumGraphicsBackend<T> {
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<T: GLGraphicsBackend> Facade for GliumGraphicsBackend<T> {
fn get_context(&self) -> &Rc<Context> {
&self.context
}
}
impl<T: GLGraphicsBackend + 'static> From<T> for GliumGraphicsBackend<T> {
fn from(backend: T) -> Self {
GliumGraphicsBackend::new(backend)
}
}
unsafe impl<T: GLGraphicsBackend> Backend for InternalBackend<T> {
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<Cell<Option<Box<dyn std::error::Error>>>>);
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<f32>,
stencil: Option<i32>,
) {
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<u16> {
self.0.get_depth_buffer_bits()
}
fn get_stencil_buffer_bits(&self) -> Option<u16> {
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<glium::index::IndicesSource<'a>>,
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<S>(
&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)
}
}

View File

@ -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<E: Error> {
/// 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);
}

View File

@ -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<Gles2RendererInternal>,
buffers: Vec<WeakGles2Buffer>,
current_buffer: Option<Gles2Buffer>,
}
struct Gles2RendererInternal {
gl: ffi::Gles2,
egl: EGLContext,
extensions: Vec<String>,
programs: [Gles2Program; shaders::FRAGMENT_COUNT],
logger: Option<*mut ::slog::Logger>,
}
pub struct Gles2Frame {
internal: Arc<Gles2RendererInternal>,
projection: Matrix3<f32>,
}
#[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<ffi::types::GLuint, Gles2Error> {
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<ffi::types::GLuint, Gles2Error> {
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<Gles2Program, Gles2Error> {
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<L>(context: EGLContext, logger: L) -> Result<Gles2Renderer, Gles2Error>
where
L: Into<Option<::slog::Logger>>
{
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::<Vec<_>>()
};
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<Dmabuf> 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<Gles2Frame, Gles2Error> {
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::<f32>::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::<f32>::identity();
let t = Matrix3::<f32>::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<Self::Texture, Self::Error> {
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<f32>, 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(())
}
}

View File

@ -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;
}
"#;

173
src/backend/renderer/mod.rs Normal file
View File

@ -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<f32> {
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<wayland_server::protocol::wl_output::Transform> 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<Target>: Unbind {
fn bind(&mut self, target: Target) -> Result<(), <Self as Renderer>::Error>;
}
pub trait Unbind: Renderer {
fn unbind(&mut self) -> Result<(), <Self as Renderer>::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<Error=Self::Error, Texture=Self::Texture>;
#[must_use]
fn begin(&mut self, width: u32, height: u32, transform: Transform) -> Result<Self::Frame, Self::Error>;
#[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<Self::Texture, Self::Error>;
}
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<f32>, 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::<f32>::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::<f32>::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>;
}