use std::any::Any;
use std::borrow::Cow;
use std::sync::{Arc, OnceLock};
use nv_core::{FeedId, MonotonicTs, TypedMetadata, WallTs};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PixelFormat {
Rgb8,
Bgr8,
Rgba8,
Nv12,
I420,
Gray8,
}
impl PixelFormat {
#[must_use]
pub fn bytes_per_pixel(&self) -> Option<u32> {
match self {
Self::Rgb8 | Self::Bgr8 => Some(3),
Self::Rgba8 => Some(4),
Self::Gray8 => Some(1),
Self::Nv12 | Self::I420 => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Residency {
Host,
Device,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DataAccess {
HostReadable,
MappableToHost,
Opaque,
}
#[derive(Debug, Clone, thiserror::Error)]
pub enum FrameAccessError {
#[error("frame is not host-accessible (opaque device-resident data)")]
NotHostAccessible,
#[error("host materialization failed: {detail}")]
MaterializationFailed {
detail: String,
},
}
pub type HostMaterializeFn = Box<dyn Fn() -> Result<HostBytes, FrameAccessError> + Send + Sync>;
pub struct HostBytes {
repr: HostBytesRepr,
}
enum HostBytesRepr {
Owned(Vec<u8>),
Mapped {
ptr: *const u8,
len: usize,
_guard: Box<dyn Any + Send + Sync>,
},
}
unsafe impl Send for HostBytes {}
unsafe impl Sync for HostBytes {}
impl HostBytes {
#[must_use]
pub fn from_vec(data: Vec<u8>) -> Self {
Self {
repr: HostBytesRepr::Owned(data),
}
}
#[must_use]
pub unsafe fn from_mapped(
ptr: *const u8,
len: usize,
guard: Box<dyn Any + Send + Sync>,
) -> Self {
Self {
repr: HostBytesRepr::Mapped {
ptr,
len,
_guard: guard,
},
}
}
}
impl AsRef<[u8]> for HostBytes {
fn as_ref(&self) -> &[u8] {
match &self.repr {
HostBytesRepr::Owned(v) => v.as_slice(),
HostBytesRepr::Mapped { ptr, len, .. } => unsafe {
std::slice::from_raw_parts(*ptr, *len)
},
}
}
}
impl std::ops::Deref for HostBytes {
type Target = [u8];
fn deref(&self) -> &[u8] {
self.as_ref()
}
}
impl std::fmt::Debug for HostBytes {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.repr {
HostBytesRepr::Owned(v) => write!(f, "HostBytes::Owned({} bytes)", v.len()),
HostBytesRepr::Mapped { len, .. } => write!(f, "HostBytes::Mapped({len} bytes)"),
}
}
}
pub(crate) type PinGuard = Box<dyn Any + Send + Sync>;
pub(crate) enum PixelData {
Mapped {
ptr: *const u8,
len: usize,
_guard: PinGuard,
},
Owned(Vec<u8>),
Device {
handle: Arc<dyn Any + Send + Sync>,
materialize: Option<HostMaterializeFn>,
},
}
unsafe impl Send for PixelData {}
unsafe impl Sync for PixelData {}
pub(crate) struct FrameInner {
pub feed_id: FeedId,
pub seq: u64,
pub ts: MonotonicTs,
pub wall_ts: WallTs,
pub width: u32,
pub height: u32,
pub format: PixelFormat,
pub stride: u32,
pub data: PixelData,
pub metadata: TypedMetadata,
pub host_cache: OnceLock<Result<HostBytes, FrameAccessError>>,
}
#[derive(Clone)]
pub struct FrameEnvelope {
pub(crate) inner: Arc<FrameInner>,
}
impl FrameEnvelope {
#[must_use]
#[allow(clippy::too_many_arguments)]
pub fn new_owned(
feed_id: FeedId,
seq: u64,
ts: MonotonicTs,
wall_ts: WallTs,
width: u32,
height: u32,
format: PixelFormat,
stride: u32,
data: Vec<u8>,
metadata: TypedMetadata,
) -> Self {
Self {
inner: Arc::new(FrameInner {
feed_id,
seq,
ts,
wall_ts,
width,
height,
format,
stride,
data: PixelData::Owned(data),
metadata,
host_cache: OnceLock::new(),
}),
}
}
#[must_use]
#[allow(clippy::too_many_arguments)]
pub fn new_device(
feed_id: FeedId,
seq: u64,
ts: MonotonicTs,
wall_ts: WallTs,
width: u32,
height: u32,
format: PixelFormat,
stride: u32,
handle: Arc<dyn Any + Send + Sync>,
materialize: Option<HostMaterializeFn>,
metadata: TypedMetadata,
) -> Self {
Self {
inner: Arc::new(FrameInner {
feed_id,
seq,
ts,
wall_ts,
width,
height,
format,
stride,
data: PixelData::Device {
handle,
materialize,
},
metadata,
host_cache: OnceLock::new(),
}),
}
}
#[must_use]
#[allow(clippy::too_many_arguments)]
pub unsafe fn new_mapped(
feed_id: FeedId,
seq: u64,
ts: MonotonicTs,
wall_ts: WallTs,
width: u32,
height: u32,
format: PixelFormat,
stride: u32,
ptr: *const u8,
len: usize,
guard: PinGuard,
metadata: TypedMetadata,
) -> Self {
Self {
inner: Arc::new(FrameInner {
feed_id,
seq,
ts,
wall_ts,
width,
height,
format,
stride,
data: PixelData::Mapped {
ptr,
len,
_guard: guard,
},
metadata,
host_cache: OnceLock::new(),
}),
}
}
#[must_use]
pub fn feed_id(&self) -> FeedId {
self.inner.feed_id
}
#[must_use]
pub fn seq(&self) -> u64 {
self.inner.seq
}
#[must_use]
pub fn ts(&self) -> MonotonicTs {
self.inner.ts
}
#[must_use]
pub fn wall_ts(&self) -> WallTs {
self.inner.wall_ts
}
#[must_use]
pub fn width(&self) -> u32 {
self.inner.width
}
#[must_use]
pub fn height(&self) -> u32 {
self.inner.height
}
#[must_use]
pub fn format(&self) -> PixelFormat {
self.inner.format
}
#[must_use]
pub fn stride(&self) -> u32 {
self.inner.stride
}
#[must_use]
pub fn residency(&self) -> Residency {
match &self.inner.data {
PixelData::Owned(_) | PixelData::Mapped { .. } => Residency::Host,
PixelData::Device { .. } => Residency::Device,
}
}
#[must_use]
pub fn data_access(&self) -> DataAccess {
match &self.inner.data {
PixelData::Owned(_) | PixelData::Mapped { .. } => DataAccess::HostReadable,
PixelData::Device {
materialize: Some(_),
..
} => DataAccess::MappableToHost,
PixelData::Device {
materialize: None, ..
} => DataAccess::Opaque,
}
}
#[must_use]
pub fn is_host_readable(&self) -> bool {
!matches!(&self.inner.data, PixelData::Device { .. })
}
#[must_use]
pub fn host_data(&self) -> Option<&[u8]> {
match &self.inner.data {
PixelData::Owned(v) => Some(v.as_slice()),
PixelData::Mapped { ptr, len, .. } => {
Some(unsafe { std::slice::from_raw_parts(*ptr, *len) })
}
PixelData::Device { .. } => None,
}
}
pub fn require_host_data(&self) -> Result<Cow<'_, [u8]>, FrameAccessError> {
match &self.inner.data {
PixelData::Owned(v) => Ok(Cow::Borrowed(v.as_slice())),
PixelData::Mapped { ptr, len, .. } => Ok(Cow::Borrowed(unsafe {
std::slice::from_raw_parts(*ptr, *len)
})),
PixelData::Device {
materialize: Some(f),
..
} => {
let cached = self.inner.host_cache.get_or_init(|| {
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)) {
Ok(result) => result,
Err(payload) => {
let detail = match payload.downcast_ref::<&str>() {
Some(s) => (*s).to_owned(),
None => match payload.downcast_ref::<String>() {
Some(s) => s.clone(),
None => "unknown panic in host materializer".to_owned(),
},
};
Err(FrameAccessError::MaterializationFailed { detail })
}
}
});
match cached {
Ok(bytes) => Ok(Cow::Borrowed(bytes.as_ref())),
Err(e) => Err(e.clone()),
}
}
PixelData::Device {
materialize: None, ..
} => Err(FrameAccessError::NotHostAccessible),
}
}
#[must_use]
pub fn accelerated_handle<T: Send + Sync + 'static>(&self) -> Option<&T> {
match &self.inner.data {
PixelData::Device { handle, .. } => handle.downcast_ref::<T>(),
_ => None,
}
}
#[must_use]
pub fn metadata(&self) -> &TypedMetadata {
&self.inner.metadata
}
}
impl std::fmt::Debug for FrameEnvelope {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FrameEnvelope")
.field("feed_id", &self.inner.feed_id)
.field("seq", &self.inner.seq)
.field("ts", &self.inner.ts)
.field("dims", &(self.inner.width, self.inner.height))
.field("format", &self.inner.format)
.field("residency", &self.residency())
.field("data_access", &self.data_access())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_frame() -> FrameEnvelope {
FrameEnvelope::new_owned(
FeedId::new(1),
0,
MonotonicTs::ZERO,
WallTs::from_micros(0),
320,
240,
PixelFormat::Rgb8,
960,
vec![0u8; 320 * 240 * 3],
TypedMetadata::new(),
)
}
#[test]
fn clone_is_cheap() {
let f1 = test_frame();
let f2 = f1.clone();
let d1 = f1.host_data().unwrap();
let d2 = f2.host_data().unwrap();
assert!(std::ptr::eq(d1.as_ptr(), d2.as_ptr()));
}
#[test]
fn accessors() {
let f = test_frame();
assert_eq!(f.width(), 320);
assert_eq!(f.height(), 240);
assert_eq!(f.format(), PixelFormat::Rgb8);
assert_eq!(f.host_data().unwrap().len(), 320 * 240 * 3);
}
const _: () = {
const fn assert_send<T: Send>() {}
assert_send::<PixelData>();
};
const _: () = {
const fn assert_sync<T: Sync>() {}
assert_sync::<PixelData>();
};
const _: () = {
const fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<FrameEnvelope>();
};
#[test]
fn frame_is_send_across_threads() {
let f = test_frame();
let handle = std::thread::spawn(move || {
assert_eq!(f.width(), 320);
f
});
let f = handle.join().unwrap();
assert_eq!(f.height(), 240);
}
#[test]
fn frame_is_sync_across_threads() {
let f = Arc::new(test_frame());
let f2 = Arc::clone(&f);
let handle = std::thread::spawn(move || {
assert_eq!(f2.host_data().unwrap().len(), 320 * 240 * 3);
});
assert_eq!(f.width(), 320);
handle.join().unwrap();
}
#[test]
fn mapped_frame_send_invariant() {
let data = vec![1u8, 2, 3, 4, 5, 6];
let ptr = data.as_ptr();
let len = data.len();
let guard: PinGuard = Box::new(data);
let f = unsafe {
FrameEnvelope::new_mapped(
FeedId::new(1),
0,
MonotonicTs::ZERO,
WallTs::from_micros(0),
2,
1,
PixelFormat::Rgb8,
6,
ptr,
len,
guard,
TypedMetadata::new(),
)
};
let handle = std::thread::spawn(move || {
assert_eq!(f.host_data().unwrap(), &[1, 2, 3, 4, 5, 6]);
});
handle.join().unwrap();
}
#[derive(Debug, Clone, PartialEq)]
struct MockGpuBuffer {
device_id: u32,
mem_handle: u64,
}
fn device_frame(surface: MockGpuBuffer) -> FrameEnvelope {
FrameEnvelope::new_device(
FeedId::new(2),
10,
MonotonicTs::from_nanos(5_000_000),
WallTs::from_micros(100),
1920,
1080,
PixelFormat::Nv12,
1920,
Arc::new(surface),
None,
TypedMetadata::new(),
)
}
#[test]
fn owned_frame_is_host_resident() {
let f = test_frame();
assert_eq!(f.residency(), Residency::Host);
assert!(f.is_host_readable());
assert!(f.host_data().is_some());
assert_eq!(f.host_data().unwrap().len(), 320 * 240 * 3);
}
#[test]
fn mapped_frame_is_host_resident() {
let data = vec![42u8; 6];
let ptr = data.as_ptr();
let len = data.len();
let guard: PinGuard = Box::new(data);
let f = unsafe {
FrameEnvelope::new_mapped(
FeedId::new(1),
0,
MonotonicTs::ZERO,
WallTs::from_micros(0),
2,
1,
PixelFormat::Rgb8,
6,
ptr,
len,
guard,
TypedMetadata::new(),
)
};
assert_eq!(f.residency(), Residency::Host);
assert!(f.is_host_readable());
assert_eq!(f.host_data(), Some([42u8; 6].as_slice()));
}
#[test]
fn device_frame_residency() {
let f = device_frame(MockGpuBuffer {
device_id: 0,
mem_handle: 0xDEAD,
});
assert_eq!(f.residency(), Residency::Device);
assert!(!f.is_host_readable());
assert!(f.host_data().is_none());
}
#[test]
fn accelerated_handle_downcast() {
let surface = MockGpuBuffer {
device_id: 3,
mem_handle: 0xBEEF,
};
let f = device_frame(surface.clone());
let recovered = f.accelerated_handle::<MockGpuBuffer>().unwrap();
assert_eq!(recovered, &surface);
}
#[test]
fn accelerated_handle_wrong_type_returns_none() {
let f = device_frame(MockGpuBuffer {
device_id: 0,
mem_handle: 0,
});
assert!(f.accelerated_handle::<String>().is_none());
}
#[test]
fn host_frame_accelerated_handle_returns_none() {
let f = test_frame();
assert!(f.accelerated_handle::<MockGpuBuffer>().is_none());
}
#[test]
fn device_frame_host_data_returns_none() {
let f = device_frame(MockGpuBuffer {
device_id: 0,
mem_handle: 0,
});
assert!(f.host_data().is_none());
}
#[test]
fn device_frame_preserves_metadata_and_geometry() {
#[derive(Clone, Debug, PartialEq)]
struct Tag(u32);
let mut meta = TypedMetadata::new();
meta.insert(Tag(99));
let f = FrameEnvelope::new_device(
FeedId::new(5),
42,
MonotonicTs::from_nanos(1_000),
WallTs::from_micros(500),
3840,
2160,
PixelFormat::Rgb8,
3840 * 3,
Arc::new(MockGpuBuffer {
device_id: 1,
mem_handle: 0xCAFE,
}),
None,
meta,
);
assert_eq!(f.feed_id(), FeedId::new(5));
assert_eq!(f.seq(), 42);
assert_eq!(f.width(), 3840);
assert_eq!(f.height(), 2160);
assert_eq!(f.format(), PixelFormat::Rgb8);
assert_eq!(f.stride(), 3840 * 3);
assert_eq!(f.metadata().get::<Tag>(), Some(&Tag(99)));
}
#[test]
fn device_frame_clone_shares_handle() {
let f1 = device_frame(MockGpuBuffer {
device_id: 0,
mem_handle: 0xAA,
});
let f2 = f1.clone();
let s1 = f1.accelerated_handle::<MockGpuBuffer>().unwrap();
let s2 = f2.accelerated_handle::<MockGpuBuffer>().unwrap();
assert!(std::ptr::eq(s1, s2));
}
#[test]
fn device_frame_is_send_sync() {
let f = device_frame(MockGpuBuffer {
device_id: 0,
mem_handle: 0,
});
let handle = std::thread::spawn(move || {
assert_eq!(f.residency(), Residency::Device);
f.accelerated_handle::<MockGpuBuffer>().unwrap().device_id
});
assert_eq!(handle.join().unwrap(), 0);
}
#[test]
fn residency_branch_pattern() {
let host = test_frame();
let device = device_frame(MockGpuBuffer {
device_id: 1,
mem_handle: 0xFF,
});
for f in [host, device] {
match f.residency() {
Residency::Host => {
let bytes = f.host_data().expect("host-resident");
assert!(!bytes.is_empty());
}
Residency::Device => {
let buf = f
.accelerated_handle::<MockGpuBuffer>()
.expect("expected MockGpuBuffer");
assert_eq!(buf.device_id, 1);
}
}
}
}
#[test]
fn debug_includes_residency() {
let f = test_frame();
let dbg = format!("{f:?}");
assert!(dbg.contains("Host"));
let f2 = device_frame(MockGpuBuffer {
device_id: 0,
mem_handle: 0,
});
let dbg2 = format!("{f2:?}");
assert!(dbg2.contains("Device"));
}
#[test]
fn host_frame_data_access() {
let f = test_frame();
assert_eq!(f.data_access(), DataAccess::HostReadable);
}
#[test]
fn opaque_device_frame_data_access() {
let f = device_frame(MockGpuBuffer {
device_id: 0,
mem_handle: 0,
});
assert_eq!(f.data_access(), DataAccess::Opaque);
}
#[test]
fn mappable_device_frame_data_access() {
let data = vec![10u8, 20, 30];
let f = FrameEnvelope::new_device(
FeedId::new(3),
0,
MonotonicTs::ZERO,
WallTs::from_micros(0),
1,
1,
PixelFormat::Rgb8,
3,
Arc::new(MockGpuBuffer {
device_id: 1,
mem_handle: 0xAA,
}),
Some(Box::new(move || Ok(HostBytes::from_vec(data.clone())))),
TypedMetadata::new(),
);
assert_eq!(f.residency(), Residency::Device);
assert_eq!(f.data_access(), DataAccess::MappableToHost);
}
#[test]
fn require_host_data_host_frame_is_borrowed() {
let f = test_frame();
let cow = f.require_host_data().unwrap();
assert!(matches!(cow, Cow::Borrowed(_)));
assert_eq!(cow.len(), 320 * 240 * 3);
}
#[test]
fn require_host_data_mappable_device_materializes() {
let expected = vec![1u8, 2, 3, 4, 5, 6];
let expected_clone = expected.clone();
let f = FrameEnvelope::new_device(
FeedId::new(4),
0,
MonotonicTs::ZERO,
WallTs::from_micros(0),
2,
1,
PixelFormat::Rgb8,
6,
Arc::new(MockGpuBuffer {
device_id: 0,
mem_handle: 0,
}),
Some(Box::new(move || {
Ok(HostBytes::from_vec(expected_clone.clone()))
})),
TypedMetadata::new(),
);
let cow = f.require_host_data().unwrap();
assert!(matches!(cow, Cow::Borrowed(_)));
assert_eq!(&*cow, &expected[..]);
}
#[test]
fn require_host_data_opaque_device_returns_error() {
let f = device_frame(MockGpuBuffer {
device_id: 0,
mem_handle: 0,
});
let err = f.require_host_data().unwrap_err();
assert!(matches!(err, FrameAccessError::NotHostAccessible));
}
#[test]
fn require_host_data_materialization_failure() {
let f = FrameEnvelope::new_device(
FeedId::new(5),
0,
MonotonicTs::ZERO,
WallTs::from_micros(0),
1,
1,
PixelFormat::Gray8,
1,
Arc::new(MockGpuBuffer {
device_id: 0,
mem_handle: 0,
}),
Some(Box::new(|| {
Err(FrameAccessError::MaterializationFailed {
detail: "transfer timeout".into(),
})
})),
TypedMetadata::new(),
);
let err = f.require_host_data().unwrap_err();
assert!(matches!(
err,
FrameAccessError::MaterializationFailed { .. }
));
assert!(err.to_string().contains("transfer timeout"));
}
#[test]
fn data_access_branch_pattern() {
let host = test_frame();
let data = vec![42u8; 6];
let mappable = FrameEnvelope::new_device(
FeedId::new(6),
0,
MonotonicTs::ZERO,
WallTs::from_micros(0),
2,
1,
PixelFormat::Rgb8,
6,
Arc::new(MockGpuBuffer {
device_id: 0,
mem_handle: 0,
}),
Some(Box::new(move || Ok(HostBytes::from_vec(data.clone())))),
TypedMetadata::new(),
);
let opaque = device_frame(MockGpuBuffer {
device_id: 0,
mem_handle: 0,
});
for f in [host, mappable, opaque] {
match f.data_access() {
DataAccess::HostReadable => {
assert!(f.host_data().is_some());
assert!(f.require_host_data().is_ok());
}
DataAccess::MappableToHost => {
assert!(f.host_data().is_none());
assert!(f.require_host_data().is_ok());
}
DataAccess::Opaque => {
assert!(f.host_data().is_none());
assert!(f.require_host_data().is_err());
}
}
}
}
const _: () = {
const fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<HostBytes>();
};
#[test]
fn host_bytes_from_vec() {
let hb = HostBytes::from_vec(vec![1, 2, 3]);
assert_eq!(hb.as_ref(), &[1, 2, 3]);
assert_eq!(&*hb, &[1, 2, 3]);
}
#[test]
fn host_bytes_from_mapped_zero_copy() {
let data = vec![10u8, 20, 30];
let ptr = data.as_ptr();
let len = data.len();
let guard: Box<dyn Any + Send + Sync> = Box::new(data);
let hb = unsafe { HostBytes::from_mapped(ptr, len, guard) };
assert_eq!(hb.as_ref(), &[10, 20, 30]);
}
#[test]
fn require_host_data_memoizes_materialization() {
use std::sync::atomic::{AtomicU32, Ordering};
let call_count = Arc::new(AtomicU32::new(0));
let cc = Arc::clone(&call_count);
let f = FrameEnvelope::new_device(
FeedId::new(10),
0,
MonotonicTs::ZERO,
WallTs::from_micros(0),
1,
1,
PixelFormat::Gray8,
1,
Arc::new(MockGpuBuffer {
device_id: 0,
mem_handle: 0,
}),
Some(Box::new(move || {
cc.fetch_add(1, Ordering::Relaxed);
Ok(HostBytes::from_vec(vec![42]))
})),
TypedMetadata::new(),
);
let r1 = f.require_host_data().unwrap();
let r2 = f.require_host_data().unwrap();
assert_eq!(&*r1, &[42u8]);
assert_eq!(&*r2, &[42u8]);
assert!(std::ptr::eq(r1.as_ptr(), r2.as_ptr()));
assert_eq!(call_count.load(Ordering::Relaxed), 1);
}
#[test]
fn require_host_data_cache_shared_across_clones() {
use std::sync::atomic::{AtomicU32, Ordering};
let call_count = Arc::new(AtomicU32::new(0));
let cc = Arc::clone(&call_count);
let f1 = FrameEnvelope::new_device(
FeedId::new(11),
0,
MonotonicTs::ZERO,
WallTs::from_micros(0),
1,
1,
PixelFormat::Gray8,
1,
Arc::new(MockGpuBuffer {
device_id: 0,
mem_handle: 0,
}),
Some(Box::new(move || {
cc.fetch_add(1, Ordering::Relaxed);
Ok(HostBytes::from_vec(vec![99]))
})),
TypedMetadata::new(),
);
let f2 = f1.clone();
let r1 = f1.require_host_data().unwrap();
let r2 = f2.require_host_data().unwrap();
assert_eq!(&*r1, &[99u8]);
assert_eq!(&*r2, &[99u8]);
assert_eq!(call_count.load(Ordering::Relaxed), 1);
}
#[test]
fn require_host_data_concurrent_access() {
use std::sync::Barrier;
use std::sync::atomic::{AtomicU32, Ordering};
let call_count = Arc::new(AtomicU32::new(0));
let cc = Arc::clone(&call_count);
let f = FrameEnvelope::new_device(
FeedId::new(12),
0,
MonotonicTs::ZERO,
WallTs::from_micros(0),
1,
1,
PixelFormat::Gray8,
1,
Arc::new(MockGpuBuffer {
device_id: 0,
mem_handle: 0,
}),
Some(Box::new(move || {
cc.fetch_add(1, Ordering::Relaxed);
Ok(HostBytes::from_vec(vec![7]))
})),
TypedMetadata::new(),
);
let barrier = Arc::new(Barrier::new(4));
let handles: Vec<_> = (0..4)
.map(|_| {
let f = f.clone();
let b = Arc::clone(&barrier);
std::thread::spawn(move || {
b.wait();
let r = f.require_host_data().unwrap();
assert_eq!(&*r, &[7u8]);
})
})
.collect();
for h in handles {
h.join().unwrap();
}
assert_eq!(call_count.load(Ordering::Relaxed), 1);
}
#[test]
fn require_host_data_failure_is_cached() {
use std::sync::atomic::{AtomicU32, Ordering};
let call_count = Arc::new(AtomicU32::new(0));
let cc = Arc::clone(&call_count);
let f = FrameEnvelope::new_device(
FeedId::new(13),
0,
MonotonicTs::ZERO,
WallTs::from_micros(0),
1,
1,
PixelFormat::Gray8,
1,
Arc::new(MockGpuBuffer {
device_id: 0,
mem_handle: 0,
}),
Some(Box::new(move || {
cc.fetch_add(1, Ordering::Relaxed);
Err(FrameAccessError::MaterializationFailed {
detail: "device busy".into(),
})
})),
TypedMetadata::new(),
);
let e1 = f.require_host_data().unwrap_err();
let e2 = f.require_host_data().unwrap_err();
assert!(e1.to_string().contains("device busy"));
assert!(e2.to_string().contains("device busy"));
assert_eq!(call_count.load(Ordering::Relaxed), 1);
}
#[test]
fn require_host_data_catches_materializer_panic() {
let f = FrameEnvelope::new_device(
FeedId::new(15),
0,
MonotonicTs::ZERO,
WallTs::from_micros(0),
1,
1,
PixelFormat::Gray8,
1,
Arc::new(MockGpuBuffer {
device_id: 0,
mem_handle: 0,
}),
Some(Box::new(|| panic!("adapter exploded"))),
TypedMetadata::new(),
);
let err = f.require_host_data().unwrap_err();
assert!(matches!(
err,
FrameAccessError::MaterializationFailed { .. }
));
assert!(err.to_string().contains("adapter exploded"));
let err2 = f.require_host_data().unwrap_err();
assert!(err2.to_string().contains("adapter exploded"));
}
#[test]
fn require_host_data_host_frame_skips_cache() {
let f = test_frame();
let r1 = f.require_host_data().unwrap();
let r2 = f.require_host_data().unwrap();
assert!(matches!(r1, Cow::Borrowed(_)));
assert!(matches!(r2, Cow::Borrowed(_)));
assert!(std::ptr::eq(r1.as_ptr(), r2.as_ptr()));
}
#[test]
fn require_host_data_mapped_materializer() {
let f = FrameEnvelope::new_device(
FeedId::new(14),
0,
MonotonicTs::ZERO,
WallTs::from_micros(0),
1,
3,
PixelFormat::Gray8,
1,
Arc::new(MockGpuBuffer {
device_id: 0,
mem_handle: 0,
}),
Some(Box::new(|| {
let data = vec![10u8, 20, 30];
let ptr = data.as_ptr();
let len = data.len();
let guard: Box<dyn Any + Send + Sync> = Box::new(data);
Ok(unsafe { HostBytes::from_mapped(ptr, len, guard) })
})),
TypedMetadata::new(),
);
let cow = f.require_host_data().unwrap();
assert_eq!(&*cow, &[10, 20, 30]);
let cow2 = f.require_host_data().unwrap();
assert!(std::ptr::eq(cow.as_ptr(), cow2.as_ptr()));
}
}