diff --git a/asset/shader/vulkan/raytrace.rgen b/asset/shader/vulkan/raytrace.rgen index de6da57f..f0604f8e 100644 --- a/asset/shader/vulkan/raytrace.rgen +++ b/asset/shader/vulkan/raytrace.rgen @@ -151,14 +151,30 @@ HardwareRaytraceInstanceData GetRaytraceInstanceData(const int instance_index) return raytrace_instances[instance_index]; } +bool UseSharedSceneTransform() +{ + return raytrace_instances.length() > 0 && + raytrace_instances[0].metadata.z != 0u; +} + vec3 TransformPoint(const mat4 matrix, const vec3 point) { return (matrix * vec4(point, 1.0)).xyz; } +mat4 GetObjectToWorld(const HardwareRaytraceInstanceData instance_data) +{ + return UseSharedSceneTransform() ? ubo.model : instance_data.object_to_world; +} + +mat4 GetWorldToObject(const HardwareRaytraceInstanceData instance_data) +{ + return UseSharedSceneTransform() ? ubo.model_inv : instance_data.world_to_object; +} + mat3 GetNormalMatrix(const HardwareRaytraceInstanceData instance_data) { - return transpose(mat3(instance_data.world_to_object)); + return transpose(mat3(GetWorldToObject(instance_data))); } // ---------------------------------------------------------------------------- @@ -392,6 +408,14 @@ bool anyHitTriangles(const vec3 ray_origin, const vec3 ray_dir, const float max_ return false; } + vec3 trace_origin = ray_origin; + vec3 trace_dir = ray_dir; + if (UseSharedSceneTransform()) + { + trace_origin = TransformPoint(ubo.model_inv, ray_origin); + trace_dir = normalize((ubo.model_inv * vec4(ray_dir, 0.0)).xyz); + } + ray_payload.hit_data = vec4(0.0); ray_payload.index_data = ivec4(-1); traceRayEXT( @@ -401,9 +425,9 @@ bool anyHitTriangles(const vec3 ray_origin, const vec3 ray_dir, const float max_ 0, 0, 0, - ray_origin, + trace_origin, 0.001, - ray_dir, + trace_dir, max_t, 0); return ray_payload.hit_data.x != 0.0; @@ -436,6 +460,14 @@ HitInfo TraceScene(const vec3 ray_origin_world, const vec3 ray_dir_world) return info; } + vec3 trace_origin = ray_origin_world; + vec3 trace_dir = ray_dir_world; + if (UseSharedSceneTransform()) + { + trace_origin = TransformPoint(ubo.model_inv, ray_origin_world); + trace_dir = normalize((ubo.model_inv * vec4(ray_dir_world, 0.0)).xyz); + } + ray_payload.hit_data = vec4(0.0); ray_payload.index_data = ivec4(-1); traceRayEXT( @@ -445,9 +477,9 @@ HitInfo TraceScene(const vec3 ray_origin_world, const vec3 ray_dir_world) 0, 0, 0, - ray_origin_world, + trace_origin, 0.001, - ray_dir_world, + trace_dir, 1.0e30, 0); if (ray_payload.hit_data.x == 0.0) @@ -475,7 +507,7 @@ HitInfo TraceScene(const vec3 ray_origin_world, const vec3 ray_dir_world) tri.v0.position * w + tri.v1.position * info.bary.x + tri.v2.position * info.bary.y; - info.pos_world = TransformPoint(instance_data.object_to_world, info.pos_model); + info.pos_world = TransformPoint(GetObjectToWorld(instance_data), info.pos_model); info.normal_model = normalize( tri.v0.normal * w + tri.v1.normal * info.bary.x + diff --git a/frame/vulkan/buffer_resources.cpp b/frame/vulkan/buffer_resources.cpp index dae88534..3f2b25ab 100644 --- a/frame/vulkan/buffer_resources.cpp +++ b/frame/vulkan/buffer_resources.cpp @@ -23,7 +23,7 @@ void BufferResourceManager::Clear() { storage_buffers_.clear(); storage_buffer_indices_.clear(); - uniform_buffer_ = {}; + uniform_buffers_.clear(); } BufferResource BufferResourceManager::MakeGpuBuffer( @@ -216,57 +216,74 @@ bool BufferResourceManager::UpdateStorageBuffer( return true; } -void BufferResourceManager::BuildUniformBuffer(vk::DeviceSize size_bytes) +void BufferResourceManager::BuildUniformBuffers( + std::size_t count, + vk::DeviceSize size_bytes) { - if (size_bytes == 0) + if (count == 0 || size_bytes == 0) { - throw std::runtime_error("Uniform buffer size must be non-zero."); + throw std::runtime_error( + "Uniform buffer count and size must be non-zero."); } - vk::UniqueDeviceMemory uniform_memory; - auto uniform_buf = memory_manager_->CreateBuffer( - size_bytes, - vk::BufferUsageFlagBits::eUniformBuffer, - vk::MemoryPropertyFlagBits::eHostVisible | - vk::MemoryPropertyFlagBits::eHostCoherent, - uniform_memory); + uniform_buffers_.clear(); + uniform_buffers_.reserve(count); + for (std::size_t i = 0; i < count; ++i) + { + vk::UniqueDeviceMemory uniform_memory; + auto uniform_buf = memory_manager_->CreateBuffer( + size_bytes, + vk::BufferUsageFlagBits::eUniformBuffer, + vk::MemoryPropertyFlagBits::eHostVisible | + vk::MemoryPropertyFlagBits::eHostCoherent, + uniform_memory); - uniform_buffer_.name = "uniforms"; - uniform_buffer_.size = size_bytes; - uniform_buffer_.buffer = std::move(uniform_buf); - uniform_buffer_.memory = std::move(uniform_memory); + BufferResource uniform_buffer = {}; + uniform_buffer.name = "uniforms_" + std::to_string(i); + uniform_buffer.size = size_bytes; + uniform_buffer.buffer = std::move(uniform_buf); + uniform_buffer.memory = std::move(uniform_memory); + uniform_buffers_.push_back(std::move(uniform_buffer)); + } } void BufferResourceManager::UpdateUniform( + std::size_t index, const void* data, std::size_t byte_count) const { - if (!uniform_buffer_.buffer || - !uniform_buffer_.memory || - uniform_buffer_.size == 0) + if (index >= uniform_buffers_.size()) + { + (*logger_)->warn("Uniform buffer index {} is out of range.", index); + return; + } + const auto& uniform_buffer = uniform_buffers_[index]; + if (!uniform_buffer.buffer || + !uniform_buffer.memory || + uniform_buffer.size == 0) { (*logger_)->warn("Uniform buffer not initialized before update."); return; } - if (byte_count > static_cast(uniform_buffer_.size)) + if (byte_count > static_cast(uniform_buffer.size)) { (*logger_)->warn( "Uniform update size {} exceeds buffer capacity {}.", byte_count, - static_cast(uniform_buffer_.size)); + static_cast(uniform_buffer.size)); return; } void* mapped = device_.mapMemory( - *uniform_buffer_.memory, 0, uniform_buffer_.size); + *uniform_buffer.memory, 0, uniform_buffer.size); if (data && byte_count > 0) { std::memcpy(mapped, data, byte_count); } else { - std::memset(mapped, 0, uniform_buffer_.size); + std::memset(mapped, 0, uniform_buffer.size); } - device_.unmapMemory(*uniform_buffer_.memory); + device_.unmapMemory(*uniform_buffer.memory); } void BufferResourceManager::LogCpuBufferSamples( diff --git a/frame/vulkan/buffer_resources.h b/frame/vulkan/buffer_resources.h index a514c9a1..7695a24b 100644 --- a/frame/vulkan/buffer_resources.h +++ b/frame/vulkan/buffer_resources.h @@ -39,17 +39,39 @@ class BufferResourceManager bool UpdateStorageBuffer( const std::string& name, const std::vector& bytes); - void BuildUniformBuffer(vk::DeviceSize size_bytes); - void UpdateUniform(const void* data, std::size_t byte_count) const; + void BuildUniformBuffers( + std::size_t count, + vk::DeviceSize size_bytes); + void BuildUniformBuffer(vk::DeviceSize size_bytes) + { + BuildUniformBuffers(1, size_bytes); + } + void UpdateUniform( + std::size_t index, + const void* data, + std::size_t byte_count) const; + void UpdateUniform(const void* data, std::size_t byte_count) const + { + UpdateUniform(0, data, byte_count); + } const std::vector& GetStorageBuffers() const { return storage_buffers_; } + const BufferResource* GetUniformBuffer(std::size_t index) const + { + if (index >= uniform_buffers_.size()) + { + return nullptr; + } + return uniform_buffers_[index].buffer ? &uniform_buffers_[index] + : nullptr; + } const BufferResource* GetUniformBuffer() const { - return uniform_buffer_.buffer ? &uniform_buffer_ : nullptr; + return GetUniformBuffer(0); } void LogCpuBufferSamples( @@ -69,7 +91,7 @@ class BufferResourceManager const Logger* logger_; std::vector storage_buffers_; std::unordered_map storage_buffer_indices_; - BufferResource uniform_buffer_; + std::vector uniform_buffers_; }; } // namespace frame::vulkan diff --git a/frame/vulkan/device.cpp b/frame/vulkan/device.cpp index d5e85557..88567ba6 100644 --- a/frame/vulkan/device.cpp +++ b/frame/vulkan/device.cpp @@ -467,6 +467,40 @@ bool HasRaytracingSourceMeshes(frame::LevelInterface& level) return !GetRaytracingSourceMeshMaterials(level).empty(); } +std::optional GetSharedRaytraceSceneTransform( + frame::LevelInterface& level, + double time_seconds) +{ + const auto source_mesh_materials = GetRaytracingSourceMeshMaterials(level); + if (source_mesh_materials.empty()) + { + return std::nullopt; + } + + std::optional shared_model = std::nullopt; + for (const auto& [source_node_id, source_material_id] : source_mesh_materials) + { + (void)source_material_id; + auto* node = + dynamic_cast(&level.GetSceneNodeFromId(source_node_id)); + if (!node) + { + return std::nullopt; + } + const glm::mat4 model = node->GetLocalModel(time_seconds); + if (!shared_model) + { + shared_model = model; + continue; + } + if (*shared_model != model) + { + return std::nullopt; + } + } + return shared_model; +} + constexpr std::size_t kRaytraceFloatsPerVertex = 12; constexpr std::size_t kRaytraceTriangleVertexStrideBytes = sizeof(float) * kRaytraceFloatsPerVertex; @@ -960,23 +994,37 @@ std::vector CollectHardwareRaytraceSourceEntries( std::vector BuildHardwareRaytracingInstanceData( - const std::vector& entries) + const std::vector& entries, + const std::optional& shared_scene_model = std::nullopt) { std::vector data = {}; data.reserve(entries.size()); + const bool use_shared_scene_transform = shared_scene_model.has_value(); for (const auto& entry : entries) { frame::vulkan::Device::HardwareRaytracingInstanceData instance = {}; - instance.object_to_world = entry.model; - const float det = glm::determinant(glm::mat3(entry.model)); - instance.world_to_object = - std::abs(det) > 1.0e-8f - ? glm::inverse(entry.model) - : glm::mat4(1.0f); + if (use_shared_scene_transform) + { + // Shared-transform scenes keep geometry/TLAS in scene-local space. + // The current scene matrix is supplied through the uniform block + // each frame, so baking it into the instance buffer would make + // shading lag behind the animated scene transform. + instance.object_to_world = glm::mat4(1.0f); + instance.world_to_object = glm::mat4(1.0f); + } + else + { + instance.object_to_world = entry.model; + const float entry_det = glm::determinant(glm::mat3(entry.model)); + instance.world_to_object = + std::abs(entry_det) > 1.0e-8f + ? glm::inverse(entry.model) + : glm::mat4(1.0f); + } instance.metadata = glm::uvec4( entry.triangle_offset, entry.material_id, - 0u, + use_shared_scene_transform ? 1u : 0u, 0u); data.push_back(std::move(instance)); } @@ -1025,15 +1073,19 @@ bool HardwareRaytracingLayoutMatches( std::vector BuildHardwareRaytracingAsInstances( - const std::vector& entries) + const std::vector& entries, + const std::optional& shared_scene_model = std::nullopt) { std::vector instances = {}; instances.reserve(entries.size()); + const bool use_shared_scene_transform = shared_scene_model.has_value(); for (std::size_t i = 0; i < entries.size(); ++i) { const auto& entry = entries[i]; vk::AccelerationStructureInstanceKHR instance{}; - instance.transform = MakeTransformMatrix(entry.model); + instance.transform = use_shared_scene_transform + ? MakeIdentityTransform() + : MakeTransformMatrix(entry.model); instance.instanceCustomIndex = static_cast(i); instance.mask = 0xFF; instance.instanceShaderBindingTableRecordOffset = 0; @@ -2377,6 +2429,9 @@ void Device::DestroyHardwareRaytracingSceneSlot( scene.instance_count = 0; scene.state_hash = 0; scene.ready = false; + scene.uniform_block = {}; + scene.has_uniform_block = false; + scene.uses_shared_scene_transform = false; } void Device::EnsureHardwareRaytracingSceneSlotResources( @@ -2471,7 +2526,10 @@ void Device::BuildHardwareRaytracingSceneSlot( const std::vector& instance_data, const std::vector& instances_without_addresses, + const UniformBlock& uniform_block, std::size_t state_hash, + bool use_uniform_snapshot, + bool use_shared_scene_transform, bool async_submit) { if (!vk_unique_device_ || !gpu_memory_manager_ || !command_queue_ || @@ -2615,6 +2673,9 @@ void Device::BuildHardwareRaytracingSceneSlot( &tlas_range_info}; scene.state_hash = state_hash; + scene.uniform_block = uniform_block; + scene.has_uniform_block = use_uniform_snapshot; + scene.uses_shared_scene_transform = use_shared_scene_transform; if (async_submit) { auto submission = command_queue_->SubmitOneTimeAsync( @@ -2645,6 +2706,39 @@ void Device::BuildHardwareRaytracingSceneSlot( } } +UniformBlock Device::BuildCurrentRaytracingUniformBlock() const +{ + if (!level_ || !swapchain_resources_ || !active_program_info_) + { + return UniformBlock{}; + } + + const auto extent = swapchain_resources_->GetExtent(); + const bool use_world_space_raytrace_scene = + use_compute_raytracing_ && + !use_hardware_raytracing_ && + RaytraceSceneRequiresWorldSpaceBuffers(*level_); + std::string preferred_scene_root; + if (!use_world_space_raytrace_scene && + active_program_info_->program_id != NullId) + { + preferred_scene_root = + level_->GetProgramFromId(active_program_info_->program_id) + .GetTemporarySceneRoot(); + } + + const SceneState scene_state = BuildSceneState( + *level_, + frame::Logger::GetInstance(), + {extent.width, extent.height}, + elapsed_time_seconds_, + active_program_info_->material_id, + !use_compute_raytracing_, + preferred_scene_root, + use_world_space_raytrace_scene); + return MakeUniformBlock(scene_state, elapsed_time_seconds_); +} + void Device::PollHardwareRaytracingSceneBuilds() { if (!vk_unique_device_ || !command_queue_) @@ -2710,6 +2804,12 @@ void Device::UpdateHardwareRaytracingScene() const auto entries = CollectHardwareRaytraceSourceEntries( *level_, static_cast(elapsed_time_seconds_)); + const auto shared_scene_model = + RaytraceSceneRequiresWorldSpaceBuffers(*level_) + ? std::nullopt + : GetSharedRaytraceSceneTransform( + *level_, + static_cast(elapsed_time_seconds_)); if (entries.empty() || !HardwareRaytracingLayoutMatches(entries, hardware_raytracing_geometries_)) { @@ -2738,7 +2838,7 @@ void Device::UpdateHardwareRaytracingScene() const std::size_t state_hash = BuildRaytracingSourceStateHash( *level_, static_cast(elapsed_time_seconds_), - true); + !shared_scene_model.has_value()); if (active_hardware_raytracing_scene_index_) { const auto& active_scene = @@ -2766,11 +2866,15 @@ void Device::UpdateHardwareRaytracingScene() return; } + const UniformBlock uniform_block = BuildCurrentRaytracingUniformBlock(); BuildHardwareRaytracingSceneSlot( *slot_index, - BuildHardwareRaytracingInstanceData(entries), - BuildHardwareRaytracingAsInstances(entries), + BuildHardwareRaytracingInstanceData(entries, shared_scene_model), + BuildHardwareRaytracingAsInstances(entries, shared_scene_model), + uniform_block, state_hash, + !shared_scene_model.has_value(), + shared_scene_model.has_value(), true); } @@ -3213,14 +3317,39 @@ void Device::RecordCommandBuffer( { return; } - if (!buffer_resources_->GetUniformBuffer()) - { - return; - } - auto block = MakeUniformBlock( - state, elapsed_time_seconds_); - buffer_resources_->UpdateUniform( - &block, sizeof(UniformBlock)); + if (!buffer_resources_->GetUniformBuffer(current_frame_)) + { + return; + } + UniformBlock block = MakeUniformBlock( + state, elapsed_time_seconds_); + if (use_hardware_raytracing_ && + descriptor_scene_indices_[current_frame_] && + *descriptor_scene_indices_[current_frame_] < + hardware_raytracing_scene_slots_.size()) + { + const auto& scene = hardware_raytracing_scene_slots_ + [*descriptor_scene_indices_[current_frame_]]; + if (scene.uses_shared_scene_transform && level_) + { + if (const auto shared_scene_model = + GetSharedRaytraceSceneTransform( + *level_, + static_cast(elapsed_time_seconds_)); + shared_scene_model) + { + block.model = *shared_scene_model; + block.model_inv = glm::inverse(*shared_scene_model); + } + } + else if (scene.has_uniform_block) + { + block = scene.uniform_block; + } + } + buffer_resources_->UpdateUniform( + current_frame_, + &block, sizeof(UniformBlock)); }; update_uniform_buffer(scene_state); @@ -3310,13 +3439,27 @@ void Device::RecordCommandBuffer( compute_output_in_shader_read_ = false; } - if (use_raytracing_pipeline_ && - raytracing_pipeline_ && - raytracing_pipeline_layout_) - { - command_buffer.bindPipeline( - vk::PipelineBindPoint::eRayTracingKHR, - *raytracing_pipeline_); + if (use_raytracing_pipeline_ && + raytracing_pipeline_ && + raytracing_pipeline_layout_) + { + if (use_hardware_raytracing_) + { + vk::MemoryBarrier acceleration_structure_barrier( + vk::AccessFlagBits::eAccelerationStructureWriteKHR, + vk::AccessFlagBits::eAccelerationStructureReadKHR | + vk::AccessFlagBits::eShaderRead); + command_buffer.pipelineBarrier( + vk::PipelineStageFlagBits::eAccelerationStructureBuildKHR, + vk::PipelineStageFlagBits::eRayTracingShaderKHR, + {}, + acceleration_structure_barrier, + nullptr, + nullptr); + } + command_buffer.bindPipeline( + vk::PipelineBindPoint::eRayTracingKHR, + *raytracing_pipeline_); command_buffer.bindDescriptorSets( vk::PipelineBindPoint::eRayTracingKHR, *raytracing_pipeline_layout_, @@ -4285,6 +4428,12 @@ void Device::CreateHardwareRaytracingScene() const auto entries = CollectHardwareRaytraceSourceEntries( *level_, static_cast(elapsed_time_seconds_)); + const auto shared_scene_model = + RaytraceSceneRequiresWorldSpaceBuffers(*level_) + ? std::nullopt + : GetSharedRaytraceSceneTransform( + *level_, + static_cast(elapsed_time_seconds_)); if (entries.empty()) { use_hardware_raytracing_ = false; @@ -4504,12 +4653,15 @@ void Device::CreateHardwareRaytracingScene() pending_hardware_raytracing_scene_index_.reset(); BuildHardwareRaytracingSceneSlot( 0u, - instance_data, - BuildHardwareRaytracingAsInstances(entries), + BuildHardwareRaytracingInstanceData(entries, shared_scene_model), + BuildHardwareRaytracingAsInstances(entries, shared_scene_model), + BuildCurrentRaytracingUniformBlock(), BuildRaytracingSourceStateHash( *level_, static_cast(elapsed_time_seconds_), - true), + !shared_scene_model.has_value()), + !shared_scene_model.has_value(), + shared_scene_model.has_value(), false); logger_->info( @@ -4987,18 +5139,28 @@ void Device::CreateDescriptorResources() } - const BufferResource* uniform = nullptr; - if (!uniform_bindings.empty()) - { - buffer_resources_->BuildUniformBuffer( - static_cast(sizeof(UniformBlock))); - uniform = buffer_resources_->GetUniformBuffer(); - if (!uniform) - { - logger_->error("Failed to allocate Vulkan uniform buffer."); - return; - } - } + std::array uniforms = {}; + if (!uniform_bindings.empty()) + { + buffer_resources_->BuildUniformBuffers( + kMaxFramesInFlight, + static_cast(sizeof(UniformBlock))); + bool have_uniforms = true; + for (std::size_t frame = 0; frame < uniforms.size(); ++frame) + { + uniforms[frame] = buffer_resources_->GetUniformBuffer(frame); + if (!uniforms[frame]) + { + have_uniforms = false; + break; + } + } + if (!have_uniforms) + { + logger_->error("Failed to allocate Vulkan uniform buffers."); + return; + } + } std::vector texture_infos; if (!texture_ids.empty()) @@ -5211,12 +5373,12 @@ void Device::CreateDescriptorResources() &storage_infos.back()); } - if (uniform && !uniform_bindings.empty()) + if (uniforms[frame] && !uniform_bindings.empty()) { uniform_infos.emplace_back( - *uniform->buffer, + *uniforms[frame]->buffer, 0, - uniform->size); + uniforms[frame]->size); descriptor_writes.emplace_back( descriptor_set, uniform_bindings.front(), diff --git a/frame/vulkan/device.h b/frame/vulkan/device.h index 48250042..c06d7e97 100644 --- a/frame/vulkan/device.h +++ b/frame/vulkan/device.h @@ -15,10 +15,11 @@ #include "frame/camera.h" #include "frame/device_interface.h" #include "frame/texture_interface.h" -#include "frame/logger.h" -#include "frame/vulkan/buffer_resources.h" -#include "frame/vulkan/mesh_resources.h" -#include "frame/vulkan/vulkan_dispatch.h" +#include "frame/logger.h" +#include "frame/vulkan/buffer_resources.h" +#include "frame/vulkan/mesh_resources.h" +#include "frame/vulkan/scene_state.h" +#include "frame/vulkan/vulkan_dispatch.h" namespace frame::vulkan { @@ -183,8 +184,12 @@ class Device : public DeviceInterface std::size_t slot_index, const std::vector& instance_data, const std::vector& instances, + const UniformBlock& uniform_block, std::size_t state_hash, + bool use_uniform_snapshot, + bool use_shared_scene_transform, bool async_submit); + UniformBlock BuildCurrentRaytracingUniformBlock() const; vk::DescriptorSet GetDescriptorSet(std::size_t frame_index) const; void CopyBuffer(vk::Buffer src, vk::Buffer dst, vk::DeviceSize size); void TransitionImageLayout( @@ -234,9 +239,11 @@ class Device : public DeviceInterface vk::UniquePipelineLayout raytracing_pipeline_layout_; std::unique_ptr gpu_memory_manager_; std::unique_ptr command_queue_; - std::unique_ptr buffer_resources_; - std::unique_ptr mesh_resources_; + std::unique_ptr buffer_resources_; + std::unique_ptr mesh_resources_; static constexpr std::size_t kMaxFramesInFlight = 2; + static constexpr std::size_t kHardwareRaytracingSceneSlotCount = + kMaxFramesInFlight + 1; vk::UniqueDescriptorSetLayout descriptor_set_layout_; vk::UniqueDescriptorPool descriptor_pool_; std::array descriptor_sets_ = {}; @@ -353,6 +360,9 @@ class Device : public DeviceInterface vk::UniqueFence pending_fence; vk::UniqueBuffer pending_scratch_buffer; vk::UniqueDeviceMemory pending_scratch_memory; + UniformBlock uniform_block = {}; + bool has_uniform_block = false; + bool uses_shared_scene_transform = false; }; struct HardwareRaytracingInstanceData { @@ -362,9 +372,9 @@ class Device : public DeviceInterface }; private: std::vector hardware_raytracing_geometries_; - std::array + std::array hardware_raytracing_scene_slots_ = {}; - std::optional active_hardware_raytracing_scene_index_ = 0; + std::optional active_hardware_raytracing_scene_index_; std::optional pending_hardware_raytracing_scene_index_; vk::UniqueBuffer raytracing_sbt_buffer_; vk::UniqueDeviceMemory raytracing_sbt_memory_;