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/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/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..9113095b 100644 --- a/src/libs/gapi/BindingGroupLayout.hpp +++ b/src/libs/gapi/BindingGroupLayout.hpp @@ -5,7 +5,11 @@ #include "gapi/Shader.hpp" #include "gapi/Limits.hpp" +#include "common/hashing/Hash.hpp" + +#include #include +#include namespace RR::GAPI { @@ -37,6 +41,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 +76,57 @@ 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(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: + 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 resourceSlots; + + 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 9b02880d..b7494520 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 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 48830bff..d30c67da 100644 --- a/src/libs/render/CMakeLists.txt +++ b/src/libs/render/CMakeLists.txt @@ -11,10 +11,9 @@ set(SRC RenderTarget.cpp Effect.hpp Effect.cpp + EffectContext.hpp EffectManager.cpp EffectManager.hpp - BindingBlockLayout.cpp - BindingBlockLayout.hpp Submission.cpp Submission.hpp ) 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; + }; +} diff --git a/src/libs/render/EffectManager.cpp b/src/libs/render/EffectManager.cpp index c79c7037..496ee9e2 100644 --- a/src/libs/render/EffectManager.cpp +++ b/src/libs/render/EffectManager.cpp @@ -3,22 +3,24 @@ #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" +#include + namespace RR::Render { - EffectManager::EffectManager() {}; - EffectManager::~EffectManager() {}; + EffectManager::EffectManager() { }; + EffectManager::~EffectManager() { }; Common::RResult EffectManager::Init(std::string_view path) { @@ -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) { @@ -62,18 +65,72 @@ namespace RR::Render elements.push_back(element); } - const GAPI::BindingGroupLayoutDesc layoutDesc { elements }; - bindingGroupLayouts.emplace_back(deviceContext.CreateBindingGroupLayout(layoutDesc, group.name)); - } + const GAPI::BindingGroupLayoutDesc layoutDesc {elements}; + 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)] = layout.get(); } return Common::RResult::Ok; @@ -88,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; @@ -104,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) @@ -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..303db08f 100644 --- a/src/libs/render/EffectManager.hpp +++ b/src/libs/render/EffectManager.hpp @@ -1,14 +1,12 @@ #pragma once -#include "BindingBlockLayout.hpp" - #include "common/Singleton.hpp" #include "common/hashing/Hash.hpp" -#include "absl/container/flat_hash_map.h" - #include "gapi/ForwardDeclarations.hpp" +#include "absl/container/flat_hash_map.h" + #include namespace RR::EffectLibrary @@ -40,7 +38,7 @@ namespace RR::Render eastl::unique_ptr effectLibrary; eastl::vector> shaders; eastl::vector> bindingGroupLayouts; - eastl::vector blockLayouts; - absl::flat_hash_map blockLayoutMap; // nameHash -> index + // nameHash -> layout ptr (owned by bindingGroupLayouts) + absl::flat_hash_map layoutMap; }; -} \ No newline at end of file +} 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++)