use crate::cpu::SmartAlloc;
use crate::gpu::traits::{
GpuAllocator, GpuBuffer, GpuAllocError, BufferUsage, MemoryType,
GpuMemoryIntent, GpuLifetime, GpuAllocRequirements, GpuAllocStats,
};
use super::budget::UnifiedBudgetManager;
use std::sync::Arc;
pub struct UnifiedAllocator {
cpu_alloc: SmartAlloc,
gpu_alloc: Box<dyn GpuAllocator>,
budget_manager: UnifiedBudgetManager,
}
#[derive(Debug, Clone)]
pub enum UnifiedError {
CpuAllocationFailed(String),
GpuAllocationFailed(GpuAllocError),
BudgetExceeded,
InvalidTransfer,
}
impl std::fmt::Display for UnifiedError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
UnifiedError::CpuAllocationFailed(msg) => write!(f, "CPU allocation failed: {}", msg),
UnifiedError::GpuAllocationFailed(err) => write!(f, "GPU allocation failed: {}", err),
UnifiedError::BudgetExceeded => write!(f, "Unified memory budget exceeded"),
UnifiedError::InvalidTransfer => write!(f, "Invalid CPU-GPU transfer"),
}
}
}
impl std::error::Error for UnifiedError {}
pub struct UnifiedBuffer {
cpu_data: Option<Vec<u8>>,
gpu_buffer: Option<Box<dyn GpuBuffer>>,
location: BufferLocation,
size: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BufferLocation {
CpuOnly,
GpuOnly,
Synchronized,
}
impl UnifiedAllocator {
pub fn new(cpu_alloc: SmartAlloc, gpu_alloc: Box<dyn GpuAllocator>) -> Self {
Self {
cpu_alloc,
gpu_alloc,
budget_manager: UnifiedBudgetManager::new(),
}
}
pub fn begin_frame(&mut self) {
self.cpu_alloc.begin_frame();
}
pub fn end_frame(&mut self) {
self.cpu_alloc.end_frame();
self.budget_manager.reset_frame_usage();
}
pub fn create_cpu_buffer(&mut self, size: usize) -> Result<UnifiedBuffer, UnifiedError> {
if !self.budget_manager.check_cpu_budget(size) {
return Err(UnifiedError::BudgetExceeded);
}
let cpu_data = vec![0u8; size];
self.budget_manager.add_cpu_usage(size);
Ok(UnifiedBuffer {
cpu_data: Some(cpu_data),
gpu_buffer: None,
location: BufferLocation::CpuOnly,
size,
})
}
pub fn create_gpu_buffer_with_intent(
&mut self,
size: usize,
intent: GpuMemoryIntent,
lifetime: GpuLifetime,
) -> Result<UnifiedBuffer, UnifiedError> {
if !self.budget_manager.check_gpu_budget(size) {
return Err(UnifiedError::BudgetExceeded);
}
let req = GpuAllocRequirements::new(size, intent, lifetime);
let gpu_buffer = self.gpu_alloc.allocate(req)
.map_err(UnifiedError::GpuAllocationFailed)?;
self.budget_manager.add_gpu_usage(size);
Ok(UnifiedBuffer {
cpu_data: None,
gpu_buffer: Some(gpu_buffer),
location: BufferLocation::GpuOnly,
size,
})
}
pub fn create_gpu_buffer(
&mut self,
size: usize,
usage: BufferUsage,
memory_type: MemoryType,
) -> Result<UnifiedBuffer, UnifiedError> {
let intent = match memory_type {
MemoryType::DeviceLocal => GpuMemoryIntent::DeviceOnly,
MemoryType::HostVisible | MemoryType::HostCoherent => GpuMemoryIntent::HostVisible,
MemoryType::HostCached => GpuMemoryIntent::HostCached,
MemoryType::Lazy => GpuMemoryIntent::DeviceOnly, };
self.create_gpu_buffer_with_intent(size, intent, GpuLifetime::Persistent)
}
pub fn create_staging_buffer(&mut self, size: usize) -> Result<UnifiedBuffer, UnifiedError> {
if !self.budget_manager.check_combined_budget(size * 2) {
return Err(UnifiedError::BudgetExceeded);
}
let cpu_data = vec![0u8; size];
let req = GpuAllocRequirements::new(size, GpuMemoryIntent::Staging, GpuLifetime::Frame);
let gpu_buffer = self.gpu_alloc.allocate(req)
.map_err(UnifiedError::GpuAllocationFailed)?;
self.budget_manager.add_cpu_usage(size);
self.budget_manager.add_gpu_usage(size);
Ok(UnifiedBuffer {
cpu_data: Some(cpu_data),
gpu_buffer: Some(gpu_buffer),
location: BufferLocation::Synchronized,
size,
})
}
pub fn transfer_to_gpu(&mut self, buffer: &mut UnifiedBuffer) -> Result<(), UnifiedError> {
if buffer.cpu_data.is_none() {
return Err(UnifiedError::InvalidTransfer);
}
if buffer.gpu_buffer.is_none() {
let gpu_buffer = self.gpu_alloc.allocate_buffer(
buffer.size,
BufferUsage::TRANSFER_DST,
MemoryType::DeviceLocal,
).map_err(UnifiedError::GpuAllocationFailed)?;
buffer.gpu_buffer = Some(gpu_buffer);
self.budget_manager.add_gpu_usage(buffer.size);
}
if let Some(gpu_buf) = buffer.gpu_buffer.as_mut() {
if let Some(ptr) = gpu_buf.map() {
unsafe {
std::ptr::copy_nonoverlapping(
buffer.cpu_data.as_ref().unwrap().as_ptr(),
ptr,
buffer.size,
);
}
gpu_buf.flush(0, buffer.size)
.map_err(UnifiedError::GpuAllocationFailed)?;
gpu_buf.unmap();
}
}
buffer.location = BufferLocation::Synchronized;
Ok(())
}
pub fn get_usage(&self) -> (usize, usize) {
(self.budget_manager.cpu_usage(), self.budget_manager.gpu_usage())
}
pub fn cpu_allocator(&self) -> &SmartAlloc {
&self.cpu_alloc
}
pub fn gpu_allocator(&self) -> &dyn GpuAllocator {
self.gpu_alloc.as_ref()
}
}
impl UnifiedBuffer {
pub fn cpu_slice(&self) -> Option<&[u8]> {
self.cpu_data.as_deref()
}
pub fn cpu_slice_mut(&mut self) -> Option<&mut [u8]> {
self.cpu_data.as_deref_mut()
}
pub fn gpu_buffer(&self) -> Option<&dyn GpuBuffer> {
self.gpu_buffer.as_deref()
}
pub fn location(&self) -> BufferLocation {
self.location
}
pub fn size(&self) -> usize {
self.size
}
}