use ash::vk;
use concinnity_core::gfx::render_types::{CLUSTER_COUNT, CLUSTER_LIST_LEN, ClusterParams};
use concinnity_core::render::error::RenderResult;
use super::allocator::{DeviceAllocator, PooledBuffer};
use super::context::VkContext;
use super::descriptor_layout::{Binding, PoolSizes};
use super::pipeline_desc::compute_pipeline;
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,
};
use crate::vulkan::record::Recorder;
pub(in crate::vulkan) fn cluster_list_size() -> vk::DeviceSize {
CLUSTER_LIST_LEN as vk::DeviceSize * std::mem::size_of::<u32>() as vk::DeviceSize
}
const PROBE_RECORDS_BINDING: u32 = 3;
pub(in crate::vulkan) struct VkLightCull {
pub pipeline: OwnedPipeline,
pub pipeline_layout: OwnedPipelineLayout,
pub set_layout: OwnedSetLayout,
pub _descriptor_pool: OwnedDescriptorPool,
pub sets: Vec<vk::DescriptorSet>,
pub cluster_buffer: PooledBuffer,
pub params_buffers: Vec<PooledBuffer>,
pub unclustered_buffer: PooledBuffer,
}
impl VkLightCull {
pub(in crate::vulkan) fn write_probe_records(
&self,
device: &VkDevice,
frame: usize,
records: vk::DescriptorBufferInfo,
) {
SetWrites::new(self.sets[frame])
.buffer(
PROBE_RECORDS_BINDING,
vk::DescriptorType::STORAGE_BUFFER,
records.buffer,
records.offset,
records.range,
)
.apply(device);
}
pub(in crate::vulkan) fn destroy(&mut self, _device: &VkDevice) {
self.cluster_buffer = PooledBuffer::null();
self.params_buffers.clear();
self.unclustered_buffer = PooledBuffer::null();
}
}
pub(in crate::vulkan) fn light_cull_set_bindings() -> [Binding; 4] {
use vk::DescriptorType as T;
let compute = vk::ShaderStageFlags::COMPUTE;
[
(0, T::UNIFORM_BUFFER, compute),
(1, T::STORAGE_BUFFER, compute),
(2, T::STORAGE_BUFFER, compute),
(PROBE_RECORDS_BINDING, T::STORAGE_BUFFER, compute),
]
}
pub(in crate::vulkan) struct LightCullInputs {
pub params: vk::Buffer,
pub lights: vk::Buffer,
pub lists: vk::Buffer,
pub probe_records: Option<vk::Buffer>,
}
pub(in crate::vulkan) fn write_light_cull_set(
device: &VkDevice,
set: vk::DescriptorSet,
inputs: &LightCullInputs,
) {
let mut writes = SetWrites::new(set)
.uniform_buffer(0, inputs.params, vk::WHOLE_SIZE)
.storage_buffer(1, inputs.lights, vk::WHOLE_SIZE)
.storage_buffer(2, inputs.lists, cluster_list_size());
if let Some(records) = inputs.probe_records {
writes = writes.storage_buffer(PROBE_RECORDS_BINDING, records, vk::WHOLE_SIZE);
}
writes.apply(device);
}
pub(in crate::vulkan) fn build_light_cull(
alloc: &DeviceAllocator,
device: &VkDevice,
frames: usize,
local_light_buffer: vk::Buffer,
hot_reload: bool,
) -> RenderResult<VkLightCull> {
let cluster_buffer = alloc.create_buffer(
cluster_list_size(),
vk::BufferUsageFlags::STORAGE_BUFFER,
vk::MemoryPropertyFlags::DEVICE_LOCAL,
)?;
let params_size = std::mem::size_of::<ClusterParams>() as vk::DeviceSize;
let mut params_buffers = Vec::with_capacity(frames);
for _ in 0..frames {
params_buffers.push(alloc.create_buffer(
params_size,
vk::BufferUsageFlags::UNIFORM_BUFFER,
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
)?);
}
let unclustered_buffer = alloc.create_buffer(
params_size,
vk::BufferUsageFlags::UNIFORM_BUFFER,
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
)?;
unclustered_buffer.write_val(0, &ClusterParams::ZERO);
let set_layout = create_descriptor_set_layout(device, &light_cull_set_bindings())?;
let set_layouts = [set_layout.handle()];
let layout_info = vk::PipelineLayoutCreateInfo::default().set_layouts(&set_layouts);
let pipeline_layout = device
.create_pipeline_layout(&layout_info)
.map_err(|e| super::error::map_vk_result(e, "light cull pipeline layout"))?;
let spirv = super::builtin_shaders::LIGHT_CULL.compile(hot_reload)?;
let pipeline = compute_pipeline(device, pipeline_layout.handle(), &spirv, "light cull")?;
let f = frames as u32;
let sizes = PoolSizes::default()
.sets(&light_cull_set_bindings(), f)
.build();
let pool_info = vk::DescriptorPoolCreateInfo::default()
.max_sets(f)
.pool_sizes(&sizes);
let descriptor_pool = device
.create_descriptor_pool(&pool_info)
.map_err(|e| super::error::map_vk_result(e, "light cull descriptor pool"))?;
let layouts: Vec<_> = (0..frames).map(|_| set_layout.handle()).collect();
let alloc_info = vk::DescriptorSetAllocateInfo::default()
.descriptor_pool(descriptor_pool.handle())
.set_layouts(&layouts);
let sets = unsafe { device.allocate_descriptor_sets(&alloc_info) }
.map_err(|e| super::error::map_vk_result(e, "light cull descriptor sets"))?;
for (i, &set) in sets.iter().enumerate() {
write_light_cull_set(
device,
set,
&LightCullInputs {
params: params_buffers[i].buffer(),
lights: local_light_buffer,
lists: cluster_buffer.buffer(),
probe_records: None,
},
);
}
Ok(VkLightCull {
pipeline,
pipeline_layout,
set_layout,
_descriptor_pool: descriptor_pool,
sets,
cluster_buffer,
params_buffers,
unclustered_buffer,
})
}
impl VkContext {
pub(in crate::vulkan) fn write_cluster_params(&self, frame_idx: usize, params: &ClusterParams) {
self.light_cull.params_buffers[frame_idx].write_val(0, params);
}
pub(in crate::vulkan) fn encode_light_cull(&self, rec: &Recorder<'_>, frame_idx: usize) {
if let Some(&set) = self.light_cull.sets.get(frame_idx) {
self.bin_clusters(rec, set);
}
}
pub(in crate::vulkan) fn bin_clusters(&self, rec: &Recorder<'_>, set: vk::DescriptorSet) {
let layout = &self.light_cull.pipeline_layout;
rec.bind_pipeline(vk::PipelineBindPoint::COMPUTE, &self.light_cull.pipeline);
rec.bind_descriptor_sets(vk::PipelineBindPoint::COMPUTE, layout, 0, &[set], &[]);
rec.dispatch(CLUSTER_COUNT.div_ceil(64), 1, 1);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cluster_list_size_covers_every_list() {
assert_eq!(cluster_list_size(), CLUSTER_LIST_LEN as vk::DeviceSize * 4);
}
}