diff options
| author | Valentin Popov <valentin@popov.link> | 2026-10-18 10:23:35 +0300 |
|---|---|---|
| committer | Valentin Popov <valentin@popov.link> | 2026-10-18 10:24:16 +0300 |
| commit | 5a48d0b26685526eaa2c7c824e6e0828177246b0 (patch) | |
| tree | 3cbeb39f69ea27b50216389b585678d7c7daa163 /adapters/fparkan-render-vulkan/src/ffi/swapchain_resources.rs | |
| parent | 7abf45de7e3356362c55f966c446291728723f59 (diff) | |
| download | fparkan-codex/runtime-point-light-ssbo.tar.xz fparkan-codex/runtime-point-light-ssbo.zip | |
feat(render): upload dynamic point lights per framecodex/runtime-point-light-ssbo
Diffstat (limited to 'adapters/fparkan-render-vulkan/src/ffi/swapchain_resources.rs')
| -rw-r--r-- | adapters/fparkan-render-vulkan/src/ffi/swapchain_resources.rs | 163 |
1 files changed, 156 insertions, 7 deletions
diff --git a/adapters/fparkan-render-vulkan/src/ffi/swapchain_resources.rs b/adapters/fparkan-render-vulkan/src/ffi/swapchain_resources.rs index b8049d3..89ca43a 100644 --- a/adapters/fparkan-render-vulkan/src/ffi/swapchain_resources.rs +++ b/adapters/fparkan-render-vulkan/src/ffi/swapchain_resources.rs @@ -9,9 +9,10 @@ use std::collections::BTreeMap; use super::{ color_subresource_range, create_depth_attachment, create_material_specular_buffer, - destroy_depth_attachment, VulkanAllocatedBuffer, VulkanAllocatedImage, VulkanDepthAttachment, - VulkanInstanceProbe, VulkanLogicalDeviceProbe, VulkanSmokeRendererError, VulkanSwapchainProbe, - TRIANGLE_FRAGMENT_SHADER_WORDS, TRIANGLE_VERTEX_SHADER_WORDS, VULKAN_FRAME_UNIFORM_BYTES, + create_point_light_buffer, destroy_depth_attachment, VulkanAllocatedBuffer, + VulkanAllocatedImage, VulkanDepthAttachment, VulkanInstanceProbe, VulkanLogicalDeviceProbe, + VulkanSmokeRendererError, VulkanSwapchainProbe, TRIANGLE_FRAGMENT_SHADER_WORDS, + TRIANGLE_VERTEX_SHADER_WORDS, VULKAN_FRAME_UNIFORM_BYTES, VULKAN_MATERIAL_SPECULAR_RECORD_BYTES, }; @@ -33,6 +34,11 @@ pub(super) struct VulkanSwapchainResources { pub(super) frame_uniform_stride: u64, pub(super) material_specular_buffer: VulkanAllocatedBuffer, pub(super) material_specular_stride: u64, + pub(super) point_light_buffer: VulkanAllocatedBuffer, + pub(super) point_light_range: u64, + pub(super) point_light_stride: u64, + pub(super) point_light_capacity: usize, + pub(super) max_point_light_count: usize, /// Live graphics pipelines indexed by canonical backend-neutral state. pub(super) pipelines: BTreeMap<PipelineKey, vk::Pipeline>, pub(super) framebuffers: Vec<vk::Framebuffer>, @@ -73,6 +79,71 @@ pub(super) fn update_material_texture_descriptor( Ok(()) } +/// Grows the dynamic light SSBO when the scene adds lights beyond its current +/// capacity. The caller must idle the device before updating these descriptors. +pub(super) fn ensure_point_light_capacity( + instance: &VulkanInstanceProbe, + device: &VulkanLogicalDeviceProbe, + resources: &mut VulkanSwapchainResources, + needed: usize, +) -> Result<(), VulkanSmokeRendererError> { + if needed <= resources.point_light_capacity { + return Ok(()); + } + if needed > resources.max_point_light_count { + return Err(VulkanSmokeRendererError::InvalidPointLights { + context: "point-light count exceeds maxStorageBufferRange", + }); + } + let target = resources + .point_light_capacity + .saturating_mul(2) + .max(1) + .max(needed) + .min(resources.max_point_light_count); + let (new_buffer, new_range, new_stride, max_count) = + create_point_light_buffer(instance, device, target)?; + if max_count != resources.max_point_light_count { + unsafe { + device.device().destroy_buffer(new_buffer.buffer, None); + device.device().free_memory(new_buffer.memory, None); + } + return Err(VulkanSmokeRendererError::InvariantViolation { + context: "point-light capacity changed for the active device", + }); + } + let info = vk::DescriptorBufferInfo::default() + .buffer(new_buffer.buffer) + .offset(0) + .range(new_range); + let writes = resources + .descriptor_sets + .iter() + .copied() + .map(|descriptor_set| { + vk::WriteDescriptorSet::default() + .dst_set(descriptor_set) + .dst_binding(6) + .descriptor_type(vk::DescriptorType::STORAGE_BUFFER_DYNAMIC) + .buffer_info(std::slice::from_ref(&info)) + }) + .collect::<Vec<_>>(); + // SAFETY: The caller waited for device idle before replacing a descriptor + // that submitted command buffers may reference. + unsafe { device.device().update_descriptor_sets(&writes, &[]) }; + let old_buffer = std::mem::replace(&mut resources.point_light_buffer, new_buffer); + resources.point_light_range = new_range; + resources.point_light_stride = new_stride; + resources.point_light_capacity = target; + // SAFETY: All prior submissions have completed and descriptors now point + // at the replacement allocation. + unsafe { + device.device().destroy_buffer(old_buffer.buffer, None); + device.device().free_memory(old_buffer.memory, None); + } + Ok(()) +} + struct PartialSwapchainResources { image_views: Vec<vk::ImageView>, readback_buffers: Vec<VulkanAllocatedBuffer>, @@ -87,6 +158,11 @@ struct PartialSwapchainResources { frame_uniform_stride: u64, material_specular_buffer: Option<VulkanAllocatedBuffer>, material_specular_stride: u64, + point_light_buffer: Option<VulkanAllocatedBuffer>, + point_light_range: u64, + point_light_stride: u64, + point_light_capacity: usize, + max_point_light_count: usize, pipelines: BTreeMap<PipelineKey, vk::Pipeline>, framebuffers: Vec<vk::Framebuffer>, command_buffers: Vec<vk::CommandBuffer>, @@ -133,6 +209,11 @@ pub(super) fn create_swapchain_resources( frame_uniform_stride: 0, material_specular_buffer: None, material_specular_stride: 0, + point_light_buffer: None, + point_light_range: 0, + point_light_stride: 0, + point_light_capacity: 0, + max_point_light_count: 0, pipelines: BTreeMap::new(), framebuffers: Vec::new(), command_buffers: Vec::new(), @@ -173,6 +254,20 @@ pub(super) fn create_swapchain_resources( }; partial.material_specular_stride = material_specular_stride; partial.material_specular_buffer = Some(material_specular_buffer); + let point_light_capacity = 1; + let (point_light_buffer, point_light_range, point_light_stride, max_point_light_count) = + match create_point_light_buffer(instance, device, point_light_capacity) { + Ok(buffer) => buffer, + Err(error) => { + destroy_partial_swapchain_resources(device, command_pool, partial); + return Err(error); + } + }; + partial.point_light_range = point_light_range; + partial.point_light_stride = point_light_stride; + partial.point_light_capacity = point_light_capacity; + partial.max_point_light_count = max_point_light_count; + partial.point_light_buffer = Some(point_light_buffer); let ( depth_attachment, render_pass, @@ -199,6 +294,11 @@ pub(super) fn create_swapchain_resources( .material_specular_buffer .as_ref() .expect("material specular buffer is present before descriptor creation"), + partial + .point_light_buffer + .as_ref() + .expect("point-light buffer is present before descriptor creation"), + partial.point_light_range, ) { Ok(bundle) => bundle, Err(error) => { @@ -320,6 +420,13 @@ pub(super) fn create_swapchain_resources( .material_specular_buffer .expect("material specular buffer is present after successful swapchain setup"), material_specular_stride: partial.material_specular_stride, + point_light_buffer: partial + .point_light_buffer + .expect("point-light buffer is present after successful swapchain setup"), + point_light_range: partial.point_light_range, + point_light_stride: partial.point_light_stride, + point_light_capacity: partial.point_light_capacity, + max_point_light_count: partial.max_point_light_count, pipelines: partial.pipelines, framebuffers: partial.framebuffers, command_buffers: partial.command_buffers, @@ -349,6 +456,8 @@ fn create_swapchain_pipeline_bundle( depth_request: DepthStencilSupport, frame_uniform_buffer: &VulkanAllocatedBuffer, material_specular_buffer: &VulkanAllocatedBuffer, + point_light_buffer: &VulkanAllocatedBuffer, + point_light_range: u64, ) -> Result< ( VulkanDepthAttachment, @@ -377,6 +486,8 @@ fn create_swapchain_pipeline_bundle( texture_slot_indices, frame_uniform_buffer, material_specular_buffer, + point_light_buffer, + point_light_range, ) .inspect_err(|_| { // SAFETY: The render pass was created above on this live logical device and is destroyed on setup failure. @@ -603,6 +714,8 @@ fn create_texture_descriptor_bundle( texture_slot_indices: &[usize], frame_uniform_buffer: &VulkanAllocatedBuffer, material_specular_buffer: &VulkanAllocatedBuffer, + point_light_buffer: &VulkanAllocatedBuffer, + point_light_range: u64, ) -> Result< ( vk::DescriptorSetLayout, @@ -615,6 +728,7 @@ fn create_texture_descriptor_bundle( const TEXTURES_PER_MATERIAL: usize = 4; const FRAME_BINDING: u32 = 4; const MATERIAL_SPECULAR_BINDING: u32 = 5; + const POINT_LIGHT_BINDING: u32 = 6; let mut bindings = (0..TEXTURES_PER_MATERIAL) .map(|binding| { vk::DescriptorSetLayoutBinding::default() @@ -638,6 +752,13 @@ fn create_texture_descriptor_bundle( .descriptor_count(1) .stage_flags(vk::ShaderStageFlags::VERTEX), ); + bindings.push( + vk::DescriptorSetLayoutBinding::default() + .binding(POINT_LIGHT_BINDING) + .descriptor_type(vk::DescriptorType::STORAGE_BUFFER_DYNAMIC) + .descriptor_count(1) + .stage_flags(vk::ShaderStageFlags::VERTEX), + ); let layout_info = vk::DescriptorSetLayoutCreateInfo::default().bindings(&bindings); // SAFETY: The layout description is stack-owned and references no external memory. let layout = unsafe { @@ -676,10 +797,17 @@ fn create_texture_descriptor_bundle( let frame_pool_size = vk::DescriptorPoolSize::default() .ty(vk::DescriptorType::UNIFORM_BUFFER_DYNAMIC) .descriptor_count(material_count); - let material_specular_pool_size = vk::DescriptorPoolSize::default() + let storage_pool_size = vk::DescriptorPoolSize::default() .ty(vk::DescriptorType::STORAGE_BUFFER_DYNAMIC) - .descriptor_count(material_count); - let pool_sizes = [pool_size, frame_pool_size, material_specular_pool_size]; + .descriptor_count(material_count.checked_mul(2).ok_or_else(|| { + // SAFETY: The layout was created above on this device and is + // rolled back before reporting an impossible descriptor count. + unsafe { device.device().destroy_descriptor_set_layout(layout, None) }; + VulkanSmokeRendererError::InvariantViolation { + context: "dynamic storage descriptor count overflow", + } + })?); + let pool_sizes = [pool_size, frame_pool_size, storage_pool_size]; let pool_info = vk::DescriptorPoolCreateInfo::default() .pool_sizes(&pool_sizes) .max_sets(material_count); @@ -751,7 +879,11 @@ fn create_texture_descriptor_bundle( .buffer(material_specular_buffer.buffer) .offset(0) .range(VULKAN_MATERIAL_SPECULAR_RECORD_BYTES as u64); - let mut writes = Vec::with_capacity(descriptor_sets.len() * (TEXTURES_PER_MATERIAL + 2)); + let point_light_info = vk::DescriptorBufferInfo::default() + .buffer(point_light_buffer.buffer) + .offset(0) + .range(point_light_range); + let mut writes = Vec::with_capacity(descriptor_sets.len() * (TEXTURES_PER_MATERIAL + 3)); for (set_index, descriptor_set) in descriptor_sets.iter().copied().enumerate() { for binding in 0..TEXTURES_PER_MATERIAL { let image_info = &image_infos[set_index * TEXTURES_PER_MATERIAL + binding]; @@ -777,6 +909,13 @@ fn create_texture_descriptor_bundle( .descriptor_type(vk::DescriptorType::STORAGE_BUFFER_DYNAMIC) .buffer_info(std::slice::from_ref(&material_specular_info)), ); + writes.push( + vk::WriteDescriptorSet::default() + .dst_set(descriptor_set) + .dst_binding(POINT_LIGHT_BINDING) + .descriptor_type(vk::DescriptorType::STORAGE_BUFFER_DYNAMIC) + .buffer_info(std::slice::from_ref(&point_light_info)), + ); } // SAFETY: Descriptor sets, sampler and image views are live; every texture upload completed its shader-read transition. unsafe { device.device().update_descriptor_sets(&writes, &[]) }; @@ -1114,6 +1253,12 @@ pub(super) fn destroy_swapchain_resources( device .device() .free_memory(resources.material_specular_buffer.memory, None); + device + .device() + .destroy_buffer(resources.point_light_buffer.buffer, None); + device + .device() + .free_memory(resources.point_light_buffer.memory, None); } } @@ -1177,5 +1322,9 @@ fn destroy_partial_swapchain_resources( device.device().destroy_buffer(buffer.buffer, None); device.device().free_memory(buffer.memory, None); } + if let Some(buffer) = partial.point_light_buffer { + device.device().destroy_buffer(buffer.buffer, None); + device.device().free_memory(buffer.memory, None); + } } } |
