use ash::vk;
use concinnity_core::gfx::render_types::{DrawObject, InstancedCluster, SkinnedDrawObject};
use concinnity_core::render::error::{RenderError, RenderResult};
use concinnity_core::render::fullscreen::align_up;
use concinnity_core::render::retire_pool::RetirePool;
use concinnity_core::render::rt_accel::{
AccelBook, EmptyHead, FrameRing, HeadRefresh, InstanceBlas, RefreshMode, RtStep, RtUpdate,
ScratchRing, SeedSet, StaticRing, empty_head,
};
use concinnity_core::render::rt_geom::{RtDynamicMode, pack_row_major_3x4};
use concinnity_core::render::rt_refit::{BlasUpdate, SkinnedRefit};
use concinnity_core::render::rt_topology::blas_vertex_count;
use concinnity_core::render::uniforms::SkinParams;
use super::allocator::{DeviceAllocator, PooledBuffer};
use super::context::VkGeometry;
use super::descriptor_layout::{Binding, PoolSizes};
use super::pipeline_desc::compute_pipeline;
use super::record::cmd_push_constants;
use super::resources::create_descriptor_set_layout;
use super::set_writes::SetWrites;
use crate::vulkan::builtin_shaders::CompileProgram;
use crate::vulkan::owned::{
OwnedDescriptorPool, OwnedPipeline, OwnedPipelineLayout, OwnedSetLayout, VkDevice,
};
const VERTEX_STRIDE: u64 = 56;
fn tlas_instance(
model: [[f32; 4]; 4],
custom_index: u32,
blas_address: u64,
) -> vk::AccelerationStructureInstanceKHR {
vk::AccelerationStructureInstanceKHR {
transform: vk::TransformMatrixKHR {
matrix: pack_row_major_3x4(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 instance_blas_address(
blas: InstanceBlas<'_, AccelBuffer>,
fresh: &[u64],
skinned: &[u64],
) -> u64 {
match blas {
InstanceBlas::Head { blas, .. } => blas.address,
InstanceBlas::Fresh { index } => fresh.get(index).copied().unwrap_or(0),
InstanceBlas::Skinned { n } => skinned.get(n).copied().unwrap_or(0),
}
}
fn scratch_capacity(required: u64, align: u64) -> u64 {
required + align
}
struct ScratchSlot {
_pooled: PooledBuffer,
addr: u64,
}
fn alloc_scratch(
alloc: &DeviceAllocator,
device: &VkDevice,
capacity: u64,
align: u64,
) -> RenderResult<ScratchSlot> {
let pooled = alloc.create_buffer(
capacity,
vk::BufferUsageFlags::STORAGE_BUFFER | vk::BufferUsageFlags::SHADER_DEVICE_ADDRESS,
vk::MemoryPropertyFlags::DEVICE_LOCAL,
)?;
let addr = align_up(buffer_address(device, pooled.buffer()), align);
Ok(ScratchSlot {
_pooled: pooled,
addr,
})
}
fn ensure_scratch(
ring: &mut ScratchRing<ScratchSlot>,
ctx: RtDeviceCtx,
frame_idx: usize,
required: u64,
) -> RenderResult<u64> {
let align = scratch_alignment(ctx.instance, ctx.pd);
ring.ensure(frame_idx, scratch_capacity(required, align), |capacity| {
alloc_scratch(ctx.alloc, ctx.device, capacity, align)
})
.map(|slot| slot.addr)
}
struct AccelBuffer {
accel: vk::AccelerationStructureKHR,
_pooled: PooledBuffer,
size: u64,
address: 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) {}
pub(in crate::vulkan) fn forget_wired(&mut self) {
for frame in &mut self.wired {
frame.fill([vk::Buffer::null(); 3]);
}
}
}
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],
pub skin: &'a mut SkinPipeline,
}
struct SkinnedRebuild<'a> {
ctx: RtDeviceCtx<'a>,
cmd: vk::CommandBuffer,
draw_objects: &'a [DrawObject],
skinned: SkinnedRtInputs<'a>,
frame_idx: usize,
full_build: bool,
}
enum Retired {
Accel(AccelBuffer),
Device { _buffer: DeviceBuffer },
Host { _buffer: HostBuffer },
}
impl Retired {
fn destroy(&self, as_loader: &ash::khr::acceleration_structure::Device) {
if let Retired::Accel(b) = self {
b.destroy(as_loader);
}
}
}
struct RetireSink<'a> {
pool: &'a mut RetirePool<Retired>,
now: u64,
}
impl<'a> RetireSink<'a> {
fn new(pool: &'a mut RetirePool<Retired>, now: u64) -> Self {
Self { pool, now }
}
fn accel(self, resource: AccelBuffer) {
self.pool.push(self.now, Retired::Accel(resource));
}
fn device(self, resource: DeviceBuffer) {
self.pool
.push(self.now, Retired::Device { _buffer: resource });
}
fn host(self, resource: HostBuffer) {
self.pool
.push(self.now, Retired::Host { _buffer: resource });
}
}
#[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);
}
}
}
#[derive(Default)]
struct SkinnedScratch {
params: Vec<BlasParams>,
blas_addresses: Vec<u64>,
}
pub(super) struct RtAccelData {
as_loader: ash::khr::acceleration_structure::Device,
book: AccelBook<AccelBuffer, vk::AccelerationStructureInstanceKHR>,
live_tlas: vk::AccelerationStructureKHR,
live_geom: vk::Buffer,
live_geom_size: vk::DeviceSize,
scratch: ScratchRing<ScratchSlot>,
tlas_size: u64,
tlas_scratch: u64,
instance_count: u32,
frames_in_flight: u64,
retire: RetirePool<Retired>,
static_ring: StaticRing<StaticFrameRing>,
skinned_ring: FrameRing<SkinnedFrameRing>,
live_deformed: vk::Buffer,
_deformed_dummy: DeviceBuffer,
skinned_indices: vk::Buffer,
frames_in_flight_usize: usize,
skinned_scratch: SkinnedScratch,
}
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,
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct SharedGeometry {
vertex_buffer: vk::Buffer,
index_buffer: vk::Buffer,
vertex_count: u64,
}
impl SharedGeometry {
pub(in crate::vulkan) fn of(geometry: &VkGeometry) -> Self {
Self {
vertex_buffer: geometry.vertex_buffer.buffer(),
index_buffer: geometry.index_buffer.buffer(),
vertex_count: geometry.vertex_buffer_bytes / VERTEX_STRIDE,
}
}
fn addresses(&self, device: &VkDevice) -> SharedAddresses {
SharedAddresses {
vertex: buffer_address(device, self.vertex_buffer),
index: buffer_address(device, self.index_buffer),
vertex_count: self.vertex_count,
}
}
}
#[derive(Clone, Copy)]
struct SharedAddresses {
vertex: u64,
index: u64,
vertex_count: u64,
}
impl SharedAddresses {
fn draw_params(&self, obj: &DrawObject) -> BlasParams {
let base_vertex = u64::try_from(obj.base_vertex).unwrap_or(0);
BlasParams {
vertex_address: self.vertex + base_vertex * VERTEX_STRIDE,
max_vertex: blas_vertex_count(obj.base_vertex, self.vertex_count).saturating_sub(1),
index_byte_offset: obj.index_offset as u32 * 4,
primitive_count: (obj.index_count / 3) as u32,
}
}
fn cluster_params(&self, cluster: &InstancedCluster) -> BlasParams {
BlasParams {
vertex_address: self.vertex,
max_vertex: blas_vertex_count(0, self.vertex_count).saturating_sub(1),
index_byte_offset: cluster.index_offset as u32 * 4,
primitive_count: (cluster.index_count / 3) as 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 build_info<'a>(
ty: vk::AccelerationStructureTypeKHR,
geo: &'a vk::AccelerationStructureGeometryKHR<'a>,
) -> vk::AccelerationStructureBuildGeometryInfoKHR<'a> {
vk::AccelerationStructureBuildGeometryInfoKHR::default()
.ty(ty)
.flags(vk::BuildAccelerationStructureFlagsKHR::PREFER_FAST_TRACE)
.mode(vk::BuildAccelerationStructureModeKHR::BUILD)
.geometries(std::slice::from_ref(geo))
}
fn build_sizes(
as_loader: &ash::khr::acceleration_structure::Device,
info: &vk::AccelerationStructureBuildGeometryInfoKHR<'_>,
primitive_count: u32,
) -> vk::AccelerationStructureBuildSizesInfoKHR<'static> {
let mut sizes = vk::AccelerationStructureBuildSizesInfoKHR::default();
unsafe {
as_loader.get_acceleration_structure_build_sizes(
vk::AccelerationStructureBuildTypeKHR::DEVICE,
info,
&[primitive_count],
&mut sizes,
);
}
sizes
}
fn blas_build_sizes(
as_loader: &ash::khr::acceleration_structure::Device,
geo: &vk::AccelerationStructureGeometryKHR<'_>,
primitive_count: u32,
) -> vk::AccelerationStructureBuildSizesInfoKHR<'static> {
let info = build_info(vk::AccelerationStructureTypeKHR::BOTTOM_LEVEL, geo);
build_sizes(as_loader, &info, primitive_count)
}
fn tlas_build_sizes(
as_loader: &ash::khr::acceleration_structure::Device,
geo: &vk::AccelerationStructureGeometryKHR<'_>,
instance_count: u32,
) -> vk::AccelerationStructureBuildSizesInfoKHR<'static> {
let info = build_info(vk::AccelerationStructureTypeKHR::TOP_LEVEL, geo);
build_sizes(as_loader, &info, instance_count)
}
fn build_range(
primitive_count: u32,
primitive_offset: u32,
) -> vk::AccelerationStructureBuildRangeInfoKHR {
vk::AccelerationStructureBuildRangeInfoKHR::default()
.primitive_count(primitive_count)
.primitive_offset(primitive_offset)
.first_vertex(0)
.transform_offset(0)
}
fn record_build(
as_loader: &ash::khr::acceleration_structure::Device,
cmd: vk::CommandBuffer,
info: vk::AccelerationStructureBuildGeometryInfoKHR<'_>,
range: vk::AccelerationStructureBuildRangeInfoKHR,
) {
unsafe {
as_loader.cmd_build_acceleration_structures(
cmd,
std::slice::from_ref(&info),
&[std::slice::from_ref(&range)],
);
}
}
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,
) -> RenderResult<AccelBuffer> {
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| super::error::map_vk_result(e, "create acceleration structure"))?;
let address = unsafe {
as_loader.get_acceleration_structure_device_address(
&vk::AccelerationStructureDeviceAddressInfoKHR::default().acceleration_structure(accel),
)
};
Ok(AccelBuffer {
accel,
_pooled: pooled,
size,
address,
})
}
fn host_buffer_size<T>(data: &[T]) -> vk::DeviceSize {
(std::mem::size_of_val(data).max(std::mem::size_of::<T>()) as vk::DeviceSize).max(16)
}
fn create_host_buffer<T: Copy>(
alloc: &DeviceAllocator,
data: &[T],
usage: vk::BufferUsageFlags,
_label: &str,
) -> RenderResult<HostBuffer> {
let size = host_buffer_size(data);
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,
) -> RenderResult<()> {
let needed = host_buffer_size(data);
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 host_buffer(slot: &Option<HostBuffer>) -> RenderResult<vk::Buffer> {
slot.as_ref()
.map(|b| b.buffer)
.ok_or_else(missing_slot_buffer)
}
fn live_accel(slot: &Option<AccelBuffer>) -> RenderResult<vk::AccelerationStructureKHR> {
slot.as_ref()
.map(|b| b.accel)
.ok_or_else(missing_slot_buffer)
}
fn missing_slot_buffer() -> RenderError {
RenderError::Other("RT ring slot is missing a buffer it was just sized for".into())
}
fn ensure_accel(
slot: &mut Option<AccelBuffer>,
alloc: &DeviceAllocator,
as_loader: &ash::khr::acceleration_structure::Device,
size: u64,
ty: vk::AccelerationStructureTypeKHR,
retire: RetireSink,
) -> RenderResult<bool> {
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,
) -> RenderResult<bool> {
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,
) -> RenderResult<DeviceBuffer> {
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(in crate::vulkan) fn build_rt_skin(
alloc: &DeviceAllocator,
device: &VkDevice,
hot_reload: bool,
) -> Option<SkinPipeline> {
build_skin_pipeline(alloc, device, hot_reload)
.inspect_err(|e| {
tracing::warn!(
"RT skin pipeline build failed (skinned meshes absent from reflections): {e}"
)
})
.ok()
}
fn skin_set() -> [Binding; 5] {
std::array::from_fn(|b| {
(
b as u32,
vk::DescriptorType::STORAGE_BUFFER,
vk::ShaderStageFlags::COMPUTE,
)
})
}
pub(super) fn build_skin_pipeline(
alloc: &DeviceAllocator,
device: &VkDevice,
hot_reload: bool,
) -> RenderResult<SkinPipeline> {
let spv = super::builtin_shaders::RT_SKIN.compile(hot_reload)?;
let set_layout = create_descriptor_set_layout(device, &skin_set())?;
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| super::error::map_vk_result(e, "rt skin pipeline layout"))?;
let pipeline = compute_pipeline(device, pipeline_layout.handle(), &spv, "rt skin")?;
let morph_dummy_pooled = alloc.create_buffer(
28,
vk::BufferUsageFlags::STORAGE_BUFFER,
vk::MemoryPropertyFlags::DEVICE_LOCAL,
)?;
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, dst_stages: vk::PipelineStageFlags) {
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,
dst_stages,
vk::DependencyFlags::empty(),
std::slice::from_ref(&barrier),
&[],
&[],
);
}
}
const BUILD_TO_BUILD: vk::PipelineStageFlags =
vk::PipelineStageFlags::ACCELERATION_STRUCTURE_BUILD_KHR;
const BUILD_TO_TRACE: vk::PipelineStageFlags = vk::PipelineStageFlags::from_raw(
vk::PipelineStageFlags::ACCELERATION_STRUCTURE_BUILD_KHR.as_raw()
| vk::PipelineStageFlags::FRAGMENT_SHADER.as_raw(),
);
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,
}
pub(in crate::vulkan) struct RtSceneGeometry<'a> {
pub(in crate::vulkan) shared: SharedGeometry,
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) exclude_seethrough: bool,
pub(in crate::vulkan) skinned_present: bool,
}
pub(super) fn build_rt_accel(
ctx: RtDeviceCtx,
command_pool: vk::CommandPool,
queue: vk::Queue,
geometry: RtSceneGeometry,
frames_in_flight: usize,
) -> RenderResult<Option<RtAccelData>> {
let RtDeviceCtx {
alloc,
instance,
device,
pd,
} = ctx;
let RtSceneGeometry {
shared,
draw_objects,
clusters,
albedo_count,
exclude_seethrough,
skinned_present,
} = geometry;
let as_loader = ash::khr::acceleration_structure::Device::new(instance, device);
let seed = SeedSet::new(draw_objects, clusters, exclude_seethrough);
if seed.builds_nothing(skinned_present) {
return Ok(None);
}
let shared = shared.addresses(device);
let ibuf_addr = shared.index;
let params: Vec<BlasParams> = seed
.objects
.iter()
.map(|&i| shared.draw_params(&draw_objects[i]))
.chain(seed.clusters.iter().map(|c| shared.cluster_params(c)))
.collect();
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 sizes = blas_build_sizes(&as_loader, &geo, p.primitive_count);
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 mut book = AccelBook::new(&seed, blas, draw_objects, albedo_count as u32)?;
book.fill_instances(draw_objects, None, |model, id, blas| {
tlas_instance(model, id, instance_blas_address(blas, &[], &[]))
});
let instance_count = book.instances().len() as u32;
let instance_buffer = create_host_buffer(
alloc,
book.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,
book.geom_table(),
vk::BufferUsageFlags::STORAGE_BUFFER,
"RT geometry table",
)?;
let tlas_geo = tlas_geometry(buffer_address(device, instance_buffer.buffer));
let tlas_sizes = tlas_build_sizes(&as_loader, &tlas_geo, instance_count);
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 = ScratchRing::filled(
frames_in_flight,
scratch_capacity(max_scratch, align),
|capacity| alloc_scratch(alloc, device, capacity, align),
)?;
let scratch_addr = scratch.get(0).map_or(0, |slot| slot.addr);
super::texture::one_shot_submit(device, command_pool, queue, |cmd| {
for (dst, p) in book.head().iter().zip(¶ms) {
let geo = blas_geometry(p, ibuf_addr);
record_build(
&as_loader,
cmd,
build_info(vk::AccelerationStructureTypeKHR::BOTTOM_LEVEL, &geo)
.dst_acceleration_structure(dst.accel)
.scratch_data(vk::DeviceOrHostAddressKHR {
device_address: scratch_addr,
}),
build_range(p.primitive_count, p.index_byte_offset),
);
build_barrier(device, cmd, BUILD_TO_BUILD);
}
let tlas_geo = tlas_geometry(buffer_address(device, instance_buffer.buffer));
record_build(
&as_loader,
cmd,
build_info(vk::AccelerationStructureTypeKHR::TOP_LEVEL, &tlas_geo)
.dst_acceleration_structure(tlas.accel)
.scratch_data(vk::DeviceOrHostAddressKHR {
device_address: scratch_addr,
}),
build_range(instance_count, 0),
);
})?;
let deformed_dummy = create_device_buffer(alloc, device, VERTEX_STRIDE)?;
let live_tlas = tlas.accel;
let live_geom = geom_table.buffer;
let live_geom_size = geom_table.size;
let static_ring = StaticRing::new(
frames_in_flight,
StaticFrameRing {
tlas: Some(tlas),
instance: Some(instance_buffer),
geom: Some(geom_table),
},
);
Ok(Some(RtAccelData {
as_loader,
book,
live_tlas,
live_geom,
live_geom_size,
scratch,
tlas_size: tlas_sizes.acceleration_structure_size,
tlas_scratch: tlas_sizes.build_scratch_size,
instance_count,
frames_in_flight: (frames_in_flight.max(1)) as u64,
retire: RetirePool::new(),
static_ring,
skinned_ring: FrameRing::new(frames_in_flight),
live_deformed: deformed_dummy.buffer,
_deformed_dummy: deformed_dummy,
skinned_indices: vk::Buffer::null(),
frames_in_flight_usize: frames_in_flight.max(1),
skinned_scratch: SkinnedScratch::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 shared: SharedGeometry,
pub skinned: Option<SkinnedRtInputs<'a>>,
}
#[derive(Clone, Copy)]
struct TopologyRefresh {
shared: SharedGeometry,
exclude_seethrough: bool,
mode: RefreshMode,
frame_idx: usize,
}
#[derive(Clone, Copy)]
struct StaticBuilt {
tlas: vk::AccelerationStructureKHR,
geom: vk::Buffer,
geom_size: vk::DeviceSize,
instance_count: u32,
}
impl StaticBuilt {
fn of(slot: &StaticFrameRing, instance_count: u32) -> RenderResult<Self> {
let geom = slot.geom.as_ref().ok_or_else(missing_slot_buffer)?;
Ok(Self {
tlas: live_accel(&slot.tlas)?,
geom: geom.buffer,
geom_size: geom.size,
instance_count,
})
}
}
struct PendingRefresh {
refresh: HeadRefresh,
fresh: Vec<Option<AccelBuffer>>,
}
impl RtAccelData {
pub(super) fn dynamic_update(
&mut self,
ctx: RtDeviceCtx,
cmd: vk::CommandBuffer,
draw_objects: &[DrawObject],
inputs: RtDynamicInputs,
) -> RenderResult<RtUpdate> {
let RtDynamicInputs {
policy:
RtRebuildPolicy {
mode,
topology_dirty,
exclude_seethrough,
},
frame_idx,
shared,
skinned,
} = inputs;
let now = self.book.tick();
while let Some(r) = self.retire.pop_due(now, self.frames_in_flight) {
r.destroy(&self.as_loader);
}
let Some(plan) = self
.book
.plan(mode, topology_dirty, skinned.as_ref().map(|s| s.objects))
else {
return Ok(RtUpdate::Done);
};
let mut refreshed = Ok(());
if let Some(mode) = plan.refresh {
let req = TopologyRefresh {
shared,
exclude_seethrough,
mode,
frame_idx,
};
let if_empty = empty_head(plan.skinned, skinned.is_some());
refreshed = self.refresh_topology(ctx, cmd, draw_objects, req, if_empty);
if refreshed.is_err() {
self.book.owe_refresh();
}
}
let stepped = match self.book.next_step(mode, &plan, draw_objects) {
RtStep::Keep => RtUpdate::Done,
RtStep::Tlas if self.book.is_empty() && skinned.is_none() => {
self.book.release_skinned();
RtUpdate::Done
}
RtStep::Tlas => {
self.rebuild_tlas(ctx, cmd, draw_objects, frame_idx)?;
RtUpdate::Done
}
RtStep::Skinned => match skinned {
Some(skinned) => self.rebuild_skinned(SkinnedRebuild {
ctx,
cmd,
draw_objects,
skinned,
frame_idx,
full_build: plan.full_skinned_build,
})?,
None => RtUpdate::Done,
},
};
refreshed.map(|()| stepped)
}
pub(super) fn is_spent(&self, skinned_present: bool) -> bool {
self.book.is_spent(skinned_present)
}
fn refresh_topology(
&mut self,
ctx: RtDeviceCtx,
cmd: vk::CommandBuffer,
draw_objects: &[DrawObject],
req: TopologyRefresh,
if_empty: EmptyHead,
) -> RenderResult<()> {
let refresh = self
.book
.plan_refresh(draw_objects, req.exclude_seethrough, req.mode);
if if_empty != EmptyHead::Build && self.book.refresh_leaves_nothing(&refresh) {
let orphans = self.book.commit_refresh(refresh, Vec::new(), draw_objects);
self.book.park(orphans);
if if_empty == EmptyHead::Drop {
self.book.release_skinned();
}
return Ok(());
}
let (next, mut slot) = self.static_ring.take_next();
let mut pending = PendingRefresh {
fresh: (0..refresh.indices().len()).map(|_| None).collect(),
refresh,
};
let recorded = self.record_refresh(ctx, cmd, draw_objects, &mut pending, req, &mut slot);
match recorded {
Ok((tlas_sizes, built)) => {
self.commit_refresh(pending, draw_objects, built, tlas_sizes);
self.static_ring.publish(next, slot);
Ok(())
}
Err(e) => {
for b in pending.fresh.into_iter().flatten() {
b.destroy(&self.as_loader);
}
self.static_ring.put(next, slot);
Err(e)
}
}
}
fn record_refresh(
&mut self,
ctx: RtDeviceCtx,
cmd: vk::CommandBuffer,
draw_objects: &[DrawObject],
pending: &mut PendingRefresh,
req: TopologyRefresh,
slot: &mut StaticFrameRing,
) -> RenderResult<(
vk::AccelerationStructureBuildSizesInfoKHR<'static>,
StaticBuilt,
)> {
let RtDeviceCtx { alloc, device, .. } = ctx;
let PendingRefresh { refresh, fresh } = pending;
let shared = req.shared.addresses(device);
let now = self.book.clock();
let mut fresh_params: Vec<(BlasParams, usize)> = Vec::new();
let mut max_scratch: u64 = 0;
for (j, idx) in refresh.fresh_slots() {
let p = shared.draw_params(&draw_objects[idx]);
let geo = blas_geometry(&p, shared.index);
let sizes = blas_build_sizes(&self.as_loader, &geo, p.primitive_count);
fresh[j] = Some(create_accel(
alloc,
&self.as_loader,
sizes.acceleration_structure_size,
vk::AccelerationStructureTypeKHR::BOTTOM_LEVEL,
)?);
max_scratch = max_scratch.max(sizes.build_scratch_size);
fresh_params.push((p, j));
}
let fresh_addresses: Vec<u64> = fresh
.iter()
.map(|b| b.as_ref().map_or(0, |b| b.address))
.collect();
self.book
.fill_refresh_instances(refresh, draw_objects, |model, id, blas| {
tlas_instance(
model,
id,
instance_blas_address(blas, &fresh_addresses, &[]),
)
});
let instance_count = self.book.instances().len() as u32;
write_or_recreate_host(
&mut slot.instance,
alloc,
self.book.instances(),
vk::BufferUsageFlags::SHADER_DEVICE_ADDRESS
| vk::BufferUsageFlags::ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_KHR,
"RT instance buffer",
RetireSink::new(&mut self.retire, now),
)?;
write_or_recreate_host(
&mut slot.geom,
alloc,
self.book.geom_table(),
vk::BufferUsageFlags::STORAGE_BUFFER,
"RT geometry table",
RetireSink::new(&mut self.retire, now),
)?;
let instance_buffer = host_buffer(&slot.instance)?;
let tlas_geo = tlas_geometry(buffer_address(device, instance_buffer));
let tlas_sizes = tlas_build_sizes(&self.as_loader, &tlas_geo, instance_count);
max_scratch = max_scratch.max(tlas_sizes.build_scratch_size);
let scratch_addr = ensure_scratch(&mut self.scratch, ctx, req.frame_idx, max_scratch)?;
ensure_accel(
&mut slot.tlas,
alloc,
&self.as_loader,
tlas_sizes.acceleration_structure_size,
vk::AccelerationStructureTypeKHR::TOP_LEVEL,
RetireSink::new(&mut self.retire, now),
)?;
let built = StaticBuilt::of(slot, instance_count)?;
let tlas = built.tlas;
self.book.check_refresh(refresh, fresh)?;
let scratch = vk::DeviceOrHostAddressKHR {
device_address: scratch_addr,
};
for (p, j) in &fresh_params {
let Some(dst) = fresh[*j].as_ref() else {
continue;
};
let geo = blas_geometry(p, shared.index);
record_build(
&self.as_loader,
cmd,
build_info(vk::AccelerationStructureTypeKHR::BOTTOM_LEVEL, &geo)
.dst_acceleration_structure(dst.accel)
.scratch_data(scratch),
build_range(p.primitive_count, p.index_byte_offset),
);
build_barrier(device, cmd, BUILD_TO_BUILD);
}
record_build(
&self.as_loader,
cmd,
build_info(vk::AccelerationStructureTypeKHR::TOP_LEVEL, &tlas_geo)
.dst_acceleration_structure(tlas)
.scratch_data(scratch),
build_range(instance_count, 0),
);
build_barrier(device, cmd, BUILD_TO_TRACE);
Ok((tlas_sizes, built))
}
fn commit_refresh(
&mut self,
pending: PendingRefresh,
draw_objects: &[DrawObject],
built: StaticBuilt,
tlas_sizes: vk::AccelerationStructureBuildSizesInfoKHR<'static>,
) {
self.publish_static(built);
let PendingRefresh { refresh, fresh } = pending;
let orphans = self.book.commit_refresh(refresh, fresh, draw_objects);
self.retire_orphans(orphans);
self.tlas_size = tlas_sizes.acceleration_structure_size;
self.tlas_scratch = tlas_sizes.build_scratch_size;
self.book.release_skinned();
self.skinned_ring.unpublish(self.book.clock());
for ring in self.skinned_ring.slots_mut() {
ring.refit.reset();
}
}
fn retire_orphans(&mut self, orphans: Vec<AccelBuffer>) {
let now = self.book.clock();
for orphan in orphans.into_iter().chain(self.book.take_parked()) {
self.retire.push(now, Retired::Accel(orphan));
}
}
fn publish_static(&mut self, built: StaticBuilt) {
self.live_tlas = built.tlas;
self.live_geom = built.geom;
self.live_geom_size = built.geom_size;
self.instance_count = built.instance_count;
}
fn rebuild_tlas(
&mut self,
ctx: RtDeviceCtx,
cmd: vk::CommandBuffer,
draw_objects: &[DrawObject],
frame_idx: usize,
) -> RenderResult<()> {
let (next, mut slot) = self.static_ring.take_next();
let result = self.rebuild_tlas_into(ctx, cmd, draw_objects, frame_idx, &mut slot);
if result.is_ok() {
self.static_ring.publish(next, slot);
} else {
self.static_ring.put(next, slot);
}
result
}
fn rebuild_tlas_into(
&mut self,
ctx: RtDeviceCtx,
cmd: vk::CommandBuffer,
draw_objects: &[DrawObject],
frame_idx: usize,
slot: &mut StaticFrameRing,
) -> RenderResult<()> {
let RtDeviceCtx { alloc, device, .. } = ctx;
let now = self.book.clock();
self.book
.fill_instances(draw_objects, None, |model, id, blas| {
tlas_instance(model, id, instance_blas_address(blas, &[], &[]))
});
let instance_count = self.book.instances().len() as u32;
write_or_recreate_host(
&mut slot.instance,
alloc,
self.book.instances(),
vk::BufferUsageFlags::SHADER_DEVICE_ADDRESS
| vk::BufferUsageFlags::ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_KHR,
"RT instance buffer",
RetireSink::new(&mut self.retire, now),
)?;
write_or_recreate_host(
&mut slot.geom,
alloc,
self.book.geom_table(),
vk::BufferUsageFlags::STORAGE_BUFFER,
"RT geometry table",
RetireSink::new(&mut self.retire, now),
)?;
ensure_accel(
&mut slot.tlas,
alloc,
&self.as_loader,
self.tlas_size,
vk::AccelerationStructureTypeKHR::TOP_LEVEL,
RetireSink::new(&mut self.retire, now),
)?;
let instance_buffer = host_buffer(&slot.instance)?;
let built = StaticBuilt::of(slot, instance_count)?;
let scratch_addr = ensure_scratch(&mut self.scratch, ctx, frame_idx, self.tlas_scratch)?;
let tlas_geo = tlas_geometry(buffer_address(device, instance_buffer));
record_build(
&self.as_loader,
cmd,
build_info(vk::AccelerationStructureTypeKHR::TOP_LEVEL, &tlas_geo)
.dst_acceleration_structure(built.tlas)
.scratch_data(vk::DeviceOrHostAddressKHR {
device_address: scratch_addr,
}),
build_range(instance_count, 0),
);
build_barrier(device, cmd, BUILD_TO_TRACE);
self.publish_static(built);
if self.book.commit_static().is_some() {
self.skinned_ring.unpublish(self.book.clock());
for ring in self.skinned_ring.slots_mut() {
ring.refit.reset();
}
}
self.retire_orphans(Vec::new());
Ok(())
}
fn rebuild_skinned(&mut self, req: SkinnedRebuild) -> RenderResult<RtUpdate> {
let frame_idx = req.frame_idx;
let now = self.book.clock();
let Some(mut slot) = self.skinned_ring.take(frame_idx, now)? else {
return Ok(RtUpdate::Skipped);
};
let result = self.rebuild_skinned_into(req, &mut slot);
if result.is_ok() {
self.skinned_ring.publish(frame_idx, slot, now);
self.retire_orphans(Vec::new());
} else {
slot.refit.reset();
self.skinned_ring.put(frame_idx, slot);
}
result.map(|()| RtUpdate::Done)
}
fn rebuild_skinned_into(
&mut self,
req: SkinnedRebuild,
slot: &mut SkinnedFrameRing,
) -> RenderResult<()> {
let SkinnedRebuild {
ctx,
cmd,
draw_objects,
skinned,
frame_idx,
full_build,
} = req;
let skin = &mut *skinned.skin;
let RtDeviceCtx { alloc, device, .. } = ctx;
let now = self.book.clock();
let frames = self.frames_in_flight_usize;
let pipeline = skin.pipeline.handle();
let pipeline_layout = skin.pipeline_layout.handle();
let deformed_extent = self.book.skinned_vertex_extent(skinned.objects);
self.book
.fill_skinned_shapes(skinned.objects, deformed_extent as u32);
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, now),
)?;
let deformed = slot
.deformed
.as_ref()
.ok_or_else(missing_slot_buffer)?
.handle();
ensure_skin_sets(device, skin, frames, skinned.objects.len())?;
let visible = self.book.visible_skinned();
let frame_sets = &skin.sets[frame_idx];
let frame_wired = &mut skin.wired[frame_idx];
for &obj_idx in visible {
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;
SetWrites::new(frame_sets[obj_idx])
.storage_buffer(0, skinned.vertex_buffer, vk::WHOLE_SIZE)
.storage_buffer(1, joint_buffer, vk::WHOLE_SIZE)
.storage_buffer(2, deformed.buffer, vk::WHOLE_SIZE)
.storage_buffer(3, skin.morph_dummy, vk::WHOLE_SIZE)
.storage_buffer(4, skin.morph_dummy, vk::WHOLE_SIZE)
.apply(device);
}
unsafe {
device.cmd_bind_pipeline(cmd, vk::PipelineBindPoint::COMPUTE, pipeline);
}
for &obj_idx in visible {
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,
};
unsafe {
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::COMPUTE,
pipeline_layout,
0,
std::slice::from_ref(&frame_sets[obj_idx]),
&[],
);
cmd_push_constants(
device,
cmd,
pipeline_layout,
vk::ShaderStageFlags::COMPUTE,
¶ms,
);
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;
let SkinnedScratch {
params: skinned_params,
blas_addresses: skinned_blas_addresses,
} = &mut self.skinned_scratch;
skinned_params.clear();
skinned_params.extend(visible.iter().map(|&i| {
let obj = &skinned.objects[i];
BlasParams {
vertex_address: deformed.address,
max_vertex,
index_byte_offset: obj.index_offset as u32 * 4,
primitive_count: (obj.index_count / 3) 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 info = skinned_blas_build_info(&geo, BlasUpdate::Build);
let sizes = build_sizes(&self.as_loader, &info, p.primitive_count);
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
.iter()
.take(skinned_params.len())
.map(|b| b.address),
);
self.book
.fill_instances(draw_objects, Some(skinned.objects), |model, id, blas| {
tlas_instance(
model,
id,
instance_blas_address(blas, &[], skinned_blas_addresses),
)
});
let instance_count = self.book.instances().len() as u32;
write_or_recreate_host(
&mut slot.instance,
alloc,
self.book.instances(),
vk::BufferUsageFlags::SHADER_DEVICE_ADDRESS
| vk::BufferUsageFlags::ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_KHR,
"RT instance buffer",
RetireSink::new(&mut self.retire, now),
)?;
write_or_recreate_host(
&mut slot.geom,
alloc,
self.book.geom_table(),
vk::BufferUsageFlags::STORAGE_BUFFER,
"RT geometry table",
RetireSink::new(&mut self.retire, now),
)?;
let instance_buffer = host_buffer(&slot.instance)?;
let tlas_geo = tlas_geometry(buffer_address(device, instance_buffer));
let tlas_sizes = tlas_build_sizes(&self.as_loader, &tlas_geo, instance_count);
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, now),
)?;
let tlas = live_accel(&slot.tlas)?;
let scratch_addr = ensure_scratch(&mut self.scratch, ctx, frame_idx, max_scratch)?;
let update = slot
.refit
.plan(self.book.skinned_shapes(), storage_changed || full_build);
let scratch = vk::DeviceOrHostAddressKHR {
device_address: scratch_addr,
};
for (p, dst) in self.skinned_scratch.params.iter().zip(&slot.blas) {
let geo = skinned_blas_geometry(p, skinned_idx_addr);
let mut bi = skinned_blas_build_info(&geo, update)
.dst_acceleration_structure(dst.accel)
.scratch_data(scratch);
if update == BlasUpdate::Refit {
bi.src_acceleration_structure = dst.accel;
}
record_build(
&self.as_loader,
cmd,
bi,
build_range(p.primitive_count, p.index_byte_offset),
);
build_barrier(device, cmd, BUILD_TO_BUILD);
}
record_build(
&self.as_loader,
cmd,
build_info(vk::AccelerationStructureTypeKHR::TOP_LEVEL, &tlas_geo)
.dst_acceleration_structure(tlas)
.scratch_data(scratch),
build_range(instance_count, 0),
);
build_barrier(device, cmd, BUILD_TO_TRACE);
for leftover in slot.blas.drain(self.skinned_scratch.params.len()..) {
leftover.destroy(&self.as_loader);
}
let geom = slot.geom.as_ref().ok_or_else(missing_slot_buffer)?;
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.book.commit_skinned();
Ok(())
}
pub(super) fn destroy(&mut self) {
for r in self.retire.drain() {
r.destroy(&self.as_loader);
}
for slot in self.skinned_ring.slots_mut() {
slot.destroy(&self.as_loader);
}
for slot in self.static_ring.slots_mut() {
slot.destroy(&self.as_loader);
}
for b in self.book.drain_blas() {
b.destroy(&self.as_loader);
}
}
}
pub(super) fn ensure_skin_sets(
device: &VkDevice,
skin: &mut SkinPipeline,
frames: usize,
object_count: usize,
) -> RenderResult<()> {
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_sizes = PoolSizes::default().sets(&skin_set(), total).build();
let pool = device
.create_descriptor_pool(
&vk::DescriptorPoolCreateInfo::default()
.pool_sizes(&pool_sizes)
.max_sets(total),
)
.map_err(|e| super::error::map_vk_result(e, "skin descriptor pool"))?;
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| super::error::map_vk_result(e, "alloc skin descriptor sets"))?;
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,
) -> RenderResult<DeviceBuffer> {
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::VkRayTracing {
pub(in crate::vulkan) fn collect_retired(&mut self, frames_in_flight: usize) {
self.retire_tick += 1;
let depth = frames_in_flight as u64 + 1;
while let Some(mut accel) = self.retired.pop_due(self.retire_tick, depth) {
accel.destroy();
}
}
pub(in crate::vulkan) fn retire_accel(&mut self) {
if let Some(accel) = self.accel.take() {
self.retired.push(self.retire_tick, accel);
}
}
pub(in crate::vulkan) fn destroy_accels(&mut self) {
if let Some(mut accel) = self.accel.take() {
accel.destroy();
}
for mut accel in self.retired.drain() {
accel.destroy();
}
}
}
impl super::context::VkContext {
pub(in crate::vulkan) fn build_scene_accel(
&self,
skinned_present: bool,
) -> RenderResult<Option<RtAccelData>> {
build_rt_accel(
RtDeviceCtx {
alloc: &self.hw.alloc,
instance: &self.hw.instance,
device: &self.hw.device,
pd: self.hw.physical_device,
},
self.commands.command_pool,
self.hw.graphics_queue,
RtSceneGeometry {
shared: SharedGeometry::of(&self.geometry),
draw_objects: &self.state.draw.objects,
clusters: &self.instanced.clusters,
albedo_count: self.scene.textures.len(),
exclude_seethrough: self.seethrough_meshes_enabled(),
skinned_present,
},
self.frames_in_flight,
)
}
pub(in crate::vulkan) fn build_scene_accel_or_warn(&self) -> Option<RtAccelData> {
self.build_scene_accel(self.rt_skinned_present())
.unwrap_or_else(|e| {
tracing::warn!("RT acceleration-structure build failed: {e}");
None
})
}
pub(in crate::vulkan) fn rt_skinned_buffers(&self) -> Option<(vk::Buffer, vk::Buffer)> {
let present = self.rt.skinned_geometry
&& self.rt.skin.is_some()
&& !self.state.skinned.draw_objects.is_empty()
&& !self.skinned.vertex_buffer.is_null()
&& !self.skinned.index_buffer.is_null();
present.then(|| {
(
self.skinned.vertex_buffer.buffer(),
self.skinned.index_buffer.buffer(),
)
})
}
pub(in crate::vulkan) fn rt_skinned_present(&self) -> bool {
self.rt_skinned_buffers().is_some()
}
pub(in crate::vulkan) fn forget_wired_accel(&mut self) {
if let Some(rt) = self.rt_reflections.as_mut() {
rt.forget_accel();
}
if let Some(transparent) = self.transparent.as_mut() {
transparent.forget_rt_dynamic();
}
if let Some(skin) = self.rt.skin.as_mut() {
skin.forget_wired();
}
}
pub(in crate::vulkan) fn rebuild_rt_accel(&mut self) {
self.rt.destroy_accels();
self.rt.accel = self.build_scene_accel_or_warn();
self.forget_wired_accel();
self.rewire_shared_geometry_readers();
}
pub(in crate::vulkan) fn rewire_shared_geometry_readers(&self) {
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(&self.hw.device, vertex_buffer, index_buffer);
}
if let Some(transparent) = self.transparent.as_ref() {
transparent.wire_rt_geometry(&self.hw.device, vertex_buffer, index_buffer);
}
}
pub(in crate::vulkan) fn build_main_skin(&mut self, vertex_total: usize) -> RenderResult<()> {
let device = self.hw.device.clone();
let frames = self.frames_in_flight.max(1);
let n = self.state.skinned.draw_objects.len();
if n == 0 {
return Ok(());
}
let mut skin = build_skin_pipeline(&self.hw.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 {
SetWrites::new(skin.sets[f][o])
.storage_buffer(1, self.skinned.joint_buffers[f][o].buffer(), vk::WHOLE_SIZE)
.storage_buffer(3, skin.morph_dummy, vk::WHOLE_SIZE)
.storage_buffer(4, skin.morph_dummy, vk::WHOLE_SIZE)
.apply(&device);
}
}
self.skinned.skin = Some(skin);
self.skinned.deformed = deformed;
self.state.draw.n_skinned = n;
Ok(())
}
pub(in crate::vulkan) fn refresh_main_skin_geometry(
&mut self,
vertex_total: usize,
) -> RenderResult<()> {
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,
) -> RenderResult<Vec<DeviceBuffer>> {
let frames = self.frames_in_flight.max(1);
let n = self.state.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.hw.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) {
SetWrites::new(set)
.storage_buffer(0, src_buffer, vk::WHOLE_SIZE)
.storage_buffer(2, deformed_buf.buffer, vk::WHOLE_SIZE)
.apply(&self.hw.device);
}
}
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.state.draw.n_skinned == 0 || self.skinned.deformed.len() <= frame_idx {
return;
}
let device = &self.hw.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
.state
.skinned
.draw_objects
.iter()
.take(self.state.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),
};
unsafe {
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::COMPUTE,
skin.pipeline_layout.handle(),
0,
std::slice::from_ref(&frame_sets[o]),
&[],
);
cmd_push_constants(
device,
cmd,
skin.pipeline_layout.handle(),
vk::ShaderStageFlags::COMPUTE,
¶ms,
);
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::*;
use concinnity_core::gfx::mesh_payload;
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 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 an_empty_host_buffer_still_holds_one_element() {
assert_eq!(
host_buffer_size::<concinnity_core::gfx::render_types::RtGeomEntry>(&[]),
128
);
assert_eq!(host_buffer_size::<u8>(&[]), 16);
assert_eq!(host_buffer_size(&[0u64; 3]), 24);
}
#[test]
fn scratch_capacity_leaves_room_for_the_aligned_address() {
assert_eq!(scratch_capacity(1000, 256), 1256);
assert_eq!(scratch_capacity(1000, 1), 1001);
}
#[test]
fn rt_skin_kernel_compiles() {
concinnity_shader::require_dxc!();
let spv = crate::vulkan::builtin_shaders::RT_SKIN
.compile(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::<mesh_payload::Vertex>() as u64, VERTEX_STRIDE);
}
}