use drmkit_core::Device;
use drmkit_gbm::{GbmBuffer, GbmDevice};
use drmkit_scene::{
AcquiredBuffer, BindingModel, DmaBufDesc, LayerBufferSource, SourceError, SourceFormat,
};
use crate::{ExternalDmaBufSource, ExternalError, ExternalPlane};
#[derive(Debug)]
pub struct GbmBufferSource {
buffer: GbmBuffer,
imported: ExternalDmaBufSource,
dma_buf: std::os::fd::OwnedFd,
pitch: u32,
format: SourceFormat,
gbm: GbmDevice,
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum GbmSourceError {
#[error("allocating a GBM buffer: {0}")]
Allocate(#[from] drmkit_gbm::GbmError),
#[error("importing the GBM buffer: {0}")]
Import(#[from] ExternalError),
#[error("the driver allocated {planes} planes; only single-plane buffers are supported")]
MultiPlanar {
planes: u32,
},
}
impl GbmBufferSource {
pub fn create(
device: &Device,
width: u32,
height: u32,
fourcc: u32,
modifiers: &[u64],
) -> Result<Self, GbmSourceError> {
let gbm = GbmDevice::new(device)?;
let buffer = GbmBuffer::create_with_modifiers(&gbm, width, height, fourcc, modifiers)?;
let (imported, dma_buf, format) = Self::import(device, &buffer)?;
Ok(Self {
pitch: buffer.stride(),
buffer,
imported,
dma_buf,
format,
gbm,
})
}
fn import(
device: &Device,
buffer: &GbmBuffer,
) -> Result<(ExternalDmaBufSource, std::os::fd::OwnedFd, SourceFormat), GbmSourceError> {
let planes = buffer.plane_count();
if planes != 1 {
return Err(GbmSourceError::MultiPlanar { planes });
}
let format = SourceFormat {
fourcc: buffer.fourcc(),
modifier: buffer.modifier(),
width: buffer.width(),
height: buffer.height(),
};
let dma_buf = buffer.export()?;
let source = ExternalDmaBufSource::create(
device,
format,
&[ExternalPlane {
fd: std::os::fd::AsFd::as_fd(&dma_buf),
offset: 0,
pitch: buffer.stride(),
}],
None,
)?;
Ok((source, dma_buf, format))
}
#[must_use]
pub const fn buffer(&self) -> &GbmBuffer {
&self.buffer
}
#[must_use]
pub fn fb_id(&self) -> Option<u32> {
self.imported.fb_id()
}
}
impl LayerBufferSource for GbmBufferSource {
fn acquire(&mut self) -> Result<AcquiredBuffer, SourceError> {
self.imported.acquire()
}
fn release(&mut self, acquired: AcquiredBuffer) {
self.imported.release(acquired);
}
fn binding_model(&self) -> BindingModel {
BindingModel::SceneSubmitsFbId
}
fn format(&self) -> SourceFormat {
self.format
}
fn export_dma_buf(&mut self) -> Result<DmaBufDesc<'_>, SourceError> {
Ok(DmaBufDesc {
fds: vec![std::os::fd::AsFd::as_fd(&self.dma_buf)],
offsets: vec![0],
pitches: vec![self.pitch],
format: self.format,
})
}
fn on_session_paused(&mut self) {
self.imported.on_session_paused();
}
fn on_session_resumed(&mut self, device: &Device) -> Result<(), SourceError> {
let SourceFormat {
fourcc,
modifier,
width,
height,
} = self.format;
let gbm = GbmDevice::new(device).map_err(|error| resume_error(&error.into()))?;
let buffer = GbmBuffer::create_with_modifiers(&gbm, width, height, fourcc, &[modifier])
.map_err(|error| resume_error(&error.into()))?;
let (imported, dma_buf, format) =
Self::import(device, &buffer).map_err(|error| resume_error(&error))?;
self.imported.forget();
self.gbm = gbm;
self.pitch = buffer.stride();
self.buffer = buffer;
self.imported = imported;
self.dma_buf = dma_buf;
self.format = format;
Ok(())
}
}
fn resume_error(error: &GbmSourceError) -> SourceError {
match error {
GbmSourceError::Allocate(drmkit_gbm::GbmError::Io(errno)) => SourceError::Failed(*errno),
GbmSourceError::Import(error) => crate::external::source_error(error),
GbmSourceError::MultiPlanar { .. } => SourceError::Unsupported,
_ => SourceError::Failed(rustix::io::Errno::IO),
}
}
#[cfg(test)]
mod resume_error_tests {
use super::{GbmSourceError, resume_error};
use drmkit_scene::SourceError;
use rustix::io::Errno;
#[test]
fn a_failed_reallocation_keeps_its_errno() {
let cases = [
(
GbmSourceError::Allocate(drmkit_gbm::GbmError::Io(Errno::NOMEM)),
Errno::NOMEM,
),
(
GbmSourceError::Allocate(drmkit_gbm::GbmError::Allocation("refused".to_owned())),
Errno::IO,
),
(
GbmSourceError::Import(crate::ExternalError::Io(Errno::NOSPC)),
Errno::NOSPC,
),
];
for (error, errno) in cases {
assert!(
matches!(resume_error(&error), SourceError::Failed(got) if got == errno),
"{error}"
);
}
assert!(matches!(
resume_error(&GbmSourceError::MultiPlanar { planes: 2 }),
SourceError::Unsupported
));
}
}