use ash::vk;
use concinnity_core::gfx::render_types;
use concinnity_core::render::backend_init::SceneData;
use concinnity_core::render::error::RenderResult;
use super::CullPlan;
use super::bindless::BindlessPass;
use crate::vulkan::context::{VkCullKernels, VkDescriptors, VkSceneAssets, VkTargets};
use crate::vulkan::descriptor_layout::Binding;
use crate::vulkan::init::InitGpu;
use crate::vulkan::pipeline::*;
use crate::vulkan::pipeline_desc::compute_pipeline;
use crate::vulkan::resources::{alloc_descriptor_sets, create_descriptor_set_layout};
use crate::vulkan::set_writes::SetWrites;
pub(super) struct ComputeCull {
pub(super) kernels: Option<VkCullKernels>,
pub(super) sets: Vec<vk::DescriptorSet>,
pub(super) draw_args_buffers: Vec<crate::vulkan::allocator::PooledBuffer>,
pub(super) indirect_buffers: Vec<crate::vulkan::allocator::PooledBuffer>,
pub(super) status_buffers: Vec<crate::vulkan::allocator::PooledBuffer>,
pub(super) hiz: Option<crate::vulkan::hiz::HiZResources>,
}
pub(super) struct ComputeInputs<'a> {
pub(super) world: &'a SceneData<'a>,
pub(super) scene: &'a VkSceneAssets,
pub(super) targets: &'a VkTargets,
pub(super) descriptors: &'a VkDescriptors,
pub(super) plan: &'a CullPlan,
pub(super) bindless: &'a BindlessPass,
pub(super) shader_bucket_count: usize,
pub(super) occlusion_two_pass: bool,
}
pub(super) fn cull_set_bindings<const N: usize>() -> [Binding; N] {
std::array::from_fn(|b| {
(
b as u32,
vk::DescriptorType::STORAGE_BUFFER,
vk::ShaderStageFlags::COMPUTE,
)
})
}
pub(super) fn main_cull_set() -> [Binding; 4] {
cull_set_bindings()
}
pub(super) fn build_compute_cull(
gpu: &InitGpu<'_>,
inputs: ComputeInputs<'_>,
) -> RenderResult<ComputeCull> {
let InitGpu {
hw,
command_pool,
frames,
hot_reload,
} = *gpu;
let (device, alloc, graphics_queue) = (&hw.device, &hw.alloc, hw.graphics_queue);
let ComputeInputs {
world,
scene,
targets,
descriptors,
plan,
bindless,
shader_bucket_count,
occlusion_two_pass,
} = inputs;
let CullPlan {
n_cull,
n_instances,
bindless_active,
..
} = *plan;
let object_buffers = &bindless.object_buffers;
let cull_status_buffers = if bindless_active {
let status_size = n_cull as u64 * std::mem::size_of::<u32>() as u64;
let mut bufs = Vec::with_capacity(frames);
for _ in 0..frames {
bufs.push(alloc.create_buffer(
status_size,
vk::BufferUsageFlags::STORAGE_BUFFER | vk::BufferUsageFlags::TRANSFER_SRC,
vk::MemoryPropertyFlags::DEVICE_LOCAL,
)?);
}
bufs
} else {
Vec::new()
};
type CullPipelineResources = (
Option<VkCullKernels>,
Vec<vk::DescriptorSet>,
Vec<crate::vulkan::allocator::PooledBuffer>,
Vec<crate::vulkan::allocator::PooledBuffer>,
Option<crate::vulkan::hiz::HiZResources>,
);
let (kernels, cull_sets, draw_args_buffers, indirect_buffers, hiz): CullPipelineResources =
if bindless_active {
let set_layout = create_descriptor_set_layout(device, &main_cull_set())?;
let depth_views: Vec<vk::ImageView> =
targets.depth_images.iter().map(|img| img.view).collect();
let hiz = crate::vulkan::hiz::HiZResources::new(
crate::vulkan::hiz::HiZDeviceCtx {
alloc,
device,
command_pool,
queue: graphics_queue,
},
crate::vulkan::hiz::HiZTarget {
width: targets.render_extent.width,
height: targets.render_extent.height,
depth_views: &depth_views,
},
targets.msaa_samples.as_raw(),
frames,
occlusion_two_pass,
hot_reload,
)?;
let push_range = vk::PushConstantRange::default()
.stage_flags(vk::ShaderStageFlags::COMPUTE)
.offset(0)
.size(CULL_PUSH_CONSTANT_BYTES);
let layouts = [set_layout.handle(), hiz.read_set_layout.handle()];
let pipeline_layout = device
.create_pipeline_layout(
&vk::PipelineLayoutCreateInfo::default()
.set_layouts(&layouts)
.push_constant_ranges(std::slice::from_ref(&push_range)),
)
.map_err(|e| crate::vulkan::error::map_vk_result(e, "cull pipeline layout"))?;
let cs = compile_cull_shader(hot_reload)?;
let pipeline = compute_pipeline(device, pipeline_layout.handle(), &cs, "cull")?;
let n = n_cull as u64;
let object_buffer_size = n * std::mem::size_of::<render_types::GpuObjectData>() as u64;
let draw_args_size = n * std::mem::size_of::<render_types::GpuDrawArgs>() as u64;
let indirect_size = shader_bucket_count as u64
* n
* std::mem::size_of::<vk::DrawIndexedIndirectCommand>() as u64;
let mut da_buffers = Vec::with_capacity(frames);
let mut ind_buffers = Vec::with_capacity(frames);
for _ in 0..frames {
da_buffers.push(alloc.create_buffer(
draw_args_size,
vk::BufferUsageFlags::STORAGE_BUFFER,
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
)?);
ind_buffers.push(alloc.create_buffer(
indirect_size,
vk::BufferUsageFlags::STORAGE_BUFFER | vk::BufferUsageFlags::INDIRECT_BUFFER,
vk::MemoryPropertyFlags::DEVICE_LOCAL,
)?);
}
let set_layouts: Vec<_> = (0..frames).map(|_| set_layout.handle()).collect();
let sets =
alloc_descriptor_sets(device, descriptors.descriptor_pool.handle(), &set_layouts)?;
for (i, &set) in sets.iter().enumerate() {
SetWrites::new(set)
.storage_buffer(0, object_buffers[i].buffer(), object_buffer_size)
.storage_buffer(1, da_buffers[i].buffer(), draw_args_size)
.storage_buffer(2, ind_buffers[i].buffer(), indirect_size)
.storage_buffer(
3,
cull_status_buffers[i].buffer(),
n * std::mem::size_of::<u32>() as u64,
)
.apply(device);
}
let kernels = VkCullKernels {
pipeline,
pipeline_phase2: None,
pipeline_layout,
set_layout,
};
(Some(kernels), sets, da_buffers, ind_buffers, Some(hiz))
} else {
(None, Vec::new(), Vec::new(), Vec::new(), None)
};
if n_instances > 0 && !object_buffers.is_empty() {
use concinnity_core::gfx::render_types::{
GpuDrawArgs, GpuObjectData, draw_args_flags, instance_object_records,
};
let records =
instance_object_records(&world.instanced_clusters, scene.textures.len() as u32);
let mut draw_args: Vec<GpuDrawArgs> = Vec::with_capacity(records.len());
for cluster in &world.instanced_clusters {
for _ in &cluster.instances {
draw_args.push(GpuDrawArgs {
index_count: cluster.index_count as u32,
index_offset: cluster.index_offset as u32,
base_vertex: 0,
flags: draw_args_flags(true, true, true),
});
}
}
let n_objects = world.draw_objects.len();
let obj_stride = std::mem::size_of::<GpuObjectData>();
let da_stride = std::mem::size_of::<GpuDrawArgs>();
for (obj_buf, da_buf) in object_buffers.iter().zip(draw_args_buffers.iter()) {
obj_buf.write_slice(n_objects * obj_stride, &records);
da_buf.write_slice(n_objects * da_stride, &draw_args);
}
}
Ok(ComputeCull {
kernels,
sets: cull_sets,
draw_args_buffers,
indirect_buffers,
status_buffers: cull_status_buffers,
hiz,
})
}