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 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::{
backend::{
drm::{DrmDevice, DrmError, DrmEvent, DrmRenderSurface},
allocator::dmabuf::Dmabuf,
drm::{DrmDevice, DrmError, DrmEvent, GbmBufferedSurface},
egl::{EGLContext, EGLDisplay},
libinput::{LibinputInputBackend, LibinputSessionInterface},
renderer::{
gles2::{Gles2Renderer, Gles2Texture},
Frame, Renderer, Transform,
Bind, Frame, Renderer, Transform,
},
session::{auto::AutoSession, Session, Signal as SessionSignal},
udev::{UdevBackend, UdevEvent},
@ -44,7 +40,7 @@ use smithay::{
Device as ControlDevice,
},
},
gbm::{BufferObject as GbmBuffer, Device as GbmDevice},
gbm::Device as GbmDevice,
input::Libinput,
nix::{fcntl::OFlag, sys::stat::dev_t},
wayland_server::{
@ -60,6 +56,11 @@ use smithay::{
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::shell::{MyWindowMap, Roles};
@ -189,10 +190,7 @@ pub fn run_udev(
{
let mut formats = Vec::new();
for backend_data in state.backend_data.backends.values() {
let surfaces = backend_data.surfaces.borrow_mut();
if let Some(surface) = surfaces.values().next() {
formats.extend(surface.borrow_mut().renderer().dmabuf_formats().cloned());
}
formats.extend(backend_data.renderer.borrow().dmabuf_formats().cloned());
}
init_dmabuf_global(
@ -201,11 +199,8 @@ pub fn run_udev(
|buffer, mut ddata| {
let anvil_state = ddata.get::<AnvilState<UdevData>>().unwrap();
for backend_data in anvil_state.backend_data.backends.values() {
let surfaces = backend_data.surfaces.borrow_mut();
if let Some(surface) = surfaces.values().next() {
if surface.borrow_mut().renderer().import_dmabuf(buffer).is_ok() {
return true;
}
if backend_data.renderer.borrow_mut().import_dmabuf(buffer).is_ok() {
return true;
}
}
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 {
_restart_token: SignalToken,
surfaces: Rc<RefCell<HashMap<crtc::Handle, Rc<RefCell<RenderSurface>>>>>,
context: EGLContext,
egl: EGLDisplay,
pointer_image: Gles2Texture,
renderer: Rc<RefCell<Gles2Renderer>>,
gbm: GbmDevice<SessionFd>,
registration_token: RegistrationToken,
event_dispatcher: Dispatcher<'static, DrmDevice<SessionFd>, AnvilState<UdevData>>,
dev_id: u64,
pointer_image: Gles2Texture,
}
pub fn scan_connectors(
device: &mut DrmDevice<SessionFd>,
gbm: &GbmDevice<SessionFd>,
egl: &EGLDisplay,
context: &EGLContext,
renderer: &mut Gles2Renderer,
display: &mut Display,
output_map: &mut Vec<MyOutput>,
signaler: &Signaler<SessionSignal>,
@ -374,20 +367,6 @@ pub fn scan_connectors(
connector_info.interface_id(),
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(
crtc,
connector_info.modes()[0],
@ -400,14 +379,19 @@ pub fn scan_connectors(
}
};
surface.link(signaler.clone());
let renderer = match DrmRenderSurface::new(surface, gbm.clone(), renderer, logger.clone())
{
Ok(renderer) => renderer,
Err(err) => {
warn!(logger, "Failed to create rendering surface: {}", err);
continue;
}
};
let renderer_formats =
Bind::<Dmabuf>::supported_formats(renderer).expect("Dmabuf renderer without formats");
let renderer =
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(
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(
&mut device,
&gbm,
&egl,
&context,
&mut *renderer.borrow_mut(),
&mut *self.display.borrow_mut(),
&mut self.backend_data.output_map,
&self.backend_data.signaler,
&self.log,
)));
// we leak this texture (we would need to call `destroy_texture` on Drop of DrmRenderer),
// but only on shutdown anyway, because we do not support hot-pluggin, so it does not really matter.
let pointer_image = {
let context = EGLContext::new_shared(&egl, &context, self.log.clone()).unwrap();
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 pointer_image = renderer
.borrow_mut()
.import_bitmap(&self.backend_data.pointer_image)
.expect("Failed to load pointer");
let dev_id = device.device_id();
let handle = self.handle.clone();
@ -544,7 +526,7 @@ impl AnvilState<UdevData> {
for backend in backends.borrow_mut().values() {
// render first 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(
@ -554,8 +536,7 @@ impl AnvilState<UdevData> {
registration_token,
event_dispatcher,
surfaces: backends,
egl,
context,
renderer,
gbm,
pointer_image,
dev_id,
@ -580,8 +561,7 @@ impl AnvilState<UdevData> {
*backends = scan_connectors(
&mut *source,
&backend_data.gbm,
&backend_data.egl,
&backend_data.context,
&mut *backend_data.renderer.borrow_mut(),
&mut *display,
&mut self.backend_data.output_map,
&signaler,
@ -591,7 +571,12 @@ impl AnvilState<UdevData> {
for renderer in backends.values() {
let logger = logger.clone();
// 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 {
let result = render_surface(
&mut *surface.borrow_mut(),
&mut *device_backend.renderer.borrow_mut(),
#[cfg(feature = "egl")]
self.egl_reader.as_ref(),
device_backend.dev_id,
@ -700,6 +686,7 @@ impl AnvilState<UdevData> {
#[allow(clippy::too_many_arguments)]
fn render_surface(
surface: &mut RenderSurface,
renderer: &mut Gles2Renderer,
#[cfg(feature = "egl")] egl_buffer_reader: Option<&EGLBufferReader>,
device_id: dev_t,
crtc: crtc::Handle,
@ -725,39 +712,70 @@ fn render_surface(
.map(|output| output.size)
.unwrap_or((0, 0)); // in this case the output will be removed.
// and draw in sync with our monitor
surface
.render(|renderer, frame| {
frame.clear([0.8, 0.8, 0.9, 1.0])?;
// draw the surfaces
draw_windows(
renderer,
frame,
#[cfg(feature = "egl")]
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,
)?;
let dmabuf = surface.next_buffer()?;
renderer.bind(dmabuf)?;
// and draw to our buffer
match renderer
.render(
width,
height,
Transform::Flipped180, // Scanout is rotated
|renderer, frame| {
frame.clear([0.8, 0.8, 0.9, 1.0])?;
// draw the surfaces
draw_windows(
renderer,
frame,
#[cfg(feature = "egl")]
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(
// 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,
#[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,
frame,
wl_surface,
@ -767,60 +785,34 @@ fn render_surface(
*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(
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(())
})
Ok(())
},
)
.map_err(Into::<SwapBuffersError>::into)
.and_then(|x| x)
.map_err(Into::<SwapBuffersError>::into)
{
Ok(()) => surface.queue_buffer().map_err(Into::<SwapBuffersError>::into),
Err(err) => Err(err),
}
}
fn schedule_initial_render<Data: 'static>(
renderer: Rc<RefCell<RenderSurface>>,
surface: Rc<RefCell<RenderSurface>>,
renderer: Rc<RefCell<Gles2Renderer>>,
evt_handle: &LoopHandle<'static, Data>,
logger: ::slog::Logger,
) {
let result = {
let mut surface = surface.borrow_mut();
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))
initial_render(&mut *surface, &mut *renderer)
};
if let Err(err) = result {
match err {
@ -829,9 +821,25 @@ fn schedule_initial_render<Data: 'static>(
// 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));
evt_handle.insert_idle(move |_| schedule_initial_render(surface, renderer, &handle, logger));
}
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(self) mod error;
#[cfg(feature = "backend_gbm")]
mod render;
#[cfg(feature = "backend_session")]
pub(self) mod session;
pub(self) mod surface;
@ -71,7 +69,7 @@ pub(self) mod surface;
pub use device::{DevPath, DrmDevice, DrmEvent};
pub use error::Error as DrmError;
#[cfg(feature = "backend_gbm")]
pub use render::{DrmRenderSurface, Error as DrmRenderError};
pub use surface::gbm::{Error as GbmBufferedSurfaceError, GbmBufferedSurface};
pub use surface::DrmSurface;
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::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::{
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;
/// 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
/// of a single renderer. In these cases `DrmRenderSurface` provides a way to quickly
/// get up and running without manually handling and binding buffers.
pub struct DrmRenderSurface<D: AsRawFd + 'static, A: Allocator<B>, R: Bind<Dmabuf>, B: Buffer> {
buffers: Buffers<D, B>,
swapchain: Swapchain<A, B, (Dmabuf, BufferObject<FbHandle<D>>)>,
renderer: R,
pub struct GbmBufferedSurface<D: AsRawFd + 'static> {
buffers: Buffers<D>,
swapchain: Swapchain<GbmDevice<D>, BufferObject<()>, (Dmabuf, FbHandle<D>)>,
drm: Arc<DrmSurface<D>>,
}
impl<D, A, B, R, E1, E2, E3> DrmRenderSurface<D, A, R, B>
impl<D> GbmBufferedSurface<D>
where
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
/// of a surface, an allocator and a renderer.
@ -48,12 +41,15 @@ where
/// The function will futhermore check for compatibility by enumerating
/// supported pixel formats and choosing an appropriate one.
#[allow(clippy::type_complexity)]
pub fn new<L: Into<Option<::slog::Logger>>>(
pub fn new<L>(
drm: DrmSurface<D>,
allocator: A,
mut renderer: R,
allocator: GbmDevice<D>,
mut renderer_formats: HashSet<Format>,
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 do not want something like Abgr4444, which looses color information
// - some formats might perform terribly
@ -73,15 +69,10 @@ where
.filter(|fmt| fmt.code == code)
.cloned()
.collect::<HashSet<_>>();
let renderer_formats = Bind::<Dmabuf>::supported_formats(&renderer)
.expect("Dmabuf renderer without formats")
.iter()
.filter(|fmt| fmt.code == code)
.cloned()
.collect::<HashSet<_>>();
renderer_formats.retain(|fmt| fmt.code == code);
debug!(logger, "Remaining plane formats: {:?}", plane_formats);
debug!(logger, "Remaining renderer formats: {:?}", renderer_formats);
trace!(logger, "Plane formats: {:?}", plane_formats);
trace!(logger, "Renderer formats: {:?}", renderer_formats);
debug!(
logger,
"Remaining intersected formats: {:?}",
@ -95,6 +86,7 @@ where
} else if renderer_formats.is_empty() {
return Err(Error::NoSupportedRendererFormat);
}
let formats = {
// Special case: if a format supports explicit LINEAR (but no implicit Modifiers)
// and the other doesn't support any modifier, force Implicit.
@ -127,8 +119,7 @@ where
let mode = drm.pending_mode();
let gbm = unsafe { GbmDevice::new_from_fd(drm.as_raw_fd())? };
let mut swapchain = Swapchain::new(
let mut swapchain: Swapchain<GbmDevice<D>, BufferObject<()>, (Dmabuf, FbHandle<D>)> = Swapchain::new(
allocator,
mode.size().0 as u32,
mode.size().1 as u32,
@ -137,116 +128,63 @@ where
);
// Test format
let buffer = swapchain.acquire().map_err(Error::SwapchainError)?.unwrap();
let dmabuf = buffer.export().map_err(Error::AsDmabufError)?;
let buffer = swapchain.acquire()?.unwrap();
let format = Format {
code,
modifier: buffer.format().modifier, // no guarantee
// that this is stable across allocations, but
// we want to print that here for debugging proposes.
// It has no further use.
modifier: buffer.modifier().unwrap(), // no guarantee
// that this is stable across allocations, but
// we want to print that here for debugging proposes.
// It has no further use.
};
match renderer
.bind(dmabuf.clone())
.map_err(Error::<E1, E2, E3>::RenderError)
.and_then(|_| {
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));
let fb = attach_framebuffer(&drm, &*buffer)?;
let dmabuf = buffer.export()?;
let handle = fb.fb;
*buffer.userdata() = Some((dmabuf, fb));
match drm.test_buffer(fb, &mode, true) {
Ok(_) => {
debug!(logger, "Success, choosen format: {:?}", format);
let buffers = Buffers::new(drm.clone(), gbm, buffer);
Ok(DrmRenderSurface {
buffers,
swapchain,
renderer,
drm,
})
}
Err(err) => {
warn!(
logger,
"Mode-setting failed with automatically selected buffer format {:?}: {}",
format,
err
);
Err(err).map_err(Into::into)
}
}
match drm.test_buffer(handle, &mode, true) {
Ok(_) => {
debug!(logger, "Choosen format: {:?}", format);
let buffers = Buffers::new(drm.clone(), buffer);
Ok(GbmBufferedSurface {
buffers,
swapchain,
drm,
})
}
Err(err) => {
warn!(
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
pub fn renderer(&mut self) -> &mut R {
&mut self.renderer
/// Retrieves the next buffer to be rendered into.
///
/// *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
/// and this surface set a the rendering target.
pub fn render<F, S>(&mut self, rendering: F) -> Result<S, Error<E1, E2, E3>>
where
F: FnOnce(&mut R, &mut <R as Renderer>::Frame) -> S,
{
let mode = self.drm.pending_mode();
let (width, height) = (mode.size().0 as u32, mode.size().1 as u32);
let slot = self
.swapchain
.acquire()
.map_err(Error::SwapchainError)?
.ok_or(Error::NoFreeSlotsError)?;
let dmabuf = match &*slot.userdata() {
Some((buf, _)) => buf.clone(),
None => (*slot).export().map_err(Error::AsDmabufError)?,
};
self.renderer.bind(dmabuf.clone()).map_err(Error::RenderError)?;
let result = self.renderer
.render(
width, height,
Transform::Flipped180 /* TODO: add Add<Transform> implementation to add and correct _transform here */,
rendering,
)
.map_err(Error::RenderError)?;
match self.buffers.queue::<E1, E2, E3>(slot, dmabuf) {
Ok(()) => {}
Err(Error::DrmError(drm)) => return Err(drm.into()),
Err(Error::GbmError(err)) => return Err(err.into()),
_ => unreachable!(),
}
Ok(result)
/// Queues the current buffer for rendering.
///
/// *Note*: This function needs to be followed up with [`GbmBufferedSurface::frame_submitted`]
/// when a vblank event is received, that denotes successful scanout of the buffer.
/// Otherwise the underlying swapchain will eventually run out of buffers.
pub fn queue_buffer(&mut self) -> Result<(), Error> {
self.buffers.queue()
}
/// Marks the current frame as submitted.
///
/// Needs to be called, after the vblank event of the matching [`DrmDevice`](super::DrmDevice)
/// was received after calling [`DrmRenderSurface::render`] on this surface.
/// Otherwise the rendering will run out of buffers eventually.
pub fn frame_submitted(&mut self) -> Result<(), Error<E1, E2, E3>> {
/// *Note*: Needs to be called, after the vblank event of the matching [`DrmDevice`](super::DrmDevice)
/// was received after calling [`GbmBufferedSurface::queue_buffer`] on this surface.
/// Otherwise the underlying swapchain will run out of buffers eventually.
pub fn frame_submitted(&mut self) -> Result<(), Error> {
self.buffers.submitted()
}
@ -266,7 +204,7 @@ where
}
/// 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> {
self.drm.pending_connectors()
}
@ -283,13 +221,13 @@ where
/// (e.g. no suitable [`encoder`](drm::control::encoder) may be found)
/// or is not compatible with the currently pending
/// [`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)
}
/// Tries to mark a [`connector`](drm::control::connector)
/// 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)
}
@ -299,7 +237,7 @@ where
/// (e.g. no suitable [`encoder`](drm::control::encoder) may be found)
/// or is not compatible with the currently pending
/// [`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)
}
@ -321,7 +259,7 @@ where
/// Fails if the mode is not compatible with the underlying
/// [`crtc`](drm::control::crtc) or any of the
/// 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)
}
}
@ -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> {
gbm: GbmDevice<gbm::FdWrapper>,
struct Buffers<D: AsRawFd + 'static> {
drm: Arc<DrmSurface<D>>,
_current_fb: DmabufSlot<B, D>,
pending_fb: Option<DmabufSlot<B, D>>,
queued_fb: Option<DmabufSlot<B, D>>,
_current_fb: DmabufSlot<D>,
pending_fb: Option<DmabufSlot<D>>,
queued_fb: Option<DmabufSlot<D>>,
next_fb: Option<DmabufSlot<D>>,
}
impl<D, B> Buffers<D, B>
impl<D> Buffers<D>
where
B: Buffer + AsDmabuf,
D: AsRawFd + 'static,
{
pub fn new(
drm: Arc<DrmSurface<D>>,
gbm: GbmDevice<gbm::FdWrapper>,
slot: Slot<B, (Dmabuf, BufferObject<FbHandle<D>>)>,
) -> Buffers<D, B> {
pub fn new(drm: Arc<DrmSurface<D>>, slot: DmabufSlot<D>) -> Buffers<D> {
Buffers {
drm,
gbm,
_current_fb: slot,
pending_fb: None,
queued_fb: None,
next_fb: None,
}
}
pub fn queue<E1, E2, E3>(
pub fn next(
&mut self,
slot: Slot<B, (Dmabuf, BufferObject<FbHandle<D>>)>,
dmabuf: Dmabuf,
) -> Result<(), Error<E1, E2, E3>>
where
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));
swapchain: &mut Swapchain<GbmDevice<D>, BufferObject<()>, (Dmabuf, FbHandle<D>)>,
) -> Result<Dmabuf, Error> {
if let Some(slot) = self.next_fb.as_ref() {
return Ok(slot.userdata().as_ref().unwrap().0.clone());
}
self.queued_fb = Some(slot);
if self.pending_fb.is_none() {
let slot = swapchain.acquire()?.ok_or(Error::NoFreeSlotsError)?;
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()
} else {
Ok(())
}
}
pub fn submitted<E1, E2, E3>(&mut self) -> Result<(), Error<E1, E2, E3>>
where
E1: std::error::Error + 'static,
E2: std::error::Error + 'static,
E3: std::error::Error + 'static,
{
pub fn submitted(&mut self) -> Result<(), Error> {
if self.pending_fb.is_none() {
return Ok(());
}
@ -407,23 +346,10 @@ where
}
}
fn submit<E1, E2, E3>(&mut self) -> Result<(), Error<E1, E2, E3>>
where
E1: std::error::Error + 'static,
E2: std::error::Error + 'static,
E3: std::error::Error + 'static,
{
fn submit(&mut self) -> Result<(), Error> {
// 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 fb = slot
.userdata()
.as_ref()
.unwrap()
.1
.userdata()
.unwrap()
.unwrap()
.fb;
let fb = slot.userdata().as_ref().unwrap().1.fb;
let flip = if self.drm.commit_pending() {
self.drm.commit([(fb, self.drm.plane())].iter(), true)
@ -437,19 +363,10 @@ where
}
}
fn import_dmabuf<A, E1, E2, E3>(
drm: &Arc<DrmSurface<A>>,
gbm: &GbmDevice<gbm::FdWrapper>,
buffer: &Dmabuf,
) -> Result<BufferObject<FbHandle<A>>, Error<E1, E2, E3>>
fn attach_framebuffer<A>(drm: &Arc<DrmSurface<A>>, bo: &BufferObject<()>) -> Result<FbHandle<A>, Error>
where
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() {
Modifier::Invalid => None,
x => Some(x),
@ -468,15 +385,15 @@ where
if num > 2 { 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 {
drm.add_planar_framebuffer(&bo, &[None, None, None, None], 0)
drm.add_planar_framebuffer(bo, &[None, None, None, None], 0)
} {
Ok(fb) => fb,
Err(source) => {
// We only support this as a fallback of last resort for ARGB8888 visuals,
// 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 {
errmsg: "Failed to add framebuffer",
dev: drm.dev_path(),
@ -484,7 +401,7 @@ where
}));
}
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 {
errmsg: "Failed to add framebuffer",
dev: drm.dev_path(),
@ -492,19 +409,12 @@ where
})?
}
};
bo.set_userdata(FbHandle { drm: drm.clone(), fb }).unwrap();
Ok(bo)
Ok(FbHandle { drm: drm.clone(), fb })
}
/// Errors thrown by a [`DrmRenderSurface`]
/// Errors thrown by a [`GbmBufferedSurface`]
#[derive(Debug, thiserror::Error)]
pub enum Error<E1, E2, E3>
where
E1: std::error::Error + 'static,
E2: std::error::Error + 'static,
E3: std::error::Error + 'static,
{
pub enum Error {
/// No supported pixel format for the given plane could be determined
#[error("No supported plane buffer format found")]
NoSupportedPlaneFormat,
@ -526,24 +436,13 @@ where
/// Error importing the rendered buffer to libgbm for scan-out
#[error("The underlying gbm device encounted an error: {0}")]
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("The allocated buffer could not be exported as a dmabuf: {0}")]
AsDmabufError(#[source] E2),
/// Error during rendering
#[error("The renderer encounted an error: {0}")]
RenderError(#[source] E3),
AsDmabufError(#[from] GbmConvertError),
}
impl<
E1: std::error::Error + 'static,
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 {
impl From<Error> for SwapBuffersError {
fn from(err: Error) -> SwapBuffersError {
match err {
x @ Error::NoSupportedPlaneFormat
| x @ Error::NoSupportedRendererFormat
@ -552,9 +451,7 @@ impl<
x @ Error::NoFreeSlotsError => SwapBuffersError::TemporaryFailure(Box::new(x)),
Error::DrmError(err) => err.into(),
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::RenderError(err) => err.into(),
}
}
}

View File

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