use ash::vk;
use crate::vulkan::owned::{
OwnedDescriptorPool, OwnedPipeline, OwnedPipelineLayout, OwnedSetLayout, VkDevice,
};
use crate::gfx::render_types::{DrawObject, InstancedCluster, RtGeomEntry, SkinnedDrawObject};
use crate::gfx::rt_geom::{cluster_geom_entry, geom_entry, models_dirty, skinned_geom_entry};
use crate::gfx::rt_refit::{BlasUpdate, SkinnedRefit, SkinnedShape};
use crate::gfx::rt_topology::{GeomSig, plan_topology_refresh};
use concinnity_core::render::uniforms::SkinParams;
pub(super) use crate::gfx::rt_geom::RtDynamicMode;
use super::allocator::{DeviceAllocator, PooledBuffer};
use super::pipeline::spv_module;
use crate::vulkan::slang_builtins::SlangCompile;
const VERTEX_STRIDE: u64 = 56;
pub(super) fn pack_instance_transform(model: [[f32; 4]; 4]) -> vk::TransformMatrixKHR {
vk::TransformMatrixKHR {
matrix: [
model[0][0],
model[1][0],
model[2][0],
model[3][0],
model[0][1],
model[1][1],
model[2][1],
model[3][1],
model[0][2],
model[1][2],
model[2][2],
model[3][2],
],
}
}
fn tlas_instance(
model: [[f32; 4]; 4],
custom_index: u32,
blas_address: u64,
) -> vk::AccelerationStructureInstanceKHR {
vk::AccelerationStructureInstanceKHR {
transform: pack_instance_transform(model),
instance_custom_index_and_mask: vk::Packed24_8::new(custom_index & 0x00FF_FFFF, 0xFFu8),
instance_shader_binding_table_record_offset_and_flags: vk::Packed24_8::new(0, 0u8),
acceleration_structure_reference: vk::AccelerationStructureReferenceKHR {
device_handle: blas_address,
},
}
}
fn align_up(value: u64, align: u64) -> u64 {
if align <= 1 {
value
} else {
(value + align - 1) & !(align - 1)
}
}
struct AccelBuffer {
accel: vk::AccelerationStructureKHR,
_pooled: PooledBuffer,
size: u64,
}
impl AccelBuffer {
fn destroy(&self, as_loader: &ash::khr::acceleration_structure::Device) {
unsafe {
as_loader.destroy_acceleration_structure(self.accel, None);
}
}
}
struct HostBuffer {
buffer: vk::Buffer,
pooled: PooledBuffer,
size: vk::DeviceSize,
}
pub(super) struct DeviceBuffer {
pub(super) buffer: vk::Buffer,
_pooled: PooledBuffer,
address: u64,
size: u64,
}
impl DeviceBuffer {
fn handle(&self) -> DeviceBufferRef {
DeviceBufferRef {
buffer: self.buffer,
address: self.address,
}
}
}
#[derive(Clone, Copy)]
struct DeviceBufferRef {
buffer: vk::Buffer,
address: u64,
}
pub(super) struct SkinPipeline {
set_layout: OwnedSetLayout,
pipeline_layout: OwnedPipelineLayout,
pipeline: OwnedPipeline,
descriptor_pool: OwnedDescriptorPool,
pub(in crate::vulkan) sets: Vec<Vec<vk::DescriptorSet>>,
wired: Vec<Vec<[vk::Buffer; 3]>>,
pub(in crate::vulkan) morph_dummy: vk::Buffer,
_morph_dummy_pooled: PooledBuffer,
}
impl SkinPipeline {
pub(super) fn destroy(&self, _device: &VkDevice) {}
}
fn skin_set_current(
wired: &[vk::Buffer; 3],
want: &[vk::Buffer; 3],
storage_changed: bool,
) -> bool {
!storage_changed && wired == want
}
pub(super) struct SkinnedRtInputs<'a> {
pub objects: &'a [SkinnedDrawObject],
pub vertex_buffer: vk::Buffer,
pub index_buffer: vk::Buffer,
pub joint_buffers: &'a [PooledBuffer],
}
struct SkinnedRebuild<'a> {
ctx: RtDeviceCtx<'a>,
cmd: vk::CommandBuffer,
draw_objects: &'a [DrawObject],
skinned: SkinnedRtInputs<'a>,
frame_idx: usize,
}
struct Retired {
free_at: u64,
accel: Vec<AccelBuffer>,
_device: Vec<DeviceBuffer>,
_host: Vec<HostBuffer>,
}
impl Retired {
fn new(free_at: u64) -> Self {
Self {
free_at,
accel: Vec::new(),
_device: Vec::new(),
_host: Vec::new(),
}
}
fn destroy(&self, as_loader: &ash::khr::acceleration_structure::Device) {
for b in &self.accel {
b.destroy(as_loader);
}
}
}
struct RetireSink<'a> {
pool: &'a mut Vec<Retired>,
free_at: u64,
}
impl<'a> RetireSink<'a> {
fn new(pool: &'a mut Vec<Retired>, now: u64, depth: u64) -> Self {
Self {
pool,
free_at: now + depth,
}
}
fn accel(self, resource: AccelBuffer) {
let mut entry = Retired::new(self.free_at);
entry.accel.push(resource);
self.pool.push(entry);
}
fn device(self, resource: DeviceBuffer) {
let mut entry = Retired::new(self.free_at);
entry._device.push(resource);
self.pool.push(entry);
}
fn host(self, resource: HostBuffer) {
let mut entry = Retired::new(self.free_at);
entry._host.push(resource);
self.pool.push(entry);
}
}
#[derive(Default)]
struct SkinnedFrameRing {
deformed: Option<DeviceBuffer>,
blas: Vec<AccelBuffer>,
refit: SkinnedRefit,
tlas: Option<AccelBuffer>,
instance: Option<HostBuffer>,
geom: Option<HostBuffer>,
}
impl SkinnedFrameRing {
fn destroy(&mut self, as_loader: &ash::khr::acceleration_structure::Device) {
for b in &self.blas {
b.destroy(as_loader);
}
if let Some(t) = &self.tlas {
t.destroy(as_loader);
}
}
}
#[derive(Default)]
struct StaticFrameRing {
tlas: Option<AccelBuffer>,
instance: Option<HostBuffer>,
geom: Option<HostBuffer>,
}
impl StaticFrameRing {
fn destroy(&self, as_loader: &ash::khr::acceleration_structure::Device) {
if let Some(t) = &self.tlas {
t.destroy(as_loader);
}
}
}
fn next_slot(cursor: usize, len: usize) -> usize {
(cursor + 1) % len.max(1)
}
#[derive(Default)]
struct RtUpdateScratch {
skinned: Vec<usize>,
models: Vec<[[f32; 4]; 4]>,
shapes: Vec<SkinnedShape>,
params: Vec<BlasParams>,
blas_addresses: Vec<u64>,
instances: Vec<vk::AccelerationStructureInstanceKHR>,
geom: Vec<RtGeomEntry>,
}
fn collect_models(
object_indices: &[usize],
draw_objects: &[DrawObject],
out: &mut Vec<[[f32; 4]; 4]>,
) -> bool {
out.clear();
for &idx in object_indices {
match draw_objects.get(idx) {
Some(o) if o.resident && o.index_count >= 3 => out.push(o.model),
_ => return false,
}
}
true
}
pub(super) struct RtAccelData {
as_loader: ash::khr::acceleration_structure::Device,
blas: Vec<AccelBuffer>,
static_blas_count: usize,
live_tlas: vk::AccelerationStructureKHR,
live_geom: vk::Buffer,
live_geom_size: vk::DeviceSize,
scratch: PooledBuffer,
scratch_addr: u64,
scratch_capacity: u64,
tlas_size: u64,
instance_count: u32,
frames_in_flight: u64,
object_indices: Vec<usize>,
draw_blas_sigs: Vec<GeomSig>,
blas_addresses: Vec<u64>,
cached_models: Vec<[[f32; 4]; 4]>,
cluster_instances: Vec<vk::AccelerationStructureInstanceKHR>,
cluster_geom: Vec<RtGeomEntry>,
albedo_count: usize,
vbuf_addr: u64,
ibuf_addr: u64,
total_vertices: usize,
retire: Vec<Retired>,
frame_counter: u64,
static_ring: Vec<StaticFrameRing>,
static_cursor: usize,
skinned_ring: Vec<SkinnedFrameRing>,
skin: Option<SkinPipeline>,
live_deformed: vk::Buffer,
_deformed_dummy: DeviceBuffer,
skinned_indices: vk::Buffer,
has_skinned: bool,
frames_in_flight_usize: usize,
update_scratch: RtUpdateScratch,
}
unsafe impl Send for RtAccelData {}
impl RtAccelData {
pub(super) fn tlas(&self) -> vk::AccelerationStructureKHR {
self.live_tlas
}
pub(super) fn geom_table(&self) -> (vk::Buffer, vk::DeviceSize) {
(self.live_geom, self.live_geom_size)
}
pub(super) fn deformed_verts(&self) -> vk::Buffer {
self.live_deformed
}
pub(super) fn skinned_indices(&self) -> vk::Buffer {
self.skinned_indices
}
}
struct BlasParams {
vertex_address: u64,
max_vertex: u32,
index_byte_offset: u32,
primitive_count: u32,
}
fn blas_geometry(p: &BlasParams, index_address: u64) -> vk::AccelerationStructureGeometryKHR<'_> {
let triangles = vk::AccelerationStructureGeometryTrianglesDataKHR::default()
.vertex_format(vk::Format::R32G32B32_SFLOAT)
.vertex_data(vk::DeviceOrHostAddressConstKHR {
device_address: p.vertex_address,
})
.vertex_stride(VERTEX_STRIDE)
.max_vertex(p.max_vertex)
.index_type(vk::IndexType::UINT32)
.index_data(vk::DeviceOrHostAddressConstKHR {
device_address: index_address,
});
vk::AccelerationStructureGeometryKHR::default()
.geometry_type(vk::GeometryTypeKHR::TRIANGLES)
.geometry(vk::AccelerationStructureGeometryDataKHR { triangles })
.flags(vk::GeometryFlagsKHR::OPAQUE)
}
fn skinned_blas_geometry(
p: &BlasParams,
index_address: u64,
) -> vk::AccelerationStructureGeometryKHR<'_> {
let triangles = vk::AccelerationStructureGeometryTrianglesDataKHR::default()
.vertex_format(vk::Format::R32G32B32_SFLOAT)
.vertex_data(vk::DeviceOrHostAddressConstKHR {
device_address: p.vertex_address,
})
.vertex_stride(VERTEX_STRIDE)
.max_vertex(p.max_vertex)
.index_type(vk::IndexType::UINT32)
.index_data(vk::DeviceOrHostAddressConstKHR {
device_address: index_address,
});
vk::AccelerationStructureGeometryKHR::default()
.geometry_type(vk::GeometryTypeKHR::TRIANGLES)
.geometry(vk::AccelerationStructureGeometryDataKHR { triangles })
.flags(vk::GeometryFlagsKHR::OPAQUE)
}
fn skinned_blas_build_info<'a>(
geo: &'a vk::AccelerationStructureGeometryKHR<'a>,
update: BlasUpdate,
) -> vk::AccelerationStructureBuildGeometryInfoKHR<'a> {
let mode = match update {
BlasUpdate::Build => vk::BuildAccelerationStructureModeKHR::BUILD,
BlasUpdate::Refit => vk::BuildAccelerationStructureModeKHR::UPDATE,
};
vk::AccelerationStructureBuildGeometryInfoKHR::default()
.ty(vk::AccelerationStructureTypeKHR::BOTTOM_LEVEL)
.flags(
vk::BuildAccelerationStructureFlagsKHR::PREFER_FAST_TRACE
| vk::BuildAccelerationStructureFlagsKHR::ALLOW_UPDATE,
)
.mode(mode)
.geometries(std::slice::from_ref(geo))
}
fn tlas_geometry(instance_address: u64) -> vk::AccelerationStructureGeometryKHR<'static> {
let instances = vk::AccelerationStructureGeometryInstancesDataKHR::default()
.array_of_pointers(false)
.data(vk::DeviceOrHostAddressConstKHR {
device_address: instance_address,
});
vk::AccelerationStructureGeometryKHR::default()
.geometry_type(vk::GeometryTypeKHR::INSTANCES)
.geometry(vk::AccelerationStructureGeometryDataKHR { instances })
.flags(vk::GeometryFlagsKHR::OPAQUE)
}
fn buffer_address(device: &VkDevice, buffer: vk::Buffer) -> u64 {
unsafe {
device.get_buffer_device_address(&vk::BufferDeviceAddressInfo::default().buffer(buffer))
}
}
fn create_accel(
alloc: &DeviceAllocator,
as_loader: &ash::khr::acceleration_structure::Device,
size: u64,
ty: vk::AccelerationStructureTypeKHR,
) -> Result<AccelBuffer, String> {
let size = size.max(256);
let pooled = alloc.create_buffer(
size,
vk::BufferUsageFlags::ACCELERATION_STRUCTURE_STORAGE_KHR
| vk::BufferUsageFlags::SHADER_DEVICE_ADDRESS,
vk::MemoryPropertyFlags::DEVICE_LOCAL,
)?;
let buffer = pooled.buffer();
let info = vk::AccelerationStructureCreateInfoKHR::default()
.buffer(buffer)
.offset(0)
.size(size)
.ty(ty);
let accel = unsafe { as_loader.create_acceleration_structure(&info, None) }
.map_err(|e| format!("create acceleration structure: {e}"))?;
Ok(AccelBuffer {
accel,
_pooled: pooled,
size,
})
}
fn create_host_buffer<T: Copy>(
alloc: &DeviceAllocator,
data: &[T],
usage: vk::BufferUsageFlags,
_label: &str,
) -> Result<HostBuffer, String> {
let size = (std::mem::size_of_val(data) as vk::DeviceSize).max(16);
let pooled = alloc.create_buffer(
size,
usage,
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
)?;
let buffer = pooled.buffer();
pooled.write_slice(0, data);
Ok(HostBuffer {
buffer,
pooled,
size,
})
}
fn write_or_recreate_host<T: Copy>(
slot: &mut Option<HostBuffer>,
alloc: &DeviceAllocator,
data: &[T],
usage: vk::BufferUsageFlags,
label: &str,
retire: RetireSink,
) -> Result<(), String> {
let needed = (std::mem::size_of_val(data) as vk::DeviceSize).max(16);
if let Some(buf) = slot.as_ref()
&& buf.size >= needed
{
buf.pooled.write_slice(0, data);
return Ok(());
}
let fresh = create_host_buffer(alloc, data, usage, label)?;
if let Some(old) = slot.replace(fresh) {
retire.host(old);
}
Ok(())
}
fn ensure_accel(
slot: &mut Option<AccelBuffer>,
alloc: &DeviceAllocator,
as_loader: &ash::khr::acceleration_structure::Device,
size: u64,
ty: vk::AccelerationStructureTypeKHR,
retire: RetireSink,
) -> Result<bool, String> {
if slot.as_ref().is_some_and(|b| b.size >= size) {
return Ok(false);
}
let fresh = create_accel(alloc, as_loader, size, ty)?;
if let Some(old) = slot.replace(fresh) {
retire.accel(old);
}
Ok(true)
}
fn ensure_device_buffer(
slot: &mut Option<DeviceBuffer>,
alloc: &DeviceAllocator,
device: &VkDevice,
size: u64,
retire: RetireSink,
) -> Result<bool, String> {
if slot.as_ref().is_some_and(|b| b.size >= size) {
return Ok(false);
}
let fresh = create_device_buffer(alloc, device, size)?;
if let Some(old) = slot.replace(fresh) {
retire.device(old);
}
Ok(true)
}
fn create_device_buffer(
alloc: &DeviceAllocator,
device: &VkDevice,
size: u64,
) -> Result<DeviceBuffer, String> {
let size = size.max(VERTEX_STRIDE);
let pooled = alloc.create_buffer(
size,
vk::BufferUsageFlags::STORAGE_BUFFER
| vk::BufferUsageFlags::SHADER_DEVICE_ADDRESS
| vk::BufferUsageFlags::ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_KHR,
vk::MemoryPropertyFlags::DEVICE_LOCAL,
)?;
let buffer = pooled.buffer();
let address = buffer_address(device, buffer);
Ok(DeviceBuffer {
buffer,
_pooled: pooled,
address,
size,
})
}
pub(super) fn build_skin_pipeline(
alloc: &DeviceAllocator,
device: &VkDevice,
hot_reload: bool,
) -> Result<SkinPipeline, String> {
let spv = super::slang_builtins::RT_SKIN.compile(&super::builtins::Ctx::plain(hot_reload))?;
let module = spv_module(device, &spv)?;
let bindings: Vec<vk::DescriptorSetLayoutBinding> = (0..5u32)
.map(|b| {
vk::DescriptorSetLayoutBinding::default()
.binding(b)
.descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
.descriptor_count(1)
.stage_flags(vk::ShaderStageFlags::COMPUTE)
})
.collect();
let set_layout = device
.create_descriptor_set_layout(
&vk::DescriptorSetLayoutCreateInfo::default().bindings(&bindings),
)
.map_err(|e| format!("rt skin descriptor set layout: {e}"))?;
let pc = vk::PushConstantRange::default()
.stage_flags(vk::ShaderStageFlags::COMPUTE)
.offset(0)
.size(std::mem::size_of::<SkinParams>() as u32);
let set_layouts = [set_layout.handle()];
let pipeline_layout = device
.create_pipeline_layout(
&vk::PipelineLayoutCreateInfo::default()
.set_layouts(&set_layouts)
.push_constant_ranges(std::slice::from_ref(&pc)),
)
.map_err(|e| {
format!("rt skin pipeline layout: {e}")
})?;
let entry = std::ffi::CString::new("main").unwrap();
let stage = vk::PipelineShaderStageCreateInfo::default()
.stage(vk::ShaderStageFlags::COMPUTE)
.module(module.handle())
.name(&entry);
let info = vk::ComputePipelineCreateInfo::default()
.stage(stage)
.layout(pipeline_layout.handle());
let pipeline = crate::vulkan::pipeline_cache::create_compute_pipeline(device, &info);
let pipeline = pipeline.map_err(|e| format!("create rt skin pipeline: {e}"))?;
let morph_dummy_pooled = alloc
.create_buffer(
28,
vk::BufferUsageFlags::STORAGE_BUFFER,
vk::MemoryPropertyFlags::DEVICE_LOCAL,
)
.map_err(|e| format!("rt skin morph dummy buffer: {e}"))?;
Ok(SkinPipeline {
set_layout,
pipeline_layout,
pipeline,
descriptor_pool: OwnedDescriptorPool::null(),
sets: Vec::new(),
wired: Vec::new(),
morph_dummy: morph_dummy_pooled.buffer(),
_morph_dummy_pooled: morph_dummy_pooled,
})
}
fn build_barrier(device: &VkDevice, cmd: vk::CommandBuffer) {
let barrier = vk::MemoryBarrier::default()
.src_access_mask(vk::AccessFlags::ACCELERATION_STRUCTURE_WRITE_KHR)
.dst_access_mask(
vk::AccessFlags::ACCELERATION_STRUCTURE_READ_KHR
| vk::AccessFlags::ACCELERATION_STRUCTURE_WRITE_KHR,
);
unsafe {
device.cmd_pipeline_barrier(
cmd,
vk::PipelineStageFlags::ACCELERATION_STRUCTURE_BUILD_KHR,
vk::PipelineStageFlags::ACCELERATION_STRUCTURE_BUILD_KHR,
vk::DependencyFlags::empty(),
std::slice::from_ref(&barrier),
&[],
&[],
);
}
}
fn scratch_alignment(instance: &ash::Instance, pd: vk::PhysicalDevice) -> u64 {
let mut as_props = vk::PhysicalDeviceAccelerationStructurePropertiesKHR::default();
let mut props2 = vk::PhysicalDeviceProperties2::default().push_next(&mut as_props);
unsafe { instance.get_physical_device_properties2(pd, &mut props2) };
(as_props.min_acceleration_structure_scratch_offset_alignment as u64).max(1)
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct RtDeviceCtx<'a> {
pub(in crate::vulkan) alloc: &'a DeviceAllocator,
pub(in crate::vulkan) instance: &'a ash::Instance,
pub(in crate::vulkan) device: &'a VkDevice,
pub(in crate::vulkan) pd: vk::PhysicalDevice,
}
fn participates_in_bvh(o: &DrawObject, exclude_seethrough: bool) -> bool {
o.resident && o.index_count >= 3 && !(exclude_seethrough && o.material.see_through != 0)
}
pub(in crate::vulkan) struct RtSceneGeometry<'a> {
pub(in crate::vulkan) vertex_buffer: vk::Buffer,
pub(in crate::vulkan) index_buffer: vk::Buffer,
pub(in crate::vulkan) draw_objects: &'a [DrawObject],
pub(in crate::vulkan) clusters: &'a [InstancedCluster],
pub(in crate::vulkan) albedo_count: usize,
pub(in crate::vulkan) total_vertices: usize,
pub(in crate::vulkan) exclude_seethrough: bool,
}
pub(super) fn build_rt_accel(
ctx: RtDeviceCtx,
command_pool: vk::CommandPool,
queue: vk::Queue,
geometry: RtSceneGeometry,
frames_in_flight: usize,
hot_reload: bool,
) -> Result<Option<RtAccelData>, String> {
let RtDeviceCtx {
alloc,
instance,
device,
pd,
} = ctx;
let RtSceneGeometry {
vertex_buffer,
index_buffer,
draw_objects,
clusters,
albedo_count,
total_vertices,
exclude_seethrough,
} = geometry;
let as_loader = ash::khr::acceleration_structure::Device::new(instance, device);
let object_indices: Vec<usize> = draw_objects
.iter()
.enumerate()
.filter(|(_, o)| participates_in_bvh(o, exclude_seethrough))
.map(|(i, _)| i)
.collect();
let cluster_list: Vec<(usize, &InstancedCluster)> = clusters
.iter()
.enumerate()
.filter(|(_, c)| c.index_count >= 3 && !c.instances.is_empty())
.collect();
if object_indices.is_empty() && cluster_list.is_empty() {
return Ok(None);
}
let vbuf_addr = buffer_address(device, vertex_buffer);
let ibuf_addr = buffer_address(device, index_buffer);
let mut params: Vec<BlasParams> = Vec::with_capacity(object_indices.len() + cluster_list.len());
for &i in &object_indices {
let obj = &draw_objects[i];
let base_vertex = obj.base_vertex as u64;
params.push(BlasParams {
vertex_address: vbuf_addr + base_vertex * VERTEX_STRIDE,
max_vertex: (total_vertices as u64)
.saturating_sub(base_vertex)
.saturating_sub(1) as u32,
index_byte_offset: obj.index_offset as u32 * 4,
primitive_count: (obj.index_count / 3) as u32,
});
}
for (_, c) in &cluster_list {
params.push(BlasParams {
vertex_address: vbuf_addr,
max_vertex: (total_vertices as u64).saturating_sub(1) as u32,
index_byte_offset: c.index_offset as u32 * 4,
primitive_count: (c.index_count / 3) as u32,
});
}
let mut blas: Vec<AccelBuffer> = Vec::with_capacity(params.len());
let mut max_scratch: u64 = 0;
for p in ¶ms {
let geo = blas_geometry(p, ibuf_addr);
let build_info = vk::AccelerationStructureBuildGeometryInfoKHR::default()
.ty(vk::AccelerationStructureTypeKHR::BOTTOM_LEVEL)
.flags(vk::BuildAccelerationStructureFlagsKHR::PREFER_FAST_TRACE)
.mode(vk::BuildAccelerationStructureModeKHR::BUILD)
.geometries(std::slice::from_ref(&geo));
let mut sizes = vk::AccelerationStructureBuildSizesInfoKHR::default();
unsafe {
as_loader.get_acceleration_structure_build_sizes(
vk::AccelerationStructureBuildTypeKHR::DEVICE,
&build_info,
&[p.primitive_count],
&mut sizes,
);
}
blas.push(create_accel(
alloc,
&as_loader,
sizes.acceleration_structure_size,
vk::AccelerationStructureTypeKHR::BOTTOM_LEVEL,
)?);
max_scratch = max_scratch.max(sizes.build_scratch_size);
}
let blas_addresses: Vec<u64> = blas
.iter()
.map(|b| unsafe {
as_loader.get_acceleration_structure_device_address(
&vk::AccelerationStructureDeviceAddressInfoKHR::default()
.acceleration_structure(b.accel),
)
})
.collect();
let draw_blas_count = object_indices.len();
let mut instances: Vec<vk::AccelerationStructureInstanceKHR> =
Vec::with_capacity(object_indices.len());
let mut geom_entries: Vec<RtGeomEntry> = Vec::with_capacity(object_indices.len());
for (slot, &i) in object_indices.iter().enumerate() {
let obj = &draw_objects[i];
instances.push(tlas_instance(obj.model, slot as u32, blas_addresses[slot]));
geom_entries.push(geom_entry(obj, albedo_count as u32));
}
let mut cluster_instances: Vec<vk::AccelerationStructureInstanceKHR> = Vec::new();
let mut cluster_geom: Vec<RtGeomEntry> = Vec::new();
for (ci, (_, c)) in cluster_list.iter().enumerate() {
let blas_address = blas_addresses[draw_blas_count + ci];
for model in &c.instances {
let id = (instances.len() + cluster_instances.len()) as u32;
cluster_instances.push(tlas_instance(*model, id, blas_address));
cluster_geom.push(cluster_geom_entry(c, *model, albedo_count as u32));
}
}
instances.extend_from_slice(&cluster_instances);
geom_entries.extend_from_slice(&cluster_geom);
let instance_count = instances.len() as u32;
let instance_buffer = create_host_buffer(
alloc,
&instances,
vk::BufferUsageFlags::SHADER_DEVICE_ADDRESS
| vk::BufferUsageFlags::ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_KHR,
"RT instance buffer",
)?;
let geom_table = create_host_buffer(
alloc,
&geom_entries,
vk::BufferUsageFlags::STORAGE_BUFFER,
"RT geometry table",
)?;
let tlas_geo = tlas_geometry(buffer_address(device, instance_buffer.buffer));
let tlas_build_info = vk::AccelerationStructureBuildGeometryInfoKHR::default()
.ty(vk::AccelerationStructureTypeKHR::TOP_LEVEL)
.flags(vk::BuildAccelerationStructureFlagsKHR::PREFER_FAST_TRACE)
.mode(vk::BuildAccelerationStructureModeKHR::BUILD)
.geometries(std::slice::from_ref(&tlas_geo));
let mut tlas_sizes = vk::AccelerationStructureBuildSizesInfoKHR::default();
unsafe {
as_loader.get_acceleration_structure_build_sizes(
vk::AccelerationStructureBuildTypeKHR::DEVICE,
&tlas_build_info,
&[instance_count],
&mut tlas_sizes,
);
}
max_scratch = max_scratch.max(tlas_sizes.build_scratch_size);
let tlas = create_accel(
alloc,
&as_loader,
tlas_sizes.acceleration_structure_size,
vk::AccelerationStructureTypeKHR::TOP_LEVEL,
)?;
let align = scratch_alignment(instance, pd);
let scratch_capacity = max_scratch + align;
let scratch = alloc.create_buffer(
scratch_capacity,
vk::BufferUsageFlags::STORAGE_BUFFER | vk::BufferUsageFlags::SHADER_DEVICE_ADDRESS,
vk::MemoryPropertyFlags::DEVICE_LOCAL,
)?;
let scratch_addr = align_up(buffer_address(device, scratch.buffer()), align);
super::texture::one_shot_submit(device, command_pool, queue, |cmd| {
for (slot, p) in params.iter().enumerate() {
let geo = blas_geometry(p, ibuf_addr);
let mut bi = vk::AccelerationStructureBuildGeometryInfoKHR::default()
.ty(vk::AccelerationStructureTypeKHR::BOTTOM_LEVEL)
.flags(vk::BuildAccelerationStructureFlagsKHR::PREFER_FAST_TRACE)
.mode(vk::BuildAccelerationStructureModeKHR::BUILD)
.geometries(std::slice::from_ref(&geo));
bi.dst_acceleration_structure = blas[slot].accel;
bi.scratch_data = vk::DeviceOrHostAddressKHR {
device_address: scratch_addr,
};
let range = vk::AccelerationStructureBuildRangeInfoKHR::default()
.primitive_count(p.primitive_count)
.primitive_offset(p.index_byte_offset)
.first_vertex(0)
.transform_offset(0);
unsafe {
as_loader.cmd_build_acceleration_structures(
cmd,
std::slice::from_ref(&bi),
&[std::slice::from_ref(&range)],
);
}
build_barrier(device, cmd);
}
let tlas_geo = tlas_geometry(buffer_address(device, instance_buffer.buffer));
let mut bi = vk::AccelerationStructureBuildGeometryInfoKHR::default()
.ty(vk::AccelerationStructureTypeKHR::TOP_LEVEL)
.flags(vk::BuildAccelerationStructureFlagsKHR::PREFER_FAST_TRACE)
.mode(vk::BuildAccelerationStructureModeKHR::BUILD)
.geometries(std::slice::from_ref(&tlas_geo));
bi.dst_acceleration_structure = tlas.accel;
bi.scratch_data = vk::DeviceOrHostAddressKHR {
device_address: scratch_addr,
};
let range = vk::AccelerationStructureBuildRangeInfoKHR::default()
.primitive_count(instance_count)
.primitive_offset(0)
.first_vertex(0)
.transform_offset(0);
unsafe {
as_loader.cmd_build_acceleration_structures(
cmd,
std::slice::from_ref(&bi),
&[std::slice::from_ref(&range)],
);
}
})?;
let cached_models = object_indices
.iter()
.map(|&i| draw_objects[i].model)
.collect();
let draw_blas_sigs = object_indices
.iter()
.map(|&i| GeomSig::of(&draw_objects[i]))
.collect();
let static_blas_count = blas.len();
let deformed_dummy = create_device_buffer(alloc, device, VERTEX_STRIDE)?;
let mut static_ring: Vec<StaticFrameRing> = (0..frames_in_flight.max(1))
.map(|_| StaticFrameRing::default())
.collect();
let live_tlas = tlas.accel;
let live_geom = geom_table.buffer;
let live_geom_size = geom_table.size;
static_ring[0] = StaticFrameRing {
tlas: Some(tlas),
instance: Some(instance_buffer),
geom: Some(geom_table),
};
let skin = match build_skin_pipeline(alloc, device, hot_reload) {
Ok(s) => Some(s),
Err(e) => {
tracing::warn!(
"RT skin pipeline build failed (skinned meshes absent from reflections): {e}"
);
None
}
};
Ok(Some(RtAccelData {
as_loader,
blas,
static_blas_count,
live_tlas,
live_geom,
live_geom_size,
scratch,
scratch_addr,
scratch_capacity,
tlas_size: tlas_sizes.acceleration_structure_size,
instance_count,
frames_in_flight: (frames_in_flight.max(1)) as u64,
object_indices,
draw_blas_sigs,
blas_addresses,
cached_models,
cluster_instances,
cluster_geom,
albedo_count,
vbuf_addr,
ibuf_addr,
total_vertices,
retire: Vec::new(),
frame_counter: 0,
static_ring,
static_cursor: 0,
skinned_ring: (0..frames_in_flight.max(1))
.map(|_| SkinnedFrameRing::default())
.collect(),
skin,
live_deformed: deformed_dummy.buffer,
_deformed_dummy: deformed_dummy,
skinned_indices: vk::Buffer::null(),
has_skinned: false,
frames_in_flight_usize: frames_in_flight.max(1),
update_scratch: RtUpdateScratch::default(),
}))
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct RtRebuildPolicy {
pub mode: RtDynamicMode,
pub topology_dirty: bool,
pub exclude_seethrough: bool,
}
pub(in crate::vulkan) struct RtDynamicInputs<'a> {
pub policy: RtRebuildPolicy,
pub frame_idx: usize,
pub skinned: Option<SkinnedRtInputs<'a>>,
}
impl RtAccelData {
pub(super) fn dynamic_update(
&mut self,
ctx: RtDeviceCtx,
cmd: vk::CommandBuffer,
draw_objects: &[DrawObject],
inputs: RtDynamicInputs,
) {
let mut scratch = std::mem::take(&mut self.update_scratch);
self.dynamic_update_inner(ctx, cmd, draw_objects, inputs, &mut scratch);
self.update_scratch = scratch;
}
fn dynamic_update_inner(
&mut self,
ctx: RtDeviceCtx,
cmd: vk::CommandBuffer,
draw_objects: &[DrawObject],
inputs: RtDynamicInputs,
scratch: &mut RtUpdateScratch,
) {
let RtDynamicInputs {
policy:
RtRebuildPolicy {
mode,
topology_dirty,
exclude_seethrough,
},
frame_idx,
skinned,
} = inputs;
self.frame_counter += 1;
let now = self.frame_counter;
let mut i = 0;
while i < self.retire.len() {
if self.retire[i].free_at <= now {
let r = self.retire.swap_remove(i);
r.destroy(&self.as_loader);
} else {
i += 1;
}
}
if !mode.is_dynamic() {
return;
}
scratch.skinned.clear();
if let (Some(_), Some(s)) = (&self.skin, &skinned) {
scratch.skinned.extend(
s.objects
.iter()
.enumerate()
.filter(|(_, o)| o.visible && o.index_count >= 3)
.map(|(i, _)| i),
);
}
if topology_dirty
&& let Err(e) = self.refresh_topology(ctx, cmd, draw_objects, exclude_seethrough, now)
{
tracing::warn!("RT topology refresh failed (keeping live BVH): {e}");
}
if !scratch.skinned.is_empty() {
let s = skinned.expect("scratch.skinned non-empty implies inputs present");
if !collect_models(&self.object_indices, draw_objects, &mut scratch.models) {
return;
}
let req = SkinnedRebuild {
ctx,
cmd,
draw_objects,
skinned: s,
frame_idx,
};
if let Err(e) = self.rebuild_skinned(req, scratch) {
tracing::warn!("RT skinned rebuild failed (keeping live BVH): {e}");
}
return;
}
if topology_dirty {
return;
}
if !collect_models(&self.object_indices, draw_objects, &mut scratch.models) {
return;
}
let needs_rebuild = match mode {
RtDynamicMode::Auto => {
self.has_skinned || models_dirty(&self.cached_models, &scratch.models)
}
RtDynamicMode::Rebuild | RtDynamicMode::Tlas => true,
RtDynamicMode::Off => false,
};
if !needs_rebuild {
return;
}
if let Err(e) = self.rebuild_tlas(ctx, cmd, draw_objects, scratch) {
tracing::warn!("RT dynamic TLAS rebuild failed (keeping live BVH): {e}");
}
}
fn blas_device_address(&self, accel: vk::AccelerationStructureKHR) -> u64 {
unsafe {
self.as_loader.get_acceleration_structure_device_address(
&vk::AccelerationStructureDeviceAddressInfoKHR::default()
.acceleration_structure(accel),
)
}
}
fn refresh_topology(
&mut self,
ctx: RtDeviceCtx,
cmd: vk::CommandBuffer,
draw_objects: &[DrawObject],
exclude_seethrough: bool,
now: u64,
) -> Result<(), String> {
self.static_cursor = next_slot(self.static_cursor, self.static_ring.len());
let cursor = self.static_cursor;
let mut slot = std::mem::take(&mut self.static_ring[cursor]);
let result =
self.refresh_topology_into(ctx, cmd, draw_objects, exclude_seethrough, now, &mut slot);
self.static_ring[cursor] = slot;
result
}
fn refresh_topology_into(
&mut self,
ctx: RtDeviceCtx,
cmd: vk::CommandBuffer,
draw_objects: &[DrawObject],
exclude_seethrough: bool,
now: u64,
slot: &mut StaticFrameRing,
) -> Result<(), String> {
let RtDeviceCtx {
alloc,
instance,
device,
pd,
} = ctx;
let new_indices: Vec<usize> = draw_objects
.iter()
.enumerate()
.filter(|(_, o)| participates_in_bvh(o, exclude_seethrough))
.map(|(i, _)| i)
.collect();
let new_sigs: Vec<GeomSig> = new_indices
.iter()
.map(|&i| GeomSig::of(&draw_objects[i]))
.collect();
if new_indices.is_empty() && self.cluster_instances.is_empty() {
return Ok(());
}
let new_draw_count = new_indices.len();
let mut rebaked_clusters = self.cluster_instances.clone();
for (ci, inst) in rebaked_clusters.iter_mut().enumerate() {
let id = (new_draw_count + ci) as u32;
inst.instance_custom_index_and_mask = vk::Packed24_8::new(id & 0x00FF_FFFF, 0xFFu8);
}
let plan = plan_topology_refresh(
&self.object_indices,
&self.draw_blas_sigs,
&new_indices,
&new_sigs,
);
let old_draw_count = self.object_indices.len();
let cluster_count = self.static_blas_count - old_draw_count;
let mut fresh_slots: Vec<Option<AccelBuffer>> =
(0..new_indices.len()).map(|_| None).collect();
let mut new_addrs: Vec<u64> = vec![0; new_indices.len()];
let mut fresh_params: Vec<(BlasParams, usize)> = Vec::new();
let mut max_scratch: u64 = 0;
for (j, reuse) in plan.reuse.iter().enumerate() {
match reuse {
Some(k) => new_addrs[j] = self.blas_addresses[*k],
None => {
let obj = &draw_objects[new_indices[j]];
let base_vertex = obj.base_vertex as u64;
let p = BlasParams {
vertex_address: self.vbuf_addr + base_vertex * VERTEX_STRIDE,
max_vertex: (self.total_vertices as u64)
.saturating_sub(base_vertex)
.saturating_sub(1) as u32,
index_byte_offset: obj.index_offset as u32 * 4,
primitive_count: (obj.index_count / 3) as u32,
};
let geo = blas_geometry(&p, self.ibuf_addr);
let build_info = vk::AccelerationStructureBuildGeometryInfoKHR::default()
.ty(vk::AccelerationStructureTypeKHR::BOTTOM_LEVEL)
.flags(vk::BuildAccelerationStructureFlagsKHR::PREFER_FAST_TRACE)
.mode(vk::BuildAccelerationStructureModeKHR::BUILD)
.geometries(std::slice::from_ref(&geo));
let mut sizes = vk::AccelerationStructureBuildSizesInfoKHR::default();
unsafe {
self.as_loader.get_acceleration_structure_build_sizes(
vk::AccelerationStructureBuildTypeKHR::DEVICE,
&build_info,
&[p.primitive_count],
&mut sizes,
);
}
let blas = create_accel(
alloc,
&self.as_loader,
sizes.acceleration_structure_size,
vk::AccelerationStructureTypeKHR::BOTTOM_LEVEL,
)?;
new_addrs[j] = self.blas_device_address(blas.accel);
max_scratch = max_scratch.max(sizes.build_scratch_size);
fresh_slots[j] = Some(blas);
fresh_params.push((p, j));
}
}
}
let mut instances: Vec<vk::AccelerationStructureInstanceKHR> =
Vec::with_capacity(new_indices.len() + rebaked_clusters.len());
let mut geom_entries: Vec<RtGeomEntry> = Vec::with_capacity(instances.capacity());
for (inst, &idx) in new_indices.iter().enumerate() {
let obj = &draw_objects[idx];
instances.push(tlas_instance(obj.model, inst as u32, new_addrs[inst]));
geom_entries.push(geom_entry(obj, self.albedo_count as u32));
}
instances.extend_from_slice(&rebaked_clusters);
geom_entries.extend_from_slice(&self.cluster_geom);
let instance_count = instances.len() as u32;
write_or_recreate_host(
&mut slot.instance,
alloc,
&instances,
vk::BufferUsageFlags::SHADER_DEVICE_ADDRESS
| vk::BufferUsageFlags::ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_KHR,
"RT instance buffer",
RetireSink::new(&mut self.retire, self.frame_counter, self.frames_in_flight),
)?;
write_or_recreate_host(
&mut slot.geom,
alloc,
&geom_entries,
vk::BufferUsageFlags::STORAGE_BUFFER,
"RT geometry table",
RetireSink::new(&mut self.retire, self.frame_counter, self.frames_in_flight),
)?;
let instance_buffer = slot
.instance
.as_ref()
.expect("instance buffer written above")
.buffer;
let tlas_geo = tlas_geometry(buffer_address(device, instance_buffer));
let tlas_build_info = vk::AccelerationStructureBuildGeometryInfoKHR::default()
.ty(vk::AccelerationStructureTypeKHR::TOP_LEVEL)
.flags(vk::BuildAccelerationStructureFlagsKHR::PREFER_FAST_TRACE)
.mode(vk::BuildAccelerationStructureModeKHR::BUILD)
.geometries(std::slice::from_ref(&tlas_geo));
let mut tlas_sizes = vk::AccelerationStructureBuildSizesInfoKHR::default();
unsafe {
self.as_loader.get_acceleration_structure_build_sizes(
vk::AccelerationStructureBuildTypeKHR::DEVICE,
&tlas_build_info,
&[instance_count],
&mut tlas_sizes,
);
}
max_scratch = max_scratch.max(tlas_sizes.build_scratch_size);
let align = scratch_alignment(instance, pd);
if max_scratch + align > self.scratch_capacity {
self.grow_scratch(alloc, device, max_scratch, align)?;
}
let scratch_addr = self.scratch_addr;
ensure_accel(
&mut slot.tlas,
alloc,
&self.as_loader,
tlas_sizes.acceleration_structure_size,
vk::AccelerationStructureTypeKHR::TOP_LEVEL,
RetireSink::new(&mut self.retire, self.frame_counter, self.frames_in_flight),
)?;
let tlas = slot.tlas.as_ref().expect("TLAS sized above").accel;
for (p, j) in &fresh_params {
let geo = blas_geometry(p, self.ibuf_addr);
let mut bi = vk::AccelerationStructureBuildGeometryInfoKHR::default()
.ty(vk::AccelerationStructureTypeKHR::BOTTOM_LEVEL)
.flags(vk::BuildAccelerationStructureFlagsKHR::PREFER_FAST_TRACE)
.mode(vk::BuildAccelerationStructureModeKHR::BUILD)
.geometries(std::slice::from_ref(&geo));
bi.dst_acceleration_structure =
fresh_slots[*j].as_ref().expect("fresh BLAS present").accel;
bi.scratch_data = vk::DeviceOrHostAddressKHR {
device_address: scratch_addr,
};
let range = vk::AccelerationStructureBuildRangeInfoKHR::default()
.primitive_count(p.primitive_count)
.primitive_offset(p.index_byte_offset)
.first_vertex(0)
.transform_offset(0);
unsafe {
self.as_loader.cmd_build_acceleration_structures(
cmd,
std::slice::from_ref(&bi),
&[std::slice::from_ref(&range)],
);
}
build_barrier(device, cmd);
}
let tlas_geo = tlas_geometry(buffer_address(device, instance_buffer));
let mut bi = vk::AccelerationStructureBuildGeometryInfoKHR::default()
.ty(vk::AccelerationStructureTypeKHR::TOP_LEVEL)
.flags(vk::BuildAccelerationStructureFlagsKHR::PREFER_FAST_TRACE)
.mode(vk::BuildAccelerationStructureModeKHR::BUILD)
.geometries(std::slice::from_ref(&tlas_geo));
bi.dst_acceleration_structure = tlas;
bi.scratch_data = vk::DeviceOrHostAddressKHR {
device_address: scratch_addr,
};
let range = vk::AccelerationStructureBuildRangeInfoKHR::default()
.primitive_count(instance_count)
.primitive_offset(0)
.first_vertex(0)
.transform_offset(0);
unsafe {
self.as_loader.cmd_build_acceleration_structures(
cmd,
std::slice::from_ref(&bi),
&[std::slice::from_ref(&range)],
);
let barrier = vk::MemoryBarrier::default()
.src_access_mask(vk::AccessFlags::ACCELERATION_STRUCTURE_WRITE_KHR)
.dst_access_mask(vk::AccessFlags::ACCELERATION_STRUCTURE_READ_KHR);
device.cmd_pipeline_barrier(
cmd,
vk::PipelineStageFlags::ACCELERATION_STRUCTURE_BUILD_KHR,
vk::PipelineStageFlags::FRAGMENT_SHADER,
vk::DependencyFlags::empty(),
std::slice::from_ref(&barrier),
&[],
&[],
);
}
let mut old_blas = std::mem::take(&mut self.blas);
let _ = std::mem::take(&mut self.blas_addresses);
let cluster_blas: Vec<AccelBuffer> = old_blas.split_off(old_draw_count);
let mut draw_head: Vec<Option<AccelBuffer>> = old_blas.into_iter().map(Some).collect();
let mut new_blas: Vec<AccelBuffer> = Vec::with_capacity(new_indices.len() + cluster_count);
for (j, reuse) in plan.reuse.iter().enumerate() {
match reuse {
Some(k) => new_blas.push(draw_head[*k].take().expect("reused draw BLAS present")),
None => new_blas.push(fresh_slots[j].take().expect("fresh draw BLAS present")),
}
}
let orphans: Vec<AccelBuffer> = plan
.retire
.iter()
.map(|&k| draw_head[k].take().expect("orphan draw BLAS present"))
.collect();
if !orphans.is_empty() {
let mut entry = Retired::new(now + self.frames_in_flight);
entry.accel = orphans;
self.retire.push(entry);
}
new_blas.extend(cluster_blas);
for b in &new_blas[new_indices.len()..] {
new_addrs.push(self.blas_device_address(b.accel));
}
let geom = slot.geom.as_ref().expect("geometry table written above");
self.live_tlas = tlas;
self.live_geom = geom.buffer;
self.live_geom_size = geom.size;
self.blas = new_blas;
self.blas_addresses = new_addrs;
self.static_blas_count = new_indices.len() + cluster_count;
self.draw_blas_sigs = new_sigs;
self.cluster_instances = rebaked_clusters;
self.tlas_size = tlas_sizes.acceleration_structure_size;
self.instance_count = instance_count;
self.has_skinned = false;
for ring in &mut self.skinned_ring {
ring.refit.reset();
}
self.cached_models = new_indices.iter().map(|&i| draw_objects[i].model).collect();
self.object_indices = new_indices;
Ok(())
}
fn rebuild_tlas(
&mut self,
ctx: RtDeviceCtx,
cmd: vk::CommandBuffer,
draw_objects: &[DrawObject],
scratch: &mut RtUpdateScratch,
) -> Result<(), String> {
self.static_cursor = next_slot(self.static_cursor, self.static_ring.len());
let cursor = self.static_cursor;
let mut slot = std::mem::take(&mut self.static_ring[cursor]);
let result = self.rebuild_tlas_into(ctx, cmd, draw_objects, scratch, &mut slot);
self.static_ring[cursor] = slot;
result
}
fn rebuild_tlas_into(
&mut self,
ctx: RtDeviceCtx,
cmd: vk::CommandBuffer,
draw_objects: &[DrawObject],
scratch: &mut RtUpdateScratch,
slot: &mut StaticFrameRing,
) -> Result<(), String> {
let RtDeviceCtx {
alloc,
device,
instance: _,
pd: _,
} = ctx;
let RtUpdateScratch {
models,
instances,
geom: geom_entries,
..
} = scratch;
instances.clear();
geom_entries.clear();
for (inst, &idx) in self.object_indices.iter().enumerate() {
let obj = &draw_objects[idx];
instances.push(tlas_instance(
obj.model,
inst as u32,
self.blas_addresses[inst],
));
geom_entries.push(geom_entry(obj, self.albedo_count as u32));
}
instances.extend_from_slice(&self.cluster_instances);
geom_entries.extend_from_slice(&self.cluster_geom);
self.instance_count = instances.len() as u32;
write_or_recreate_host(
&mut slot.instance,
alloc,
instances.as_slice(),
vk::BufferUsageFlags::SHADER_DEVICE_ADDRESS
| vk::BufferUsageFlags::ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_KHR,
"RT instance buffer",
RetireSink::new(&mut self.retire, self.frame_counter, self.frames_in_flight),
)?;
write_or_recreate_host(
&mut slot.geom,
alloc,
geom_entries.as_slice(),
vk::BufferUsageFlags::STORAGE_BUFFER,
"RT geometry table",
RetireSink::new(&mut self.retire, self.frame_counter, self.frames_in_flight),
)?;
ensure_accel(
&mut slot.tlas,
alloc,
&self.as_loader,
self.tlas_size,
vk::AccelerationStructureTypeKHR::TOP_LEVEL,
RetireSink::new(&mut self.retire, self.frame_counter, self.frames_in_flight),
)?;
let instance_buffer = slot
.instance
.as_ref()
.expect("instance buffer written above")
.buffer;
let tlas = slot.tlas.as_ref().expect("TLAS sized above").accel;
let tlas_geo = tlas_geometry(buffer_address(device, instance_buffer));
let mut bi = vk::AccelerationStructureBuildGeometryInfoKHR::default()
.ty(vk::AccelerationStructureTypeKHR::TOP_LEVEL)
.flags(vk::BuildAccelerationStructureFlagsKHR::PREFER_FAST_TRACE)
.mode(vk::BuildAccelerationStructureModeKHR::BUILD)
.geometries(std::slice::from_ref(&tlas_geo));
bi.dst_acceleration_structure = tlas;
bi.scratch_data = vk::DeviceOrHostAddressKHR {
device_address: self.scratch_addr,
};
let range = vk::AccelerationStructureBuildRangeInfoKHR::default()
.primitive_count(self.instance_count)
.primitive_offset(0)
.first_vertex(0)
.transform_offset(0);
unsafe {
self.as_loader.cmd_build_acceleration_structures(
cmd,
std::slice::from_ref(&bi),
&[std::slice::from_ref(&range)],
);
let barrier = vk::MemoryBarrier::default()
.src_access_mask(vk::AccessFlags::ACCELERATION_STRUCTURE_WRITE_KHR)
.dst_access_mask(vk::AccessFlags::ACCELERATION_STRUCTURE_READ_KHR);
device.cmd_pipeline_barrier(
cmd,
vk::PipelineStageFlags::ACCELERATION_STRUCTURE_BUILD_KHR,
vk::PipelineStageFlags::FRAGMENT_SHADER,
vk::DependencyFlags::empty(),
std::slice::from_ref(&barrier),
&[],
&[],
);
}
let geom = slot.geom.as_ref().expect("geometry table written above");
self.live_tlas = tlas;
self.live_geom = geom.buffer;
self.live_geom_size = geom.size;
if self.has_skinned {
self.has_skinned = false;
for ring in &mut self.skinned_ring {
ring.refit.reset();
}
}
self.cached_models.clear();
self.cached_models.extend_from_slice(models);
Ok(())
}
fn rebuild_skinned(
&mut self,
req: SkinnedRebuild,
scratch: &mut RtUpdateScratch,
) -> Result<(), String> {
let frame_idx = req.frame_idx;
let mut slot = std::mem::take(&mut self.skinned_ring[frame_idx]);
let result = self.rebuild_skinned_into(req, scratch, &mut slot);
self.skinned_ring[frame_idx] = slot;
result
}
fn rebuild_skinned_into(
&mut self,
req: SkinnedRebuild,
scratch: &mut RtUpdateScratch,
slot: &mut SkinnedFrameRing,
) -> Result<(), String> {
let SkinnedRebuild {
ctx,
cmd,
draw_objects,
skinned,
frame_idx,
} = req;
let skinned = &skinned;
let RtUpdateScratch {
skinned: skinned_objects,
models,
shapes,
params: skinned_params,
blas_addresses: skinned_blas_addresses,
instances,
geom: geom_entries,
} = scratch;
let RtDeviceCtx {
alloc,
instance,
device,
pd,
} = ctx;
let skin = self
.skin
.as_ref()
.ok_or("rebuild_skinned called without a skin pipeline")?;
let pipeline = skin.pipeline.handle();
let pipeline_layout = skin.pipeline_layout.handle();
let deformed_extent: u64 = skinned_objects
.iter()
.map(|&i| {
skinned.objects[i].vertex_base as u64 + skinned.objects[i].vertex_count as u64
})
.max()
.unwrap_or(0);
let deformed_bytes = (deformed_extent * VERTEX_STRIDE).max(VERTEX_STRIDE);
let mut storage_changed = ensure_device_buffer(
&mut slot.deformed,
alloc,
device,
deformed_bytes,
RetireSink::new(&mut self.retire, self.frame_counter, self.frames_in_flight),
)?;
let deformed = slot
.deformed
.as_ref()
.expect("deformed buffer sized above")
.handle();
self.ensure_skin_sets(device, skinned.objects.len())?;
let skin = self.skin.as_mut().expect("skin pipeline present");
let frame_sets = &skin.sets[frame_idx];
let frame_wired = &mut skin.wired[frame_idx];
for &obj_idx in skinned_objects.iter() {
let joint_buffer = skinned
.joint_buffers
.get(obj_idx)
.map(|b| b.buffer())
.unwrap_or(vk::Buffer::null());
if joint_buffer == vk::Buffer::null() {
continue;
}
let want = [skinned.vertex_buffer, joint_buffer, deformed.buffer];
if skin_set_current(&frame_wired[obj_idx], &want, storage_changed) {
continue;
}
frame_wired[obj_idx] = want;
let src_info = vk::DescriptorBufferInfo::default()
.buffer(skinned.vertex_buffer)
.offset(0)
.range(vk::WHOLE_SIZE);
let pal_info = vk::DescriptorBufferInfo::default()
.buffer(joint_buffer)
.offset(0)
.range(vk::WHOLE_SIZE);
let dst_info = vk::DescriptorBufferInfo::default()
.buffer(deformed.buffer)
.offset(0)
.range(vk::WHOLE_SIZE);
let set = frame_sets[obj_idx];
let dummy_info = vk::DescriptorBufferInfo::default()
.buffer(skin.morph_dummy)
.offset(0)
.range(vk::WHOLE_SIZE);
let writes = [
vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(0)
.descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
.buffer_info(std::slice::from_ref(&src_info)),
vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(1)
.descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
.buffer_info(std::slice::from_ref(&pal_info)),
vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(2)
.descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
.buffer_info(std::slice::from_ref(&dst_info)),
vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(3)
.descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
.buffer_info(std::slice::from_ref(&dummy_info)),
vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(4)
.descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
.buffer_info(std::slice::from_ref(&dummy_info)),
];
unsafe { device.update_descriptor_sets(&writes, &[]) };
}
let skin = self.skin.as_ref().expect("skin pipeline present");
let frame_sets = &skin.sets[frame_idx];
unsafe {
device.cmd_bind_pipeline(cmd, vk::PipelineBindPoint::COMPUTE, pipeline);
}
for &obj_idx in skinned_objects.iter() {
let obj = &skinned.objects[obj_idx];
let joint_buffer = skinned
.joint_buffers
.get(obj_idx)
.map(|b| b.buffer())
.unwrap_or(vk::Buffer::null());
if joint_buffer == vk::Buffer::null() {
continue;
}
let params = SkinParams {
vertex_base: obj.vertex_base,
vertex_count: obj.vertex_count as u32,
joint_count: obj.joint_count.max(1) as u32,
target_count: 0,
};
let bytes = unsafe {
std::slice::from_raw_parts(
¶ms as *const SkinParams as *const u8,
std::mem::size_of::<SkinParams>(),
)
};
unsafe {
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::COMPUTE,
pipeline_layout,
0,
std::slice::from_ref(&frame_sets[obj_idx]),
&[],
);
device.cmd_push_constants(
cmd,
pipeline_layout,
vk::ShaderStageFlags::COMPUTE,
0,
bytes,
);
device.cmd_dispatch(cmd, (obj.vertex_count as u32).div_ceil(64), 1, 1);
}
}
unsafe {
let barrier = vk::MemoryBarrier::default()
.src_access_mask(vk::AccessFlags::SHADER_WRITE)
.dst_access_mask(
vk::AccessFlags::ACCELERATION_STRUCTURE_READ_KHR | vk::AccessFlags::SHADER_READ,
);
device.cmd_pipeline_barrier(
cmd,
vk::PipelineStageFlags::COMPUTE_SHADER,
vk::PipelineStageFlags::ACCELERATION_STRUCTURE_BUILD_KHR
| vk::PipelineStageFlags::FRAGMENT_SHADER,
vk::DependencyFlags::empty(),
std::slice::from_ref(&barrier),
&[],
&[],
);
}
let skinned_idx_addr = buffer_address(device, skinned.index_buffer);
let max_vertex = deformed_extent.saturating_sub(1) as u32;
skinned_params.clear();
shapes.clear();
for &i in skinned_objects.iter() {
let obj = &skinned.objects[i];
skinned_params.push(BlasParams {
vertex_address: deformed.address,
max_vertex,
index_byte_offset: obj.index_offset as u32 * 4,
primitive_count: (obj.index_count / 3) as u32,
});
shapes.push(SkinnedShape {
index_offset: obj.index_offset,
index_count: obj.index_count,
vertex_extent: deformed_extent as u32,
});
}
let mut max_scratch: u64 = 0;
for (si, p) in skinned_params.iter().enumerate() {
let geo = skinned_blas_geometry(p, skinned_idx_addr);
let build_info = skinned_blas_build_info(&geo, BlasUpdate::Build);
let mut sizes = vk::AccelerationStructureBuildSizesInfoKHR::default();
unsafe {
self.as_loader.get_acceleration_structure_build_sizes(
vk::AccelerationStructureBuildTypeKHR::DEVICE,
&build_info,
&[p.primitive_count],
&mut sizes,
);
}
let needed = sizes.acceleration_structure_size;
match slot.blas.get(si) {
Some(b) if b.size >= needed => {}
Some(_) => {
let fresh = create_accel(
alloc,
&self.as_loader,
needed,
vk::AccelerationStructureTypeKHR::BOTTOM_LEVEL,
)?;
std::mem::replace(&mut slot.blas[si], fresh).destroy(&self.as_loader);
storage_changed = true;
}
None => {
slot.blas.push(create_accel(
alloc,
&self.as_loader,
needed,
vk::AccelerationStructureTypeKHR::BOTTOM_LEVEL,
)?);
storage_changed = true;
}
}
max_scratch = max_scratch
.max(sizes.build_scratch_size)
.max(sizes.update_scratch_size);
}
storage_changed |= slot.blas.len() > skinned_params.len();
skinned_blas_addresses.clear();
skinned_blas_addresses.extend(slot.blas[..skinned_params.len()].iter().map(|b| {
unsafe {
self.as_loader.get_acceleration_structure_device_address(
&vk::AccelerationStructureDeviceAddressInfoKHR::default()
.acceleration_structure(b.accel),
)
}
}));
instances.clear();
geom_entries.clear();
for (inst, &idx) in self.object_indices.iter().enumerate() {
let obj = &draw_objects[idx];
instances.push(tlas_instance(
obj.model,
inst as u32,
self.blas_addresses[inst],
));
geom_entries.push(geom_entry(obj, self.albedo_count as u32));
}
instances.extend_from_slice(&self.cluster_instances);
geom_entries.extend_from_slice(&self.cluster_geom);
for (si, &obj_idx) in skinned_objects.iter().enumerate() {
let obj = &skinned.objects[obj_idx];
let id = instances.len() as u32;
instances.push(tlas_instance(obj.model, id, skinned_blas_addresses[si]));
geom_entries.push(skinned_geom_entry(obj, self.albedo_count as u32));
}
let instance_count = instances.len() as u32;
write_or_recreate_host(
&mut slot.instance,
alloc,
instances.as_slice(),
vk::BufferUsageFlags::SHADER_DEVICE_ADDRESS
| vk::BufferUsageFlags::ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_KHR,
"RT instance buffer",
RetireSink::new(&mut self.retire, self.frame_counter, self.frames_in_flight),
)?;
write_or_recreate_host(
&mut slot.geom,
alloc,
geom_entries.as_slice(),
vk::BufferUsageFlags::STORAGE_BUFFER,
"RT geometry table",
RetireSink::new(&mut self.retire, self.frame_counter, self.frames_in_flight),
)?;
let instance_buffer = slot
.instance
.as_ref()
.expect("instance buffer written above")
.buffer;
let tlas_geo = tlas_geometry(buffer_address(device, instance_buffer));
let tlas_build_info = vk::AccelerationStructureBuildGeometryInfoKHR::default()
.ty(vk::AccelerationStructureTypeKHR::TOP_LEVEL)
.flags(vk::BuildAccelerationStructureFlagsKHR::PREFER_FAST_TRACE)
.mode(vk::BuildAccelerationStructureModeKHR::BUILD)
.geometries(std::slice::from_ref(&tlas_geo));
let mut tlas_sizes = vk::AccelerationStructureBuildSizesInfoKHR::default();
unsafe {
self.as_loader.get_acceleration_structure_build_sizes(
vk::AccelerationStructureBuildTypeKHR::DEVICE,
&tlas_build_info,
&[instance_count],
&mut tlas_sizes,
);
}
max_scratch = max_scratch.max(tlas_sizes.build_scratch_size);
ensure_accel(
&mut slot.tlas,
alloc,
&self.as_loader,
tlas_sizes.acceleration_structure_size,
vk::AccelerationStructureTypeKHR::TOP_LEVEL,
RetireSink::new(&mut self.retire, self.frame_counter, self.frames_in_flight),
)?;
let tlas = slot.tlas.as_ref().expect("TLAS sized above").accel;
let align = scratch_alignment(instance, pd);
if max_scratch + align > self.scratch_size() {
self.grow_scratch(alloc, device, max_scratch, align)?;
}
let scratch_addr = self.scratch_addr;
let update = slot.refit.plan(shapes, storage_changed);
for (si, p) in skinned_params.iter().enumerate() {
let geo = skinned_blas_geometry(p, skinned_idx_addr);
let mut bi = skinned_blas_build_info(&geo, update);
bi.dst_acceleration_structure = slot.blas[si].accel;
if update == BlasUpdate::Refit {
bi.src_acceleration_structure = slot.blas[si].accel;
}
bi.scratch_data = vk::DeviceOrHostAddressKHR {
device_address: scratch_addr,
};
let range = vk::AccelerationStructureBuildRangeInfoKHR::default()
.primitive_count(p.primitive_count)
.primitive_offset(p.index_byte_offset)
.first_vertex(0)
.transform_offset(0);
unsafe {
self.as_loader.cmd_build_acceleration_structures(
cmd,
std::slice::from_ref(&bi),
&[std::slice::from_ref(&range)],
);
}
build_barrier(device, cmd);
}
let tlas_geo = tlas_geometry(buffer_address(device, instance_buffer));
let mut bi = vk::AccelerationStructureBuildGeometryInfoKHR::default()
.ty(vk::AccelerationStructureTypeKHR::TOP_LEVEL)
.flags(vk::BuildAccelerationStructureFlagsKHR::PREFER_FAST_TRACE)
.mode(vk::BuildAccelerationStructureModeKHR::BUILD)
.geometries(std::slice::from_ref(&tlas_geo));
bi.dst_acceleration_structure = tlas;
bi.scratch_data = vk::DeviceOrHostAddressKHR {
device_address: scratch_addr,
};
let range = vk::AccelerationStructureBuildRangeInfoKHR::default()
.primitive_count(instance_count)
.primitive_offset(0)
.first_vertex(0)
.transform_offset(0);
unsafe {
self.as_loader.cmd_build_acceleration_structures(
cmd,
std::slice::from_ref(&bi),
&[std::slice::from_ref(&range)],
);
let barrier = vk::MemoryBarrier::default()
.src_access_mask(vk::AccessFlags::ACCELERATION_STRUCTURE_WRITE_KHR)
.dst_access_mask(vk::AccessFlags::ACCELERATION_STRUCTURE_READ_KHR);
device.cmd_pipeline_barrier(
cmd,
vk::PipelineStageFlags::ACCELERATION_STRUCTURE_BUILD_KHR,
vk::PipelineStageFlags::FRAGMENT_SHADER,
vk::DependencyFlags::empty(),
std::slice::from_ref(&barrier),
&[],
&[],
);
}
for leftover in slot.blas.drain(skinned_params.len()..) {
leftover.destroy(&self.as_loader);
}
let geom = slot.geom.as_ref().expect("geometry table written above");
self.live_tlas = tlas;
self.live_geom = geom.buffer;
self.live_geom_size = geom.size;
self.live_deformed = deformed.buffer;
self.instance_count = instance_count;
self.skinned_indices = skinned.index_buffer;
self.has_skinned = true;
self.cached_models.clear();
self.cached_models.extend_from_slice(models);
Ok(())
}
fn scratch_size(&self) -> u64 {
self.scratch_capacity
}
fn grow_scratch(
&mut self,
alloc: &DeviceAllocator,
device: &VkDevice,
required: u64,
align: u64,
) -> Result<(), String> {
let new_capacity = required + align;
let buffer = alloc.create_buffer(
new_capacity,
vk::BufferUsageFlags::STORAGE_BUFFER | vk::BufferUsageFlags::SHADER_DEVICE_ADDRESS,
vk::MemoryPropertyFlags::DEVICE_LOCAL,
)?;
let addr = align_up(buffer_address(device, buffer.buffer()), align);
self.scratch = buffer;
self.scratch_addr = addr;
self.scratch_capacity = new_capacity;
Ok(())
}
fn ensure_skin_sets(&mut self, device: &VkDevice, object_count: usize) -> Result<(), String> {
let frames = self.frames_in_flight_usize;
let skin = self
.skin
.as_mut()
.ok_or("ensure_skin_sets called without a skin pipeline")?;
ensure_skin_sets(device, skin, frames, object_count)
}
pub(super) fn destroy(&mut self, device: &VkDevice) {
for r in self.retire.drain(..) {
r.destroy(&self.as_loader);
}
for slot in &mut self.skinned_ring {
slot.destroy(&self.as_loader);
}
for slot in &self.static_ring {
slot.destroy(&self.as_loader);
}
for b in &self.blas {
b.destroy(&self.as_loader);
}
if let Some(skin) = &self.skin {
skin.destroy(device);
}
}
}
pub(super) fn ensure_skin_sets(
device: &VkDevice,
skin: &mut SkinPipeline,
frames: usize,
object_count: usize,
) -> Result<(), String> {
let have = skin.sets.first().map(|s| s.len()).unwrap_or(0);
if object_count == 0 || have >= object_count {
return Ok(());
}
let total = (frames * object_count) as u32;
let pool_size = vk::DescriptorPoolSize::default()
.ty(vk::DescriptorType::STORAGE_BUFFER)
.descriptor_count(total * 5);
let pool = device
.create_descriptor_pool(
&vk::DescriptorPoolCreateInfo::default()
.pool_sizes(std::slice::from_ref(&pool_size))
.max_sets(total),
)
.map_err(|e| format!("skin descriptor pool: {e}"))?;
let mut sets: Vec<Vec<vk::DescriptorSet>> = Vec::with_capacity(frames);
for _ in 0..frames {
let layouts: Vec<vk::DescriptorSetLayout> = (0..object_count)
.map(|_| skin.set_layout.handle())
.collect();
let alloc = unsafe {
device.allocate_descriptor_sets(
&vk::DescriptorSetAllocateInfo::default()
.descriptor_pool(pool.handle())
.set_layouts(&layouts),
)
}
.map_err(|e| format!("alloc skin descriptor sets: {e}"))?;
sets.push(alloc);
}
skin.descriptor_pool = pool;
skin.sets = sets;
skin.wired = (0..frames)
.map(|_| vec![[vk::Buffer::null(); 3]; object_count])
.collect();
Ok(())
}
pub(super) fn create_main_deformed_buffer(
alloc: &DeviceAllocator,
size: u64,
) -> Result<DeviceBuffer, String> {
let size = size.max(VERTEX_STRIDE);
let pooled = alloc.create_buffer(
size,
vk::BufferUsageFlags::STORAGE_BUFFER | vk::BufferUsageFlags::VERTEX_BUFFER,
vk::MemoryPropertyFlags::DEVICE_LOCAL,
)?;
let buffer = pooled.buffer();
Ok(DeviceBuffer {
buffer,
_pooled: pooled,
address: 0,
size,
})
}
impl super::context::VkContext {
pub(in crate::vulkan) fn rebuild_rt_accel(&mut self) {
let fresh = match build_rt_accel(
RtDeviceCtx {
alloc: &self.alloc,
instance: &self.instance,
device: &self.device,
pd: self.physical_device,
},
self.commands.command_pool,
self.graphics_queue,
RtSceneGeometry {
vertex_buffer: self.geometry.vertex_buffer.buffer(),
index_buffer: self.geometry.index_buffer.buffer(),
draw_objects: &self.draw.objects,
clusters: &self.instanced.clusters,
albedo_count: self.textures.len(),
total_vertices: self.rt_static_vertex_count,
exclude_seethrough: self.seethrough_meshes_enabled(),
},
self.frames_in_flight,
self.hot_reload.enabled,
) {
Ok(accel) => accel,
Err(e) => {
tracing::warn!("RT acceleration-structure rebuild failed (dropping BVH): {e}");
None
}
};
if let Some(mut old) = self.rt_accel.take() {
old.destroy(&self.device);
}
self.rt_accel = fresh;
let device = self.device.clone();
let (vertex_buffer, index_buffer) = (
self.geometry.vertex_buffer.buffer(),
self.geometry.index_buffer.buffer(),
);
if let Some(rt) = self.rt_reflections.as_ref() {
rt.rewire_geometry(&device, vertex_buffer, index_buffer);
}
if let Some(transparent) = self.transparent.as_ref() {
transparent.wire_rt_geometry(&device, vertex_buffer, index_buffer);
}
}
pub(in crate::vulkan) fn build_main_skin(&mut self, vertex_total: usize) -> Result<(), String> {
let device = self.device.clone();
let frames = self.frames_in_flight.max(1);
let n = self.skinned.draw_objects.len();
if n == 0 {
return Ok(());
}
let mut skin = build_skin_pipeline(&self.alloc, &device, self.hot_reload.enabled)?;
ensure_skin_sets(&device, &mut skin, frames, n)?;
let deformed = self.build_deformed_ring(&skin.sets, vertex_total)?;
for f in 0..frames {
for o in 0..n {
let set = skin.sets[f][o];
let pal_info = vk::DescriptorBufferInfo::default()
.buffer(self.skinned.joint_buffers[f][o].buffer())
.offset(0)
.range(vk::WHOLE_SIZE);
let dummy_info = vk::DescriptorBufferInfo::default()
.buffer(skin.morph_dummy)
.offset(0)
.range(vk::WHOLE_SIZE);
let writes = [
vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(1)
.descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
.buffer_info(std::slice::from_ref(&pal_info)),
vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(3)
.descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
.buffer_info(std::slice::from_ref(&dummy_info)),
vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(4)
.descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
.buffer_info(std::slice::from_ref(&dummy_info)),
];
unsafe { device.update_descriptor_sets(&writes, &[]) };
}
}
self.skinned.skin = Some(skin);
self.skinned.deformed = deformed;
self.draw.n_skinned = n;
Ok(())
}
pub(in crate::vulkan) fn refresh_main_skin_geometry(
&mut self,
vertex_total: usize,
) -> Result<(), String> {
let Some(skin) = self.skinned.skin.as_ref() else {
return Ok(());
};
let deformed = self.build_deformed_ring(&skin.sets, vertex_total)?;
self.skinned.deformed = deformed;
Ok(())
}
fn build_deformed_ring(
&self,
sets: &[Vec<vk::DescriptorSet>],
vertex_total: usize,
) -> Result<Vec<DeviceBuffer>, String> {
let frames = self.frames_in_flight.max(1);
let n = self.skinned.draw_objects.len();
let deformed_bytes = (vertex_total as u64 * VERTEX_STRIDE).max(VERTEX_STRIDE);
let mut deformed: Vec<DeviceBuffer> = Vec::with_capacity(frames);
for _ in 0..frames {
deformed.push(create_main_deformed_buffer(&self.alloc, deformed_bytes)?);
}
let src_buffer = self.skinned.vertex_buffer.buffer();
for (f, deformed_buf) in deformed.iter().enumerate() {
for &set in sets[f].iter().take(n) {
let src_info = vk::DescriptorBufferInfo::default()
.buffer(src_buffer)
.offset(0)
.range(vk::WHOLE_SIZE);
let dst_info = vk::DescriptorBufferInfo::default()
.buffer(deformed_buf.buffer)
.offset(0)
.range(vk::WHOLE_SIZE);
let writes = [
vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(0)
.descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
.buffer_info(std::slice::from_ref(&src_info)),
vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(2)
.descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
.buffer_info(std::slice::from_ref(&dst_info)),
];
unsafe { self.device.update_descriptor_sets(&writes, &[]) };
}
}
self.skinned
.deformed_primed
.store(false, std::sync::atomic::Ordering::Relaxed);
Ok(deformed)
}
pub(in crate::vulkan) fn encode_skin(&self, cmd: vk::CommandBuffer, frame_idx: usize) {
let Some(skin) = self.skinned.skin.as_ref() else {
return;
};
if self.draw.n_skinned == 0 || self.skinned.deformed.len() <= frame_idx {
return;
}
let device = &self.device;
let frame_sets = &skin.sets[frame_idx];
unsafe {
device.cmd_bind_pipeline(cmd, vk::PipelineBindPoint::COMPUTE, skin.pipeline.handle());
}
for (o, obj) in self
.skinned
.draw_objects
.iter()
.take(self.draw.n_skinned)
.enumerate()
{
let params = SkinParams {
vertex_base: obj.vertex_base,
vertex_count: obj.vertex_count as u32,
joint_count: obj.joint_count.max(1) as u32,
target_count: self
.skinned
.morph_target_counts
.get(o)
.copied()
.unwrap_or(0),
};
let bytes = unsafe {
std::slice::from_raw_parts(
¶ms as *const SkinParams as *const u8,
std::mem::size_of::<SkinParams>(),
)
};
unsafe {
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::COMPUTE,
skin.pipeline_layout.handle(),
0,
std::slice::from_ref(&frame_sets[o]),
&[],
);
device.cmd_push_constants(
cmd,
skin.pipeline_layout.handle(),
vk::ShaderStageFlags::COMPUTE,
0,
bytes,
);
device.cmd_dispatch(cmd, (obj.vertex_count as u32).div_ceil(64), 1, 1);
}
}
let barrier = vk::MemoryBarrier::default()
.src_access_mask(vk::AccessFlags::SHADER_WRITE)
.dst_access_mask(vk::AccessFlags::VERTEX_ATTRIBUTE_READ);
unsafe {
device.cmd_pipeline_barrier(
cmd,
vk::PipelineStageFlags::COMPUTE_SHADER,
vk::PipelineStageFlags::VERTEX_INPUT,
vk::DependencyFlags::empty(),
std::slice::from_ref(&barrier),
&[],
&[],
);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn next_slot_wraps_around_the_ring() {
assert_eq!(next_slot(0, 3), 1);
assert_eq!(next_slot(1, 3), 2);
assert_eq!(next_slot(2, 3), 0);
assert_eq!(next_slot(0, 1), 0);
}
fn buf(raw: u64) -> vk::Buffer {
use ash::vk::Handle;
vk::Buffer::from_raw(raw)
}
#[test]
fn skin_set_skips_when_the_slot_hands_back_the_same_buffers() {
let wired = [buf(1), buf(2), buf(3)];
assert!(skin_set_current(&wired, &wired.clone(), false));
}
#[test]
fn skin_set_repoints_when_the_deformed_buffer_moves() {
let wired = [buf(1), buf(2), buf(3)];
let want = [buf(1), buf(2), buf(4)];
assert!(!skin_set_current(&wired, &want, false));
}
#[test]
fn skin_set_repoints_when_a_named_resource_was_reallocated() {
let wired = [buf(1), buf(2), buf(3)];
assert!(!skin_set_current(&wired, &wired.clone(), true));
}
#[test]
fn pack_instance_transform_transposes_column_major_to_3x4_row_major() {
let model = [
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[10.0, 20.0, 30.0, 1.0],
];
let t = pack_instance_transform(model);
assert_eq!(
t.matrix,
[
1.0, 0.0, 0.0, 10.0, 0.0, 1.0, 0.0, 20.0, 0.0, 0.0, 1.0, 30.0
]
);
}
#[test]
fn pack_instance_transform_preserves_a_rotation_shear() {
let model = [
[1.0, 2.0, 3.0, 0.0],
[4.0, 5.0, 6.0, 0.0],
[7.0, 8.0, 9.0, 0.0],
[10.0, 11.0, 12.0, 1.0],
];
let t = pack_instance_transform(model);
assert_eq!(
t.matrix,
[
1.0, 4.0, 7.0, 10.0, 2.0, 5.0, 8.0, 11.0, 3.0, 6.0, 9.0, 12.0
]
);
}
#[test]
fn instance_packs_custom_index_and_full_mask() {
let d = tlas_instance(
[
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
],
7,
0xDEAD_BEEF,
);
assert_eq!(d.instance_custom_index_and_mask.low_24(), 7);
assert_eq!(d.instance_custom_index_and_mask.high_8(), 0xFF);
assert_eq!(
unsafe { d.acceleration_structure_reference.device_handle },
0xDEAD_BEEF
);
}
#[test]
fn align_up_rounds_to_power_of_two() {
assert_eq!(align_up(0, 256), 0);
assert_eq!(align_up(1, 256), 256);
assert_eq!(align_up(256, 256), 256);
assert_eq!(align_up(257, 256), 512);
assert_eq!(align_up(123, 1), 123);
}
#[test]
fn rt_skin_kernel_compiles() {
if !crate::slangc_gate::slangc_available() {
return;
}
let spv = crate::vulkan::slang_builtins::RT_SKIN
.compile(&crate::vulkan::builtins::Ctx::plain(false))
.expect("rt skin kernel compiles");
assert!(super::super::pipeline::is_spirv(&spv));
}
#[test]
fn vertex_stride_matches_the_deformed_payload() {
assert_eq!(
size_of::<crate::gfx::mesh_payload::Vertex>() as u64,
VERTEX_STRIDE
);
}
}