use std::{ mem, sync::{mpsc::Receiver, Weak}, }; use drm_fourcc::DrmFourcc; use tracing::instrument; use x11rb::{connection::Connection, protocol::xproto::PixmapWrapper, rust_connection::RustConnection}; use crate::{ backend::{ allocator::{ dmabuf::{AnyError, Dmabuf}, Allocator, Slot, Swapchain, }, x11::{buffer::PixmapWrapperExt, window_inner::WindowInner, AllocateBuffersError, Window}, }, utils::{Logical, Size}, }; use super::{WindowTemporary, X11Error}; /// An error that may occur when presenting. #[derive(Debug, thiserror::Error)] pub enum PresentError { /// The dmabuf being presented has too many planes. #[error("The Dmabuf had too many planes")] TooManyPlanes, /// Duplicating the dmabuf handles failed. #[error("Duplicating the file descriptors for the dmabuf handles failed")] DupFailed(String), /// The format dmabuf presented does not match the format of the window. #[error("Buffer had incorrect format, expected: {0}")] IncorrectFormat(DrmFourcc), } /// An X11 surface which uses GBM to allocate and present buffers. #[derive(Debug)] pub struct X11Surface { pub(crate) connection: Weak, pub(crate) window: Weak, pub(crate) resize: Receiver>, pub(crate) swapchain: Swapchain + 'static>>, pub(crate) format: DrmFourcc, pub(crate) width: u16, pub(crate) height: u16, pub(crate) buffer: Option>, pub(crate) span: tracing::Span, } impl X11Surface { /// Returns the window the surface presents to. /// /// This will return [`None`] if the window has been destroyed. pub fn window(&self) -> Option + '_> { self.window.upgrade().map(Window).map(WindowTemporary) } /// Returns the format of the buffers the surface accepts. pub fn format(&self) -> DrmFourcc { self.format } /// Returns the next buffer that will be presented to the Window and its age. /// /// You may bind this buffer to a renderer to render. /// This function will return the same buffer until [`submit`](Self::submit) is called /// or [`reset_buffers`](Self::reset_buffers) is used to reset the buffers. #[instrument(level = "trace", parent = &self.span, skip(self))] #[profiling::function] pub fn buffer(&mut self) -> Result<(Dmabuf, u8), AllocateBuffersError> { if let Some(new_size) = self.resize.try_iter().last() { self.resize(new_size); } if self.buffer.is_none() { self.buffer = Some( self.swapchain .acquire() .map_err(Into::::into)? .ok_or(AllocateBuffersError::NoFreeSlots)?, ); } let slot = self.buffer.as_ref().unwrap(); let age = slot.age(); Ok(((*slot).clone(), age)) } /// Consume and submit the buffer to the window. #[instrument(level = "trace", parent = &self.span, skip(self))] #[profiling::function] pub fn submit(&mut self) -> Result<(), X11Error> { if let Some(connection) = self.connection.upgrade() { // Get a new buffer let mut next = self .swapchain .acquire() .map_err(Into::::into)? .ok_or(AllocateBuffersError::NoFreeSlots)?; // Swap the buffers if let Some(current) = self.buffer.as_mut() { mem::swap(&mut next, current); } { let window = self.window().ok_or(AllocateBuffersError::WindowDestroyed)?; let pixmap = PixmapWrapper::with_dmabuf(&*connection, window.as_ref(), &next)?; // Now present the current buffer let _ = pixmap.present(&*connection, window.as_ref())?; } self.swapchain.submitted(&next); // Flush the connection after presenting to the window to ensure we don't run out of buffer space in the X11 connection. let _ = connection.flush(); } Ok(()) } /// Resets the internal buffers. #[instrument(level = "trace", parent = &self.span, skip(self))] pub fn reset_buffers(&mut self) { self.swapchain.reset_buffers(); self.buffer = None; } /// Reset the age for all internal buffers. /// /// This can be used to clear the damage history. #[instrument(level = "trace", parent = &self.span, skip(self))] pub fn reset_buffer_ages(&mut self) { self.swapchain.reset_buffer_ages(); } fn resize(&mut self, size: Size) { self.swapchain.resize(size.w as u32, size.h as u32); self.buffer = None; self.width = size.w; self.height = size.h; } }