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::{ use smithay::{
backend::{ backend::{
egl::display::EGLBufferReader, egl::display::EGLBufferReader,
renderer::{Renderer, Texture, Transform}, renderer::{Frame, Renderer, Texture, Transform},
SwapBuffersError, SwapBuffersError,
}, },
reexports::{ 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, renderer: &mut R,
frame: &mut F,
surface: &wl_surface::WlSurface, surface: &wl_surface::WlSurface,
egl_buffer_reader: Option<&EGLBufferReader>, egl_buffer_reader: Option<&EGLBufferReader>,
(x, y): (i32, i32), (x, y): (i32, i32),
@ -45,7 +46,8 @@ pub fn draw_cursor<R, E, T>(
log: &Logger, log: &Logger,
) -> Result<(), SwapBuffersError> ) -> Result<(), SwapBuffersError>
where 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>, E: std::error::Error + Into<SwapBuffersError>,
T: Texture + 'static, T: Texture + 'static,
{ {
@ -59,11 +61,12 @@ where
(0, 0) (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, renderer: &mut R,
frame: &mut F,
root: &wl_surface::WlSurface, root: &wl_surface::WlSurface,
egl_buffer_reader: Option<&EGLBufferReader>, egl_buffer_reader: Option<&EGLBufferReader>,
location: (i32, i32), location: (i32, i32),
@ -71,7 +74,8 @@ fn draw_surface_tree<R, E, T>(
log: &Logger, log: &Logger,
) -> Result<(), SwapBuffersError> ) -> Result<(), SwapBuffersError>
where 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>, E: std::error::Error + Into<SwapBuffersError>,
T: Texture + 'static, T: Texture + 'static,
{ {
@ -79,8 +83,8 @@ where
compositor_token.with_surface_tree_upward( compositor_token.with_surface_tree_upward(
root, root,
(), location,
|_surface, attributes, _, _| { |_surface, attributes, role, &(mut x, mut y)| {
// Pull a new buffer if available // Pull a new buffer if available
if let Some(data) = attributes.user_data.get::<RefCell<SurfaceData>>() { if let Some(data) = attributes.user_data.get::<RefCell<SurfaceData>>() {
let mut data = data.borrow_mut(); let mut data = data.borrow_mut();
@ -107,31 +111,7 @@ where
} }
} }
// Now, should we be drawn ? // Now, should we be drawn ?
if data.texture.is_some() { if data.texture.is_some() {// if yes, also process the children
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 Role::<SubsurfaceRole>::has(role) { if Role::<SubsurfaceRole>::has(role) {
x += data.current_state.sub_location.0; x += data.current_state.sub_location.0;
y += data.current_state.sub_location.1; y += data.current_state.sub_location.1;
@ -161,7 +141,7 @@ where
x += sub_x; x += sub_x;
y += sub_y; y += sub_y;
} }
if let Err(err) = renderer.render_texture_at( if let Err(err) = frame.render_texture_at(
&texture.texture, &texture.texture,
(x, y), (x, y),
Transform::Normal, /* TODO */ Transform::Normal, /* TODO */
@ -178,8 +158,9 @@ where
result result
} }
pub fn draw_windows<R, E, T>( pub fn draw_windows<R, E, F, T>(
renderer: &mut R, renderer: &mut R,
frame: &mut F,
egl_buffer_reader: Option<&EGLBufferReader>, egl_buffer_reader: Option<&EGLBufferReader>,
window_map: &MyWindowMap, window_map: &MyWindowMap,
output_rect: Option<Rectangle>, output_rect: Option<Rectangle>,
@ -187,7 +168,8 @@ pub fn draw_windows<R, E, T>(
log: &::slog::Logger, log: &::slog::Logger,
) -> Result<(), SwapBuffersError> ) -> Result<(), SwapBuffersError>
where 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>, E: std::error::Error + Into<SwapBuffersError>,
T: Texture + 'static, T: Texture + 'static,
{ {
@ -206,6 +188,7 @@ where
// this surface is a root of a subsurface tree that needs to be drawn // this surface is a root of a subsurface tree that needs to be drawn
if let Err(err) = draw_surface_tree( if let Err(err) = draw_surface_tree(
renderer, renderer,
frame,
&wl_surface, &wl_surface,
egl_buffer_reader, egl_buffer_reader,
initial_place, initial_place,
@ -220,8 +203,9 @@ where
result result
} }
pub fn draw_dnd_icon<R, E, T>( pub fn draw_dnd_icon<R, E, F, T>(
renderer: &mut R, renderer: &mut R,
frame: &mut F,
surface: &wl_surface::WlSurface, surface: &wl_surface::WlSurface,
egl_buffer_reader: Option<&EGLBufferReader>, egl_buffer_reader: Option<&EGLBufferReader>,
(x, y): (i32, i32), (x, y): (i32, i32),
@ -229,7 +213,8 @@ pub fn draw_dnd_icon<R, E, T>(
log: &::slog::Logger, log: &::slog::Logger,
) -> Result<(), SwapBuffersError> ) -> Result<(), SwapBuffersError>
where 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>, E: std::error::Error + Into<SwapBuffersError>,
T: Texture + 'static, T: Texture + 'static,
{ {
@ -239,39 +224,5 @@ where
"Trying to display as a dnd icon a surface that does not have the DndIcon role." "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) draw_surface_tree(renderer, frame, 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),
}
}
} }

View File

@ -21,7 +21,7 @@ use smithay::{
libinput::{LibinputInputBackend, LibinputSessionInterface}, libinput::{LibinputInputBackend, LibinputSessionInterface},
renderer::{ renderer::{
gles2::{Gles2Renderer, Gles2Texture}, gles2::{Gles2Renderer, Gles2Texture},
Renderer, Transform, Frame, Renderer, Transform,
}, },
session::{auto::AutoSession, Session, Signal as SessionSignal}, session::{auto::AutoSession, Session, Signal as SessionSignal},
udev::{UdevBackend, UdevEvent}, udev::{UdevBackend, UdevEvent},
@ -528,10 +528,10 @@ impl<Data: 'static> UdevHandlerImpl<Data> {
.unwrap(); .unwrap();
trace!(self.logger, "Backends: {:?}", backends.borrow().keys()); trace!(self.logger, "Backends: {:?}", backends.borrow().keys());
for renderer in backends.borrow_mut().values() { for backend in backends.borrow_mut().values() {
// render first frame // render first frame
trace!(self.logger, "Scheduling 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( self.backends.insert(
@ -767,70 +767,108 @@ impl DrmRenderer {
.unwrap_or((0, 0)); // in this case the output will be removed. .unwrap_or((0, 0)); // in this case the output will be removed.
// and draw in sync with our monitor // and draw in sync with our monitor
surface.queue_frame()?; surface.render(|renderer, frame| {
surface.clear([0.8, 0.8, 0.9, 1.0])?; frame.clear([0.8, 0.8, 0.9, 1.0])?;
// draw the surfaces // draw the surfaces
draw_windows( draw_windows(
surface, renderer,
egl_buffer_reader, frame,
window_map, egl_buffer_reader,
Some(Rectangle { window_map,
x: x as i32, Some(Rectangle {
y: y as i32, x: x as i32,
width: width as i32, y: y as i32,
height: height as i32, width: width as i32,
}), height: height as i32,
*compositor_token, }),
logger, *compositor_token,
)?; logger,
)?;
// get pointer coordinates // get pointer coordinates
let (ptr_x, ptr_y) = *pointer_location; let (ptr_x, ptr_y) = *pointer_location;
let ptr_x = ptr_x.trunc().abs() as i32 - x as i32; let ptr_x = ptr_x.trunc().abs() as i32 - x as i32;
let ptr_y = ptr_y.trunc().abs() as i32 - y as i32; let ptr_y = ptr_y.trunc().abs() as i32 - y as i32;
// set cursor // set cursor
if ptr_x >= 0 && ptr_x < width as i32 && ptr_y >= 0 && ptr_y < height as i32 { if ptr_x >= 0 && ptr_x < width as i32 && ptr_y >= 0 && ptr_y < height as i32 {
// draw the dnd icon if applicable // draw the dnd icon if applicable
{ {
if let Some(ref wl_surface) = dnd_icon.as_ref() { if let Some(ref wl_surface) = dnd_icon.as_ref() {
if wl_surface.as_ref().is_alive() { if wl_surface.as_ref().is_alive() {
draw_dnd_icon( draw_dnd_icon(
surface, 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, wl_surface,
egl_buffer_reader, egl_buffer_reader,
(ptr_x, ptr_y), (ptr_x, ptr_y),
*compositor_token, *compositor_token,
logger, 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 { Ok(())
draw_cursor( }).map_err(Into::<SwapBuffersError>::into)
surface, .and_then(|x| x)
wl_surface, .map_err(Into::<SwapBuffersError>::into)
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()
} }
} }
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 std::{cell::RefCell, rc::Rc, sync::atomic::Ordering, time::Duration};
use smithay::{ use smithay::{
backend::{input::InputBackend, renderer::Renderer, winit, SwapBuffersError}, backend::{input::InputBackend, renderer::Frame, winit, SwapBuffersError},
reexports::{ reexports::{
calloop::EventLoop, calloop::EventLoop,
wayland_server::{protocol::wl_output, Display}, wayland_server::{protocol::wl_output, Display},
@ -82,6 +82,7 @@ pub fn run_winit(
}); });
let start_time = std::time::Instant::now(); let start_time = std::time::Instant::now();
let mut cursor_visible = true;
info!(log, "Initialization completed, starting the main loop."); info!(log, "Initialization completed, starting the main loop.");
@ -98,70 +99,75 @@ pub fn run_winit(
{ {
let mut renderer = renderer.borrow_mut(); 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 let result = renderer.render(|renderer, frame| {
draw_windows( frame.clear([0.8, 0.8, 0.9, 1.0])?;
&mut *renderer,
reader.as_ref(),
&*state.window_map.borrow(),
None,
state.ctoken,
&log,
)
.expect("Failed to renderer windows");
let (x, y) = *state.pointer_location.borrow(); // draw the windows
// draw the dnd icon if any draw_windows(
{ renderer,
let guard = state.dnd_icon.lock().unwrap(); frame,
if let Some(ref surface) = *guard { reader.as_ref(),
if surface.as_ref().is_alive() { &*state.window_map.borrow(),
draw_dnd_icon( None,
&mut *renderer, 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, surface,
reader.as_ref(), reader.as_ref(),
(x as i32, y as i32), (x as i32, y as i32),
state.ctoken, state.ctoken,
&log, &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 Ok(())
if let CursorImageStatus::Image(ref surface) = *guard { }).map_err(Into::<SwapBuffersError>::into)
renderer.window().set_cursor_visible(false); .and_then(|x| x.into());
draw_cursor(
&mut *renderer, renderer.window().set_cursor_visible(cursor_visible);
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);
}
}
if let Err(SwapBuffersError::ContextLost(err)) = renderer.finish() { if let Err(SwapBuffersError::ContextLost(err)) = result {
error!(log, "Critical Rendering Error: {}", err); error!(log, "Critical Rendering Error: {}", err);
state.running.store(false, Ordering::SeqCst); state.running.store(false, Ordering::SeqCst);
} }

View File

@ -2,17 +2,12 @@ use std::collections::HashSet;
use std::os::unix::io::AsRawFd; use std::os::unix::io::AsRawFd;
use std::sync::Arc; use std::sync::Arc;
#[cfg(feature = "wayland_frontend")]
use crate::wayland::compositor::Damage;
use cgmath::Matrix3;
use drm::buffer::PlanarBuffer; use drm::buffer::PlanarBuffer;
use drm::control::{connector, crtc, framebuffer, plane, Device, Mode}; use drm::control::{connector, crtc, framebuffer, plane, Device, Mode};
use gbm::{BufferObject, BufferObjectFlags, Device as GbmDevice}; 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 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::SwapBuffersError;
use crate::backend::{ use crate::backend::{
allocator::{ allocator::{
@ -20,8 +15,6 @@ use crate::backend::{
Allocator, Buffer, Format, Fourcc, Modifier, Slot, Swapchain, 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. /// 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> { pub struct DrmRenderSurface<D: AsRawFd + 'static, A: Allocator<B>, R: Bind<Dmabuf>, B: Buffer> {
_format: Format, _format: Format,
buffers: Buffers<D, B>, buffers: Buffers<D, B>,
current_buffer: Option<(Slot<B, (Dmabuf, BufferObject<FbHandle<D>>)>, Dmabuf)>,
swapchain: Swapchain<A, B, (Dmabuf, BufferObject<FbHandle<D>>)>, swapchain: Swapchain<A, B, (Dmabuf, BufferObject<FbHandle<D>>)>,
renderer: R, renderer: R,
drm: Arc<DrmSurface<D>>, drm: Arc<DrmSurface<D>>,
@ -169,11 +161,11 @@ where
.map_err(Error::<E1, E2, E3>::RenderError) .map_err(Error::<E1, E2, E3>::RenderError)
.and_then(|_| { .and_then(|_| {
renderer 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) .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)) .and_then(|_| renderer.unbind().map_err(Error::RenderError))
{ {
Ok(_) => {} Ok(_) => {}
@ -204,7 +196,6 @@ where
renderer, renderer,
swapchain, swapchain,
buffers, buffers,
current_buffer: None,
}) })
} }
Err(err) => { Err(err) => {
@ -217,34 +208,53 @@ where
} }
} }
/// Shortcut to [`Renderer::begin`] with the pending mode as dimensions. /// Access the underlying renderer
pub fn queue_frame(&mut self) -> Result<(), Error<E1, E2, E3>> { pub fn renderer(&mut self) -> &mut R {
let mode = self.drm.pending_mode(); &mut self.renderer
let (width, height) = (mode.size().0 as u32, mode.size().1 as u32);
self.begin(width, height, Transform::Normal)
} }
/// Shortcut to abort the current frame. /// Shortcut to [`Renderer::render`] with the pending mode as dimensions
/// /// and this surface set a the rendering target.
/// Allows [`DrmRenderSurface::queue_frame`] or [`Renderer::begin`] to be called again pub fn render<F, S>(&mut self, rendering: F) -> Result<S, Error<E1, E2, E3>>
/// without displaying the current rendering context to the user. where
pub fn drop_frame(&mut self) -> Result<(), SwapBuffersError> { F: FnOnce(&mut R, &mut <R as Renderer>::Frame) -> S
if self.current_buffer.is_none() { {
return Ok(()); 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 Ok(result)
let result = self.renderer.finish();
// but do not queue the buffer, drop it in any case
let _ = self.current_buffer.take();
result
} }
/// Marks the current frame as submitted. /// Marks the current frame as submitted.
/// ///
/// Needs to be called, after the vblank event of the matching [`DrmDevice`](super::DrmDevice) /// 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 /// was received after calling [`DrmRenderSurface::render`] on this surface.
/// will run out of buffers eventually. /// Otherwise the rendering will run out of buffers eventually.
pub fn frame_submitted(&mut self) -> Result<(), Error<E1, E2, E3>> { pub fn frame_submitted(&mut self) -> Result<(), Error<E1, E2, E3>> {
self.buffers.submitted() 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> { struct FbHandle<D: AsRawFd + 'static> {
drm: Arc<DrmSurface<D>>, drm: Arc<DrmSurface<D>>,
fb: framebuffer::Handle, fb: framebuffer::Handle,

View File

@ -19,7 +19,7 @@ use cgmath::{prelude::*, Matrix3};
mod shaders; mod shaders;
mod version; mod version;
use super::{Bind, Renderer, Texture, Transform, Unbind}; use super::{Bind, Frame, Renderer, Texture, Transform, Unbind};
use crate::backend::allocator::{ use crate::backend::allocator::{
dmabuf::{Dmabuf, WeakDmabuf}, dmabuf::{Dmabuf, WeakDmabuf},
Format, Format,
@ -48,7 +48,7 @@ pub mod ffi {
// cannot assume, that resources between two renderers are (and even can be) shared. // cannot assume, that resources between two renderers are (and even can be) shared.
static RENDERER_COUNTER: AtomicUsize = AtomicUsize::new(0); static RENDERER_COUNTER: AtomicUsize = AtomicUsize::new(0);
#[derive(Debug)] #[derive(Debug, Clone)]
struct Gles2Program { struct Gles2Program {
program: ffi::types::GLuint, program: ffi::types::GLuint,
uniform_tex: ffi::types::GLint, uniform_tex: ffi::types::GLint,
@ -147,13 +147,12 @@ pub struct Gles2Renderer {
buffers: Vec<WeakGles2Buffer>, buffers: Vec<WeakGles2Buffer>,
target_buffer: Option<Gles2Buffer>, target_buffer: Option<Gles2Buffer>,
target_surface: Option<Rc<EGLSurface>>, target_surface: Option<Rc<EGLSurface>>,
current_projection: Option<Matrix3<f32>>,
extensions: Vec<String>, extensions: Vec<String>,
programs: [Gles2Program; shaders::FRAGMENT_COUNT], programs: [Gles2Program; shaders::FRAGMENT_COUNT],
#[cfg(feature = "wayland_frontend")] #[cfg(feature = "wayland_frontend")]
textures: HashMap<BufferEntry, Gles2Texture>, textures: HashMap<BufferEntry, Gles2Texture>,
gl: ffi::Gles2,
egl: EGLContext, egl: EGLContext,
gl: ffi::Gles2,
destruction_callback: Receiver<CleanupResource>, destruction_callback: Receiver<CleanupResource>,
destruction_callback_sender: Sender<CleanupResource>, destruction_callback_sender: Sender<CleanupResource>,
logger_ptr: Option<*mut ::slog::Logger>, logger_ptr: Option<*mut ::slog::Logger>,
@ -161,6 +160,13 @@ pub struct Gles2Renderer {
_not_send: *mut (), _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 { impl fmt::Debug for Gles2Renderer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Gles2Renderer") f.debug_struct("Gles2Renderer")
@ -168,7 +174,6 @@ impl fmt::Debug for Gles2Renderer {
.field("buffers", &self.buffers) .field("buffers", &self.buffers)
.field("target_buffer", &self.target_buffer) .field("target_buffer", &self.target_buffer)
.field("target_surface", &self.target_surface) .field("target_surface", &self.target_surface)
.field("current_projection", &self.current_projection)
.field("extensions", &self.extensions) .field("extensions", &self.extensions)
.field("programs", &self.programs) .field("programs", &self.programs)
// ffi::Gles2 does not implement Debug // ffi::Gles2 does not implement Debug
@ -453,7 +458,6 @@ impl Gles2Renderer {
buffers: Vec::new(), buffers: Vec::new(),
#[cfg(feature = "wayland_frontend")] #[cfg(feature = "wayland_frontend")]
textures: HashMap::new(), textures: HashMap::new(),
current_projection: None,
destruction_callback: rx, destruction_callback: rx,
destruction_callback_sender: tx, destruction_callback_sender: tx,
logger_ptr, logger_ptr,
@ -596,7 +600,6 @@ impl Gles2Renderer {
self.gl.BindTexture(ffi::TEXTURE_2D, 0); self.gl.BindTexture(ffi::TEXTURE_2D, 0);
} }
self.egl.unbind()?;
Ok(texture) Ok(texture)
}) })
.map_err(Gles2Error::BufferAccessError)? .map_err(Gles2Error::BufferAccessError)?
@ -671,7 +674,6 @@ impl Gles2Renderer {
self.make_current()?; self.make_current()?;
let tex = Some(texture.0.texture); let tex = Some(texture.0.texture);
self.import_egl_image(egl_images[0], false, tex)?; self.import_egl_image(egl_images[0], false, tex)?;
self.egl.unbind()?;
} }
Ok(Some(texture)) Ok(Some(texture))
} }
@ -867,6 +869,7 @@ static TEX_COORDS: [ffi::types::GLfloat; 8] = [
impl Renderer for Gles2Renderer { impl Renderer for Gles2Renderer {
type Error = Gles2Error; type Error = Gles2Error;
type TextureId = Gles2Texture; type TextureId = Gles2Texture;
type Frame = Gles2Frame;
#[cfg(feature = "wayland_frontend")] #[cfg(feature = "wayland_frontend")]
fn shm_formats(&self) -> &[wl_shm::Format] { fn shm_formats(&self) -> &[wl_shm::Format] {
@ -962,7 +965,15 @@ impl Renderer for Gles2Renderer {
Ok(texture) 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()?; self.make_current()?;
// delayed destruction until the next frame rendering. // delayed destruction until the next frame rendering.
self.cleanup()?; self.cleanup()?;
@ -991,13 +1002,44 @@ impl Renderer for Gles2Renderer {
renderer[2][0] = -(1.0f32.copysign(renderer[0][0] + renderer[1][0])); renderer[2][0] = -(1.0f32.copysign(renderer[0][0] + renderer[1][0]));
renderer[2][1] = -(1.0f32.copysign(renderer[0][1] + renderer[1][1])); renderer[2][1] = -(1.0f32.copysign(renderer[0][1] + renderer[1][1]));
// output transformation passed in by the user let mut frame = Gles2Frame {
self.current_projection = Some(transform.matrix() * renderer); gl: self.gl.clone(),
Ok(()) 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> { fn clear(&mut self, color: [f32; 4]) -> Result<(), Self::Error> {
self.make_current()?;
unsafe { unsafe {
self.gl.ClearColor(color[0], color[1], color[2], color[3]); self.gl.ClearColor(color[0], color[1], color[2], color[3]);
self.gl.Clear(ffi::COLOR_BUFFER_BIT); self.gl.Clear(ffi::COLOR_BUFFER_BIT);
@ -1012,13 +1054,8 @@ impl Renderer for Gles2Renderer {
mut matrix: Matrix3<f32>, mut matrix: Matrix3<f32>,
alpha: f32, alpha: f32,
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
self.make_current()?;
if self.current_projection.is_none() {
return Err(Gles2Error::UnconstraintRenderingOperation);
}
//apply output transformation //apply output transformation
matrix = self.current_projection.as_ref().unwrap() * matrix; matrix = self.current_projection * matrix;
let target = if tex.0.is_external { let target = if tex.0.is_external {
ffi::TEXTURE_EXTERNAL_OES ffi::TEXTURE_EXTERNAL_OES
@ -1083,31 +1120,4 @@ impl Renderer for Gles2Renderer {
Ok(()) 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; 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. /// Abstraction of commonly used rendering operations for compositors.
pub trait Renderer { pub trait Renderer {
/// Error type returned by the rendering operations of this renderer. /// Error type returned by the rendering operations of this renderer.
type Error: Error; type Error: Error;
/// Texture Handle type used by this renderer. /// Texture Handle type used by this renderer.
type TextureId: Texture; type TextureId: Texture;
type Frame: Frame<Error=Self::Error, TextureId=Self::TextureId>;
/// Import a given bitmap into the renderer. /// 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`. /// - 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. /// - This renderer implements `Bind`, no target was bound *and* has no default target.
/// - (Renderers not implementing `Bind` always have a default target.) /// - (Renderers not implementing `Bind` always have a default target.)
fn begin( fn render<F, R>(
&mut self, &mut self,
width: u32, width: u32,
height: u32, height: u32,
transform: Transform, transform: Transform,
) -> Result<(), <Self as Renderer>::Error>; rendering: F,
) -> Result<R, Self::Error>
/// Finish a renderering context, previously started by `begin`. where
/// F: FnOnce(&mut Self, &mut Self::Frame) -> R
/// 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)
}
} }
/// Returns the dimensions of a wl_buffer /// Returns the dimensions of a wl_buffer

View File

@ -10,17 +10,12 @@ use crate::backend::{
UnusedEvent, UnusedEvent,
}, },
renderer::{ renderer::{
gles2::{Gles2Error, Gles2Renderer, Gles2Texture}, gles2::{Gles2Error, Gles2Renderer, Gles2Frame, Gles2Texture},
Bind, Renderer, Transform, 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 std::{cell::RefCell, rc::Rc, time::Instant};
use wayland_egl as wegl; use wayland_egl as wegl;
#[cfg(feature = "wayland_frontend")]
use wayland_server::protocol::{wl_buffer, wl_shm};
use wayland_server::Display; use wayland_server::Display;
use winit::{ use winit::{
dpi::{LogicalPosition, LogicalSize, PhysicalSize}, dpi::{LogicalPosition, LogicalSize, PhysicalSize},
@ -259,72 +254,27 @@ impl WinitGraphicsBackend {
&*self.window &*self.window
} }
/// Shortcut to `Renderer::begin` with the current window dimensions. /// Access the underlying renderer
pub fn begin(&mut self) -> Result<(), Gles2Error> { 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 (width, height) = {
let size = self.size.borrow(); let size = self.size.borrow();
size.physical_size.into() size.physical_size.into()
}; };
self.renderer.bind(self.egl.clone())?; self.renderer.bind(self.egl.clone())?;
self.renderer.begin(width, height, Transform::Normal) let result = self.renderer.render(width, height, Transform::Normal, rendering)?;
}
}
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()?;
self.egl.swap_buffers()?; self.egl.swap_buffers()?;
Ok(()) self.renderer.unbind()?;
Ok(result)
} }
} }