#![cfg(all(target_os = "windows", feature = "static"))]
use edgefirst_tensor::blob::TransportMode;
use edgefirst_tensor::{
CpuAccess, DType, Error, PixelFormat, Tensor, TensorDyn, TensorMapTrait, TensorMemory,
TensorTrait,
};
use std::os::windows::io::AsRawHandle;
#[path = "support/cuda_require.rs"]
mod cuda_require;
fn warp_has_no_cuda_device(what: &str) -> bool {
let warp = edgefirst_tensor::d3d11::device().is_ok_and(|d| d.is_warp());
if warp {
report_skip(&format!("{what}: WARP adapter has no CUDA device"));
}
warp
}
fn report_skip(reason: &str) {
use std::io::Write;
let _ = writeln!(&mut std::io::stderr(), "SKIPPED: {reason}");
}
#[test]
fn image_with_dmabuf_on_windows_is_a_texture_tensor() {
if !edgefirst_tensor::is_gpu_buffer_available() {
report_skip("no D3D11 device on this box");
return;
}
let t = Tensor::<u8>::image(
640,
480,
PixelFormat::Rgba,
Some(TensorMemory::DmaBuf),
CpuAccess::ReadWrite,
)
.unwrap();
assert_eq!(t.memory(), TensorMemory::DmaBuf);
assert!(t.d3d11_texture().is_some());
assert_eq!(t.d3d11_layout().unwrap().texture_width, 640);
assert_eq!(t.d3d11_layout().unwrap().texture_height, 480);
let stride = t.row_stride().unwrap_or(640 * 4);
assert!(stride >= 640 * 4);
{
let mut m = t.map().unwrap();
m.as_mut_slice()[..4].copy_from_slice(&[1, 2, 3, 4]);
}
assert_eq!(&t.map().unwrap().as_slice()[..4], &[1, 2, 3, 4]);
}
#[test]
fn nv12_odd_height_allocates_the_combined_plane() {
let t = Tensor::<u8>::image(
640,
481,
PixelFormat::Nv12,
Some(TensorMemory::DmaBuf),
CpuAccess::Read,
)
.unwrap();
assert_eq!(
t.d3d11_layout().unwrap().texture_height,
PixelFormat::Nv12.allocation_shape(640, 481).unwrap()[0]
);
}
#[test]
fn unsupported_format_is_a_loud_error_not_a_silent_downgrade() {
let err = Tensor::<u8>::image(
64,
64,
PixelFormat::Vyuy,
Some(TensorMemory::DmaBuf),
CpuAccess::None,
)
.unwrap_err();
assert!(
err.to_string()
.contains("no zero-copy D3D11 texture layout"),
"unexpected error: {err}"
);
}
#[test]
fn try_map_returns_would_block_or_succeeds_and_other_backings_alias_map() {
let t = Tensor::<u8>::image(
1920,
1080,
PixelFormat::Rgba,
Some(TensorMemory::DmaBuf),
CpuAccess::ReadWrite,
)
.unwrap();
match t.try_map() {
Ok(_) => {}
Err(Error::IoError(e)) => assert_eq!(e.kind(), std::io::ErrorKind::WouldBlock),
Err(e) => panic!("unexpected {e}"),
}
match t.try_map_with(CpuAccess::Read) {
Ok(_) => {}
Err(Error::IoError(e)) => assert_eq!(e.kind(), std::io::ErrorKind::WouldBlock),
Err(e) => panic!("unexpected {e}"),
}
let m = Tensor::<u8>::new(&[16], None, None).unwrap();
assert!(m.try_map().is_ok());
}
#[test]
fn try_map_makes_progress_and_sees_the_written_bytes() {
let t = Tensor::<u8>::image(
64,
64,
PixelFormat::Rgba,
Some(TensorMemory::DmaBuf),
CpuAccess::ReadWrite,
)
.unwrap();
{
let mut m = t.map_write().unwrap();
m.as_mut_slice()[..4].copy_from_slice(&[9, 8, 7, 6]);
}
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2);
let mapped = loop {
match t.try_map_with(CpuAccess::Read) {
Ok(v) => break v,
Err(Error::IoError(e)) if e.kind() == std::io::ErrorKind::WouldBlock => {}
Err(e) => panic!("unexpected {e}"),
}
assert!(
std::time::Instant::now() < deadline,
"try_map_with never succeeded"
);
std::thread::yield_now();
};
assert_eq!(&mapped.as_slice()[..4], &[9, 8, 7, 6]);
}
#[test]
fn tensordyn_exposes_the_same_surface() {
let t = TensorDyn::image(
64,
32,
PixelFormat::Bgra,
DType::U8,
Some(TensorMemory::DmaBuf),
CpuAccess::None,
)
.unwrap();
assert!(t.d3d11_texture().is_some());
assert!(t.gpu_completion().unwrap().is_none());
let h = t.d3d11_shared_handle().unwrap();
let again = unsafe {
TensorDyn::from_d3d11_shared_handle(
h.as_raw_handle() as _,
64,
32,
PixelFormat::Bgra,
DType::U8,
CpuAccess::None,
None,
None,
)
}
.unwrap();
assert_eq!(again.memory(), TensorMemory::DmaBuf);
assert!(again.d3d11_texture().is_some());
}
#[test]
fn from_d3d11_texture_wraps_and_gpu_write_is_recorded() {
let t = Tensor::<u8>::image(
32,
16,
PixelFormat::Rgba,
Some(TensorMemory::DmaBuf),
CpuAccess::Read,
)
.unwrap();
let ptr = t.d3d11_texture().unwrap();
let wrapped = unsafe {
Tensor::<u8>::from_d3d11_texture(ptr, 32, 16, PixelFormat::Rgba, CpuAccess::Read, None)
}
.unwrap();
assert_eq!(wrapped.memory(), TensorMemory::DmaBuf);
assert_eq!(wrapped.d3d11_texture(), Some(ptr));
assert_eq!(
wrapped.buffer_identity().id(),
t.buffer_identity().id(),
"a pointer-wrapped texture shares the original's identity"
);
assert_eq!(
t.buffer_identity().kind(),
edgefirst_tensor::IdentityKind::D3d11Texture,
"a texture tensor is identified by its texture"
);
assert_eq!(
t.buffer_identity().id(),
((edgefirst_tensor::IdentityKind::D3d11Texture as u64) << 56) ^ (ptr as usize as u64),
"the static backend's key is the ID3D11Texture2D pointer, the same value \
the dynamic backend reads back through ef_tensor_d3d11_texture"
);
assert!(wrapped.gpu_completion().unwrap().is_none());
wrapped.set_gpu_write(7).unwrap();
let completion = wrapped.gpu_completion().unwrap().expect("a recorded write");
assert_eq!(completion.value, 7);
}
#[test]
fn handle_imports_of_different_textures_do_not_share_an_identity() {
let open = |t: &TensorDyn| {
let h = t.d3d11_shared_handle().unwrap();
let imported = unsafe {
TensorDyn::from_d3d11_shared_handle(
h.as_raw_handle() as _,
32,
16,
PixelFormat::Rgba,
DType::U8,
CpuAccess::None,
None,
None,
)
}
.unwrap();
drop(h);
imported
};
let make = || {
TensorDyn::image(
32,
16,
PixelFormat::Rgba,
DType::U8,
Some(TensorMemory::DmaBuf),
CpuAccess::None,
)
.unwrap()
};
let (a, b) = (make(), make());
let (ia, ib) = (open(&a), open(&b));
assert_ne!(
ia.buffer_identity().id(),
ib.buffer_identity().id(),
"two imports of different textures must not collide on a recycled handle value"
);
}
#[test]
fn write_only_map_of_a_narrowed_window_is_refused_and_names_readwrite() {
let mut t = Tensor::<u8>::image(
64,
16,
PixelFormat::Rgba,
Some(TensorMemory::DmaBuf),
CpuAccess::ReadWrite,
)
.unwrap();
assert!(t.map_write().is_ok(), "the whole tensor is writable");
t.set_logical_shape(&[4, 64, 4]).unwrap();
let err = t.map_write().unwrap_err();
assert!(
matches!(&err, Error::PartialWriteRequiresReadWrite(m) if m.contains("CpuAccess::ReadWrite")),
"unexpected error: {err}"
);
assert!(t.map_mut().is_ok(), "the read-write window is accepted");
}
#[test]
fn d3d11_gpu_write_value_matches_the_completion_value() {
let t = Tensor::<u8>::image(
32,
16,
PixelFormat::Rgba,
Some(TensorMemory::DmaBuf),
CpuAccess::None,
)
.unwrap();
assert_eq!(t.gpu_write_value(), 0);
t.set_gpu_write(11).unwrap();
let completion = t.gpu_completion().unwrap().expect("a recorded write");
assert_eq!(t.gpu_write_value(), completion.value);
let t = TensorDyn::from(t);
assert_eq!(t.gpu_write_value(), 11);
t.set_gpu_write(12).unwrap();
let completion = t.gpu_completion().unwrap().expect("a recorded write");
assert_eq!(t.gpu_write_value(), completion.value);
let host = Tensor::<u8>::new(&[16], None, None).unwrap();
assert_eq!(host.gpu_write_value(), 0);
assert_eq!(TensorDyn::from(host).gpu_write_value(), 0);
}
#[test]
fn non_texture_tensors_report_no_d3d11_backing() {
let m = Tensor::<u8>::new(&[16], None, None).unwrap();
assert!(m.d3d11_texture().is_none());
assert!(m.d3d11_layout().is_none());
assert!(matches!(
m.d3d11_shared_handle(),
Err(Error::NotImplemented(_))
));
assert!(matches!(m.gpu_completion(), Err(Error::NotImplemented(_))));
}
#[test]
fn plain_new_with_dmabuf_names_the_image_constructor() {
let err = Tensor::<u8>::new(&[16], Some(TensorMemory::DmaBuf), None).unwrap_err();
assert!(
err.to_string().contains("Tensor::image"),
"unexpected error: {err}"
);
}
#[test]
fn descriptor_round_trips_a_texture_with_its_completion() {
let t = TensorDyn::image(
32,
16,
PixelFormat::Rgba,
DType::U8,
Some(TensorMemory::DmaBuf),
CpuAccess::ReadWrite,
)
.unwrap();
{
let mut m = t.map_bytes(CpuAccess::ReadWrite).unwrap();
m.as_mut_slice()[..4].copy_from_slice(&[9, 8, 7, 6]);
}
let v = edgefirst_tensor::d3d11::device()
.unwrap()
.signal()
.expect("fence");
t.set_gpu_write(v).unwrap();
let desc = t.descriptor_pinned(None);
assert_eq!(desc.kind, edgefirst_tensor::protocol::kind::D3D11_TEXTURE);
assert!(desc.flags & edgefirst_tensor::protocol::flags::SYNC_PRESENT != 0);
assert_eq!(desc.sync, v);
let imported = TensorDyn::import_descriptor(&desc).unwrap();
assert_eq!(imported.memory(), TensorMemory::DmaBuf);
assert_eq!(
&imported.map_bytes(CpuAccess::Read).unwrap().as_slice()[..4],
&[9, 8, 7, 6]
);
let pin = t.pin_host(CpuAccess::Read).unwrap();
assert!(!pin.as_mut_ptr().is_null(), "a texture tensor does pin");
let pinned = t.descriptor_pinned(Some(&pin));
assert_eq!(pinned.ptr.0, desc.ptr.0);
assert_ne!(pinned.ptr.0, pin.as_mut_ptr());
assert!(TensorDyn::import_descriptor(&pinned).is_ok());
}
#[test]
fn descriptor_round_trips_a_semi_planar_texture() {
let mut t = TensorDyn::image(
640,
481,
PixelFormat::Nv12,
DType::U8,
Some(TensorMemory::DmaBuf),
CpuAccess::ReadWrite,
)
.unwrap();
let imported = TensorDyn::import_descriptor(&t.descriptor_pinned(None)).unwrap();
assert_eq!(imported.width(), Some(640));
assert_eq!(imported.height(), Some(481));
assert_eq!(imported.width(), t.width());
assert_eq!(imported.height(), t.height());
assert_eq!(imported.shape(), t.shape());
assert_eq!(imported.row_stride(), t.row_stride());
t.set_logical_shape(&[481, 640]).unwrap();
let imported = TensorDyn::import_descriptor(&t.descriptor_pinned(None)).unwrap();
assert_eq!(imported.shape(), &[481, 640]);
assert_eq!(imported.row_stride(), t.row_stride());
}
fn copy_rows(dst: &mut [u8], src: &[u8], row_bytes: usize, dst_stride: usize) {
for (i, row) in src.chunks(row_bytes).enumerate() {
dst[i * dst_stride..i * dst_stride + row.len()].copy_from_slice(row);
}
}
#[test]
fn cuda_map_matches_the_cpu_map_and_map_mut_writes_back() {
if warp_has_no_cuda_device("cuda_map_matches_the_cpu_map_and_map_mut_writes_back") {
return;
}
if !cuda_require::cuda_available_or_skip("cuda_map_matches_the_cpu_map_and_map_mut_writes_back")
{
return;
}
let device = edgefirst_tensor::d3d11::device().unwrap();
let t = Tensor::<u8>::image(
256,
128,
PixelFormat::Rgba,
Some(TensorMemory::DmaBuf),
CpuAccess::ReadWrite,
)
.unwrap();
let pattern: Vec<u8> = (0..256 * 128 * 4).map(|i| (i * 7 + 13) as u8).collect();
{
let mut m = t.map().unwrap();
copy_rows(
m.as_mut_slice(),
&pattern,
256 * 4,
t.row_stride().unwrap_or(256 * 4),
);
}
let v = device.signal().unwrap();
t.set_gpu_write(v).unwrap();
let cm = t.cuda_map().expect("CUDA handle attached at allocation");
assert_eq!(cm.len(), 256 * 128 * 4);
let mut host = vec![0u8; cm.len()];
assert!(unsafe {
edgefirst_tensor::memcpy_device_to_host(
host.as_mut_ptr().cast(),
cm.device_ptr(),
host.len(),
)
});
assert_eq!(host, pattern);
drop(cm);
{
let cm = t.cuda_map_mut().unwrap();
let ones = vec![1u8; cm.len()];
assert!(unsafe {
edgefirst_tensor::memcpy_host_to_device(
cm.device_ptr(),
ones.as_ptr().cast(),
ones.len(),
)
});
}
let m = t.map_read().unwrap();
let stride = t.row_stride().unwrap_or(256 * 4);
for row in 0..128 {
let at = row * stride;
assert!(
m.as_slice()[at..at + 256 * 4].iter().all(|&b| b == 1),
"row {row} was not written back"
);
}
}
#[test]
fn cuda_map_covers_a_texture_whose_allocation_the_driver_padded() {
if warp_has_no_cuda_device("cuda_map_covers_a_texture_whose_allocation_the_driver_padded") {
return;
}
if !cuda_require::cuda_available_or_skip(
"cuda_map_covers_a_texture_whose_allocation_the_driver_padded",
) {
return;
}
let (w, h) = (640usize, 480usize);
let t = Tensor::<u8>::image(
w,
h,
PixelFormat::Rgba,
Some(TensorMemory::DmaBuf),
CpuAccess::ReadWrite,
)
.unwrap();
let pattern: Vec<u8> = (0..w * h * 4).map(|i| (i % 251) as u8).collect();
{
let mut m = t.map().unwrap();
copy_rows(
m.as_mut_slice(),
&pattern,
w * 4,
t.row_stride().unwrap_or(w * 4),
);
}
let cm = t.cuda_map().expect("the padded declaration imports");
assert_eq!(cm.len(), w * h * 4);
let mut host = vec![0u8; cm.len()];
assert!(unsafe {
edgefirst_tensor::memcpy_device_to_host(
host.as_mut_ptr().cast(),
cm.device_ptr(),
host.len(),
)
});
assert_eq!(host, pattern);
}
#[test]
fn cuda_map_reads_the_nv12_combined_plane() {
if warp_has_no_cuda_device("cuda_map_reads_the_nv12_combined_plane") {
return;
}
if !cuda_require::cuda_available_or_skip("cuda_map_reads_the_nv12_combined_plane") {
return;
}
let (w, h) = (640usize, 480usize);
let t = Tensor::<u8>::image(
w,
h,
PixelFormat::Nv12,
Some(TensorMemory::DmaBuf),
CpuAccess::ReadWrite,
)
.unwrap();
let layout = t.d3d11_layout().unwrap();
let stride = layout.tight_row_bytes();
assert_eq!(t.row_stride().unwrap_or(stride), stride);
let rows = layout.texture_height;
assert_eq!(rows, PixelFormat::Nv12.allocation_shape(w, h).unwrap()[0]);
let pattern: Vec<u8> = (0..stride * rows).map(|i| (i % 253) as u8).collect();
{
let mut m = t.map().unwrap();
copy_rows(m.as_mut_slice(), &pattern, stride, stride);
}
let cm = t.cuda_map().expect("the combined plane imports");
assert_eq!(
cm.len(),
stride * rows,
"the mapping is the whole combined plane at the texture's own row"
);
let mut host = vec![0u8; cm.len()];
assert!(unsafe {
edgefirst_tensor::memcpy_device_to_host(
host.as_mut_ptr().cast(),
cm.device_ptr(),
host.len(),
)
});
assert_eq!(host, pattern);
}
#[test]
fn blob_export_import_shares_a_texture_in_process() {
let t = TensorDyn::image(
32,
16,
PixelFormat::Rgba,
DType::U8,
Some(TensorMemory::DmaBuf),
CpuAccess::ReadWrite,
)
.unwrap();
{
let mut m = t.map_bytes(CpuAccess::ReadWrite).unwrap();
m.as_mut_slice()[..2].copy_from_slice(&[3, 4]);
}
let (blob, fds) = edgefirst_tensor::blob::export(&t, TransportMode::Reference).unwrap();
assert!(
fds.is_empty(),
"an NT handle does not travel in an fd table"
);
let view = edgefirst_tensor::blob::BlobView::parse(&blob).unwrap();
assert_eq!(view.header().pid, std::process::id());
let planes = view.planes().unwrap();
assert!(planes
.iter()
.all(|p| p.handle_bytes.len() == edgefirst_tensor::blob::D3D11_HANDLE_BYTES));
let back = edgefirst_tensor::blob::import(&blob, &[]).unwrap();
assert_eq!(back.memory(), TensorMemory::DmaBuf);
assert_eq!(back.format(), Some(PixelFormat::Rgba));
assert_eq!(back.shape(), t.shape());
assert_eq!(
&back.map_bytes(CpuAccess::Read).unwrap().as_slice()[..2],
&[3, 4]
);
}
#[test]
fn blob_import_keeps_the_local_pitch_not_the_blobs_stride() {
let (w, h) = (37usize, 16usize);
let t = TensorDyn::image(
w,
h,
PixelFormat::Rgba,
DType::U8,
Some(TensorMemory::DmaBuf),
CpuAccess::ReadWrite,
)
.unwrap();
let (blob, _) = edgefirst_tensor::blob::export(&t, TransportMode::Reference).unwrap();
let handle = t.d3d11_shared_handle().unwrap();
let direct = unsafe {
TensorDyn::from_d3d11_shared_handle(
handle.as_raw_handle(),
w,
h,
PixelFormat::Rgba,
DType::U8,
CpuAccess::ReadWrite,
None,
None,
)
}
.unwrap();
let view = edgefirst_tensor::blob::BlobView::parse(&blob).unwrap();
let regions = edgefirst_tensor::blob::region_offsets(&view.header()).unwrap();
let carried = view.planes().unwrap()[0].stride;
let bogus = carried + 64;
let at = regions.planes + 16;
let mut tampered = blob.clone();
tampered[at..at + 8].copy_from_slice(&bogus.to_le_bytes());
let back = edgefirst_tensor::blob::import(&tampered, &[]).unwrap();
assert_eq!(
back.row_stride(),
direct.row_stride(),
"the import keeps the pitch this device reports for the texture"
);
assert_eq!(
back.effective_row_stride(),
direct.effective_row_stride(),
"and reports the same row as a fresh open of the same texture"
);
assert_ne!(
back.effective_row_stride(),
Some(bogus as usize),
"the blob's stride field must not reach the imported tensor"
);
}
struct TempBlob(std::path::PathBuf);
impl Drop for TempBlob {
fn drop(&mut self) {
let _ = std::fs::remove_file(&self.0);
}
}
#[test]
fn blob_import_from_another_process_duplicates_the_handles() {
if let Ok(path) = std::env::var("EF_D3D11_CHILD") {
return child_imports_the_exported_blob(&path);
}
let t = TensorDyn::image(
32,
16,
PixelFormat::Rgba,
DType::U8,
Some(TensorMemory::DmaBuf),
CpuAccess::ReadWrite,
)
.unwrap();
{
let mut m = t.map_bytes(CpuAccess::ReadWrite).unwrap();
m.as_mut_slice()[..2].copy_from_slice(&[5, 6]);
}
let value = edgefirst_tensor::d3d11::device()
.unwrap()
.signal()
.expect("fence");
t.set_gpu_write(value).unwrap();
let (blob, _) = edgefirst_tensor::blob::export(&t, TransportMode::Reference).unwrap();
let file =
TempBlob(std::env::temp_dir().join(format!("ef-d3d11-blob-{}.bin", std::process::id())));
std::fs::write(&file.0, &blob).unwrap();
let status = std::process::Command::new(std::env::current_exe().unwrap())
.args([
"blob_import_from_another_process_duplicates_the_handles",
"--exact",
"--nocapture",
])
.env("EF_D3D11_CHILD", &file.0)
.status()
.unwrap();
drop(t);
assert!(
status.success(),
"child could not import the exported texture"
);
}
fn child_imports_the_exported_blob(path: &str) {
let blob = std::fs::read(path).expect("the parent wrote the blob");
let t = edgefirst_tensor::blob::import(&blob, &[]).expect("import in the child");
assert_eq!(t.memory(), TensorMemory::DmaBuf);
let completion = t
.gpu_completion()
.expect("gpu_completion")
.expect("the blob carried a completion, so the import recorded a local one");
assert_ne!(
completion.value, 0,
"the import recorded a value on this process's fence"
);
let d = edgefirst_tensor::d3d11::device().unwrap();
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(5);
while d.completed_value() < completion.value {
assert!(
std::time::Instant::now() < deadline,
"the recorded value {} never completed on this process's fence (at {})",
completion.value,
d.completed_value()
);
std::thread::yield_now();
}
assert_eq!(
&t.map_bytes(CpuAccess::Read).unwrap().as_slice()[..2],
&[5, 6]
);
if edgefirst_tensor::is_cuda_available() {
match t.cuda_map() {
Some(m) => assert!(
!m.device_ptr().is_null(),
"the CUDA map has a device pointer"
),
None => report_skip("no CUDA registration on the imported texture"),
}
}
}
fn ramped_rgba(w: usize, h: usize) -> TensorDyn {
let t = TensorDyn::image(
w,
h,
PixelFormat::Rgba,
DType::U8,
Some(TensorMemory::DmaBuf),
CpuAccess::ReadWrite,
)
.expect("texture alloc");
assert_eq!(t.memory(), TensorMemory::DmaBuf);
let mut m = t.map_bytes(CpuAccess::Write).expect("map parent");
for (i, b) in m.as_mut_slice().iter_mut().enumerate() {
*b = (i & 0xff) as u8;
}
drop(m);
t
}
#[test]
fn d3d11_view_descriptor_imports_at_the_window_with_the_textures_pitch() {
let parent = ramped_rgba(64, 64);
let pitch = parent.effective_row_stride().expect("parent pitch");
let view = parent
.view(edgefirst_tensor::Region::new(8, 8, 16, 16))
.expect("view");
let desc = view.descriptor_pinned(None);
assert_eq!(desc.kind, edgefirst_tensor::protocol::kind::D3D11_TEXTURE);
assert_eq!(desc.shape(), &[16, 16, 4]);
let rebuilt = TensorDyn::import_descriptor(&desc).expect("a window is a valid shape");
assert_eq!(rebuilt.shape(), &[16, 16, 4], "narrowed to the window");
assert_eq!(
rebuilt.effective_row_stride(),
Some(pitch),
"rows are still the texture's rows"
);
assert_eq!(
rebuilt.plane_offset(),
None,
"the descriptor cannot carry the offset; the capsule restores it"
);
let m = rebuilt.map_bytes(CpuAccess::Read).expect("map rebuilt");
assert_eq!(m.as_slice()[0], 0);
}
#[test]
fn d3d11_window_descriptor_is_packed_and_non_empty_only() {
let nv12 = TensorDyn::image(
64,
64,
PixelFormat::Nv12,
DType::U8,
Some(TensorMemory::DmaBuf),
CpuAccess::ReadWrite,
)
.expect("nv12 alloc");
let mut desc = nv12.descriptor_pinned(None);
desc.ndim = 2;
desc.shape[0] = 32;
desc.shape[1] = 32;
let err = TensorDyn::import_descriptor(&desc).expect_err("semi-planar window");
assert!(matches!(err, Error::InvalidArgument(_)), "{err}");
let rgba = ramped_rgba(64, 64);
for (label, shape) in [
("wrong channels", [16usize, 16, 3]),
("empty rows", [0, 16, 4]),
("taller than the texture", [65, 16, 4]),
] {
let mut desc = rgba.descriptor_pinned(None);
desc.shape[0] = shape[0] as u64;
desc.shape[1] = shape[1] as u64;
desc.shape[2] = shape[2] as u64;
let err = TensorDyn::import_descriptor(&desc).expect_err(label);
assert!(matches!(err, Error::InvalidArgument(_)), "{label}: {err}");
}
}
#[test]
fn set_plane_offset_moves_the_d3d11_map_window() {
let parent = ramped_rgba(64, 64);
let view = parent
.view(edgefirst_tensor::Region::new(8, 8, 16, 16))
.expect("view");
let offset = view.plane_offset().expect("a view carries its offset");
let mut rebuilt =
TensorDyn::import_descriptor(&view.descriptor_pinned(None)).expect("reconstruct");
rebuilt.set_plane_offset(offset);
assert_eq!(rebuilt.plane_offset(), Some(offset));
let m = rebuilt
.map_bytes(CpuAccess::Read)
.expect("map after the offset");
let s = m.as_slice();
assert_ne!(
s[0], 0,
"map() still starts at the texture's origin -- set_plane_offset was a \
no-op on D3D11 storage (issue #161)"
);
let pitch = rebuilt.effective_row_stride().expect("pitch");
for row in 0..16 {
for x in 0..16 * 4 {
let at = row * pitch + x;
assert_eq!(
s[at],
((offset + at) & 0xff) as u8,
"window byte {at} must be parent[{}]",
offset + at
);
}
}
}
#[test]
fn nested_d3d11_subviews_do_not_compound_their_plane_offset() {
let parent = ramped_rgba(64, 64);
let pitch = parent.effective_row_stride().expect("pitch");
let outer = parent
.view(edgefirst_tensor::Region::new(8, 8, 32, 32))
.expect("outer view");
assert_eq!(outer.plane_offset(), Some(8 * pitch + 8 * 4));
let inner = outer
.view(edgefirst_tensor::Region::new(4, 4, 16, 16))
.expect("inner view");
let expected = 12 * pitch + 12 * 4;
assert_eq!(
inner.plane_offset(),
Some(expected),
"offsets add once, not twice"
);
let m = inner.map_bytes(CpuAccess::Read).expect("map nested view");
assert_eq!(
m.as_slice()[0],
(expected & 0xff) as u8,
"nested window byte 0 must be parent[{expected}], not a compounded offset"
);
}
#[test]
fn set_format_clears_the_d3d11_map_window() {
let parent = ramped_rgba(64, 64);
let view = parent
.view(edgefirst_tensor::Region::new(8, 8, 16, 16))
.expect("view");
let mut t = TensorDyn::import_descriptor(&view.descriptor_pinned(None)).expect("reconstruct");
t.set_plane_offset(view.plane_offset().expect("offset"));
assert_ne!(
t.map_bytes(CpuAccess::Read).expect("map before").as_slice()[0],
0,
"precondition: the offset is live before the format change"
);
t.set_format(PixelFormat::Bgra).expect("new format");
assert_eq!(t.plane_offset(), None, "the wrapper field is cleared");
assert_eq!(
t.map_bytes(CpuAccess::Read).expect("map after").as_slice()[0],
0,
"map() still starts at the old offset -- set_format cleared the wrapper \
field but not the D3D11 storage (issue #161)"
);
}
#[test]
fn reshape_clears_the_d3d11_map_window() {
let parent = ramped_rgba(64, 64);
let view = parent
.view(edgefirst_tensor::Region::new(8, 8, 16, 16))
.expect("view");
let mut t = TensorDyn::import_descriptor(&view.descriptor_pinned(None)).expect("reconstruct");
t.set_plane_offset(view.plane_offset().expect("offset"));
assert_ne!(
t.map_bytes(CpuAccess::Read).expect("map before").as_slice()[0],
0
);
t.reshape(&[16 * 16 * 4]).expect("same element count");
assert_eq!(t.plane_offset(), None);
assert_eq!(
t.map_bytes(CpuAccess::Read).expect("map after").as_slice()[0],
0,
"map() still starts at the old offset after reshape (issue #161)"
);
}
#[test]
fn d3d11_map_refuses_a_plane_offset_past_the_backing() {
let parent = ramped_rgba(64, 64);
let mut t = TensorDyn::import_descriptor(&parent.descriptor_pinned(None)).expect("reconstruct");
let backing = t.effective_row_stride().expect("pitch") * 64;
t.set_plane_offset(backing + 1);
let err = t
.map_bytes(CpuAccess::Read)
.expect_err("a window past the backing");
assert!(matches!(err, Error::InsufficientCapacity { .. }), "{err}");
t.set_plane_offset(backing);
let err = t
.map_bytes(CpuAccess::Read)
.expect_err("an empty window for a non-empty tensor");
assert!(matches!(err, Error::InsufficientCapacity { .. }), "{err}");
t.set_plane_offset(1);
let err = t
.map_bytes(CpuAccess::Read)
.expect_err("a window one byte short of the image");
assert!(matches!(err, Error::InsufficientCapacity { .. }), "{err}");
t.set_plane_offset(0);
assert!(!t.map_bytes(CpuAccess::Read).expect("origin").is_empty());
}
#[test]
fn one_row_d3d11_view_descriptor_maps_the_producers_texels() {
let parent = ramped_rgba(64, 64);
let pitch = parent.effective_row_stride().expect("parent pitch");
for (x, y) in [(8usize, 8usize), (48, 63)] {
let view = parent
.view(edgefirst_tensor::Region::new(x, y, 16, 1))
.expect("one-row view");
assert_eq!(
view.effective_row_stride(),
Some(16 * 4),
"a one-row view is tight"
);
let offset = view.plane_offset().expect("a view carries its offset");
assert_eq!(offset, y * pitch + x * 4, "the offset is in the pitch");
let desc = view.descriptor_pinned(None);
let mut rebuilt = TensorDyn::import_descriptor(&desc).expect("a one-row window");
assert_eq!(rebuilt.shape(), &[1, 16, 4]);
assert_eq!(
rebuilt.effective_row_stride(),
Some(16 * 4),
"a narrowed one-row window keeps view()'s tight stride"
);
rebuilt.set_plane_offset(offset);
let expect: Vec<u8> = (offset..offset + 16 * 4)
.map(|i| (i & 0xff) as u8)
.collect();
let m = rebuilt
.map_bytes(CpuAccess::Read)
.expect("the window maps, last row included");
assert_eq!(
m.as_slice(),
expect.as_slice(),
"rebuilt window at ({x}, {y})"
);
let v = view.map_bytes(CpuAccess::Read).expect("map the view");
assert_eq!(v.as_slice(), expect.as_slice(), "fresh view at ({x}, {y})");
}
}