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:
parent
d99108a8e6
commit
4d5d7afb5a
|
@ -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 _)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
"#;
|
|
@ -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>;
|
||||
}
|
Loading…
Reference in New Issue