renderer: Closure-based api

This commit is contained in:
Victor Brekenfeld 2021-05-23 23:03:43 +02:00
parent 73420b75bc
commit 978ef1b393
7 changed files with 353 additions and 475 deletions

View File

@ -6,7 +6,7 @@ use slog::Logger;
use smithay::{
backend::{
egl::display::EGLBufferReader,
renderer::{Renderer, Texture, Transform},
renderer::{Frame, Renderer, Texture, Transform},
SwapBuffersError,
},
reexports::{
@ -36,8 +36,9 @@ impl<T> Drop for BufferTextures<T> {
}
}
pub fn draw_cursor<R, E, T>(
pub fn draw_cursor<R, E, F, T>(
renderer: &mut R,
frame: &mut F,
surface: &wl_surface::WlSurface,
egl_buffer_reader: Option<&EGLBufferReader>,
(x, y): (i32, i32),
@ -45,7 +46,8 @@ pub fn draw_cursor<R, E, T>(
log: &Logger,
) -> Result<(), SwapBuffersError>
where
R: Renderer<Error = E, TextureId = T>,
R: Renderer<Error = E, TextureId = T, Frame=F>,
F: Frame<Error = E, TextureId = T>,
E: std::error::Error + Into<SwapBuffersError>,
T: Texture + 'static,
{
@ -59,11 +61,12 @@ where
(0, 0)
}
};
draw_surface_tree(renderer, surface, egl_buffer_reader, (x - dx, y - dy), token, log)
draw_surface_tree(renderer, frame, surface, egl_buffer_reader, (x - dx, y - dy), token, log)
}
fn draw_surface_tree<R, E, T>(
fn draw_surface_tree<R, E, F, T>(
renderer: &mut R,
frame: &mut F,
root: &wl_surface::WlSurface,
egl_buffer_reader: Option<&EGLBufferReader>,
location: (i32, i32),
@ -71,7 +74,8 @@ fn draw_surface_tree<R, E, T>(
log: &Logger,
) -> Result<(), SwapBuffersError>
where
R: Renderer<Error = E, TextureId = T>,
R: Renderer<Error = E, TextureId = T, Frame=F>,
F: Frame<Error = E, TextureId = T>,
E: std::error::Error + Into<SwapBuffersError>,
T: Texture + 'static,
{
@ -79,8 +83,8 @@ where
compositor_token.with_surface_tree_upward(
root,
(),
|_surface, attributes, _, _| {
location,
|_surface, attributes, role, &(mut x, mut y)| {
// Pull a new buffer if available
if let Some(data) = attributes.user_data.get::<RefCell<SurfaceData>>() {
let mut data = data.borrow_mut();
@ -107,31 +111,7 @@ where
}
}
// Now, should we be drawn ?
if data.texture.is_some() {
TraversalAction::DoChildren(())
} else {
// we are not displayed, so our children are neither
TraversalAction::SkipChildren
}
} else {
// we are not displayed, so our children are neither
TraversalAction::SkipChildren
}
},
|_, _, _, _| {},
|_, _, _, _| true,
);
compositor_token.with_surface_tree_upward(
root,
location,
|_surface, attributes, role, &(mut x, mut y)| {
// Pull a new buffer if available
if let Some(data) = attributes.user_data.get::<RefCell<SurfaceData>>() {
let data = data.borrow();
// Now, should we be drawn ?
if data.texture.is_some() {
// if yes, also process the children
if data.texture.is_some() {// if yes, also process the children
if Role::<SubsurfaceRole>::has(role) {
x += data.current_state.sub_location.0;
y += data.current_state.sub_location.1;
@ -161,7 +141,7 @@ where
x += sub_x;
y += sub_y;
}
if let Err(err) = renderer.render_texture_at(
if let Err(err) = frame.render_texture_at(
&texture.texture,
(x, y),
Transform::Normal, /* TODO */
@ -178,8 +158,9 @@ where
result
}
pub fn draw_windows<R, E, T>(
pub fn draw_windows<R, E, F, T>(
renderer: &mut R,
frame: &mut F,
egl_buffer_reader: Option<&EGLBufferReader>,
window_map: &MyWindowMap,
output_rect: Option<Rectangle>,
@ -187,7 +168,8 @@ pub fn draw_windows<R, E, T>(
log: &::slog::Logger,
) -> Result<(), SwapBuffersError>
where
R: Renderer<Error = E, TextureId = T>,
R: Renderer<Error = E, TextureId = T, Frame=F>,
F: Frame<Error = E, TextureId = T>,
E: std::error::Error + Into<SwapBuffersError>,
T: Texture + 'static,
{
@ -206,6 +188,7 @@ where
// this surface is a root of a subsurface tree that needs to be drawn
if let Err(err) = draw_surface_tree(
renderer,
frame,
&wl_surface,
egl_buffer_reader,
initial_place,
@ -220,8 +203,9 @@ where
result
}
pub fn draw_dnd_icon<R, E, T>(
pub fn draw_dnd_icon<R, E, F, T>(
renderer: &mut R,
frame: &mut F,
surface: &wl_surface::WlSurface,
egl_buffer_reader: Option<&EGLBufferReader>,
(x, y): (i32, i32),
@ -229,7 +213,8 @@ pub fn draw_dnd_icon<R, E, T>(
log: &::slog::Logger,
) -> Result<(), SwapBuffersError>
where
R: Renderer<Error = E, TextureId = T>,
R: Renderer<Error = E, TextureId = T, Frame=F>,
F: Frame<Error = E, TextureId = T>,
E: std::error::Error + Into<SwapBuffersError>,
T: Texture + 'static,
{
@ -239,39 +224,5 @@ where
"Trying to display as a dnd icon a surface that does not have the DndIcon role."
);
}
draw_surface_tree(renderer, surface, egl_buffer_reader, (x, y), token, log)
}
pub fn schedule_initial_render<R: Renderer + 'static, Data: 'static>(
renderer: Rc<RefCell<R>>,
evt_handle: &LoopHandle<Data>,
logger: ::slog::Logger,
) where
<R as Renderer>::Error: Into<SwapBuffersError>,
{
let result = {
let mut renderer = renderer.borrow_mut();
// Does not matter if we render an empty frame
renderer
.begin(1, 1, Transform::Normal)
.map_err(Into::<SwapBuffersError>::into)
.and_then(|_| {
renderer
.clear([0.8, 0.8, 0.9, 1.0])
.map_err(Into::<SwapBuffersError>::into)
})
.and_then(|_| renderer.finish())
};
if let Err(err) = result {
match err {
SwapBuffersError::AlreadySwapped => {}
SwapBuffersError::TemporaryFailure(err) => {
// TODO dont reschedule after 3(?) retries
warn!(logger, "Failed to submit page_flip: {}", err);
let handle = evt_handle.clone();
evt_handle.insert_idle(move |_| schedule_initial_render(renderer, &handle, logger));
}
SwapBuffersError::ContextLost(err) => panic!("Rendering loop lost: {}", err),
}
}
draw_surface_tree(renderer, frame, surface, egl_buffer_reader, (x, y), token, log)
}

View File

@ -21,7 +21,7 @@ use smithay::{
libinput::{LibinputInputBackend, LibinputSessionInterface},
renderer::{
gles2::{Gles2Renderer, Gles2Texture},
Renderer, Transform,
Frame, Renderer, Transform,
},
session::{auto::AutoSession, Session, Signal as SessionSignal},
udev::{UdevBackend, UdevEvent},
@ -528,10 +528,10 @@ impl<Data: 'static> UdevHandlerImpl<Data> {
.unwrap();
trace!(self.logger, "Backends: {:?}", backends.borrow().keys());
for renderer in backends.borrow_mut().values() {
for backend in backends.borrow_mut().values() {
// render first frame
trace!(self.logger, "Scheduling frame");
schedule_initial_render(renderer.clone(), &self.loop_handle, self.logger.clone());
schedule_initial_render(backend.clone(), &self.loop_handle, self.logger.clone());
}
self.backends.insert(
@ -767,70 +767,108 @@ impl DrmRenderer {
.unwrap_or((0, 0)); // in this case the output will be removed.
// and draw in sync with our monitor
surface.queue_frame()?;
surface.clear([0.8, 0.8, 0.9, 1.0])?;
// draw the surfaces
draw_windows(
surface,
egl_buffer_reader,
window_map,
Some(Rectangle {
x: x as i32,
y: y as i32,
width: width as i32,
height: height as i32,
}),
*compositor_token,
logger,
)?;
surface.render(|renderer, frame| {
frame.clear([0.8, 0.8, 0.9, 1.0])?;
// draw the surfaces
draw_windows(
renderer,
frame,
egl_buffer_reader,
window_map,
Some(Rectangle {
x: x as i32,
y: y as i32,
width: width as i32,
height: height as i32,
}),
*compositor_token,
logger,
)?;
// get pointer coordinates
let (ptr_x, ptr_y) = *pointer_location;
let ptr_x = ptr_x.trunc().abs() as i32 - x as i32;
let ptr_y = ptr_y.trunc().abs() as i32 - y as i32;
// get pointer coordinates
let (ptr_x, ptr_y) = *pointer_location;
let ptr_x = ptr_x.trunc().abs() as i32 - x as i32;
let ptr_y = ptr_y.trunc().abs() as i32 - y as i32;
// set cursor
if ptr_x >= 0 && ptr_x < width as i32 && ptr_y >= 0 && ptr_y < height as i32 {
// draw the dnd icon if applicable
{
if let Some(ref wl_surface) = dnd_icon.as_ref() {
if wl_surface.as_ref().is_alive() {
draw_dnd_icon(
surface,
// set cursor
if ptr_x >= 0 && ptr_x < width as i32 && ptr_y >= 0 && ptr_y < height as i32 {
// draw the dnd icon if applicable
{
if let Some(ref wl_surface) = dnd_icon.as_ref() {
if wl_surface.as_ref().is_alive() {
draw_dnd_icon(
renderer,
frame,
wl_surface,
egl_buffer_reader,
(ptr_x, ptr_y),
*compositor_token,
logger,
)?;
}
}
}
// draw the cursor as relevant
{
// reset the cursor if the surface is no longer alive
let mut reset = false;
if let CursorImageStatus::Image(ref surface) = *cursor_status {
reset = !surface.as_ref().is_alive();
}
if reset {
*cursor_status = CursorImageStatus::Default;
}
if let CursorImageStatus::Image(ref wl_surface) = *cursor_status {
draw_cursor(
renderer,
frame,
wl_surface,
egl_buffer_reader,
(ptr_x, ptr_y),
*compositor_token,
logger,
)?;
} else {
frame.render_texture_at(pointer_image, (ptr_x, ptr_y), Transform::Normal, 1.0)?;
}
}
}
// draw the cursor as relevant
{
// reset the cursor if the surface is no longer alive
let mut reset = false;
if let CursorImageStatus::Image(ref surface) = *cursor_status {
reset = !surface.as_ref().is_alive();
}
if reset {
*cursor_status = CursorImageStatus::Default;
}
if let CursorImageStatus::Image(ref wl_surface) = *cursor_status {
draw_cursor(
surface,
wl_surface,
egl_buffer_reader,
(ptr_x, ptr_y),
*compositor_token,
logger,
)?;
} else {
surface.render_texture_at(pointer_image, (ptr_x, ptr_y), Transform::Normal, 1.0)?;
}
}
}
surface.finish()
Ok(())
}).map_err(Into::<SwapBuffersError>::into)
.and_then(|x| x)
.map_err(Into::<SwapBuffersError>::into)
}
}
fn schedule_initial_render<Data: 'static>(
renderer: Rc<RefCell<RenderSurface>>,
evt_handle: &LoopHandle<Data>,
logger: ::slog::Logger,
) {
let result = {
let mut renderer = renderer.borrow_mut();
// Does not matter if we render an empty frame
renderer
.render(|_, frame| {
frame
.clear([0.8, 0.8, 0.9, 1.0])
.map_err(Into::<SwapBuffersError>::into)
})
.map_err(Into::<SwapBuffersError>::into)
.and_then(|x| x.map_err(Into::<SwapBuffersError>::into))
};
if let Err(err) = result {
match err {
SwapBuffersError::AlreadySwapped => {}
SwapBuffersError::TemporaryFailure(err) => {
// TODO dont reschedule after 3(?) retries
warn!(logger, "Failed to submit page_flip: {}", err);
let handle = evt_handle.clone();
evt_handle.insert_idle(move |_| schedule_initial_render(renderer, &handle, logger));
}
SwapBuffersError::ContextLost(err) => panic!("Rendering loop lost: {}", err),
}
}
}

View File

@ -1,7 +1,7 @@
use std::{cell::RefCell, rc::Rc, sync::atomic::Ordering, time::Duration};
use smithay::{
backend::{input::InputBackend, renderer::Renderer, winit, SwapBuffersError},
backend::{input::InputBackend, renderer::Frame, winit, SwapBuffersError},
reexports::{
calloop::EventLoop,
wayland_server::{protocol::wl_output, Display},
@ -82,6 +82,7 @@ pub fn run_winit(
});
let start_time = std::time::Instant::now();
let mut cursor_visible = true;
info!(log, "Initialization completed, starting the main loop.");
@ -98,70 +99,75 @@ pub fn run_winit(
{
let mut renderer = renderer.borrow_mut();
renderer.begin().expect("Failed to render frame");
renderer
.clear([0.8, 0.8, 0.9, 1.0])
.expect("Failed to clear frame");
// draw the windows
draw_windows(
&mut *renderer,
reader.as_ref(),
&*state.window_map.borrow(),
None,
state.ctoken,
&log,
)
.expect("Failed to renderer windows");
let result = renderer.render(|renderer, frame| {
frame.clear([0.8, 0.8, 0.9, 1.0])?;
let (x, y) = *state.pointer_location.borrow();
// draw the dnd icon if any
{
let guard = state.dnd_icon.lock().unwrap();
if let Some(ref surface) = *guard {
if surface.as_ref().is_alive() {
draw_dnd_icon(
&mut *renderer,
// draw the windows
draw_windows(
renderer,
frame,
reader.as_ref(),
&*state.window_map.borrow(),
None,
state.ctoken,
&log,
)?;
let (x, y) = *state.pointer_location.borrow();
// draw the dnd icon if any
{
let guard = state.dnd_icon.lock().unwrap();
if let Some(ref surface) = *guard {
if surface.as_ref().is_alive() {
draw_dnd_icon(
renderer,
frame,
surface,
reader.as_ref(),
(x as i32, y as i32),
state.ctoken,
&log,
)?;
}
}
}
// draw the cursor as relevant
{
let mut guard = state.cursor_status.lock().unwrap();
// reset the cursor if the surface is no longer alive
let mut reset = false;
if let CursorImageStatus::Image(ref surface) = *guard {
reset = !surface.as_ref().is_alive();
}
if reset {
*guard = CursorImageStatus::Default;
}
// draw as relevant
if let CursorImageStatus::Image(ref surface) = *guard {
cursor_visible = false;
draw_cursor(
renderer,
frame,
surface,
reader.as_ref(),
(x as i32, y as i32),
state.ctoken,
&log,
)
.expect("Failed to render dnd icon");
)?;
} else {
cursor_visible = true;
}
}
}
// draw the cursor as relevant
{
let mut guard = state.cursor_status.lock().unwrap();
// reset the cursor if the surface is no longer alive
let mut reset = false;
if let CursorImageStatus::Image(ref surface) = *guard {
reset = !surface.as_ref().is_alive();
}
if reset {
*guard = CursorImageStatus::Default;
}
// draw as relevant
if let CursorImageStatus::Image(ref surface) = *guard {
renderer.window().set_cursor_visible(false);
draw_cursor(
&mut *renderer,
surface,
reader.as_ref(),
(x as i32, y as i32),
state.ctoken,
&log,
)
.expect("Failed to render cursor");
} else {
renderer.window().set_cursor_visible(true);
}
}
Ok(())
}).map_err(Into::<SwapBuffersError>::into)
.and_then(|x| x.into());
renderer.window().set_cursor_visible(cursor_visible);
if let Err(SwapBuffersError::ContextLost(err)) = renderer.finish() {
if let Err(SwapBuffersError::ContextLost(err)) = result {
error!(log, "Critical Rendering Error: {}", err);
state.running.store(false, Ordering::SeqCst);
}

View File

@ -2,17 +2,12 @@ use std::collections::HashSet;
use std::os::unix::io::AsRawFd;
use std::sync::Arc;
#[cfg(feature = "wayland_frontend")]
use crate::wayland::compositor::Damage;
use cgmath::Matrix3;
use drm::buffer::PlanarBuffer;
use drm::control::{connector, crtc, framebuffer, plane, Device, Mode};
use gbm::{BufferObject, BufferObjectFlags, Device as GbmDevice};
#[cfg(feature = "wayland_frontend")]
use wayland_server::protocol::{wl_buffer, wl_shm};
use super::{device::DevPath, surface::DrmSurfaceInternal, DrmError, DrmSurface};
use crate::backend::renderer::{Bind, Renderer, Texture, Transform};
use crate::backend::renderer::{Bind, Frame, Renderer, Texture, Transform};
use crate::backend::SwapBuffersError;
use crate::backend::{
allocator::{
@ -20,8 +15,6 @@ use crate::backend::{
Allocator, Buffer, Format, Fourcc, Modifier, Slot, Swapchain,
},
};
#[cfg(all(feature = "backend_egl", feature = "wayland_frontend"))]
use crate::backend::egl::display::EGLBufferReader;
/// Simplified by limited abstraction to link single [`DrmSurface`]s to renderers.
///
@ -33,7 +26,6 @@ use crate::backend::egl::display::EGLBufferReader;
pub struct DrmRenderSurface<D: AsRawFd + 'static, A: Allocator<B>, R: Bind<Dmabuf>, B: Buffer> {
_format: Format,
buffers: Buffers<D, B>,
current_buffer: Option<(Slot<B, (Dmabuf, BufferObject<FbHandle<D>>)>, Dmabuf)>,
swapchain: Swapchain<A, B, (Dmabuf, BufferObject<FbHandle<D>>)>,
renderer: R,
drm: Arc<DrmSurface<D>>,
@ -169,11 +161,11 @@ where
.map_err(Error::<E1, E2, E3>::RenderError)
.and_then(|_| {
renderer
.begin(mode.size().0 as u32, mode.size().1 as u32, Transform::Normal)
.render(mode.size().0 as u32, mode.size().1 as u32, Transform::Normal, |_, frame| {
frame.clear([0.0, 0.0, 0.0, 1.0])
})
.map_err(Error::RenderError)
})
.and_then(|_| renderer.clear([0.0, 0.0, 0.0, 1.0]).map_err(Error::RenderError))
.and_then(|_| renderer.finish().map_err(|_| Error::InitialRenderingError))
.and_then(|_| renderer.unbind().map_err(Error::RenderError))
{
Ok(_) => {}
@ -204,7 +196,6 @@ where
renderer,
swapchain,
buffers,
current_buffer: None,
})
}
Err(err) => {
@ -217,34 +208,53 @@ where
}
}
/// Shortcut to [`Renderer::begin`] with the pending mode as dimensions.
pub fn queue_frame(&mut self) -> Result<(), Error<E1, E2, E3>> {
let mode = self.drm.pending_mode();
let (width, height) = (mode.size().0 as u32, mode.size().1 as u32);
self.begin(width, height, Transform::Normal)
/// Access the underlying renderer
pub fn renderer(&mut self) -> &mut R {
&mut self.renderer
}
/// Shortcut to abort the current frame.
///
/// Allows [`DrmRenderSurface::queue_frame`] or [`Renderer::begin`] to be called again
/// without displaying the current rendering context to the user.
pub fn drop_frame(&mut self) -> Result<(), SwapBuffersError> {
if self.current_buffer.is_none() {
return Ok(());
/// Shortcut to [`Renderer::render`] with the pending mode as dimensions
/// and this surface set a the rendering target.
pub fn render<F, S>(&mut self, rendering: F) -> Result<S, Error<E1, E2, E3>>
where
F: FnOnce(&mut R, &mut <R as Renderer>::Frame) -> S
{
let mode = self.drm.pending_mode();
let (width, height) = (mode.size().0 as u32, mode.size().1 as u32);
let slot = self
.swapchain
.acquire()
.map_err(Error::SwapchainError)?
.ok_or(Error::NoFreeSlotsError)?;
let dmabuf = match &*slot.userdata() {
Some((buf, _)) => buf.clone(),
None => (*slot).export().map_err(Error::AsDmabufError)?,
};
self.renderer.bind(dmabuf.clone()).map_err(Error::RenderError)?;
let result = self.renderer
.render(
width, height,
Transform::Flipped180 /* TODO: add Add<Transform> implementation to add and correct _transform here */,
rendering,
)
.map_err(Error::RenderError)?;
match self.buffers.queue::<E1, E2, E3>(slot, dmabuf) {
Ok(()) => {}
Err(Error::DrmError(drm)) => return Err(drm.into()),
Err(Error::GbmError(err)) => return Err(err.into()),
_ => unreachable!(),
}
// finish the renderer in case it needs it
let result = self.renderer.finish();
// but do not queue the buffer, drop it in any case
let _ = self.current_buffer.take();
result
Ok(result)
}
/// Marks the current frame as submitted.
///
/// Needs to be called, after the vblank event of the matching [`DrmDevice`](super::DrmDevice)
/// was received after calling [`Renderer::finish`] on this surface. Otherwise the rendering
/// will run out of buffers eventually.
/// was received after calling [`DrmRenderSurface::render`] on this surface.
/// Otherwise the rendering will run out of buffers eventually.
pub fn frame_submitted(&mut self) -> Result<(), Error<E1, E2, E3>> {
self.buffers.submitted()
}
@ -325,100 +335,6 @@ where
}
}
impl<D, A, B, T, R, E1, E2, E3> Renderer for DrmRenderSurface<D, A, R, B>
where
D: AsRawFd + 'static,
A: Allocator<B, Error = E1>,
B: Buffer + AsDmabuf<Error = E2>,
R: Bind<Dmabuf> + Renderer<Error = E3, TextureId = T>,
T: Texture,
E1: std::error::Error + 'static,
E2: std::error::Error + 'static,
E3: std::error::Error + 'static,
{
type Error = Error<E1, E2, E3>;
type TextureId = T;
#[cfg(feature = "image")]
fn import_bitmap<C: std::ops::Deref<Target = [u8]>>(
&mut self,
image: &image::ImageBuffer<image::Rgba<u8>, C>,
) -> Result<Self::TextureId, Self::Error> {
self.renderer.import_bitmap(image).map_err(Error::RenderError)
}
#[cfg(feature = "wayland_frontend")]
fn shm_formats(&self) -> &[wl_shm::Format] {
self.renderer.shm_formats()
}
#[cfg(all(feature = "backend_egl", feature = "wayland_frontend"))]
fn import_buffer(
&mut self,
buffer: &wl_buffer::WlBuffer,
damage: Option<&Damage>,
egl: Option<&EGLBufferReader>,
) -> Result<Self::TextureId, Self::Error> {
self.renderer
.import_buffer(buffer, damage, egl)
.map_err(Error::RenderError)
}
fn begin(&mut self, width: u32, height: u32, _transform: Transform) -> Result<(), Error<E1, E2, E3>> {
if self.current_buffer.is_some() {
return Ok(());
}
let slot = self
.swapchain
.acquire()
.map_err(Error::SwapchainError)?
.ok_or(Error::NoFreeSlotsError)?;
let dmabuf = match &*slot.userdata() {
Some((buf, _)) => buf.clone(),
None => (*slot).export().map_err(Error::AsDmabufError)?,
};
self.renderer.bind(dmabuf.clone()).map_err(Error::RenderError)?;
self.current_buffer = Some((slot, dmabuf));
self.renderer
.begin(width, height, Transform::Flipped180 /* TODO: add Add<Transform> implementation to add and correct _transform here */)
.map_err(Error::RenderError)
}
fn clear(&mut self, color: [f32; 4]) -> Result<(), Self::Error> {
self.renderer.clear(color).map_err(Error::RenderError)
}
fn render_texture(
&mut self,
texture: &Self::TextureId,
matrix: Matrix3<f32>,
alpha: f32,
) -> Result<(), Self::Error> {
self.renderer
.render_texture(texture, matrix, alpha)
.map_err(Error::RenderError)
}
fn finish(&mut self) -> Result<(), SwapBuffersError> {
if self.current_buffer.is_none() {
return Err(SwapBuffersError::AlreadySwapped);
}
let result = self.renderer.finish();
if result.is_ok() {
let (slot, dmabuf) = self.current_buffer.take().unwrap();
match self.buffers.queue::<E1, E2, E3>(slot, dmabuf) {
Ok(()) => {}
Err(Error::DrmError(drm)) => return Err(drm.into()),
Err(Error::GbmError(err)) => return Err(SwapBuffersError::ContextLost(Box::new(err))),
_ => unreachable!(),
}
}
result
}
}
struct FbHandle<D: AsRawFd + 'static> {
drm: Arc<DrmSurface<D>>,
fb: framebuffer::Handle,

View File

@ -19,7 +19,7 @@ use cgmath::{prelude::*, Matrix3};
mod shaders;
mod version;
use super::{Bind, Renderer, Texture, Transform, Unbind};
use super::{Bind, Frame, Renderer, Texture, Transform, Unbind};
use crate::backend::allocator::{
dmabuf::{Dmabuf, WeakDmabuf},
Format,
@ -48,7 +48,7 @@ pub mod ffi {
// cannot assume, that resources between two renderers are (and even can be) shared.
static RENDERER_COUNTER: AtomicUsize = AtomicUsize::new(0);
#[derive(Debug)]
#[derive(Debug, Clone)]
struct Gles2Program {
program: ffi::types::GLuint,
uniform_tex: ffi::types::GLint,
@ -147,13 +147,12 @@ pub struct Gles2Renderer {
buffers: Vec<WeakGles2Buffer>,
target_buffer: Option<Gles2Buffer>,
target_surface: Option<Rc<EGLSurface>>,
current_projection: Option<Matrix3<f32>>,
extensions: Vec<String>,
programs: [Gles2Program; shaders::FRAGMENT_COUNT],
#[cfg(feature = "wayland_frontend")]
textures: HashMap<BufferEntry, Gles2Texture>,
gl: ffi::Gles2,
egl: EGLContext,
gl: ffi::Gles2,
destruction_callback: Receiver<CleanupResource>,
destruction_callback_sender: Sender<CleanupResource>,
logger_ptr: Option<*mut ::slog::Logger>,
@ -161,6 +160,13 @@ pub struct Gles2Renderer {
_not_send: *mut (),
}
/// Handle to the currently rendered frame during [`Gles2Renderer::render`](Renderer::render)
pub struct Gles2Frame {
current_projection: Matrix3<f32>,
gl: ffi::Gles2,
programs: [Gles2Program; shaders::FRAGMENT_COUNT],
}
impl fmt::Debug for Gles2Renderer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Gles2Renderer")
@ -168,7 +174,6 @@ impl fmt::Debug for Gles2Renderer {
.field("buffers", &self.buffers)
.field("target_buffer", &self.target_buffer)
.field("target_surface", &self.target_surface)
.field("current_projection", &self.current_projection)
.field("extensions", &self.extensions)
.field("programs", &self.programs)
// ffi::Gles2 does not implement Debug
@ -453,7 +458,6 @@ impl Gles2Renderer {
buffers: Vec::new(),
#[cfg(feature = "wayland_frontend")]
textures: HashMap::new(),
current_projection: None,
destruction_callback: rx,
destruction_callback_sender: tx,
logger_ptr,
@ -596,7 +600,6 @@ impl Gles2Renderer {
self.gl.BindTexture(ffi::TEXTURE_2D, 0);
}
self.egl.unbind()?;
Ok(texture)
})
.map_err(Gles2Error::BufferAccessError)?
@ -671,7 +674,6 @@ impl Gles2Renderer {
self.make_current()?;
let tex = Some(texture.0.texture);
self.import_egl_image(egl_images[0], false, tex)?;
self.egl.unbind()?;
}
Ok(Some(texture))
}
@ -867,6 +869,7 @@ static TEX_COORDS: [ffi::types::GLfloat; 8] = [
impl Renderer for Gles2Renderer {
type Error = Gles2Error;
type TextureId = Gles2Texture;
type Frame = Gles2Frame;
#[cfg(feature = "wayland_frontend")]
fn shm_formats(&self) -> &[wl_shm::Format] {
@ -962,7 +965,15 @@ impl Renderer for Gles2Renderer {
Ok(texture)
}
fn begin(&mut self, width: u32, height: u32, transform: Transform) -> Result<(), Gles2Error> {
fn render<F, R>(
&mut self,
width: u32, height: u32,
transform: Transform,
rendering: F,
) -> Result<R, Self::Error>
where
F: FnOnce(&mut Self, &mut Self::Frame) -> R
{
self.make_current()?;
// delayed destruction until the next frame rendering.
self.cleanup()?;
@ -991,13 +1002,44 @@ impl Renderer for Gles2Renderer {
renderer[2][0] = -(1.0f32.copysign(renderer[0][0] + renderer[1][0]));
renderer[2][1] = -(1.0f32.copysign(renderer[0][1] + renderer[1][1]));
// output transformation passed in by the user
self.current_projection = Some(transform.matrix() * renderer);
Ok(())
let mut frame = Gles2Frame {
gl: self.gl.clone(),
programs: self.programs.clone(),
// output transformation passed in by the user
current_projection: transform.matrix() * renderer,
};
let result = rendering(self, &mut frame);
unsafe {
self.gl.Flush();
// We need to wait for the previously submitted GL commands to complete
// or otherwise the buffer could be submitted to the drm surface while
// still writing to the buffer which results in flickering on the screen.
// The proper solution would be to create a fence just before calling
// glFlush that the backend can use to wait for the commands to be finished.
// In case of a drm atomic backend the fence could be supplied by using the
// IN_FENCE_FD property.
// See https://01.org/linuxgraphics/gfx-docs/drm/gpu/drm-kms.html#explicit-fencing-properties for
// the topic on submitting a IN_FENCE_FD and the mesa kmskube example
// https://gitlab.freedesktop.org/mesa/kmscube/-/blob/9f63f359fab1b5d8e862508e4e51c9dfe339ccb0/drm-atomic.c
// especially here
// https://gitlab.freedesktop.org/mesa/kmscube/-/blob/9f63f359fab1b5d8e862508e4e51c9dfe339ccb0/drm-atomic.c#L147
// and here
// https://gitlab.freedesktop.org/mesa/kmscube/-/blob/9f63f359fab1b5d8e862508e4e51c9dfe339ccb0/drm-atomic.c#L235
self.gl.Finish();
self.gl.Disable(ffi::BLEND);
}
Ok(result)
}
}
impl Frame for Gles2Frame {
type Error = Gles2Error;
type TextureId = Gles2Texture;
fn clear(&mut self, color: [f32; 4]) -> Result<(), Self::Error> {
self.make_current()?;
unsafe {
self.gl.ClearColor(color[0], color[1], color[2], color[3]);
self.gl.Clear(ffi::COLOR_BUFFER_BIT);
@ -1012,13 +1054,8 @@ impl Renderer for Gles2Renderer {
mut matrix: Matrix3<f32>,
alpha: f32,
) -> Result<(), Self::Error> {
self.make_current()?;
if self.current_projection.is_none() {
return Err(Gles2Error::UnconstraintRenderingOperation);
}
//apply output transformation
matrix = self.current_projection.as_ref().unwrap() * matrix;
matrix = self.current_projection * matrix;
let target = if tex.0.is_external {
ffi::TEXTURE_EXTERNAL_OES
@ -1083,31 +1120,4 @@ impl Renderer for Gles2Renderer {
Ok(())
}
fn finish(&mut self) -> Result<(), crate::backend::SwapBuffersError> {
self.make_current()?;
unsafe {
self.gl.Flush();
// We need to wait for the previously submitted GL commands to complete
// or otherwise the buffer could be submitted to the drm surface while
// still writing to the buffer which results in flickering on the screen.
// The proper solution would be to create a fence just before calling
// glFlush that the backend can use to wait for the commands to be finished.
// In case of a drm atomic backend the fence could be supplied by using the
// IN_FENCE_FD property.
// See https://01.org/linuxgraphics/gfx-docs/drm/gpu/drm-kms.html#explicit-fencing-properties for
// the topic on submitting a IN_FENCE_FD and the mesa kmskube example
// https://gitlab.freedesktop.org/mesa/kmscube/-/blob/9f63f359fab1b5d8e862508e4e51c9dfe339ccb0/drm-atomic.c
// especially here
// https://gitlab.freedesktop.org/mesa/kmscube/-/blob/9f63f359fab1b5d8e862508e4e51c9dfe339ccb0/drm-atomic.c#L147
// and here
// https://gitlab.freedesktop.org/mesa/kmscube/-/blob/9f63f359fab1b5d8e862508e4e51c9dfe339ccb0/drm-atomic.c#L235
self.gl.Finish();
self.gl.Disable(ffi::BLEND);
}
self.current_projection = None;
Ok(())
}
}

View File

@ -140,12 +140,67 @@ pub trait Texture {
fn height(&self) -> u32;
}
pub trait Frame {
/// Error type returned by the rendering operations of this renderer.
type Error: Error;
/// Texture Handle type used by this renderer.
type TextureId: Texture;
/// Clear the complete current target with a single given color.
///
/// This operation is only valid in between a `begin` and `finish`-call.
/// If called outside this operation may error-out, do nothing or modify future rendering results in any way.
fn clear(&mut self, color: [f32; 4]) -> Result<(), Self::Error>;
/// Render a texture to the current target using given projection matrix and alpha.
///
/// This operation is only valid in between a `begin` and `finish`-call.
/// If called outside this operation may error-out, do nothing or modify future rendering results in any way.
fn render_texture(
&mut self,
texture: &Self::TextureId,
matrix: Matrix3<f32>,
alpha: f32,
) -> Result<(), Self::Error>;
/// Render a texture to the current target as a flat 2d-plane at a given
/// position, applying the given transformation with the given alpha value.
///
/// This operation is only valid in between a `begin` and `finish`-call.
/// If called outside this operation may error-out, do nothing or modify future rendering results in any way.
fn render_texture_at(
&mut self,
texture: &Self::TextureId,
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)
}
}
/// Abstraction of commonly used rendering operations for compositors.
pub trait Renderer {
/// Error type returned by the rendering operations of this renderer.
type Error: Error;
/// Texture Handle type used by this renderer.
type TextureId: Texture;
type Frame: Frame<Error=Self::Error, TextureId=Self::TextureId>;
/// Import a given bitmap into the renderer.
///
@ -196,64 +251,16 @@ pub trait Renderer {
/// - There was a previous `begin`-call, which was not terminated by `finish`.
/// - This renderer implements `Bind`, no target was bound *and* has no default target.
/// - (Renderers not implementing `Bind` always have a default target.)
fn begin(
fn render<F, R>(
&mut self,
width: u32,
height: u32,
transform: Transform,
) -> Result<(), <Self as Renderer>::Error>;
/// Finish a renderering context, previously started by `begin`.
///
/// After this operation is finished the current rendering target contains a sucessfully rendered image.
/// If the image is immediently shown to the user depends on the target.
fn finish(&mut self) -> Result<(), SwapBuffersError>;
/// Clear the complete current target with a single given color.
///
/// This operation is only valid in between a `begin` and `finish`-call.
/// If called outside this operation may error-out, do nothing or modify future rendering results in any way.
fn clear(&mut self, color: [f32; 4]) -> Result<(), Self::Error>;
/// Render a texture to the current target using given projection matrix and alpha.
///
/// This operation is only valid in between a `begin` and `finish`-call.
/// If called outside this operation may error-out, do nothing or modify future rendering results in any way.
fn render_texture(
&mut self,
texture: &Self::TextureId,
matrix: Matrix3<f32>,
alpha: f32,
) -> Result<(), Self::Error>;
/// Render a texture to the current target as a flat 2d-plane at a given
/// position, applying the given transformation with the given alpha value.
///
/// This operation is only valid in between a `begin` and `finish`-call.
/// If called outside this operation may error-out, do nothing or modify future rendering results in any way.
fn render_texture_at(
&mut self,
texture: &Self::TextureId,
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)
}
rendering: F,
) -> Result<R, Self::Error>
where
F: FnOnce(&mut Self, &mut Self::Frame) -> R
;
}
/// Returns the dimensions of a wl_buffer

View File

@ -10,17 +10,12 @@ use crate::backend::{
UnusedEvent,
},
renderer::{
gles2::{Gles2Error, Gles2Renderer, Gles2Texture},
Bind, Renderer, Transform,
gles2::{Gles2Error, Gles2Renderer, Gles2Frame, Gles2Texture},
Bind, Unbind, Frame, Renderer, Transform,
},
};
#[cfg(feature = "wayland_frontend")]
use crate::wayland::compositor::Damage;
use cgmath::Matrix3;
use std::{cell::RefCell, rc::Rc, time::Instant};
use wayland_egl as wegl;
#[cfg(feature = "wayland_frontend")]
use wayland_server::protocol::{wl_buffer, wl_shm};
use wayland_server::Display;
use winit::{
dpi::{LogicalPosition, LogicalSize, PhysicalSize},
@ -259,72 +254,27 @@ impl WinitGraphicsBackend {
&*self.window
}
/// Shortcut to `Renderer::begin` with the current window dimensions.
pub fn begin(&mut self) -> Result<(), Gles2Error> {
/// Access the underlying renderer
pub fn renderer(&mut self) -> &mut Gles2Renderer {
&mut self.renderer
}
/// Shortcut to `Renderer::render` with the current window dimensions
/// and this window set as the rendering target.
pub fn render<F, R>(&mut self, rendering: F) -> Result<R, crate::backend::SwapBuffersError>
where
F: FnOnce(&mut Gles2Renderer, &mut Gles2Frame) -> R
{
let (width, height) = {
let size = self.size.borrow();
size.physical_size.into()
};
self.renderer.bind(self.egl.clone())?;
self.renderer.begin(width, height, Transform::Normal)
}
}
impl Renderer for WinitGraphicsBackend {
type Error = Gles2Error;
type TextureId = Gles2Texture;
#[cfg(feature = "image")]
fn import_bitmap<C: std::ops::Deref<Target = [u8]>>(
&mut self,
image: &image::ImageBuffer<image::Rgba<u8>, C>,
) -> Result<Self::TextureId, Self::Error> {
self.renderer.import_bitmap(image)
}
#[cfg(feature = "wayland_frontend")]
fn shm_formats(&self) -> &[wl_shm::Format] {
Renderer::shm_formats(&self.renderer)
}
#[cfg(feature = "wayland_frontend")]
fn import_buffer(
&mut self,
buffer: &wl_buffer::WlBuffer,
damage: Option<&Damage>,
egl: Option<&EGLBufferReader>,
) -> Result<Self::TextureId, Self::Error> {
self.renderer.import_buffer(buffer, damage, egl)
}
fn begin(
&mut self,
width: u32,
height: u32,
transform: Transform,
) -> Result<(), <Self as Renderer>::Error> {
self.renderer.bind(self.egl.clone())?;
self.renderer.begin(width, height, transform)
}
fn clear(&mut self, color: [f32; 4]) -> Result<(), Self::Error> {
self.renderer.clear(color)
}
fn render_texture(
&mut self,
texture: &Self::TextureId,
matrix: Matrix3<f32>,
alpha: f32,
) -> Result<(), Self::Error> {
self.renderer.render_texture(texture, matrix, alpha)
}
fn finish(&mut self) -> Result<(), crate::backend::SwapBuffersError> {
self.renderer.finish()?;
let result = self.renderer.render(width, height, Transform::Normal, rendering)?;
self.egl.swap_buffers()?;
Ok(())
self.renderer.unbind()?;
Ok(result)
}
}