From a5c0372631bf49522cd9b800a454393e6278ab6f Mon Sep 17 00:00:00 2001 From: Zhirnokleev Konstantin Date: Sun, 15 Mar 2026 04:37:08 +0400 Subject: [PATCH 1/6] Render: EffectContext: Add stub EffectContext and factory method Add a minimal stub EffectContext class (Phase 9 placeholder) that holds an Effect pointer. Add CreateEffectContext(Effect*) factory method on CommandEncoder as the entry point for callers. No binding logic yet -- Set*/Bind/snapshot path follows in Phase 9. --- src/libs/render/CMakeLists.txt | 1 + src/libs/render/CommandEncoder.hpp | 3 +++ src/libs/render/EffectContext.hpp | 19 +++++++++++++++++++ 3 files changed, 23 insertions(+) create mode 100644 src/libs/render/EffectContext.hpp diff --git a/src/libs/render/CMakeLists.txt b/src/libs/render/CMakeLists.txt index 48830bff..d5d4af34 100644 --- a/src/libs/render/CMakeLists.txt +++ b/src/libs/render/CMakeLists.txt @@ -11,6 +11,7 @@ set(SRC RenderTarget.cpp Effect.hpp Effect.cpp + EffectContext.hpp EffectManager.cpp EffectManager.hpp BindingBlockLayout.cpp diff --git a/src/libs/render/CommandEncoder.hpp b/src/libs/render/CommandEncoder.hpp index 94055a59..10df75ef 100644 --- a/src/libs/render/CommandEncoder.hpp +++ b/src/libs/render/CommandEncoder.hpp @@ -6,6 +6,7 @@ #include "math/ForwardDeclarations.hpp" #include "render/Effect.hpp" +#include "render/EffectContext.hpp" #include "common/NonCopyableMovable.hpp" namespace RR::Render @@ -18,6 +19,8 @@ namespace RR::Render public: RenderPassEncoder BeginRenderPass(const GAPI::RenderPassDesc& renderPass); + EffectContext CreateEffectContext(Effect* effect) { return EffectContext(effect); } + void Finish() { ASSERT_MSG(state == State::Open, "CommandEncoder was not started"); diff --git a/src/libs/render/EffectContext.hpp b/src/libs/render/EffectContext.hpp new file mode 100644 index 00000000..91e80dc7 --- /dev/null +++ b/src/libs/render/EffectContext.hpp @@ -0,0 +1,19 @@ +#pragma once + +namespace RR::Render +{ + class Effect; + + // Staging object for one draw or dispatch call (Phase 9). + // Stack-allocatable. Snapshots state into the command list at Draw/Dispatch. + class EffectContext + { + public: + explicit EffectContext(Effect* effect) : effect(effect) { } + + Effect* GetEffect() const { return effect; } + + private: + Effect* effect = nullptr; + }; +} From 82c7f25f33e82ecef1016a210411509915eb75f2 Mon Sep 17 00:00:00 2001 From: Zhirnokleev Konstantin Date: Sun, 15 Mar 2026 05:27:15 +0400 Subject: [PATCH 2/6] Render: EffectManager: Serialize rootBindingGroupIndex per pass Asset::PassDesc lacked the root binding group index that the compiler already computes (rootBindingGroupIndex in RR::PassDesc). The field was silently dropped during serialization and never restored at load time, making the default parameter block unidentifiable at runtime. Add rootBindingGroupIndex to Asset::PassDesc, write it in EffectSerializer::AddEffect, and read it back in EffectLibrary::Load. Bump asset format VERSION 1 -> 2 to reject stale binaries. --- src/libs/effect_library/EffectFormat.hpp | 3 ++- src/libs/effect_library/EffectLibrary.cpp | 1 + src/tools/shader_compiler/EffectSerializer.cpp | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libs/effect_library/EffectFormat.hpp b/src/libs/effect_library/EffectFormat.hpp index a91b7f0f..acf72180 100644 --- a/src/libs/effect_library/EffectFormat.hpp +++ b/src/libs/effect_library/EffectFormat.hpp @@ -164,7 +164,7 @@ namespace RR::EffectLibrary struct Header { static constexpr uint32_t MAGIC = 0x4C584652; - static constexpr uint32_t VERSION = 1; + static constexpr uint32_t VERSION = 2; uint32_t magic; uint32_t version; uint32_t stringsSectionSize; @@ -202,6 +202,7 @@ namespace RR::EffectLibrary GAPI::RasterizerDesc rasterizerDesc; GAPI::DepthStencilDesc depthStencilDesc; GAPI::ShaderStageMask shaderStages; + uint32_t rootBindingGroupIndex; // List of shader indexes based on shaderStages mask }; diff --git a/src/libs/effect_library/EffectLibrary.cpp b/src/libs/effect_library/EffectLibrary.cpp index 811434d0..43ebc768 100644 --- a/src/libs/effect_library/EffectLibrary.cpp +++ b/src/libs/effect_library/EffectLibrary.cpp @@ -315,6 +315,7 @@ namespace RR::EffectLibrary passDesc.rasterizerDesc = assetPassDesc.rasterizerDesc; passDesc.depthStencilDesc = assetPassDesc.depthStencilDesc; passDesc.blendDesc = assetPassDesc.blendDesc; + passDesc.rootBindingGroupIndex = assetPassDesc.rootBindingGroupIndex; eastl::fixed_vector shaderStages; diff --git a/src/tools/shader_compiler/EffectSerializer.cpp b/src/tools/shader_compiler/EffectSerializer.cpp index ef101798..f35bd8a2 100644 --- a/src/tools/shader_compiler/EffectSerializer.cpp +++ b/src/tools/shader_compiler/EffectSerializer.cpp @@ -243,6 +243,7 @@ namespace RR passDesc.depthStencilDesc = pass.depthStencilDesc; passDesc.blendDesc = pass.blendDesc; passDesc.shaderStages = GAPI::ShaderStageMask::None; + passDesc.rootBindingGroupIndex = pass.rootBindingGroupIndex; eastl::fixed_vector shaderIndexes; for (uint32_t i = 0; i < pass.shaderIndexes.size(); i++) From 2e85228c1ac77cff55f101153799888085811017 Mon Sep 17 00:00:00 2001 From: Zhirnokleev Konstantin Date: Sun, 15 Mar 2026 15:59:29 +0400 Subject: [PATCH 3/6] GAPI: BindingGroupLayout: Absorb reflection metadata from BindingBlockLayout BindingBlockLayout was a Render-layer wrapper that shadowed the same binding-group concept as GAPI::BindingGroupLayout, adding reflection fields (FieldDesc, ResourceSlotDesc, hash maps) alongside the GPU handle. The separation provided no architectural benefit: the GPU handle is already hidden behind the pimpl, and the Render layer is the natural consumer of both sides. Merge FieldDesc, ResourceSlotDesc, BindingGroupReflectionDesc and the associated O(1) lookup methods into GAPI::BindingGroupLayout. Add BindingGroupLayout.cpp for non-trivial method bodies. Remove Render::BindingBlockLayout entirely. Collapse the two init loops in EffectManager::Init into one and rename blockLayoutMap to layoutMap. --- src/libs/gapi/BindingGroupLayout.cpp | 37 +++++++++++ src/libs/gapi/BindingGroupLayout.hpp | 71 ++++++++++++++++++++- src/libs/gapi/CMakeLists.txt | 3 +- src/libs/render/BindingBlockLayout.cpp | 83 ------------------------- src/libs/render/BindingBlockLayout.hpp | 85 -------------------------- src/libs/render/CMakeLists.txt | 2 - src/libs/render/EffectManager.cpp | 77 ++++++++++++++++++++--- src/libs/render/EffectManager.hpp | 7 +-- 8 files changed, 177 insertions(+), 188 deletions(-) create mode 100644 src/libs/gapi/BindingGroupLayout.cpp delete mode 100644 src/libs/render/BindingBlockLayout.cpp delete mode 100644 src/libs/render/BindingBlockLayout.hpp diff --git a/src/libs/gapi/BindingGroupLayout.cpp b/src/libs/gapi/BindingGroupLayout.cpp new file mode 100644 index 00000000..6f51a740 --- /dev/null +++ b/src/libs/gapi/BindingGroupLayout.cpp @@ -0,0 +1,37 @@ +#include "BindingGroupLayout.hpp" + +namespace RR::GAPI +{ + void BindingGroupLayout::InitReflection(const BindingGroupReflectionDesc& desc) + { + bindingSpace = desc.bindingSpace; + uniformBufferSize = desc.uniformBufferSize; + uniformCbvSlot = desc.uniformCbvSlot; + + for (const auto& f : desc.fields) + { + const uint32_t index = static_cast(fields.size()); + fields.push_back(f); + fieldMap[f.nameHash] = index; + } + + for (const auto& r : desc.resources) + { + const uint32_t index = static_cast(resourceSlots.size()); + resourceSlots.push_back(r); + resourceMap[r.nameHash] = index; + } + } + + uint32_t BindingGroupLayout::FindFieldIndex(Common::HashType nameHash) const + { + const auto it = fieldMap.find(nameHash); + return it != fieldMap.end() ? it->second : INVALID_SLOT; + } + + uint32_t BindingGroupLayout::FindResourceSlotIndex(Common::HashType nameHash) const + { + const auto it = resourceMap.find(nameHash); + return it != resourceMap.end() ? it->second : INVALID_SLOT; + } +} diff --git a/src/libs/gapi/BindingGroupLayout.hpp b/src/libs/gapi/BindingGroupLayout.hpp index 09d00125..87244bbb 100644 --- a/src/libs/gapi/BindingGroupLayout.hpp +++ b/src/libs/gapi/BindingGroupLayout.hpp @@ -5,6 +5,11 @@ #include "gapi/Shader.hpp" #include "gapi/Limits.hpp" +#include "common/hashing/Hash.hpp" + +#include "absl/container/flat_hash_map.h" + +#include #include namespace RR::GAPI @@ -37,6 +42,32 @@ namespace RR::GAPI eastl::span elements; }; + // Reflection: a single uniform field within a CBV + struct FieldDesc + { + Common::HashType nameHash; + uint16_t offset; // byte offset in uniform buffer + uint16_t size; // byte size of field + }; + + // Reflection: a single resource slot (SRV, UAV, sampler) + struct ResourceSlotDesc + { + Common::HashType nameHash; + uint16_t binding; // binding index in bind group + uint16_t slotIndex; // dense index in EffectContext::resources[] + BindingType type; + }; + + struct BindingGroupReflectionDesc + { + uint32_t bindingSpace = 0; + eastl::span fields; + eastl::span resources; + uint32_t uniformBufferSize = 0; + uint32_t uniformCbvSlot = ~0u; + }; + class IBindingGroupLayout { public: @@ -46,12 +77,48 @@ namespace RR::GAPI class BindingGroupLayout final : public Resource { public: + static constexpr uint32_t INVALID_SLOT = ~0u; + BindingGroupLayout(const std::string& name) : Resource(Type::BindingGroupLayout, name) { } - }; + // Populate reflection metadata (call after GPU init). + void InitReflection(const BindingGroupReflectionDesc& desc); -} + uint32_t GetBindingSpace() const { return bindingSpace; } + uint32_t GetUniformBufferSize() const { return uniformBufferSize; } + uint32_t GetUniformCbvSlot() const { return uniformCbvSlot; } + + uint32_t GetFieldCount() const { return static_cast(fields.size()); } + const FieldDesc& GetField(uint32_t index) const + { + ASSERT(index < fields.size()); + return fields[index]; + } + + uint32_t GetResourceSlotCount() const { return static_cast(resourceSlots.size()); } + const ResourceSlotDesc& GetResourceSlot(uint32_t index) const + { + ASSERT(index < resourceSlots.size()); + return resourceSlots[index]; + } + // O(1) lookup by precomputed name hash. Cache the index for hot paths. + uint32_t FindFieldIndex(Common::HashType nameHash) const; + uint32_t FindResourceSlotIndex(Common::HashType nameHash) const; + + private: + uint32_t bindingSpace = 0; + uint32_t uniformBufferSize = 0; + uint32_t uniformCbvSlot = INVALID_SLOT; + + eastl::fixed_vector fields; + eastl::fixed_vector resourceSlots; + + absl::flat_hash_map fieldMap; // nameHash -> field index + absl::flat_hash_map resourceMap; // nameHash -> slot index + }; + +} diff --git a/src/libs/gapi/CMakeLists.txt b/src/libs/gapi/CMakeLists.txt index 9b02880d..ea7c3ac3 100644 --- a/src/libs/gapi/CMakeLists.txt +++ b/src/libs/gapi/CMakeLists.txt @@ -3,6 +3,7 @@ project (gapi) set(SRC Buffer.cpp Buffer.hpp + BindingGroupLayout.cpp BindingGroupLayout.hpp BindingGroup.hpp CommandList.hpp @@ -37,4 +38,4 @@ source_group( "" FILES ${SRC} ${COMMANDS} ) add_library(${PROJECT_NAME} ${SRC}) set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "libs") target_include_directories(${PROJECT_NAME} PRIVATE "..") -target_link_libraries(${PROJECT_NAME} PRIVATE common) \ No newline at end of file +target_link_libraries(${PROJECT_NAME} PRIVATE common absl::flat_hash_map) \ No newline at end of file diff --git a/src/libs/render/BindingBlockLayout.cpp b/src/libs/render/BindingBlockLayout.cpp deleted file mode 100644 index c05d6454..00000000 --- a/src/libs/render/BindingBlockLayout.cpp +++ /dev/null @@ -1,83 +0,0 @@ -#include "BindingBlockLayout.hpp" - -#include "effect_library/EffectLibrary.hpp" - -#include - -namespace RR::Render -{ - void BindingBlockLayout::InitFromReflection(const EffectLibrary::BindingGroupReflection& group) - { - name = group.name; - bindingSpace = group.bindingSpace; - - uint16_t resourceSlotIndex = 0; - - for (const auto& res : group.resources) - { - switch (res.type) - { - case GAPI::BindingType::ConstantBuffer: - { - // First CBV becomes the uniform buffer slot - if (uniformCbvSlot == INVALID_SLOT) - uniformCbvSlot = res.binding; - - for (const auto& uniform : res.uniformFields) - { - ASSERT(uniform.offset <= std::numeric_limits::max()); - ASSERT(uniform.size <= std::numeric_limits::max()); - FieldDesc field; - field.nameHash = Common::Hash(uniform.name); - field.offset = static_cast(uniform.offset); - field.size = static_cast(uniform.size); - - const uint32_t fieldIndex = static_cast(fields.size()); - fields.push_back(field); - fieldMap[field.nameHash] = fieldIndex; - - // Track maximum extent for buffer size - const uint32_t extent = uniform.offset + uniform.size; - if (extent > uniformBufferSize) - uniformBufferSize = extent; - } - break; - } - case GAPI::BindingType::TextureSRV: - case GAPI::BindingType::TextureUAV: - case GAPI::BindingType::BufferSRV: - case GAPI::BindingType::BufferUAV: - case GAPI::BindingType::Sampler: - { - ResourceSlotDesc slot; - slot.nameHash = Common::Hash(res.name); - slot.binding = static_cast(res.binding); - slot.slotIndex = resourceSlotIndex++; - slot.type = res.type; - - const uint32_t index = static_cast(resourceSlots.size()); - resourceSlots.push_back(slot); - resourceMap[slot.nameHash] = index; - break; - } - default: - { - ASSERT_MSG(false, "Unknown binding type"); - break; - } - } - } - } - - uint32_t BindingBlockLayout::FindFieldIndex(Common::HashType nameHash) const - { - auto it = fieldMap.find(nameHash); - return it != fieldMap.end() ? it->second : INVALID_SLOT; - } - - uint32_t BindingBlockLayout::FindResourceSlotIndex(Common::HashType nameHash) const - { - auto it = resourceMap.find(nameHash); - return it != resourceMap.end() ? it->second : INVALID_SLOT; - } -} diff --git a/src/libs/render/BindingBlockLayout.hpp b/src/libs/render/BindingBlockLayout.hpp deleted file mode 100644 index bf99cd93..00000000 --- a/src/libs/render/BindingBlockLayout.hpp +++ /dev/null @@ -1,85 +0,0 @@ -#pragma once - -#include "gapi/BindingGroupLayout.hpp" -#include "gapi/Limits.hpp" - -#include "absl/container/flat_hash_map.h" - -#include "common/hashing/Hash.hpp" - -namespace RR::EffectLibrary -{ - struct BindingGroupReflection; - struct ResourceReflection; -} - -namespace RR::Render -{ - // Describes a single uniform field within a CBV - struct FieldDesc - { - Common::HashType nameHash; // precomputed hash for fast lookup - uint16_t offset; // byte offset in uniform buffer - uint16_t size; // byte size of field - }; - - // Describes a single resource slot (SRV, UAV, sampler) - struct ResourceSlotDesc - { - Common::HashType nameHash; // precomputed hash - uint16_t binding; // binding index in bind group - uint16_t slotIndex; // index in BindingBlock::resources[] - GAPI::BindingType type; // TextureSRV, BufferSRV, Sampler, etc. - }; - - // Immutable layout built once from EffectLibrary reflection data. - // Shared (read-only) across all BindingBlock instances with same layout. - class BindingBlockLayout - { - public: - static constexpr uint32_t INVALID_SLOT = ~0u; - - BindingBlockLayout() = default; - - void InitFromReflection(const EffectLibrary::BindingGroupReflection& group); - - const char* GetName() const { return name; } - uint32_t GetBindingSpace() const { return bindingSpace; } - uint32_t GetUniformBufferSize() const { return uniformBufferSize; } - uint32_t GetUniformCbvSlot() const { return uniformCbvSlot; } - - uint32_t GetFieldCount() const { return static_cast(fields.size()); } - const FieldDesc& GetField(uint32_t index) const - { - ASSERT(index < fields.size()); - return fields[index]; - } - - uint32_t GetResourceSlotCount() const { return static_cast(resourceSlots.size()); } - const ResourceSlotDesc& GetResourceSlot(uint32_t index) const - { - ASSERT(index < resourceSlots.size()); - return resourceSlots[index]; - } - - // O(1) hash-map lookup by name hash. For hot paths, cache the index. - uint32_t FindFieldIndex(Common::HashType nameHash) const; - uint32_t FindResourceSlotIndex(Common::HashType nameHash) const; - - const GAPI::BindingGroupLayout* GetGapiLayout() const { return gapiLayout; } - void SetGapiLayout(const GAPI::BindingGroupLayout* layout) { gapiLayout = layout; } - - private: - const char* name = nullptr; - uint32_t bindingSpace = 0; - uint32_t uniformBufferSize = 0; // total CBV size in bytes (0 if none) - uint32_t uniformCbvSlot = INVALID_SLOT; // binding index of default CBV - const GAPI::BindingGroupLayout* gapiLayout = nullptr; - - eastl::fixed_vector fields; - eastl::fixed_vector resourceSlots; - - absl::flat_hash_map fieldMap; // nameHash -> field index - absl::flat_hash_map resourceMap; // nameHash -> resource slot index - }; -} diff --git a/src/libs/render/CMakeLists.txt b/src/libs/render/CMakeLists.txt index d5d4af34..d30c67da 100644 --- a/src/libs/render/CMakeLists.txt +++ b/src/libs/render/CMakeLists.txt @@ -14,8 +14,6 @@ set(SRC EffectContext.hpp EffectManager.cpp EffectManager.hpp - BindingBlockLayout.cpp - BindingBlockLayout.hpp Submission.cpp Submission.hpp ) diff --git a/src/libs/render/EffectManager.cpp b/src/libs/render/EffectManager.cpp index c79c7037..06f30fdc 100644 --- a/src/libs/render/EffectManager.cpp +++ b/src/libs/render/EffectManager.cpp @@ -15,6 +15,8 @@ #include "common/Result.hpp" #include "common/hashing/Hash.hpp" +#include + namespace RR::Render { EffectManager::EffectManager() {}; @@ -47,6 +49,7 @@ namespace RR::Render { const auto& group = effectLibrary->GetBindingGroupReflection(i); + // --- GPU binding layout --- eastl::fixed_vector elements; for (const auto& res : group.resources) { @@ -63,17 +66,71 @@ namespace RR::Render } const GAPI::BindingGroupLayoutDesc layoutDesc { elements }; - bindingGroupLayouts.emplace_back(deviceContext.CreateBindingGroupLayout(layoutDesc, group.name)); - } + auto& layout = bindingGroupLayouts.emplace_back( + deviceContext.CreateBindingGroupLayout(layoutDesc, group.name)); - blockLayouts.resize(effectLibrary->GetBindingGroupCount()); - for (size_t i = 0; i < effectLibrary->GetBindingGroupCount(); i++) - { - const auto& group = effectLibrary->GetBindingGroupReflection(i); - blockLayouts[i].InitFromReflection(group); - blockLayouts[i].SetGapiLayout(bindingGroupLayouts[i].get()); + // --- Reflection metadata --- + eastl::fixed_vector fields; + eastl::fixed_vector resources; + uint32_t uniformBufferSize = 0; + uint32_t uniformCbvSlot = GAPI::BindingGroupLayout::INVALID_SLOT; + uint16_t resourceSlotIndex = 0; + + for (const auto& res : group.resources) + { + switch (res.type) + { + case GAPI::BindingType::ConstantBuffer: + { + if (uniformCbvSlot == GAPI::BindingGroupLayout::INVALID_SLOT) + uniformCbvSlot = res.binding; + + for (const auto& uniform : res.uniformFields) + { + ASSERT(uniform.offset <= std::numeric_limits::max()); + ASSERT(uniform.size <= std::numeric_limits::max()); + + GAPI::FieldDesc field; + field.nameHash = Common::Hash(uniform.name); + field.offset = static_cast(uniform.offset); + field.size = static_cast(uniform.size); + fields.push_back(field); + + const uint32_t extent = uniform.offset + uniform.size; + if (extent > uniformBufferSize) + uniformBufferSize = extent; + } + break; + } + case GAPI::BindingType::TextureSRV: + case GAPI::BindingType::TextureUAV: + case GAPI::BindingType::BufferSRV: + case GAPI::BindingType::BufferUAV: + case GAPI::BindingType::Sampler: + { + GAPI::ResourceSlotDesc slot; + slot.nameHash = Common::Hash(res.name); + slot.binding = static_cast(res.binding); + slot.slotIndex = resourceSlotIndex++; + slot.type = res.type; + resources.push_back(slot); + break; + } + default: + ASSERT_MSG(false, "Unknown binding type"); + break; + } + } + + GAPI::BindingGroupReflectionDesc reflDesc; + reflDesc.bindingSpace = group.bindingSpace; + reflDesc.fields = fields; + reflDesc.resources = resources; + reflDesc.uniformBufferSize = uniformBufferSize; + reflDesc.uniformCbvSlot = uniformCbvSlot; + layout->InitReflection(reflDesc); - blockLayoutMap[Common::Hash(group.name)] = static_cast(i); + layoutMap[Common::Hash(group.name)] = static_cast(i); } return Common::RResult::Ok; @@ -120,4 +177,4 @@ namespace RR::Render return effect; } -} \ No newline at end of file +} diff --git a/src/libs/render/EffectManager.hpp b/src/libs/render/EffectManager.hpp index 7a66ac5f..ed898ac9 100644 --- a/src/libs/render/EffectManager.hpp +++ b/src/libs/render/EffectManager.hpp @@ -1,7 +1,5 @@ #pragma once -#include "BindingBlockLayout.hpp" - #include "common/Singleton.hpp" #include "common/hashing/Hash.hpp" @@ -40,7 +38,6 @@ namespace RR::Render eastl::unique_ptr effectLibrary; eastl::vector> shaders; eastl::vector> bindingGroupLayouts; - eastl::vector blockLayouts; - absl::flat_hash_map blockLayoutMap; // nameHash -> index + absl::flat_hash_map layoutMap; // nameHash -> index }; -} \ No newline at end of file +} From e22416d5d88f7c60cdc5769da2f2f0ed255d635b Mon Sep 17 00:00:00 2001 From: Zhirnokleev Konstantin Date: Sun, 15 Mar 2026 15:59:46 +0400 Subject: [PATCH 4/6] Render: EffectManager: Clang format this effect manager --- src/libs/render/EffectManager.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/libs/render/EffectManager.cpp b/src/libs/render/EffectManager.cpp index 06f30fdc..284ee3ac 100644 --- a/src/libs/render/EffectManager.cpp +++ b/src/libs/render/EffectManager.cpp @@ -3,14 +3,14 @@ #include "render/DeviceContext.hpp" #include "render/Effect.hpp" -#include "effect_library/EffectLibrary.hpp" #include "effect_library/EffectFormat.hpp" +#include "effect_library/EffectLibrary.hpp" #include "gapi/BindingGroupLayout.hpp" #include "gapi/Limits.hpp" -#include #include "gapi/GpuResource.hpp" +#include #include "common/Result.hpp" #include "common/hashing/Hash.hpp" @@ -19,8 +19,8 @@ namespace RR::Render { - EffectManager::EffectManager() {}; - EffectManager::~EffectManager() {}; + EffectManager::EffectManager() { }; + EffectManager::~EffectManager() { }; Common::RResult EffectManager::Init(std::string_view path) { @@ -65,7 +65,7 @@ namespace RR::Render elements.push_back(element); } - const GAPI::BindingGroupLayoutDesc layoutDesc { elements }; + const GAPI::BindingGroupLayoutDesc layoutDesc {elements}; auto& layout = bindingGroupLayouts.emplace_back( deviceContext.CreateBindingGroupLayout(layoutDesc, group.name)); @@ -145,14 +145,14 @@ namespace RR::Render EffectLibrary::EffectDesc libraryEffectDesc; // TODO, Could be replace with const expr hash. Todo replace with hashed string - if(!effectLibrary->GetEffectDesc(Common::Hash(name), libraryEffectDesc)) + if (!effectLibrary->GetEffectDesc(Common::Hash(name), libraryEffectDesc)) { LOG_ERROR("Failed to get effect desc for effect: {}", name); return nullptr; } Render::EffectDesc effectDesc; - for(auto& pass : libraryEffectDesc.passes) + for (auto& pass : libraryEffectDesc.passes) { Render::EffectDesc::PassDesc passDesc; passDesc.name = pass.name; @@ -161,7 +161,7 @@ namespace RR::Render passDesc.blendDesc = pass.blendDesc; static_assert(eastl::tuple_size::value == eastl::tuple_size::value); - for(uint32_t i = 0; i < pass.shaderIndexes.size(); i++) + for (uint32_t i = 0; i < pass.shaderIndexes.size(); i++) { const auto shaderIndex = pass.shaderIndexes[i]; if (shaderIndex == EffectLibrary::Asset::INVALID_INDEX) From 3c27fa077271efbab0425193696ed4dea7a7016b Mon Sep 17 00:00:00 2001 From: Zhirnokleev Konstantin Date: Sun, 15 Mar 2026 16:33:24 +0400 Subject: [PATCH 5/6] GAPI: BindingGroupLayout: Replace fieldMap/resourceMap with vector_map For small, read-only lookup tables built once at init time, eastl::vector_map backed by fixed_vector gives cache-friendly binary search with inline storage and no heap allocation. Remove absl dependency from the gapi target. --- src/libs/gapi/BindingGroupLayout.hpp | 24 ++++++++++++++++-------- src/libs/gapi/CMakeLists.txt | 2 +- src/libs/render/EffectManager.hpp | 14 +++++++++++--- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/libs/gapi/BindingGroupLayout.hpp b/src/libs/gapi/BindingGroupLayout.hpp index 87244bbb..9113095b 100644 --- a/src/libs/gapi/BindingGroupLayout.hpp +++ b/src/libs/gapi/BindingGroupLayout.hpp @@ -7,10 +7,9 @@ #include "common/hashing/Hash.hpp" -#include "absl/container/flat_hash_map.h" - #include #include +#include namespace RR::GAPI { @@ -61,7 +60,7 @@ namespace RR::GAPI struct BindingGroupReflectionDesc { - uint32_t bindingSpace = 0; + uint32_t bindingSpace = 0; eastl::span fields; eastl::span resources; uint32_t uniformBufferSize = 0; @@ -105,20 +104,29 @@ namespace RR::GAPI return resourceSlots[index]; } - // O(1) lookup by precomputed name hash. Cache the index for hot paths. + // O(log n) lookup by precomputed name hash. Cache the index for hot paths. uint32_t FindFieldIndex(Common::HashType nameHash) const; uint32_t FindResourceSlotIndex(Common::HashType nameHash) const; private: - uint32_t bindingSpace = 0; + using HashToIndex = eastl::pair; + + template + using Lookup = eastl::vector_map< + Common::HashType, uint32_t, + eastl::less, + EASTLAllocatorType, + eastl::fixed_vector>; + + uint32_t bindingSpace = 0; uint32_t uniformBufferSize = 0; uint32_t uniformCbvSlot = INVALID_SLOT; - eastl::fixed_vector fields; + eastl::fixed_vector fields; eastl::fixed_vector resourceSlots; - absl::flat_hash_map fieldMap; // nameHash -> field index - absl::flat_hash_map resourceMap; // nameHash -> slot index + Lookup<16> fieldMap; // nameHash -> field index + Lookup resourceMap; // nameHash -> slot index }; } diff --git a/src/libs/gapi/CMakeLists.txt b/src/libs/gapi/CMakeLists.txt index ea7c3ac3..b7494520 100644 --- a/src/libs/gapi/CMakeLists.txt +++ b/src/libs/gapi/CMakeLists.txt @@ -38,4 +38,4 @@ source_group( "" FILES ${SRC} ${COMMANDS} ) add_library(${PROJECT_NAME} ${SRC}) set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "libs") target_include_directories(${PROJECT_NAME} PRIVATE "..") -target_link_libraries(${PROJECT_NAME} PRIVATE common absl::flat_hash_map) \ No newline at end of file +target_link_libraries(${PROJECT_NAME} PRIVATE common) \ No newline at end of file diff --git a/src/libs/render/EffectManager.hpp b/src/libs/render/EffectManager.hpp index ed898ac9..6072dade 100644 --- a/src/libs/render/EffectManager.hpp +++ b/src/libs/render/EffectManager.hpp @@ -3,10 +3,11 @@ #include "common/Singleton.hpp" #include "common/hashing/Hash.hpp" -#include "absl/container/flat_hash_map.h" - #include "gapi/ForwardDeclarations.hpp" +#include +#include + #include namespace RR::EffectLibrary @@ -35,9 +36,16 @@ namespace RR::Render size_t GetShaderCount() const { return shaders.size(); } private: + using HashToIndex = eastl::pair; + using LayoutLookup = eastl::vector_map< + Common::HashType, uint32_t, + eastl::less, + EASTLAllocatorType, + eastl::fixed_vector>; + eastl::unique_ptr effectLibrary; eastl::vector> shaders; eastl::vector> bindingGroupLayouts; - absl::flat_hash_map layoutMap; // nameHash -> index + LayoutLookup layoutMap; // nameHash -> index }; } From 1bce0cc16cd99b09f9a376e0a7798c78e2e978d5 Mon Sep 17 00:00:00 2001 From: Zhirnokleev Konstantin Date: Sun, 15 Mar 2026 17:25:15 +0400 Subject: [PATCH 6/6] Common: Add PrehashedHasher; Render: EffectManager: layoutMap stores BGL* MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add Common::PrehashedHasher — a pass-through hasher that avoids re-hashing an already-hashed key. Switch EffectManager::layoutMap from vector_map to flat_hash_map, eliminating the index indirection (hash→index→object becomes hash→pointer directly). --- src/libs/common/hashing/Hash.hpp | 7 +++++++ src/libs/render/EffectManager.cpp | 2 +- src/libs/render/EffectManager.hpp | 13 +++---------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/libs/common/hashing/Hash.hpp b/src/libs/common/hashing/Hash.hpp index 1d6c4e40..f26af39e 100644 --- a/src/libs/common/hashing/Hash.hpp +++ b/src/libs/common/hashing/Hash.hpp @@ -107,4 +107,11 @@ namespace RR::Common { return ConstexprHash(str, len); } + + // Pass-through hasher for values that are already a hash. + // Avoids double-hashing when using HashType as a map key. + struct PrehashedHasher + { + size_t operator()(HashType val) const noexcept { return val; } + }; } \ No newline at end of file diff --git a/src/libs/render/EffectManager.cpp b/src/libs/render/EffectManager.cpp index 284ee3ac..496ee9e2 100644 --- a/src/libs/render/EffectManager.cpp +++ b/src/libs/render/EffectManager.cpp @@ -130,7 +130,7 @@ namespace RR::Render reflDesc.uniformCbvSlot = uniformCbvSlot; layout->InitReflection(reflDesc); - layoutMap[Common::Hash(group.name)] = static_cast(i); + layoutMap[Common::Hash(group.name)] = layout.get(); } return Common::RResult::Ok; diff --git a/src/libs/render/EffectManager.hpp b/src/libs/render/EffectManager.hpp index 6072dade..303db08f 100644 --- a/src/libs/render/EffectManager.hpp +++ b/src/libs/render/EffectManager.hpp @@ -5,8 +5,7 @@ #include "gapi/ForwardDeclarations.hpp" -#include -#include +#include "absl/container/flat_hash_map.h" #include @@ -36,16 +35,10 @@ namespace RR::Render size_t GetShaderCount() const { return shaders.size(); } private: - using HashToIndex = eastl::pair; - using LayoutLookup = eastl::vector_map< - Common::HashType, uint32_t, - eastl::less, - EASTLAllocatorType, - eastl::fixed_vector>; - eastl::unique_ptr effectLibrary; eastl::vector> shaders; eastl::vector> bindingGroupLayouts; - LayoutLookup layoutMap; // nameHash -> index + // nameHash -> layout ptr (owned by bindingGroupLayouts) + absl::flat_hash_map layoutMap; }; }