diff --git a/frame/opengl/buffer.cpp b/frame/opengl/buffer.cpp index 37f00cdf..f6d491f5 100644 --- a/frame/opengl/buffer.cpp +++ b/frame/opengl/buffer.cpp @@ -1,5 +1,6 @@ #include "buffer.h" +#include #include #include #include @@ -7,6 +8,56 @@ namespace frame::opengl { +namespace +{ + +bool MatchesRawBytes( + const std::vector& raw_data, + std::size_t size, + const void* data) +{ + if (raw_data.size() != size) + { + return false; + } + if (size == 0) + { + return true; + } + if (!data) + { + return std::all_of( + raw_data.begin(), + raw_data.end(), + [](std::uint8_t byte) { return byte == 0; }); + } + return std::memcmp(raw_data.data(), data, size) == 0; +} + +template +bool MatchesRawVector( + const std::vector& raw_data, + const std::vector& vector) +{ + const std::size_t size = vector.size() * sizeof(T); + if (raw_data.size() != size) + { + return false; + } + if (size == 0) + { + return true; + } + return std::memcmp(raw_data.data(), vector.data(), size) == 0; +} + +void IncrementGeneration(std::uint64_t& generation) +{ + ++generation; +} + +} // namespace + Buffer::Buffer( const BufferTypeEnum buffer_type /*= BufferTypeEnum::ARRAY_BUFFER*/, const BufferUsageEnum buffer_usage /*= BufferUsageEnum::STATIC_DRAW*/) @@ -42,6 +93,10 @@ void Buffer::BindBase(int binding) const void Buffer::Copy(const std::size_t size, const void* data /*= nullptr*/) const { + if (MatchesRawBytes(raw_data_, size, data)) + { + return; + } raw_data_.assign(size, 0); if (data && size > 0) { @@ -54,10 +109,15 @@ void Buffer::Copy(const std::size_t size, const void* data /*= nullptr*/) const data, static_cast(buffer_usage_)); UnBind(); + IncrementGeneration(generation_); } void Buffer::Copy(const std::vector& vector) const { + if (MatchesRawVector(raw_data_, vector)) + { + return; + } raw_data_.resize(vector.size() * sizeof(float)); if (!vector.empty()) { @@ -73,10 +133,15 @@ void Buffer::Copy(const std::vector& vector) const vector.data(), static_cast(buffer_usage_)); UnBind(); + IncrementGeneration(generation_); } void Buffer::Copy(const std::vector& vector) const { + if (MatchesRawVector(raw_data_, vector)) + { + return; + } raw_data_.resize(vector.size() * sizeof(unsigned int)); if (!vector.empty()) { @@ -92,10 +157,15 @@ void Buffer::Copy(const std::vector& vector) const vector.data(), static_cast(buffer_usage_)); UnBind(); + IncrementGeneration(generation_); } void Buffer::Copy(const std::vector& vector) const { + if (raw_data_ == vector) + { + return; + } raw_data_ = vector; Bind(); glBufferData( @@ -104,6 +174,7 @@ void Buffer::Copy(const std::vector& vector) const vector.data(), static_cast(buffer_usage_)); UnBind(); + IncrementGeneration(generation_); } std::size_t Buffer::GetSize() const @@ -120,6 +191,14 @@ std::size_t Buffer::GetSize() const void Buffer::Clear() const { + const bool already_zero = std::all_of( + raw_data_.begin(), + raw_data_.end(), + [](std::uint8_t byte) { return byte == 0; }); + if (already_zero) + { + return; + } std::fill(raw_data_.begin(), raw_data_.end(), 0); Bind(); glClearBufferData( @@ -129,6 +208,7 @@ void Buffer::Clear() const GL_UNSIGNED_INT, nullptr); UnBind(); + IncrementGeneration(generation_); } std::unique_ptr CreatePointBuffer( diff --git a/frame/opengl/buffer.h b/frame/opengl/buffer.h index 986c1db2..eea8bf9f 100644 --- a/frame/opengl/buffer.h +++ b/frame/opengl/buffer.h @@ -114,6 +114,10 @@ class Buffer : public BindInterface, public BufferInterface { return raw_data_; } + std::uint64_t GetGeneration() const + { + return generation_; + } public: /** @@ -162,6 +166,7 @@ class Buffer : public BindInterface, public BufferInterface std::string name_ = "buffer???"; mutable bool locked_bind_ = false; mutable std::vector raw_data_ = {}; + mutable std::uint64_t generation_ = 0; const BufferTypeEnum buffer_type_ = BufferTypeEnum::ARRAY_BUFFER; const BufferUsageEnum buffer_usage_ = BufferUsageEnum::STATIC_DRAW; unsigned int buffer_object_ = 0; diff --git a/frame/opengl/renderer.cpp b/frame/opengl/renderer.cpp index 1378da0b..32698270 100644 --- a/frame/opengl/renderer.cpp +++ b/frame/opengl/renderer.cpp @@ -3,7 +3,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -129,11 +131,6 @@ bool RaytraceSceneRequiresWorldSpaceBuffers(frame::LevelInterface& level) return false; } -bool HasRaytracingSourceMeshes(frame::LevelInterface& level) -{ - return !GetRaytracingSourceMeshMaterials(level).empty(); -} - std::vector> GetActiveTextureBindings( const MaterialInterface& material, const ProgramInterface& program) @@ -342,6 +339,93 @@ std::array ResolveRaytracingColorMultiplier( return multiplier; } +template +void HashCombine(std::size_t& seed, const T& value) +{ + seed ^= std::hash{}(value) + 0x9e3779b9u + (seed << 6u) + (seed >> 2u); +} + +void HashFloat(std::size_t& seed, float value) +{ + std::uint32_t bits = 0; + std::memcpy(&bits, &value, sizeof(bits)); + HashCombine(seed, bits); +} + +void HashColor(std::size_t& seed, const std::array& color) +{ + for (const float channel : color) + { + HashFloat(seed, channel); + } +} + +void HashMatrix(std::size_t& seed, const glm::mat4& matrix) +{ + const float* values = glm::value_ptr(matrix); + for (int i = 0; i < 16; ++i) + { + HashFloat(seed, values[i]); + } +} + +std::size_t BuildRaytracingSourceStateHash( + frame::LevelInterface& level, + double time_seconds) +{ + std::size_t state_hash = 0; + const auto source_mesh_materials = GetRaytracingSourceMeshMaterials(level); + HashCombine(state_hash, source_mesh_materials.size()); + for (const auto& [source_node_id, source_material_id] : source_mesh_materials) + { + HashCombine(state_hash, static_cast(source_node_id)); + HashCombine(state_hash, static_cast(source_material_id)); + + auto* node = + dynamic_cast(&level.GetSceneNodeFromId(source_node_id)); + if (!node) + { + continue; + } + + HashMatrix(state_hash, node->GetLocalModel(time_seconds)); + HashCombine( + state_hash, + IsTransmissiveMaterial(level, source_material_id)); + HashColor( + state_hash, + ResolveRaytracingSourceMaterialColor(level, source_material_id)); + + const auto mesh_id = node->GetLocalMesh(); + HashCombine(state_hash, static_cast(mesh_id)); + if (!mesh_id) + { + continue; + } + + const auto triangle_buffer_id = + level.GetMeshFromId(mesh_id).GetTriangleBufferId(); + HashCombine( + state_hash, + static_cast(triangle_buffer_id)); + if (!triangle_buffer_id) + { + continue; + } + + auto* triangle_buffer = dynamic_cast( + &level.GetBufferFromId(triangle_buffer_id)); + if (!triangle_buffer) + { + continue; + } + + HashCombine(state_hash, triangle_buffer->GetGeneration()); + HashCombine(state_hash, triangle_buffer->GetRawData().size()); + } + return state_hash; +} + constexpr std::size_t kRaytraceFloatsPerVertex = 12; constexpr std::size_t kRaytraceTriangleVertexStrideBytes = sizeof(float) * kRaytraceFloatsPerVertex; @@ -622,15 +706,14 @@ void Renderer::UpdateRaytraceBuffersIfNeeded(SkinnedMesh& skinned_mesh) } } - if (HasRaytracingSourceMeshes(level_)) - { - UpdateAggregateRaytraceSceneBuffers(); - } } void Renderer::UpdateAggregateRaytraceSceneBuffers() { - if (last_raytrace_scene_buffer_update_time_ == delta_time_) + const std::size_t scene_state_hash = + BuildRaytracingSourceStateHash(level_, delta_time_); + if (has_raytrace_scene_state_hash_ && + last_raytrace_scene_state_hash_ == scene_state_hash) { return; } @@ -689,7 +772,8 @@ void Renderer::UpdateAggregateRaytraceSceneBuffers() update_named_buffer(material, "BvhBufferOpaque", opaque_bvh); } - last_raytrace_scene_buffer_update_time_ = delta_time_; + last_raytrace_scene_state_hash_ = scene_state_hash; + has_raytrace_scene_state_hash_ = true; } std::optional Renderer::RenderNode( diff --git a/frame/opengl/renderer.h b/frame/opengl/renderer.h index f6262201..6c3203b6 100644 --- a/frame/opengl/renderer.h +++ b/frame/opengl/renderer.h @@ -145,7 +145,8 @@ class Renderer : public RendererInterface // Texture frame (used in render mesh). frame::proto::TextureFrame texture_frame_; bool first_render_ = true; - double last_raytrace_scene_buffer_update_time_ = -1.0; + std::size_t last_raytrace_scene_state_hash_ = 0; + bool has_raytrace_scene_state_hash_ = false; // The render callback it will be called once per mesh. RenderCallback callback_ = [](UniformCollectionInterface&, MeshInterface&, diff --git a/frame/vulkan/buffer.cpp b/frame/vulkan/buffer.cpp index 74d2f898..3030219b 100644 --- a/frame/vulkan/buffer.cpp +++ b/frame/vulkan/buffer.cpp @@ -1,16 +1,72 @@ #include "frame/vulkan/buffer.h" +#include #include #include namespace frame::vulkan { +namespace +{ + +bool MatchesRawBytes( + const std::vector& raw_data, + std::size_t size, + const void* data) +{ + if (raw_data.size() != size) + { + return false; + } + if (size == 0) + { + return true; + } + if (!data) + { + return std::all_of( + raw_data.begin(), + raw_data.end(), + [](std::uint8_t byte) { return byte == 0; }); + } + return std::memcmp(raw_data.data(), data, size) == 0; +} + +template +bool MatchesRawVector( + const std::vector& raw_data, + const std::vector& vector) +{ + const std::size_t size = vector.size() * sizeof(T); + if (raw_data.size() != size) + { + return false; + } + if (size == 0) + { + return true; + } + return std::memcmp(raw_data.data(), vector.data(), size) == 0; +} + +void IncrementGeneration(std::uint64_t& generation) +{ + ++generation; +} + +} // namespace + void Buffer::Copy(std::size_t size, const void* data) const { + if (MatchesRawBytes(data_, size, data)) + { + return; + } data_.resize(size); if (size == 0) { + IncrementGeneration(generation_); return; } if (data) @@ -21,32 +77,57 @@ void Buffer::Copy(std::size_t size, const void* data) const { std::fill(data_.begin(), data_.end(), 0); } + IncrementGeneration(generation_); } void Buffer::Copy(const std::vector& vector) const { + if (MatchesRawVector(data_, vector)) + { + return; + } CopyBytes( vector.data(), vector.size() * sizeof(float), data_); + IncrementGeneration(generation_); } void Buffer::Copy(const std::vector& vector) const { + if (MatchesRawVector(data_, vector)) + { + return; + } CopyBytes( vector.data(), vector.size() * sizeof(std::uint32_t), data_); + IncrementGeneration(generation_); } void Buffer::Copy(const std::vector& vector) const { + if (data_ == vector) + { + return; + } data_ = vector; + IncrementGeneration(generation_); } void Buffer::Clear() const { + const bool already_zero = std::all_of( + data_.begin(), + data_.end(), + [](std::uint8_t byte) { return byte == 0; }); + if (already_zero) + { + return; + } std::fill(data_.begin(), data_.end(), 0); + IncrementGeneration(generation_); } std::size_t Buffer::GetSize() const diff --git a/frame/vulkan/buffer.h b/frame/vulkan/buffer.h index f68addaf..893db627 100644 --- a/frame/vulkan/buffer.h +++ b/frame/vulkan/buffer.h @@ -30,6 +30,10 @@ class Buffer : public frame::BufferInterface { return data_; } + std::uint64_t GetGeneration() const + { + return generation_; + } std::string GetName() const override { @@ -46,6 +50,7 @@ class Buffer : public frame::BufferInterface const void* data, std::size_t size, std::vector& dst); mutable std::vector data_ = {}; + mutable std::uint64_t generation_ = 0; std::string name_ = {}; }; diff --git a/frame/vulkan/device.cpp b/frame/vulkan/device.cpp index 27182f16..c20e68d3 100644 --- a/frame/vulkan/device.cpp +++ b/frame/vulkan/device.cpp @@ -4,11 +4,12 @@ #include #include #include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -38,9 +39,10 @@ #include "frame/vulkan/texture.h" #include "frame/vulkan/texture_resources.h" #include "frame/vulkan/skinned_mesh.h" -#include "frame/proto/uniform.pb.h" -#include -#include +#include "frame/proto/uniform.pb.h" +#include +#include +#include namespace frame::vulkan { @@ -318,10 +320,10 @@ std::array ResolveRaytracingReferenceColor( return kWhite; } -std::array ResolveRaytracingColorMultiplier( - const std::array& source_color, - const std::array& reference_color) -{ +std::array ResolveRaytracingColorMultiplier( + const std::array& source_color, + const std::array& reference_color) +{ std::array multiplier = {1.0f, 1.0f, 1.0f, source_color[3]}; for (std::size_t channel = 0; channel < 3; ++channel) { @@ -331,10 +333,97 @@ std::array ResolveRaytracingColorMultiplier( value /= reference_color[channel]; } multiplier[channel] = std::clamp(value, 0.0f, 4.0f); - } - return multiplier; -} - + } + return multiplier; +} + +template +void HashCombine(std::size_t& seed, const T& value) +{ + seed ^= std::hash{}(value) + 0x9e3779b9u + (seed << 6u) + (seed >> 2u); +} + +void HashFloat(std::size_t& seed, float value) +{ + std::uint32_t bits = 0; + std::memcpy(&bits, &value, sizeof(bits)); + HashCombine(seed, bits); +} + +void HashColor(std::size_t& seed, const std::array& color) +{ + for (const float channel : color) + { + HashFloat(seed, channel); + } +} + +void HashMatrix(std::size_t& seed, const glm::mat4& matrix) +{ + const float* values = glm::value_ptr(matrix); + for (int i = 0; i < 16; ++i) + { + HashFloat(seed, values[i]); + } +} + +std::size_t BuildRaytracingSourceStateHash( + frame::LevelInterface& level, + double time_seconds) +{ + std::size_t state_hash = 0; + const auto source_mesh_materials = GetRaytracingSourceMeshMaterials(level); + HashCombine(state_hash, source_mesh_materials.size()); + for (const auto& [source_node_id, source_material_id] : source_mesh_materials) + { + HashCombine(state_hash, static_cast(source_node_id)); + HashCombine(state_hash, static_cast(source_material_id)); + + auto* node = + dynamic_cast(&level.GetSceneNodeFromId(source_node_id)); + if (!node) + { + continue; + } + + HashMatrix(state_hash, node->GetLocalModel(time_seconds)); + HashCombine( + state_hash, + IsTransmissiveMaterial(level, source_material_id)); + HashColor( + state_hash, + ResolveRaytracingSourceMaterialColor(level, source_material_id)); + + const auto mesh_id = node->GetLocalMesh(); + HashCombine(state_hash, static_cast(mesh_id)); + if (!mesh_id) + { + continue; + } + + const auto triangle_buffer_id = + level.GetMeshFromId(mesh_id).GetTriangleBufferId(); + HashCombine( + state_hash, + static_cast(triangle_buffer_id)); + if (!triangle_buffer_id) + { + continue; + } + + auto* triangle_buffer = dynamic_cast( + &level.GetBufferFromId(triangle_buffer_id)); + if (!triangle_buffer) + { + continue; + } + + HashCombine(state_hash, triangle_buffer->GetGeneration()); + HashCombine(state_hash, triangle_buffer->GetRawData().size()); + } + return state_hash; +} + bool RaytraceSceneRequiresWorldSpaceBuffers(frame::LevelInterface& level) { for (const auto& [node_id, material_id] : @@ -367,12 +456,7 @@ bool RaytraceSceneRequiresWorldSpaceBuffers(frame::LevelInterface& level) return false; } -bool HasRaytracingSourceMeshes(frame::LevelInterface& level) -{ - return !GetRaytracingSourceMeshMaterials(level).empty(); -} - -constexpr std::size_t kRaytraceFloatsPerVertex = 12; +constexpr std::size_t kRaytraceFloatsPerVertex = 12; constexpr std::size_t kRaytraceTriangleVertexStrideBytes = sizeof(float) * kRaytraceFloatsPerVertex; @@ -1015,7 +1099,8 @@ void Device::StartupFromLevelData(const frame::json::LevelData& level_data) use_raytracing_pipeline_ = false; compute_output_in_shader_read_ = false; elapsed_time_seconds_ = 0.0f; - last_raytrace_scene_buffer_update_time_ = -1.0f; + last_raytrace_scene_state_hash_ = 0; + has_raytrace_scene_state_hash_ = false; // Prefer programs configured for render passes; otherwise fall back to the // first available program. @@ -1685,7 +1770,8 @@ void Device::Cleanup() current_level_data_.reset(); level_.reset(); elapsed_time_seconds_ = 0.0f; - last_raytrace_scene_buffer_update_time_ = -1.0f; + last_raytrace_scene_state_hash_ = 0; + has_raytrace_scene_state_hash_ = false; active_program_info_.reset(); use_procedural_quad_pipeline_ = false; use_raytracing_pipeline_ = false; @@ -1795,15 +1881,17 @@ void Device::UpdateRaytraceBuffers() previous_samples[triangle_buffer_id] = sample; } - auto& triangle_buffer = dynamic_cast( - level_->GetBufferFromId(triangle_buffer_id)); - triangle_buffer.Copy(triangles); - if (buffer_resources_->UpdateStorageBuffer( - level_->GetNameFromId(triangle_buffer_id), - triangle_buffer.GetRawData())) - { - ++updated_buffer_count; - } + auto& triangle_buffer = dynamic_cast( + level_->GetBufferFromId(triangle_buffer_id)); + const auto generation_before = triangle_buffer.GetGeneration(); + triangle_buffer.Copy(triangles); + if (triangle_buffer.GetGeneration() != generation_before && + buffer_resources_->UpdateStorageBuffer( + level_->GetNameFromId(triangle_buffer_id), + triangle_buffer.GetRawData())) + { + ++updated_buffer_count; + } } } @@ -1813,22 +1901,24 @@ void Device::UpdateRaytraceBuffers() auto bvh_nodes = skinned_mesh->EvaluateRaytraceBvh(skinning_time); if (!bvh_nodes.empty()) { - auto& bvh_buffer = dynamic_cast( - level_->GetBufferFromId(bvh_buffer_id)); - bvh_buffer.Copy( - bvh_nodes.size() * sizeof(frame::BVHNode), - bvh_nodes.data()); - if (buffer_resources_->UpdateStorageBuffer( - level_->GetNameFromId(bvh_buffer_id), - bvh_buffer.GetRawData())) - { - ++updated_buffer_count; - } + auto& bvh_buffer = dynamic_cast( + level_->GetBufferFromId(bvh_buffer_id)); + const auto generation_before = bvh_buffer.GetGeneration(); + bvh_buffer.Copy( + bvh_nodes.size() * sizeof(frame::BVHNode), + bvh_nodes.data()); + if (bvh_buffer.GetGeneration() != generation_before && + buffer_resources_->UpdateStorageBuffer( + level_->GetNameFromId(bvh_buffer_id), + bvh_buffer.GetRawData())) + { + ++updated_buffer_count; + } } } } bool updated_aggregate_scene = false; - if (HasRaytracingSourceMeshes(*level_)) + if (RaytraceSceneRequiresWorldSpaceBuffers(*level_)) { updated_aggregate_scene = UpdateAggregateRaytracingSceneBuffers( !use_hardware_raytracing_); @@ -1867,10 +1957,15 @@ bool Device::UpdateAggregateRaytracingSceneBuffers(bool build_software_bvh) { return false; } - if (last_raytrace_scene_buffer_update_time_ == elapsed_time_seconds_) - { - return false; - } + const std::size_t scene_state_hash = + BuildRaytracingSourceStateHash( + *level_, + static_cast(elapsed_time_seconds_)); + if (has_raytrace_scene_state_hash_ && + last_raytrace_scene_state_hash_ == scene_state_hash) + { + return false; + } auto update_buffer = [&](const char* inner_name, const std::vector& bytes) { @@ -1933,9 +2028,10 @@ bool Device::UpdateAggregateRaytracingSceneBuffers(bool build_software_bvh) BuildAggregateBvhBytes(opaque_triangles)); } - last_raytrace_scene_buffer_update_time_ = elapsed_time_seconds_; - return updated; -} + last_raytrace_scene_state_hash_ = scene_state_hash; + has_raytrace_scene_state_hash_ = true; + return updated; +} void Device::UpdateHardwareRaytracingScene() { @@ -2474,7 +2570,7 @@ void Device::RecordCommandBuffer( const bool use_world_space_raytrace_scene = level_ && (use_compute_raytracing_ || use_raytracing_pipeline_) && - HasRaytracingSourceMeshes(*level_); + RaytraceSceneRequiresWorldSpaceBuffers(*level_); std::string preferred_scene_root; if (!use_world_space_raytrace_scene && level_ && diff --git a/frame/vulkan/device.h b/frame/vulkan/device.h index 7999a128..5e148022 100644 --- a/frame/vulkan/device.h +++ b/frame/vulkan/device.h @@ -265,13 +265,14 @@ class Device : public DeviceInterface std::unordered_map buffer_ids_by_inner; }; - std::optional current_level_data_; - std::optional active_program_info_; - bool use_procedural_quad_pipeline_ = false; - float elapsed_time_seconds_ = 0.0f; - float last_raytrace_scene_buffer_update_time_ = -1.0f; - vk::ShaderStageFlags push_constant_stages_ = {}; - std::uint32_t push_constant_size_ = 0; + std::optional current_level_data_; + std::optional active_program_info_; + bool use_procedural_quad_pipeline_ = false; + float elapsed_time_seconds_ = 0.0f; + std::size_t last_raytrace_scene_state_hash_ = 0; + bool has_raytrace_scene_state_hash_ = false; + vk::ShaderStageFlags push_constant_stages_ = {}; + std::uint32_t push_constant_size_ = 0; GuiRenderCallback gui_render_callback_; vk::PhysicalDeviceBufferDeviceAddressFeatures buffer_device_address_features_ = {};