use edgefirst_tensor_abi::{EfClientState, EfCudaMapFn, EfCudaUnmapFn, EfCudaUnregisterFn};
use libloading::Library;
#[cfg(target_os = "windows")]
use std::ffi::c_char;
use std::ffi::c_void;
use std::os::raw::{c_int, c_uint};
#[cfg(all(target_os = "windows", feature = "static"))]
use std::os::windows::io::{AsRawHandle, OwnedHandle, RawHandle};
use std::sync::{Arc, OnceLock};
#[cfg(target_os = "windows")]
use windows::core::PCWSTR;
#[cfg(target_os = "windows")]
use windows::Win32::System::LibraryLoader::{GetModuleFileNameW, GetModuleHandleW};
pub(crate) type CudaError = c_int; pub type GraphicsResource = *mut c_void; pub(crate) type ExternalMemory = *mut c_void; pub type CudaStream = *mut c_void; #[cfg(target_os = "windows")]
pub(crate) type MipmappedArray = *mut c_void; #[cfg(target_os = "windows")]
pub(crate) type CudaArray = *mut c_void; #[cfg(target_os = "windows")]
pub(crate) type ExternalSemaphore = *mut c_void;
#[allow(non_snake_case, dead_code)]
pub(crate) struct CudaTable {
_lib: Library,
pub runtime_path: std::path::PathBuf,
pub graphics_gl_register_buffer:
unsafe extern "C" fn(*mut GraphicsResource, c_uint, c_uint) -> CudaError,
pub graphics_map_resources:
unsafe extern "C" fn(c_int, *mut GraphicsResource, *mut c_void) -> CudaError,
pub graphics_get_mapped_pointer:
unsafe extern "C" fn(*mut *mut c_void, *mut usize, GraphicsResource) -> CudaError,
pub graphics_unmap_resources:
unsafe extern "C" fn(c_int, *mut GraphicsResource, *mut c_void) -> CudaError,
pub graphics_unregister_resource: unsafe extern "C" fn(GraphicsResource) -> CudaError,
pub import_external_memory:
unsafe extern "C" fn(*mut ExternalMemory, *const c_void) -> CudaError,
pub external_memory_get_mapped_buffer:
unsafe extern "C" fn(*mut *mut c_void, ExternalMemory, *const c_void) -> CudaError,
pub destroy_external_memory: unsafe extern "C" fn(ExternalMemory) -> CudaError,
pub memcpy: unsafe extern "C" fn(*mut c_void, *const c_void, usize, c_int) -> CudaError,
pub free: unsafe extern "C" fn(*mut c_void) -> CudaError,
pub stream_create: unsafe extern "C" fn(*mut CudaStream) -> CudaError,
pub stream_synchronize: unsafe extern "C" fn(CudaStream) -> CudaError,
pub stream_destroy: unsafe extern "C" fn(CudaStream) -> CudaError,
#[cfg(target_os = "windows")]
pub d3d11_get_device: unsafe extern "C" fn(*mut c_int, *mut c_void) -> CudaError,
#[cfg(target_os = "windows")]
pub set_device: unsafe extern "C" fn(c_int) -> CudaError,
#[cfg(target_os = "windows")]
pub external_memory_get_mapped_mipmapped_array:
unsafe extern "C" fn(*mut MipmappedArray, ExternalMemory, *const c_void) -> CudaError,
#[cfg(target_os = "windows")]
pub get_mipmapped_array_level:
unsafe extern "C" fn(*mut CudaArray, MipmappedArray, c_uint) -> CudaError,
#[cfg(target_os = "windows")]
pub free_mipmapped_array: unsafe extern "C" fn(MipmappedArray) -> CudaError,
#[cfg(target_os = "windows")]
pub memcpy_2d_from_array_async: unsafe extern "C" fn(
*mut c_void,
usize,
CudaArray,
usize,
usize,
usize,
usize,
c_int,
CudaStream,
) -> CudaError,
#[cfg(target_os = "windows")]
pub memcpy_2d_to_array_async: unsafe extern "C" fn(
CudaArray,
usize,
usize,
*const c_void,
usize,
usize,
usize,
c_int,
CudaStream,
) -> CudaError,
#[cfg(target_os = "windows")]
pub malloc: unsafe extern "C" fn(*mut *mut c_void, usize) -> CudaError,
#[cfg(target_os = "windows")]
pub import_external_semaphore:
unsafe extern "C" fn(*mut ExternalSemaphore, *const c_void) -> CudaError,
#[cfg(target_os = "windows")]
pub wait_external_semaphores_async: unsafe extern "C" fn(
*const ExternalSemaphore,
*const c_void,
c_uint,
CudaStream,
) -> CudaError,
#[cfg(target_os = "windows")]
pub destroy_external_semaphore: unsafe extern "C" fn(ExternalSemaphore) -> CudaError,
#[cfg(target_os = "windows")]
pub get_error_string: unsafe extern "C" fn(CudaError) -> *const c_char,
}
static TABLE: OnceLock<Option<CudaTable>> = OnceLock::new();
#[cfg(miri)]
fn load() -> Option<CudaTable> {
None
}
#[cfg(all(not(miri), not(target_os = "windows")))]
fn load() -> Option<CudaTable> {
for name in [
"libcudart.so",
"libcudart.so.13",
"libcudart.so.12",
"libcudart.so.11.0",
] {
let Ok(lib) = (unsafe { Library::new(name) }) else {
continue;
};
if let Some(table) = load_table(lib, std::path::PathBuf::from(name)) {
log::debug!("CUDA runtime loaded from {}", table.runtime_path.display());
return Some(table);
}
}
None
}
#[cfg(all(not(miri), target_os = "windows"))]
fn load() -> Option<CudaTable> {
let names = ["cudart64_13.dll", "cudart64_12.dll", "cudart64_110.dll"];
let mut candidates: Vec<(std::path::PathBuf, bool)> = names
.iter()
.map(|n| (std::path::PathBuf::from(n), false))
.collect();
if let Ok(cuda_path) = std::env::var("CUDA_PATH") {
for n in names {
let full = std::path::Path::new(&cuda_path).join("bin").join(n);
candidates.push((full, true));
}
}
for (candidate, is_full_path) in &candidates {
let Ok(lib) = (unsafe { Library::new(candidate) }) else {
continue;
};
let runtime_path = if *is_full_path {
candidate.clone()
} else {
resolve_loaded_module_path(candidate.as_os_str()).unwrap_or_else(|| candidate.clone())
};
if let Some(table) = load_table(lib, runtime_path) {
log::debug!("CUDA runtime loaded from {}", table.runtime_path.display());
return Some(table);
}
}
None
}
#[cfg(target_os = "windows")]
fn resolve_loaded_module_path(name: &std::ffi::OsStr) -> Option<std::path::PathBuf> {
use std::os::windows::ffi::{OsStrExt, OsStringExt};
let wide: Vec<u16> = name.encode_wide().chain(std::iter::once(0)).collect();
let handle = unsafe { GetModuleHandleW(PCWSTR(wide.as_ptr())) }.ok()?;
let mut buf = [0u16; 1024];
let len = unsafe { GetModuleFileNameW(Some(handle), &mut buf) } as usize;
(len > 0 && len < buf.len())
.then(|| std::path::PathBuf::from(std::ffi::OsString::from_wide(&buf[..len])))
}
#[cfg(not(miri))]
fn load_table(lib: Library, runtime_path: std::path::PathBuf) -> Option<CudaTable> {
macro_rules! sym {
($n:literal) => {{
match unsafe { lib.get(concat!($n, "\0").as_bytes()) } {
Ok(s) => *s,
Err(_) => {
log::debug!("CUDA runtime missing required symbol {}", $n);
return None;
}
}
}};
}
#[cfg(target_os = "windows")]
macro_rules! sym_versioned {
($v2:literal, $v1:literal) => {{
let found = unsafe { lib.get(concat!($v2, "\0").as_bytes()) }
.or_else(|_| unsafe { lib.get(concat!($v1, "\0").as_bytes()) });
match found {
Ok(s) => *s,
Err(_) => {
log::debug!("CUDA runtime missing required symbol {} (or {})", $v2, $v1);
return None;
}
}
}};
}
Some(CudaTable {
runtime_path,
graphics_gl_register_buffer: sym!("cudaGraphicsGLRegisterBuffer"),
graphics_map_resources: sym!("cudaGraphicsMapResources"),
graphics_get_mapped_pointer: sym!("cudaGraphicsResourceGetMappedPointer"),
graphics_unmap_resources: sym!("cudaGraphicsUnmapResources"),
graphics_unregister_resource: sym!("cudaGraphicsUnregisterResource"),
import_external_memory: sym!("cudaImportExternalMemory"),
external_memory_get_mapped_buffer: sym!("cudaExternalMemoryGetMappedBuffer"),
destroy_external_memory: sym!("cudaDestroyExternalMemory"),
memcpy: sym!("cudaMemcpy"),
free: sym!("cudaFree"),
stream_create: sym!("cudaStreamCreate"),
stream_synchronize: sym!("cudaStreamSynchronize"),
stream_destroy: sym!("cudaStreamDestroy"),
#[cfg(target_os = "windows")]
d3d11_get_device: sym!("cudaD3D11GetDevice"),
#[cfg(target_os = "windows")]
set_device: sym!("cudaSetDevice"),
#[cfg(target_os = "windows")]
external_memory_get_mapped_mipmapped_array: sym!(
"cudaExternalMemoryGetMappedMipmappedArray"
),
#[cfg(target_os = "windows")]
get_mipmapped_array_level: sym!("cudaGetMipmappedArrayLevel"),
#[cfg(target_os = "windows")]
free_mipmapped_array: sym!("cudaFreeMipmappedArray"),
#[cfg(target_os = "windows")]
memcpy_2d_from_array_async: sym!("cudaMemcpy2DFromArrayAsync"),
#[cfg(target_os = "windows")]
memcpy_2d_to_array_async: sym!("cudaMemcpy2DToArrayAsync"),
#[cfg(target_os = "windows")]
malloc: sym!("cudaMalloc"),
#[cfg(target_os = "windows")]
import_external_semaphore: sym!("cudaImportExternalSemaphore"),
#[cfg(target_os = "windows")]
wait_external_semaphores_async: sym_versioned!(
"cudaWaitExternalSemaphoresAsync_v2",
"cudaWaitExternalSemaphoresAsync"
),
#[cfg(target_os = "windows")]
destroy_external_semaphore: sym!("cudaDestroyExternalSemaphore"),
#[cfg(target_os = "windows")]
get_error_string: sym!("cudaGetErrorString"),
_lib: lib,
})
}
#[cfg(all(target_os = "windows", feature = "static"))]
fn err_str(t: &CudaTable, rc: CudaError) -> String {
let p = unsafe { (t.get_error_string)(rc) };
if p.is_null() {
return format!("cudaError {rc}");
}
let text = unsafe { std::ffi::CStr::from_ptr(p) }.to_string_lossy();
format!("{text} (cudaError {rc})")
}
pub(crate) fn table() -> Option<&'static CudaTable> {
TABLE.get_or_init(load).as_ref()
}
pub fn is_cuda_available() -> bool {
table().is_some()
}
pub fn runtime_path() -> Option<&'static std::path::Path> {
table().map(|t| t.runtime_path.as_path())
}
pub const CUDA_MEMCPY_DEVICE_TO_HOST: c_int = 2;
pub const CUDA_MEMCPY_HOST_TO_DEVICE: c_int = 1;
pub unsafe fn memcpy_device_to_host(
host: *mut c_void,
device: *const c_void,
count: usize,
) -> bool {
match table() {
Some(t) => unsafe { (t.memcpy)(host, device, count, CUDA_MEMCPY_DEVICE_TO_HOST) == 0 },
None => false,
}
}
pub unsafe fn memcpy_host_to_device(
device: *mut c_void,
host: *const c_void,
count: usize,
) -> bool {
match table() {
Some(t) => unsafe { (t.memcpy)(device, host, count, CUDA_MEMCPY_HOST_TO_DEVICE) == 0 },
None => false,
}
}
pub fn stream_create() -> Option<CudaStream> {
let t = table()?;
let mut stream: CudaStream = std::ptr::null_mut();
if unsafe { (t.stream_create)(&mut stream) } != 0 {
return None;
}
Some(stream)
}
pub unsafe fn stream_synchronize(stream: CudaStream) -> bool {
match table() {
Some(t) => unsafe { (t.stream_synchronize)(stream) == 0 },
None => false,
}
}
pub unsafe fn stream_destroy(stream: CudaStream) {
if let Some(t) = table() {
let _ = unsafe { (t.stream_destroy)(stream) };
}
}
pub fn gl_register_buffer(buffer_id: u32) -> Option<usize> {
let t = table()?;
let mut res: GraphicsResource = std::ptr::null_mut();
let rc = unsafe { (t.graphics_gl_register_buffer)(&mut res, buffer_id, 0) };
if rc != 0 {
log::debug!("cudaGraphicsGLRegisterBuffer(buffer={buffer_id}) failed: cudaError {rc}");
return None;
}
Some(res as usize)
}
pub fn gl_map_resource(resource: usize) -> Option<(usize, usize)> {
let t = table()?;
let mut res = resource as GraphicsResource;
if unsafe { (t.graphics_map_resources)(1, &mut res, std::ptr::null_mut()) } != 0 {
return None;
}
let (mut ptr, mut size) = (std::ptr::null_mut::<c_void>(), 0usize);
if unsafe { (t.graphics_get_mapped_pointer)(&mut ptr, &mut size, res) } != 0 {
unsafe {
(t.graphics_unmap_resources)(1, &mut res, std::ptr::null_mut());
}
return None;
}
Some((ptr as usize, size))
}
pub fn gl_unmap_resource(resource: usize) {
if let Some(t) = table() {
let mut r = resource as GraphicsResource;
unsafe {
(t.graphics_unmap_resources)(1, &mut r, std::ptr::null_mut());
}
}
}
pub fn gl_unregister_resource(resource: usize) {
if let Some(t) = table() {
unsafe {
(t.graphics_unregister_resource)(resource as GraphicsResource);
}
}
}
#[allow(dead_code)] pub(crate) const CUDA_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD: c_uint = 1;
#[allow(dead_code)] #[repr(C)]
pub(crate) struct CudaExternalMemoryHandleDesc {
pub type_: c_int,
pub _pad0: u32,
pub handle_fd: c_int,
pub _union_rest: [u32; 3],
pub size: u64,
pub flags: c_uint,
pub _tail: u32,
}
#[allow(dead_code)] #[repr(C)]
pub(crate) struct CudaExternalMemoryBufferDesc {
pub offset: u64,
pub size: u64,
pub flags: c_uint,
pub _tail: u32,
}
#[cfg(all(target_os = "linux", feature = "static"))]
pub(crate) fn import_dma_fd(fd: i32, size: usize) -> Option<(ExternalMemory, *mut c_void)> {
use std::os::fd::{AsRawFd, BorrowedFd, IntoRawFd};
let t = table()?;
let dup = unsafe { BorrowedFd::borrow_raw(fd) }
.try_clone_to_owned()
.ok()?;
let mut desc: CudaExternalMemoryHandleDesc = unsafe { std::mem::zeroed() };
desc.type_ = CUDA_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD as c_int;
desc.handle_fd = dup.as_raw_fd();
desc.size = size as u64;
let mut ext: ExternalMemory = std::ptr::null_mut();
let rc = unsafe { (t.import_external_memory)(&mut ext, &desc as *const _ as *const c_void) };
if rc != 0 {
log::debug!("cudaImportExternalMemory(OpaqueFd, size={size}) failed: cudaError {rc}");
let raw = dup.into_raw_fd();
if unsafe { libc::close(raw) } != 0 {
log::debug!(
"reclaim of CUDA-import dup fd {raw} after failed import: {}",
std::io::Error::last_os_error()
);
}
return None;
}
let _ = dup.into_raw_fd();
let bdesc = CudaExternalMemoryBufferDesc {
offset: 0,
size: size as u64,
flags: 0,
_tail: 0,
};
let mut dptr: *mut c_void = std::ptr::null_mut();
if unsafe {
(t.external_memory_get_mapped_buffer)(&mut dptr, ext, &bdesc as *const _ as *const c_void)
} != 0
{
unsafe { (t.destroy_external_memory)(ext) };
return None;
}
Some((ext, dptr))
}
#[allow(dead_code)] #[cfg(target_os = "windows")]
pub(crate) const CUDA_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_RESOURCE: c_int = 5;
#[allow(dead_code)] #[cfg(target_os = "windows")]
pub(crate) const CUDA_EXTERNAL_SEMAPHORE_HANDLE_TYPE_D3D11_FENCE: c_int = 4;
#[allow(dead_code)] pub(crate) const CUDA_EXTERNAL_MEMORY_DEDICATED: c_uint = 1;
#[allow(dead_code)] pub(crate) const CUDA_MEMCPY_DEVICE_TO_DEVICE: c_int = 3;
#[allow(dead_code)] pub(crate) const CUDA_CHANNEL_FORMAT_KIND_UNSIGNED: c_int = 1;
#[allow(dead_code)] pub(crate) const CUDA_CHANNEL_FORMAT_KIND_FLOAT: c_int = 2;
#[allow(dead_code)] #[cfg(target_os = "windows")]
#[repr(C)]
pub(crate) struct CudaExternalMemoryHandleDescWin32 {
pub type_: c_int,
pub _pad0: u32,
pub win32_handle: *mut c_void,
pub win32_name: *const c_void,
pub size: u64,
pub flags: c_uint,
pub reserved: [u32; 16],
pub _tail: u32,
}
#[allow(dead_code)] #[repr(C)]
#[derive(Clone, Copy)]
pub(crate) struct CudaChannelFormatDesc {
pub x: c_int,
pub y: c_int,
pub z: c_int,
pub w: c_int,
pub f: c_int,
}
#[allow(dead_code)] #[repr(C)]
#[derive(Clone, Copy)]
pub(crate) struct CudaExtent {
pub width: usize,
pub height: usize,
pub depth: usize,
}
#[allow(dead_code)] #[cfg(target_os = "windows")]
#[repr(C)]
pub(crate) struct CudaExternalMemoryMipmappedArrayDesc {
pub offset: u64,
pub format_desc: CudaChannelFormatDesc,
pub _pad0: u32,
pub extent: CudaExtent,
pub flags: c_uint,
pub num_levels: c_uint,
pub reserved: [u32; 16],
}
#[allow(dead_code)] #[cfg(target_os = "windows")]
#[repr(C)]
pub(crate) struct CudaExternalSemaphoreHandleDesc {
pub type_: c_int,
pub _pad0: u32,
pub win32_handle: *mut c_void,
pub win32_name: *const c_void,
pub flags: c_uint,
pub reserved: [u32; 16],
pub _tail: u32,
}
#[allow(dead_code)] #[cfg(target_os = "windows")]
#[repr(C)]
pub(crate) struct CudaExternalSemaphoreWaitParams {
pub fence_value: u64,
pub nv_sci_sync: u64,
pub keyed_mutex_key: u64,
pub keyed_mutex_timeout_ms: c_uint,
pub _pad0: u32,
pub params_reserved: [u32; 10],
pub flags: c_uint,
pub reserved: [u32; 16],
pub _tail: u32,
}
#[cfg(all(target_os = "windows", feature = "static"))]
struct D3d11External {
ext_mem: ExternalMemory,
mipmapped: MipmappedArray,
level0: CudaArray,
linear: std::sync::Mutex<Option<*mut c_void>>,
bytes: usize,
row_bytes: usize,
rows: usize,
stream: CudaStream,
device: c_int,
semaphore: Option<&'static SharedSemaphore>,
last_write: Arc<std::sync::atomic::AtomicU64>,
}
#[cfg(all(target_os = "windows", feature = "static"))]
pub(crate) fn import_d3d11_texture(
handle: RawHandle,
layout: crate::d3d11_layout::D3d11ImageLayout,
adapter: *mut c_void,
fence_handle: Option<OwnedHandle>,
last_write: Arc<std::sync::atomic::AtomicU64>,
) -> Option<CudaHandle> {
let t = table()?;
static CUDA_DEVICE_SELECTED: OnceLock<Option<c_int>> = OnceLock::new();
let device = (*CUDA_DEVICE_SELECTED.get_or_init(|| {
let mut dev = -1;
let rc = unsafe { (t.d3d11_get_device)(&mut dev, adapter) };
if rc != 0 {
log::debug!(
"cudaD3D11GetDevice for the process adapter: {}; D3D11 texture tensors \
will have no CUDA handle",
err_str(t, rc)
);
return None;
}
let rc = unsafe { (t.set_device)(dev) };
if rc != 0 {
log::debug!("cudaSetDevice({dev}): {}", err_str(t, rc));
return None;
}
Some(dev)
}))?;
let (bits, kind, channels) = match layout.dxgi_format {
crate::d3d11_layout::DXGI_FORMAT_R8_UNORM => (8, CUDA_CHANNEL_FORMAT_KIND_UNSIGNED, 1),
crate::d3d11_layout::DXGI_FORMAT_R8G8_UNORM => (8, CUDA_CHANNEL_FORMAT_KIND_UNSIGNED, 2),
crate::d3d11_layout::DXGI_FORMAT_R8G8B8A8_UNORM
| crate::d3d11_layout::DXGI_FORMAT_B8G8R8A8_UNORM => {
(8, CUDA_CHANNEL_FORMAT_KIND_UNSIGNED, 4)
}
crate::d3d11_layout::DXGI_FORMAT_R16G16B16A16_FLOAT => {
(16, CUDA_CHANNEL_FORMAT_KIND_FLOAT, 4)
}
crate::d3d11_layout::DXGI_FORMAT_R32G32B32A32_FLOAT => {
(32, CUDA_CHANNEL_FORMAT_KIND_FLOAT, 4)
}
other => {
log::debug!("no CUDA channel format for DXGI format {other}");
return None;
}
};
let ch = |i: c_int| if i < channels { bits } else { 0 };
let mut md: CudaExternalMemoryMipmappedArrayDesc = unsafe { std::mem::zeroed() };
md.format_desc = CudaChannelFormatDesc {
x: ch(0),
y: ch(1),
z: ch(2),
w: ch(3),
f: kind,
};
md.extent = CudaExtent {
width: layout.texture_width,
height: layout.texture_height,
depth: 0,
};
md.num_levels = 1;
let (ext, mm) = import_and_map_array(t, handle, &md, layout)?;
let mut level0: CudaArray = std::ptr::null_mut();
let rc = unsafe { (t.get_mipmapped_array_level)(&mut level0, mm, 0) };
let stream = if rc == 0 && !level0.is_null() {
stream_create()
} else {
None
};
let Some(stream) = stream else {
if rc != 0 {
log::debug!("cudaGetMipmappedArrayLevel(0): {}", err_str(t, rc));
} else if level0.is_null() {
log::debug!("cudaGetMipmappedArrayLevel(0) reported success but left a null array");
} else {
log::debug!("cudaStreamCreate for an imported D3D11 texture failed");
}
unsafe {
(t.free_mipmapped_array)(mm);
(t.destroy_external_memory)(ext);
}
return None;
};
let semaphore = fence_handle.and_then(|fh| shared_semaphore(t, fh));
Some(CudaHandle {
size: layout.tight_bytes(),
kind: CudaBacking::D3d11(D3d11External {
ext_mem: ext,
mipmapped: mm,
level0,
linear: std::sync::Mutex::new(None),
bytes: layout.tight_bytes(),
row_bytes: layout.tight_row_bytes(),
rows: layout.texture_height,
stream,
device,
semaphore,
last_write,
}),
})
}
#[cfg(all(target_os = "windows", feature = "static"))]
struct SharedSemaphore {
sem: ExternalSemaphore,
_handle: OwnedHandle,
}
#[cfg(all(target_os = "windows", feature = "static"))]
unsafe impl Send for SharedSemaphore {}
#[cfg(all(target_os = "windows", feature = "static"))]
unsafe impl Sync for SharedSemaphore {}
#[cfg(all(target_os = "windows", feature = "static"))]
fn shared_semaphore(t: &CudaTable, fence_handle: OwnedHandle) -> Option<&'static SharedSemaphore> {
static IMPORTED: OnceLock<Option<SharedSemaphore>> = OnceLock::new();
IMPORTED
.get_or_init(move || {
let mut sd: CudaExternalSemaphoreHandleDesc = unsafe { std::mem::zeroed() };
sd.type_ = CUDA_EXTERNAL_SEMAPHORE_HANDLE_TYPE_D3D11_FENCE;
sd.win32_handle = fence_handle.as_raw_handle();
let mut sem: ExternalSemaphore = std::ptr::null_mut();
let rc = unsafe {
(t.import_external_semaphore)(&mut sem, &sd as *const _ as *const c_void)
};
if rc != 0 {
log::debug!(
"cudaImportExternalSemaphore(D3D11Fence): {}",
err_str(t, rc)
);
return None;
}
Some(SharedSemaphore {
sem,
_handle: fence_handle,
})
})
.as_ref()
}
#[cfg(all(target_os = "windows", feature = "static"))]
fn d3d11_copy_is_ordered(recorded: u64, wait_queued: bool) -> bool {
recorded == 0 || wait_queued
}
#[cfg(all(target_os = "windows", feature = "static"))]
static CUDA_IMPORT_SIZE_PADDED: std::sync::atomic::AtomicBool =
std::sync::atomic::AtomicBool::new(false);
#[cfg(all(target_os = "windows", feature = "static"))]
fn import_and_map_array(
t: &CudaTable,
handle: RawHandle,
md: &CudaExternalMemoryMipmappedArrayDesc,
layout: crate::d3d11_layout::D3d11ImageLayout,
) -> Option<(ExternalMemory, MipmappedArray)> {
const PAGE: usize = 4096;
let tight = layout.tight_bytes();
let start = usize::from(CUDA_IMPORT_SIZE_PADDED.load(std::sync::atomic::Ordering::Relaxed));
for multiple in [1usize, 2, 4].into_iter().skip(start) {
let size = tight.max(PAGE).saturating_mul(multiple);
let mut hd: CudaExternalMemoryHandleDescWin32 = unsafe { std::mem::zeroed() };
hd.type_ = CUDA_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_RESOURCE;
hd.win32_handle = handle;
hd.size = size as u64;
hd.flags = CUDA_EXTERNAL_MEMORY_DEDICATED;
let mut ext: ExternalMemory = std::ptr::null_mut();
let rc = unsafe { (t.import_external_memory)(&mut ext, &hd as *const _ as *const c_void) };
if rc != 0 {
log::debug!(
"cudaImportExternalMemory(D3D11Resource, size {size}): {}",
err_str(t, rc)
);
continue;
}
let mut mm: MipmappedArray = std::ptr::null_mut();
let rc = unsafe {
(t.external_memory_get_mapped_mipmapped_array)(
&mut mm,
ext,
md as *const _ as *const c_void,
)
};
if rc == 0 {
if multiple > 1
&& !CUDA_IMPORT_SIZE_PADDED.swap(true, std::sync::atomic::Ordering::Relaxed)
{
log::debug!(
"this adapter pads D3D11 allocations: a {tight}-byte texture mapped \
only when declared at {multiple}x ({size} bytes), so later imports \
start there"
);
}
return Some((ext, mm));
}
log::debug!(
"cudaExternalMemoryGetMappedMipmappedArray over {size} declared bytes: {}",
err_str(t, rc)
);
unsafe { (t.destroy_external_memory)(ext) };
}
None
}
pub trait CudaGlOps: Send + Sync {
fn map(&self, resource: GraphicsResource) -> Option<(*mut c_void, usize)>;
fn unmap(&self, resource: GraphicsResource);
fn unregister(&self, resource: GraphicsResource);
}
enum CudaBacking {
#[allow(dead_code)] GlBuffer {
resource: GraphicsResource,
ops: Arc<dyn CudaGlOps>,
},
#[allow(dead_code)] ExternalMem {
ext_mem: ExternalMemory,
dptr: *mut c_void,
},
#[cfg(all(target_os = "windows", feature = "static"))]
D3d11(D3d11External),
Detached,
}
unsafe impl Send for CudaBacking {}
unsafe impl Sync for CudaBacking {}
pub struct CudaHandle {
kind: CudaBacking,
size: usize,
}
impl std::fmt::Debug for CudaHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let kind = match &self.kind {
CudaBacking::GlBuffer { .. } => "GlBuffer",
CudaBacking::ExternalMem { .. } => "ExternalMem",
CudaBacking::Detached => "Detached",
#[cfg(all(target_os = "windows", feature = "static"))]
CudaBacking::D3d11(_) => "D3d11",
};
f.debug_struct("CudaHandle")
.field("kind", &kind)
.field("size", &self.size)
.finish()
}
}
impl CudaHandle {
pub fn new_gl(resource: GraphicsResource, size: usize, ops: Arc<dyn CudaGlOps>) -> Self {
Self {
kind: CudaBacking::GlBuffer { resource, ops },
size,
}
}
pub fn into_client_parts(self) -> crate::Result<CudaClientParts> {
let mut this = self;
let kind = std::mem::replace(&mut this.kind, CudaBacking::Detached);
let size = this.size;
match kind {
CudaBacking::GlBuffer { resource, ops } => {
let channel = Arc::new(CudaGlChannel { ops });
let state = EfClientState {
ctx: Arc::as_ptr(&channel) as *const c_void,
retain: Some(cuda_vt_retain),
release: Some(cuda_vt_release),
};
Ok(CudaClientParts {
state,
resource,
size,
map_fn: cuda_vt_map,
unmap_fn: cuda_vt_unmap,
unregister_fn: cuda_vt_unregister,
_channel: channel,
})
}
other => {
this.kind = other;
drop(this);
Err(crate::Error::InvalidArgument(
"cuda attach is for GL-buffer registrations only".into(),
))
}
}
}
#[allow(dead_code)] pub(crate) fn new_external(ext_mem: ExternalMemory, dptr: *mut c_void, size: usize) -> Self {
Self {
kind: CudaBacking::ExternalMem { ext_mem, dptr },
size,
}
}
pub fn map(&self) -> Option<CudaMap<'_>> {
match &self.kind {
CudaBacking::GlBuffer { resource, ops } => {
let (ptr, len) = ops.map(*resource)?;
Some(CudaMap {
ptr,
len,
release: Release::GlUnmap(ops.clone(), *resource),
_marker: std::marker::PhantomData,
})
}
CudaBacking::ExternalMem { dptr, .. } => Some(CudaMap {
ptr: *dptr,
len: self.size,
release: Release::None,
_marker: std::marker::PhantomData,
}),
#[cfg(all(target_os = "windows", feature = "static"))]
CudaBacking::D3d11(x) => Some(CudaMap {
ptr: self.d3d11_copy_in(x)?,
len: x.bytes,
release: Release::None,
_marker: std::marker::PhantomData,
}),
CudaBacking::Detached => None,
}
}
pub fn map_mut(&self) -> Option<CudaMap<'_>> {
#[cfg(all(target_os = "windows", feature = "static"))]
if let CudaBacking::D3d11(x) = &self.kind {
return Some(CudaMap {
ptr: self.d3d11_copy_in(x)?,
len: x.bytes,
release: Release::D3d11Writeback {
level0: x.level0,
row_bytes: x.row_bytes,
rows: x.rows,
stream: x.stream,
device: x.device,
},
_marker: std::marker::PhantomData,
});
}
self.map()
}
#[cfg(all(target_os = "windows", feature = "static"))]
fn d3d11_copy_in(&self, x: &D3d11External) -> Option<*mut c_void> {
let t = table()?;
let rc = unsafe { (t.set_device)(x.device) };
if rc != 0 {
log::debug!("cudaSetDevice({}): {}", x.device, err_str(t, rc));
return None;
}
let mut guard = x.linear.lock().unwrap_or_else(|e| e.into_inner());
if guard.is_none() {
let mut p: *mut c_void = std::ptr::null_mut();
let rc = unsafe { (t.malloc)(&mut p, x.bytes) };
if rc != 0 {
log::debug!("cudaMalloc({}): {}", x.bytes, err_str(t, rc));
return None;
}
*guard = Some(p);
}
let dptr = guard.unwrap();
let value = x.last_write.load(std::sync::atomic::Ordering::Acquire);
let mut queued = false;
if let (Some(shared), true) = (x.semaphore, value != 0) {
let mut wp: CudaExternalSemaphoreWaitParams = unsafe { std::mem::zeroed() };
wp.fence_value = value;
let rc = unsafe {
(t.wait_external_semaphores_async)(
&shared.sem,
&wp as *const _ as *const c_void,
1,
x.stream,
)
};
queued = rc == 0;
if rc != 0 {
log::warn!(
"cudaWaitExternalSemaphoresAsync({value}): {}",
err_str(t, rc)
);
}
}
if !d3d11_copy_is_ordered(value, queued) {
log::warn!(
"CUDA map of a D3D11 texture with a recorded write ({value}) this handle cannot \
wait for ({}); declining the device mapping so the caller falls back to the \
host map, which the immediate context orders",
if x.semaphore.is_some() {
"the wait could not be queued"
} else {
"no external semaphore was imported"
}
);
return None;
}
let rc = unsafe {
(t.memcpy_2d_from_array_async)(
dptr,
x.row_bytes,
x.level0,
0,
0,
x.row_bytes,
x.rows,
CUDA_MEMCPY_DEVICE_TO_DEVICE,
x.stream,
)
};
if rc != 0 {
log::warn!("cudaMemcpy2DFromArrayAsync: {}", err_str(t, rc));
return None;
}
let rc = unsafe { (t.stream_synchronize)(x.stream) };
if rc != 0 {
log::warn!(
"cudaStreamSynchronize after the array copy: {}",
err_str(t, rc)
);
return None;
}
Some(dptr)
}
}
impl Drop for CudaHandle {
fn drop(&mut self) {
match &self.kind {
CudaBacking::Detached => {}
CudaBacking::GlBuffer { resource, ops } => ops.unregister(*resource),
CudaBacking::ExternalMem { ext_mem, dptr: _ } => {
if let Some(t) = table() {
unsafe {
(t.destroy_external_memory)(*ext_mem);
}
}
}
#[cfg(all(target_os = "windows", feature = "static"))]
CudaBacking::D3d11(x) => {
let Some(t) = table() else { return };
let rc = unsafe { (t.set_device)(x.device) };
if rc != 0 {
log::debug!(
"cudaSetDevice({}) before releasing a D3D11 import: {}",
x.device,
err_str(t, rc)
);
}
let linear = x.linear.lock().unwrap_or_else(|e| e.into_inner()).take();
unsafe {
if let Some(p) = linear {
(t.free)(p);
}
(t.stream_destroy)(x.stream);
(t.free_mipmapped_array)(x.mipmapped);
(t.destroy_external_memory)(x.ext_mem);
}
}
}
}
}
struct CudaGlChannel {
ops: Arc<dyn CudaGlOps>,
}
pub struct CudaClientParts {
pub state: EfClientState,
pub resource: GraphicsResource,
pub size: usize,
pub map_fn: EfCudaMapFn,
pub unmap_fn: EfCudaUnmapFn,
pub unregister_fn: EfCudaUnregisterFn,
_channel: Arc<CudaGlChannel>,
}
unsafe extern "C" fn cuda_vt_retain(ctx: *const c_void) {
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| unsafe {
Arc::increment_strong_count(ctx as *const CudaGlChannel);
}));
}
unsafe extern "C" fn cuda_vt_release(ctx: *const c_void) {
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| unsafe {
Arc::decrement_strong_count(ctx as *const CudaGlChannel);
}));
}
unsafe extern "C" fn cuda_vt_map(
ctx: *const c_void,
resource: *mut c_void,
out_ptr: *mut *mut c_void,
out_len: *mut usize,
) -> c_int {
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let channel = unsafe { &*(ctx as *const CudaGlChannel) };
match channel.ops.map(resource) {
Some((ptr, len)) => {
unsafe {
*out_ptr = ptr;
*out_len = len;
}
0
}
None => 1,
}
}))
.unwrap_or(1)
}
unsafe extern "C" fn cuda_vt_unmap(ctx: *const c_void, resource: *mut c_void) {
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let channel = unsafe { &*(ctx as *const CudaGlChannel) };
channel.ops.unmap(resource);
}));
}
unsafe extern "C" fn cuda_vt_unregister(ctx: *const c_void, resource: *mut c_void) {
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let channel = unsafe { &*(ctx as *const CudaGlChannel) };
channel.ops.unregister(resource);
}));
}
struct ImportedCudaGlOps {
state: EfClientState,
map_fn: EfCudaMapFn,
unmap_fn: EfCudaUnmapFn,
unregister_fn: EfCudaUnregisterFn,
}
unsafe impl Send for ImportedCudaGlOps {}
unsafe impl Sync for ImportedCudaGlOps {}
impl Drop for ImportedCudaGlOps {
fn drop(&mut self) {
if let Some(release) = self.state.release {
unsafe { release(self.state.ctx) };
}
}
}
impl CudaGlOps for ImportedCudaGlOps {
fn map(&self, resource: GraphicsResource) -> Option<(*mut c_void, usize)> {
let mut ptr = std::ptr::null_mut();
let mut len = 0usize;
let rc = unsafe { (self.map_fn)(self.state.ctx, resource, &mut ptr, &mut len) };
(rc == 0).then_some((ptr, len))
}
fn unmap(&self, resource: GraphicsResource) {
unsafe { (self.unmap_fn)(self.state.ctx, resource) };
}
fn unregister(&self, resource: GraphicsResource) {
unsafe { (self.unregister_fn)(self.state.ctx, resource) };
}
}
pub unsafe fn client_state_cuda_ops(
state: EfClientState,
map_fn: EfCudaMapFn,
unmap_fn: EfCudaUnmapFn,
unregister_fn: EfCudaUnregisterFn,
) -> crate::Result<Arc<dyn CudaGlOps>> {
if state.ctx.is_null() || state.retain.is_none() || state.release.is_none() {
return Err(crate::Error::InvalidArgument(
"CUDA client state must carry a non-NULL ctx, retain and release".into(),
));
}
let retain = state.retain.expect("checked above");
unsafe { retain(state.ctx) };
Ok(Arc::new(ImportedCudaGlOps {
state,
map_fn,
unmap_fn,
unregister_fn,
}))
}
enum Release {
None,
GlUnmap(Arc<dyn CudaGlOps>, GraphicsResource),
#[cfg(feature = "dynamic")]
FfiUnmap(*mut c_void),
#[cfg(all(target_os = "windows", feature = "static"))]
D3d11Writeback {
level0: CudaArray,
row_bytes: usize,
rows: usize,
stream: CudaStream,
device: c_int,
},
}
pub struct CudaMap<'a> {
ptr: *mut c_void,
len: usize,
release: Release,
_marker: std::marker::PhantomData<&'a ()>,
}
unsafe impl Send for CudaMap<'_> {}
unsafe impl Sync for CudaMap<'_> {}
impl CudaMap<'_> {
pub fn device_ptr(&self) -> *mut c_void {
self.ptr
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
#[cfg(feature = "dynamic")]
pub(crate) unsafe fn from_ffi(raw: *mut c_void) -> Option<Self> {
let mut len = 0usize;
let ptr = unsafe { edgefirst_tensor_ffi::ef_tensor_cuda_device_ptr(raw, &mut len) };
if ptr.is_null() {
unsafe { edgefirst_tensor_ffi::ef_tensor_cuda_unmap(raw) };
return None;
}
Some(CudaMap {
ptr,
len,
release: Release::FfiUnmap(raw),
_marker: std::marker::PhantomData,
})
}
}
impl Drop for CudaMap<'_> {
fn drop(&mut self) {
match std::mem::replace(&mut self.release, Release::None) {
Release::None => {}
Release::GlUnmap(ops, r) => ops.unmap(r),
#[cfg(feature = "dynamic")]
Release::FfiUnmap(h) => {
unsafe { edgefirst_tensor_ffi::ef_tensor_cuda_unmap(h) }
}
#[cfg(all(target_os = "windows", feature = "static"))]
Release::D3d11Writeback {
level0,
row_bytes,
rows,
stream,
device,
} => {
let Some(t) = table() else { return };
let rc = unsafe { (t.set_device)(device) };
if rc != 0 {
log::warn!(
"cudaSetDevice({device}) before the D3D11 write-back: {}",
err_str(t, rc)
);
return;
}
let rc = unsafe {
(t.memcpy_2d_to_array_async)(
level0,
0,
0,
self.ptr,
row_bytes,
row_bytes,
rows,
CUDA_MEMCPY_DEVICE_TO_DEVICE,
stream,
)
};
if rc != 0 {
log::warn!("cudaMemcpy2DToArrayAsync: {}", err_str(t, rc));
}
let rc = unsafe { (t.stream_synchronize)(stream) };
if rc != 0 {
log::warn!(
"cudaStreamSynchronize after the write-back: {}",
err_str(t, rc)
);
}
}
}
}
}
#[cfg(test)]
mod ext_mem_layout {
use super::*;
#[test]
fn external_memory_desc_abi() {
assert_eq!(std::mem::size_of::<CudaExternalMemoryHandleDesc>(), 40);
assert_eq!(std::mem::align_of::<CudaExternalMemoryHandleDesc>(), 8);
let d: CudaExternalMemoryHandleDesc = unsafe { std::mem::zeroed() };
let base = &d as *const _ as usize;
assert_eq!((&d.size as *const _ as usize) - base, 24);
assert_eq!((&d.flags as *const _ as usize) - base, 32);
assert_eq!(std::mem::size_of::<CudaExternalMemoryBufferDesc>(), 24);
let b: CudaExternalMemoryBufferDesc = unsafe { std::mem::zeroed() };
let bb = &b as *const _ as usize;
assert_eq!((&b.size as *const _ as usize) - bb, 8);
}
}
#[cfg(all(test, target_os = "windows", feature = "static"))]
mod d3d11_ordering {
use super::d3d11_copy_is_ordered;
#[test]
fn a_recorded_write_with_no_wait_behind_it_declines_the_copy() {
assert!(d3d11_copy_is_ordered(0, false), "nothing to wait for");
assert!(d3d11_copy_is_ordered(0, true));
assert!(d3d11_copy_is_ordered(42, true), "the wait was queued");
assert!(
!d3d11_copy_is_ordered(42, false),
"no semaphore, or the wait failed to queue"
);
}
}
#[cfg(all(test, target_os = "windows"))]
mod d3d11_desc_layout {
use super::*;
fn off<T, F>(base: &T, field: &F) -> usize {
(field as *const F as usize) - (base as *const T as usize)
}
#[test]
fn external_memory_and_semaphore_descs_match_driver_types_h() {
assert_eq!(
std::mem::size_of::<CudaExternalMemoryHandleDescWin32>(),
104
);
let d: CudaExternalMemoryHandleDescWin32 = unsafe { std::mem::zeroed() };
assert_eq!(off(&d, &d.win32_handle), 8);
assert_eq!(off(&d, &d.size), 24);
assert_eq!(off(&d, &d.flags), 32);
assert_eq!(
std::mem::size_of::<CudaExternalMemoryMipmappedArrayDesc>(),
128
);
let m: CudaExternalMemoryMipmappedArrayDesc = unsafe { std::mem::zeroed() };
assert_eq!(off(&m, &m.format_desc), 8);
assert_eq!(off(&m, &m.extent), 32);
assert_eq!(off(&m, &m.flags), 56);
assert_eq!(off(&m, &m.num_levels), 60);
assert_eq!(std::mem::size_of::<CudaExternalSemaphoreHandleDesc>(), 96);
let s: CudaExternalSemaphoreHandleDesc = unsafe { std::mem::zeroed() };
assert_eq!(off(&s, &s.win32_handle), 8);
assert_eq!(off(&s, &s.flags), 24);
assert_eq!(std::mem::size_of::<CudaExternalSemaphoreWaitParams>(), 144);
let w: CudaExternalSemaphoreWaitParams = unsafe { std::mem::zeroed() };
assert_eq!(off(&w, &w.fence_value), 0);
assert_eq!(off(&w, &w.flags), 72);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pub_primitives_degrade_without_libcudart() {
if is_cuda_available() {
crate::test_support::report_skip(
"pub_primitives_degrade_without_libcudart - a CUDA runtime is loaded",
);
return;
}
assert!(table().is_none());
assert!(runtime_path().is_none());
assert!(stream_create().is_none());
assert!(gl_register_buffer(0).is_none());
assert!(gl_map_resource(0).is_none());
gl_unmap_resource(0); gl_unregister_resource(0); assert!(!unsafe { memcpy_device_to_host(std::ptr::null_mut(), std::ptr::null(), 0) });
}
}
#[cfg(test)]
mod handle_tests {
use super::*;
use std::sync::{
atomic::{AtomicUsize, Ordering},
Arc,
};
struct MockOps {
unmaps: Arc<AtomicUsize>,
unregisters: Arc<AtomicUsize>,
}
impl CudaGlOps for MockOps {
fn map(&self, _r: GraphicsResource) -> Option<(*mut std::ffi::c_void, usize)> {
Some((0x1000usize as *mut _, 4096))
}
fn unmap(&self, _r: GraphicsResource) {
self.unmaps.fetch_add(1, Ordering::SeqCst);
}
fn unregister(&self, _r: GraphicsResource) {
self.unregisters.fetch_add(1, Ordering::SeqCst);
}
}
#[test]
fn cudamap_guard_unmaps_on_drop_for_glbuffer() {
let unmaps = Arc::new(AtomicUsize::new(0));
let unregisters = Arc::new(AtomicUsize::new(0));
{
let h = CudaHandle::new_gl(
0x1usize as GraphicsResource,
4096,
Arc::new(MockOps {
unmaps: unmaps.clone(),
unregisters: unregisters.clone(),
}),
);
{
let m = h.map().expect("map");
assert_eq!(m.device_ptr() as usize, 0x1000);
assert_eq!(m.len(), 4096);
assert!(!m.is_empty());
}
assert_eq!(
unmaps.load(Ordering::SeqCst),
1,
"Drop must unmap a GlBuffer"
);
assert_eq!(unregisters.load(Ordering::SeqCst), 0);
}
assert_eq!(
unregisters.load(Ordering::SeqCst),
1,
"Dropping a GlBuffer handle must unregister"
);
}
#[test]
fn map_mut_of_a_glbuffer_maps_and_unmaps_like_map() {
let unmaps = Arc::new(AtomicUsize::new(0));
let h = CudaHandle::new_gl(
0x3usize as GraphicsResource,
4096,
Arc::new(MockOps {
unmaps: unmaps.clone(),
unregisters: Arc::new(AtomicUsize::new(0)),
}),
);
{
let m = h.map_mut().expect("map_mut");
assert_eq!(m.device_ptr() as usize, 0x1000);
assert_eq!(m.len(), 4096);
}
assert_eq!(unmaps.load(Ordering::SeqCst), 1);
}
struct NoneOps;
impl CudaGlOps for NoneOps {
fn map(&self, _r: GraphicsResource) -> Option<(*mut std::ffi::c_void, usize)> {
None
}
fn unmap(&self, _r: GraphicsResource) {}
fn unregister(&self, _r: GraphicsResource) {}
}
#[test]
fn glbuffer_map_returns_none_when_ops_map_fails() {
let h = CudaHandle::new_gl(0x9usize as GraphicsResource, 4096, Arc::new(NoneOps));
assert!(
h.map().is_none(),
"GlBuffer map propagates ops.map() failure"
);
}
#[test]
fn glbuffer_handle_debug_and_empty_map() {
let unmaps = Arc::new(AtomicUsize::new(0));
let unregisters = Arc::new(AtomicUsize::new(0));
let h = CudaHandle::new_gl(
0x2usize as GraphicsResource,
0,
Arc::new(MockOps {
unmaps: unmaps.clone(),
unregisters: unregisters.clone(),
}),
);
let dbg = format!("{h:?}");
assert!(
dbg.contains("GlBuffer"),
"debug names the backing kind: {dbg}"
);
assert!(dbg.contains("size"), "debug includes size: {dbg}");
}
#[test]
fn external_mem_map_is_persistent_and_debug_names_kind() {
let dptr = 0xCAFE_0000usize as *mut std::ffi::c_void;
let h = CudaHandle::new_external(std::ptr::null_mut(), dptr, 8192);
let dbg = format!("{h:?}");
assert!(dbg.contains("ExternalMem"), "debug names the kind: {dbg}");
{
let m = h.map().expect("ExternalMem map is always Some");
assert_eq!(m.device_ptr(), dptr, "persistent device ptr passthrough");
assert_eq!(m.len(), 8192);
assert!(!m.is_empty());
}
std::mem::forget(h);
}
#[derive(Default)]
struct ClientCalls {
retains: AtomicUsize,
releases: AtomicUsize,
maps: AtomicUsize,
unmaps: AtomicUsize,
unregisters: AtomicUsize,
}
fn calls<'a>(ctx: *const std::ffi::c_void) -> &'a ClientCalls {
unsafe { &*(ctx as *const ClientCalls) }
}
unsafe extern "C" fn fake_retain(ctx: *const std::ffi::c_void) {
calls(ctx).retains.fetch_add(1, Ordering::SeqCst);
}
unsafe extern "C" fn fake_release(ctx: *const std::ffi::c_void) {
calls(ctx).releases.fetch_add(1, Ordering::SeqCst);
}
unsafe extern "C" fn fake_map(
ctx: *const std::ffi::c_void,
resource: *mut std::ffi::c_void,
out_ptr: *mut *mut std::ffi::c_void,
out_len: *mut usize,
) -> std::ffi::c_int {
calls(ctx).maps.fetch_add(1, Ordering::SeqCst);
if resource as usize != 1 {
return 7;
}
unsafe {
*out_ptr = 0x1000usize as *mut std::ffi::c_void;
*out_len = 64;
}
0
}
unsafe extern "C" fn fake_unmap(ctx: *const std::ffi::c_void, _r: *mut std::ffi::c_void) {
calls(ctx).unmaps.fetch_add(1, Ordering::SeqCst);
}
unsafe extern "C" fn fake_unregister(ctx: *const std::ffi::c_void, _r: *mut std::ffi::c_void) {
calls(ctx).unregisters.fetch_add(1, Ordering::SeqCst);
}
#[test]
fn client_channel_ops_forward_through_the_clients_functions() {
let log = ClientCalls::default();
let state = EfClientState {
ctx: &log as *const ClientCalls as *const std::ffi::c_void,
retain: Some(fake_retain),
release: Some(fake_release),
};
let ops = unsafe { client_state_cuda_ops(state, fake_map, fake_unmap, fake_unregister) }
.expect("a complete client state");
assert_eq!(log.retains.load(Ordering::SeqCst), 1);
assert_eq!(
ops.map(1usize as GraphicsResource),
Some((0x1000usize as *mut std::ffi::c_void, 64))
);
assert_eq!(ops.map(2usize as GraphicsResource), None);
assert_eq!(log.maps.load(Ordering::SeqCst), 2);
ops.unmap(1usize as GraphicsResource);
ops.unregister(1usize as GraphicsResource);
assert_eq!(log.unmaps.load(Ordering::SeqCst), 1);
assert_eq!(log.unregisters.load(Ordering::SeqCst), 1);
assert_eq!(log.releases.load(Ordering::SeqCst), 0);
drop(ops);
assert_eq!(log.releases.load(Ordering::SeqCst), 1);
assert_eq!(log.retains.load(Ordering::SeqCst), 1);
}
#[test]
fn a_client_state_missing_a_part_is_refused_without_a_retain() {
let log = ClientCalls::default();
let ctx = &log as *const ClientCalls as *const std::ffi::c_void;
let incomplete = [
EfClientState {
ctx: std::ptr::null(),
retain: Some(fake_retain),
release: Some(fake_release),
},
EfClientState {
ctx,
retain: None,
release: Some(fake_release),
},
EfClientState {
ctx,
retain: Some(fake_retain),
release: None,
},
];
for state in incomplete {
let r = unsafe { client_state_cuda_ops(state, fake_map, fake_unmap, fake_unregister) };
assert!(matches!(r, Err(crate::Error::InvalidArgument(_))));
}
assert_eq!(log.retains.load(Ordering::SeqCst), 0);
}
#[test]
fn external_mem_zero_len_map_is_empty() {
let h = CudaHandle::new_external(std::ptr::null_mut(), std::ptr::null_mut(), 0);
{
let m = h.map().expect("map");
assert_eq!(m.len(), 0);
assert!(m.is_empty(), "zero-length mapping is empty");
assert!(m.device_ptr().is_null());
}
std::mem::forget(h); }
}