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/smoke.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/smoke.rs')
| -rw-r--r-- | adapters/fparkan-render-vulkan/src/ffi/smoke.rs | 143 |
1 files changed, 126 insertions, 17 deletions
diff --git a/adapters/fparkan-render-vulkan/src/ffi/smoke.rs b/adapters/fparkan-render-vulkan/src/ffi/smoke.rs index ac359c2..b3d5ab3 100644 --- a/adapters/fparkan-render-vulkan/src/ffi/smoke.rs +++ b/adapters/fparkan-render-vulkan/src/ffi/smoke.rs @@ -11,14 +11,14 @@ use super::{ create_validation_messenger, create_vertex_buffer, create_vulkan_instance_probe, create_vulkan_logical_device_probe_for_request, create_vulkan_surface_probe, create_vulkan_swapchain_probe_for_extent, destroy_allocated_buffer, destroy_allocated_image, - destroy_swapchain_resources, plan_vulkan_surface, readback_buffer_bytes, - update_static_texture_image, write_frame_uniform, write_material_specular_buffer, - write_static_mesh_index_buffer, write_static_mesh_vertex_buffer, VulkanAllocatedBuffer, - VulkanInstanceConfig, VulkanInstanceProbe, VulkanLogicalDeviceProbe, VulkanReadbackArtifact, - VulkanSmokeFrameOutcome, VulkanSmokeRenderer, VulkanSmokeRendererCreateInfo, - VulkanSmokeRendererError, VulkanSmokeRendererReport, VulkanSmokeShutdownReport, - VulkanSurfaceProbe, VulkanSwapchainProbe, VulkanSwapchainResources, VulkanValidationMessenger, - VulkanValidationReport, + destroy_swapchain_resources, ensure_point_light_capacity, plan_vulkan_surface, + readback_buffer_bytes, update_static_texture_image, write_frame_uniform, + write_material_specular_buffer, write_point_light_buffer, write_static_mesh_index_buffer, + write_static_mesh_vertex_buffer, VulkanAllocatedBuffer, VulkanInstanceConfig, + VulkanInstanceProbe, VulkanLogicalDeviceProbe, VulkanReadbackArtifact, VulkanSmokeFrameOutcome, + VulkanSmokeRenderer, VulkanSmokeRendererCreateInfo, VulkanSmokeRendererError, + VulkanSmokeRendererReport, VulkanSmokeShutdownReport, VulkanSurfaceProbe, VulkanSwapchainProbe, + VulkanSwapchainResources, VulkanValidationMessenger, VulkanValidationReport, }; use crate::policy::KHR_PORTABILITY_SUBSET_EXTENSION; use crate::shader::validate_triangle_shaders; @@ -440,6 +440,8 @@ impl VulkanSmokeRenderer { readback_enabled: false, camera: create_info.camera, frame_uniforms: super::VulkanFrameUniforms::from_camera(create_info.camera), + point_lights: Vec::new(), + point_lights_explicit: false, frame_sync: Vec::new(), images_in_flight: Vec::new(), current_frame: 0, @@ -576,6 +578,41 @@ impl VulkanSmokeRenderer { Ok(()) } + /// Replaces the complete dynamic point-light set for subsequent frames. + /// + /// The storage buffer grows on demand and rejects counts beyond the + /// device's `maxStorageBufferRange`; it never truncates scene lights. + /// Values are uploaded to the current fence-safe frame slot by + /// [`Self::draw_frame`]. + pub fn set_point_lights( + &mut self, + point_lights: &[super::VulkanPointLight], + ) -> Result<(), VulkanSmokeRendererError> { + let max_count = self.resources_ref()?.max_point_light_count; + if point_lights.len() > max_count { + return Err(VulkanSmokeRendererError::InvalidPointLights { + context: "point-light count exceeds maxStorageBufferRange", + }); + } + if point_lights.iter().any(|light| { + !light + .position + .into_iter() + .chain(light.rgb) + .chain([light.range]) + .chain(light.coefficients) + .all(f32::is_finite) + }) { + return Err(VulkanSmokeRendererError::InvalidPointLights { + context: "non-finite point-light field", + }); + } + self.point_lights.clear(); + self.point_lights.extend_from_slice(point_lights); + self.point_lights_explicit = true; + Ok(()) + } + /// Enables the optional swapchain image copy used by screenshot/readback /// callers. Runtime frames leave it disabled to avoid a transfer every /// frame. @@ -1318,6 +1355,44 @@ impl VulkanSmokeRenderer { context: "vkWaitForFences", result: error, })?; + let legacy_point_light = self.frame_uniforms.point_light; + let point_light_count = if self.point_lights_explicit { + self.point_lights.len() + } else { + usize::from(legacy_point_light.active) + }; + let needs_point_light_growth = + point_light_count > self.resources_ref()?.point_light_capacity; + if needs_point_light_growth { + // Updating every material's binding 6 and replacing the shared + // SSBO requires all prior command buffers to finish. + // SAFETY: The renderer owns the logical device and no submission + // can observe the replacement after this idle point. + unsafe { self.device_ref()?.device().device_wait_idle() }.map_err(|result| { + VulkanSmokeRendererError::VulkanOperation { + context: "vkDeviceWaitIdle(point-light buffer growth)", + result, + } + })?; + let instance = + self.instance + .as_ref() + .ok_or(VulkanSmokeRendererError::InvariantViolation { + context: "Vulkan instance during point-light growth", + })?; + let device = + self.device + .as_ref() + .ok_or(VulkanSmokeRendererError::InvariantViolation { + context: "logical device during point-light growth", + })?; + let resources = self.swapchain_resources.as_mut().ok_or( + VulkanSmokeRendererError::InvariantViolation { + context: "swapchain resources during point-light growth", + }, + )?; + ensure_point_light_capacity(instance, device, resources, point_light_count)?; + } if self.material_specular_dirty { // The storage buffer is shared by all in-flight frames. Wait for // every prior use before replacing the records so a setter cannot @@ -1377,14 +1452,42 @@ impl VulkanSmokeRenderer { } self.images_in_flight[image_index_usize] = in_flight_fence; let frame_bytes = self.frame_uniforms.to_ne_bytes(); - let resources = self.resources_ref()?; - write_frame_uniform( - self.device_ref()?, - &resources.frame_uniform_buffer, - resources.frame_uniform_stride, - self.current_frame, - &frame_bytes, - )?; + { + let explicit_point_lights = self.point_lights_explicit; + let legacy_point_light = self.frame_uniforms.point_light; + let point_lights = if explicit_point_lights { + self.point_lights.as_slice() + } else if legacy_point_light.active { + std::slice::from_ref(&legacy_point_light) + } else { + &[] + }; + let device = + self.device + .as_ref() + .ok_or(VulkanSmokeRendererError::InvariantViolation { + context: "logical device during frame upload", + })?; + let resources = self.swapchain_resources.as_ref().ok_or( + VulkanSmokeRendererError::InvariantViolation { + context: "swapchain resources during frame upload", + }, + )?; + write_frame_uniform( + device, + &resources.frame_uniform_buffer, + resources.frame_uniform_stride, + self.current_frame, + &frame_bytes, + )?; + write_point_light_buffer( + device, + &resources.point_light_buffer, + resources.point_light_stride, + self.current_frame, + point_lights, + )?; + } let render_finished = self .resources_ref()? .render_finished @@ -1693,6 +1796,12 @@ impl VulkanSmokeRenderer { .saturating_mul(u64::try_from(texture_index).unwrap_or(u64::MAX)), ) .unwrap_or(u32::MAX); + let point_light_offset = u32::try_from( + resources + .point_light_stride + .saturating_mul(u64::try_from(self.current_frame).unwrap_or(u64::MAX)), + ) + .unwrap_or(u32::MAX); let descriptor_set = resources.descriptor_sets.get(texture_index).ok_or( VulkanSmokeRendererError::InvariantViolation { context: "static material descriptor set", @@ -1704,7 +1813,7 @@ impl VulkanSmokeRenderer { resources.pipeline_layout, 0, std::slice::from_ref(descriptor_set), - &[frame_offset, material_offset], + &[frame_offset, material_offset, point_light_offset], ); let alpha_cutoff = range.alpha_test_cutoff().to_ne_bytes(); device.device().cmd_push_constants( |
