use std::ffi::c_void;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use gpu_handle_types::{
CudaBufferHandle, CudaPtr2D, D3D11Texture, D3D12BufferHandle, D3D12Resource, GpuResource, InvalidHandleError,
KeepAlive, MetalBufferHandle, MetalTextureHandle, OpenClMem, PixelFormat, ResourceKeepAlive, VkBufferHandle,
VkImage,
};
static_assertions::assert_impl_all!(GpuResource: Clone);
#[cfg(not(target_family = "wasm"))]
static_assertions::assert_impl_all!(GpuResource: Send, Sync);
#[cfg(all(target_family = "wasm", feature = "web", feature = "wgpu"))]
static_assertions::assert_not_impl_any!(GpuResource: Send, Sync);
static_assertions::assert_impl_all!(VkImage: Send, Sync, Clone);
static_assertions::assert_impl_all!(D3D11Texture: Send, Sync, Clone);
static_assertions::assert_impl_all!(D3D12Resource: Send, Sync, Clone);
static_assertions::assert_impl_all!(CudaPtr2D: Send, Sync, Clone);
static_assertions::assert_impl_all!(OpenClMem: Send, Sync, Clone);
static_assertions::assert_impl_all!(MetalTextureHandle: Send, Sync, Clone);
static_assertions::assert_impl_all!(Arc<dyn ResourceKeepAlive>: Clone);
#[cfg(not(target_family = "wasm"))]
static_assertions::assert_impl_all!(Arc<dyn ResourceKeepAlive>: Send, Sync);
#[cfg(target_family = "wasm")]
static_assertions::assert_not_impl_any!(Arc<dyn ResourceKeepAlive>: Send, Sync);
#[test]
fn vk_image_rejects_null_handle() {
let r = unsafe {
VkImage::try_from_raw(
0, 0x1234 as *mut c_void, core::ptr::null_mut(), core::ptr::null_mut(), None, 0, None, )
};
assert!(matches!(r, Err(InvalidHandleError::NullHandle(_))));
}
#[test]
fn vk_image_accepts_null_device_for_trusted_import() {
let r = unsafe {
VkImage::try_from_raw(
0xABCD,
core::ptr::null_mut(),
core::ptr::null_mut(),
core::ptr::null_mut(),
None,
0,
None,
)
};
let h = r.expect("null device is permitted on the trusted-import path");
assert_eq!(h.image(), 0xABCD);
assert!(h.device().is_null());
}
#[test]
fn vk_image_accepts_valid_inputs() {
let r = unsafe {
VkImage::try_from_raw(
0xDEAD_BEEF,
0x1000 as *mut c_void,
0x2000 as *mut c_void,
0x3000 as *mut c_void,
Some([1u8; 16]),
64,
None,
)
};
let h = r.expect("valid inputs accepted");
assert_eq!(h.image(), 0xDEAD_BEEF);
assert_eq!(h.row_pitch(), 64);
assert_eq!(h.uuid(), Some([1u8; 16]));
}
#[test]
fn vk_buffer_rejects_zero_size() {
let r = unsafe { VkBufferHandle::try_from_raw(0xAA, std::ptr::dangling_mut::<c_void>(), None, 0, None) };
assert!(matches!(r, Err(InvalidHandleError::ZeroSize(_))));
}
#[test]
fn d3d12_buffer_rejects_zero_size() {
let r = unsafe { D3D12BufferHandle::try_from_raw(std::ptr::dangling_mut::<c_void>(), None, 0, None) };
assert!(matches!(r, Err(InvalidHandleError::ZeroSize(_))));
}
#[test]
fn cuda_ptr_rejects_null() {
let r = unsafe { CudaPtr2D::try_from_raw(0, 64, None, None) };
assert!(matches!(r, Err(InvalidHandleError::NullHandle(_))));
}
#[test]
fn cuda_buffer_rejects_zero_size() {
let r = unsafe { CudaBufferHandle::try_from_raw(0xAA, 0, None, None) };
assert!(matches!(r, Err(InvalidHandleError::ZeroSize(_))));
}
#[test]
fn cuda_ptr_plane_row_pitch_falls_back_to_row_pitch() {
let h = unsafe { CudaPtr2D::try_from_raw(0xAA, 256, None, None) }.expect("non-null");
assert_eq!(h.plane_row_pitch(0), 256);
assert_eq!(h.plane_row_pitch(1), 256);
assert_eq!(h.plane_row_pitch(2), 256);
assert_eq!(h.plane_row_pitch(3), 256);
assert_eq!(h.plane_row_pitch(4), 256);
assert_eq!(h.plane_row_pitches(), None);
}
#[test]
fn cuda_ptr_plane_row_pitch_honours_explicit_per_plane() {
let h = unsafe { CudaPtr2D::try_from_raw(0xAA, 1024, None, None) }
.expect("non-null")
.with_plane_row_pitches([512, 512, 0]);
assert_eq!(h.plane_row_pitch(0), 1024, "plane 0 always uses row_pitch");
assert_eq!(h.plane_row_pitch(1), 512);
assert_eq!(h.plane_row_pitch(2), 512);
assert_eq!(h.plane_row_pitch(3), 1024);
assert_eq!(h.plane_row_pitches(), Some([512, 512, 0]));
}
#[test]
fn opencl_rejects_null_pointers() {
let r = unsafe { OpenClMem::try_from_raw(core::ptr::null_mut(), std::ptr::dangling_mut::<c_void>(), 0, None) };
assert!(matches!(r, Err(InvalidHandleError::NullPointer(_))));
}
#[test]
fn metal_handles_reject_null() {
let r = unsafe { MetalTextureHandle::try_from_raw(core::ptr::null_mut(), None, None) };
assert!(matches!(r, Err(InvalidHandleError::NullPointer(_))));
let r = unsafe { MetalBufferHandle::try_from_raw(core::ptr::null_mut(), 0, None, None) };
assert!(matches!(r, Err(InvalidHandleError::NullPointer(_))));
}
#[test]
fn d3d_resources_reject_null() {
let r = unsafe { D3D11Texture::try_from_raw(core::ptr::null_mut(), None, 0, None) };
assert!(matches!(r, Err(InvalidHandleError::NullPointer(_))));
let r = unsafe { D3D12Resource::try_from_raw(core::ptr::null_mut(), None, 0, None) };
assert!(matches!(r, Err(InvalidHandleError::NullPointer(_))));
}
#[derive(Debug)]
struct DropCounter {
counter: Arc<AtomicUsize>,
}
impl Drop for DropCounter {
fn drop(&mut self) {
self.counter.fetch_add(1, Ordering::SeqCst);
}
}
impl gpu_handle_types::ResourceKeepAlive for DropCounter {}
#[test]
fn keep_alive_drop_runs_once_on_last_clone() {
let counter = Arc::new(AtomicUsize::new(0));
let dc = Arc::new(DropCounter { counter: counter.clone() });
let keep: KeepAlive = dc.clone();
let h = unsafe {
VkImage::try_from_raw(
0x1,
std::ptr::dangling_mut::<c_void>(),
core::ptr::null_mut(),
core::ptr::null_mut(),
None,
0,
Some(keep),
)
}
.unwrap();
let h2 = h.clone();
let h3 = h.clone();
assert_eq!(counter.load(Ordering::SeqCst), 0, "no drop while clones live");
drop(h);
drop(h2);
assert_eq!(counter.load(Ordering::SeqCst), 0, "no drop while any clone is alive");
drop(h3);
drop(dc); assert_eq!(counter.load(Ordering::SeqCst), 1, "exactly one drop after last clone");
}
#[test]
fn keep_alive_trait_object_dyn_dispatch() {
#[derive(Debug)]
struct CustomMarker(#[allow(dead_code)] u32);
impl gpu_handle_types::ResourceKeepAlive for CustomMarker {}
let m: KeepAlive = Arc::new(CustomMarker(42));
let _: KeepAlive = m.clone();
}
#[test]
fn gpu_resource_clone_propagates_through_enum() {
let bytes = vec![0u8; 1024].into_boxed_slice();
let ptr = bytes.as_ptr() as *mut u8;
let size = bytes.len();
Box::leak(bytes);
let cpu = unsafe { gpu_handle_types::CpuBytes::try_from_raw(ptr, size, 64, PixelFormat::RgbaU8, None).unwrap() };
let r = GpuResource::CpuBytes(cpu);
let r2 = r.clone();
let _r3 = r2.clone();
let _ = format!("{:?}", r);
}
#[test]
fn opengl_simple_constructors_work() {
let tex = GpuResource::opengl_texture_simple(42);
let buf = GpuResource::opengl_buffer_simple(7, 1024);
match tex {
GpuResource::GlTextureHandle(h) => assert_eq!(h.name(), 42),
_ => panic!("wrong variant"),
}
match buf {
GpuResource::GlBufferHandle(h) => assert_eq!(h.name(), 7),
_ => panic!("wrong variant"),
}
}
#[test]
#[should_panic(expected = "must be non-zero")]
fn opengl_simple_panics_on_zero_name() {
GpuResource::opengl_texture_simple(0);
}