#[cfg(test)]
mod buffer_alloc_tests {
use crate::parcel::Parcel;
use crate::test_support::SerialGpuDevice;
use crate::types::{BufferFlags, ResourceAccess};
use crate::{BufferKind, Context, MemoryExchange, Scheme};
use std::sync::Arc;
fn make_device() -> SerialGpuDevice {
SerialGpuDevice::new()
}
fn withdraw_bytes(ctx: &Context, device: &crate::Device, arc: &Arc<crate::buffer::Allocation>) -> Vec<u8> {
let parcel = Parcel::from_whole_buffer(Arc::clone(arc), Arc::downgrade(&device.inner));
let mut scheme = Scheme::new(ctx);
let tx = MemoryExchange::new(ctx)
.bind_withdraw(&mut scheme, &parcel)
.expect("bind_withdraw");
let mut sub = scheme.submit().expect("submit");
tx.claim(&mut sub).expect("claim").consume().expect("consume").to_vec()
}
#[test]
fn resize_preserves_contents() {
let device = make_device();
let ctx = device.create_context().expect("context");
let mut arc = Arc::new(
device
.alloc_buffer_with_data(&[1u32, 2, 3, 4], BufferKind::Scattered)
.expect("buf"),
);
Arc::get_mut(&mut arc).unwrap().resize_to(32).expect("resize");
let out = withdraw_bytes(&ctx, &device, &arc);
let words: &[u32] = bytemuck::cast_slice(&out);
assert_eq!(&words[..4], &[1u32, 2, 3, 4]);
for w in &words[4..8] {
assert_eq!(*w, 0, "new region should be zero");
}
}
#[test]
fn resize_preserves_bindless_index() {
let device = make_device();
let mut buf = device
.alloc_buffer(16, BufferKind::Scattered, None, BufferFlags::empty())
.expect("buf");
let idx = buf.resource_index(ResourceAccess::Write).expect("bindless");
buf.resize_to(256).expect("resize");
assert_eq!(buf.resource_index(ResourceAccess::Write), Some(idx));
}
#[test]
fn resize_down_truncates() {
let device = make_device();
let ctx = device.create_context().expect("context");
let mut arc = Arc::new(
device
.alloc_buffer_with_data(&[10u32, 20, 30, 40], BufferKind::Scattered)
.expect("buf"),
);
Arc::get_mut(&mut arc).unwrap().resize_to(8).expect("resize down");
let out = withdraw_bytes(&ctx, &device, &arc);
let words: &[u32] = bytemuck::cast_slice(&out);
assert_eq!(words, &[10u32, 20]);
}
#[test]
fn resize_uninitialized_skips_copy() {
let device = make_device();
let ctx = device.create_context().expect("context");
let mut arc = Arc::new(
device
.alloc_buffer_with_data(&[0xABCD_BEEFu32], BufferKind::Scattered)
.expect("buf"),
);
Arc::get_mut(&mut arc)
.unwrap()
.resize_to_uninitialized(8)
.expect("resize uni");
let _ = withdraw_bytes(&ctx, &device, &arc);
}
#[test]
fn new_with_capacity_hint_smoke() {
let device = make_device();
let b = device
.alloc_buffer_with_capacity(16, 4096, BufferKind::Scattered, BufferFlags::empty())
.expect("b");
assert_eq!(b.size(), 16);
assert!(b.allocated_size() >= 4096, "expected oversize allocation");
}
#[test]
fn oversize_resize_within_capacity_preserves_and_zeros_tail() {
let device = make_device();
let ctx = device.create_context().expect("context");
let mut arc = Arc::new(
device
.alloc_buffer_with_capacity(16, 4096, BufferKind::Scattered, BufferFlags::empty())
.expect("buf"),
);
let idx = arc.resource_index(ResourceAccess::Write).expect("bindless");
Arc::get_mut(&mut arc).unwrap().write(0, &[0xabu8; 16]).expect("seed");
Arc::get_mut(&mut arc).unwrap().resize_to(256).expect("grow within cap");
assert_eq!(arc.resource_index(ResourceAccess::Write), Some(idx));
assert!(arc.size() >= 256);
let got = withdraw_bytes(&ctx, &device, &arc);
assert_eq!(&got[..16], &[0xabu8; 16]);
assert!(got[16..].iter().all(|&x| x == 0));
}
#[test]
fn oversize_resize_beyond_capacity_falls_back_and_preserves() {
let device = make_device();
let ctx = device.create_context().expect("context");
let mut arc = Arc::new(
device
.alloc_buffer_with_capacity(16, 256, BufferKind::Scattered, BufferFlags::empty())
.expect("buf"),
);
Arc::get_mut(&mut arc).unwrap().write(0, &[7u8; 16]).expect("w");
Arc::get_mut(&mut arc).unwrap().resize_to(512).expect("grow past cap");
assert!(arc.allocated_size() >= 512);
let got = withdraw_bytes(&ctx, &device, &arc);
assert_eq!(&got[..16], &[7u8; 16]);
}
#[test]
fn hint_unused_above_does_not_corrupt_prefix() {
let device = make_device();
let ctx = device.create_context().expect("context");
let mut arc = Arc::new(
device
.alloc_buffer_with_capacity(64, 4096, BufferKind::Scattered, BufferFlags::empty())
.expect("buf"),
);
Arc::get_mut(&mut arc).unwrap().write(0, &[0x11u8; 64]).expect("w");
Arc::get_mut(&mut arc).unwrap().hint_unused_above(32);
let got = withdraw_bytes(&ctx, &device, &arc);
assert_eq!(&got[..32], &[0x11u8; 32]);
}
#[cfg(all(target_os = "macos", feature = "metal"))]
#[test]
fn device_capabilities_metal_reports_constant_resize() {
use crate::{types::BufferResizeCost, BackendType};
let device = SerialGpuDevice::preferring(crate::DeviceType::IntegratedGpu);
let _ctx = device.create_context().expect("context");
assert_eq!(device.backend_type(), BackendType::Metal);
let caps = device.capabilities();
assert_eq!(caps.buffer_resize_cost, BufferResizeCost::Constant);
assert!(caps.buffer_decommit_supported);
}
#[cfg(feature = "vulkan")]
#[test]
fn device_capabilities_vulkan_reports_pagebind_when_sparse() {
use crate::{types::BufferResizeCost, BackendType};
let device = SerialGpuDevice::preferring(crate::DeviceType::DiscreteGpu);
if device.backend_type() != BackendType::Vulkan {
return;
}
let caps = device.capabilities();
if caps.buffer_resize_cost == BufferResizeCost::PageBind {
assert_eq!(caps.buffer_page_size, 64 * 1024);
assert!(caps.buffer_decommit_supported);
}
}
#[cfg(feature = "dx12")]
#[test]
fn device_capabilities_dx12_reports_pagebind_when_reserved_supported() {
use crate::{types::BufferResizeCost, BackendType};
let device = SerialGpuDevice::preferring(crate::DeviceType::DiscreteGpu);
if device.backend_type() != BackendType::Dx12 {
return;
}
let caps = device.capabilities();
if caps.buffer_resize_cost == BufferResizeCost::PageBind {
assert_eq!(caps.buffer_page_size, 64 * 1024);
assert!(caps.buffer_decommit_supported);
}
}
#[cfg(any(feature = "vulkan", feature = "dx12"))]
#[test]
fn sparse_backend_oversize_resize_and_hint_within_capacity() {
use crate::types::BufferResizeCost;
let device = SerialGpuDevice::preferring(crate::DeviceType::DiscreteGpu);
if device.capabilities().buffer_resize_cost != BufferResizeCost::PageBind {
return;
}
let ctx = device.create_context().expect("context");
let mut arc = Arc::new(
device
.alloc_buffer_with_capacity(64, 4096, BufferKind::Scattered, BufferFlags::empty())
.expect("buf"),
);
Arc::get_mut(&mut arc).unwrap().write(0, &[0x11u8; 64]).expect("w");
Arc::get_mut(&mut arc).unwrap().resize_to(256).expect("grow within cap");
let got = withdraw_bytes(&ctx, &device, &arc);
assert_eq!(&got[..64], &[0x11u8; 64]);
assert!(got[64..].iter().all(|&x| x == 0));
Arc::get_mut(&mut arc).unwrap().hint_unused_above(32);
let prefix = withdraw_bytes(&ctx, &device, &arc);
assert_eq!(&prefix[..32], &[0x11u8; 32]);
}
#[cfg(all(feature = "dx12", target_os = "windows"))]
#[test]
fn dx12_reserved_buffer_resize_compute_smoke() {
use crate::types::BufferResizeCost;
use crate::{BackendType, ComputePipeline, DeviceType, NodeAccess, ShaderModule};
const SMOKY_SHADER: &str = r#"
import goldy_exp;
[goldy_compute]
[numthreads(16, 1, 1)]
void cs_main(Scattered<uint> data, ThreadId id) {
data[id.x] = data[id.x] * 2;
}
"#;
let device = SerialGpuDevice::preferring(DeviceType::DiscreteGpu);
let ctx = device.create_context().expect("context");
if device.backend_type() != BackendType::Dx12 {
return;
}
if device.capabilities().buffer_resize_cost != BufferResizeCost::PageBind {
return;
}
let mut arc = Arc::new(
device
.alloc_buffer_with_capacity(256, 4 * 64 * 1024, BufferKind::Scattered, BufferFlags::empty())
.expect("buf"),
);
let bindless = arc.resource_index(ResourceAccess::ReadWrite).expect("bindless");
let initial: Vec<u32> = (1..=16).collect();
Arc::get_mut(&mut arc)
.unwrap()
.write(0, bytemuck::cast_slice(&initial))
.expect("w");
Arc::get_mut(&mut arc)
.unwrap()
.resize_to(200 * 1024)
.expect("grow across tiles");
assert_eq!(arc.resource_index(ResourceAccess::ReadWrite), Some(bindless));
let out = withdraw_bytes(&ctx, &device, &arc);
let read: &[u32] = bytemuck::cast_slice(&out[..64]);
assert_eq!(read, initial.as_slice());
let shader = ShaderModule::from_slang(&device, SMOKY_SHADER).expect("shader");
let pipeline = ComputePipeline::new(&device, &shader).expect("pipeline");
{
let mut scheme = Scheme::new(&ctx);
scheme
.node("n0", &pipeline)
.with_parcel(arc.as_ref(), NodeAccess::ReadWrite)
.dispatch(1, 1, 1);
let parcel = Parcel::from_whole_buffer(Arc::clone(&arc), Arc::downgrade(&device.inner));
let grant = MemoryExchange::new(&ctx)
.bind_withdraw(&mut scheme, &parcel)
.expect("withdraw");
let mut sub = scheme.submit().expect("dispatch");
let loan = grant.claim(&mut sub).expect("claim").consume().expect("consume");
let read: &[u32] = bytemuck::cast_slice(&loan[..64]);
for (i, &v) in read.iter().enumerate() {
assert_eq!(v, (i as u32 + 1) * 2, "after first dispatch[{i}]");
}
}
Arc::get_mut(&mut arc)
.unwrap()
.resize_to(64 * 1024)
.expect("shrink to one tile");
assert_eq!(arc.resource_index(ResourceAccess::ReadWrite), Some(bindless));
Arc::get_mut(&mut arc).unwrap().hint_unused_above(64 * 1024);
Arc::get_mut(&mut arc)
.unwrap()
.resize_to(200 * 1024)
.expect("grow after decommit hint");
assert_eq!(arc.resource_index(ResourceAccess::ReadWrite), Some(bindless));
let initial2: Vec<u32> = (0..16).collect();
Arc::get_mut(&mut arc)
.unwrap()
.write(0, bytemuck::cast_slice(&initial2))
.expect("w2");
let mut scheme2 = Scheme::new(&ctx);
scheme2
.node("n1", &pipeline)
.with_parcel(arc.as_ref(), NodeAccess::ReadWrite)
.dispatch(1, 1, 1);
let parcel2 = Parcel::from_whole_buffer(Arc::clone(&arc), Arc::downgrade(&device.inner));
let grant2 = MemoryExchange::new(&ctx)
.bind_withdraw(&mut scheme2, &parcel2)
.expect("withdraw2");
let mut sub2 = scheme2.submit().expect("dispatch2");
let loan2 = grant2.claim(&mut sub2).expect("claim").consume().expect("consume");
let read: &[u32] = bytemuck::cast_slice(&loan2[..64]);
for (i, &v) in read.iter().enumerate() {
assert_eq!(v, (i as u32) * 2, "after second dispatch[{i}]");
}
}
#[test]
fn hint_unused_above_smoke() {
let device = make_device();
let mut buf = device
.alloc_buffer(64, BufferKind::Scattered, None, BufferFlags::empty())
.expect("buf");
buf.hint_unused_above(32);
}
}