From 978ef1b393dbfccd0ab4980cf45941d01a36af23 Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Sun, 23 May 2021 23:03:43 +0200 Subject: [PATCH] renderer: Closure-based api --- anvil/src/drawing.rs | 97 +++++------------ anvil/src/udev.rs | 150 ++++++++++++++++---------- anvil/src/winit.rs | 114 ++++++++++---------- src/backend/drm/render.rs | 168 ++++++++---------------------- src/backend/renderer/gles2/mod.rs | 102 ++++++++++-------- src/backend/renderer/mod.rs | 115 ++++++++++---------- src/backend/winit.rs | 82 +++------------ 7 files changed, 353 insertions(+), 475 deletions(-) diff --git a/anvil/src/drawing.rs b/anvil/src/drawing.rs index f7dacaf..e2739b1 100644 --- a/anvil/src/drawing.rs +++ b/anvil/src/drawing.rs @@ -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 Drop for BufferTextures { } } -pub fn draw_cursor( +pub fn draw_cursor( 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( log: &Logger, ) -> Result<(), SwapBuffersError> where - R: Renderer, + R: Renderer, + F: Frame, E: std::error::Error + Into, 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( +fn draw_surface_tree( 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( log: &Logger, ) -> Result<(), SwapBuffersError> where - R: Renderer, + R: Renderer, + F: Frame, E: std::error::Error + Into, 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::>() { 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::>() { - 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::::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( +pub fn draw_windows( renderer: &mut R, + frame: &mut F, egl_buffer_reader: Option<&EGLBufferReader>, window_map: &MyWindowMap, output_rect: Option, @@ -187,7 +168,8 @@ pub fn draw_windows( log: &::slog::Logger, ) -> Result<(), SwapBuffersError> where - R: Renderer, + R: Renderer, + F: Frame, E: std::error::Error + Into, 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( +pub fn draw_dnd_icon( 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( log: &::slog::Logger, ) -> Result<(), SwapBuffersError> where - R: Renderer, + R: Renderer, + F: Frame, E: std::error::Error + Into, 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( - renderer: Rc>, - evt_handle: &LoopHandle, - logger: ::slog::Logger, -) where - ::Error: Into, -{ - 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::::into) - .and_then(|_| { - renderer - .clear([0.8, 0.8, 0.9, 1.0]) - .map_err(Into::::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) } diff --git a/anvil/src/udev.rs b/anvil/src/udev.rs index 1b9288f..37f6175 100644 --- a/anvil/src/udev.rs +++ b/anvil/src/udev.rs @@ -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 UdevHandlerImpl { .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::::into) + .and_then(|x| x) + .map_err(Into::::into) } } + +fn schedule_initial_render( + renderer: Rc>, + evt_handle: &LoopHandle, + 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::::into) + }) + .map_err(Into::::into) + .and_then(|x| x.map_err(Into::::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), + } + } +} \ No newline at end of file diff --git a/anvil/src/winit.rs b/anvil/src/winit.rs index aa3af49..8892e68 100644 --- a/anvil/src/winit.rs +++ b/anvil/src/winit.rs @@ -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::::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); } diff --git a/src/backend/drm/render.rs b/src/backend/drm/render.rs index 842e997..247bab5 100644 --- a/src/backend/drm/render.rs +++ b/src/backend/drm/render.rs @@ -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, R: Bind, B: Buffer> { _format: Format, buffers: Buffers, - current_buffer: Option<(Slot>)>, Dmabuf)>, swapchain: Swapchain>)>, renderer: R, drm: Arc>, @@ -169,11 +161,11 @@ where .map_err(Error::::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> { - 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(&mut self, rendering: F) -> Result> + where + F: FnOnce(&mut R, &mut ::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 implementation to add and correct _transform here */, + rendering, + ) + .map_err(Error::RenderError)?; + + match self.buffers.queue::(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> { self.buffers.submitted() } @@ -325,100 +335,6 @@ where } } -impl Renderer for DrmRenderSurface -where - D: AsRawFd + 'static, - A: Allocator, - B: Buffer + AsDmabuf, - R: Bind + Renderer, - T: Texture, - E1: std::error::Error + 'static, - E2: std::error::Error + 'static, - E3: std::error::Error + 'static, -{ - type Error = Error; - type TextureId = T; - - #[cfg(feature = "image")] - fn import_bitmap>( - &mut self, - image: &image::ImageBuffer, C>, - ) -> Result { - 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.renderer - .import_buffer(buffer, damage, egl) - .map_err(Error::RenderError) - } - - fn begin(&mut self, width: u32, height: u32, _transform: Transform) -> Result<(), Error> { - 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 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, - 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::(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 { drm: Arc>, fb: framebuffer::Handle, diff --git a/src/backend/renderer/gles2/mod.rs b/src/backend/renderer/gles2/mod.rs index 5b1a240..551efad 100644 --- a/src/backend/renderer/gles2/mod.rs +++ b/src/backend/renderer/gles2/mod.rs @@ -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, target_buffer: Option, target_surface: Option>, - current_projection: Option>, extensions: Vec, programs: [Gles2Program; shaders::FRAGMENT_COUNT], #[cfg(feature = "wayland_frontend")] textures: HashMap, - gl: ffi::Gles2, egl: EGLContext, + gl: ffi::Gles2, destruction_callback: Receiver, destruction_callback_sender: Sender, 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, + 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( + &mut self, + width: u32, height: u32, + transform: Transform, + rendering: F, + ) -> Result + 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, 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(()) - } } diff --git a/src/backend/renderer/mod.rs b/src/backend/renderer/mod.rs index e19d344..de11695 100644 --- a/src/backend/renderer/mod.rs +++ b/src/backend/renderer/mod.rs @@ -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, + 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::::identity(); + + // position and scale + let size = texture.size(); + mat = mat * Matrix3::from_translation(Vector2::new(pos.0 as f32, pos.1 as f32)); + mat = mat * Matrix3::from_nonuniform_scale(size.0 as f32, size.1 as f32); + + //apply surface transformation + mat = mat * Matrix3::from_translation(Vector2::new(0.5, 0.5)); + if transform == Transform::Normal { + assert_eq!(mat, mat * transform.invert().matrix()); + assert_eq!(transform.matrix(), Matrix3::::identity()); + } + mat = mat * transform.invert().matrix(); + mat = mat * Matrix3::from_translation(Vector2::new(-0.5, -0.5)); + + self.render_texture(texture, mat, alpha) + } +} + /// 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; /// 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( &mut self, width: u32, height: u32, transform: Transform, - ) -> Result<(), ::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, - 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::::identity(); - - // position and scale - let size = texture.size(); - mat = mat * Matrix3::from_translation(Vector2::new(pos.0 as f32, pos.1 as f32)); - mat = mat * Matrix3::from_nonuniform_scale(size.0 as f32, size.1 as f32); - - //apply surface transformation - mat = mat * Matrix3::from_translation(Vector2::new(0.5, 0.5)); - if transform == Transform::Normal { - assert_eq!(mat, mat * transform.invert().matrix()); - assert_eq!(transform.matrix(), Matrix3::::identity()); - } - mat = mat * transform.invert().matrix(); - mat = mat * Matrix3::from_translation(Vector2::new(-0.5, -0.5)); - - self.render_texture(texture, mat, alpha) - } + rendering: F, + ) -> Result + where + F: FnOnce(&mut Self, &mut Self::Frame) -> R + ; } /// Returns the dimensions of a wl_buffer diff --git a/src/backend/winit.rs b/src/backend/winit.rs index fbb93bd..3177142 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -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(&mut self, rendering: F) -> Result + 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>( - &mut self, - image: &image::ImageBuffer, C>, - ) -> Result { - 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.renderer.import_buffer(buffer, damage, egl) - } - - fn begin( - &mut self, - width: u32, - height: u32, - transform: Transform, - ) -> Result<(), ::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, - 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) } }