pbrt/src/utils/backend.rs

209 lines
6.3 KiB
Rust

use std::alloc::Layout;
pub trait GpuAllocator: Send + Sync {
/// Allocate `size` bytes with given alignment.
/// Returns a host-mapped pointer.
unsafe fn alloc(&self, layout: Layout) -> *mut u8;
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout);
}
/// CPU fallback — standard system allocator.
pub struct SystemAllocator;
impl Default for SystemAllocator {
fn default() -> Self {
Self {}
}
}
impl GpuAllocator for SystemAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
if layout.size() == 0 {
return layout.align() as *mut u8;
}
unsafe { std::alloc::alloc(layout) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
if layout.size() > 0 {
unsafe {
std::alloc::dealloc(ptr, layout);
}
}
}
}
/// CUDA unified memory backend using CudaAllocator
#[cfg(feature = "cuda")]
pub mod cuda {
use super::GpuAllocator;
use std::alloc::Layout;
pub struct CudaAllocator;
impl Default for CudaAllocator {
fn default() -> Self {
Self {}
}
}
impl GpuAllocator for CudaAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
use cust::memory::cuda_malloc_unified;
use cust_raw::driver_sys::*;
let size = layout.size().max(layout.align());
if size == 0 {
return layout.align() as *mut u8;
}
let mut ctx: CUcontext = std::ptr::null_mut();
cuCtxGetCurrent(&mut ctx);
if ctx.is_null() {
let mut primary: CUcontext = std::ptr::null_mut();
cuDevicePrimaryCtxRetain(&mut primary, 0);
cuCtxSetCurrent(primary);
}
let mut unified_ptr =
unsafe { cuda_malloc_unified::<u8>(size).expect("cuda_malloc_unified failed") };
let raw = unified_ptr.as_raw_mut();
std::mem::forget(unified_ptr);
raw
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
use cust::memory::{UnifiedPointer, cuda_free_unified};
if layout.size() > 0 {
let _ = unsafe { cuda_free_unified(UnifiedPointer::wrap(ptr)) };
}
}
}
}
/// Vulkan backend (gpu-allocator for now, there might be a better solution)
#[cfg(feature = "vulkan")]
pub mod vulkan {
use super::GpuAllocator;
use ash::vk;
use gpu_allocator::MemoryLocation;
use gpu_allocator::vulkan::{
Allocation, AllocationCreateDesc, AllocationScheme, Allocator, AllocatorCreateDesc,
};
use parking_lot::Mutex;
use std::alloc::Layout;
use std::collections::HashMap;
use std::sync::OnceLock;
// So, having a static allocator seems like a terrible idea
// But I cant find a way to get a functioning generic Arena constructor
// That might not even be a necessity, since rust-gpu/rust-cuda might actually handle that
// differently
static VK_ALLOCATOR: OnceLock<VulkanAllocatorInner> = OnceLock::new();
struct VulkanAllocatorInner {
state: Mutex<VulkanState>,
}
struct VulkanState {
allocator: Allocator,
allocations: HashMap<usize, Allocation>,
}
pub fn init_vulkan(
instance: &ash::Instance,
device: &ash::Device,
physical_device: vk::PhysicalDevice,
) {
VK_ALLOCATOR.get_or_init(|| {
let allocator = Allocator::new(&AllocatorCreateDesc {
instance: instance.clone(),
device: device.clone(),
physical_device,
debug_settings: Default::default(),
buffer_device_address: false,
allocation_sizes: Default::default(),
})
.expect("Failed to create Vulkan allocator");
VulkanAllocatorInner {
state: Mutex::new(VulkanState {
allocator,
allocations: HashMap::new(),
}),
}
});
}
fn inner() -> &'static VulkanAllocatorInner {
VK_ALLOCATOR
.get()
.expect("Vulkan not initialized — call init_vulkan() before Arena::default()")
}
impl Default for VulkanAllocator {
fn default() -> Self {
let _ = inner();
Self
}
}
pub struct VulkanAllocator;
// impl VulkanAllocator {
// pub fn new(allocator: Allocator) -> Self {
// Self {
// allocator: Mutex::new(allocator),
// allocations: Mutex::new(HashMap::new()),
// }
// }
// }
impl GpuAllocator for VulkanAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let size = layout.size().max(layout.align());
if size == 0 {
return layout.align() as *mut u8;
}
let inner = inner();
let mut state = inner.state.lock();
let allocation = state
.allocator
.allocate(&AllocationCreateDesc {
name: "arena",
requirements: vk::MemoryRequirements {
size: size as u64,
alignment: layout.align() as u64,
memory_type_bits: u32::MAX,
},
location: MemoryLocation::CpuToGpu,
linear: true,
allocation_scheme: AllocationScheme::GpuAllocatorManaged,
})
.expect("Vulkan allocation failed");
let ptr = allocation
.mapped_ptr()
.expect("Vulkan allocation not host-mapped")
.as_ptr() as *mut u8;
state.allocations.insert(ptr as usize, allocation);
ptr
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
if layout.size() == 0 {
return;
}
let inner = inner();
let mut state = inner.state.lock();
if let Some(allocation) = state.allocations.remove(&(ptr as usize)) {
state
.allocator
.free(allocation)
.expect("Vulkan free failed");
}
}
}
}