Rework `DrmRenderSurface` into `GbmBufferedSurface`.

Removes the renderer from the `DrmRenderSurface` allowing anvil
to use just one renderer per backend.

Since the old `DrmRenderSurface` was dependant on gbm anyway to import
buffers, the new `GbmBufferedSurface` does now only supports gbm as an allocator,
which hugely simplifies the code and also skips some unnecessary imports/exports.
This commit is contained in:
Victor Brekenfeld 2021-06-18 23:28:48 +02:00 committed by Victor Berger
parent 6bd0d71ebc
commit ce3b2d1eab
4 changed files with 255 additions and 350 deletions

View File

@ -12,19 +12,15 @@ use std::{
use image::{ImageBuffer, Rgba}; use image::{ImageBuffer, Rgba};
use slog::Logger; use slog::Logger;
#[cfg(feature = "egl")]
use smithay::{
backend::{drm::DevPath, egl::display::EGLBufferReader, renderer::ImportDma, udev::primary_gpu},
wayland::dmabuf::init_dmabuf_global,
};
use smithay::{ use smithay::{
backend::{ backend::{
drm::{DrmDevice, DrmError, DrmEvent, DrmRenderSurface}, allocator::dmabuf::Dmabuf,
drm::{DrmDevice, DrmError, DrmEvent, GbmBufferedSurface},
egl::{EGLContext, EGLDisplay}, egl::{EGLContext, EGLDisplay},
libinput::{LibinputInputBackend, LibinputSessionInterface}, libinput::{LibinputInputBackend, LibinputSessionInterface},
renderer::{ renderer::{
gles2::{Gles2Renderer, Gles2Texture}, gles2::{Gles2Renderer, Gles2Texture},
Frame, Renderer, Transform, Bind, Frame, Renderer, Transform,
}, },
session::{auto::AutoSession, Session, Signal as SessionSignal}, session::{auto::AutoSession, Session, Signal as SessionSignal},
udev::{UdevBackend, UdevEvent}, udev::{UdevBackend, UdevEvent},
@ -44,7 +40,7 @@ use smithay::{
Device as ControlDevice, Device as ControlDevice,
}, },
}, },
gbm::{BufferObject as GbmBuffer, Device as GbmDevice}, gbm::Device as GbmDevice,
input::Libinput, input::Libinput,
nix::{fcntl::OFlag, sys::stat::dev_t}, nix::{fcntl::OFlag, sys::stat::dev_t},
wayland_server::{ wayland_server::{
@ -60,6 +56,11 @@ use smithay::{
seat::CursorImageStatus, seat::CursorImageStatus,
}, },
}; };
#[cfg(feature = "egl")]
use smithay::{
backend::{drm::DevPath, egl::display::EGLBufferReader, renderer::ImportDma, udev::primary_gpu},
wayland::dmabuf::init_dmabuf_global,
};
use crate::drawing::*; use crate::drawing::*;
use crate::shell::{MyWindowMap, Roles}; use crate::shell::{MyWindowMap, Roles};
@ -189,10 +190,7 @@ pub fn run_udev(
{ {
let mut formats = Vec::new(); let mut formats = Vec::new();
for backend_data in state.backend_data.backends.values() { for backend_data in state.backend_data.backends.values() {
let surfaces = backend_data.surfaces.borrow_mut(); formats.extend(backend_data.renderer.borrow().dmabuf_formats().cloned());
if let Some(surface) = surfaces.values().next() {
formats.extend(surface.borrow_mut().renderer().dmabuf_formats().cloned());
}
} }
init_dmabuf_global( init_dmabuf_global(
@ -201,11 +199,8 @@ pub fn run_udev(
|buffer, mut ddata| { |buffer, mut ddata| {
let anvil_state = ddata.get::<AnvilState<UdevData>>().unwrap(); let anvil_state = ddata.get::<AnvilState<UdevData>>().unwrap();
for backend_data in anvil_state.backend_data.backends.values() { for backend_data in anvil_state.backend_data.backends.values() {
let surfaces = backend_data.surfaces.borrow_mut(); if backend_data.renderer.borrow_mut().import_dmabuf(buffer).is_ok() {
if let Some(surface) = surfaces.values().next() { return true;
if surface.borrow_mut().renderer().import_dmabuf(buffer).is_ok() {
return true;
}
} }
} }
false false
@ -318,25 +313,23 @@ impl Drop for MyOutput {
} }
} }
pub type RenderSurface = DrmRenderSurface<SessionFd, GbmDevice<SessionFd>, Gles2Renderer, GbmBuffer<()>>; pub type RenderSurface = GbmBufferedSurface<SessionFd>;
struct BackendData { struct BackendData {
_restart_token: SignalToken, _restart_token: SignalToken,
surfaces: Rc<RefCell<HashMap<crtc::Handle, Rc<RefCell<RenderSurface>>>>>, surfaces: Rc<RefCell<HashMap<crtc::Handle, Rc<RefCell<RenderSurface>>>>>,
context: EGLContext, pointer_image: Gles2Texture,
egl: EGLDisplay, renderer: Rc<RefCell<Gles2Renderer>>,
gbm: GbmDevice<SessionFd>, gbm: GbmDevice<SessionFd>,
registration_token: RegistrationToken, registration_token: RegistrationToken,
event_dispatcher: Dispatcher<'static, DrmDevice<SessionFd>, AnvilState<UdevData>>, event_dispatcher: Dispatcher<'static, DrmDevice<SessionFd>, AnvilState<UdevData>>,
dev_id: u64, dev_id: u64,
pointer_image: Gles2Texture,
} }
pub fn scan_connectors( pub fn scan_connectors(
device: &mut DrmDevice<SessionFd>, device: &mut DrmDevice<SessionFd>,
gbm: &GbmDevice<SessionFd>, gbm: &GbmDevice<SessionFd>,
egl: &EGLDisplay, renderer: &mut Gles2Renderer,
context: &EGLContext,
display: &mut Display, display: &mut Display,
output_map: &mut Vec<MyOutput>, output_map: &mut Vec<MyOutput>,
signaler: &Signaler<SessionSignal>, signaler: &Signaler<SessionSignal>,
@ -374,20 +367,6 @@ pub fn scan_connectors(
connector_info.interface_id(), connector_info.interface_id(),
crtc, crtc,
); );
let context = match EGLContext::new_shared(egl, context, logger.clone()) {
Ok(context) => context,
Err(err) => {
warn!(logger, "Failed to create EGLContext: {}", err);
continue;
}
};
let renderer = match unsafe { Gles2Renderer::new(context, logger.clone()) } {
Ok(renderer) => renderer,
Err(err) => {
warn!(logger, "Failed to create Gles2 Renderer: {}", err);
continue;
}
};
let mut surface = match device.create_surface( let mut surface = match device.create_surface(
crtc, crtc,
connector_info.modes()[0], connector_info.modes()[0],
@ -400,14 +379,19 @@ pub fn scan_connectors(
} }
}; };
surface.link(signaler.clone()); surface.link(signaler.clone());
let renderer = match DrmRenderSurface::new(surface, gbm.clone(), renderer, logger.clone())
{ let renderer_formats =
Ok(renderer) => renderer, Bind::<Dmabuf>::supported_formats(renderer).expect("Dmabuf renderer without formats");
Err(err) => {
warn!(logger, "Failed to create rendering surface: {}", err); let renderer =
continue; match GbmBufferedSurface::new(surface, gbm.clone(), renderer_formats, logger.clone())
} {
}; Ok(renderer) => renderer,
Err(err) => {
warn!(logger, "Failed to create rendering surface: {}", err);
continue;
}
};
output_map.push(MyOutput::new( output_map.push(MyOutput::new(
display, display,
@ -498,26 +482,24 @@ impl AnvilState<UdevData> {
} }
}; };
let renderer = Rc::new(RefCell::new(unsafe {
Gles2Renderer::new(context, self.log.clone()).unwrap()
}));
let backends = Rc::new(RefCell::new(scan_connectors( let backends = Rc::new(RefCell::new(scan_connectors(
&mut device, &mut device,
&gbm, &gbm,
&egl, &mut *renderer.borrow_mut(),
&context,
&mut *self.display.borrow_mut(), &mut *self.display.borrow_mut(),
&mut self.backend_data.output_map, &mut self.backend_data.output_map,
&self.backend_data.signaler, &self.backend_data.signaler,
&self.log, &self.log,
))); )));
// we leak this texture (we would need to call `destroy_texture` on Drop of DrmRenderer), let pointer_image = renderer
// but only on shutdown anyway, because we do not support hot-pluggin, so it does not really matter. .borrow_mut()
let pointer_image = { .import_bitmap(&self.backend_data.pointer_image)
let context = EGLContext::new_shared(&egl, &context, self.log.clone()).unwrap(); .expect("Failed to load pointer");
let mut renderer = unsafe { Gles2Renderer::new(context, self.log.clone()).unwrap() };
renderer
.import_bitmap(&self.backend_data.pointer_image)
.expect("Failed to load pointer")
};
let dev_id = device.device_id(); let dev_id = device.device_id();
let handle = self.handle.clone(); let handle = self.handle.clone();
@ -544,7 +526,7 @@ impl AnvilState<UdevData> {
for backend in backends.borrow_mut().values() { for backend in backends.borrow_mut().values() {
// render first frame // render first frame
trace!(self.log, "Scheduling frame"); trace!(self.log, "Scheduling frame");
schedule_initial_render(backend.clone(), &self.handle, self.log.clone()); schedule_initial_render(backend.clone(), renderer.clone(), &self.handle, self.log.clone());
} }
self.backend_data.backends.insert( self.backend_data.backends.insert(
@ -554,8 +536,7 @@ impl AnvilState<UdevData> {
registration_token, registration_token,
event_dispatcher, event_dispatcher,
surfaces: backends, surfaces: backends,
egl, renderer,
context,
gbm, gbm,
pointer_image, pointer_image,
dev_id, dev_id,
@ -580,8 +561,7 @@ impl AnvilState<UdevData> {
*backends = scan_connectors( *backends = scan_connectors(
&mut *source, &mut *source,
&backend_data.gbm, &backend_data.gbm,
&backend_data.egl, &mut *backend_data.renderer.borrow_mut(),
&backend_data.context,
&mut *display, &mut *display,
&mut self.backend_data.output_map, &mut self.backend_data.output_map,
&signaler, &signaler,
@ -591,7 +571,12 @@ impl AnvilState<UdevData> {
for renderer in backends.values() { for renderer in backends.values() {
let logger = logger.clone(); let logger = logger.clone();
// render first frame // render first frame
schedule_initial_render(renderer.clone(), &loop_handle, logger); schedule_initial_render(
renderer.clone(),
backend_data.renderer.clone(),
&loop_handle,
logger,
);
} }
} }
} }
@ -651,6 +636,7 @@ impl AnvilState<UdevData> {
for (&crtc, surface) in to_render_iter { for (&crtc, surface) in to_render_iter {
let result = render_surface( let result = render_surface(
&mut *surface.borrow_mut(), &mut *surface.borrow_mut(),
&mut *device_backend.renderer.borrow_mut(),
#[cfg(feature = "egl")] #[cfg(feature = "egl")]
self.egl_reader.as_ref(), self.egl_reader.as_ref(),
device_backend.dev_id, device_backend.dev_id,
@ -700,6 +686,7 @@ impl AnvilState<UdevData> {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn render_surface( fn render_surface(
surface: &mut RenderSurface, surface: &mut RenderSurface,
renderer: &mut Gles2Renderer,
#[cfg(feature = "egl")] egl_buffer_reader: Option<&EGLBufferReader>, #[cfg(feature = "egl")] egl_buffer_reader: Option<&EGLBufferReader>,
device_id: dev_t, device_id: dev_t,
crtc: crtc::Handle, crtc: crtc::Handle,
@ -725,39 +712,70 @@ fn render_surface(
.map(|output| output.size) .map(|output| output.size)
.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 let dmabuf = surface.next_buffer()?;
surface renderer.bind(dmabuf)?;
.render(|renderer, frame| { // and draw to our buffer
frame.clear([0.8, 0.8, 0.9, 1.0])?; match renderer
// draw the surfaces .render(
draw_windows( width,
renderer, height,
frame, Transform::Flipped180, // Scanout is rotated
#[cfg(feature = "egl")] |renderer, frame| {
egl_buffer_reader, frame.clear([0.8, 0.8, 0.9, 1.0])?;
window_map, // draw the surfaces
Some(Rectangle { draw_windows(
x: x as i32, renderer,
y: y as i32, frame,
width: width as i32, #[cfg(feature = "egl")]
height: height as i32, egl_buffer_reader,
}), window_map,
*compositor_token, Some(Rectangle {
logger, x: x as i32,
)?; y: y as i32,
width: width as i32,
height: height as i32,
}),
*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(
renderer,
frame,
wl_surface,
#[cfg(feature = "egl")]
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, renderer,
frame, frame,
wl_surface, wl_surface,
@ -767,60 +785,34 @@ fn render_surface(
*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( },
renderer, )
frame,
wl_surface,
#[cfg(feature = "egl")]
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)?;
}
}
}
Ok(())
})
.map_err(Into::<SwapBuffersError>::into) .map_err(Into::<SwapBuffersError>::into)
.and_then(|x| x) .and_then(|x| x)
.map_err(Into::<SwapBuffersError>::into) .map_err(Into::<SwapBuffersError>::into)
{
Ok(()) => surface.queue_buffer().map_err(Into::<SwapBuffersError>::into),
Err(err) => Err(err),
}
} }
fn schedule_initial_render<Data: 'static>( fn schedule_initial_render<Data: 'static>(
renderer: Rc<RefCell<RenderSurface>>, surface: Rc<RefCell<RenderSurface>>,
renderer: Rc<RefCell<Gles2Renderer>>,
evt_handle: &LoopHandle<'static, Data>, evt_handle: &LoopHandle<'static, Data>,
logger: ::slog::Logger, logger: ::slog::Logger,
) { ) {
let result = { let result = {
let mut surface = surface.borrow_mut();
let mut renderer = renderer.borrow_mut(); let mut renderer = renderer.borrow_mut();
// Does not matter if we render an empty frame initial_render(&mut *surface, &mut *renderer)
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 { if let Err(err) = result {
match err { match err {
@ -829,9 +821,25 @@ fn schedule_initial_render<Data: 'static>(
// TODO dont reschedule after 3(?) retries // TODO dont reschedule after 3(?) retries
warn!(logger, "Failed to submit page_flip: {}", err); warn!(logger, "Failed to submit page_flip: {}", err);
let handle = evt_handle.clone(); let handle = evt_handle.clone();
evt_handle.insert_idle(move |_| schedule_initial_render(renderer, &handle, logger)); evt_handle.insert_idle(move |_| schedule_initial_render(surface, renderer, &handle, logger));
} }
SwapBuffersError::ContextLost(err) => panic!("Rendering loop lost: {}", err), SwapBuffersError::ContextLost(err) => panic!("Rendering loop lost: {}", err),
} }
} }
} }
fn initial_render(surface: &mut RenderSurface, renderer: &mut Gles2Renderer) -> Result<(), SwapBuffersError> {
let dmabuf = surface.next_buffer()?;
renderer.bind(dmabuf)?;
// Does not matter if we render an empty frame
renderer
.render(1, 1, Transform::Normal, |_, 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))?;
surface.queue_buffer()?;
Ok(())
}

View File

@ -62,8 +62,6 @@
pub(crate) mod device; pub(crate) mod device;
pub(self) mod error; pub(self) mod error;
#[cfg(feature = "backend_gbm")]
mod render;
#[cfg(feature = "backend_session")] #[cfg(feature = "backend_session")]
pub(self) mod session; pub(self) mod session;
pub(self) mod surface; pub(self) mod surface;
@ -71,7 +69,7 @@ pub(self) mod surface;
pub use device::{DevPath, DrmDevice, DrmEvent}; pub use device::{DevPath, DrmDevice, DrmEvent};
pub use error::Error as DrmError; pub use error::Error as DrmError;
#[cfg(feature = "backend_gbm")] #[cfg(feature = "backend_gbm")]
pub use render::{DrmRenderSurface, Error as DrmRenderError}; pub use surface::gbm::{Error as GbmBufferedSurfaceError, GbmBufferedSurface};
pub use surface::DrmSurface; pub use surface::DrmSurface;
use drm::control::{crtc, plane, Device as ControlDevice, PlaneType}; use drm::control::{crtc, plane, Device as ControlDevice, PlaneType};

View File

@ -4,14 +4,14 @@ use std::sync::Arc;
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, Device as GbmDevice};
use super::{device::DevPath, surface::DrmSurfaceInternal, DrmError, DrmSurface};
use crate::backend::allocator::{ use crate::backend::allocator::{
dmabuf::{AsDmabuf, Dmabuf}, dmabuf::{AsDmabuf, Dmabuf},
Allocator, Buffer, Format, Fourcc, Modifier, Slot, Swapchain, gbm::GbmConvertError,
Format, Fourcc, Modifier, Slot, Swapchain,
}; };
use crate::backend::renderer::{Bind, Frame, Renderer, Transform}; use crate::backend::drm::{device::DevPath, surface::DrmSurfaceInternal, DrmError, DrmSurface};
use crate::backend::SwapBuffersError; use crate::backend::SwapBuffersError;
/// Simplified by limited abstraction to link single [`DrmSurface`]s to renderers. /// Simplified by limited abstraction to link single [`DrmSurface`]s to renderers.
@ -21,22 +21,15 @@ use crate::backend::SwapBuffersError;
/// In some scenarios it might be enough to use of a drm-surface as the one and only target /// In some scenarios it might be enough to use of a drm-surface as the one and only target
/// of a single renderer. In these cases `DrmRenderSurface` provides a way to quickly /// of a single renderer. In these cases `DrmRenderSurface` provides a way to quickly
/// get up and running without manually handling and binding buffers. /// get up and running without manually handling and binding buffers.
pub struct DrmRenderSurface<D: AsRawFd + 'static, A: Allocator<B>, R: Bind<Dmabuf>, B: Buffer> { pub struct GbmBufferedSurface<D: AsRawFd + 'static> {
buffers: Buffers<D, B>, buffers: Buffers<D>,
swapchain: Swapchain<A, B, (Dmabuf, BufferObject<FbHandle<D>>)>, swapchain: Swapchain<GbmDevice<D>, BufferObject<()>, (Dmabuf, FbHandle<D>)>,
renderer: R,
drm: Arc<DrmSurface<D>>, drm: Arc<DrmSurface<D>>,
} }
impl<D, A, B, R, E1, E2, E3> DrmRenderSurface<D, A, R, B> impl<D> GbmBufferedSurface<D>
where where
D: AsRawFd + 'static, D: AsRawFd + 'static,
A: Allocator<B, Error = E1>,
B: Buffer + AsDmabuf<Error = E2>,
R: Bind<Dmabuf> + Renderer<Error = E3>,
E1: std::error::Error + 'static,
E2: std::error::Error + 'static,
E3: std::error::Error + 'static,
{ {
/// Create a new `DrmRendererSurface` from a given compatible combination /// Create a new `DrmRendererSurface` from a given compatible combination
/// of a surface, an allocator and a renderer. /// of a surface, an allocator and a renderer.
@ -48,12 +41,15 @@ where
/// The function will futhermore check for compatibility by enumerating /// The function will futhermore check for compatibility by enumerating
/// supported pixel formats and choosing an appropriate one. /// supported pixel formats and choosing an appropriate one.
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
pub fn new<L: Into<Option<::slog::Logger>>>( pub fn new<L>(
drm: DrmSurface<D>, drm: DrmSurface<D>,
allocator: A, allocator: GbmDevice<D>,
mut renderer: R, mut renderer_formats: HashSet<Format>,
log: L, log: L,
) -> Result<DrmRenderSurface<D, A, R, B>, Error<E1, E2, E3>> { ) -> Result<GbmBufferedSurface<D>, Error>
where
L: Into<Option<::slog::Logger>>,
{
// we cannot simply pick the first supported format of the intersection of *all* formats, because: // we cannot simply pick the first supported format of the intersection of *all* formats, because:
// - we do not want something like Abgr4444, which looses color information // - we do not want something like Abgr4444, which looses color information
// - some formats might perform terribly // - some formats might perform terribly
@ -73,15 +69,10 @@ where
.filter(|fmt| fmt.code == code) .filter(|fmt| fmt.code == code)
.cloned() .cloned()
.collect::<HashSet<_>>(); .collect::<HashSet<_>>();
let renderer_formats = Bind::<Dmabuf>::supported_formats(&renderer) renderer_formats.retain(|fmt| fmt.code == code);
.expect("Dmabuf renderer without formats")
.iter()
.filter(|fmt| fmt.code == code)
.cloned()
.collect::<HashSet<_>>();
debug!(logger, "Remaining plane formats: {:?}", plane_formats); trace!(logger, "Plane formats: {:?}", plane_formats);
debug!(logger, "Remaining renderer formats: {:?}", renderer_formats); trace!(logger, "Renderer formats: {:?}", renderer_formats);
debug!( debug!(
logger, logger,
"Remaining intersected formats: {:?}", "Remaining intersected formats: {:?}",
@ -95,6 +86,7 @@ where
} else if renderer_formats.is_empty() { } else if renderer_formats.is_empty() {
return Err(Error::NoSupportedRendererFormat); return Err(Error::NoSupportedRendererFormat);
} }
let formats = { let formats = {
// Special case: if a format supports explicit LINEAR (but no implicit Modifiers) // Special case: if a format supports explicit LINEAR (but no implicit Modifiers)
// and the other doesn't support any modifier, force Implicit. // and the other doesn't support any modifier, force Implicit.
@ -127,8 +119,7 @@ where
let mode = drm.pending_mode(); let mode = drm.pending_mode();
let gbm = unsafe { GbmDevice::new_from_fd(drm.as_raw_fd())? }; let mut swapchain: Swapchain<GbmDevice<D>, BufferObject<()>, (Dmabuf, FbHandle<D>)> = Swapchain::new(
let mut swapchain = Swapchain::new(
allocator, allocator,
mode.size().0 as u32, mode.size().0 as u32,
mode.size().1 as u32, mode.size().1 as u32,
@ -137,116 +128,63 @@ where
); );
// Test format // Test format
let buffer = swapchain.acquire().map_err(Error::SwapchainError)?.unwrap(); let buffer = swapchain.acquire()?.unwrap();
let dmabuf = buffer.export().map_err(Error::AsDmabufError)?;
let format = Format { let format = Format {
code, code,
modifier: buffer.format().modifier, // no guarantee modifier: buffer.modifier().unwrap(), // no guarantee
// that this is stable across allocations, but // that this is stable across allocations, but
// we want to print that here for debugging proposes. // we want to print that here for debugging proposes.
// It has no further use. // It has no further use.
}; };
match renderer let fb = attach_framebuffer(&drm, &*buffer)?;
.bind(dmabuf.clone()) let dmabuf = buffer.export()?;
.map_err(Error::<E1, E2, E3>::RenderError) let handle = fb.fb;
.and_then(|_| { *buffer.userdata() = Some((dmabuf, fb));
renderer
.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.unbind().map_err(Error::RenderError))
{
Ok(_) => {
let bo = import_dmabuf(&drm, &gbm, &dmabuf)?;
let fb = bo.userdata().unwrap().unwrap().fb;
*buffer.userdata() = Some((dmabuf, bo));
match drm.test_buffer(fb, &mode, true) { match drm.test_buffer(handle, &mode, true) {
Ok(_) => { Ok(_) => {
debug!(logger, "Success, choosen format: {:?}", format); debug!(logger, "Choosen format: {:?}", format);
let buffers = Buffers::new(drm.clone(), gbm, buffer); let buffers = Buffers::new(drm.clone(), buffer);
Ok(DrmRenderSurface { Ok(GbmBufferedSurface {
buffers, buffers,
swapchain, swapchain,
renderer, drm,
drm, })
})
}
Err(err) => {
warn!(
logger,
"Mode-setting failed with automatically selected buffer format {:?}: {}",
format,
err
);
Err(err).map_err(Into::into)
}
}
} }
Err(err) => { Err(err) => {
warn!( warn!(
logger, logger,
"Rendering failed with automatically selected format {:?}: {}", format, err "Mode-setting failed with automatically selected buffer format {:?}: {}", format, err
); );
Err(err) Err(err).map_err(Into::into)
} }
} }
} }
/// Access the underlying renderer /// Retrieves the next buffer to be rendered into.
pub fn renderer(&mut self) -> &mut R { ///
&mut self.renderer /// *Note*: This function can be called multiple times and
/// will return the same buffer until it is queued (see [`GbmBufferedSurface::queue_buffer`]).
pub fn next_buffer(&mut self) -> Result<Dmabuf, Error> {
self.buffers.next(&mut self.swapchain)
} }
/// Shortcut to [`Renderer::render`] with the pending mode as dimensions /// Queues the current buffer for rendering.
/// and this surface set a the rendering target. ///
pub fn render<F, S>(&mut self, rendering: F) -> Result<S, Error<E1, E2, E3>> /// *Note*: This function needs to be followed up with [`GbmBufferedSurface::frame_submitted`]
where /// when a vblank event is received, that denotes successful scanout of the buffer.
F: FnOnce(&mut R, &mut <R as Renderer>::Frame) -> S, /// Otherwise the underlying swapchain will eventually run out of buffers.
{ pub fn queue_buffer(&mut self) -> Result<(), Error> {
let mode = self.drm.pending_mode(); self.buffers.queue()
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!(),
}
Ok(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) /// *Note*: Needs to be called, after the vblank event of the matching [`DrmDevice`](super::DrmDevice)
/// was received after calling [`DrmRenderSurface::render`] on this surface. /// was received after calling [`GbmBufferedSurface::queue_buffer`] on this surface.
/// Otherwise the rendering will run out of buffers eventually. /// Otherwise the underlying swapchain 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> {
self.buffers.submitted() self.buffers.submitted()
} }
@ -266,7 +204,7 @@ where
} }
/// Returns the pending [`connector`](drm::control::connector)s /// Returns the pending [`connector`](drm::control::connector)s
/// used for the next frame in [`render`](DrmRenderSurface::render) /// used for the next frame queued via [`queue_buffer`](GbmBufferedSurface::queue_buffer).
pub fn pending_connectors(&self) -> impl IntoIterator<Item = connector::Handle> { pub fn pending_connectors(&self) -> impl IntoIterator<Item = connector::Handle> {
self.drm.pending_connectors() self.drm.pending_connectors()
} }
@ -283,13 +221,13 @@ where
/// (e.g. no suitable [`encoder`](drm::control::encoder) may be found) /// (e.g. no suitable [`encoder`](drm::control::encoder) may be found)
/// or is not compatible with the currently pending /// or is not compatible with the currently pending
/// [`Mode`](drm::control::Mode). /// [`Mode`](drm::control::Mode).
pub fn add_connector(&self, connector: connector::Handle) -> Result<(), Error<E1, E2, E3>> { pub fn add_connector(&self, connector: connector::Handle) -> Result<(), Error> {
self.drm.add_connector(connector).map_err(Error::DrmError) self.drm.add_connector(connector).map_err(Error::DrmError)
} }
/// Tries to mark a [`connector`](drm::control::connector) /// Tries to mark a [`connector`](drm::control::connector)
/// for removal on the next commit. /// for removal on the next commit.
pub fn remove_connector(&self, connector: connector::Handle) -> Result<(), Error<E1, E2, E3>> { pub fn remove_connector(&self, connector: connector::Handle) -> Result<(), Error> {
self.drm.remove_connector(connector).map_err(Error::DrmError) self.drm.remove_connector(connector).map_err(Error::DrmError)
} }
@ -299,7 +237,7 @@ where
/// (e.g. no suitable [`encoder`](drm::control::encoder) may be found) /// (e.g. no suitable [`encoder`](drm::control::encoder) may be found)
/// or is not compatible with the currently pending /// or is not compatible with the currently pending
/// [`Mode`](drm::control::Mode). /// [`Mode`](drm::control::Mode).
pub fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Error<E1, E2, E3>> { pub fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Error> {
self.drm.set_connectors(connectors).map_err(Error::DrmError) self.drm.set_connectors(connectors).map_err(Error::DrmError)
} }
@ -321,7 +259,7 @@ where
/// Fails if the mode is not compatible with the underlying /// Fails if the mode is not compatible with the underlying
/// [`crtc`](drm::control::crtc) or any of the /// [`crtc`](drm::control::crtc) or any of the
/// pending [`connector`](drm::control::connector)s. /// pending [`connector`](drm::control::connector)s.
pub fn use_mode(&self, mode: Mode) -> Result<(), Error<E1, E2, E3>> { pub fn use_mode(&self, mode: Mode) -> Result<(), Error> {
self.drm.use_mode(mode).map_err(Error::DrmError) self.drm.use_mode(mode).map_err(Error::DrmError)
} }
} }
@ -337,65 +275,66 @@ impl<A: AsRawFd + 'static> Drop for FbHandle<A> {
} }
} }
type DmabufSlot<B, D> = Slot<B, (Dmabuf, BufferObject<FbHandle<D>>)>; type DmabufSlot<D> = Slot<BufferObject<()>, (Dmabuf, FbHandle<D>)>;
struct Buffers<D: AsRawFd + 'static, B: Buffer> { struct Buffers<D: AsRawFd + 'static> {
gbm: GbmDevice<gbm::FdWrapper>,
drm: Arc<DrmSurface<D>>, drm: Arc<DrmSurface<D>>,
_current_fb: DmabufSlot<B, D>, _current_fb: DmabufSlot<D>,
pending_fb: Option<DmabufSlot<B, D>>, pending_fb: Option<DmabufSlot<D>>,
queued_fb: Option<DmabufSlot<B, D>>, queued_fb: Option<DmabufSlot<D>>,
next_fb: Option<DmabufSlot<D>>,
} }
impl<D, B> Buffers<D, B> impl<D> Buffers<D>
where where
B: Buffer + AsDmabuf,
D: AsRawFd + 'static, D: AsRawFd + 'static,
{ {
pub fn new( pub fn new(drm: Arc<DrmSurface<D>>, slot: DmabufSlot<D>) -> Buffers<D> {
drm: Arc<DrmSurface<D>>,
gbm: GbmDevice<gbm::FdWrapper>,
slot: Slot<B, (Dmabuf, BufferObject<FbHandle<D>>)>,
) -> Buffers<D, B> {
Buffers { Buffers {
drm, drm,
gbm,
_current_fb: slot, _current_fb: slot,
pending_fb: None, pending_fb: None,
queued_fb: None, queued_fb: None,
next_fb: None,
} }
} }
pub fn queue<E1, E2, E3>( pub fn next(
&mut self, &mut self,
slot: Slot<B, (Dmabuf, BufferObject<FbHandle<D>>)>, swapchain: &mut Swapchain<GbmDevice<D>, BufferObject<()>, (Dmabuf, FbHandle<D>)>,
dmabuf: Dmabuf, ) -> Result<Dmabuf, Error> {
) -> Result<(), Error<E1, E2, E3>> if let Some(slot) = self.next_fb.as_ref() {
where return Ok(slot.userdata().as_ref().unwrap().0.clone());
B: AsDmabuf<Error = E2>,
E1: std::error::Error + 'static,
E2: std::error::Error + 'static,
E3: std::error::Error + 'static,
{
if slot.userdata().is_none() {
let bo = import_dmabuf(&self.drm, &self.gbm, &dmabuf)?;
*slot.userdata() = Some((dmabuf, bo));
} }
self.queued_fb = Some(slot); let slot = swapchain.acquire()?.ok_or(Error::NoFreeSlotsError)?;
if self.pending_fb.is_none() {
let maybe_buffer = slot.userdata().as_ref().map(|(buf, _)| buf.clone());
let dmabuf = match maybe_buffer {
Some(buf) => buf.clone(),
None => {
let dmabuf = slot.export()?;
let fb_handle = attach_framebuffer(&self.drm, &*slot)?;
*slot.userdata() = Some((dmabuf.clone(), fb_handle));
dmabuf
}
};
self.next_fb = Some(slot);
Ok(dmabuf)
}
pub fn queue(&mut self) -> Result<(), Error> {
self.queued_fb = self.next_fb.take();
if self.pending_fb.is_none() && self.queued_fb.is_some() {
self.submit() self.submit()
} else { } else {
Ok(()) Ok(())
} }
} }
pub fn submitted<E1, E2, E3>(&mut self) -> Result<(), Error<E1, E2, E3>> pub fn submitted(&mut self) -> Result<(), Error> {
where
E1: std::error::Error + 'static,
E2: std::error::Error + 'static,
E3: std::error::Error + 'static,
{
if self.pending_fb.is_none() { if self.pending_fb.is_none() {
return Ok(()); return Ok(());
} }
@ -407,23 +346,10 @@ where
} }
} }
fn submit<E1, E2, E3>(&mut self) -> Result<(), Error<E1, E2, E3>> fn submit(&mut self) -> Result<(), Error> {
where
E1: std::error::Error + 'static,
E2: std::error::Error + 'static,
E3: std::error::Error + 'static,
{
// yes it does not look like it, but both of these lines should be safe in all cases. // yes it does not look like it, but both of these lines should be safe in all cases.
let slot = self.queued_fb.take().unwrap(); let slot = self.queued_fb.take().unwrap();
let fb = slot let fb = slot.userdata().as_ref().unwrap().1.fb;
.userdata()
.as_ref()
.unwrap()
.1
.userdata()
.unwrap()
.unwrap()
.fb;
let flip = if self.drm.commit_pending() { let flip = if self.drm.commit_pending() {
self.drm.commit([(fb, self.drm.plane())].iter(), true) self.drm.commit([(fb, self.drm.plane())].iter(), true)
@ -437,19 +363,10 @@ where
} }
} }
fn import_dmabuf<A, E1, E2, E3>( fn attach_framebuffer<A>(drm: &Arc<DrmSurface<A>>, bo: &BufferObject<()>) -> Result<FbHandle<A>, Error>
drm: &Arc<DrmSurface<A>>,
gbm: &GbmDevice<gbm::FdWrapper>,
buffer: &Dmabuf,
) -> Result<BufferObject<FbHandle<A>>, Error<E1, E2, E3>>
where where
A: AsRawFd + 'static, A: AsRawFd + 'static,
E1: std::error::Error + 'static,
E2: std::error::Error + 'static,
E3: std::error::Error + 'static,
{ {
// TODO check userdata and return early
let mut bo = buffer.import_to(&gbm, BufferObjectFlags::SCANOUT)?;
let modifier = match bo.modifier().unwrap() { let modifier = match bo.modifier().unwrap() {
Modifier::Invalid => None, Modifier::Invalid => None,
x => Some(x), x => Some(x),
@ -468,15 +385,15 @@ where
if num > 2 { modifier } else { None }, if num > 2 { modifier } else { None },
if num > 3 { modifier } else { None }, if num > 3 { modifier } else { None },
]; ];
drm.add_planar_framebuffer(&bo, &modifiers, drm_ffi::DRM_MODE_FB_MODIFIERS) drm.add_planar_framebuffer(bo, &modifiers, drm_ffi::DRM_MODE_FB_MODIFIERS)
} else { } else {
drm.add_planar_framebuffer(&bo, &[None, None, None, None], 0) drm.add_planar_framebuffer(bo, &[None, None, None, None], 0)
} { } {
Ok(fb) => fb, Ok(fb) => fb,
Err(source) => { Err(source) => {
// We only support this as a fallback of last resort for ARGB8888 visuals, // We only support this as a fallback of last resort for ARGB8888 visuals,
// like xf86-video-modesetting does. // like xf86-video-modesetting does.
if drm::buffer::Buffer::format(&bo) != Fourcc::Argb8888 || bo.handles()[1].is_some() { if drm::buffer::Buffer::format(bo) != Fourcc::Argb8888 || bo.handles()[1].is_some() {
return Err(Error::DrmError(DrmError::Access { return Err(Error::DrmError(DrmError::Access {
errmsg: "Failed to add framebuffer", errmsg: "Failed to add framebuffer",
dev: drm.dev_path(), dev: drm.dev_path(),
@ -484,7 +401,7 @@ where
})); }));
} }
debug!(logger, "Failed to add framebuffer, trying legacy method"); debug!(logger, "Failed to add framebuffer, trying legacy method");
drm.add_framebuffer(&bo, 32, 32) drm.add_framebuffer(bo, 32, 32)
.map_err(|source| DrmError::Access { .map_err(|source| DrmError::Access {
errmsg: "Failed to add framebuffer", errmsg: "Failed to add framebuffer",
dev: drm.dev_path(), dev: drm.dev_path(),
@ -492,19 +409,12 @@ where
})? })?
} }
}; };
bo.set_userdata(FbHandle { drm: drm.clone(), fb }).unwrap(); Ok(FbHandle { drm: drm.clone(), fb })
Ok(bo)
} }
/// Errors thrown by a [`DrmRenderSurface`] /// Errors thrown by a [`GbmBufferedSurface`]
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum Error<E1, E2, E3> pub enum Error {
where
E1: std::error::Error + 'static,
E2: std::error::Error + 'static,
E3: std::error::Error + 'static,
{
/// No supported pixel format for the given plane could be determined /// No supported pixel format for the given plane could be determined
#[error("No supported plane buffer format found")] #[error("No supported plane buffer format found")]
NoSupportedPlaneFormat, NoSupportedPlaneFormat,
@ -526,24 +436,13 @@ where
/// Error importing the rendered buffer to libgbm for scan-out /// Error importing the rendered buffer to libgbm for scan-out
#[error("The underlying gbm device encounted an error: {0}")] #[error("The underlying gbm device encounted an error: {0}")]
GbmError(#[from] std::io::Error), GbmError(#[from] std::io::Error),
/// Error allocating or converting newly created buffers
#[error("The swapchain encounted an error: {0}")]
SwapchainError(#[source] E1),
/// Error exporting as Dmabuf /// Error exporting as Dmabuf
#[error("The allocated buffer could not be exported as a dmabuf: {0}")] #[error("The allocated buffer could not be exported as a dmabuf: {0}")]
AsDmabufError(#[source] E2), AsDmabufError(#[from] GbmConvertError),
/// Error during rendering
#[error("The renderer encounted an error: {0}")]
RenderError(#[source] E3),
} }
impl< impl From<Error> for SwapBuffersError {
E1: std::error::Error + 'static, fn from(err: Error) -> SwapBuffersError {
E2: std::error::Error + 'static,
E3: std::error::Error + Into<SwapBuffersError> + 'static,
> From<Error<E1, E2, E3>> for SwapBuffersError
{
fn from(err: Error<E1, E2, E3>) -> SwapBuffersError {
match err { match err {
x @ Error::NoSupportedPlaneFormat x @ Error::NoSupportedPlaneFormat
| x @ Error::NoSupportedRendererFormat | x @ Error::NoSupportedRendererFormat
@ -552,9 +451,7 @@ impl<
x @ Error::NoFreeSlotsError => SwapBuffersError::TemporaryFailure(Box::new(x)), x @ Error::NoFreeSlotsError => SwapBuffersError::TemporaryFailure(Box::new(x)),
Error::DrmError(err) => err.into(), Error::DrmError(err) => err.into(),
Error::GbmError(err) => SwapBuffersError::ContextLost(Box::new(err)), Error::GbmError(err) => SwapBuffersError::ContextLost(Box::new(err)),
Error::SwapchainError(err) => SwapBuffersError::ContextLost(Box::new(err)),
Error::AsDmabufError(err) => SwapBuffersError::ContextLost(Box::new(err)), Error::AsDmabufError(err) => SwapBuffersError::ContextLost(Box::new(err)),
Error::RenderError(err) => err.into(),
} }
} }
} }

View File

@ -10,6 +10,8 @@ use drm::{Device as BasicDevice, DriverCapability};
use nix::libc::dev_t; use nix::libc::dev_t;
pub(super) mod atomic; pub(super) mod atomic;
#[cfg(feature = "backend_gbm")]
pub(super) mod gbm;
pub(super) mod legacy; pub(super) mod legacy;
use super::{device::DevPath, error::Error, plane_type, planes, PlaneType, Planes}; use super::{device::DevPath, error::Error, plane_type, planes, PlaneType, Planes};
use crate::backend::allocator::{Format, Fourcc, Modifier}; use crate::backend::allocator::{Format, Fourcc, Modifier};