Adopt binding plan for builtin object-id pass
This commit is contained in:
@@ -336,6 +336,15 @@ Unity-like Shader Authoring (.shader)
|
||||
- 至少 `ForwardLit + Unlit + ObjectId` 共用同一套 shader/material 执行边界
|
||||
- 新增 pass 不再默认要求先写一套新的硬编码 binding 路径
|
||||
|
||||
当前进展(`2026-04-03`):
|
||||
|
||||
- 已完成:builtin `ObjectId` pass 接入通用 pass binding plan
|
||||
- builtin object-id shader 已显式声明 `PerObject` 资源合约
|
||||
- `BuiltinObjectIdPass` 已改为消费通用 binding plan,不再硬编码 `set0/binding0` 常量布局
|
||||
- 显式 shader `resources` 与 legacy object-id fallback 现在走同一套解析与校验路径
|
||||
- 已验证:`rendering_unit_tests` 59/59,`shader_tests` 26/26
|
||||
- 下一步:把同一套共享执行边界继续推到 `Unlit`,并评估是否抽出 forward/object-id 共用的 pass layout 构建与 descriptor set 组装骨架
|
||||
|
||||
### 阶段 D:扩展 AssetDatabase / Library Artifact 能力
|
||||
|
||||
目标:
|
||||
|
||||
@@ -6,6 +6,15 @@
|
||||
"tags": {
|
||||
"LightMode": "ObjectId"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"name": "PerObjectConstants",
|
||||
"type": "ConstantBuffer",
|
||||
"set": 0,
|
||||
"binding": 0,
|
||||
"semantic": "PerObject"
|
||||
}
|
||||
],
|
||||
"variants": [
|
||||
{
|
||||
"stage": "Vertex",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <XCEngine/Rendering/ObjectIdPass.h>
|
||||
#include <XCEngine/Rendering/ObjectIdEncoding.h>
|
||||
#include <XCEngine/Rendering/RenderMaterialUtility.h>
|
||||
#include <XCEngine/Rendering/RenderResourceCache.h>
|
||||
#include <XCEngine/Core/Asset/ResourceHandle.h>
|
||||
#include <XCEngine/RHI/RHIPipelineState.h>
|
||||
@@ -54,6 +55,8 @@ private:
|
||||
RHI::RHIType m_backendType = RHI::RHIType::D3D12;
|
||||
RHI::RHIPipelineLayout* m_pipelineLayout = nullptr;
|
||||
RHI::RHIPipelineState* m_pipelineState = nullptr;
|
||||
PassResourceBindingLocation m_perObjectBinding = {};
|
||||
Core::uint32 m_firstDescriptorSet = 0;
|
||||
Resources::ResourceHandle<Resources::Shader> m_builtinObjectIdShader;
|
||||
RenderResourceCache m_resourceCache;
|
||||
std::unordered_map<uint64_t, OwnedDescriptorSet> m_perObjectSets;
|
||||
|
||||
@@ -229,6 +229,19 @@ inline Containers::Array<Resources::ShaderResourceBindingDesc> BuildLegacyBuilti
|
||||
return bindings;
|
||||
}
|
||||
|
||||
inline Containers::Array<Resources::ShaderResourceBindingDesc> BuildLegacyBuiltinObjectIdPassResourceBindings() {
|
||||
Containers::Array<Resources::ShaderResourceBindingDesc> bindings;
|
||||
bindings.Resize(1);
|
||||
|
||||
bindings[0].name = "PerObjectConstants";
|
||||
bindings[0].type = Resources::ShaderResourceType::ConstantBuffer;
|
||||
bindings[0].set = 0;
|
||||
bindings[0].binding = 0;
|
||||
bindings[0].semantic = "PerObject";
|
||||
|
||||
return bindings;
|
||||
}
|
||||
|
||||
inline bool IsBuiltinPassResourceTypeCompatible(
|
||||
BuiltinPassResourceSemantic semantic,
|
||||
Resources::ShaderResourceType type) {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "Resources/Mesh/Mesh.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
@@ -222,18 +223,55 @@ bool BuiltinObjectIdPass::CreateResources(const RenderContext& context) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Containers::Array<Resources::ShaderResourceBindingDesc> resourceBindings = objectIdPass->resources;
|
||||
if (resourceBindings.Empty()) {
|
||||
resourceBindings = BuildLegacyBuiltinObjectIdPassResourceBindings();
|
||||
}
|
||||
|
||||
BuiltinPassResourceBindingPlan bindingPlan = {};
|
||||
Containers::String bindingPlanError;
|
||||
if (!TryBuildBuiltinPassResourceBindingPlan(resourceBindings, bindingPlan, &bindingPlanError)) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
(Containers::String("BuiltinObjectIdPass failed to resolve pass resource bindings: ") + bindingPlanError).CStr());
|
||||
DestroyResources();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bindingPlan.perObject.IsValid() ||
|
||||
bindingPlan.bindings.Size() != 1u ||
|
||||
bindingPlan.descriptorSetCount != 1u ||
|
||||
!bindingPlan.usesConstantBuffers ||
|
||||
bindingPlan.usesTextures ||
|
||||
bindingPlan.usesSamplers) {
|
||||
Debug::Logger::Get().Error(
|
||||
Debug::LogCategory::Rendering,
|
||||
"BuiltinObjectIdPass requires exactly one PerObject constant-buffer binding");
|
||||
DestroyResources();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_perObjectBinding = bindingPlan.perObject;
|
||||
m_firstDescriptorSet = bindingPlan.firstDescriptorSet;
|
||||
|
||||
std::vector<std::vector<RHI::DescriptorSetLayoutBinding>> setBindingStorage(
|
||||
static_cast<size_t>(bindingPlan.maxSetIndex) + 1u);
|
||||
RHI::DescriptorSetLayoutBinding constantBinding = {};
|
||||
constantBinding.binding = 0;
|
||||
constantBinding.binding = m_perObjectBinding.binding;
|
||||
constantBinding.type = static_cast<uint32_t>(RHI::DescriptorType::CBV);
|
||||
constantBinding.count = 1;
|
||||
setBindingStorage[m_perObjectBinding.set].push_back(constantBinding);
|
||||
|
||||
RHI::DescriptorSetLayoutDesc constantLayout = {};
|
||||
constantLayout.bindings = &constantBinding;
|
||||
constantLayout.bindingCount = 1;
|
||||
std::vector<RHI::DescriptorSetLayoutDesc> setLayouts(setBindingStorage.size());
|
||||
for (size_t setIndex = 0; setIndex < setBindingStorage.size(); ++setIndex) {
|
||||
setLayouts[setIndex].bindings =
|
||||
setBindingStorage[setIndex].empty() ? nullptr : setBindingStorage[setIndex].data();
|
||||
setLayouts[setIndex].bindingCount = static_cast<uint32_t>(setBindingStorage[setIndex].size());
|
||||
}
|
||||
|
||||
RHI::RHIPipelineLayoutDesc pipelineLayoutDesc = {};
|
||||
pipelineLayoutDesc.setLayouts = &constantLayout;
|
||||
pipelineLayoutDesc.setLayoutCount = 1;
|
||||
pipelineLayoutDesc.setLayouts = setLayouts.empty() ? nullptr : setLayouts.data();
|
||||
pipelineLayoutDesc.setLayoutCount = static_cast<uint32_t>(setLayouts.size());
|
||||
m_pipelineLayout = m_device->CreatePipelineLayout(pipelineLayoutDesc);
|
||||
if (m_pipelineLayout == nullptr) {
|
||||
DestroyResources();
|
||||
@@ -281,10 +319,16 @@ void BuiltinObjectIdPass::DestroyResources() {
|
||||
|
||||
m_device = nullptr;
|
||||
m_backendType = RHI::RHIType::D3D12;
|
||||
m_perObjectBinding = {};
|
||||
m_firstDescriptorSet = 0;
|
||||
m_builtinObjectIdShader.Reset();
|
||||
}
|
||||
|
||||
RHI::RHIDescriptorSet* BuiltinObjectIdPass::GetOrCreatePerObjectSet(uint64_t objectId) {
|
||||
if (m_perObjectBinding.IsValid() == false) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto existing = m_perObjectSets.find(objectId);
|
||||
if (existing != m_perObjectSets.end()) {
|
||||
return existing->second.set;
|
||||
@@ -302,7 +346,7 @@ RHI::RHIDescriptorSet* BuiltinObjectIdPass::GetOrCreatePerObjectSet(uint64_t obj
|
||||
}
|
||||
|
||||
RHI::DescriptorSetLayoutBinding binding = {};
|
||||
binding.binding = 0;
|
||||
binding.binding = m_perObjectBinding.binding;
|
||||
binding.type = static_cast<uint32_t>(RHI::DescriptorType::CBV);
|
||||
binding.count = 1;
|
||||
|
||||
@@ -369,10 +413,10 @@ bool BuiltinObjectIdPass::DrawVisibleItem(
|
||||
visibleItem.localToWorld.Transpose(),
|
||||
EncodeObjectIdToColor(objectId)
|
||||
};
|
||||
constantSet->WriteConstant(0, &constants, sizeof(constants));
|
||||
constantSet->WriteConstant(m_perObjectBinding.binding, &constants, sizeof(constants));
|
||||
|
||||
RHI::RHIDescriptorSet* descriptorSets[] = { constantSet };
|
||||
commandList->SetGraphicsDescriptorSets(0, 1, descriptorSets, m_pipelineLayout);
|
||||
commandList->SetGraphicsDescriptorSets(m_firstDescriptorSet, 1, descriptorSets, m_pipelineLayout);
|
||||
|
||||
if (visibleItem.hasSection) {
|
||||
const Containers::Array<Resources::MeshSection>& sections = visibleItem.mesh->GetSections();
|
||||
|
||||
@@ -120,6 +120,46 @@ TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLegacy
|
||||
EXPECT_EQ(plan.linearClampSampler.set, 4u);
|
||||
}
|
||||
|
||||
TEST(BuiltinObjectIdPass_Test, BuiltinObjectIdShaderDeclaresExplicitPerObjectResourceContract) {
|
||||
ShaderLoader loader;
|
||||
LoadResult result = loader.Load(GetBuiltinObjectIdShaderPath());
|
||||
ASSERT_TRUE(result);
|
||||
ASSERT_NE(result.resource, nullptr);
|
||||
|
||||
Shader* shader = static_cast<Shader*>(result.resource);
|
||||
ASSERT_NE(shader, nullptr);
|
||||
|
||||
const ShaderPass* pass = shader->FindPass("ObjectId");
|
||||
ASSERT_NE(pass, nullptr);
|
||||
ASSERT_EQ(pass->resources.Size(), 1u);
|
||||
|
||||
EXPECT_EQ(pass->resources[0].semantic, "PerObject");
|
||||
EXPECT_EQ(pass->resources[0].type, ShaderResourceType::ConstantBuffer);
|
||||
EXPECT_EQ(pass->resources[0].set, 0u);
|
||||
EXPECT_EQ(pass->resources[0].binding, 0u);
|
||||
|
||||
delete shader;
|
||||
}
|
||||
|
||||
TEST(BuiltinObjectIdPass_Test, BuildsBuiltinPassResourceBindingPlanFromLegacyFallbackResources) {
|
||||
const Array<ShaderResourceBindingDesc> bindings = BuildLegacyBuiltinObjectIdPassResourceBindings();
|
||||
ASSERT_EQ(bindings.Size(), 1u);
|
||||
|
||||
BuiltinPassResourceBindingPlan plan = {};
|
||||
String error;
|
||||
EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(bindings, plan, &error)) << error.CStr();
|
||||
ASSERT_EQ(plan.bindings.Size(), 1u);
|
||||
EXPECT_TRUE(plan.perObject.IsValid());
|
||||
EXPECT_FALSE(plan.material.IsValid());
|
||||
EXPECT_FALSE(plan.baseColorTexture.IsValid());
|
||||
EXPECT_FALSE(plan.linearClampSampler.IsValid());
|
||||
EXPECT_EQ(plan.firstDescriptorSet, 0u);
|
||||
EXPECT_EQ(plan.descriptorSetCount, 1u);
|
||||
EXPECT_TRUE(plan.usesConstantBuffers);
|
||||
EXPECT_FALSE(plan.usesTextures);
|
||||
EXPECT_FALSE(plan.usesSamplers);
|
||||
}
|
||||
|
||||
TEST(BuiltinObjectIdPass_Test, UsesFloat3PositionInputLayoutForStaticMeshVertices) {
|
||||
const InputLayoutDesc inputLayout = BuiltinObjectIdPass::BuildInputLayout();
|
||||
|
||||
|
||||
@@ -507,6 +507,11 @@ TEST(ShaderLoader, LoadBuiltinObjectIdShaderBuildsBackendVariants) {
|
||||
|
||||
const ShaderPass* pass = shader->FindPass("ObjectId");
|
||||
ASSERT_NE(pass, nullptr);
|
||||
ASSERT_EQ(pass->resources.Size(), 1u);
|
||||
EXPECT_EQ(pass->resources[0].semantic, "PerObject");
|
||||
EXPECT_EQ(pass->resources[0].type, ShaderResourceType::ConstantBuffer);
|
||||
EXPECT_EQ(pass->resources[0].set, 0u);
|
||||
EXPECT_EQ(pass->resources[0].binding, 0u);
|
||||
ASSERT_EQ(pass->variants.Size(), 6u);
|
||||
ASSERT_EQ(pass->tags.Size(), 1u);
|
||||
EXPECT_EQ(pass->tags[0].name, "LightMode");
|
||||
|
||||
Reference in New Issue
Block a user