#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use super::error::{GpuError, GpuResult};
use super::GpuBackend;
use crate::kernel::{Complex, Float};
#[derive(Debug)]
pub struct GpuBuffer<T: Float> {
size: usize,
backend: GpuBackend,
cpu_data: Vec<Complex<T>>,
}
#[cfg(feature = "std")]
fn backend_pool_id(backend: GpuBackend) -> u32 {
match backend {
GpuBackend::Cuda => 0,
GpuBackend::Metal => 1,
_ => 2,
}
}
#[cfg(feature = "std")]
fn staging_key<T: Float>(backend: GpuBackend, size: usize) -> super::pool::PoolKey {
let bytes = size.saturating_mul(core::mem::size_of::<Complex<T>>());
super::pool::PoolKey {
backend_id: backend_pool_id(backend),
rounded_size: super::pool::round_pool_size(bytes),
kind: super::pool::BufferKind::Scratch,
}
}
#[cfg(feature = "std")]
fn acquire_staging_from<T: Float>(
pool: &super::pool::GpuBufferPool,
backend: GpuBackend,
size: usize,
) -> Vec<Complex<T>> {
let key = staging_key::<T>(backend, size);
let pooled = pool.acquire(key, |rounded_bytes| {
Some(super::pool::PooledBuffer::new(
alloc_box(Vec::<Complex<T>>::new()),
rounded_bytes,
))
});
let mut data = match pooled {
Some(buf) => match buf.downcast::<Vec<Complex<T>>>() {
Ok(boxed) => *boxed,
Err(_) => Vec::new(),
},
None => Vec::new(),
};
data.clear();
data.resize(size, Complex::<T>::zero());
data
}
#[cfg(feature = "std")]
fn release_staging_to<T: Float>(
pool: &super::pool::GpuBufferPool,
backend: GpuBackend,
size: usize,
mut data: Vec<Complex<T>>,
) {
if size == 0 {
return;
}
data.clear();
let key = staging_key::<T>(backend, size);
pool.release(
key,
super::pool::PooledBuffer::new(alloc_box(data), key.rounded_size),
);
}
#[cfg(feature = "std")]
fn alloc_box<T: core::any::Any + Send>(value: T) -> Box<dyn core::any::Any + Send> {
Box::new(value)
}
fn acquire_staging<T: Float>(backend: GpuBackend, size: usize) -> Vec<Complex<T>> {
#[cfg(feature = "std")]
{
acquire_staging_from::<T>(super::pool::global_pool(), backend, size)
}
#[cfg(not(feature = "std"))]
{
let _ = backend;
vec![Complex::<T>::zero(); size]
}
}
fn release_staging<T: Float>(backend: GpuBackend, size: usize, data: Vec<Complex<T>>) {
#[cfg(feature = "std")]
{
release_staging_to::<T>(super::pool::global_pool(), backend, size, data);
}
#[cfg(not(feature = "std"))]
{
let _ = (backend, size, data);
}
}
impl<T: Float> GpuBuffer<T> {
pub fn new(size: usize, backend: GpuBackend) -> GpuResult<Self> {
if size == 0 {
return Err(GpuError::InvalidSize(size));
}
let cpu_data = acquire_staging::<T>(backend, size);
Ok(Self {
size,
backend,
cpu_data,
})
}
pub fn from_slice(data: &[Complex<T>], backend: GpuBackend) -> GpuResult<Self> {
if data.is_empty() {
return Err(GpuError::InvalidSize(0));
}
let mut buffer = Self::new(data.len(), backend)?;
buffer.upload(data)?;
Ok(buffer)
}
#[must_use]
pub const fn size(&self) -> usize {
self.size
}
#[must_use]
pub const fn backend(&self) -> GpuBackend {
self.backend
}
pub fn upload(&mut self, data: &[Complex<T>]) -> GpuResult<()> {
if data.len() != self.size {
return Err(GpuError::SizeMismatch {
expected: self.size,
got: data.len(),
});
}
self.cpu_data.copy_from_slice(data);
Ok(())
}
pub fn download(&mut self, data: &mut [Complex<T>]) -> GpuResult<()> {
if data.len() != self.size {
return Err(GpuError::SizeMismatch {
expected: self.size,
got: data.len(),
});
}
data.copy_from_slice(&self.cpu_data);
Ok(())
}
#[must_use]
pub fn cpu_data(&self) -> &[Complex<T>] {
&self.cpu_data
}
pub fn cpu_data_mut(&mut self) -> &mut [Complex<T>] {
&mut self.cpu_data
}
}
impl<T: Float> Drop for GpuBuffer<T> {
fn drop(&mut self) {
let data = core::mem::take(&mut self.cpu_data);
release_staging::<T>(self.backend, self.size, data);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn gpu_buffer_is_send_sync_by_auto_derive() {
const fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<GpuBuffer<f32>>();
assert_send_sync::<GpuBuffer<f64>>();
}
#[test]
fn test_gpu_buffer_creation() {
let buffer: GpuBuffer<f64> =
GpuBuffer::new(1024, GpuBackend::Auto).expect("Failed to create buffer");
assert_eq!(buffer.size(), 1024);
}
#[test]
fn test_gpu_buffer_cpu_data() {
let mut buffer: GpuBuffer<f64> =
GpuBuffer::new(8, GpuBackend::Auto).expect("Failed to create buffer");
buffer.cpu_data_mut()[0] = Complex::new(1.0, 2.0);
assert_eq!(buffer.cpu_data()[0], Complex::new(1.0, 2.0));
}
#[cfg(feature = "std")]
#[test]
fn staging_pool_reuses_released_buffer() {
use crate::gpu::pool::GpuBufferPool;
let pool = GpuBufferPool::new(64 * 1024 * 1024);
let backend = GpuBackend::Metal;
let size = 1024usize;
let mut v1 = acquire_staging_from::<f32>(&pool, backend, size);
assert_eq!(v1.len(), size);
v1[0] = Complex::new(7.0_f32, 0.0);
let cap_before = v1.capacity();
let ptr_before = v1.as_ptr() as usize;
release_staging_to::<f32>(&pool, backend, size, v1);
assert!(
pool.current_bytes() > 0,
"released staging buffer should be accounted in the pool"
);
let v2 = acquire_staging_from::<f32>(&pool, backend, size);
assert_eq!(v2.len(), size);
assert_eq!(
v2.as_ptr() as usize,
ptr_before,
"second acquire must reuse the same backing allocation"
);
assert_eq!(
v2.capacity(),
cap_before,
"reused buffer should retain its capacity"
);
assert_eq!(v2[0], Complex::new(0.0_f32, 0.0));
assert_eq!(pool.current_bytes(), 0);
}
#[cfg(feature = "std")]
#[test]
fn staging_pool_separates_by_element_type() {
use crate::gpu::pool::GpuBufferPool;
let pool = GpuBufferPool::new(64 * 1024 * 1024);
let backend = GpuBackend::Cuda;
let vf32 = acquire_staging_from::<f32>(&pool, backend, 512);
let vf64 = acquire_staging_from::<f64>(&pool, backend, 512);
release_staging_to::<f32>(&pool, backend, 512, vf32);
release_staging_to::<f64>(&pool, backend, 512, vf64);
let again = acquire_staging_from::<f32>(&pool, backend, 512);
assert_eq!(again.len(), 512);
}
#[cfg(feature = "std")]
#[test]
fn gpu_buffer_new_uses_global_pool() {
use crate::gpu::global_gpu_pool;
let before = global_gpu_pool().current_bytes();
{
let _buf: GpuBuffer<f64> = GpuBuffer::new(2048, GpuBackend::Metal).expect("buffer");
} let after = global_gpu_pool().current_bytes();
assert!(
after >= before,
"dropping a GpuBuffer should return its staging allocation to the pool"
);
}
}