use drm_fourcc::DrmFourcc;
use edgefirst_egl::{self as egl, Attrib};
use edgefirst_tensor::{ColorEncoding, ColorRange, PixelFormat, PixelLayout, Tensor, TensorTrait};
use std::os::fd::AsRawFd;
use std::os::unix::io::RawFd;
use super::context::egl_ext;
use super::fourcc::pixel_format_to_drm;
use super::processor::DMA_IMPORT_OFFSET_ALIGN;
use crate::colorimetry::resolve_colorimetry;
use crate::Error;
#[derive(Debug, Clone)]
pub(super) struct DmaImportAttrs {
pub width: usize,
pub height: usize,
pub drm_fourcc: DrmFourcc,
pub plane0_fd: RawFd,
pub plane0_pitch: usize,
pub plane0_offset: usize,
pub x_shift_px: u32,
pub plane1: Option<DmaPlane1Attrs>,
pub is_yuv: bool,
pub yuv_encoding: ColorEncoding,
pub yuv_range: ColorRange,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct DmaPlane1Attrs {
pub fd: RawFd,
pub pitch: usize,
pub offset: usize,
}
pub(super) fn aligned_source_base(offset: usize, bpp: usize, align: usize) -> Option<(usize, u32)> {
if bpp == 0 || align == 0 || !offset.is_multiple_of(bpp) {
return None;
}
let mut a = align;
let mut b = bpp;
while b != 0 {
let t = a % b;
a = b;
b = t;
}
let step = (align / a).checked_mul(bpp)?;
let base = offset - offset % step;
Some((base, u32::try_from((offset - base) / bpp).ok()?))
}
pub(super) fn rebase_fits_pitch(
width_px: usize,
x_shift_px: u32,
bpp: usize,
plane0_pitch: usize,
) -> bool {
width_px
.checked_add(x_shift_px as usize)
.and_then(|w| w.checked_mul(bpp))
.is_some_and(|row| row <= plane0_pitch)
}
fn rebase_bpp(src_fmt: PixelFormat) -> Option<usize> {
matches!(
src_fmt,
PixelFormat::Rgba | PixelFormat::Bgra | PixelFormat::Rgb | PixelFormat::Grey
)
.then(|| src_fmt.channels())
}
fn import_plane0_channels(src_fmt: PixelFormat) -> usize {
if src_fmt == PixelFormat::Nv12 || src_fmt.layout() == PixelLayout::Planar {
1
} else {
src_fmt.channels()
}
}
fn tight_plane0_pitch(src_fmt: PixelFormat, width_px: usize, channels: usize) -> usize {
if src_fmt == PixelFormat::Nv12 {
width_px
} else {
width_px * channels
}
}
pub(super) fn resolve_source_plane0(
src_fmt: PixelFormat,
width_px: usize,
plane0_pitch: usize,
plane_offset: usize,
) -> (usize, u32) {
let Some(bpp) = rebase_bpp(src_fmt) else {
return (plane_offset, 0);
};
if plane_offset.is_multiple_of(DMA_IMPORT_OFFSET_ALIGN) {
return (plane_offset, 0);
}
aligned_source_base(plane_offset, bpp, DMA_IMPORT_OFFSET_ALIGN)
.filter(|&(_, shift)| rebase_fits_pitch(width_px, shift, bpp, plane0_pitch))
.unwrap_or((plane_offset, 0))
}
pub(super) fn resolved_source_plane0_offset(src: &Tensor<u8>, src_fmt: PixelFormat) -> usize {
let width = src.width().unwrap_or(0);
let pitch = src
.effective_row_stride()
.unwrap_or_else(|| tight_plane0_pitch(src_fmt, width, import_plane0_channels(src_fmt)));
resolve_source_plane0(src_fmt, width, pitch, src.plane_offset().unwrap_or(0)).0
}
impl DmaImportAttrs {
pub fn from_tensor(
src: &Tensor<u8>,
src_fmt: PixelFormat,
for_dst: bool,
) -> Result<Self, Error> {
let view_origin = if for_dst { src.view_origin() } else { None };
let (src_w, src_h) = match view_origin {
Some(vo) => (vo.parent_width, vo.parent_height),
None => (
src.width().ok_or(Error::NotAnImage)?,
src.height().ok_or(Error::NotAnImage)?,
),
};
let src_channels = src_fmt.channels();
let cm = resolve_colorimetry(src.colorimetry(), src.height());
let yuv_encoding = cm.encoding.unwrap_or(ColorEncoding::Bt709);
let yuv_range = cm.range.unwrap_or(ColorRange::Limited);
let (width, height, drm_fourcc, channels) = if src_fmt == PixelFormat::Nv12 {
if !src_w.is_multiple_of(2) {
return Err(Error::NotSupported(format!(
"EGLImage requires even width for {src_fmt} (4:2:0 chroma), got {src_w}"
)));
}
(src_w, src_h, pixel_format_to_drm(src_fmt)?, 1usize)
} else if src_fmt.layout() == PixelLayout::Planar {
if !src_w.is_multiple_of(16) {
return Err(Error::NotSupported(format!(
"EGLImage requires width divisible by 16 for {src_fmt}, got {src_w}"
)));
}
match src_fmt {
PixelFormat::PlanarRgb => (src_w, src_h * 3, DrmFourcc::R8, 1),
_ => {
return Err(Error::NotSupported(format!(
"Unsupported Planar format {src_fmt:?}"
)));
}
}
} else {
let needs_width_mod_4 = !matches!(src_fmt, PixelFormat::Rgba | PixelFormat::Bgra);
if needs_width_mod_4 && !src_w.is_multiple_of(4) {
return Err(Error::NotSupported(format!(
"EGLImage requires width divisible by 4 for {src_fmt}, got {src_w}"
)));
}
(src_w, src_h, pixel_format_to_drm(src_fmt)?, src_channels)
};
debug_assert_eq!(
channels,
import_plane0_channels(src_fmt),
"{src_fmt:?}: the import's plane-0 channel count and the gate's must agree"
);
let fd = src
.dmabuf()
.map_err(|e| {
Error::NotImplemented(format!(
"OpenGL EGLImage requires DMA tensor, got {:?} ({e})",
src.memory()
))
})?
.as_raw_fd();
let uv_fd = if src.is_multiplane() {
let chroma = src.chroma().unwrap();
let chroma_fd = chroma.dmabuf().map_err(|e| {
Error::NotImplemented(format!("Multiplane chroma tensor must be DMA-backed ({e})"))
})?;
Some(chroma_fd.as_raw_fd())
} else {
None
};
let plane0_pitch = match view_origin {
Some(vo) => vo.parent_row_stride,
None => src
.effective_row_stride()
.unwrap_or_else(|| tight_plane0_pitch(src_fmt, width, channels)),
};
let plane0_offset = if view_origin.is_some() {
0
} else {
src.plane_offset().unwrap_or(0)
};
let (plane0_offset, x_shift_px) = if for_dst {
(plane0_offset, 0)
} else {
resolve_source_plane0(src_fmt, width, plane0_pitch, plane0_offset)
};
let width = width + x_shift_px as usize;
let plane1 = if src_fmt == PixelFormat::Nv12 {
let chroma_h = height.div_ceil(2);
let default_chroma_pitch = plane0_pitch;
let (plane1_fd, uv_offset) = if let Some(chroma_fd) = uv_fd {
let chroma_offset = src.chroma().and_then(|c| c.plane_offset()).unwrap_or(0);
(chroma_fd, chroma_offset)
} else {
(fd, plane0_offset + plane0_pitch * height)
};
let plane1_pitch = if let Some(chroma) = src.chroma() {
chroma
.effective_row_stride()
.unwrap_or(default_chroma_pitch)
} else {
default_chroma_pitch
};
let chroma_data = plane1_pitch.saturating_mul(chroma_h);
let required = uv_offset.saturating_add(chroma_data);
let buf_size = {
let mut stat: libc::stat = unsafe { std::mem::zeroed() };
if unsafe { libc::fstat(plane1_fd, &mut stat) } == 0 && stat.st_size > 0 {
Some(stat.st_size as usize)
} else {
None
}
};
if let Some(sz) = buf_size {
if required > sz {
return Err(Error::InvalidShape(format!(
"{src_fmt} chroma plane offset {uv_offset} + required {chroma_data} bytes \
exceeds chroma fd buffer size {sz} — the chroma PlaneDescriptor \
likely references the wrong fd (e.g. Y-only buffer with the \
vmeta global offset instead of the UV buffer's own fd)",
)));
}
}
Some(DmaPlane1Attrs {
fd: plane1_fd,
pitch: plane1_pitch,
offset: uv_offset,
})
} else {
None
};
Ok(DmaImportAttrs {
width,
height,
drm_fourcc,
plane0_fd: fd,
plane0_pitch,
plane0_offset,
x_shift_px,
plane1,
is_yuv: src_fmt.is_yuv(),
yuv_encoding,
yuv_range,
})
}
pub fn from_tensor_nv_r8(src: &Tensor<u8>, src_fmt: PixelFormat) -> Result<Self, Error> {
let src_w = src.width().ok_or(Error::NotAnImage)?;
let src_h = src.height().ok_or(Error::NotAnImage)?;
if !matches!(
src_fmt,
PixelFormat::Nv12 | PixelFormat::Nv16 | PixelFormat::Nv24
) {
return Err(Error::NotSupported(format!(
"from_tensor_nv_r8 only supports NV12/NV16/NV24, got {src_fmt:?}"
)));
}
if src.is_multiplane() {
return Err(Error::NotSupported(
"from_tensor_nv_r8: multiplane tensor not supported; use 2-plane Path A".into(),
));
}
let tex_width = src.effective_row_stride().unwrap_or(src_w);
let combined_h = src_fmt.combined_plane_height(src_h).ok_or_else(|| {
Error::NotImplemented(format!("Path-B R8 import: {src_fmt:?} is not semi-planar"))
})?;
let fd = src
.dmabuf()
.map_err(|e| {
Error::NotImplemented(format!(
"OpenGL Path-B R8 import requires DMA tensor, got {:?} ({e})",
src.memory()
))
})?
.as_raw_fd();
let plane0_offset = src.plane_offset().unwrap_or(0);
Ok(DmaImportAttrs {
width: tex_width,
height: combined_h,
drm_fourcc: DrmFourcc::R8,
plane0_fd: fd,
plane0_pitch: tex_width,
plane0_offset,
x_shift_px: 0,
plane1: None,
is_yuv: false, yuv_encoding: ColorEncoding::Bt709,
yuv_range: ColorRange::Limited,
})
}
pub fn from_tensor_nv_chroma_gr88(
src: &Tensor<u8>,
src_fmt: PixelFormat,
) -> Result<Self, Error> {
let src_w = src.width().ok_or(Error::NotAnImage)?;
let src_h = src.height().ok_or(Error::NotAnImage)?;
let layout = src_fmt.chroma_layout().ok_or_else(|| {
Error::NotSupported(format!("NV chroma import: {src_fmt:?} is not semi-planar"))
})?;
if src.is_multiplane() {
return Err(Error::NotSupported(
"NV chroma import: multiplane tensor not supported".into(),
));
}
let tex_width = src.effective_row_stride().unwrap_or(src_w);
let fd = src
.dmabuf()
.map_err(|e| {
Error::NotImplemented(format!(
"NV chroma import requires a DMA tensor, got {:?} ({e})",
src.memory()
))
})?
.as_raw_fd();
let (sx, sy) = (layout.shift_x, layout.shift_y);
Ok(DmaImportAttrs {
width: src_w.div_ceil(1 << sx),
height: src_h.div_ceil(1 << sy),
drm_fourcc: DrmFourcc::Gr88,
plane0_fd: fd,
plane0_pitch: tex_width * layout.uv_rows_per_luma,
plane0_offset: src.plane_offset().unwrap_or(0) + tex_width * src_h,
x_shift_px: 0,
plane1: None,
is_yuv: false,
yuv_encoding: ColorEncoding::Bt709,
yuv_range: ColorRange::Limited,
})
}
pub fn to_egl_attribs(&self) -> Vec<Attrib> {
let mut attrs = vec![
egl_ext::LINUX_DRM_FOURCC as Attrib,
self.drm_fourcc as Attrib,
edgefirst_egl::WIDTH as Attrib,
self.width as Attrib,
edgefirst_egl::HEIGHT as Attrib,
self.height as Attrib,
egl_ext::DMA_BUF_PLANE0_PITCH as Attrib,
self.plane0_pitch as Attrib,
egl_ext::DMA_BUF_PLANE0_OFFSET as Attrib,
self.plane0_offset as Attrib,
egl_ext::DMA_BUF_PLANE0_FD as Attrib,
self.plane0_fd as Attrib,
egl::IMAGE_PRESERVED as Attrib,
egl::TRUE as Attrib,
];
if let Some(ref p1) = self.plane1 {
attrs.extend_from_slice(&[
egl_ext::DMA_BUF_PLANE1_FD as Attrib,
p1.fd as Attrib,
egl_ext::DMA_BUF_PLANE1_OFFSET as Attrib,
p1.offset as Attrib,
egl_ext::DMA_BUF_PLANE1_PITCH as Attrib,
p1.pitch as Attrib,
]);
}
if self.is_yuv {
let cs_hint = match self.yuv_encoding {
ColorEncoding::Bt601 => egl_ext::ITU_REC601,
ColorEncoding::Bt709 => egl_ext::ITU_REC709,
ColorEncoding::Bt2020 => egl_ext::ITU_REC2020,
_ => egl_ext::ITU_REC709,
};
let range_hint = match self.yuv_range {
ColorRange::Full => egl_ext::YUV_FULL_RANGE,
ColorRange::Limited => egl_ext::YUV_NARROW_RANGE,
_ => egl_ext::YUV_NARROW_RANGE,
};
attrs.extend_from_slice(&[
egl_ext::YUV_COLOR_SPACE_HINT as Attrib,
cs_hint as Attrib,
egl_ext::SAMPLE_RANGE_HINT as Attrib,
range_hint as Attrib,
]);
}
attrs.push(edgefirst_egl::NONE as Attrib);
attrs
}
}
#[cfg(test)]
pub(super) fn egl_attrib_value(attrs: &[Attrib], key: u32) -> Option<Attrib> {
let mut i = 0;
while i + 1 < attrs.len() {
if attrs[i] == key as Attrib {
return Some(attrs[i + 1]);
}
i += 2;
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "dma_test_formats")]
use edgefirst_tensor::{is_dma_available, PlaneDescriptor, TensorMemory};
#[cfg(feature = "dma_test_formats")]
fn alloc_dma(bytes: usize, name: &str) -> Option<Tensor<u8>> {
if !is_dma_available() {
return None;
}
match Tensor::<u8>::new(&[bytes], Some(TensorMemory::DmaBuf), Some(name)) {
Ok(t) if t.memory() == TensorMemory::DmaBuf => Some(t),
_ => None,
}
}
#[cfg(feature = "dma_test_formats")]
fn import_nv12(
luma_pd: PlaneDescriptor,
chroma_pd: Option<PlaneDescriptor>,
width: usize,
height: usize,
) -> Result<edgefirst_tensor::TensorDyn, crate::Error> {
let proc = crate::ImageProcessor::new()?;
proc.import_image(
luma_pd,
chroma_pd,
width,
height,
PixelFormat::Nv12,
edgefirst_tensor::DType::U8,
None,
)
}
#[test]
#[cfg(feature = "dma_test_formats")]
fn from_tensor_source_view_imports_own_region_dst_imports_parent() {
if !is_dma_available() {
crate::test_support::report_skip("from_tensor_source_view... - DMA not available");
return;
}
let parent = match Tensor::<u8>::image_with_stride(
64,
64,
PixelFormat::Rgba,
320,
Some(TensorMemory::DmaBuf),
edgefirst_tensor::CpuAccess::ReadWrite,
) {
Ok(t) => t,
Err(_) => {
crate::test_support::report_skip("image_with_stride DMA unavailable");
return;
}
};
let view = parent
.view(edgefirst_tensor::Region::new(8, 8, 32, 16))
.unwrap();
let s = DmaImportAttrs::from_tensor(&view, PixelFormat::Rgba, false).unwrap();
assert_eq!(
(s.width, s.height),
(32 + 8, 16),
"source view imports its own dimensions, widened by the shift"
);
assert_eq!(s.x_shift_px, 8, "32 bytes of remainder is 8 RGBA pixels");
assert_eq!(
s.plane0_offset,
8 * 320,
"source view imports at the aligned base below its own byte offset"
);
assert_eq!(
s.plane0_offset + s.x_shift_px as usize * 4,
8 * 320 + 8 * 4,
"and the shift closes that base back to the view's own offset"
);
assert_eq!(s.plane0_pitch, 320, "source view keeps the parent pitch");
let d = DmaImportAttrs::from_tensor(&view, PixelFormat::Rgba, true).unwrap();
assert_eq!(
(d.width, d.height),
(64, 64),
"dest view imports the parent dimensions"
);
assert_eq!(
d.plane0_offset, 0,
"dest view imports the parent at offset 0"
);
assert_eq!(d.plane0_pitch, 320, "dest view imports at the parent pitch");
}
#[test]
#[cfg(feature = "dma_test_formats")]
fn test_nv12_true_multiplane_attrs() {
let width: usize = 1920;
let height: usize = 1088;
let stride: usize = 1920;
let luma_bytes = stride * height;
let chroma_bytes = stride * height.div_ceil(2);
let luma_buf = match alloc_dma(luma_bytes, "luma_buf") {
Some(t) => t,
None => {
crate::test_support::report_skip(
"test_nv12_true_multiplane_attrs - DMA not available",
);
return;
}
};
let chroma_buf = match alloc_dma(chroma_bytes, "chroma_buf") {
Some(t) => t,
None => {
crate::test_support::report_skip(
"test_nv12_true_multiplane_attrs - DMA alloc failed",
);
return;
}
};
let luma_fd = luma_buf.dmabuf().unwrap();
let chroma_fd = chroma_buf.dmabuf().unwrap();
let luma_pd = PlaneDescriptor::new(luma_fd).unwrap().with_stride(stride);
let chroma_pd = PlaneDescriptor::new(chroma_fd).unwrap().with_stride(stride);
let tensor = import_nv12(luma_pd, Some(chroma_pd), width, height).unwrap();
let tensor_u8 = tensor.as_u8().unwrap();
let attrs = DmaImportAttrs::from_tensor(tensor_u8, PixelFormat::Nv12, false).unwrap();
let p1 = attrs.plane1.as_ref().expect("NV12 must have plane1");
assert_ne!(
attrs.plane0_fd, p1.fd,
"true multiplane: plane0_fd and plane1_fd must be different"
);
assert_eq!(attrs.plane0_offset, 0, "luma offset must be 0");
assert_eq!(p1.offset, 0, "chroma offset must be 0 for true multiplane");
assert_eq!(attrs.plane0_pitch, stride);
assert_eq!(p1.pitch, stride);
assert_eq!(attrs.width, width);
assert_eq!(attrs.height, height);
assert!(attrs.is_yuv);
assert_eq!(attrs.drm_fourcc, DrmFourcc::Nv12);
}
#[test]
#[cfg(feature = "dma_test_formats")]
fn test_nv12_same_fd_multiplane_attrs() {
let width: usize = 1920;
let height: usize = 1088;
let stride: usize = 1920;
let luma_size = stride * height;
let chroma_size = stride * height.div_ceil(2);
let total_bytes = luma_size + chroma_size;
let buf = match alloc_dma(total_bytes, "shared_buf") {
Some(t) => t,
None => {
crate::test_support::report_skip(
"test_nv12_same_fd_multiplane_attrs - DMA not available",
);
return;
}
};
let fd = buf.dmabuf().unwrap();
let luma_pd = PlaneDescriptor::new(fd)
.unwrap()
.with_stride(stride)
.with_offset(0);
let chroma_pd = PlaneDescriptor::new(fd)
.unwrap()
.with_stride(stride)
.with_offset(luma_size);
let tensor = import_nv12(luma_pd, Some(chroma_pd), width, height).unwrap();
let tensor_u8 = tensor.as_u8().unwrap();
let attrs = DmaImportAttrs::from_tensor(tensor_u8, PixelFormat::Nv12, false).unwrap();
let p1 = attrs.plane1.as_ref().expect("NV12 must have plane1");
assert_ne!(
attrs.plane0_fd, p1.fd,
"same-fd multiplane: raw fds must differ (each is a dup)"
);
assert_eq!(attrs.plane0_offset, 0);
assert_eq!(
p1.offset, luma_size,
"chroma offset must be luma_size for same-fd multiplane"
);
assert_eq!(attrs.plane0_pitch, stride);
assert_eq!(p1.pitch, stride);
assert_eq!(attrs.width, width);
assert_eq!(attrs.height, height);
}
#[test]
#[cfg(feature = "dma_test_formats")]
fn test_nv12_even_width_relaxed_gate() {
let (height, stride) = (64usize, 128usize);
let total = stride * height + stride * height.div_ceil(2);
let buf = match alloc_dma(total, "nv12_even_w") {
Some(t) => t,
None => {
crate::test_support::report_skip(
"test_nv12_even_width_relaxed_gate - DMA not available",
);
return;
}
};
let luma_pd = PlaneDescriptor::new(buf.dmabuf().unwrap())
.unwrap()
.with_stride(stride);
let t66 = import_nv12(luma_pd, None, 66, height).unwrap();
let attrs = DmaImportAttrs::from_tensor(t66.as_u8().unwrap(), PixelFormat::Nv12, false)
.expect("even-but-not-4-aligned NV12 width must import after the %4→%2 relaxation");
assert_eq!(attrs.width, 66);
let luma_pd2 = PlaneDescriptor::new(buf.dmabuf().unwrap())
.unwrap()
.with_stride(stride);
if let Ok(t65) = import_nv12(luma_pd2, None, 65, height) {
assert!(
DmaImportAttrs::from_tensor(t65.as_u8().unwrap(), PixelFormat::Nv12, false)
.is_err(),
"odd-width NV12 must still be rejected by the even-width gate"
);
}
}
#[test]
#[cfg(feature = "dma_test_formats")]
fn test_nv12_contiguous_single_fd_attrs() {
let width: usize = 1920;
let height: usize = 1088;
let stride: usize = 1920;
let total_h = height * 3 / 2; let total_bytes = stride * total_h;
let buf = match alloc_dma(total_bytes, "contiguous_buf") {
Some(t) => t,
None => {
crate::test_support::report_skip(
"test_nv12_contiguous_single_fd_attrs - DMA not available",
);
return;
}
};
let fd = buf.dmabuf().unwrap();
let luma_pd = PlaneDescriptor::new(fd).unwrap().with_stride(stride);
let tensor = import_nv12(luma_pd, None, width, height).unwrap();
let tensor_u8 = tensor.as_u8().unwrap();
let attrs = DmaImportAttrs::from_tensor(tensor_u8, PixelFormat::Nv12, false).unwrap();
let p1 = attrs.plane1.as_ref().expect("NV12 must have plane1");
assert_eq!(
attrs.plane0_fd, p1.fd,
"contiguous: plane0_fd and plane1_fd must be the same raw fd"
);
let expected_uv_offset = stride * height;
assert_eq!(attrs.plane0_offset, 0);
assert_eq!(
p1.offset, expected_uv_offset,
"contiguous UV offset must be stride * height"
);
assert_eq!(attrs.plane0_pitch, stride);
assert_eq!(p1.pitch, stride);
}
#[test]
#[cfg(feature = "dma_test_formats")]
fn test_nv12_contiguous_padded_stride_attrs() {
let width: usize = 1920;
let height: usize = 1080;
let stride: usize = 2048; let total_h = height * 3 / 2;
let total_bytes = stride * total_h;
let buf = match alloc_dma(total_bytes, "padded_buf") {
Some(t) => t,
None => {
crate::test_support::report_skip(
"test_nv12_contiguous_padded_stride_attrs - DMA not available",
);
return;
}
};
let fd = buf.dmabuf().unwrap();
let luma_pd = PlaneDescriptor::new(fd).unwrap().with_stride(stride);
let tensor = import_nv12(luma_pd, None, width, height).unwrap();
let tensor_u8 = tensor.as_u8().unwrap();
let attrs = DmaImportAttrs::from_tensor(tensor_u8, PixelFormat::Nv12, false).unwrap();
let p1 = attrs.plane1.as_ref().unwrap();
let expected_uv_offset = stride * height;
assert_eq!(
p1.offset, expected_uv_offset,
"contiguous padded: UV offset must use stride ({stride}), not width ({width})"
);
assert_eq!(attrs.plane0_pitch, stride);
assert_eq!(p1.pitch, stride, "contiguous: UV pitch must match Y pitch");
}
#[test]
#[cfg(feature = "dma_test_formats")]
fn test_nv12_multiplane_padded_strides_attrs() {
let width: usize = 1920;
let height: usize = 1080;
let luma_stride: usize = 2048;
let chroma_stride: usize = 2048;
let luma_bytes = luma_stride * height;
let chroma_bytes = chroma_stride * height.div_ceil(2);
let luma_buf = match alloc_dma(luma_bytes, "luma_padded") {
Some(t) => t,
None => {
crate::test_support::report_skip(
"test_nv12_multiplane_padded_strides_attrs - DMA not available",
);
return;
}
};
let chroma_buf = match alloc_dma(chroma_bytes, "chroma_padded") {
Some(t) => t,
None => {
crate::test_support::report_skip("DMA alloc failed");
return;
}
};
let luma_fd = luma_buf.dmabuf().unwrap();
let chroma_fd = chroma_buf.dmabuf().unwrap();
let luma_pd = PlaneDescriptor::new(luma_fd)
.unwrap()
.with_stride(luma_stride);
let chroma_pd = PlaneDescriptor::new(chroma_fd)
.unwrap()
.with_stride(chroma_stride);
let tensor = import_nv12(luma_pd, Some(chroma_pd), width, height).unwrap();
let tensor_u8 = tensor.as_u8().unwrap();
let attrs = DmaImportAttrs::from_tensor(tensor_u8, PixelFormat::Nv12, false).unwrap();
let p1 = attrs.plane1.as_ref().unwrap();
assert_eq!(attrs.plane0_pitch, luma_stride);
assert_eq!(p1.pitch, chroma_stride);
assert_eq!(attrs.plane0_offset, 0);
assert_eq!(p1.offset, 0);
}
#[test]
#[cfg(feature = "dma_test_formats")]
fn test_nv12_same_fd_nonzero_luma_offset() {
let width: usize = 640;
let height: usize = 480;
let stride: usize = 640;
let luma_offset: usize = 4096; let luma_size = stride * height;
let chroma_offset = luma_offset + luma_size;
let total_bytes = chroma_offset + stride * height.div_ceil(2);
let buf = match alloc_dma(total_bytes, "offset_buf") {
Some(t) => t,
None => {
crate::test_support::report_skip(
"test_nv12_same_fd_nonzero_luma_offset - DMA not available",
);
return;
}
};
let fd = buf.dmabuf().unwrap();
let luma_pd = PlaneDescriptor::new(fd)
.unwrap()
.with_stride(stride)
.with_offset(luma_offset);
let chroma_pd = PlaneDescriptor::new(fd)
.unwrap()
.with_stride(stride)
.with_offset(chroma_offset);
let tensor = import_nv12(luma_pd, Some(chroma_pd), width, height).unwrap();
let tensor_u8 = tensor.as_u8().unwrap();
let attrs = DmaImportAttrs::from_tensor(tensor_u8, PixelFormat::Nv12, false).unwrap();
let p1 = attrs.plane1.as_ref().unwrap();
assert_eq!(attrs.plane0_offset, luma_offset);
assert_eq!(p1.offset, chroma_offset);
}
#[test]
#[cfg(feature = "dma_test_formats")]
fn test_nv12_chroma_offset_exceeds_buffer() {
let width: usize = 1920;
let height: usize = 1088;
let stride: usize = 1920;
let y_size = stride * height; let chroma_h = height.div_ceil(2);
let _chroma_size = stride * chroma_h;
let y_buf = match alloc_dma(y_size, "y_only_buf") {
Some(t) => t,
None => {
crate::test_support::report_skip(
"test_nv12_chroma_offset_exceeds_buffer - DMA not available",
);
return;
}
};
let fd = y_buf.dmabuf().unwrap();
let luma_pd = PlaneDescriptor::new(fd)
.unwrap()
.with_stride(stride)
.with_offset(0);
let chroma_pd = PlaneDescriptor::new(fd)
.unwrap()
.with_stride(stride)
.with_offset(y_size);
let result = import_nv12(luma_pd, Some(chroma_pd), width, height);
let tensor = result.unwrap();
let tensor_u8 = tensor.as_u8().unwrap();
let err = DmaImportAttrs::from_tensor(tensor_u8, PixelFormat::Nv12, false);
assert!(
err.is_err(),
"from_tensor must reject chroma offset {y_size} on a {y_size}-byte buffer"
);
let msg = err.unwrap_err().to_string();
assert!(
msg.contains("exceeds chroma fd buffer size"),
"error must mention buffer size, got: {msg}"
);
}
#[test]
fn test_egl_attribs_true_multiplane() {
let attrs = DmaImportAttrs {
width: 1920,
height: 1080,
drm_fourcc: DrmFourcc::Nv12,
plane0_fd: 10,
plane0_pitch: 1920,
plane0_offset: 0,
x_shift_px: 0,
plane1: Some(DmaPlane1Attrs {
fd: 11, pitch: 1920,
offset: 0,
}),
is_yuv: true,
yuv_encoding: ColorEncoding::Bt709,
yuv_range: ColorRange::Limited,
};
let egl = attrs.to_egl_attribs();
assert_eq!(egl_attrib_value(&egl, egl_ext::DMA_BUF_PLANE0_FD), Some(10));
assert_eq!(
egl_attrib_value(&egl, egl_ext::DMA_BUF_PLANE0_PITCH),
Some(1920)
);
assert_eq!(
egl_attrib_value(&egl, egl_ext::DMA_BUF_PLANE0_OFFSET),
Some(0)
);
assert_eq!(egl_attrib_value(&egl, egl_ext::DMA_BUF_PLANE1_FD), Some(11));
assert_eq!(
egl_attrib_value(&egl, egl_ext::DMA_BUF_PLANE1_PITCH),
Some(1920)
);
assert_eq!(
egl_attrib_value(&egl, egl_ext::DMA_BUF_PLANE1_OFFSET),
Some(0)
);
assert_eq!(
egl_attrib_value(&egl, egl_ext::YUV_COLOR_SPACE_HINT),
Some(egl_ext::ITU_REC709 as Attrib)
);
assert_eq!(
egl_attrib_value(&egl, egl_ext::SAMPLE_RANGE_HINT),
Some(egl_ext::YUV_NARROW_RANGE as Attrib)
);
assert_eq!(*egl.last().unwrap(), edgefirst_egl::NONE as Attrib);
}
#[test]
fn test_egl_attribs_same_fd_multiplane() {
let luma_size: usize = 1920 * 1080;
let attrs = DmaImportAttrs {
width: 1920,
height: 1080,
drm_fourcc: DrmFourcc::Nv12,
plane0_fd: 10,
plane0_pitch: 1920,
plane0_offset: 0,
x_shift_px: 0,
plane1: Some(DmaPlane1Attrs {
fd: 12, pitch: 1920,
offset: luma_size,
}),
is_yuv: true,
yuv_encoding: ColorEncoding::Bt709,
yuv_range: ColorRange::Limited,
};
let egl = attrs.to_egl_attribs();
assert_eq!(egl_attrib_value(&egl, egl_ext::DMA_BUF_PLANE0_FD), Some(10));
assert_eq!(egl_attrib_value(&egl, egl_ext::DMA_BUF_PLANE1_FD), Some(12));
assert_eq!(
egl_attrib_value(&egl, egl_ext::DMA_BUF_PLANE1_OFFSET),
Some(luma_size as Attrib)
);
}
#[test]
fn test_egl_attribs_contiguous() {
let height = 1080usize;
let pitch = 1920usize;
let attrs = DmaImportAttrs {
width: 1920,
height,
drm_fourcc: DrmFourcc::Nv12,
plane0_fd: 10,
plane0_pitch: pitch,
plane0_offset: 0,
x_shift_px: 0,
plane1: Some(DmaPlane1Attrs {
fd: 10, pitch,
offset: pitch * height,
}),
is_yuv: true,
yuv_encoding: ColorEncoding::Bt709,
yuv_range: ColorRange::Limited,
};
let egl = attrs.to_egl_attribs();
let p0_fd = egl_attrib_value(&egl, egl_ext::DMA_BUF_PLANE0_FD).unwrap();
let p1_fd = egl_attrib_value(&egl, egl_ext::DMA_BUF_PLANE1_FD).unwrap();
assert_eq!(
p0_fd, p1_fd,
"contiguous: PLANE0_FD and PLANE1_FD must be the same raw fd"
);
assert_eq!(
egl_attrib_value(&egl, egl_ext::DMA_BUF_PLANE1_OFFSET),
Some((pitch * height) as Attrib)
);
}
#[test]
fn test_egl_attribs_rgba_no_plane1() {
let attrs = DmaImportAttrs {
width: 640,
height: 480,
drm_fourcc: DrmFourcc::Abgr8888,
plane0_fd: 10,
plane0_pitch: 640 * 4,
plane0_offset: 0,
x_shift_px: 0,
plane1: None,
is_yuv: false,
yuv_encoding: ColorEncoding::Bt709,
yuv_range: ColorRange::Limited,
};
let egl = attrs.to_egl_attribs();
assert_eq!(egl_attrib_value(&egl, egl_ext::DMA_BUF_PLANE1_FD), None);
assert_eq!(egl_attrib_value(&egl, egl_ext::DMA_BUF_PLANE1_PITCH), None);
assert_eq!(egl_attrib_value(&egl, egl_ext::DMA_BUF_PLANE1_OFFSET), None);
assert_eq!(egl_attrib_value(&egl, egl_ext::YUV_COLOR_SPACE_HINT), None);
assert_eq!(*egl.last().unwrap(), edgefirst_egl::NONE as Attrib);
}
#[test]
fn test_egl_attribs_contiguous_padded_stride() {
let width = 1920usize;
let height = 1080usize;
let stride = 2048usize; let attrs = DmaImportAttrs {
width,
height,
drm_fourcc: DrmFourcc::Nv12,
plane0_fd: 10,
plane0_pitch: stride,
plane0_offset: 0,
x_shift_px: 0,
plane1: Some(DmaPlane1Attrs {
fd: 10,
pitch: stride,
offset: stride * height, }),
is_yuv: true,
yuv_encoding: ColorEncoding::Bt709,
yuv_range: ColorRange::Limited,
};
let egl = attrs.to_egl_attribs();
let uv_offset = egl_attrib_value(&egl, egl_ext::DMA_BUF_PLANE1_OFFSET).unwrap();
assert_eq!(
uv_offset,
(stride * height) as Attrib,
"UV offset must use stride ({stride}×{height}={}) not width ({width}×{height}={})",
stride * height,
width * height,
);
}
fn nv12_attrs_with(encoding: ColorEncoding, range: ColorRange) -> DmaImportAttrs {
DmaImportAttrs {
width: 1920,
height: 1080,
drm_fourcc: DrmFourcc::Nv12,
plane0_fd: 10,
plane0_pitch: 1920,
plane0_offset: 0,
x_shift_px: 0,
plane1: Some(DmaPlane1Attrs {
fd: 10,
pitch: 1920,
offset: 1920 * 1080,
}),
is_yuv: true,
yuv_encoding: encoding,
yuv_range: range,
}
}
#[test]
fn test_egl_attribs_bt601_full() {
let attrs = nv12_attrs_with(ColorEncoding::Bt601, ColorRange::Full);
let egl = attrs.to_egl_attribs();
assert_eq!(
egl_attrib_value(&egl, egl_ext::YUV_COLOR_SPACE_HINT),
Some(egl_ext::ITU_REC601 as Attrib),
"BT.601 must map to ITU_REC601"
);
assert_eq!(
egl_attrib_value(&egl, egl_ext::SAMPLE_RANGE_HINT),
Some(egl_ext::YUV_FULL_RANGE as Attrib),
"Full range must map to YUV_FULL_RANGE"
);
}
#[test]
fn test_egl_attribs_bt709_limited() {
let attrs = nv12_attrs_with(ColorEncoding::Bt709, ColorRange::Limited);
let egl = attrs.to_egl_attribs();
assert_eq!(
egl_attrib_value(&egl, egl_ext::YUV_COLOR_SPACE_HINT),
Some(egl_ext::ITU_REC709 as Attrib),
"BT.709 must map to ITU_REC709"
);
assert_eq!(
egl_attrib_value(&egl, egl_ext::SAMPLE_RANGE_HINT),
Some(egl_ext::YUV_NARROW_RANGE as Attrib),
"Limited range must map to YUV_NARROW_RANGE"
);
}
#[test]
fn test_egl_attribs_bt2020() {
let attrs = nv12_attrs_with(ColorEncoding::Bt2020, ColorRange::Limited);
let egl = attrs.to_egl_attribs();
assert_eq!(
egl_attrib_value(&egl, egl_ext::YUV_COLOR_SPACE_HINT),
Some(egl_ext::ITU_REC2020 as Attrib),
"BT.2020 must map to ITU_REC2020"
);
}
#[cfg(feature = "dma_test_formats")]
#[test]
fn test_from_tensor_accepts_non_4_aligned_rgba() {
if !is_dma_available() {
crate::test_support::report_skip(
"test_from_tensor_accepts_non_4_aligned_rgba — DMA not available",
);
return;
}
use crate::{align_pitch_bytes_to_gpu_alignment, primary_plane_bpp};
for &w in &[375usize, 427, 443] {
let bpp = primary_plane_bpp(PixelFormat::Rgba, 1).unwrap();
let aligned = align_pitch_bytes_to_gpu_alignment(w * bpp).unwrap();
let t = match Tensor::<u8>::image_with_stride(
w,
64,
PixelFormat::Rgba,
aligned,
Some(edgefirst_tensor::TensorMemory::DmaBuf),
edgefirst_tensor::CpuAccess::ReadWrite,
) {
Ok(t) => t,
Err(e) => {
crate::test_support::report_skip(&format!(
"image_with_stride failed at width {w}: {e}"
));
return;
}
};
let attrs =
DmaImportAttrs::from_tensor(&t, PixelFormat::Rgba, false).unwrap_or_else(|e| {
panic!(
"RGBA width {w} must pass the pre-check; got {e}. \
Width%4 is not required for 4-bpp packed formats."
)
});
assert_eq!(attrs.width, w);
assert_eq!(attrs.plane0_pitch, aligned);
}
}
#[cfg(feature = "dma_test_formats")]
#[test]
fn test_from_tensor_accepts_non_4_aligned_bgra() {
if !is_dma_available() {
crate::test_support::report_skip(
"test_from_tensor_accepts_non_4_aligned_bgra — DMA not available",
);
return;
}
use crate::{align_pitch_bytes_to_gpu_alignment, primary_plane_bpp};
let bpp = primary_plane_bpp(PixelFormat::Bgra, 1).unwrap();
let aligned = align_pitch_bytes_to_gpu_alignment(375 * bpp).unwrap();
let t = match Tensor::<u8>::image_with_stride(
375,
64,
PixelFormat::Bgra,
aligned,
Some(edgefirst_tensor::TensorMemory::DmaBuf),
edgefirst_tensor::CpuAccess::ReadWrite,
) {
Ok(t) => t,
Err(e) => {
crate::test_support::report_skip(&format!("image_with_stride failed: {e}"));
return;
}
};
DmaImportAttrs::from_tensor(&t, PixelFormat::Bgra, false)
.expect("BGRA width 375 must pass the pre-check");
}
#[test]
fn test_from_tensor_rejects_non_4_aligned_rgb() {
let t = Tensor::<u8>::image(
375,
64,
PixelFormat::Rgb,
Some(edgefirst_tensor::TensorMemory::Mem),
edgefirst_tensor::CpuAccess::ReadWrite,
)
.unwrap();
let err = DmaImportAttrs::from_tensor(&t, PixelFormat::Rgb, false)
.expect_err("RGB width 375 must still be rejected");
match err {
crate::Error::NotSupported(msg) => {
assert!(msg.contains("divisible by 4"), "got: {msg}");
}
other => panic!("expected NotSupported, got {other:?}"),
}
}
#[test]
fn test_from_tensor_rejects_non_4_aligned_grey() {
let t = Tensor::<u8>::image(
375,
64,
PixelFormat::Grey,
Some(edgefirst_tensor::TensorMemory::Mem),
edgefirst_tensor::CpuAccess::ReadWrite,
)
.unwrap();
let err = DmaImportAttrs::from_tensor(&t, PixelFormat::Grey, false)
.expect_err("Grey width 375 must still be rejected");
assert!(matches!(err, crate::Error::NotSupported(_)));
}
#[test]
fn aligned_source_base_rgba_floors_to_64_and_shifts_whole_pixels() {
let f = |o| super::aligned_source_base(o, 4, 64);
assert_eq!(f(0), Some((0, 0)));
assert_eq!(f(64), Some((64, 0)));
assert_eq!(f(256), Some((256, 0)));
assert_eq!(f(2048), Some((2048, 0)));
assert_eq!(f(32), Some((0, 8)));
assert_eq!(f(2080), Some((2048, 8)));
}
#[test]
fn aligned_source_base_grey_shifts_every_residue_byte() {
let f = |o| super::aligned_source_base(o, 1, 64);
assert_eq!(f(0), Some((0, 0)));
assert_eq!(f(32), Some((0, 32)));
assert_eq!(f(2080), Some((2048, 32)));
assert_eq!(f(63), Some((0, 63)));
}
#[test]
fn aligned_source_base_rgb_steps_by_lcm_192() {
let f = |o| super::aligned_source_base(o, 3, 64);
assert_eq!(f(0), Some((0, 0)));
assert_eq!(f(192), Some((192, 0)));
assert_eq!(f(96), Some((0, 32)));
assert_eq!(f(2079), Some((1920, 53)));
assert_ne!(f(2079).map(|(b, _)| b), Some(2048));
}
#[test]
fn aligned_source_base_refuses_a_fractional_pixel_offset() {
assert_eq!(super::aligned_source_base(2080, 3, 64), None);
assert_eq!(super::aligned_source_base(2081, 4, 64), None);
assert_eq!(super::aligned_source_base(1, 2, 64), None);
assert_eq!(super::aligned_source_base(64, 0, 64), None);
assert_eq!(super::aligned_source_base(64, 4, 0), None);
}
#[test]
fn aligned_source_base_invariants_hold_across_a_sweep() {
for bpp in [1usize, 3, 4] {
for px in 0usize..4096 {
let offset = px * bpp;
let Some((base, shift)) = super::aligned_source_base(offset, bpp, 64) else {
panic!("a whole-pixel offset {offset} at {bpp} bpp must have a base");
};
assert_eq!(base % 64, 0, "base {base} must be 64-byte aligned");
assert!(base <= offset, "base {base} must not pass offset {offset}");
assert_eq!(
base + shift as usize * bpp,
offset,
"shift {shift} px at {bpp} bpp must close base {base} to {offset}"
);
}
}
}
#[cfg(feature = "dma_test_formats")]
fn dma_parent(width: usize, height: usize, fmt: PixelFormat) -> Option<Tensor<u8>> {
if !is_dma_available() {
return None;
}
match Tensor::<u8>::image(
width,
height,
fmt,
Some(TensorMemory::DmaBuf),
edgefirst_tensor::CpuAccess::ReadWrite,
) {
Ok(t) if t.memory() == TensorMemory::DmaBuf => Some(t),
_ => None,
}
}
#[test]
#[cfg(feature = "dma_test_formats")]
fn from_tensor_rebases_an_unaligned_rgba_source_to_an_aligned_base() {
let Some(parent) = dma_parent(64, 64, PixelFormat::Rgba) else {
use std::io::Write;
let _ = writeln!(
&mut std::io::stderr(),
"SKIPPED: {} - DMA not available",
function!()
);
return;
};
let pitch = parent.effective_row_stride().unwrap_or(256);
let view = parent
.view(edgefirst_tensor::Region::new(8, 8, 16, 16))
.unwrap();
assert_eq!(view.plane_offset(), Some(8 * pitch + 32));
let s = DmaImportAttrs::from_tensor(&view, PixelFormat::Rgba, false).unwrap();
assert_eq!(s.plane0_offset % 64, 0, "the import base must be aligned");
assert_eq!(s.plane0_offset, 8 * pitch + 32 - 32);
assert_eq!(s.x_shift_px, 8, "32 bytes is 8 RGBA pixels");
assert_eq!(s.width, 16 + 8, "the import widens by the shift");
assert_eq!(
s.plane0_pitch, pitch,
"the pitch is the parent's, unchanged"
);
}
#[test]
#[cfg(feature = "dma_test_formats")]
fn from_tensor_leaves_an_aligned_source_alone() {
let Some(parent) = dma_parent(64, 64, PixelFormat::Rgba) else {
use std::io::Write;
let _ = writeln!(
&mut std::io::stderr(),
"SKIPPED: {} - DMA not available",
function!()
);
return;
};
let pitch = parent.effective_row_stride().unwrap_or(256);
assert_eq!(pitch % 64, 0, "the premise: a 64-aligned parent pitch");
let view = parent
.view(edgefirst_tensor::Region::new(0, 8, 16, 16))
.unwrap();
let s = DmaImportAttrs::from_tensor(&view, PixelFormat::Rgba, false).unwrap();
assert_eq!(s.plane0_offset, 8 * pitch);
assert_eq!(s.x_shift_px, 0);
assert_eq!(s.width, 16);
}
#[test]
#[cfg(feature = "dma_test_formats")]
fn from_tensor_never_rebases_a_destination() {
let Some(parent) = dma_parent(64, 64, PixelFormat::Rgba) else {
use std::io::Write;
let _ = writeln!(
&mut std::io::stderr(),
"SKIPPED: {} - DMA not available",
function!()
);
return;
};
let view = parent
.view(edgefirst_tensor::Region::new(8, 8, 16, 16))
.unwrap();
let d = DmaImportAttrs::from_tensor(&view, PixelFormat::Rgba, true).unwrap();
assert_eq!(d.plane0_offset, 0, "a dst view imports its parent");
assert_eq!(d.x_shift_px, 0, "and is never shifted");
assert_eq!(d.width, 64, "at the parent's own width");
}
#[test]
fn rebase_fits_pitch_declines_a_tight_foreign_pitch() {
let fits = super::rebase_fits_pitch;
assert!(!fits(100, 1, 4, 400));
assert!(!fits(100, 8, 4, 400));
assert!(fits(100, 0, 4, 400));
assert!(fits(100, 12, 4, 448));
assert!(!fits(100, 13, 4, 448));
assert!(fits(16, 8, 4, 256));
assert!(!fits(usize::MAX, 1, 4, usize::MAX));
}
#[test]
#[cfg(feature = "dma_test_formats")]
fn from_tensor_rebases_an_unaligned_rgb_source_by_the_lcm_step() {
let Some(parent) = dma_parent(64, 64, PixelFormat::Rgb) else {
use std::io::Write;
let _ = writeln!(
&mut std::io::stderr(),
"SKIPPED: {} - DMA not available",
function!()
);
return;
};
let pitch = parent.effective_row_stride().unwrap_or(192);
let view = parent
.view(edgefirst_tensor::Region::new(8, 8, 16, 16))
.unwrap();
let offset = view.plane_offset().unwrap_or(0);
assert_eq!(offset, 8 * pitch + 24);
let expect_base = offset - offset % 192;
let expect_shift = (offset - expect_base) / 3;
assert!(
expect_shift > 0,
"precondition: offset {offset} at pitch {pitch} must be unaligned"
);
let s = DmaImportAttrs::from_tensor(&view, PixelFormat::Rgb, false).unwrap();
assert_eq!(s.plane0_offset, expect_base, "base steps by lcm(64, 3)");
assert_eq!(s.plane0_offset % 64, 0, "and is still import-aligned");
assert_eq!(s.x_shift_px as usize, expect_shift);
assert_eq!(s.width, 16 + expect_shift, "the import widens by the shift");
assert_eq!(
s.plane0_pitch, pitch,
"the pitch is the parent's, unchanged"
);
}
#[test]
#[cfg(feature = "dma_test_formats")]
fn from_tensor_rebases_an_unaligned_grey_source_byte_for_texel() {
let Some(parent) = dma_parent(64, 64, PixelFormat::Grey) else {
use std::io::Write;
let _ = writeln!(
&mut std::io::stderr(),
"SKIPPED: {} - DMA not available",
function!()
);
return;
};
let pitch = parent.effective_row_stride().unwrap_or(64);
let view = parent
.view(edgefirst_tensor::Region::new(8, 8, 16, 16))
.unwrap();
let offset = view.plane_offset().unwrap_or(0);
assert_eq!(offset, 8 * pitch + 8);
let expect_base = offset - offset % 64;
let expect_shift = offset - expect_base;
assert!(
expect_shift > 0,
"precondition: offset {offset} at pitch {pitch} must be unaligned"
);
let s = DmaImportAttrs::from_tensor(&view, PixelFormat::Grey, false).unwrap();
assert_eq!(s.plane0_offset, expect_base);
assert_eq!(s.plane0_offset % 64, 0);
assert_eq!(
s.x_shift_px as usize, expect_shift,
"1 bpp: a byte is a texel"
);
assert_eq!(s.width, 16 + expect_shift);
assert_eq!(s.plane0_pitch, pitch);
}
#[test]
fn the_resolved_offset_tells_the_four_shapes_apart() {
let aligned = |fmt, w, pitch, off| {
super::resolve_source_plane0(fmt, w, pitch, off)
.0
.is_multiple_of(DMA_IMPORT_OFFSET_ALIGN)
};
assert!(aligned(PixelFormat::Rgba, 16, 256, 2080));
assert!(aligned(PixelFormat::Rgba, 64, 256, 2048));
assert!(!aligned(PixelFormat::Rgba, 64, 256, 32));
assert!(!aligned(PixelFormat::Rgb, 16, 320, 323));
}
#[test]
#[cfg(feature = "dma_test_formats")]
fn the_gate_reads_the_offset_the_import_presents() {
if !is_dma_available() {
use std::io::Write;
let _ = writeln!(
&mut std::io::stderr(),
"SKIPPED: {} - DMA not available",
function!()
);
return;
}
let cases: [(PixelFormat, usize, usize, Option<usize>); 5] = [
(PixelFormat::Rgba, 64, 64, None),
(PixelFormat::Rgba, 64, 64, Some(32)),
(PixelFormat::Rgba, 64, 64, Some(64)),
(PixelFormat::Rgb, 100, 8, Some(323)),
(PixelFormat::Nv12, 64, 64, Some(32)),
];
let total = cases.len();
let mut compared = 0usize;
for (fmt, w, h, whole_offset) in cases {
let Some(mut parent) = dma_parent(w, h, fmt) else {
use std::io::Write;
let _ = writeln!(
&mut std::io::stderr(),
"SKIPPED: {} {fmt:?} - not allocatable here",
function!()
);
continue;
};
if fmt == PixelFormat::Rgb {
let pitch = parent.effective_row_stride().unwrap_or(0);
assert_eq!(pitch, 320, "precondition: the padded Rgb stride");
let off = whole_offset.unwrap();
assert_eq!(off, pitch + 3, "precondition: row 1, column 1");
assert_ne!(off % 3, 0, "precondition: {off} is mid-pixel");
assert_ne!(off % 64, 0, "precondition: {off} is unaligned");
}
let attrs = match whole_offset {
Some(off) => {
parent.set_plane_offset(off);
DmaImportAttrs::from_tensor(&parent, fmt, false)
.unwrap_or_else(|e| panic!("{fmt:?} at {off}: {e}"))
}
None => {
let view = parent
.view(edgefirst_tensor::Region::new(8, 8, 16, 16))
.unwrap();
let a = DmaImportAttrs::from_tensor(&view, fmt, false).unwrap();
assert_eq!(
super::resolved_source_plane0_offset(&view, fmt),
a.plane0_offset,
"{fmt:?} view: the gate and the import must agree"
);
compared += 1;
continue;
}
};
assert_eq!(
super::resolved_source_plane0_offset(&parent, fmt),
attrs.plane0_offset,
"{fmt:?} at {whole_offset:?}: the gate and the import must agree"
);
compared += 1;
}
assert_eq!(
compared, total,
"a DMA heap is available, so every case must be comparable, but only \
{compared} of {total} were (see the SKIPPED lines above); \
the gate-versus-import equality went unpinned for the rest"
);
}
#[test]
#[cfg(feature = "dma_test_formats")]
fn from_tensor_cannot_rebase_a_window_into_a_tight_pitch() {
let Some(mut t) = dma_parent(64, 64, PixelFormat::Rgba) else {
use std::io::Write;
let _ = writeln!(
&mut std::io::stderr(),
"SKIPPED: {} - DMA not available",
function!()
);
return;
};
let pitch = t.effective_row_stride().unwrap_or(256);
assert_eq!(pitch, 64 * 4, "precondition: the pitch is tight");
t.set_plane_offset(32);
let s = DmaImportAttrs::from_tensor(&t, PixelFormat::Rgba, false).unwrap();
assert_eq!(s.plane0_offset, 32, "no room to widen, so no rebase");
assert_eq!(s.x_shift_px, 0);
assert_eq!(s.width, 64, "and no widening");
assert!(
!super::resolve_source_plane0(PixelFormat::Rgba, 64, pitch, 32)
.0
.is_multiple_of(DMA_IMPORT_OFFSET_ALIGN),
"so Mali must still decline it"
);
}
#[test]
#[cfg(feature = "dma_test_formats")]
fn from_tensor_never_rebases_the_formats_the_fold_misses() {
for fmt in [PixelFormat::Yuyv, PixelFormat::Nv12, PixelFormat::PlanarRgb] {
let Some(mut t) = dma_parent(64, 64, fmt) else {
use std::io::Write;
let _ = writeln!(
&mut std::io::stderr(),
"SKIPPED: {} {fmt:?} - not allocatable here",
function!()
);
continue;
};
t.set_plane_offset(32);
let s = DmaImportAttrs::from_tensor(&t, fmt, false)
.unwrap_or_else(|e| panic!("{fmt:?} source import must resolve: {e}"));
assert_eq!(s.x_shift_px, 0, "{fmt:?} must not be rebased");
assert_eq!(
s.plane0_offset, 32,
"{fmt:?} keeps its own (unaligned) offset"
);
assert_eq!(s.width, 64, "{fmt:?} must not be widened");
}
}
}