use ash::vk;
use concinnity_core::gfx::render_types::{ShadowUniforms, SpotShadowData};
use concinnity_core::render::csm;
use concinnity_core::render::error::RenderResult;
use concinnity_core::render::spot_shadow;
use super::shadow::ShadowView;
use crate::vulkan::allocator::{DeviceAllocator, PooledBuffer};
use crate::vulkan::context::{VkContext, VkSpotShadow};
use crate::vulkan::descriptor_layout::{PoolSizes, shadow_global_set};
use crate::vulkan::owned::VkDevice;
use crate::vulkan::resources::alloc_descriptor_sets;
use crate::vulkan::set_writes::SetWrites;
use crate::vulkan::texture::GpuImage;
pub(in crate::vulkan) struct SpotShadowBuild<'a> {
pub alloc: &'a DeviceAllocator,
pub instance: &'a ash::Instance,
pub device: &'a VkDevice,
pub physical_device: vk::PhysicalDevice,
pub map: GpuImage,
pub render_pass: vk::RenderPass,
pub set_layout: vk::DescriptorSetLayout,
pub slice_size: u32,
pub spot_shadows: &'a [SpotShadowData],
}
pub(in crate::vulkan) fn build_spot_shadow(b: SpotShadowBuild<'_>) -> RenderResult<VkSpotShadow> {
let SpotShadowBuild {
alloc,
instance,
device,
physical_device,
map,
render_pass,
set_layout,
slice_size,
spot_shadows,
} = b;
let framebuffers = if spot_shadows.is_empty() {
Vec::new()
} else {
crate::vulkan::swapchain::create_shadow_framebuffers(device, render_pass, &map, slice_size)?
};
let data: Vec<SpotShadowData> = if spot_shadows.is_empty() {
vec![SpotShadowData::ZERO]
} else {
spot_shadows.to_vec()
};
let data_size = std::mem::size_of_val(data.as_slice()) as u64;
let data_buffer = alloc.create_buffer(
data_size,
vk::BufferUsageFlags::STORAGE_BUFFER,
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
)?;
upload_records(&data_buffer, &data);
let align = unsafe { instance.get_physical_device_properties(physical_device) }
.limits
.min_uniform_buffer_offset_alignment
.max(1);
let stride = (size_of::<ShadowUniforms>() as u64).div_ceil(align) * align;
let slots = framebuffers.len().max(1) as u64;
let ubo = alloc.create_buffer(
stride * slots,
vk::BufferUsageFlags::UNIFORM_BUFFER,
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
)?;
if !spot_shadows.is_empty() {
let uniforms: Vec<ShadowUniforms> = spot_shadows
.iter()
.map(|sd| {
let mut u = csm::empty_shadow_uniforms();
u.light_vps[0] = sd.light_vp;
u.active_cascades = 1;
u
})
.collect();
upload_strided(&ubo, &uniforms, stride);
}
let set_count = framebuffers.len().max(1) as u32;
let pool_sizes = PoolSizes::default()
.sets(&shadow_global_set(), set_count)
.build();
let descriptor_pool = device
.create_descriptor_pool(
&vk::DescriptorPoolCreateInfo::default()
.pool_sizes(&pool_sizes)
.max_sets(set_count),
)
.map_err(|e| crate::vulkan::error::map_vk_result(e, "spot shadow descriptor pool"))?;
let layouts: Vec<_> = (0..set_count).map(|_| set_layout).collect();
let sets = alloc_descriptor_sets(device, descriptor_pool.handle(), &layouts)?;
for (i, &set) in sets.iter().enumerate() {
SetWrites::new(set)
.buffer(
0,
vk::DescriptorType::UNIFORM_BUFFER,
ubo.buffer(),
i as u64 * stride,
size_of::<ShadowUniforms>() as u64,
)
.apply(device);
}
Ok(VkSpotShadow {
map,
framebuffers,
slice_size,
data_buffer,
ubo,
sets,
_descriptor_pool: descriptor_pool,
frusta: spot_shadows
.iter()
.map(spot_shadow::slice_frustum)
.collect(),
scheduler: Default::default(),
render_mask: 0,
})
}
fn upload_records<T: Copy>(buffer: &PooledBuffer, records: &[T]) {
buffer.write_slice(0, records);
}
fn upload_strided<T: Copy>(buffer: &PooledBuffer, records: &[T], stride: u64) {
for (i, r) in records.iter().enumerate() {
buffer.write_val(i * stride as usize, r);
}
}
impl VkContext {
pub(in crate::vulkan) fn encode_spot_shadow_pass(
&self,
cmd: vk::CommandBuffer,
frame_idx: usize,
cam_pos: [f32; 3],
) {
if self.spot_shadow.count() == 0 {
return;
}
let device = &self.hw.device;
let gpu_driven = self.shadow_views_drawable()
&& self.cull.spot_indirect_buffers.get(frame_idx).is_some();
if gpu_driven {
self.encode_spot_culls(cmd, frame_idx, cam_pos);
}
let sz = self.spot_shadow.slice_size;
for slice in self.spot_shadow.refreshed_slices() {
let framebuffer = &self.spot_shadow.framebuffers[slice as usize];
self.begin_shadow_slice(cmd, framebuffer.handle(), sz);
if gpu_driven {
self.draw_shadow_view(
device,
cmd,
frame_idx,
ShadowView {
uniforms_set: self.spot_shadow.sets[slice as usize],
vp_index: 0,
indirect: self.cull.spot_indirect_buffers[frame_idx][slice as usize]
.buffer(),
},
);
}
unsafe { device.cmd_end_render_pass(cmd) };
}
}
}