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