2026-03-30 02:22:17 +08:00
|
|
|
#include <gtest/gtest.h>
|
|
|
|
|
|
2026-04-04 14:27:44 +08:00
|
|
|
#include <XCEngine/Rendering/Passes/BuiltinDepthOnlyPass.h>
|
2026-04-02 19:17:22 +08:00
|
|
|
#include <XCEngine/Rendering/Passes/BuiltinObjectIdPass.h>
|
2026-04-04 14:27:44 +08:00
|
|
|
#include <XCEngine/Rendering/Passes/BuiltinShadowCasterPass.h>
|
2026-03-30 02:22:17 +08:00
|
|
|
#include <XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h>
|
2026-04-03 11:51:01 +08:00
|
|
|
#include <XCEngine/Resources/BuiltinResources.h>
|
2026-03-30 02:22:17 +08:00
|
|
|
#include <XCEngine/Resources/Mesh/Mesh.h>
|
2026-04-03 11:51:01 +08:00
|
|
|
#include <XCEngine/Resources/Shader/Shader.h>
|
|
|
|
|
#include <XCEngine/Resources/Shader/ShaderLoader.h>
|
2026-03-30 02:22:17 +08:00
|
|
|
#include <XCEngine/RHI/RHIEnums.h>
|
|
|
|
|
|
|
|
|
|
using namespace XCEngine::Rendering::Pipelines;
|
2026-04-02 19:17:22 +08:00
|
|
|
using namespace XCEngine::Rendering::Passes;
|
2026-04-03 16:59:18 +08:00
|
|
|
using namespace XCEngine::Rendering;
|
|
|
|
|
using namespace XCEngine::Containers;
|
2026-03-30 02:22:17 +08:00
|
|
|
using namespace XCEngine::Resources;
|
|
|
|
|
using namespace XCEngine::RHI;
|
|
|
|
|
|
|
|
|
|
TEST(BuiltinForwardPipeline_Test, UsesFloat3PositionInputLayoutForStaticMeshVertices) {
|
|
|
|
|
const InputLayoutDesc inputLayout = BuiltinForwardPipeline::BuildInputLayout();
|
|
|
|
|
|
2026-04-01 00:41:56 +08:00
|
|
|
ASSERT_EQ(inputLayout.elements.size(), 3u);
|
2026-03-30 02:22:17 +08:00
|
|
|
|
|
|
|
|
const InputElementDesc& position = inputLayout.elements[0];
|
|
|
|
|
EXPECT_EQ(position.semanticName, "POSITION");
|
|
|
|
|
EXPECT_EQ(position.semanticIndex, 0u);
|
|
|
|
|
EXPECT_EQ(position.format, static_cast<uint32_t>(Format::R32G32B32_Float));
|
|
|
|
|
EXPECT_EQ(position.inputSlot, 0u);
|
|
|
|
|
EXPECT_EQ(position.alignedByteOffset, 0u);
|
|
|
|
|
|
2026-04-01 00:41:56 +08:00
|
|
|
const InputElementDesc& normal = inputLayout.elements[1];
|
|
|
|
|
EXPECT_EQ(normal.semanticName, "NORMAL");
|
|
|
|
|
EXPECT_EQ(normal.semanticIndex, 0u);
|
|
|
|
|
EXPECT_EQ(normal.format, static_cast<uint32_t>(Format::R32G32B32_Float));
|
|
|
|
|
EXPECT_EQ(normal.inputSlot, 0u);
|
|
|
|
|
EXPECT_EQ(normal.alignedByteOffset, static_cast<uint32_t>(offsetof(StaticMeshVertex, normal)));
|
|
|
|
|
|
|
|
|
|
const InputElementDesc& texcoord = inputLayout.elements[2];
|
2026-03-30 02:22:17 +08:00
|
|
|
EXPECT_EQ(texcoord.semanticName, "TEXCOORD");
|
|
|
|
|
EXPECT_EQ(texcoord.semanticIndex, 0u);
|
|
|
|
|
EXPECT_EQ(texcoord.format, static_cast<uint32_t>(Format::R32G32_Float));
|
|
|
|
|
EXPECT_EQ(texcoord.inputSlot, 0u);
|
|
|
|
|
EXPECT_EQ(texcoord.alignedByteOffset, static_cast<uint32_t>(offsetof(StaticMeshVertex, uv0)));
|
|
|
|
|
}
|
2026-04-02 19:17:22 +08:00
|
|
|
|
2026-04-03 11:51:01 +08:00
|
|
|
TEST(BuiltinForwardPipeline_Test, BuiltinForwardShaderDeclaresExplicitForwardResourceContract) {
|
|
|
|
|
ShaderLoader loader;
|
|
|
|
|
LoadResult result = loader.Load(GetBuiltinForwardLitShaderPath());
|
|
|
|
|
ASSERT_TRUE(result);
|
|
|
|
|
ASSERT_NE(result.resource, nullptr);
|
|
|
|
|
|
|
|
|
|
Shader* shader = static_cast<Shader*>(result.resource);
|
|
|
|
|
ASSERT_NE(shader, nullptr);
|
|
|
|
|
|
|
|
|
|
const ShaderPass* pass = shader->FindPass("ForwardLit");
|
|
|
|
|
ASSERT_NE(pass, nullptr);
|
2026-04-04 23:01:34 +08:00
|
|
|
ASSERT_EQ(pass->resources.Size(), 7u);
|
2026-04-03 11:51:01 +08:00
|
|
|
|
|
|
|
|
EXPECT_EQ(pass->resources[0].semantic, "PerObject");
|
|
|
|
|
EXPECT_EQ(pass->resources[0].type, ShaderResourceType::ConstantBuffer);
|
2026-04-05 13:50:52 +08:00
|
|
|
EXPECT_EQ(pass->resources[0].set, 0u);
|
2026-04-03 11:51:01 +08:00
|
|
|
EXPECT_EQ(pass->resources[0].binding, 0u);
|
|
|
|
|
|
|
|
|
|
EXPECT_EQ(pass->resources[1].semantic, "Material");
|
|
|
|
|
EXPECT_EQ(pass->resources[1].type, ShaderResourceType::ConstantBuffer);
|
2026-04-05 13:50:52 +08:00
|
|
|
EXPECT_EQ(pass->resources[1].set, 1u);
|
2026-04-03 11:51:01 +08:00
|
|
|
EXPECT_EQ(pass->resources[1].binding, 0u);
|
|
|
|
|
|
|
|
|
|
EXPECT_EQ(pass->resources[2].semantic, "BaseColorTexture");
|
|
|
|
|
EXPECT_EQ(pass->resources[2].type, ShaderResourceType::Texture2D);
|
2026-04-05 13:50:52 +08:00
|
|
|
EXPECT_EQ(pass->resources[2].set, 2u);
|
2026-04-03 11:51:01 +08:00
|
|
|
EXPECT_EQ(pass->resources[2].binding, 0u);
|
|
|
|
|
|
|
|
|
|
EXPECT_EQ(pass->resources[3].semantic, "LinearClampSampler");
|
|
|
|
|
EXPECT_EQ(pass->resources[3].type, ShaderResourceType::Sampler);
|
2026-04-05 13:50:52 +08:00
|
|
|
EXPECT_EQ(pass->resources[3].set, 3u);
|
2026-04-03 11:51:01 +08:00
|
|
|
EXPECT_EQ(pass->resources[3].binding, 0u);
|
|
|
|
|
|
2026-04-04 23:01:34 +08:00
|
|
|
EXPECT_EQ(pass->resources[4].semantic, "ShadowReceiver");
|
|
|
|
|
EXPECT_EQ(pass->resources[4].type, ShaderResourceType::ConstantBuffer);
|
2026-04-05 13:50:52 +08:00
|
|
|
EXPECT_EQ(pass->resources[4].set, 4u);
|
2026-04-04 23:01:34 +08:00
|
|
|
EXPECT_EQ(pass->resources[4].binding, 0u);
|
|
|
|
|
|
|
|
|
|
EXPECT_EQ(pass->resources[5].semantic, "ShadowMapTexture");
|
|
|
|
|
EXPECT_EQ(pass->resources[5].type, ShaderResourceType::Texture2D);
|
2026-04-05 13:50:52 +08:00
|
|
|
EXPECT_EQ(pass->resources[5].set, 5u);
|
2026-04-04 23:01:34 +08:00
|
|
|
EXPECT_EQ(pass->resources[5].binding, 0u);
|
|
|
|
|
|
|
|
|
|
EXPECT_EQ(pass->resources[6].semantic, "ShadowMapSampler");
|
|
|
|
|
EXPECT_EQ(pass->resources[6].type, ShaderResourceType::Sampler);
|
2026-04-05 13:50:52 +08:00
|
|
|
EXPECT_EQ(pass->resources[6].set, 6u);
|
2026-04-04 23:01:34 +08:00
|
|
|
EXPECT_EQ(pass->resources[6].binding, 0u);
|
|
|
|
|
|
2026-04-03 11:51:01 +08:00
|
|
|
delete shader;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 17:18:46 +08:00
|
|
|
TEST(BuiltinForwardPipeline_Test, BuiltinUnlitShaderDeclaresExplicitSurfaceResourceContract) {
|
|
|
|
|
ShaderLoader loader;
|
|
|
|
|
LoadResult result = loader.Load(GetBuiltinUnlitShaderPath());
|
|
|
|
|
ASSERT_TRUE(result);
|
|
|
|
|
ASSERT_NE(result.resource, nullptr);
|
|
|
|
|
|
|
|
|
|
Shader* shader = static_cast<Shader*>(result.resource);
|
|
|
|
|
ASSERT_NE(shader, nullptr);
|
|
|
|
|
|
|
|
|
|
const ShaderPass* pass = shader->FindPass("Unlit");
|
|
|
|
|
ASSERT_NE(pass, nullptr);
|
|
|
|
|
ASSERT_EQ(pass->resources.Size(), 4u);
|
|
|
|
|
|
|
|
|
|
EXPECT_EQ(pass->resources[0].semantic, "PerObject");
|
|
|
|
|
EXPECT_EQ(pass->resources[0].type, ShaderResourceType::ConstantBuffer);
|
2026-04-05 13:50:52 +08:00
|
|
|
EXPECT_EQ(pass->resources[0].set, 0u);
|
2026-04-03 17:18:46 +08:00
|
|
|
EXPECT_EQ(pass->resources[0].binding, 0u);
|
|
|
|
|
|
|
|
|
|
EXPECT_EQ(pass->resources[1].semantic, "Material");
|
|
|
|
|
EXPECT_EQ(pass->resources[1].type, ShaderResourceType::ConstantBuffer);
|
2026-04-05 13:50:52 +08:00
|
|
|
EXPECT_EQ(pass->resources[1].set, 1u);
|
2026-04-03 17:18:46 +08:00
|
|
|
EXPECT_EQ(pass->resources[1].binding, 0u);
|
|
|
|
|
|
|
|
|
|
EXPECT_EQ(pass->resources[2].semantic, "BaseColorTexture");
|
|
|
|
|
EXPECT_EQ(pass->resources[2].type, ShaderResourceType::Texture2D);
|
2026-04-05 13:50:52 +08:00
|
|
|
EXPECT_EQ(pass->resources[2].set, 2u);
|
2026-04-03 17:18:46 +08:00
|
|
|
EXPECT_EQ(pass->resources[2].binding, 0u);
|
|
|
|
|
|
|
|
|
|
EXPECT_EQ(pass->resources[3].semantic, "LinearClampSampler");
|
|
|
|
|
EXPECT_EQ(pass->resources[3].type, ShaderResourceType::Sampler);
|
2026-04-05 13:50:52 +08:00
|
|
|
EXPECT_EQ(pass->resources[3].set, 3u);
|
2026-04-03 17:18:46 +08:00
|
|
|
EXPECT_EQ(pass->resources[3].binding, 0u);
|
|
|
|
|
|
|
|
|
|
delete shader;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 16:59:18 +08:00
|
|
|
TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromExplicitForwardResources) {
|
|
|
|
|
ShaderLoader loader;
|
|
|
|
|
LoadResult result = loader.Load(GetBuiltinForwardLitShaderPath());
|
|
|
|
|
ASSERT_TRUE(result);
|
|
|
|
|
ASSERT_NE(result.resource, nullptr);
|
|
|
|
|
|
|
|
|
|
Shader* shader = static_cast<Shader*>(result.resource);
|
|
|
|
|
ASSERT_NE(shader, nullptr);
|
|
|
|
|
|
|
|
|
|
const ShaderPass* pass = shader->FindPass("ForwardLit");
|
|
|
|
|
ASSERT_NE(pass, nullptr);
|
|
|
|
|
|
|
|
|
|
BuiltinPassResourceBindingPlan plan = {};
|
|
|
|
|
String error;
|
|
|
|
|
EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(pass->resources, plan, &error)) << error.CStr();
|
|
|
|
|
EXPECT_TRUE(plan.perObject.IsValid());
|
|
|
|
|
EXPECT_TRUE(plan.material.IsValid());
|
2026-04-04 23:01:34 +08:00
|
|
|
EXPECT_TRUE(plan.shadowReceiver.IsValid());
|
2026-04-03 16:59:18 +08:00
|
|
|
EXPECT_TRUE(plan.baseColorTexture.IsValid());
|
2026-04-03 17:18:46 +08:00
|
|
|
EXPECT_TRUE(plan.linearClampSampler.IsValid());
|
2026-04-04 23:01:34 +08:00
|
|
|
EXPECT_TRUE(plan.shadowMapTexture.IsValid());
|
|
|
|
|
EXPECT_TRUE(plan.shadowMapSampler.IsValid());
|
2026-04-05 13:50:52 +08:00
|
|
|
EXPECT_EQ(plan.firstDescriptorSet, 0u);
|
2026-04-04 23:01:34 +08:00
|
|
|
EXPECT_EQ(plan.descriptorSetCount, 7u);
|
2026-04-03 17:18:46 +08:00
|
|
|
EXPECT_TRUE(plan.usesConstantBuffers);
|
|
|
|
|
EXPECT_TRUE(plan.usesTextures);
|
|
|
|
|
EXPECT_TRUE(plan.usesSamplers);
|
|
|
|
|
|
|
|
|
|
delete shader;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromExplicitUnlitResources) {
|
|
|
|
|
ShaderLoader loader;
|
|
|
|
|
LoadResult result = loader.Load(GetBuiltinUnlitShaderPath());
|
|
|
|
|
ASSERT_TRUE(result);
|
|
|
|
|
ASSERT_NE(result.resource, nullptr);
|
|
|
|
|
|
|
|
|
|
Shader* shader = static_cast<Shader*>(result.resource);
|
|
|
|
|
ASSERT_NE(shader, nullptr);
|
|
|
|
|
|
|
|
|
|
const ShaderPass* pass = shader->FindPass("Unlit");
|
|
|
|
|
ASSERT_NE(pass, nullptr);
|
|
|
|
|
|
|
|
|
|
BuiltinPassResourceBindingPlan plan = {};
|
|
|
|
|
String error;
|
|
|
|
|
EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(pass->resources, plan, &error)) << error.CStr();
|
|
|
|
|
EXPECT_TRUE(plan.perObject.IsValid());
|
|
|
|
|
EXPECT_TRUE(plan.material.IsValid());
|
|
|
|
|
EXPECT_TRUE(plan.baseColorTexture.IsValid());
|
2026-04-03 16:59:18 +08:00
|
|
|
EXPECT_TRUE(plan.linearClampSampler.IsValid());
|
2026-04-05 13:50:52 +08:00
|
|
|
EXPECT_EQ(plan.firstDescriptorSet, 0u);
|
2026-04-03 16:59:18 +08:00
|
|
|
EXPECT_EQ(plan.descriptorSetCount, 4u);
|
|
|
|
|
EXPECT_TRUE(plan.usesConstantBuffers);
|
|
|
|
|
EXPECT_TRUE(plan.usesTextures);
|
|
|
|
|
EXPECT_TRUE(plan.usesSamplers);
|
|
|
|
|
|
|
|
|
|
delete shader;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 13:50:52 +08:00
|
|
|
TEST(BuiltinForwardPipeline_Test, UsesNormalizedExplicitSetIndicesForSurfaceResources) {
|
|
|
|
|
ShaderLoader loader;
|
|
|
|
|
LoadResult result = loader.Load(GetBuiltinForwardLitShaderPath());
|
|
|
|
|
ASSERT_TRUE(result);
|
|
|
|
|
ASSERT_NE(result.resource, nullptr);
|
|
|
|
|
|
|
|
|
|
Shader* shader = static_cast<Shader*>(result.resource);
|
|
|
|
|
ASSERT_NE(shader, nullptr);
|
|
|
|
|
|
|
|
|
|
const ShaderPass* pass = shader->FindPass("ForwardLit");
|
|
|
|
|
ASSERT_NE(pass, nullptr);
|
|
|
|
|
ASSERT_EQ(pass->resources.Size(), 7u);
|
2026-04-03 16:59:18 +08:00
|
|
|
|
|
|
|
|
BuiltinPassResourceBindingPlan plan = {};
|
|
|
|
|
String error;
|
2026-04-05 13:50:52 +08:00
|
|
|
EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(pass->resources, plan, &error)) << error.CStr();
|
|
|
|
|
ASSERT_EQ(plan.bindings.Size(), 7u);
|
|
|
|
|
EXPECT_EQ(plan.perObject.set, 0u);
|
|
|
|
|
EXPECT_EQ(plan.material.set, 1u);
|
|
|
|
|
EXPECT_EQ(plan.baseColorTexture.set, 2u);
|
|
|
|
|
EXPECT_EQ(plan.linearClampSampler.set, 3u);
|
|
|
|
|
EXPECT_EQ(plan.shadowReceiver.set, 4u);
|
|
|
|
|
EXPECT_EQ(plan.shadowMapTexture.set, 5u);
|
|
|
|
|
EXPECT_EQ(plan.shadowMapSampler.set, 6u);
|
|
|
|
|
|
|
|
|
|
delete shader;
|
2026-04-03 16:59:18 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-05 13:50:52 +08:00
|
|
|
TEST(BuiltinPassLayout_Test, BuildsSharedSetLayoutsFromExplicitForwardResources) {
|
|
|
|
|
ShaderLoader loader;
|
|
|
|
|
LoadResult result = loader.Load(GetBuiltinForwardLitShaderPath());
|
|
|
|
|
ASSERT_TRUE(result);
|
|
|
|
|
ASSERT_NE(result.resource, nullptr);
|
|
|
|
|
|
|
|
|
|
Shader* shader = static_cast<Shader*>(result.resource);
|
|
|
|
|
ASSERT_NE(shader, nullptr);
|
|
|
|
|
|
|
|
|
|
const ShaderPass* pass = shader->FindPass("ForwardLit");
|
|
|
|
|
ASSERT_NE(pass, nullptr);
|
|
|
|
|
ASSERT_EQ(pass->resources.Size(), 7u);
|
2026-04-04 13:48:13 +08:00
|
|
|
|
|
|
|
|
BuiltinPassResourceBindingPlan plan = {};
|
|
|
|
|
String error;
|
2026-04-05 13:50:52 +08:00
|
|
|
ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(pass->resources, plan, &error)) << error.CStr();
|
2026-04-04 13:48:13 +08:00
|
|
|
|
|
|
|
|
std::vector<BuiltinPassSetLayoutMetadata> setLayouts;
|
|
|
|
|
ASSERT_TRUE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)) << error.CStr();
|
2026-04-05 13:50:52 +08:00
|
|
|
ASSERT_EQ(setLayouts.size(), 7u);
|
2026-04-04 13:48:13 +08:00
|
|
|
|
2026-04-05 13:50:52 +08:00
|
|
|
EXPECT_EQ(setLayouts[0].layout.bindingCount, 1u);
|
|
|
|
|
EXPECT_FALSE(setLayouts[0].shaderVisible);
|
|
|
|
|
EXPECT_TRUE(setLayouts[0].usesPerObject);
|
|
|
|
|
EXPECT_FALSE(setLayouts[0].usesMaterial);
|
2026-04-04 13:48:13 +08:00
|
|
|
|
|
|
|
|
EXPECT_EQ(setLayouts[1].layout.bindingCount, 1u);
|
2026-04-05 13:50:52 +08:00
|
|
|
EXPECT_TRUE(setLayouts[1].usesMaterial);
|
2026-04-04 13:48:13 +08:00
|
|
|
EXPECT_FALSE(setLayouts[1].shaderVisible);
|
|
|
|
|
|
|
|
|
|
EXPECT_EQ(setLayouts[2].layout.bindingCount, 1u);
|
2026-04-05 13:50:52 +08:00
|
|
|
EXPECT_TRUE(setLayouts[2].usesTexture);
|
|
|
|
|
EXPECT_TRUE(setLayouts[2].shaderVisible);
|
|
|
|
|
EXPECT_EQ(setLayouts[2].heapType, DescriptorHeapType::CBV_SRV_UAV);
|
2026-04-04 13:48:13 +08:00
|
|
|
|
|
|
|
|
EXPECT_EQ(setLayouts[3].layout.bindingCount, 1u);
|
2026-04-05 13:50:52 +08:00
|
|
|
EXPECT_TRUE(setLayouts[3].usesSampler);
|
2026-04-04 13:48:13 +08:00
|
|
|
EXPECT_TRUE(setLayouts[3].shaderVisible);
|
2026-04-05 13:50:52 +08:00
|
|
|
EXPECT_EQ(setLayouts[3].heapType, DescriptorHeapType::Sampler);
|
2026-04-04 13:48:13 +08:00
|
|
|
|
|
|
|
|
EXPECT_EQ(setLayouts[4].layout.bindingCount, 1u);
|
2026-04-05 13:50:52 +08:00
|
|
|
EXPECT_TRUE(setLayouts[4].usesShadowReceiver);
|
|
|
|
|
EXPECT_FALSE(setLayouts[4].shaderVisible);
|
|
|
|
|
|
|
|
|
|
EXPECT_EQ(setLayouts[5].layout.bindingCount, 1u);
|
|
|
|
|
EXPECT_TRUE(setLayouts[5].usesTexture);
|
|
|
|
|
EXPECT_TRUE(setLayouts[5].shaderVisible);
|
|
|
|
|
|
|
|
|
|
EXPECT_EQ(setLayouts[6].layout.bindingCount, 1u);
|
|
|
|
|
EXPECT_TRUE(setLayouts[6].usesSampler);
|
|
|
|
|
EXPECT_TRUE(setLayouts[6].shaderVisible);
|
|
|
|
|
EXPECT_EQ(setLayouts[6].heapType, DescriptorHeapType::Sampler);
|
|
|
|
|
|
|
|
|
|
delete shader;
|
2026-04-04 13:48:13 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-04 14:27:44 +08:00
|
|
|
TEST(BuiltinDepthStylePass_Test, BuiltinDepthOnlyShaderDeclaresExplicitPerObjectResourceContract) {
|
|
|
|
|
ShaderLoader loader;
|
|
|
|
|
LoadResult result = loader.Load(GetBuiltinDepthOnlyShaderPath());
|
|
|
|
|
ASSERT_TRUE(result);
|
|
|
|
|
ASSERT_NE(result.resource, nullptr);
|
|
|
|
|
|
|
|
|
|
Shader* shader = static_cast<Shader*>(result.resource);
|
|
|
|
|
ASSERT_NE(shader, nullptr);
|
|
|
|
|
|
|
|
|
|
const ShaderPass* pass = shader->FindPass("DepthOnly");
|
|
|
|
|
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(BuiltinDepthStylePass_Test, BuiltinShadowCasterShaderDeclaresExplicitPerObjectResourceContract) {
|
|
|
|
|
ShaderLoader loader;
|
|
|
|
|
LoadResult result = loader.Load(GetBuiltinShadowCasterShaderPath());
|
|
|
|
|
ASSERT_TRUE(result);
|
|
|
|
|
ASSERT_NE(result.resource, nullptr);
|
|
|
|
|
|
|
|
|
|
Shader* shader = static_cast<Shader*>(result.resource);
|
|
|
|
|
ASSERT_NE(shader, nullptr);
|
|
|
|
|
|
|
|
|
|
const ShaderPass* pass = shader->FindPass("ShadowCaster");
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-03 17:05:38 +08:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 13:38:50 +08:00
|
|
|
TEST(BuiltinObjectIdPass_Test, BuildsBuiltinPassResourceBindingPlanFromExplicitObjectIdResources) {
|
|
|
|
|
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);
|
2026-04-03 17:05:38 +08:00
|
|
|
|
|
|
|
|
BuiltinPassResourceBindingPlan plan = {};
|
|
|
|
|
String error;
|
2026-04-05 13:38:50 +08:00
|
|
|
EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(pass->resources, plan, &error)) << error.CStr();
|
2026-04-03 17:05:38 +08:00
|
|
|
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);
|
2026-04-05 13:38:50 +08:00
|
|
|
|
|
|
|
|
delete shader;
|
2026-04-03 17:05:38 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-02 19:17:22 +08:00
|
|
|
TEST(BuiltinObjectIdPass_Test, UsesFloat3PositionInputLayoutForStaticMeshVertices) {
|
|
|
|
|
const InputLayoutDesc inputLayout = BuiltinObjectIdPass::BuildInputLayout();
|
|
|
|
|
|
2026-04-04 13:48:13 +08:00
|
|
|
ASSERT_EQ(inputLayout.elements.size(), 3u);
|
2026-04-02 19:17:22 +08:00
|
|
|
|
|
|
|
|
const InputElementDesc& position = inputLayout.elements[0];
|
|
|
|
|
EXPECT_EQ(position.semanticName, "POSITION");
|
|
|
|
|
EXPECT_EQ(position.semanticIndex, 0u);
|
|
|
|
|
EXPECT_EQ(position.format, static_cast<uint32_t>(Format::R32G32B32_Float));
|
|
|
|
|
EXPECT_EQ(position.inputSlot, 0u);
|
|
|
|
|
EXPECT_EQ(position.alignedByteOffset, static_cast<uint32_t>(offsetof(StaticMeshVertex, position)));
|
2026-04-04 13:48:13 +08:00
|
|
|
|
|
|
|
|
const InputElementDesc& normal = inputLayout.elements[1];
|
|
|
|
|
EXPECT_EQ(normal.semanticName, "NORMAL");
|
|
|
|
|
EXPECT_EQ(normal.semanticIndex, 0u);
|
|
|
|
|
EXPECT_EQ(normal.format, static_cast<uint32_t>(Format::R32G32B32_Float));
|
|
|
|
|
EXPECT_EQ(normal.inputSlot, 0u);
|
|
|
|
|
EXPECT_EQ(normal.alignedByteOffset, static_cast<uint32_t>(offsetof(StaticMeshVertex, normal)));
|
|
|
|
|
|
|
|
|
|
const InputElementDesc& texcoord = inputLayout.elements[2];
|
|
|
|
|
EXPECT_EQ(texcoord.semanticName, "TEXCOORD");
|
|
|
|
|
EXPECT_EQ(texcoord.semanticIndex, 0u);
|
|
|
|
|
EXPECT_EQ(texcoord.format, static_cast<uint32_t>(Format::R32G32_Float));
|
|
|
|
|
EXPECT_EQ(texcoord.inputSlot, 0u);
|
|
|
|
|
EXPECT_EQ(texcoord.alignedByteOffset, static_cast<uint32_t>(offsetof(StaticMeshVertex, uv0)));
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 13:38:50 +08:00
|
|
|
TEST(BuiltinPassLayout_Test, BuildsSharedSetLayoutsFromExplicitObjectIdResources) {
|
|
|
|
|
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);
|
2026-04-04 13:48:13 +08:00
|
|
|
|
|
|
|
|
BuiltinPassResourceBindingPlan plan = {};
|
|
|
|
|
String error;
|
2026-04-05 13:38:50 +08:00
|
|
|
ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(pass->resources, plan, &error)) << error.CStr();
|
2026-04-04 13:48:13 +08:00
|
|
|
|
|
|
|
|
std::vector<BuiltinPassSetLayoutMetadata> setLayouts;
|
|
|
|
|
ASSERT_TRUE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)) << error.CStr();
|
|
|
|
|
ASSERT_EQ(setLayouts.size(), 1u);
|
|
|
|
|
EXPECT_EQ(setLayouts[0].layout.bindingCount, 1u);
|
|
|
|
|
EXPECT_TRUE(setLayouts[0].usesPerObject);
|
|
|
|
|
EXPECT_FALSE(setLayouts[0].usesMaterial);
|
|
|
|
|
EXPECT_FALSE(setLayouts[0].usesTexture);
|
|
|
|
|
EXPECT_FALSE(setLayouts[0].usesSampler);
|
|
|
|
|
EXPECT_FALSE(setLayouts[0].shaderVisible);
|
|
|
|
|
EXPECT_EQ(setLayouts[0].heapType, DescriptorHeapType::CBV_SRV_UAV);
|
2026-04-05 13:38:50 +08:00
|
|
|
|
|
|
|
|
delete shader;
|
2026-04-04 13:48:13 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-05 13:38:50 +08:00
|
|
|
TEST(BuiltinPassLayout_Test, BuildsSharedSetLayoutsFromExplicitDepthOnlyResources) {
|
|
|
|
|
ShaderLoader loader;
|
|
|
|
|
LoadResult result = loader.Load(GetBuiltinDepthOnlyShaderPath());
|
|
|
|
|
ASSERT_TRUE(result);
|
|
|
|
|
ASSERT_NE(result.resource, nullptr);
|
|
|
|
|
|
|
|
|
|
Shader* shader = static_cast<Shader*>(result.resource);
|
|
|
|
|
ASSERT_NE(shader, nullptr);
|
|
|
|
|
|
|
|
|
|
const ShaderPass* pass = shader->FindPass("DepthOnly");
|
|
|
|
|
ASSERT_NE(pass, nullptr);
|
|
|
|
|
ASSERT_EQ(pass->resources.Size(), 1u);
|
2026-04-04 14:27:44 +08:00
|
|
|
|
|
|
|
|
BuiltinPassResourceBindingPlan plan = {};
|
|
|
|
|
String error;
|
2026-04-05 13:38:50 +08:00
|
|
|
ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(pass->resources, plan, &error)) << error.CStr();
|
2026-04-04 14:27:44 +08:00
|
|
|
|
|
|
|
|
std::vector<BuiltinPassSetLayoutMetadata> setLayouts;
|
|
|
|
|
ASSERT_TRUE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)) << error.CStr();
|
|
|
|
|
ASSERT_EQ(setLayouts.size(), 1u);
|
|
|
|
|
EXPECT_EQ(setLayouts[0].layout.bindingCount, 1u);
|
|
|
|
|
EXPECT_TRUE(setLayouts[0].usesPerObject);
|
|
|
|
|
EXPECT_FALSE(setLayouts[0].usesMaterial);
|
|
|
|
|
EXPECT_FALSE(setLayouts[0].usesTexture);
|
|
|
|
|
EXPECT_FALSE(setLayouts[0].usesSampler);
|
2026-04-05 13:38:50 +08:00
|
|
|
|
|
|
|
|
delete shader;
|
2026-04-04 14:27:44 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-05 13:38:50 +08:00
|
|
|
TEST(BuiltinPassLayout_Test, BuildsSharedSetLayoutsFromExplicitShadowCasterResources) {
|
|
|
|
|
ShaderLoader loader;
|
|
|
|
|
LoadResult result = loader.Load(GetBuiltinShadowCasterShaderPath());
|
|
|
|
|
ASSERT_TRUE(result);
|
|
|
|
|
ASSERT_NE(result.resource, nullptr);
|
|
|
|
|
|
|
|
|
|
Shader* shader = static_cast<Shader*>(result.resource);
|
|
|
|
|
ASSERT_NE(shader, nullptr);
|
|
|
|
|
|
|
|
|
|
const ShaderPass* pass = shader->FindPass("ShadowCaster");
|
|
|
|
|
ASSERT_NE(pass, nullptr);
|
|
|
|
|
ASSERT_EQ(pass->resources.Size(), 1u);
|
2026-04-04 14:27:44 +08:00
|
|
|
|
|
|
|
|
BuiltinPassResourceBindingPlan plan = {};
|
|
|
|
|
String error;
|
2026-04-05 13:38:50 +08:00
|
|
|
ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(pass->resources, plan, &error)) << error.CStr();
|
2026-04-04 14:27:44 +08:00
|
|
|
|
|
|
|
|
std::vector<BuiltinPassSetLayoutMetadata> setLayouts;
|
|
|
|
|
ASSERT_TRUE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)) << error.CStr();
|
|
|
|
|
ASSERT_EQ(setLayouts.size(), 1u);
|
|
|
|
|
EXPECT_EQ(setLayouts[0].layout.bindingCount, 1u);
|
|
|
|
|
EXPECT_TRUE(setLayouts[0].usesPerObject);
|
|
|
|
|
EXPECT_FALSE(setLayouts[0].usesMaterial);
|
|
|
|
|
EXPECT_FALSE(setLayouts[0].usesTexture);
|
|
|
|
|
EXPECT_FALSE(setLayouts[0].usesSampler);
|
2026-04-05 13:38:50 +08:00
|
|
|
|
|
|
|
|
delete shader;
|
2026-04-04 14:27:44 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-04 13:48:13 +08:00
|
|
|
TEST(BuiltinPassLayout_Test, RejectsMixedSamplerAndNonSamplerBindingsInOneSet) {
|
|
|
|
|
Array<ShaderResourceBindingDesc> bindings;
|
|
|
|
|
bindings.Resize(2);
|
|
|
|
|
|
|
|
|
|
bindings[0].name = "BaseColorTexture";
|
|
|
|
|
bindings[0].type = ShaderResourceType::Texture2D;
|
|
|
|
|
bindings[0].set = 0;
|
|
|
|
|
bindings[0].binding = 0;
|
|
|
|
|
bindings[0].semantic = "BaseColorTexture";
|
|
|
|
|
|
|
|
|
|
bindings[1].name = "LinearClampSampler";
|
|
|
|
|
bindings[1].type = ShaderResourceType::Sampler;
|
|
|
|
|
bindings[1].set = 0;
|
|
|
|
|
bindings[1].binding = 1;
|
|
|
|
|
bindings[1].semantic = "LinearClampSampler";
|
|
|
|
|
|
|
|
|
|
BuiltinPassResourceBindingPlan plan = {};
|
|
|
|
|
String error;
|
|
|
|
|
ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(bindings, plan, &error)) << error.CStr();
|
|
|
|
|
|
|
|
|
|
std::vector<BuiltinPassSetLayoutMetadata> setLayouts;
|
|
|
|
|
EXPECT_FALSE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error));
|
|
|
|
|
EXPECT_EQ(error, "Builtin pass does not support mixing sampler and non-sampler bindings in one set");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(BuiltinPassLayout_Test, RejectsDuplicateBindingsInOneSet) {
|
|
|
|
|
BuiltinPassResourceBindingPlan plan = {};
|
|
|
|
|
BuiltinPassResourceBindingDesc perObjectBinding = {};
|
|
|
|
|
perObjectBinding.semantic = BuiltinPassResourceSemantic::PerObject;
|
|
|
|
|
perObjectBinding.resourceType = ShaderResourceType::ConstantBuffer;
|
|
|
|
|
perObjectBinding.location = { 0, 0 };
|
|
|
|
|
plan.bindings.PushBack(perObjectBinding);
|
|
|
|
|
|
|
|
|
|
BuiltinPassResourceBindingDesc materialBinding = {};
|
|
|
|
|
materialBinding.semantic = BuiltinPassResourceSemantic::Material;
|
|
|
|
|
materialBinding.resourceType = ShaderResourceType::ConstantBuffer;
|
|
|
|
|
materialBinding.location = { 0, 0 };
|
|
|
|
|
plan.bindings.PushBack(materialBinding);
|
|
|
|
|
plan.maxSetIndex = 0;
|
|
|
|
|
plan.firstDescriptorSet = 0;
|
|
|
|
|
plan.descriptorSetCount = 1;
|
|
|
|
|
plan.usesConstantBuffers = true;
|
|
|
|
|
plan.perObject = { 0, 0 };
|
|
|
|
|
plan.material = { 0, 0 };
|
|
|
|
|
|
|
|
|
|
std::vector<BuiltinPassSetLayoutMetadata> setLayouts;
|
|
|
|
|
String error;
|
|
|
|
|
EXPECT_FALSE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error));
|
|
|
|
|
EXPECT_EQ(error, "Builtin pass encountered duplicate bindings inside one descriptor set");
|
2026-04-02 19:17:22 +08:00
|
|
|
}
|
2026-04-04 14:27:44 +08:00
|
|
|
|
|
|
|
|
TEST(BuiltinDepthOnlyPass_Test, UsesFloat3PositionInputLayoutForStaticMeshVertices) {
|
|
|
|
|
const InputLayoutDesc inputLayout = BuiltinDepthOnlyPass::BuildInputLayout();
|
|
|
|
|
|
|
|
|
|
ASSERT_EQ(inputLayout.elements.size(), 3u);
|
|
|
|
|
EXPECT_EQ(inputLayout.elements[0].semanticName, "POSITION");
|
|
|
|
|
EXPECT_EQ(inputLayout.elements[1].semanticName, "NORMAL");
|
|
|
|
|
EXPECT_EQ(inputLayout.elements[2].semanticName, "TEXCOORD");
|
|
|
|
|
EXPECT_EQ(inputLayout.elements[0].alignedByteOffset, static_cast<uint32_t>(offsetof(StaticMeshVertex, position)));
|
|
|
|
|
EXPECT_EQ(inputLayout.elements[1].alignedByteOffset, static_cast<uint32_t>(offsetof(StaticMeshVertex, normal)));
|
|
|
|
|
EXPECT_EQ(inputLayout.elements[2].alignedByteOffset, static_cast<uint32_t>(offsetof(StaticMeshVertex, uv0)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(BuiltinShadowCasterPass_Test, UsesFloat3PositionInputLayoutForStaticMeshVertices) {
|
|
|
|
|
const InputLayoutDesc inputLayout = BuiltinShadowCasterPass::BuildInputLayout();
|
|
|
|
|
|
|
|
|
|
ASSERT_EQ(inputLayout.elements.size(), 3u);
|
|
|
|
|
EXPECT_EQ(inputLayout.elements[0].semanticName, "POSITION");
|
|
|
|
|
EXPECT_EQ(inputLayout.elements[1].semanticName, "NORMAL");
|
|
|
|
|
EXPECT_EQ(inputLayout.elements[2].semanticName, "TEXCOORD");
|
|
|
|
|
EXPECT_EQ(inputLayout.elements[0].alignedByteOffset, static_cast<uint32_t>(offsetof(StaticMeshVertex, position)));
|
|
|
|
|
EXPECT_EQ(inputLayout.elements[1].alignedByteOffset, static_cast<uint32_t>(offsetof(StaticMeshVertex, normal)));
|
|
|
|
|
EXPECT_EQ(inputLayout.elements[2].alignedByteOffset, static_cast<uint32_t>(offsetof(StaticMeshVertex, uv0)));
|
|
|
|
|
}
|