2026-03-30 02:22:17 +08:00
|
|
|
#include <gtest/gtest.h>
|
|
|
|
|
|
2026-04-10 23:11:11 +08:00
|
|
|
#include "Rendering/Internal/ShaderVariantUtils.h"
|
2026-04-07 03:35:06 +08:00
|
|
|
|
2026-04-05 22:02:52 +08:00
|
|
|
#include <XCEngine/Rendering/Builtin/BuiltinPassLayoutUtils.h>
|
2026-04-10 23:11:11 +08:00
|
|
|
#include "Rendering/Internal/RenderSurfacePipelineUtils.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-10 23:11:11 +08:00
|
|
|
#include <XCEngine/Rendering/RenderSurface.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>
|
2026-04-07 03:35:06 +08:00
|
|
|
#include <XCEngine/RHI/ShaderCompiler/SpirvShaderCompiler.h>
|
|
|
|
|
|
|
|
|
|
#include <fstream>
|
2026-04-07 10:34:20 +08:00
|
|
|
#include <regex>
|
2026-03-30 02:22:17 +08:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
2026-04-07 10:34:20 +08:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
std::string EscapeRegexLiteralForTest(const std::string& value) {
|
|
|
|
|
std::string escaped;
|
|
|
|
|
escaped.reserve(value.size() * 2u);
|
|
|
|
|
for (const char ch : value) {
|
|
|
|
|
switch (ch) {
|
|
|
|
|
case '\\':
|
|
|
|
|
case '^':
|
|
|
|
|
case '$':
|
|
|
|
|
case '.':
|
|
|
|
|
case '|':
|
|
|
|
|
case '?':
|
|
|
|
|
case '*':
|
|
|
|
|
case '+':
|
|
|
|
|
case '(':
|
|
|
|
|
case ')':
|
|
|
|
|
case '[':
|
|
|
|
|
case ']':
|
|
|
|
|
case '{':
|
|
|
|
|
case '}':
|
|
|
|
|
escaped.push_back('\\');
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
escaped.push_back(ch);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return escaped;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
::testing::AssertionResult SourceContainsRegisterBinding(
|
|
|
|
|
const std::string& source,
|
|
|
|
|
const std::string& declaration,
|
|
|
|
|
const std::string& registerClause) {
|
|
|
|
|
const std::regex pattern(
|
|
|
|
|
EscapeRegexLiteralForTest(declaration) + "\\s*:\\s*" +
|
|
|
|
|
EscapeRegexLiteralForTest(registerClause),
|
|
|
|
|
std::regex::ECMAScript);
|
|
|
|
|
if (std::regex_search(source, pattern)) {
|
|
|
|
|
return ::testing::AssertionSuccess();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ::testing::AssertionFailure()
|
|
|
|
|
<< "Missing binding '" << declaration << " : " << registerClause << "' in source:\n"
|
|
|
|
|
<< source;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 04:44:36 +08:00
|
|
|
void AppendDefaultBuiltinPassResources(ShaderPass& pass) {
|
|
|
|
|
Array<ShaderResourceBindingDesc> bindings;
|
|
|
|
|
const bool resolved = TryBuildBuiltinPassDefaultResourceBindings(pass, bindings);
|
|
|
|
|
EXPECT_TRUE(resolved);
|
|
|
|
|
if (!resolved) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const ShaderResourceBindingDesc& binding : bindings) {
|
|
|
|
|
pass.resources.PushBack(binding);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 23:11:11 +08:00
|
|
|
class TestResourceView final : public XCEngine::RHI::RHIResourceView {
|
|
|
|
|
public:
|
|
|
|
|
TestResourceView(
|
|
|
|
|
XCEngine::RHI::ResourceViewType viewType,
|
|
|
|
|
XCEngine::RHI::ResourceViewDimension dimension,
|
|
|
|
|
XCEngine::RHI::Format format)
|
|
|
|
|
: m_viewType(viewType),
|
|
|
|
|
m_dimension(dimension),
|
|
|
|
|
m_format(format) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Shutdown() override {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void* GetNativeHandle() override {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool IsValid() const override {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
XCEngine::RHI::ResourceViewType GetViewType() const override {
|
|
|
|
|
return m_viewType;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
XCEngine::RHI::ResourceViewDimension GetDimension() const override {
|
|
|
|
|
return m_dimension;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
XCEngine::RHI::Format GetFormat() const override {
|
|
|
|
|
return m_format;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
XCEngine::RHI::ResourceViewType m_viewType;
|
|
|
|
|
XCEngine::RHI::ResourceViewDimension m_dimension;
|
|
|
|
|
XCEngine::RHI::Format m_format;
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-07 10:34:20 +08:00
|
|
|
} // namespace
|
|
|
|
|
|
2026-03-30 02:22:17 +08:00
|
|
|
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-10 23:11:11 +08:00
|
|
|
TEST(RenderSurfacePipelineUtils_Test, ResolvesContiguousSurfaceAttachmentFormatsIntoPipelineDesc) {
|
|
|
|
|
TestResourceView color0(
|
|
|
|
|
ResourceViewType::RenderTarget,
|
|
|
|
|
ResourceViewDimension::Texture2D,
|
|
|
|
|
Format::R16G16B16A16_Float);
|
|
|
|
|
TestResourceView color1(
|
|
|
|
|
ResourceViewType::RenderTarget,
|
|
|
|
|
ResourceViewDimension::Texture2D,
|
|
|
|
|
Format::R8G8B8A8_UNorm);
|
|
|
|
|
TestResourceView depth(
|
|
|
|
|
ResourceViewType::DepthStencil,
|
|
|
|
|
ResourceViewDimension::Texture2D,
|
|
|
|
|
Format::D32_Float);
|
|
|
|
|
|
|
|
|
|
RenderSurface surface(1280u, 720u);
|
|
|
|
|
surface.SetColorAttachments({ &color0, &color1 });
|
|
|
|
|
surface.SetDepthAttachment(&depth);
|
|
|
|
|
surface.SetSampleDesc(4u, 0u);
|
|
|
|
|
|
|
|
|
|
GraphicsPipelineDesc pipelineDesc = {};
|
|
|
|
|
XCEngine::Rendering::Internal::ApplySurfacePropertiesToGraphicsPipelineDesc(surface, pipelineDesc);
|
|
|
|
|
|
|
|
|
|
EXPECT_EQ(pipelineDesc.renderTargetCount, 2u);
|
|
|
|
|
EXPECT_EQ(static_cast<Format>(pipelineDesc.renderTargetFormats[0]), Format::R16G16B16A16_Float);
|
|
|
|
|
EXPECT_EQ(static_cast<Format>(pipelineDesc.renderTargetFormats[1]), Format::R8G8B8A8_UNorm);
|
|
|
|
|
EXPECT_EQ(static_cast<Format>(pipelineDesc.depthStencilFormat), Format::D32_Float);
|
|
|
|
|
EXPECT_EQ(pipelineDesc.sampleCount, 4u);
|
|
|
|
|
EXPECT_EQ(pipelineDesc.sampleQuality, 0u);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(RenderSurfacePipelineUtils_Test, PropagatesSampleQualityIntoPipelineDesc) {
|
|
|
|
|
TestResourceView color0(
|
|
|
|
|
ResourceViewType::RenderTarget,
|
|
|
|
|
ResourceViewDimension::Texture2D,
|
|
|
|
|
Format::R8G8B8A8_UNorm);
|
|
|
|
|
|
|
|
|
|
RenderSurface surface(640u, 360u);
|
|
|
|
|
surface.SetColorAttachment(&color0);
|
|
|
|
|
surface.SetSampleDesc(4u, 3u);
|
|
|
|
|
|
|
|
|
|
GraphicsPipelineDesc pipelineDesc = {};
|
|
|
|
|
XCEngine::Rendering::Internal::ApplySingleColorAttachmentPropertiesToGraphicsPipelineDesc(surface, pipelineDesc);
|
|
|
|
|
|
|
|
|
|
EXPECT_EQ(pipelineDesc.renderTargetCount, 1u);
|
|
|
|
|
EXPECT_EQ(pipelineDesc.sampleCount, 4u);
|
|
|
|
|
EXPECT_EQ(pipelineDesc.sampleQuality, 3u);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(RenderSurfacePipelineUtils_Test, StopsAtFirstNullColorAttachmentWhenResolvingFormats) {
|
|
|
|
|
TestResourceView color0(
|
|
|
|
|
ResourceViewType::RenderTarget,
|
|
|
|
|
ResourceViewDimension::Texture2D,
|
|
|
|
|
Format::R16G16B16A16_Float);
|
|
|
|
|
TestResourceView color2(
|
|
|
|
|
ResourceViewType::RenderTarget,
|
|
|
|
|
ResourceViewDimension::Texture2D,
|
|
|
|
|
Format::R8G8B8A8_UNorm);
|
|
|
|
|
|
|
|
|
|
RenderSurface surface(1280u, 720u);
|
|
|
|
|
surface.SetColorAttachments({ &color0, nullptr, &color2 });
|
|
|
|
|
|
|
|
|
|
const uint32_t colorAttachmentCount =
|
|
|
|
|
XCEngine::Rendering::Internal::ResolveSurfaceColorAttachmentCount(surface);
|
|
|
|
|
const auto colorFormats =
|
|
|
|
|
XCEngine::Rendering::Internal::ResolveSurfaceColorFormats(surface);
|
|
|
|
|
|
|
|
|
|
EXPECT_EQ(colorAttachmentCount, 1u);
|
|
|
|
|
EXPECT_EQ(static_cast<Format>(colorFormats[0]), Format::R16G16B16A16_Float);
|
|
|
|
|
EXPECT_EQ(static_cast<Format>(colorFormats[1]), Format::Unknown);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(RenderSurfacePipelineUtils_Test, NormalizesInvalidSampleCountToSingleSample) {
|
|
|
|
|
RenderSurface surface(640u, 480u);
|
|
|
|
|
surface.SetSampleDesc(0u, 7u);
|
|
|
|
|
|
|
|
|
|
EXPECT_EQ(surface.GetSampleCount(), 1u);
|
|
|
|
|
EXPECT_EQ(surface.GetSampleQuality(), 0u);
|
|
|
|
|
EXPECT_EQ(XCEngine::Rendering::Internal::ResolveSurfaceSampleCount(surface), 1u);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(RenderSurfacePipelineUtils_Test, AcceptsDepthStyleSurfaceWithDepthOnlyOutput) {
|
|
|
|
|
TestResourceView depth(
|
|
|
|
|
ResourceViewType::DepthStencil,
|
|
|
|
|
ResourceViewDimension::Texture2D,
|
|
|
|
|
Format::D32_Float);
|
|
|
|
|
|
|
|
|
|
RenderSurface surface(640u, 480u);
|
|
|
|
|
surface.SetDepthAttachment(&depth);
|
|
|
|
|
surface.SetSampleDesc(1u, 0u);
|
|
|
|
|
|
|
|
|
|
EXPECT_TRUE(XCEngine::Rendering::Internal::IsDepthStyleCompatibleSurface(surface));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(RenderSurfacePipelineUtils_Test, RejectsDepthStyleSurfaceWithoutKnownDepthFormat) {
|
|
|
|
|
TestResourceView color(
|
|
|
|
|
ResourceViewType::RenderTarget,
|
|
|
|
|
ResourceViewDimension::Texture2D,
|
|
|
|
|
Format::R8G8B8A8_UNorm);
|
|
|
|
|
|
|
|
|
|
RenderSurface surface(640u, 480u);
|
|
|
|
|
surface.SetColorAttachment(&color);
|
|
|
|
|
|
|
|
|
|
EXPECT_FALSE(XCEngine::Rendering::Internal::IsDepthStyleCompatibleSurface(surface));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(RenderSurfacePipelineUtils_Test, RejectsDepthStyleSurfaceWithUnknownSingleColorFormat) {
|
|
|
|
|
TestResourceView color(
|
|
|
|
|
ResourceViewType::RenderTarget,
|
|
|
|
|
ResourceViewDimension::Texture2D,
|
|
|
|
|
Format::Unknown);
|
|
|
|
|
TestResourceView depth(
|
|
|
|
|
ResourceViewType::DepthStencil,
|
|
|
|
|
ResourceViewDimension::Texture2D,
|
|
|
|
|
Format::D32_Float);
|
|
|
|
|
|
|
|
|
|
RenderSurface surface(640u, 480u);
|
|
|
|
|
surface.SetColorAttachment(&color);
|
|
|
|
|
surface.SetDepthAttachment(&depth);
|
|
|
|
|
|
|
|
|
|
EXPECT_FALSE(XCEngine::Rendering::Internal::IsDepthStyleCompatibleSurface(surface));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(RenderSurfacePipelineUtils_Test, RejectsDepthStyleSurfaceWithMultipleColorAttachments) {
|
|
|
|
|
TestResourceView color0(
|
|
|
|
|
ResourceViewType::RenderTarget,
|
|
|
|
|
ResourceViewDimension::Texture2D,
|
|
|
|
|
Format::R16G16B16A16_Float);
|
|
|
|
|
TestResourceView color1(
|
|
|
|
|
ResourceViewType::RenderTarget,
|
|
|
|
|
ResourceViewDimension::Texture2D,
|
|
|
|
|
Format::R8G8B8A8_UNorm);
|
|
|
|
|
TestResourceView depth(
|
|
|
|
|
ResourceViewType::DepthStencil,
|
|
|
|
|
ResourceViewDimension::Texture2D,
|
|
|
|
|
Format::D32_Float);
|
|
|
|
|
|
|
|
|
|
RenderSurface surface(640u, 480u);
|
|
|
|
|
surface.SetColorAttachments({ &color0, &color1 });
|
|
|
|
|
surface.SetDepthAttachment(&depth);
|
|
|
|
|
|
|
|
|
|
EXPECT_FALSE(XCEngine::Rendering::Internal::IsDepthStyleCompatibleSurface(surface));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(RenderSurface_Test, DefaultsDepthStateToDepthWriteAndSupportsOverrides) {
|
|
|
|
|
RenderSurface surface(640u, 480u);
|
|
|
|
|
|
|
|
|
|
EXPECT_EQ(surface.GetDepthStateBefore(), ResourceStates::DepthWrite);
|
|
|
|
|
EXPECT_EQ(surface.GetDepthStateAfter(), ResourceStates::DepthWrite);
|
|
|
|
|
|
|
|
|
|
surface.SetDepthStateBefore(ResourceStates::Common);
|
|
|
|
|
surface.SetDepthStateAfter(ResourceStates::PixelShaderResource);
|
|
|
|
|
|
|
|
|
|
EXPECT_EQ(surface.GetDepthStateBefore(), ResourceStates::Common);
|
|
|
|
|
EXPECT_EQ(surface.GetDepthStateAfter(), ResourceStates::PixelShaderResource);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 23:00:33 +08:00
|
|
|
TEST(BuiltinForwardPipeline_Test, SplitsSceneItemsIntoOpaqueAndTransparentQueueRanges) {
|
|
|
|
|
EXPECT_FALSE(IsTransparentRenderQueue(static_cast<int>(MaterialRenderQueue::Background)));
|
|
|
|
|
EXPECT_FALSE(IsTransparentRenderQueue(static_cast<int>(MaterialRenderQueue::Geometry)));
|
|
|
|
|
EXPECT_FALSE(IsTransparentRenderQueue(static_cast<int>(MaterialRenderQueue::AlphaTest)));
|
|
|
|
|
EXPECT_TRUE(IsTransparentRenderQueue(static_cast<int>(MaterialRenderQueue::Transparent)));
|
|
|
|
|
EXPECT_TRUE(IsTransparentRenderQueue(static_cast<int>(MaterialRenderQueue::Overlay)));
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-07 14:13:26 +08:00
|
|
|
TEST(BuiltinForwardPipeline_Test, BuiltinForwardShaderUsesAuthoringSurfaceContract) {
|
2026-04-03 11:51:01 +08:00
|
|
|
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);
|
|
|
|
|
|
2026-04-06 21:05:50 +08:00
|
|
|
const ShaderPropertyDesc* cutoff = shader->FindProperty("_Cutoff");
|
|
|
|
|
ASSERT_NE(cutoff, nullptr);
|
|
|
|
|
EXPECT_EQ(cutoff->type, ShaderPropertyType::Range);
|
|
|
|
|
EXPECT_EQ(cutoff->semantic, "AlphaCutoff");
|
|
|
|
|
|
2026-04-03 11:51:01 +08:00
|
|
|
const ShaderPass* pass = shader->FindPass("ForwardLit");
|
|
|
|
|
ASSERT_NE(pass, nullptr);
|
2026-04-08 04:44:36 +08:00
|
|
|
EXPECT_EQ(pass->resources.Size(), 8u);
|
2026-04-07 03:35:06 +08:00
|
|
|
EXPECT_TRUE(pass->hasFixedFunctionState);
|
|
|
|
|
EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::Back);
|
|
|
|
|
EXPECT_TRUE(pass->fixedFunctionState.depthWriteEnable);
|
|
|
|
|
EXPECT_EQ(pass->fixedFunctionState.depthFunc, MaterialComparisonFunc::LessEqual);
|
2026-04-03 11:51:01 +08:00
|
|
|
|
2026-04-07 10:34:20 +08:00
|
|
|
const ShaderStageVariant* fragmentVariant = shader->FindVariant(
|
|
|
|
|
"ForwardLit",
|
|
|
|
|
XCEngine::Resources::ShaderType::Fragment,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::D3D12);
|
|
|
|
|
ASSERT_NE(fragmentVariant, nullptr);
|
|
|
|
|
EXPECT_EQ(
|
|
|
|
|
std::string(fragmentVariant->sourceCode.CStr()).find("register("),
|
|
|
|
|
std::string::npos);
|
|
|
|
|
|
2026-04-07 03:35:06 +08:00
|
|
|
delete shader;
|
|
|
|
|
}
|
2026-04-03 11:51:01 +08:00
|
|
|
|
2026-04-07 14:13:26 +08:00
|
|
|
TEST(BuiltinForwardPipeline_Test, BuiltinUnlitShaderUsesAuthoringSurfaceContract) {
|
2026-04-07 03:35:06 +08:00
|
|
|
ShaderLoader loader;
|
|
|
|
|
LoadResult result = loader.Load(GetBuiltinUnlitShaderPath());
|
|
|
|
|
ASSERT_TRUE(result);
|
|
|
|
|
ASSERT_NE(result.resource, nullptr);
|
2026-04-03 11:51:01 +08:00
|
|
|
|
2026-04-07 03:35:06 +08:00
|
|
|
Shader* shader = static_cast<Shader*>(result.resource);
|
|
|
|
|
ASSERT_NE(shader, nullptr);
|
2026-04-03 11:51:01 +08:00
|
|
|
|
2026-04-07 03:35:06 +08:00
|
|
|
const ShaderPass* pass = shader->FindPass("Unlit");
|
|
|
|
|
ASSERT_NE(pass, nullptr);
|
2026-04-08 04:44:36 +08:00
|
|
|
EXPECT_EQ(pass->resources.Size(), 4u);
|
2026-04-07 03:35:06 +08:00
|
|
|
EXPECT_TRUE(pass->hasFixedFunctionState);
|
|
|
|
|
EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::Back);
|
|
|
|
|
EXPECT_TRUE(pass->fixedFunctionState.depthWriteEnable);
|
|
|
|
|
EXPECT_EQ(pass->fixedFunctionState.depthFunc, MaterialComparisonFunc::LessEqual);
|
2026-04-03 11:51:01 +08:00
|
|
|
|
2026-04-07 10:34:20 +08:00
|
|
|
const ShaderStageVariant* fragmentVariant = shader->FindVariant(
|
|
|
|
|
"Unlit",
|
|
|
|
|
XCEngine::Resources::ShaderType::Fragment,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::D3D12);
|
|
|
|
|
ASSERT_NE(fragmentVariant, nullptr);
|
|
|
|
|
EXPECT_EQ(
|
|
|
|
|
std::string(fragmentVariant->sourceCode.CStr()).find("register("),
|
|
|
|
|
std::string::npos);
|
|
|
|
|
|
2026-04-07 03:35:06 +08:00
|
|
|
delete shader;
|
|
|
|
|
}
|
2026-04-04 23:01:34 +08:00
|
|
|
|
2026-04-07 03:35:06 +08:00
|
|
|
TEST(BuiltinForwardPipeline_Test, BuiltinUnlitShaderBuildsVulkanRuntimeSourceWithResolvedBindings) {
|
|
|
|
|
ShaderLoader loader;
|
|
|
|
|
LoadResult result = loader.Load(GetBuiltinUnlitShaderPath());
|
|
|
|
|
ASSERT_TRUE(result);
|
|
|
|
|
ASSERT_NE(result.resource, nullptr);
|
2026-04-04 23:01:34 +08:00
|
|
|
|
2026-04-07 03:35:06 +08:00
|
|
|
Shader* shader = static_cast<Shader*>(result.resource);
|
|
|
|
|
ASSERT_NE(shader, nullptr);
|
|
|
|
|
|
|
|
|
|
const ShaderPass* pass = shader->FindPass("Unlit");
|
|
|
|
|
ASSERT_NE(pass, nullptr);
|
2026-04-04 23:01:34 +08:00
|
|
|
|
2026-04-07 03:35:06 +08:00
|
|
|
const ShaderStageVariant* fragmentVariant =
|
|
|
|
|
shader->FindVariant(
|
|
|
|
|
"Unlit",
|
|
|
|
|
XCEngine::Resources::ShaderType::Fragment,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::Vulkan);
|
|
|
|
|
ASSERT_NE(fragmentVariant, nullptr);
|
|
|
|
|
|
|
|
|
|
const std::string runtimeSource =
|
2026-04-10 23:11:11 +08:00
|
|
|
::XCEngine::Rendering::Internal::BuildRuntimeShaderSource(
|
2026-04-07 03:35:06 +08:00
|
|
|
*pass,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::Vulkan,
|
|
|
|
|
*fragmentVariant);
|
|
|
|
|
|
2026-04-07 10:34:20 +08:00
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
runtimeSource,
|
|
|
|
|
"cbuffer MaterialConstants",
|
|
|
|
|
"register(b0, space1)"));
|
2026-04-07 03:35:06 +08:00
|
|
|
EXPECT_NE(runtimeSource.find("BaseColorTexture"), std::string::npos);
|
|
|
|
|
EXPECT_NE(runtimeSource.find("space2"), std::string::npos);
|
|
|
|
|
EXPECT_NE(runtimeSource.find("LinearClampSampler"), std::string::npos);
|
|
|
|
|
EXPECT_NE(runtimeSource.find("space3"), std::string::npos);
|
2026-04-05 15:44:37 +08:00
|
|
|
|
2026-04-03 11:51:01 +08:00
|
|
|
delete shader;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-07 14:13:26 +08:00
|
|
|
TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringUnlitBindingsToDescriptorSpaces) {
|
2026-04-03 17:18:46 +08:00
|
|
|
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);
|
|
|
|
|
|
2026-04-07 03:35:06 +08:00
|
|
|
const ShaderStageVariant* d3d12Fragment = shader->FindVariant(
|
|
|
|
|
"Unlit",
|
|
|
|
|
XCEngine::Resources::ShaderType::Fragment,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::D3D12);
|
|
|
|
|
ASSERT_NE(d3d12Fragment, nullptr);
|
|
|
|
|
|
|
|
|
|
ShaderCompileDesc d3d12CompileDesc = {};
|
2026-04-10 23:11:11 +08:00
|
|
|
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
2026-04-07 03:35:06 +08:00
|
|
|
*pass,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::D3D12,
|
|
|
|
|
*d3d12Fragment,
|
|
|
|
|
d3d12CompileDesc);
|
|
|
|
|
const std::string d3d12Source(
|
|
|
|
|
reinterpret_cast<const char*>(d3d12CompileDesc.source.data()),
|
|
|
|
|
d3d12CompileDesc.source.size());
|
2026-04-07 10:34:20 +08:00
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
d3d12Source,
|
|
|
|
|
"cbuffer MaterialConstants",
|
|
|
|
|
"register(b1)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
d3d12Source,
|
|
|
|
|
"Texture2D BaseColorTexture",
|
|
|
|
|
"register(t0)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
d3d12Source,
|
|
|
|
|
"SamplerState LinearClampSampler",
|
|
|
|
|
"register(s0)"));
|
2026-04-07 03:35:06 +08:00
|
|
|
|
|
|
|
|
const ShaderStageVariant* vulkanFragment = shader->FindVariant(
|
|
|
|
|
"Unlit",
|
|
|
|
|
XCEngine::Resources::ShaderType::Fragment,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::Vulkan);
|
|
|
|
|
ASSERT_NE(vulkanFragment, nullptr);
|
|
|
|
|
|
|
|
|
|
ShaderCompileDesc vulkanCompileDesc = {};
|
2026-04-10 23:11:11 +08:00
|
|
|
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
2026-04-07 03:35:06 +08:00
|
|
|
*pass,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::Vulkan,
|
|
|
|
|
*vulkanFragment,
|
|
|
|
|
vulkanCompileDesc);
|
|
|
|
|
const std::string vulkanSource(
|
|
|
|
|
reinterpret_cast<const char*>(vulkanCompileDesc.source.data()),
|
|
|
|
|
vulkanCompileDesc.source.size());
|
2026-04-07 10:34:20 +08:00
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
vulkanSource,
|
|
|
|
|
"cbuffer MaterialConstants",
|
|
|
|
|
"register(b0, space1)"));
|
2026-04-07 03:35:06 +08:00
|
|
|
EXPECT_NE(vulkanSource.find("BaseColorTexture"), std::string::npos);
|
|
|
|
|
EXPECT_NE(vulkanSource.find("space2"), std::string::npos);
|
|
|
|
|
EXPECT_NE(vulkanSource.find("LinearClampSampler"), std::string::npos);
|
|
|
|
|
EXPECT_NE(vulkanSource.find("space3"), std::string::npos);
|
2026-04-03 17:18:46 +08:00
|
|
|
|
2026-04-07 03:35:06 +08:00
|
|
|
delete shader;
|
|
|
|
|
}
|
2026-04-03 17:18:46 +08:00
|
|
|
|
2026-04-07 14:13:26 +08:00
|
|
|
TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringForwardBindingsToDescriptorSpaces) {
|
2026-04-07 03:35:06 +08:00
|
|
|
ShaderLoader loader;
|
|
|
|
|
LoadResult result = loader.Load(GetBuiltinForwardLitShaderPath());
|
|
|
|
|
ASSERT_TRUE(result);
|
|
|
|
|
ASSERT_NE(result.resource, nullptr);
|
2026-04-03 17:18:46 +08:00
|
|
|
|
2026-04-07 03:35:06 +08:00
|
|
|
Shader* shader = static_cast<Shader*>(result.resource);
|
|
|
|
|
ASSERT_NE(shader, nullptr);
|
|
|
|
|
|
|
|
|
|
const ShaderPass* pass = shader->FindPass("ForwardLit");
|
|
|
|
|
ASSERT_NE(pass, nullptr);
|
|
|
|
|
|
|
|
|
|
ShaderKeywordSet shadowKeywords = {};
|
|
|
|
|
shadowKeywords.enabledKeywords.PushBack("XC_MAIN_LIGHT_SHADOWS");
|
|
|
|
|
|
|
|
|
|
const ShaderStageVariant* d3d12Fragment = shader->FindVariant(
|
|
|
|
|
"ForwardLit",
|
|
|
|
|
XCEngine::Resources::ShaderType::Fragment,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::D3D12,
|
|
|
|
|
shadowKeywords);
|
|
|
|
|
ASSERT_NE(d3d12Fragment, nullptr);
|
|
|
|
|
|
|
|
|
|
ShaderCompileDesc d3d12CompileDesc = {};
|
2026-04-10 23:11:11 +08:00
|
|
|
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
2026-04-07 03:35:06 +08:00
|
|
|
*pass,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::D3D12,
|
|
|
|
|
*d3d12Fragment,
|
|
|
|
|
d3d12CompileDesc);
|
|
|
|
|
const std::string d3d12Source(
|
|
|
|
|
reinterpret_cast<const char*>(d3d12CompileDesc.source.data()),
|
|
|
|
|
d3d12CompileDesc.source.size());
|
2026-04-07 10:34:20 +08:00
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
d3d12Source,
|
|
|
|
|
"cbuffer PerObjectConstants",
|
|
|
|
|
"register(b0)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
d3d12Source,
|
|
|
|
|
"cbuffer LightingConstants",
|
|
|
|
|
"register(b1)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
d3d12Source,
|
|
|
|
|
"cbuffer MaterialConstants",
|
|
|
|
|
"register(b2)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
d3d12Source,
|
|
|
|
|
"cbuffer ShadowReceiverConstants",
|
|
|
|
|
"register(b3)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
d3d12Source,
|
|
|
|
|
"Texture2D BaseColorTexture",
|
|
|
|
|
"register(t0)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
d3d12Source,
|
|
|
|
|
"SamplerState LinearClampSampler",
|
|
|
|
|
"register(s0)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
d3d12Source,
|
|
|
|
|
"Texture2D ShadowMapTexture",
|
|
|
|
|
"register(t1)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
d3d12Source,
|
|
|
|
|
"SamplerState ShadowMapSampler",
|
|
|
|
|
"register(s1)"));
|
|
|
|
|
EXPECT_EQ(d3d12Source.find("space0"), std::string::npos);
|
2026-04-07 03:35:06 +08:00
|
|
|
EXPECT_EQ(d3d12Source.find("space1"), std::string::npos);
|
|
|
|
|
|
|
|
|
|
const ShaderStageVariant* vulkanFragment = shader->FindVariant(
|
|
|
|
|
"ForwardLit",
|
|
|
|
|
XCEngine::Resources::ShaderType::Fragment,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::Vulkan,
|
|
|
|
|
shadowKeywords);
|
|
|
|
|
ASSERT_NE(vulkanFragment, nullptr);
|
|
|
|
|
|
|
|
|
|
ShaderCompileDesc vulkanCompileDesc = {};
|
2026-04-10 23:11:11 +08:00
|
|
|
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
2026-04-07 03:35:06 +08:00
|
|
|
*pass,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::Vulkan,
|
|
|
|
|
*vulkanFragment,
|
|
|
|
|
vulkanCompileDesc);
|
|
|
|
|
const std::string vulkanSource(
|
|
|
|
|
reinterpret_cast<const char*>(vulkanCompileDesc.source.data()),
|
|
|
|
|
vulkanCompileDesc.source.size());
|
2026-04-07 10:34:20 +08:00
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
vulkanSource,
|
|
|
|
|
"cbuffer PerObjectConstants",
|
|
|
|
|
"register(b0, space0)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
vulkanSource,
|
|
|
|
|
"cbuffer LightingConstants",
|
|
|
|
|
"register(b0, space1)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
vulkanSource,
|
|
|
|
|
"cbuffer MaterialConstants",
|
|
|
|
|
"register(b0, space2)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
vulkanSource,
|
|
|
|
|
"cbuffer ShadowReceiverConstants",
|
|
|
|
|
"register(b0, space3)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
vulkanSource,
|
|
|
|
|
"Texture2D BaseColorTexture",
|
|
|
|
|
"register(t0, space4)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
vulkanSource,
|
|
|
|
|
"SamplerState LinearClampSampler",
|
|
|
|
|
"register(s0, space5)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
vulkanSource,
|
|
|
|
|
"Texture2D ShadowMapTexture",
|
|
|
|
|
"register(t0, space6)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
vulkanSource,
|
|
|
|
|
"SamplerState ShadowMapSampler",
|
|
|
|
|
"register(s0, space7)"));
|
2026-04-07 03:35:06 +08:00
|
|
|
|
|
|
|
|
delete shader;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(BuiltinForwardPipeline_Test, OpenGLRuntimeTranspilesForwardShadowVariantToLegacyClipConventions) {
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
ShaderKeywordSet shadowKeywords = {};
|
|
|
|
|
shadowKeywords.enabledKeywords.PushBack("XC_MAIN_LIGHT_SHADOWS");
|
|
|
|
|
|
|
|
|
|
const ShaderStageVariant* openGLFragment = shader->FindVariant(
|
|
|
|
|
"ForwardLit",
|
|
|
|
|
XCEngine::Resources::ShaderType::Fragment,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::OpenGL,
|
|
|
|
|
shadowKeywords);
|
|
|
|
|
ASSERT_NE(openGLFragment, nullptr);
|
|
|
|
|
|
|
|
|
|
ShaderCompileDesc compileDesc = {};
|
2026-04-10 23:11:11 +08:00
|
|
|
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
2026-04-07 03:35:06 +08:00
|
|
|
*pass,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::OpenGL,
|
|
|
|
|
*openGLFragment,
|
|
|
|
|
compileDesc);
|
|
|
|
|
const std::string runtimeSource(
|
|
|
|
|
reinterpret_cast<const char*>(compileDesc.source.data()),
|
|
|
|
|
compileDesc.source.size());
|
|
|
|
|
EXPECT_NE(runtimeSource.find("const float shadowUvY = shadowNdc.y * 0.5f + 0.5f;"), std::string::npos);
|
|
|
|
|
EXPECT_NE(
|
|
|
|
|
runtimeSource.find("shadowNdc.z < -1.0f || shadowNdc.z > 1.0f"),
|
|
|
|
|
std::string::npos);
|
|
|
|
|
EXPECT_NE(
|
|
|
|
|
runtimeSource.find(
|
|
|
|
|
"const float receiverDepth = shadowNdc.z * 0.5f + 0.5f - gShadowBiasAndTexelSize.x;"),
|
|
|
|
|
std::string::npos);
|
|
|
|
|
|
|
|
|
|
XCEngine::RHI::CompiledSpirvShader spirvShader = {};
|
|
|
|
|
std::string errorMessage;
|
|
|
|
|
ASSERT_TRUE(
|
|
|
|
|
XCEngine::RHI::CompileSpirvShader(
|
|
|
|
|
compileDesc,
|
|
|
|
|
XCEngine::RHI::SpirvTargetEnvironment::Vulkan,
|
|
|
|
|
spirvShader,
|
|
|
|
|
&errorMessage))
|
|
|
|
|
<< errorMessage;
|
|
|
|
|
|
|
|
|
|
std::string glslSource;
|
|
|
|
|
ASSERT_TRUE(XCEngine::RHI::TranspileSpirvToOpenGLGLSL(spirvShader, glslSource, &errorMessage))
|
|
|
|
|
<< errorMessage;
|
|
|
|
|
EXPECT_NE(
|
|
|
|
|
glslSource.find("uniform sampler2D SPIRV_Cross_CombinedBaseColorTextureLinearClampSampler;"),
|
|
|
|
|
std::string::npos);
|
|
|
|
|
EXPECT_NE(
|
|
|
|
|
glslSource.find("uniform sampler2D SPIRV_Cross_CombinedShadowMapTextureShadowMapSampler;"),
|
|
|
|
|
std::string::npos);
|
|
|
|
|
EXPECT_NE(
|
|
|
|
|
glslSource.find("spvWorkaroundRowMajor(ShadowReceiverConstants.gWorldToShadowMatrix)"),
|
|
|
|
|
std::string::npos);
|
|
|
|
|
EXPECT_NE(glslSource.find("texture(SPIRV_Cross_CombinedShadowMapTextureShadowMapSampler"), std::string::npos);
|
|
|
|
|
|
|
|
|
|
delete shader;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(BuiltinDepthStylePass_Test, OpenGLRuntimeTranspilesDepthOnlyAlphaVariantWithTexcoordAtLocation2) {
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
ShaderKeywordSet alphaKeywords = {};
|
|
|
|
|
alphaKeywords.enabledKeywords.PushBack("XC_ALPHA_TEST");
|
|
|
|
|
|
|
|
|
|
const ShaderStageVariant* openGLVertex = shader->FindVariant(
|
|
|
|
|
"DepthOnly",
|
|
|
|
|
XCEngine::Resources::ShaderType::Vertex,
|
|
|
|
|
ShaderBackend::OpenGL,
|
|
|
|
|
alphaKeywords);
|
|
|
|
|
ASSERT_NE(openGLVertex, nullptr);
|
|
|
|
|
|
|
|
|
|
const ShaderStageVariant* openGLFragment = shader->FindVariant(
|
|
|
|
|
"DepthOnly",
|
|
|
|
|
XCEngine::Resources::ShaderType::Fragment,
|
|
|
|
|
ShaderBackend::OpenGL,
|
|
|
|
|
alphaKeywords);
|
|
|
|
|
ASSERT_NE(openGLFragment, nullptr);
|
|
|
|
|
|
|
|
|
|
auto transpileStage = [](const ShaderPass& targetPass,
|
|
|
|
|
const ShaderStageVariant& variant,
|
|
|
|
|
std::string& glslSource) {
|
|
|
|
|
ShaderCompileDesc compileDesc = {};
|
2026-04-10 23:11:11 +08:00
|
|
|
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
2026-04-07 03:35:06 +08:00
|
|
|
targetPass,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::OpenGL,
|
|
|
|
|
variant,
|
|
|
|
|
compileDesc);
|
|
|
|
|
|
|
|
|
|
XCEngine::RHI::CompiledSpirvShader spirvShader = {};
|
|
|
|
|
std::string errorMessage;
|
|
|
|
|
EXPECT_TRUE(
|
|
|
|
|
XCEngine::RHI::CompileSpirvShader(
|
|
|
|
|
compileDesc,
|
|
|
|
|
XCEngine::RHI::SpirvTargetEnvironment::Vulkan,
|
|
|
|
|
spirvShader,
|
|
|
|
|
&errorMessage))
|
|
|
|
|
<< errorMessage;
|
|
|
|
|
EXPECT_TRUE(XCEngine::RHI::TranspileSpirvToOpenGLGLSL(spirvShader, glslSource, &errorMessage))
|
|
|
|
|
<< errorMessage;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
std::string vertexGlsl;
|
|
|
|
|
transpileStage(*pass, *openGLVertex, vertexGlsl);
|
|
|
|
|
EXPECT_NE(vertexGlsl.find("layout(location = 2) in vec2"), std::string::npos);
|
|
|
|
|
|
|
|
|
|
std::string fragmentGlsl;
|
|
|
|
|
transpileStage(*pass, *openGLFragment, fragmentGlsl);
|
|
|
|
|
EXPECT_NE(fragmentGlsl.find("discard;"), std::string::npos);
|
|
|
|
|
EXPECT_NE(fragmentGlsl.find("gl_FragDepth"), std::string::npos);
|
|
|
|
|
|
|
|
|
|
delete shader;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(BuiltinDepthStylePass_Test, OpenGLRuntimeTranspilesShadowCasterAlphaVariantWithTexcoordAtLocation2) {
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
ShaderKeywordSet alphaKeywords = {};
|
|
|
|
|
alphaKeywords.enabledKeywords.PushBack("XC_ALPHA_TEST");
|
|
|
|
|
|
|
|
|
|
const ShaderStageVariant* openGLVertex = shader->FindVariant(
|
|
|
|
|
"ShadowCaster",
|
|
|
|
|
XCEngine::Resources::ShaderType::Vertex,
|
|
|
|
|
ShaderBackend::OpenGL,
|
|
|
|
|
alphaKeywords);
|
|
|
|
|
ASSERT_NE(openGLVertex, nullptr);
|
|
|
|
|
|
|
|
|
|
const ShaderStageVariant* openGLFragment = shader->FindVariant(
|
|
|
|
|
"ShadowCaster",
|
|
|
|
|
XCEngine::Resources::ShaderType::Fragment,
|
|
|
|
|
ShaderBackend::OpenGL,
|
|
|
|
|
alphaKeywords);
|
|
|
|
|
ASSERT_NE(openGLFragment, nullptr);
|
|
|
|
|
|
|
|
|
|
auto transpileStage = [](const ShaderPass& targetPass,
|
|
|
|
|
const ShaderStageVariant& variant,
|
|
|
|
|
std::string& glslSource) {
|
|
|
|
|
ShaderCompileDesc compileDesc = {};
|
2026-04-10 23:11:11 +08:00
|
|
|
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
2026-04-07 03:35:06 +08:00
|
|
|
targetPass,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::OpenGL,
|
|
|
|
|
variant,
|
|
|
|
|
compileDesc);
|
|
|
|
|
|
|
|
|
|
XCEngine::RHI::CompiledSpirvShader spirvShader = {};
|
|
|
|
|
std::string errorMessage;
|
|
|
|
|
EXPECT_TRUE(
|
|
|
|
|
XCEngine::RHI::CompileSpirvShader(
|
|
|
|
|
compileDesc,
|
|
|
|
|
XCEngine::RHI::SpirvTargetEnvironment::Vulkan,
|
|
|
|
|
spirvShader,
|
|
|
|
|
&errorMessage))
|
|
|
|
|
<< errorMessage;
|
|
|
|
|
EXPECT_TRUE(XCEngine::RHI::TranspileSpirvToOpenGLGLSL(spirvShader, glslSource, &errorMessage))
|
|
|
|
|
<< errorMessage;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
std::string vertexGlsl;
|
|
|
|
|
transpileStage(*pass, *openGLVertex, vertexGlsl);
|
|
|
|
|
EXPECT_NE(vertexGlsl.find("layout(location = 2) in vec2"), std::string::npos);
|
|
|
|
|
|
|
|
|
|
std::string fragmentGlsl;
|
|
|
|
|
transpileStage(*pass, *openGLFragment, fragmentGlsl);
|
|
|
|
|
EXPECT_NE(fragmentGlsl.find("discard;"), std::string::npos);
|
|
|
|
|
EXPECT_NE(fragmentGlsl.find("gl_FragDepth"), std::string::npos);
|
2026-04-03 17:18:46 +08:00
|
|
|
|
|
|
|
|
delete shader;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-07 14:13:26 +08:00
|
|
|
TEST(BuiltinForwardPipeline_Test, BuiltinSkyboxShaderUsesAuthoringContract) {
|
2026-04-05 23:44:32 +08:00
|
|
|
ShaderLoader loader;
|
|
|
|
|
LoadResult result = loader.Load(GetBuiltinSkyboxShaderPath());
|
|
|
|
|
ASSERT_TRUE(result);
|
|
|
|
|
ASSERT_NE(result.resource, nullptr);
|
|
|
|
|
|
|
|
|
|
Shader* shader = static_cast<Shader*>(result.resource);
|
|
|
|
|
ASSERT_NE(shader, nullptr);
|
|
|
|
|
|
|
|
|
|
const ShaderPass* pass = shader->FindPass("Skybox");
|
|
|
|
|
ASSERT_NE(pass, nullptr);
|
2026-04-08 04:44:36 +08:00
|
|
|
EXPECT_EQ(pass->resources.Size(), 5u);
|
2026-04-07 04:30:26 +08:00
|
|
|
EXPECT_TRUE(pass->hasFixedFunctionState);
|
|
|
|
|
EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::None);
|
|
|
|
|
EXPECT_FALSE(pass->fixedFunctionState.depthWriteEnable);
|
|
|
|
|
EXPECT_EQ(pass->fixedFunctionState.depthFunc, MaterialComparisonFunc::LessEqual);
|
2026-04-06 00:39:08 +08:00
|
|
|
|
|
|
|
|
const ShaderPropertyDesc* tint = shader->FindProperty("_Tint");
|
|
|
|
|
ASSERT_NE(tint, nullptr);
|
|
|
|
|
EXPECT_EQ(tint->type, ShaderPropertyType::Color);
|
|
|
|
|
EXPECT_EQ(tint->semantic, "Tint");
|
|
|
|
|
|
|
|
|
|
const ShaderPropertyDesc* exposure = shader->FindProperty("_Exposure");
|
|
|
|
|
ASSERT_NE(exposure, nullptr);
|
|
|
|
|
EXPECT_EQ(exposure->type, ShaderPropertyType::Float);
|
|
|
|
|
EXPECT_EQ(exposure->semantic, "Exposure");
|
|
|
|
|
|
|
|
|
|
const ShaderPropertyDesc* rotation = shader->FindProperty("_Rotation");
|
|
|
|
|
ASSERT_NE(rotation, nullptr);
|
|
|
|
|
EXPECT_EQ(rotation->type, ShaderPropertyType::Float);
|
|
|
|
|
EXPECT_EQ(rotation->semantic, "Rotation");
|
|
|
|
|
|
2026-04-06 02:42:08 +08:00
|
|
|
const ShaderPropertyDesc* panoramic = shader->FindProperty("_MainTex");
|
|
|
|
|
ASSERT_NE(panoramic, nullptr);
|
|
|
|
|
EXPECT_EQ(panoramic->type, ShaderPropertyType::Texture2D);
|
|
|
|
|
EXPECT_EQ(panoramic->semantic, "SkyboxPanoramicTexture");
|
|
|
|
|
|
2026-04-06 01:37:04 +08:00
|
|
|
const ShaderPropertyDesc* cubemap = shader->FindProperty("_Tex");
|
|
|
|
|
ASSERT_NE(cubemap, nullptr);
|
|
|
|
|
EXPECT_EQ(cubemap->type, ShaderPropertyType::TextureCube);
|
|
|
|
|
EXPECT_EQ(cubemap->semantic, "SkyboxTexture");
|
2026-04-05 23:44:32 +08:00
|
|
|
|
2026-04-07 04:30:26 +08:00
|
|
|
delete shader;
|
|
|
|
|
}
|
2026-04-05 23:44:32 +08:00
|
|
|
|
2026-04-08 04:44:36 +08:00
|
|
|
TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoadedSkyboxShaderContract) {
|
2026-04-07 04:30:26 +08:00
|
|
|
ShaderLoader loader;
|
|
|
|
|
LoadResult result = loader.Load(GetBuiltinSkyboxShaderPath());
|
|
|
|
|
ASSERT_TRUE(result);
|
|
|
|
|
ASSERT_NE(result.resource, nullptr);
|
2026-04-06 00:39:08 +08:00
|
|
|
|
2026-04-07 04:30:26 +08:00
|
|
|
Shader* shader = static_cast<Shader*>(result.resource);
|
|
|
|
|
ASSERT_NE(shader, nullptr);
|
2026-04-06 00:39:08 +08:00
|
|
|
|
2026-04-07 04:30:26 +08:00
|
|
|
const ShaderPass* pass = shader->FindPass("Skybox");
|
|
|
|
|
ASSERT_NE(pass, nullptr);
|
2026-04-06 00:39:08 +08:00
|
|
|
|
2026-04-07 04:30:26 +08:00
|
|
|
BuiltinPassResourceBindingPlan plan = {};
|
|
|
|
|
String error;
|
|
|
|
|
EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr();
|
|
|
|
|
ASSERT_EQ(plan.bindings.Size(), 5u);
|
|
|
|
|
EXPECT_TRUE(plan.environment.IsValid());
|
|
|
|
|
EXPECT_TRUE(plan.material.IsValid());
|
|
|
|
|
EXPECT_TRUE(plan.skyboxPanoramicTexture.IsValid());
|
|
|
|
|
EXPECT_TRUE(plan.skyboxTexture.IsValid());
|
|
|
|
|
EXPECT_TRUE(plan.linearClampSampler.IsValid());
|
|
|
|
|
EXPECT_EQ(plan.firstDescriptorSet, 0u);
|
|
|
|
|
EXPECT_EQ(plan.descriptorSetCount, 5u);
|
2026-04-06 02:42:08 +08:00
|
|
|
|
2026-04-05 23:44:32 +08:00
|
|
|
delete shader;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 23:11:11 +08:00
|
|
|
TEST(BuiltinForwardPipeline_Test, BuiltinGaussianSplatShaderUsesAuthoringContract) {
|
|
|
|
|
ShaderLoader loader;
|
|
|
|
|
LoadResult result = loader.Load(GetBuiltinGaussianSplatShaderPath());
|
|
|
|
|
ASSERT_TRUE(result);
|
|
|
|
|
ASSERT_NE(result.resource, nullptr);
|
|
|
|
|
|
|
|
|
|
Shader* shader = static_cast<Shader*>(result.resource);
|
|
|
|
|
ASSERT_NE(shader, nullptr);
|
|
|
|
|
|
|
|
|
|
const ShaderPass* pass = shader->FindPass("GaussianSplat");
|
|
|
|
|
ASSERT_NE(pass, nullptr);
|
2026-04-11 03:02:30 +08:00
|
|
|
EXPECT_EQ(pass->resources.Size(), 6u);
|
2026-04-10 23:11:11 +08:00
|
|
|
EXPECT_TRUE(pass->hasFixedFunctionState);
|
|
|
|
|
EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::None);
|
|
|
|
|
EXPECT_FALSE(pass->fixedFunctionState.depthWriteEnable);
|
|
|
|
|
EXPECT_EQ(pass->fixedFunctionState.depthFunc, MaterialComparisonFunc::LessEqual);
|
|
|
|
|
EXPECT_TRUE(pass->fixedFunctionState.blendEnable);
|
|
|
|
|
|
|
|
|
|
const ShaderPropertyDesc* pointScale = shader->FindProperty("_PointScale");
|
|
|
|
|
ASSERT_NE(pointScale, nullptr);
|
|
|
|
|
EXPECT_EQ(pointScale->type, ShaderPropertyType::Float);
|
|
|
|
|
|
|
|
|
|
const ShaderPropertyDesc* opacityScale = shader->FindProperty("_OpacityScale");
|
|
|
|
|
ASSERT_NE(opacityScale, nullptr);
|
|
|
|
|
EXPECT_EQ(opacityScale->type, ShaderPropertyType::Float);
|
|
|
|
|
|
2026-04-11 03:02:30 +08:00
|
|
|
const ShaderResourceBindingDesc* order =
|
|
|
|
|
shader->FindPassResourceBinding("GaussianSplat", "GaussianSplatOrderBuffer");
|
|
|
|
|
ASSERT_NE(order, nullptr);
|
|
|
|
|
EXPECT_EQ(order->type, ShaderResourceType::StructuredBuffer);
|
|
|
|
|
EXPECT_EQ(order->set, 2u);
|
|
|
|
|
EXPECT_EQ(order->binding, 0u);
|
|
|
|
|
EXPECT_EQ(
|
|
|
|
|
ResolveBuiltinPassResourceSemantic(*order),
|
|
|
|
|
BuiltinPassResourceSemantic::GaussianSplatOrderBuffer);
|
|
|
|
|
|
2026-04-10 23:11:11 +08:00
|
|
|
const ShaderResourceBindingDesc* positions =
|
|
|
|
|
shader->FindPassResourceBinding("GaussianSplat", "GaussianSplatPositions");
|
|
|
|
|
ASSERT_NE(positions, nullptr);
|
|
|
|
|
EXPECT_EQ(positions->type, ShaderResourceType::StructuredBuffer);
|
|
|
|
|
EXPECT_EQ(positions->set, 2u);
|
2026-04-11 03:02:30 +08:00
|
|
|
EXPECT_EQ(positions->binding, 1u);
|
2026-04-10 23:11:11 +08:00
|
|
|
EXPECT_EQ(
|
|
|
|
|
ResolveBuiltinPassResourceSemantic(*positions),
|
|
|
|
|
BuiltinPassResourceSemantic::GaussianSplatPositionBuffer);
|
|
|
|
|
|
|
|
|
|
const ShaderResourceBindingDesc* other =
|
|
|
|
|
shader->FindPassResourceBinding("GaussianSplat", "GaussianSplatOther");
|
|
|
|
|
ASSERT_NE(other, nullptr);
|
|
|
|
|
EXPECT_EQ(other->type, ShaderResourceType::StructuredBuffer);
|
|
|
|
|
EXPECT_EQ(other->set, 2u);
|
2026-04-11 03:02:30 +08:00
|
|
|
EXPECT_EQ(other->binding, 2u);
|
2026-04-10 23:11:11 +08:00
|
|
|
EXPECT_EQ(
|
|
|
|
|
ResolveBuiltinPassResourceSemantic(*other),
|
|
|
|
|
BuiltinPassResourceSemantic::GaussianSplatOtherBuffer);
|
|
|
|
|
|
|
|
|
|
const ShaderResourceBindingDesc* color =
|
|
|
|
|
shader->FindPassResourceBinding("GaussianSplat", "GaussianSplatColor");
|
|
|
|
|
ASSERT_NE(color, nullptr);
|
|
|
|
|
EXPECT_EQ(color->type, ShaderResourceType::StructuredBuffer);
|
|
|
|
|
EXPECT_EQ(color->set, 2u);
|
2026-04-11 03:02:30 +08:00
|
|
|
EXPECT_EQ(color->binding, 3u);
|
2026-04-10 23:11:11 +08:00
|
|
|
EXPECT_EQ(
|
|
|
|
|
ResolveBuiltinPassResourceSemantic(*color),
|
|
|
|
|
BuiltinPassResourceSemantic::GaussianSplatColorBuffer);
|
|
|
|
|
|
|
|
|
|
delete shader;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoadedGaussianSplatShaderContract) {
|
|
|
|
|
ShaderLoader loader;
|
|
|
|
|
LoadResult result = loader.Load(GetBuiltinGaussianSplatShaderPath());
|
|
|
|
|
ASSERT_TRUE(result);
|
|
|
|
|
ASSERT_NE(result.resource, nullptr);
|
|
|
|
|
|
|
|
|
|
Shader* shader = static_cast<Shader*>(result.resource);
|
|
|
|
|
ASSERT_NE(shader, nullptr);
|
|
|
|
|
|
|
|
|
|
const ShaderPass* pass = shader->FindPass("GaussianSplat");
|
|
|
|
|
ASSERT_NE(pass, nullptr);
|
|
|
|
|
|
|
|
|
|
BuiltinPassResourceBindingPlan plan = {};
|
|
|
|
|
String error;
|
|
|
|
|
EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr();
|
2026-04-11 03:02:30 +08:00
|
|
|
ASSERT_EQ(plan.bindings.Size(), 6u);
|
2026-04-10 23:11:11 +08:00
|
|
|
EXPECT_TRUE(plan.perObject.IsValid());
|
|
|
|
|
EXPECT_TRUE(plan.material.IsValid());
|
2026-04-11 03:02:30 +08:00
|
|
|
EXPECT_TRUE(plan.gaussianSplatOrderBuffer.IsValid());
|
2026-04-10 23:11:11 +08:00
|
|
|
EXPECT_TRUE(plan.gaussianSplatPositionBuffer.IsValid());
|
|
|
|
|
EXPECT_TRUE(plan.gaussianSplatOtherBuffer.IsValid());
|
|
|
|
|
EXPECT_TRUE(plan.gaussianSplatColorBuffer.IsValid());
|
|
|
|
|
EXPECT_FALSE(plan.gaussianSplatSHBuffer.IsValid());
|
|
|
|
|
EXPECT_EQ(plan.perObject.set, 0u);
|
|
|
|
|
EXPECT_EQ(plan.material.set, 1u);
|
2026-04-11 03:02:30 +08:00
|
|
|
EXPECT_EQ(plan.gaussianSplatOrderBuffer.set, 2u);
|
|
|
|
|
EXPECT_EQ(plan.gaussianSplatOrderBuffer.binding, 0u);
|
2026-04-10 23:11:11 +08:00
|
|
|
EXPECT_EQ(plan.gaussianSplatPositionBuffer.set, 2u);
|
2026-04-11 03:02:30 +08:00
|
|
|
EXPECT_EQ(plan.gaussianSplatPositionBuffer.binding, 1u);
|
2026-04-10 23:11:11 +08:00
|
|
|
EXPECT_EQ(plan.gaussianSplatOtherBuffer.set, 2u);
|
2026-04-11 03:02:30 +08:00
|
|
|
EXPECT_EQ(plan.gaussianSplatOtherBuffer.binding, 2u);
|
2026-04-10 23:11:11 +08:00
|
|
|
EXPECT_EQ(plan.gaussianSplatColorBuffer.set, 2u);
|
2026-04-11 03:02:30 +08:00
|
|
|
EXPECT_EQ(plan.gaussianSplatColorBuffer.binding, 3u);
|
2026-04-10 23:11:11 +08:00
|
|
|
EXPECT_EQ(plan.firstDescriptorSet, 0u);
|
|
|
|
|
EXPECT_EQ(plan.descriptorSetCount, 3u);
|
|
|
|
|
|
|
|
|
|
std::vector<BuiltinPassSetLayoutMetadata> setLayouts;
|
|
|
|
|
ASSERT_TRUE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)) << error.CStr();
|
|
|
|
|
ASSERT_EQ(setLayouts.size(), 3u);
|
|
|
|
|
EXPECT_TRUE(setLayouts[0].usesPerObject);
|
|
|
|
|
EXPECT_TRUE(setLayouts[1].usesMaterial);
|
2026-04-11 03:02:30 +08:00
|
|
|
EXPECT_TRUE(setLayouts[2].usesGaussianSplatOrderBuffer);
|
2026-04-10 23:11:11 +08:00
|
|
|
EXPECT_TRUE(setLayouts[2].usesGaussianSplatPositionBuffer);
|
|
|
|
|
EXPECT_TRUE(setLayouts[2].usesGaussianSplatOtherBuffer);
|
|
|
|
|
EXPECT_TRUE(setLayouts[2].usesGaussianSplatColorBuffer);
|
2026-04-11 03:02:30 +08:00
|
|
|
ASSERT_EQ(setLayouts[2].bindings.size(), 4u);
|
2026-04-10 23:11:11 +08:00
|
|
|
EXPECT_EQ(
|
|
|
|
|
static_cast<DescriptorType>(setLayouts[2].bindings[0].type),
|
|
|
|
|
DescriptorType::SRV);
|
|
|
|
|
EXPECT_EQ(
|
|
|
|
|
static_cast<DescriptorType>(setLayouts[2].bindings[1].type),
|
|
|
|
|
DescriptorType::SRV);
|
|
|
|
|
EXPECT_EQ(
|
|
|
|
|
static_cast<DescriptorType>(setLayouts[2].bindings[2].type),
|
|
|
|
|
DescriptorType::SRV);
|
2026-04-11 03:02:30 +08:00
|
|
|
EXPECT_EQ(
|
|
|
|
|
static_cast<DescriptorType>(setLayouts[2].bindings[3].type),
|
|
|
|
|
DescriptorType::SRV);
|
2026-04-10 23:11:11 +08:00
|
|
|
EXPECT_EQ(
|
|
|
|
|
setLayouts[2].bindings[0].resourceDimension,
|
|
|
|
|
ResourceViewDimension::StructuredBuffer);
|
|
|
|
|
EXPECT_EQ(
|
|
|
|
|
setLayouts[2].bindings[1].resourceDimension,
|
|
|
|
|
ResourceViewDimension::StructuredBuffer);
|
|
|
|
|
EXPECT_EQ(
|
|
|
|
|
setLayouts[2].bindings[2].resourceDimension,
|
|
|
|
|
ResourceViewDimension::StructuredBuffer);
|
2026-04-11 03:02:30 +08:00
|
|
|
EXPECT_EQ(
|
|
|
|
|
setLayouts[2].bindings[3].resourceDimension,
|
|
|
|
|
ResourceViewDimension::StructuredBuffer);
|
2026-04-10 23:11:11 +08:00
|
|
|
|
|
|
|
|
delete shader;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringGaussianSplatBindingsToDescriptorSpaces) {
|
|
|
|
|
ShaderLoader loader;
|
|
|
|
|
LoadResult result = loader.Load(GetBuiltinGaussianSplatShaderPath());
|
|
|
|
|
ASSERT_TRUE(result);
|
|
|
|
|
ASSERT_NE(result.resource, nullptr);
|
|
|
|
|
|
|
|
|
|
Shader* shader = static_cast<Shader*>(result.resource);
|
|
|
|
|
ASSERT_NE(shader, nullptr);
|
|
|
|
|
|
|
|
|
|
const ShaderPass* pass = shader->FindPass("GaussianSplat");
|
|
|
|
|
ASSERT_NE(pass, nullptr);
|
|
|
|
|
|
|
|
|
|
const ShaderStageVariant* d3d12Vertex = shader->FindVariant(
|
|
|
|
|
"GaussianSplat",
|
|
|
|
|
XCEngine::Resources::ShaderType::Vertex,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::D3D12);
|
|
|
|
|
ASSERT_NE(d3d12Vertex, nullptr);
|
|
|
|
|
|
|
|
|
|
ShaderCompileDesc d3d12CompileDesc = {};
|
|
|
|
|
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
|
|
|
|
*pass,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::D3D12,
|
|
|
|
|
*d3d12Vertex,
|
|
|
|
|
d3d12CompileDesc);
|
|
|
|
|
const std::string d3d12Source(
|
|
|
|
|
reinterpret_cast<const char*>(d3d12CompileDesc.source.data()),
|
|
|
|
|
d3d12CompileDesc.source.size());
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
d3d12Source,
|
|
|
|
|
"cbuffer PerObjectConstants",
|
|
|
|
|
"register(b0)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
d3d12Source,
|
|
|
|
|
"cbuffer MaterialConstants",
|
|
|
|
|
"register(b1)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
d3d12Source,
|
2026-04-11 03:02:30 +08:00
|
|
|
"StructuredBuffer<uint> GaussianSplatOrderBuffer",
|
2026-04-10 23:11:11 +08:00
|
|
|
"register(t0)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
d3d12Source,
|
2026-04-11 03:02:30 +08:00
|
|
|
"StructuredBuffer<float3> GaussianSplatPositions",
|
2026-04-10 23:11:11 +08:00
|
|
|
"register(t1)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
d3d12Source,
|
2026-04-11 03:02:30 +08:00
|
|
|
"StructuredBuffer<GaussianSplatOtherData> GaussianSplatOther",
|
2026-04-10 23:11:11 +08:00
|
|
|
"register(t2)"));
|
2026-04-11 03:02:30 +08:00
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
d3d12Source,
|
|
|
|
|
"StructuredBuffer<float4> GaussianSplatColor",
|
|
|
|
|
"register(t3)"));
|
2026-04-10 23:11:11 +08:00
|
|
|
EXPECT_EQ(d3d12Source.find("space0"), std::string::npos);
|
|
|
|
|
EXPECT_EQ(d3d12Source.find("space1"), std::string::npos);
|
|
|
|
|
EXPECT_EQ(d3d12Source.find("space2"), std::string::npos);
|
|
|
|
|
|
|
|
|
|
const ShaderStageVariant* vulkanVertex = shader->FindVariant(
|
|
|
|
|
"GaussianSplat",
|
|
|
|
|
XCEngine::Resources::ShaderType::Vertex,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::Vulkan);
|
|
|
|
|
ASSERT_NE(vulkanVertex, nullptr);
|
|
|
|
|
|
|
|
|
|
ShaderCompileDesc vulkanCompileDesc = {};
|
|
|
|
|
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
|
|
|
|
*pass,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::Vulkan,
|
|
|
|
|
*vulkanVertex,
|
|
|
|
|
vulkanCompileDesc);
|
|
|
|
|
const std::string vulkanSource(
|
|
|
|
|
reinterpret_cast<const char*>(vulkanCompileDesc.source.data()),
|
|
|
|
|
vulkanCompileDesc.source.size());
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
vulkanSource,
|
|
|
|
|
"cbuffer PerObjectConstants",
|
|
|
|
|
"register(b0, space0)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
vulkanSource,
|
|
|
|
|
"cbuffer MaterialConstants",
|
|
|
|
|
"register(b0, space1)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
vulkanSource,
|
2026-04-11 03:02:30 +08:00
|
|
|
"StructuredBuffer<uint> GaussianSplatOrderBuffer",
|
2026-04-10 23:11:11 +08:00
|
|
|
"register(t0, space2)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
vulkanSource,
|
2026-04-11 03:02:30 +08:00
|
|
|
"StructuredBuffer<float3> GaussianSplatPositions",
|
2026-04-10 23:11:11 +08:00
|
|
|
"register(t1, space2)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
vulkanSource,
|
2026-04-11 03:02:30 +08:00
|
|
|
"StructuredBuffer<GaussianSplatOtherData> GaussianSplatOther",
|
2026-04-10 23:11:11 +08:00
|
|
|
"register(t2, space2)"));
|
2026-04-11 03:02:30 +08:00
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
vulkanSource,
|
|
|
|
|
"StructuredBuffer<float4> GaussianSplatColor",
|
|
|
|
|
"register(t3, space2)"));
|
2026-04-10 23:11:11 +08:00
|
|
|
|
|
|
|
|
delete shader;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 01:30:59 +08:00
|
|
|
TEST(BuiltinForwardPipeline_Test, BuiltinGaussianSplatUtilitiesShaderUsesComputeAuthoringContract) {
|
|
|
|
|
ShaderLoader loader;
|
|
|
|
|
LoadResult result = loader.Load(GetBuiltinGaussianSplatUtilitiesShaderPath());
|
|
|
|
|
ASSERT_TRUE(result);
|
|
|
|
|
ASSERT_NE(result.resource, nullptr);
|
|
|
|
|
|
|
|
|
|
Shader* shader = static_cast<Shader*>(result.resource);
|
|
|
|
|
ASSERT_NE(shader, nullptr);
|
|
|
|
|
|
|
|
|
|
const ShaderPass* pass = shader->FindPass("GaussianSplatPrepareOrder");
|
|
|
|
|
ASSERT_NE(pass, nullptr);
|
|
|
|
|
EXPECT_EQ(pass->resources.Size(), 4u);
|
|
|
|
|
|
|
|
|
|
const ShaderResourceBindingDesc* perObject =
|
|
|
|
|
shader->FindPassResourceBinding("GaussianSplatPrepareOrder", "PerObjectConstants");
|
|
|
|
|
ASSERT_NE(perObject, nullptr);
|
|
|
|
|
EXPECT_EQ(perObject->type, ShaderResourceType::ConstantBuffer);
|
|
|
|
|
EXPECT_EQ(perObject->set, 0u);
|
|
|
|
|
EXPECT_EQ(perObject->binding, 0u);
|
|
|
|
|
EXPECT_EQ(
|
|
|
|
|
ResolveBuiltinPassResourceSemantic(*perObject),
|
|
|
|
|
BuiltinPassResourceSemantic::PerObject);
|
|
|
|
|
|
|
|
|
|
const ShaderResourceBindingDesc* positions =
|
|
|
|
|
shader->FindPassResourceBinding("GaussianSplatPrepareOrder", "GaussianSplatPositions");
|
|
|
|
|
ASSERT_NE(positions, nullptr);
|
|
|
|
|
EXPECT_EQ(positions->type, ShaderResourceType::StructuredBuffer);
|
|
|
|
|
EXPECT_EQ(positions->set, 2u);
|
|
|
|
|
EXPECT_EQ(positions->binding, 0u);
|
|
|
|
|
EXPECT_EQ(
|
|
|
|
|
ResolveBuiltinPassResourceSemantic(*positions),
|
|
|
|
|
BuiltinPassResourceSemantic::GaussianSplatPositionBuffer);
|
|
|
|
|
|
|
|
|
|
const ShaderResourceBindingDesc* sortDistances =
|
|
|
|
|
shader->FindPassResourceBinding("GaussianSplatPrepareOrder", "GaussianSplatSortDistances");
|
|
|
|
|
ASSERT_NE(sortDistances, nullptr);
|
|
|
|
|
EXPECT_EQ(sortDistances->type, ShaderResourceType::RWStructuredBuffer);
|
|
|
|
|
EXPECT_EQ(sortDistances->set, 4u);
|
|
|
|
|
EXPECT_EQ(sortDistances->binding, 0u);
|
|
|
|
|
EXPECT_EQ(
|
|
|
|
|
ResolveBuiltinPassResourceSemantic(*sortDistances),
|
|
|
|
|
BuiltinPassResourceSemantic::GaussianSplatSortDistanceBuffer);
|
|
|
|
|
|
|
|
|
|
const ShaderResourceBindingDesc* orderBuffer =
|
|
|
|
|
shader->FindPassResourceBinding("GaussianSplatPrepareOrder", "GaussianSplatOrderBuffer");
|
|
|
|
|
ASSERT_NE(orderBuffer, nullptr);
|
|
|
|
|
EXPECT_EQ(orderBuffer->type, ShaderResourceType::RWStructuredBuffer);
|
|
|
|
|
EXPECT_EQ(orderBuffer->set, 4u);
|
|
|
|
|
EXPECT_EQ(orderBuffer->binding, 1u);
|
|
|
|
|
EXPECT_EQ(
|
|
|
|
|
ResolveBuiltinPassResourceSemantic(*orderBuffer),
|
|
|
|
|
BuiltinPassResourceSemantic::GaussianSplatOrderBuffer);
|
|
|
|
|
|
|
|
|
|
const ShaderStageVariant* computeVariant = shader->FindVariant(
|
|
|
|
|
"GaussianSplatPrepareOrder",
|
|
|
|
|
XCEngine::Resources::ShaderType::Compute,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::D3D12);
|
|
|
|
|
ASSERT_NE(computeVariant, nullptr);
|
|
|
|
|
EXPECT_EQ(computeVariant->entryPoint, "GaussianSplatPrepareOrderCS");
|
|
|
|
|
|
|
|
|
|
delete shader;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoadedGaussianSplatUtilitiesShaderContract) {
|
|
|
|
|
ShaderLoader loader;
|
|
|
|
|
LoadResult result = loader.Load(GetBuiltinGaussianSplatUtilitiesShaderPath());
|
|
|
|
|
ASSERT_TRUE(result);
|
|
|
|
|
ASSERT_NE(result.resource, nullptr);
|
|
|
|
|
|
|
|
|
|
Shader* shader = static_cast<Shader*>(result.resource);
|
|
|
|
|
ASSERT_NE(shader, nullptr);
|
|
|
|
|
|
|
|
|
|
const ShaderPass* pass = shader->FindPass("GaussianSplatPrepareOrder");
|
|
|
|
|
ASSERT_NE(pass, nullptr);
|
|
|
|
|
|
|
|
|
|
BuiltinPassResourceBindingPlan plan = {};
|
|
|
|
|
String error;
|
|
|
|
|
EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr();
|
|
|
|
|
ASSERT_EQ(plan.bindings.Size(), 4u);
|
|
|
|
|
EXPECT_TRUE(plan.perObject.IsValid());
|
|
|
|
|
EXPECT_TRUE(plan.gaussianSplatPositionBuffer.IsValid());
|
|
|
|
|
EXPECT_TRUE(plan.gaussianSplatSortDistanceBuffer.IsValid());
|
|
|
|
|
EXPECT_TRUE(plan.gaussianSplatOrderBuffer.IsValid());
|
|
|
|
|
EXPECT_EQ(plan.perObject.set, 0u);
|
|
|
|
|
EXPECT_EQ(plan.gaussianSplatPositionBuffer.set, 2u);
|
|
|
|
|
EXPECT_EQ(plan.gaussianSplatSortDistanceBuffer.set, 4u);
|
|
|
|
|
EXPECT_EQ(plan.gaussianSplatSortDistanceBuffer.binding, 0u);
|
|
|
|
|
EXPECT_EQ(plan.gaussianSplatOrderBuffer.set, 4u);
|
|
|
|
|
EXPECT_EQ(plan.gaussianSplatOrderBuffer.binding, 1u);
|
|
|
|
|
EXPECT_EQ(plan.firstDescriptorSet, 0u);
|
|
|
|
|
EXPECT_EQ(plan.descriptorSetCount, 5u);
|
|
|
|
|
|
|
|
|
|
std::vector<BuiltinPassSetLayoutMetadata> setLayouts;
|
|
|
|
|
ASSERT_TRUE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)) << error.CStr();
|
|
|
|
|
ASSERT_EQ(setLayouts.size(), 5u);
|
|
|
|
|
EXPECT_TRUE(setLayouts[0].usesPerObject);
|
|
|
|
|
EXPECT_TRUE(setLayouts[2].usesGaussianSplatPositionBuffer);
|
|
|
|
|
EXPECT_TRUE(setLayouts[4].usesGaussianSplatSortDistanceBuffer);
|
|
|
|
|
EXPECT_TRUE(setLayouts[4].usesGaussianSplatOrderBuffer);
|
|
|
|
|
ASSERT_EQ(setLayouts[4].bindings.size(), 2u);
|
|
|
|
|
EXPECT_EQ(
|
|
|
|
|
static_cast<DescriptorType>(setLayouts[4].bindings[0].type),
|
|
|
|
|
DescriptorType::UAV);
|
|
|
|
|
EXPECT_EQ(
|
|
|
|
|
static_cast<DescriptorType>(setLayouts[4].bindings[1].type),
|
|
|
|
|
DescriptorType::UAV);
|
|
|
|
|
EXPECT_EQ(
|
|
|
|
|
setLayouts[4].bindings[0].resourceDimension,
|
|
|
|
|
ResourceViewDimension::StructuredBuffer);
|
|
|
|
|
EXPECT_EQ(
|
|
|
|
|
setLayouts[4].bindings[1].resourceDimension,
|
|
|
|
|
ResourceViewDimension::StructuredBuffer);
|
|
|
|
|
|
|
|
|
|
delete shader;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesGaussianSplatUtilitiesComputeBindingsToDescriptorSpaces) {
|
|
|
|
|
ShaderLoader loader;
|
|
|
|
|
LoadResult result = loader.Load(GetBuiltinGaussianSplatUtilitiesShaderPath());
|
|
|
|
|
ASSERT_TRUE(result);
|
|
|
|
|
ASSERT_NE(result.resource, nullptr);
|
|
|
|
|
|
|
|
|
|
Shader* shader = static_cast<Shader*>(result.resource);
|
|
|
|
|
ASSERT_NE(shader, nullptr);
|
|
|
|
|
|
|
|
|
|
const ShaderPass* pass = shader->FindPass("GaussianSplatPrepareOrder");
|
|
|
|
|
ASSERT_NE(pass, nullptr);
|
|
|
|
|
|
|
|
|
|
const ShaderStageVariant* d3d12Compute = shader->FindVariant(
|
|
|
|
|
"GaussianSplatPrepareOrder",
|
|
|
|
|
XCEngine::Resources::ShaderType::Compute,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::D3D12);
|
|
|
|
|
ASSERT_NE(d3d12Compute, nullptr);
|
|
|
|
|
|
|
|
|
|
ShaderCompileDesc d3d12CompileDesc = {};
|
|
|
|
|
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
|
|
|
|
*pass,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::D3D12,
|
|
|
|
|
*d3d12Compute,
|
|
|
|
|
d3d12CompileDesc);
|
|
|
|
|
const std::string d3d12Source(
|
|
|
|
|
reinterpret_cast<const char*>(d3d12CompileDesc.source.data()),
|
|
|
|
|
d3d12CompileDesc.source.size());
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
d3d12Source,
|
|
|
|
|
"cbuffer PerObjectConstants",
|
|
|
|
|
"register(b0)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
d3d12Source,
|
|
|
|
|
"StructuredBuffer<float3> GaussianSplatPositions",
|
|
|
|
|
"register(t0)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
d3d12Source,
|
|
|
|
|
"RWStructuredBuffer<uint> GaussianSplatSortDistances",
|
|
|
|
|
"register(u0)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
d3d12Source,
|
|
|
|
|
"RWStructuredBuffer<uint> GaussianSplatOrderBuffer",
|
|
|
|
|
"register(u1)"));
|
|
|
|
|
|
|
|
|
|
const ShaderStageVariant* vulkanCompute = shader->FindVariant(
|
|
|
|
|
"GaussianSplatPrepareOrder",
|
|
|
|
|
XCEngine::Resources::ShaderType::Compute,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::Vulkan);
|
|
|
|
|
ASSERT_NE(vulkanCompute, nullptr);
|
|
|
|
|
|
|
|
|
|
ShaderCompileDesc vulkanCompileDesc = {};
|
|
|
|
|
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
|
|
|
|
*pass,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::Vulkan,
|
|
|
|
|
*vulkanCompute,
|
|
|
|
|
vulkanCompileDesc);
|
|
|
|
|
const std::string vulkanSource(
|
|
|
|
|
reinterpret_cast<const char*>(vulkanCompileDesc.source.data()),
|
|
|
|
|
vulkanCompileDesc.source.size());
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
vulkanSource,
|
|
|
|
|
"cbuffer PerObjectConstants",
|
|
|
|
|
"register(b0, space0)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
vulkanSource,
|
|
|
|
|
"StructuredBuffer<float3> GaussianSplatPositions",
|
|
|
|
|
"register(t0, space2)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
vulkanSource,
|
|
|
|
|
"RWStructuredBuffer<uint> GaussianSplatSortDistances",
|
|
|
|
|
"register(u0, space4)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
vulkanSource,
|
|
|
|
|
"RWStructuredBuffer<uint> GaussianSplatOrderBuffer",
|
|
|
|
|
"register(u1, space4)"));
|
|
|
|
|
|
|
|
|
|
delete shader;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-09 02:59:36 +08:00
|
|
|
TEST(BuiltinForwardPipeline_Test, BuiltinVolumetricShaderUsesAuthoringContract) {
|
|
|
|
|
ShaderLoader loader;
|
|
|
|
|
LoadResult result = loader.Load(GetBuiltinVolumetricShaderPath());
|
|
|
|
|
ASSERT_TRUE(result);
|
|
|
|
|
ASSERT_NE(result.resource, nullptr);
|
|
|
|
|
|
|
|
|
|
Shader* shader = static_cast<Shader*>(result.resource);
|
|
|
|
|
ASSERT_NE(shader, nullptr);
|
|
|
|
|
|
|
|
|
|
const ShaderPass* pass = shader->FindPass("Volumetric");
|
|
|
|
|
ASSERT_NE(pass, nullptr);
|
2026-04-09 05:22:03 +08:00
|
|
|
EXPECT_EQ(pass->resources.Size(), 4u);
|
2026-04-09 02:59:36 +08:00
|
|
|
EXPECT_TRUE(pass->hasFixedFunctionState);
|
|
|
|
|
EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::None);
|
|
|
|
|
EXPECT_FALSE(pass->fixedFunctionState.depthWriteEnable);
|
|
|
|
|
EXPECT_EQ(pass->fixedFunctionState.depthFunc, MaterialComparisonFunc::LessEqual);
|
|
|
|
|
EXPECT_TRUE(pass->fixedFunctionState.blendEnable);
|
|
|
|
|
|
2026-04-09 05:22:03 +08:00
|
|
|
const ShaderResourceBindingDesc* lighting =
|
|
|
|
|
shader->FindPassResourceBinding("Volumetric", "LightingConstants");
|
|
|
|
|
ASSERT_NE(lighting, nullptr);
|
|
|
|
|
EXPECT_EQ(lighting->type, ShaderResourceType::ConstantBuffer);
|
|
|
|
|
EXPECT_EQ(lighting->set, 1u);
|
|
|
|
|
EXPECT_EQ(lighting->binding, 0u);
|
|
|
|
|
EXPECT_EQ(
|
|
|
|
|
ResolveBuiltinPassResourceSemantic(*lighting),
|
|
|
|
|
BuiltinPassResourceSemantic::Lighting);
|
|
|
|
|
|
2026-04-09 02:59:36 +08:00
|
|
|
const ShaderResourceBindingDesc* volumeData =
|
|
|
|
|
shader->FindPassResourceBinding("Volumetric", "VolumeData");
|
|
|
|
|
ASSERT_NE(volumeData, nullptr);
|
|
|
|
|
EXPECT_EQ(volumeData->type, ShaderResourceType::StructuredBuffer);
|
|
|
|
|
EXPECT_EQ(volumeData->set, 2u);
|
2026-04-09 05:22:03 +08:00
|
|
|
EXPECT_EQ(volumeData->binding, 1u);
|
2026-04-09 02:59:36 +08:00
|
|
|
EXPECT_EQ(
|
|
|
|
|
ResolveBuiltinPassResourceSemantic(*volumeData),
|
|
|
|
|
BuiltinPassResourceSemantic::VolumeField);
|
|
|
|
|
|
|
|
|
|
delete shader;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoadedVolumetricShaderContract) {
|
|
|
|
|
ShaderLoader loader;
|
|
|
|
|
LoadResult result = loader.Load(GetBuiltinVolumetricShaderPath());
|
|
|
|
|
ASSERT_TRUE(result);
|
|
|
|
|
ASSERT_NE(result.resource, nullptr);
|
|
|
|
|
|
|
|
|
|
Shader* shader = static_cast<Shader*>(result.resource);
|
|
|
|
|
ASSERT_NE(shader, nullptr);
|
|
|
|
|
|
|
|
|
|
const ShaderPass* pass = shader->FindPass("Volumetric");
|
|
|
|
|
ASSERT_NE(pass, nullptr);
|
|
|
|
|
|
|
|
|
|
BuiltinPassResourceBindingPlan plan = {};
|
|
|
|
|
String error;
|
|
|
|
|
EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr();
|
2026-04-09 05:22:03 +08:00
|
|
|
ASSERT_EQ(plan.bindings.Size(), 4u);
|
2026-04-09 02:59:36 +08:00
|
|
|
EXPECT_TRUE(plan.perObject.IsValid());
|
2026-04-09 05:22:03 +08:00
|
|
|
EXPECT_TRUE(plan.lighting.IsValid());
|
2026-04-09 02:59:36 +08:00
|
|
|
EXPECT_TRUE(plan.material.IsValid());
|
|
|
|
|
EXPECT_TRUE(plan.volumeField.IsValid());
|
|
|
|
|
EXPECT_EQ(plan.perObject.set, 0u);
|
2026-04-09 05:22:03 +08:00
|
|
|
EXPECT_EQ(plan.lighting.set, 1u);
|
|
|
|
|
EXPECT_EQ(plan.material.set, 2u);
|
2026-04-09 02:59:36 +08:00
|
|
|
EXPECT_EQ(plan.volumeField.set, 2u);
|
2026-04-09 05:22:03 +08:00
|
|
|
EXPECT_EQ(plan.volumeField.binding, 1u);
|
2026-04-09 02:59:36 +08:00
|
|
|
|
|
|
|
|
std::vector<BuiltinPassSetLayoutMetadata> setLayouts;
|
|
|
|
|
ASSERT_TRUE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)) << error.CStr();
|
|
|
|
|
ASSERT_EQ(setLayouts.size(), 3u);
|
|
|
|
|
EXPECT_TRUE(setLayouts[0].usesPerObject);
|
2026-04-09 05:22:03 +08:00
|
|
|
EXPECT_TRUE(setLayouts[1].usesLighting);
|
|
|
|
|
EXPECT_TRUE(setLayouts[2].usesMaterial);
|
2026-04-09 02:59:36 +08:00
|
|
|
EXPECT_TRUE(setLayouts[2].usesVolumeField);
|
2026-04-09 05:22:03 +08:00
|
|
|
ASSERT_EQ(setLayouts[2].bindings.size(), 2u);
|
2026-04-09 02:59:36 +08:00
|
|
|
EXPECT_EQ(
|
|
|
|
|
static_cast<DescriptorType>(setLayouts[2].bindings[0].type),
|
2026-04-09 05:22:03 +08:00
|
|
|
DescriptorType::CBV);
|
|
|
|
|
EXPECT_EQ(
|
|
|
|
|
static_cast<DescriptorType>(setLayouts[2].bindings[1].type),
|
2026-04-09 02:59:36 +08:00
|
|
|
DescriptorType::SRV);
|
|
|
|
|
EXPECT_EQ(
|
2026-04-09 05:22:03 +08:00
|
|
|
setLayouts[2].bindings[1].resourceDimension,
|
2026-04-09 02:59:36 +08:00
|
|
|
ResourceViewDimension::StructuredBuffer);
|
|
|
|
|
|
|
|
|
|
delete shader;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-07 14:13:26 +08:00
|
|
|
TEST(BuiltinForwardPipeline_Test, BuiltinFinalColorShaderUsesAuthoringContract) {
|
2026-04-06 16:15:19 +08:00
|
|
|
ShaderLoader loader;
|
|
|
|
|
LoadResult result = loader.Load(GetBuiltinFinalColorShaderPath());
|
|
|
|
|
ASSERT_TRUE(result);
|
|
|
|
|
ASSERT_NE(result.resource, nullptr);
|
|
|
|
|
|
|
|
|
|
Shader* shader = static_cast<Shader*>(result.resource);
|
|
|
|
|
ASSERT_NE(shader, nullptr);
|
|
|
|
|
|
|
|
|
|
const ShaderPass* pass = shader->FindPass("FinalColor");
|
|
|
|
|
ASSERT_NE(pass, nullptr);
|
2026-04-08 04:44:36 +08:00
|
|
|
EXPECT_EQ(pass->resources.Size(), 3u);
|
2026-04-07 04:30:26 +08:00
|
|
|
EXPECT_TRUE(pass->hasFixedFunctionState);
|
|
|
|
|
EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::None);
|
|
|
|
|
EXPECT_FALSE(pass->fixedFunctionState.depthWriteEnable);
|
|
|
|
|
EXPECT_EQ(pass->fixedFunctionState.depthFunc, MaterialComparisonFunc::Always);
|
2026-04-06 16:15:19 +08:00
|
|
|
|
|
|
|
|
const ShaderPropertyDesc* colorScale = shader->FindProperty("_ColorScale");
|
|
|
|
|
ASSERT_NE(colorScale, nullptr);
|
|
|
|
|
EXPECT_EQ(colorScale->type, ShaderPropertyType::Color);
|
|
|
|
|
|
|
|
|
|
const ShaderPropertyDesc* exposure = shader->FindProperty("_Exposure");
|
|
|
|
|
ASSERT_NE(exposure, nullptr);
|
|
|
|
|
EXPECT_EQ(exposure->type, ShaderPropertyType::Float);
|
|
|
|
|
|
|
|
|
|
const ShaderPropertyDesc* outputTransferMode = shader->FindProperty("_OutputTransferMode");
|
|
|
|
|
ASSERT_NE(outputTransferMode, nullptr);
|
|
|
|
|
EXPECT_EQ(outputTransferMode->type, ShaderPropertyType::Float);
|
|
|
|
|
|
|
|
|
|
const ShaderPropertyDesc* toneMappingMode = shader->FindProperty("_ToneMappingMode");
|
|
|
|
|
ASSERT_NE(toneMappingMode, nullptr);
|
|
|
|
|
EXPECT_EQ(toneMappingMode->type, ShaderPropertyType::Float);
|
|
|
|
|
|
2026-04-07 04:30:26 +08:00
|
|
|
delete shader;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 04:44:36 +08:00
|
|
|
TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoadedFinalColorShaderContract) {
|
2026-04-07 04:30:26 +08:00
|
|
|
ShaderLoader loader;
|
|
|
|
|
LoadResult result = loader.Load(GetBuiltinFinalColorShaderPath());
|
|
|
|
|
ASSERT_TRUE(result);
|
|
|
|
|
ASSERT_NE(result.resource, nullptr);
|
|
|
|
|
|
|
|
|
|
Shader* shader = static_cast<Shader*>(result.resource);
|
|
|
|
|
ASSERT_NE(shader, nullptr);
|
|
|
|
|
|
|
|
|
|
const ShaderPass* pass = shader->FindPass("FinalColor");
|
|
|
|
|
ASSERT_NE(pass, nullptr);
|
|
|
|
|
|
|
|
|
|
BuiltinPassResourceBindingPlan plan = {};
|
|
|
|
|
String error;
|
|
|
|
|
EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr();
|
|
|
|
|
ASSERT_EQ(plan.bindings.Size(), 3u);
|
|
|
|
|
EXPECT_TRUE(plan.passConstants.IsValid());
|
|
|
|
|
EXPECT_TRUE(plan.sourceColorTexture.IsValid());
|
|
|
|
|
EXPECT_TRUE(plan.linearClampSampler.IsValid());
|
|
|
|
|
EXPECT_EQ(plan.firstDescriptorSet, 0u);
|
|
|
|
|
EXPECT_EQ(plan.descriptorSetCount, 3u);
|
|
|
|
|
|
|
|
|
|
delete shader;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-07 14:13:26 +08:00
|
|
|
TEST(BuiltinForwardPipeline_Test, BuiltinColorScalePostProcessShaderUsesAuthoringContract) {
|
2026-04-07 04:30:26 +08:00
|
|
|
ShaderLoader loader;
|
|
|
|
|
LoadResult result = loader.Load(GetBuiltinColorScalePostProcessShaderPath());
|
|
|
|
|
ASSERT_TRUE(result);
|
|
|
|
|
ASSERT_NE(result.resource, nullptr);
|
|
|
|
|
|
|
|
|
|
Shader* shader = static_cast<Shader*>(result.resource);
|
|
|
|
|
ASSERT_NE(shader, nullptr);
|
|
|
|
|
|
|
|
|
|
const ShaderPass* pass = shader->FindPass("ColorScale");
|
|
|
|
|
ASSERT_NE(pass, nullptr);
|
2026-04-08 04:44:36 +08:00
|
|
|
EXPECT_EQ(pass->resources.Size(), 3u);
|
2026-04-07 04:30:26 +08:00
|
|
|
EXPECT_TRUE(pass->hasFixedFunctionState);
|
|
|
|
|
EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::None);
|
|
|
|
|
EXPECT_FALSE(pass->fixedFunctionState.depthWriteEnable);
|
|
|
|
|
EXPECT_EQ(pass->fixedFunctionState.depthFunc, MaterialComparisonFunc::Always);
|
|
|
|
|
|
|
|
|
|
const ShaderPropertyDesc* colorScale = shader->FindProperty("_ColorScale");
|
|
|
|
|
ASSERT_NE(colorScale, nullptr);
|
|
|
|
|
EXPECT_EQ(colorScale->type, ShaderPropertyType::Color);
|
|
|
|
|
|
|
|
|
|
delete shader;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 04:44:36 +08:00
|
|
|
TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoadedColorScalePostProcessShaderContract) {
|
2026-04-07 04:30:26 +08:00
|
|
|
ShaderLoader loader;
|
|
|
|
|
LoadResult result = loader.Load(GetBuiltinColorScalePostProcessShaderPath());
|
|
|
|
|
ASSERT_TRUE(result);
|
|
|
|
|
ASSERT_NE(result.resource, nullptr);
|
|
|
|
|
|
|
|
|
|
Shader* shader = static_cast<Shader*>(result.resource);
|
|
|
|
|
ASSERT_NE(shader, nullptr);
|
|
|
|
|
|
|
|
|
|
const ShaderPass* pass = shader->FindPass("ColorScale");
|
|
|
|
|
ASSERT_NE(pass, nullptr);
|
|
|
|
|
|
|
|
|
|
BuiltinPassResourceBindingPlan plan = {};
|
|
|
|
|
String error;
|
|
|
|
|
EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr();
|
|
|
|
|
ASSERT_EQ(plan.bindings.Size(), 3u);
|
|
|
|
|
EXPECT_TRUE(plan.passConstants.IsValid());
|
|
|
|
|
EXPECT_TRUE(plan.sourceColorTexture.IsValid());
|
|
|
|
|
EXPECT_TRUE(plan.linearClampSampler.IsValid());
|
|
|
|
|
EXPECT_EQ(plan.firstDescriptorSet, 0u);
|
|
|
|
|
EXPECT_EQ(plan.descriptorSetCount, 3u);
|
|
|
|
|
|
|
|
|
|
delete shader;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-07 14:13:26 +08:00
|
|
|
TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringFinalColorBindingsToDescriptorSpaces) {
|
2026-04-07 04:30:26 +08:00
|
|
|
ShaderLoader loader;
|
|
|
|
|
LoadResult result = loader.Load(GetBuiltinFinalColorShaderPath());
|
|
|
|
|
ASSERT_TRUE(result);
|
|
|
|
|
ASSERT_NE(result.resource, nullptr);
|
|
|
|
|
|
|
|
|
|
Shader* shader = static_cast<Shader*>(result.resource);
|
|
|
|
|
ASSERT_NE(shader, nullptr);
|
|
|
|
|
|
|
|
|
|
const ShaderPass* pass = shader->FindPass("FinalColor");
|
|
|
|
|
ASSERT_NE(pass, nullptr);
|
|
|
|
|
|
|
|
|
|
const ShaderStageVariant* d3d12Fragment = shader->FindVariant(
|
|
|
|
|
"FinalColor",
|
|
|
|
|
XCEngine::Resources::ShaderType::Fragment,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::D3D12);
|
|
|
|
|
ASSERT_NE(d3d12Fragment, nullptr);
|
|
|
|
|
|
|
|
|
|
ShaderCompileDesc d3d12CompileDesc = {};
|
2026-04-10 23:11:11 +08:00
|
|
|
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
2026-04-07 04:30:26 +08:00
|
|
|
*pass,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::D3D12,
|
|
|
|
|
*d3d12Fragment,
|
|
|
|
|
d3d12CompileDesc);
|
|
|
|
|
const std::string d3d12Source(
|
|
|
|
|
reinterpret_cast<const char*>(d3d12CompileDesc.source.data()),
|
|
|
|
|
d3d12CompileDesc.source.size());
|
2026-04-07 10:34:20 +08:00
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
d3d12Source,
|
|
|
|
|
"cbuffer FinalColorConstants",
|
|
|
|
|
"register(b0)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
d3d12Source,
|
2026-04-08 04:44:36 +08:00
|
|
|
"Texture2D SourceColorTexture",
|
2026-04-07 10:34:20 +08:00
|
|
|
"register(t0)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
d3d12Source,
|
2026-04-08 04:44:36 +08:00
|
|
|
"SamplerState LinearClampSampler",
|
2026-04-07 10:34:20 +08:00
|
|
|
"register(s0)"));
|
|
|
|
|
EXPECT_EQ(d3d12Source.find("space0"), std::string::npos);
|
2026-04-07 04:30:26 +08:00
|
|
|
EXPECT_EQ(d3d12Source.find("space1"), std::string::npos);
|
|
|
|
|
|
|
|
|
|
const ShaderStageVariant* vulkanFragment = shader->FindVariant(
|
|
|
|
|
"FinalColor",
|
|
|
|
|
XCEngine::Resources::ShaderType::Fragment,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::Vulkan);
|
|
|
|
|
ASSERT_NE(vulkanFragment, nullptr);
|
|
|
|
|
|
|
|
|
|
const std::string runtimeSource =
|
2026-04-10 23:11:11 +08:00
|
|
|
::XCEngine::Rendering::Internal::BuildRuntimeShaderSource(
|
2026-04-07 04:30:26 +08:00
|
|
|
*pass,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::Vulkan,
|
|
|
|
|
*vulkanFragment);
|
2026-04-07 10:34:20 +08:00
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
runtimeSource,
|
|
|
|
|
"cbuffer FinalColorConstants",
|
|
|
|
|
"register(b0, space0)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
runtimeSource,
|
2026-04-08 04:44:36 +08:00
|
|
|
"Texture2D SourceColorTexture",
|
2026-04-07 10:34:20 +08:00
|
|
|
"register(t0, space1)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
runtimeSource,
|
2026-04-08 04:44:36 +08:00
|
|
|
"SamplerState LinearClampSampler",
|
2026-04-07 10:34:20 +08:00
|
|
|
"register(s0, space2)"));
|
2026-04-07 04:30:26 +08:00
|
|
|
|
|
|
|
|
ShaderCompileDesc vulkanCompileDesc = {};
|
2026-04-10 23:11:11 +08:00
|
|
|
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
2026-04-07 04:30:26 +08:00
|
|
|
*pass,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::Vulkan,
|
|
|
|
|
*vulkanFragment,
|
|
|
|
|
vulkanCompileDesc);
|
|
|
|
|
const std::string vulkanSource(
|
|
|
|
|
reinterpret_cast<const char*>(vulkanCompileDesc.source.data()),
|
|
|
|
|
vulkanCompileDesc.source.size());
|
|
|
|
|
EXPECT_NE(vulkanSource.find("ApplyLinearToSRGB"), std::string::npos);
|
|
|
|
|
EXPECT_NE(vulkanSource.find("ApplyToneMapping"), std::string::npos);
|
|
|
|
|
|
|
|
|
|
delete shader;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-09 02:59:36 +08:00
|
|
|
TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringVolumetricBindingsToDescriptorSpaces) {
|
|
|
|
|
ShaderLoader loader;
|
|
|
|
|
LoadResult result = loader.Load(GetBuiltinVolumetricShaderPath());
|
|
|
|
|
ASSERT_TRUE(result);
|
|
|
|
|
ASSERT_NE(result.resource, nullptr);
|
|
|
|
|
|
|
|
|
|
Shader* shader = static_cast<Shader*>(result.resource);
|
|
|
|
|
ASSERT_NE(shader, nullptr);
|
|
|
|
|
|
|
|
|
|
const ShaderPass* pass = shader->FindPass("Volumetric");
|
|
|
|
|
ASSERT_NE(pass, nullptr);
|
|
|
|
|
|
|
|
|
|
const ShaderStageVariant* d3d12Fragment = shader->FindVariant(
|
|
|
|
|
"Volumetric",
|
|
|
|
|
XCEngine::Resources::ShaderType::Fragment,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::D3D12);
|
|
|
|
|
ASSERT_NE(d3d12Fragment, nullptr);
|
|
|
|
|
|
|
|
|
|
ShaderCompileDesc d3d12CompileDesc = {};
|
2026-04-10 23:11:11 +08:00
|
|
|
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
2026-04-09 02:59:36 +08:00
|
|
|
*pass,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::D3D12,
|
|
|
|
|
*d3d12Fragment,
|
|
|
|
|
d3d12CompileDesc);
|
|
|
|
|
const std::string d3d12Source(
|
|
|
|
|
reinterpret_cast<const char*>(d3d12CompileDesc.source.data()),
|
|
|
|
|
d3d12CompileDesc.source.size());
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
d3d12Source,
|
|
|
|
|
"cbuffer PerObjectConstants",
|
|
|
|
|
"register(b0)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
d3d12Source,
|
2026-04-09 05:22:03 +08:00
|
|
|
"cbuffer LightingConstants",
|
2026-04-09 02:59:36 +08:00
|
|
|
"register(b1)"));
|
2026-04-09 05:22:03 +08:00
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
d3d12Source,
|
|
|
|
|
"cbuffer MaterialConstants",
|
|
|
|
|
"register(b2)"));
|
2026-04-09 02:59:36 +08:00
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
d3d12Source,
|
|
|
|
|
"StructuredBuffer<uint> VolumeData",
|
|
|
|
|
"register(t0)"));
|
|
|
|
|
|
|
|
|
|
const ShaderStageVariant* vulkanFragment = shader->FindVariant(
|
|
|
|
|
"Volumetric",
|
|
|
|
|
XCEngine::Resources::ShaderType::Fragment,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::Vulkan);
|
|
|
|
|
ASSERT_NE(vulkanFragment, nullptr);
|
|
|
|
|
|
|
|
|
|
ShaderCompileDesc vulkanCompileDesc = {};
|
2026-04-10 23:11:11 +08:00
|
|
|
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
2026-04-09 02:59:36 +08:00
|
|
|
*pass,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::Vulkan,
|
|
|
|
|
*vulkanFragment,
|
|
|
|
|
vulkanCompileDesc);
|
|
|
|
|
const std::string vulkanSource(
|
|
|
|
|
reinterpret_cast<const char*>(vulkanCompileDesc.source.data()),
|
|
|
|
|
vulkanCompileDesc.source.size());
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
vulkanSource,
|
|
|
|
|
"cbuffer PerObjectConstants",
|
|
|
|
|
"register(b0, space0)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
vulkanSource,
|
2026-04-09 05:22:03 +08:00
|
|
|
"cbuffer LightingConstants",
|
2026-04-09 02:59:36 +08:00
|
|
|
"register(b0, space1)"));
|
2026-04-09 05:22:03 +08:00
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
vulkanSource,
|
|
|
|
|
"cbuffer MaterialConstants",
|
|
|
|
|
"register(b0, space2)"));
|
2026-04-09 02:59:36 +08:00
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
vulkanSource,
|
|
|
|
|
"StructuredBuffer<uint> VolumeData",
|
2026-04-09 05:22:03 +08:00
|
|
|
"register(t1, space2)"));
|
2026-04-09 02:59:36 +08:00
|
|
|
|
|
|
|
|
delete shader;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-07 04:30:26 +08:00
|
|
|
TEST(BuiltinForwardPipeline_Test, OpenGLRuntimeTranspilesFinalColorVariantToCombinedSourceSampler) {
|
|
|
|
|
ShaderLoader loader;
|
|
|
|
|
LoadResult result = loader.Load(GetBuiltinFinalColorShaderPath());
|
|
|
|
|
ASSERT_TRUE(result);
|
|
|
|
|
ASSERT_NE(result.resource, nullptr);
|
|
|
|
|
|
|
|
|
|
Shader* shader = static_cast<Shader*>(result.resource);
|
|
|
|
|
ASSERT_NE(shader, nullptr);
|
|
|
|
|
|
|
|
|
|
const ShaderPass* pass = shader->FindPass("FinalColor");
|
|
|
|
|
ASSERT_NE(pass, nullptr);
|
|
|
|
|
|
|
|
|
|
const ShaderStageVariant* openGLFragment = shader->FindVariant(
|
|
|
|
|
"FinalColor",
|
|
|
|
|
XCEngine::Resources::ShaderType::Fragment,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::OpenGL);
|
|
|
|
|
ASSERT_NE(openGLFragment, nullptr);
|
|
|
|
|
|
|
|
|
|
ShaderCompileDesc compileDesc = {};
|
2026-04-10 23:11:11 +08:00
|
|
|
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
2026-04-07 04:30:26 +08:00
|
|
|
*pass,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::OpenGL,
|
|
|
|
|
*openGLFragment,
|
|
|
|
|
compileDesc);
|
|
|
|
|
|
|
|
|
|
XCEngine::RHI::CompiledSpirvShader spirvShader = {};
|
|
|
|
|
std::string errorMessage;
|
|
|
|
|
ASSERT_TRUE(
|
|
|
|
|
XCEngine::RHI::CompileSpirvShader(
|
|
|
|
|
compileDesc,
|
|
|
|
|
XCEngine::RHI::SpirvTargetEnvironment::Vulkan,
|
|
|
|
|
spirvShader,
|
|
|
|
|
&errorMessage))
|
|
|
|
|
<< errorMessage;
|
|
|
|
|
|
|
|
|
|
std::string glslSource;
|
|
|
|
|
ASSERT_TRUE(XCEngine::RHI::TranspileSpirvToOpenGLGLSL(spirvShader, glslSource, &errorMessage))
|
|
|
|
|
<< errorMessage;
|
|
|
|
|
EXPECT_NE(glslSource.find("uniform sampler2D SPIRV_Cross_Combined"), std::string::npos);
|
|
|
|
|
EXPECT_NE(glslSource.find("texture(SPIRV_Cross_Combined"), std::string::npos);
|
|
|
|
|
EXPECT_NE(glslSource.find("FinalColorConstants"), std::string::npos);
|
|
|
|
|
|
|
|
|
|
delete shader;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-07 14:13:26 +08:00
|
|
|
TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringColorScaleBindingsToDescriptorSpaces) {
|
2026-04-07 04:30:26 +08:00
|
|
|
ShaderLoader loader;
|
|
|
|
|
LoadResult result = loader.Load(GetBuiltinColorScalePostProcessShaderPath());
|
|
|
|
|
ASSERT_TRUE(result);
|
|
|
|
|
ASSERT_NE(result.resource, nullptr);
|
|
|
|
|
|
|
|
|
|
Shader* shader = static_cast<Shader*>(result.resource);
|
|
|
|
|
ASSERT_NE(shader, nullptr);
|
2026-04-06 16:15:19 +08:00
|
|
|
|
2026-04-07 04:30:26 +08:00
|
|
|
const ShaderPass* pass = shader->FindPass("ColorScale");
|
|
|
|
|
ASSERT_NE(pass, nullptr);
|
2026-04-06 16:15:19 +08:00
|
|
|
|
2026-04-07 04:30:26 +08:00
|
|
|
const ShaderStageVariant* d3d12Fragment = shader->FindVariant(
|
|
|
|
|
"ColorScale",
|
|
|
|
|
XCEngine::Resources::ShaderType::Fragment,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::D3D12);
|
|
|
|
|
ASSERT_NE(d3d12Fragment, nullptr);
|
|
|
|
|
|
|
|
|
|
ShaderCompileDesc d3d12CompileDesc = {};
|
2026-04-10 23:11:11 +08:00
|
|
|
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
2026-04-07 04:30:26 +08:00
|
|
|
*pass,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::D3D12,
|
|
|
|
|
*d3d12Fragment,
|
|
|
|
|
d3d12CompileDesc);
|
|
|
|
|
const std::string d3d12Source(
|
|
|
|
|
reinterpret_cast<const char*>(d3d12CompileDesc.source.data()),
|
|
|
|
|
d3d12CompileDesc.source.size());
|
2026-04-07 10:34:20 +08:00
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
d3d12Source,
|
|
|
|
|
"cbuffer PostProcessConstants",
|
|
|
|
|
"register(b0)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
d3d12Source,
|
2026-04-08 04:44:36 +08:00
|
|
|
"Texture2D SourceColorTexture",
|
2026-04-07 10:34:20 +08:00
|
|
|
"register(t0)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
d3d12Source,
|
2026-04-08 04:44:36 +08:00
|
|
|
"SamplerState LinearClampSampler",
|
2026-04-07 10:34:20 +08:00
|
|
|
"register(s0)"));
|
|
|
|
|
EXPECT_EQ(d3d12Source.find("space0"), std::string::npos);
|
2026-04-07 04:30:26 +08:00
|
|
|
EXPECT_EQ(d3d12Source.find("space1"), std::string::npos);
|
|
|
|
|
|
|
|
|
|
const ShaderStageVariant* vulkanFragment = shader->FindVariant(
|
|
|
|
|
"ColorScale",
|
|
|
|
|
XCEngine::Resources::ShaderType::Fragment,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::Vulkan);
|
|
|
|
|
ASSERT_NE(vulkanFragment, nullptr);
|
|
|
|
|
|
|
|
|
|
const std::string runtimeSource =
|
2026-04-10 23:11:11 +08:00
|
|
|
::XCEngine::Rendering::Internal::BuildRuntimeShaderSource(
|
2026-04-07 04:30:26 +08:00
|
|
|
*pass,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::Vulkan,
|
|
|
|
|
*vulkanFragment);
|
2026-04-07 10:34:20 +08:00
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
runtimeSource,
|
|
|
|
|
"cbuffer PostProcessConstants",
|
|
|
|
|
"register(b0, space0)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
runtimeSource,
|
2026-04-08 04:44:36 +08:00
|
|
|
"Texture2D SourceColorTexture",
|
2026-04-07 10:34:20 +08:00
|
|
|
"register(t0, space1)"));
|
|
|
|
|
EXPECT_TRUE(SourceContainsRegisterBinding(
|
|
|
|
|
runtimeSource,
|
2026-04-08 04:44:36 +08:00
|
|
|
"SamplerState LinearClampSampler",
|
2026-04-07 10:34:20 +08:00
|
|
|
"register(s0, space2)"));
|
2026-04-07 04:30:26 +08:00
|
|
|
|
|
|
|
|
ShaderCompileDesc vulkanCompileDesc = {};
|
2026-04-10 23:11:11 +08:00
|
|
|
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
2026-04-07 04:30:26 +08:00
|
|
|
*pass,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::Vulkan,
|
|
|
|
|
*vulkanFragment,
|
|
|
|
|
vulkanCompileDesc);
|
|
|
|
|
const std::string vulkanSource(
|
|
|
|
|
reinterpret_cast<const char*>(vulkanCompileDesc.source.data()),
|
|
|
|
|
vulkanCompileDesc.source.size());
|
2026-04-08 04:44:36 +08:00
|
|
|
EXPECT_NE(vulkanSource.find("SourceColorTexture.Sample"), std::string::npos);
|
2026-04-07 04:30:26 +08:00
|
|
|
EXPECT_NE(vulkanSource.find("gColorScale"), std::string::npos);
|
|
|
|
|
|
|
|
|
|
delete shader;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(BuiltinForwardPipeline_Test, OpenGLRuntimeTranspilesColorScaleVariantToCombinedSourceSampler) {
|
|
|
|
|
ShaderLoader loader;
|
|
|
|
|
LoadResult result = loader.Load(GetBuiltinColorScalePostProcessShaderPath());
|
|
|
|
|
ASSERT_TRUE(result);
|
|
|
|
|
ASSERT_NE(result.resource, nullptr);
|
|
|
|
|
|
|
|
|
|
Shader* shader = static_cast<Shader*>(result.resource);
|
|
|
|
|
ASSERT_NE(shader, nullptr);
|
|
|
|
|
|
|
|
|
|
const ShaderPass* pass = shader->FindPass("ColorScale");
|
|
|
|
|
ASSERT_NE(pass, nullptr);
|
|
|
|
|
|
|
|
|
|
const ShaderStageVariant* openGLFragment = shader->FindVariant(
|
|
|
|
|
"ColorScale",
|
|
|
|
|
XCEngine::Resources::ShaderType::Fragment,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::OpenGL);
|
|
|
|
|
ASSERT_NE(openGLFragment, nullptr);
|
|
|
|
|
|
|
|
|
|
ShaderCompileDesc compileDesc = {};
|
2026-04-10 23:11:11 +08:00
|
|
|
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
|
2026-04-07 04:30:26 +08:00
|
|
|
*pass,
|
|
|
|
|
XCEngine::Resources::ShaderBackend::OpenGL,
|
|
|
|
|
*openGLFragment,
|
|
|
|
|
compileDesc);
|
|
|
|
|
|
|
|
|
|
XCEngine::RHI::CompiledSpirvShader spirvShader = {};
|
|
|
|
|
std::string errorMessage;
|
|
|
|
|
ASSERT_TRUE(
|
|
|
|
|
XCEngine::RHI::CompileSpirvShader(
|
|
|
|
|
compileDesc,
|
|
|
|
|
XCEngine::RHI::SpirvTargetEnvironment::Vulkan,
|
|
|
|
|
spirvShader,
|
|
|
|
|
&errorMessage))
|
|
|
|
|
<< errorMessage;
|
|
|
|
|
|
|
|
|
|
std::string glslSource;
|
|
|
|
|
ASSERT_TRUE(XCEngine::RHI::TranspileSpirvToOpenGLGLSL(spirvShader, glslSource, &errorMessage))
|
|
|
|
|
<< errorMessage;
|
|
|
|
|
EXPECT_NE(glslSource.find("uniform sampler2D SPIRV_Cross_Combined"), std::string::npos);
|
|
|
|
|
EXPECT_NE(glslSource.find("texture(SPIRV_Cross_Combined"), std::string::npos);
|
|
|
|
|
EXPECT_NE(glslSource.find("PostProcessConstants"), std::string::npos);
|
2026-04-06 16:15:19 +08:00
|
|
|
|
|
|
|
|
delete shader;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 04:44:36 +08:00
|
|
|
TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoadedForwardShaderContract) {
|
2026-04-03 16:59:18 +08:00
|
|
|
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;
|
2026-04-07 03:35:06 +08:00
|
|
|
EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr();
|
2026-04-03 16:59:18 +08:00
|
|
|
EXPECT_TRUE(plan.perObject.IsValid());
|
2026-04-05 15:44:37 +08:00
|
|
|
EXPECT_TRUE(plan.lighting.IsValid());
|
2026-04-03 16:59:18 +08:00
|
|
|
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-05 15:44:37 +08:00
|
|
|
EXPECT_EQ(plan.descriptorSetCount, 8u);
|
2026-04-03 17:18:46 +08:00
|
|
|
EXPECT_TRUE(plan.usesConstantBuffers);
|
|
|
|
|
EXPECT_TRUE(plan.usesTextures);
|
|
|
|
|
EXPECT_TRUE(plan.usesSamplers);
|
|
|
|
|
|
|
|
|
|
delete shader;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-07 03:35:06 +08:00
|
|
|
TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromBuiltinForwardShaderContract) {
|
2026-04-07 00:34:28 +08:00
|
|
|
ShaderPass pass = {};
|
|
|
|
|
pass.name = "ForwardLit";
|
|
|
|
|
|
|
|
|
|
ShaderPassTagEntry tag = {};
|
|
|
|
|
tag.name = "LightMode";
|
2026-04-08 13:13:42 +08:00
|
|
|
tag.value = "ForwardLit";
|
2026-04-07 00:34:28 +08:00
|
|
|
pass.tags.PushBack(tag);
|
2026-04-08 04:44:36 +08:00
|
|
|
AppendDefaultBuiltinPassResources(pass);
|
2026-04-07 00:34:28 +08:00
|
|
|
|
|
|
|
|
BuiltinPassResourceBindingPlan plan = {};
|
|
|
|
|
String error;
|
|
|
|
|
EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(pass, plan, &error)) << error.CStr();
|
|
|
|
|
EXPECT_TRUE(plan.perObject.IsValid());
|
|
|
|
|
EXPECT_TRUE(plan.lighting.IsValid());
|
|
|
|
|
EXPECT_TRUE(plan.material.IsValid());
|
|
|
|
|
EXPECT_TRUE(plan.shadowReceiver.IsValid());
|
|
|
|
|
EXPECT_TRUE(plan.baseColorTexture.IsValid());
|
|
|
|
|
EXPECT_TRUE(plan.linearClampSampler.IsValid());
|
|
|
|
|
EXPECT_TRUE(plan.shadowMapTexture.IsValid());
|
|
|
|
|
EXPECT_TRUE(plan.shadowMapSampler.IsValid());
|
|
|
|
|
EXPECT_EQ(plan.firstDescriptorSet, 0u);
|
|
|
|
|
EXPECT_EQ(plan.descriptorSetCount, 8u);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 04:44:36 +08:00
|
|
|
TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoadedUnlitShaderContract) {
|
2026-04-03 17:18:46 +08:00
|
|
|
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;
|
2026-04-07 03:35:06 +08:00
|
|
|
EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr();
|
2026-04-03 17:18:46 +08:00
|
|
|
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-08 04:44:36 +08:00
|
|
|
TEST(BuiltinForwardPipeline_Test, UsesLoadedForwardShaderResourceSetIndices) {
|
2026-04-05 13:50:52 +08:00
|
|
|
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-08 04:44:36 +08:00
|
|
|
EXPECT_EQ(pass->resources.Size(), 8u);
|
2026-04-03 16:59:18 +08:00
|
|
|
|
|
|
|
|
BuiltinPassResourceBindingPlan plan = {};
|
|
|
|
|
String error;
|
2026-04-07 03:35:06 +08:00
|
|
|
EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr();
|
2026-04-05 15:44:37 +08:00
|
|
|
ASSERT_EQ(plan.bindings.Size(), 8u);
|
2026-04-05 13:50:52 +08:00
|
|
|
EXPECT_EQ(plan.perObject.set, 0u);
|
2026-04-05 15:44:37 +08:00
|
|
|
EXPECT_EQ(plan.lighting.set, 1u);
|
|
|
|
|
EXPECT_EQ(plan.material.set, 2u);
|
|
|
|
|
EXPECT_EQ(plan.shadowReceiver.set, 3u);
|
|
|
|
|
EXPECT_EQ(plan.baseColorTexture.set, 4u);
|
|
|
|
|
EXPECT_EQ(plan.linearClampSampler.set, 5u);
|
|
|
|
|
EXPECT_EQ(plan.shadowMapTexture.set, 6u);
|
|
|
|
|
EXPECT_EQ(plan.shadowMapSampler.set, 7u);
|
2026-04-05 13:50:52 +08:00
|
|
|
|
|
|
|
|
delete shader;
|
2026-04-03 16:59:18 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-08 04:44:36 +08:00
|
|
|
TEST(BuiltinPassLayout_Test, BuildsSharedSetLayoutsFromLoadedForwardShaderResources) {
|
2026-04-05 13:50:52 +08:00
|
|
|
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-08 04:44:36 +08:00
|
|
|
EXPECT_EQ(pass->resources.Size(), 8u);
|
2026-04-04 13:48:13 +08:00
|
|
|
|
|
|
|
|
BuiltinPassResourceBindingPlan plan = {};
|
|
|
|
|
String error;
|
2026-04-07 03:35:06 +08:00
|
|
|
ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, 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 15:44:37 +08:00
|
|
|
ASSERT_EQ(setLayouts.size(), 8u);
|
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 15:44:37 +08:00
|
|
|
EXPECT_TRUE(setLayouts[1].usesLighting);
|
2026-04-04 13:48:13 +08:00
|
|
|
EXPECT_FALSE(setLayouts[1].shaderVisible);
|
|
|
|
|
|
|
|
|
|
EXPECT_EQ(setLayouts[2].layout.bindingCount, 1u);
|
2026-04-05 15:44:37 +08:00
|
|
|
EXPECT_TRUE(setLayouts[2].usesMaterial);
|
|
|
|
|
EXPECT_FALSE(setLayouts[2].shaderVisible);
|
2026-04-04 13:48:13 +08:00
|
|
|
|
|
|
|
|
EXPECT_EQ(setLayouts[3].layout.bindingCount, 1u);
|
2026-04-05 15:44:37 +08:00
|
|
|
EXPECT_TRUE(setLayouts[3].usesShadowReceiver);
|
|
|
|
|
EXPECT_FALSE(setLayouts[3].shaderVisible);
|
2026-04-04 13:48:13 +08:00
|
|
|
|
|
|
|
|
EXPECT_EQ(setLayouts[4].layout.bindingCount, 1u);
|
2026-04-05 15:44:37 +08:00
|
|
|
EXPECT_TRUE(setLayouts[4].usesTexture);
|
|
|
|
|
EXPECT_TRUE(setLayouts[4].shaderVisible);
|
|
|
|
|
EXPECT_EQ(setLayouts[4].heapType, DescriptorHeapType::CBV_SRV_UAV);
|
2026-04-05 13:50:52 +08:00
|
|
|
|
|
|
|
|
EXPECT_EQ(setLayouts[5].layout.bindingCount, 1u);
|
2026-04-05 15:44:37 +08:00
|
|
|
EXPECT_TRUE(setLayouts[5].usesSampler);
|
2026-04-05 13:50:52 +08:00
|
|
|
EXPECT_TRUE(setLayouts[5].shaderVisible);
|
2026-04-05 15:44:37 +08:00
|
|
|
EXPECT_EQ(setLayouts[5].heapType, DescriptorHeapType::Sampler);
|
2026-04-05 13:50:52 +08:00
|
|
|
|
|
|
|
|
EXPECT_EQ(setLayouts[6].layout.bindingCount, 1u);
|
2026-04-05 15:44:37 +08:00
|
|
|
EXPECT_TRUE(setLayouts[6].usesTexture);
|
2026-04-05 13:50:52 +08:00
|
|
|
EXPECT_TRUE(setLayouts[6].shaderVisible);
|
2026-04-05 15:44:37 +08:00
|
|
|
|
|
|
|
|
EXPECT_EQ(setLayouts[7].layout.bindingCount, 1u);
|
|
|
|
|
EXPECT_TRUE(setLayouts[7].usesSampler);
|
|
|
|
|
EXPECT_TRUE(setLayouts[7].shaderVisible);
|
|
|
|
|
EXPECT_EQ(setLayouts[7].heapType, DescriptorHeapType::Sampler);
|
2026-04-05 13:50:52 +08:00
|
|
|
|
|
|
|
|
delete shader;
|
2026-04-04 13:48:13 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-07 14:13:26 +08:00
|
|
|
TEST(BuiltinDepthStylePass_Test, BuiltinDepthOnlyShaderUsesAuthoringContract) {
|
2026-04-04 14:27:44 +08:00
|
|
|
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);
|
2026-04-08 04:44:36 +08:00
|
|
|
EXPECT_EQ(pass->resources.Size(), 4u);
|
2026-04-07 03:35:06 +08:00
|
|
|
EXPECT_TRUE(pass->hasFixedFunctionState);
|
|
|
|
|
EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::Back);
|
|
|
|
|
EXPECT_TRUE(pass->fixedFunctionState.depthWriteEnable);
|
|
|
|
|
EXPECT_EQ(pass->fixedFunctionState.depthFunc, MaterialComparisonFunc::LessEqual);
|
2026-04-04 14:27:44 +08:00
|
|
|
|
|
|
|
|
delete shader;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-07 14:13:26 +08:00
|
|
|
TEST(BuiltinDepthStylePass_Test, BuiltinShadowCasterShaderUsesAuthoringContract) {
|
2026-04-04 14:27:44 +08:00
|
|
|
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);
|
2026-04-08 04:44:36 +08:00
|
|
|
EXPECT_EQ(pass->resources.Size(), 4u);
|
2026-04-07 03:35:06 +08:00
|
|
|
EXPECT_TRUE(pass->hasFixedFunctionState);
|
|
|
|
|
EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::Back);
|
|
|
|
|
EXPECT_TRUE(pass->fixedFunctionState.depthWriteEnable);
|
|
|
|
|
EXPECT_EQ(pass->fixedFunctionState.depthFunc, MaterialComparisonFunc::LessEqual);
|
2026-04-04 14:27:44 +08:00
|
|
|
|
|
|
|
|
delete shader;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-07 14:13:26 +08:00
|
|
|
TEST(BuiltinObjectIdPass_Test, BuiltinObjectIdShaderUsesAuthoringContract) {
|
2026-04-03 17:05:38 +08:00
|
|
|
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);
|
2026-04-08 04:44:36 +08:00
|
|
|
EXPECT_EQ(pass->resources.Size(), 1u);
|
2026-04-07 00:38:00 +08:00
|
|
|
EXPECT_TRUE(pass->hasFixedFunctionState);
|
|
|
|
|
EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::Back);
|
|
|
|
|
EXPECT_TRUE(pass->fixedFunctionState.depthWriteEnable);
|
|
|
|
|
EXPECT_EQ(pass->fixedFunctionState.depthFunc, MaterialComparisonFunc::LessEqual);
|
2026-04-03 17:05:38 +08:00
|
|
|
|
|
|
|
|
delete shader;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-07 00:38:00 +08:00
|
|
|
TEST(BuiltinObjectIdPass_Test, BuildsBuiltinPassResourceBindingPlanFromBuiltinObjectIdShaderContract) {
|
2026-04-05 13:38:50 +08:00
|
|
|
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);
|
2026-04-03 17:05:38 +08:00
|
|
|
|
|
|
|
|
BuiltinPassResourceBindingPlan plan = {};
|
|
|
|
|
String error;
|
2026-04-07 00:38:00 +08:00
|
|
|
EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, 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-08 04:44:36 +08:00
|
|
|
TEST(BuiltinObjectIdPass_Test, BuildsBuiltinPassResourceBindingPlanFromExplicitObjectIdContract) {
|
2026-04-07 00:34:28 +08:00
|
|
|
ShaderPass pass = {};
|
|
|
|
|
pass.name = "ObjectId";
|
|
|
|
|
|
|
|
|
|
ShaderPassTagEntry tag = {};
|
|
|
|
|
tag.name = "LightMode";
|
|
|
|
|
tag.value = "ObjectId";
|
|
|
|
|
pass.tags.PushBack(tag);
|
2026-04-08 04:44:36 +08:00
|
|
|
AppendDefaultBuiltinPassResources(pass);
|
2026-04-07 00:34:28 +08:00
|
|
|
|
|
|
|
|
BuiltinPassResourceBindingPlan plan = {};
|
|
|
|
|
String error;
|
|
|
|
|
EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(pass, plan, &error)) << error.CStr();
|
|
|
|
|
ASSERT_EQ(plan.bindings.Size(), 1u);
|
|
|
|
|
EXPECT_TRUE(plan.perObject.IsValid());
|
|
|
|
|
EXPECT_FALSE(plan.material.IsValid());
|
|
|
|
|
EXPECT_EQ(plan.firstDescriptorSet, 0u);
|
|
|
|
|
EXPECT_EQ(plan.descriptorSetCount, 1u);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 04:44:36 +08:00
|
|
|
TEST(BuiltinDepthStylePass_Test, BuildsBuiltinPassResourceBindingPlanFromExplicitDepthOnlyContract) {
|
2026-04-07 00:34:28 +08:00
|
|
|
ShaderPass pass = {};
|
|
|
|
|
pass.name = "DepthOnly";
|
|
|
|
|
|
|
|
|
|
ShaderPassTagEntry tag = {};
|
|
|
|
|
tag.name = "LightMode";
|
|
|
|
|
tag.value = "DepthOnly";
|
|
|
|
|
pass.tags.PushBack(tag);
|
2026-04-08 04:44:36 +08:00
|
|
|
AppendDefaultBuiltinPassResources(pass);
|
2026-04-07 00:34:28 +08:00
|
|
|
|
|
|
|
|
BuiltinPassResourceBindingPlan plan = {};
|
|
|
|
|
String error;
|
|
|
|
|
EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(pass, plan, &error)) << error.CStr();
|
|
|
|
|
EXPECT_TRUE(plan.perObject.IsValid());
|
|
|
|
|
EXPECT_TRUE(plan.material.IsValid());
|
|
|
|
|
EXPECT_TRUE(plan.baseColorTexture.IsValid());
|
|
|
|
|
EXPECT_TRUE(plan.linearClampSampler.IsValid());
|
|
|
|
|
EXPECT_EQ(plan.firstDescriptorSet, 0u);
|
|
|
|
|
EXPECT_EQ(plan.descriptorSetCount, 4u);
|
|
|
|
|
}
|
|
|
|
|
|
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);
|
2026-04-04 13:48:13 +08:00
|
|
|
|
|
|
|
|
BuiltinPassResourceBindingPlan plan = {};
|
|
|
|
|
String error;
|
2026-04-07 00:38:00 +08:00
|
|
|
ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, 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-07 03:35:06 +08:00
|
|
|
TEST(BuiltinPassLayout_Test, BuildsSharedSetLayoutsFromBuiltinDepthOnlyShaderContract) {
|
2026-04-05 13:38:50 +08:00
|
|
|
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);
|
2026-04-04 14:27:44 +08:00
|
|
|
|
|
|
|
|
BuiltinPassResourceBindingPlan plan = {};
|
|
|
|
|
String error;
|
2026-04-07 03:35:06 +08:00
|
|
|
ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr();
|
2026-04-06 21:05:50 +08:00
|
|
|
EXPECT_TRUE(plan.perObject.IsValid());
|
|
|
|
|
EXPECT_TRUE(plan.material.IsValid());
|
|
|
|
|
EXPECT_TRUE(plan.baseColorTexture.IsValid());
|
|
|
|
|
EXPECT_TRUE(plan.linearClampSampler.IsValid());
|
|
|
|
|
EXPECT_EQ(plan.firstDescriptorSet, 0u);
|
|
|
|
|
EXPECT_EQ(plan.descriptorSetCount, 4u);
|
|
|
|
|
EXPECT_TRUE(plan.usesConstantBuffers);
|
|
|
|
|
EXPECT_TRUE(plan.usesTextures);
|
|
|
|
|
EXPECT_TRUE(plan.usesSamplers);
|
2026-04-04 14:27:44 +08:00
|
|
|
|
|
|
|
|
std::vector<BuiltinPassSetLayoutMetadata> setLayouts;
|
|
|
|
|
ASSERT_TRUE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)) << error.CStr();
|
2026-04-06 21:05:50 +08:00
|
|
|
ASSERT_EQ(setLayouts.size(), 4u);
|
2026-04-04 14:27:44 +08:00
|
|
|
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-06 21:05:50 +08:00
|
|
|
EXPECT_EQ(setLayouts[1].layout.bindingCount, 1u);
|
|
|
|
|
EXPECT_FALSE(setLayouts[1].usesPerObject);
|
|
|
|
|
EXPECT_TRUE(setLayouts[1].usesMaterial);
|
|
|
|
|
EXPECT_FALSE(setLayouts[1].usesTexture);
|
|
|
|
|
EXPECT_FALSE(setLayouts[1].usesSampler);
|
|
|
|
|
EXPECT_EQ(setLayouts[2].layout.bindingCount, 1u);
|
|
|
|
|
EXPECT_FALSE(setLayouts[2].usesPerObject);
|
|
|
|
|
EXPECT_FALSE(setLayouts[2].usesMaterial);
|
|
|
|
|
EXPECT_TRUE(setLayouts[2].usesTexture);
|
|
|
|
|
EXPECT_FALSE(setLayouts[2].usesSampler);
|
|
|
|
|
EXPECT_TRUE(setLayouts[2].shaderVisible);
|
|
|
|
|
EXPECT_EQ(setLayouts[3].layout.bindingCount, 1u);
|
|
|
|
|
EXPECT_FALSE(setLayouts[3].usesPerObject);
|
|
|
|
|
EXPECT_FALSE(setLayouts[3].usesMaterial);
|
|
|
|
|
EXPECT_FALSE(setLayouts[3].usesTexture);
|
|
|
|
|
EXPECT_TRUE(setLayouts[3].usesSampler);
|
|
|
|
|
EXPECT_TRUE(setLayouts[3].shaderVisible);
|
|
|
|
|
EXPECT_EQ(setLayouts[3].heapType, DescriptorHeapType::Sampler);
|
2026-04-05 13:38:50 +08:00
|
|
|
|
|
|
|
|
delete shader;
|
2026-04-04 14:27:44 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-07 03:35:06 +08:00
|
|
|
TEST(BuiltinPassLayout_Test, BuildsSharedSetLayoutsFromBuiltinShadowCasterShaderContract) {
|
2026-04-05 13:38:50 +08:00
|
|
|
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);
|
2026-04-04 14:27:44 +08:00
|
|
|
|
|
|
|
|
BuiltinPassResourceBindingPlan plan = {};
|
|
|
|
|
String error;
|
2026-04-07 03:35:06 +08:00
|
|
|
ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr();
|
2026-04-06 21:05:50 +08:00
|
|
|
EXPECT_TRUE(plan.perObject.IsValid());
|
|
|
|
|
EXPECT_TRUE(plan.material.IsValid());
|
|
|
|
|
EXPECT_TRUE(plan.baseColorTexture.IsValid());
|
|
|
|
|
EXPECT_TRUE(plan.linearClampSampler.IsValid());
|
|
|
|
|
EXPECT_EQ(plan.firstDescriptorSet, 0u);
|
|
|
|
|
EXPECT_EQ(plan.descriptorSetCount, 4u);
|
|
|
|
|
EXPECT_TRUE(plan.usesConstantBuffers);
|
|
|
|
|
EXPECT_TRUE(plan.usesTextures);
|
|
|
|
|
EXPECT_TRUE(plan.usesSamplers);
|
2026-04-04 14:27:44 +08:00
|
|
|
|
|
|
|
|
std::vector<BuiltinPassSetLayoutMetadata> setLayouts;
|
|
|
|
|
ASSERT_TRUE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)) << error.CStr();
|
2026-04-06 21:05:50 +08:00
|
|
|
ASSERT_EQ(setLayouts.size(), 4u);
|
2026-04-04 14:27:44 +08:00
|
|
|
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-06 21:05:50 +08:00
|
|
|
EXPECT_EQ(setLayouts[1].layout.bindingCount, 1u);
|
|
|
|
|
EXPECT_FALSE(setLayouts[1].usesPerObject);
|
|
|
|
|
EXPECT_TRUE(setLayouts[1].usesMaterial);
|
|
|
|
|
EXPECT_FALSE(setLayouts[1].usesTexture);
|
|
|
|
|
EXPECT_FALSE(setLayouts[1].usesSampler);
|
|
|
|
|
EXPECT_EQ(setLayouts[2].layout.bindingCount, 1u);
|
|
|
|
|
EXPECT_FALSE(setLayouts[2].usesPerObject);
|
|
|
|
|
EXPECT_FALSE(setLayouts[2].usesMaterial);
|
|
|
|
|
EXPECT_TRUE(setLayouts[2].usesTexture);
|
|
|
|
|
EXPECT_FALSE(setLayouts[2].usesSampler);
|
|
|
|
|
EXPECT_TRUE(setLayouts[2].shaderVisible);
|
|
|
|
|
EXPECT_EQ(setLayouts[3].layout.bindingCount, 1u);
|
|
|
|
|
EXPECT_FALSE(setLayouts[3].usesPerObject);
|
|
|
|
|
EXPECT_FALSE(setLayouts[3].usesMaterial);
|
|
|
|
|
EXPECT_FALSE(setLayouts[3].usesTexture);
|
|
|
|
|
EXPECT_TRUE(setLayouts[3].usesSampler);
|
|
|
|
|
EXPECT_TRUE(setLayouts[3].shaderVisible);
|
|
|
|
|
EXPECT_EQ(setLayouts[3].heapType, DescriptorHeapType::Sampler);
|
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
|
|
|
|
2026-04-08 19:18:07 +08:00
|
|
|
TEST(BuiltinPassLayout_Test, AcceptsRuntimeMaterialBufferBindingsWithoutBuiltinSemanticMetadata) {
|
|
|
|
|
Array<ShaderResourceBindingDesc> bindings;
|
|
|
|
|
|
|
|
|
|
ShaderResourceBindingDesc perObjectBinding = {};
|
|
|
|
|
perObjectBinding.name = "PerObjectConstants";
|
|
|
|
|
perObjectBinding.type = ShaderResourceType::ConstantBuffer;
|
|
|
|
|
perObjectBinding.set = 0u;
|
|
|
|
|
perObjectBinding.binding = 0u;
|
|
|
|
|
perObjectBinding.semantic = "PerObject";
|
|
|
|
|
bindings.PushBack(perObjectBinding);
|
|
|
|
|
|
|
|
|
|
ShaderResourceBindingDesc materialBinding = {};
|
|
|
|
|
materialBinding.name = "MaterialConstants";
|
|
|
|
|
materialBinding.type = ShaderResourceType::ConstantBuffer;
|
|
|
|
|
materialBinding.set = 1u;
|
|
|
|
|
materialBinding.binding = 0u;
|
|
|
|
|
materialBinding.semantic = "Material";
|
|
|
|
|
bindings.PushBack(materialBinding);
|
|
|
|
|
|
|
|
|
|
ShaderResourceBindingDesc bufferBinding = {};
|
|
|
|
|
bufferBinding.name = "VolumeNodes";
|
|
|
|
|
bufferBinding.type = ShaderResourceType::StructuredBuffer;
|
|
|
|
|
bufferBinding.set = 2u;
|
|
|
|
|
bufferBinding.binding = 0u;
|
|
|
|
|
bindings.PushBack(bufferBinding);
|
|
|
|
|
|
|
|
|
|
BuiltinPassResourceBindingPlan plan = {};
|
|
|
|
|
String error;
|
|
|
|
|
ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(bindings, plan, &error)) << error.CStr();
|
|
|
|
|
EXPECT_TRUE(plan.usesMaterialBuffers);
|
|
|
|
|
ASSERT_EQ(plan.materialBufferBindings.Size(), 1u);
|
|
|
|
|
EXPECT_EQ(plan.materialBufferBindings[0].name, "VolumeNodes");
|
|
|
|
|
EXPECT_EQ(plan.materialBufferBindings[0].semantic, BuiltinPassResourceSemantic::MaterialBuffer);
|
|
|
|
|
EXPECT_EQ(plan.materialBufferBindings[0].resourceType, ShaderResourceType::StructuredBuffer);
|
|
|
|
|
EXPECT_EQ(plan.materialBufferBindings[0].location.set, 2u);
|
|
|
|
|
EXPECT_EQ(plan.materialBufferBindings[0].location.binding, 0u);
|
|
|
|
|
|
|
|
|
|
std::vector<BuiltinPassSetLayoutMetadata> setLayouts;
|
|
|
|
|
ASSERT_TRUE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)) << error.CStr();
|
|
|
|
|
ASSERT_EQ(setLayouts.size(), 3u);
|
|
|
|
|
EXPECT_TRUE(setLayouts[2].usesMaterialBuffers);
|
|
|
|
|
ASSERT_EQ(setLayouts[2].materialBufferBindings.size(), 1u);
|
|
|
|
|
EXPECT_EQ(setLayouts[2].materialBufferBindings[0].name, "VolumeNodes");
|
|
|
|
|
ASSERT_EQ(setLayouts[2].bindings.size(), 1u);
|
|
|
|
|
EXPECT_EQ(
|
|
|
|
|
static_cast<DescriptorType>(setLayouts[2].bindings[0].type),
|
|
|
|
|
DescriptorType::SRV);
|
|
|
|
|
EXPECT_EQ(setLayouts[2].bindings[0].resourceDimension, ResourceViewDimension::StructuredBuffer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST(BuiltinPassLayout_Test, AcceptsRuntimeMaterialRawUavBufferBindings) {
|
|
|
|
|
Array<ShaderResourceBindingDesc> bindings;
|
|
|
|
|
|
|
|
|
|
ShaderResourceBindingDesc bufferBinding = {};
|
|
|
|
|
bufferBinding.name = "VolumeCounters";
|
|
|
|
|
bufferBinding.type = ShaderResourceType::RWRawBuffer;
|
|
|
|
|
bufferBinding.set = 4u;
|
|
|
|
|
bufferBinding.binding = 3u;
|
|
|
|
|
bindings.PushBack(bufferBinding);
|
|
|
|
|
|
|
|
|
|
BuiltinPassResourceBindingPlan plan = {};
|
|
|
|
|
String error;
|
|
|
|
|
ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(bindings, plan, &error)) << error.CStr();
|
|
|
|
|
ASSERT_EQ(plan.materialBufferBindings.Size(), 1u);
|
|
|
|
|
EXPECT_EQ(plan.materialBufferBindings[0].semantic, BuiltinPassResourceSemantic::MaterialBuffer);
|
|
|
|
|
|
|
|
|
|
std::vector<BuiltinPassSetLayoutMetadata> setLayouts;
|
|
|
|
|
ASSERT_TRUE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)) << error.CStr();
|
|
|
|
|
ASSERT_EQ(setLayouts.size(), 5u);
|
|
|
|
|
ASSERT_EQ(setLayouts[4].bindings.size(), 1u);
|
|
|
|
|
EXPECT_EQ(
|
|
|
|
|
static_cast<DescriptorType>(setLayouts[4].bindings[0].type),
|
|
|
|
|
DescriptorType::UAV);
|
|
|
|
|
EXPECT_EQ(setLayouts[4].bindings[0].resourceDimension, ResourceViewDimension::RawBuffer);
|
|
|
|
|
EXPECT_TRUE(setLayouts[4].usesMaterialBuffers);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 03:02:30 +08:00
|
|
|
TEST(BuiltinPassLayout_Test, AcceptsRuntimeMaterialTextureBindingsWithoutBuiltinSemanticMetadata) {
|
|
|
|
|
Array<ShaderResourceBindingDesc> bindings;
|
|
|
|
|
|
|
|
|
|
ShaderResourceBindingDesc perObjectBinding = {};
|
|
|
|
|
perObjectBinding.name = "PerObjectConstants";
|
|
|
|
|
perObjectBinding.type = ShaderResourceType::ConstantBuffer;
|
|
|
|
|
perObjectBinding.set = 0u;
|
|
|
|
|
perObjectBinding.binding = 0u;
|
|
|
|
|
perObjectBinding.semantic = "PerObject";
|
|
|
|
|
bindings.PushBack(perObjectBinding);
|
|
|
|
|
|
|
|
|
|
ShaderResourceBindingDesc materialBinding = {};
|
|
|
|
|
materialBinding.name = "MaterialConstants";
|
|
|
|
|
materialBinding.type = ShaderResourceType::ConstantBuffer;
|
|
|
|
|
materialBinding.set = 1u;
|
|
|
|
|
materialBinding.binding = 0u;
|
|
|
|
|
materialBinding.semantic = "Material";
|
|
|
|
|
bindings.PushBack(materialBinding);
|
|
|
|
|
|
|
|
|
|
ShaderResourceBindingDesc textureBinding = {};
|
|
|
|
|
textureBinding.name = "_LightMap";
|
|
|
|
|
textureBinding.type = ShaderResourceType::Texture2D;
|
|
|
|
|
textureBinding.set = 4u;
|
|
|
|
|
textureBinding.binding = 1u;
|
|
|
|
|
bindings.PushBack(textureBinding);
|
|
|
|
|
|
|
|
|
|
BuiltinPassResourceBindingPlan plan = {};
|
|
|
|
|
String error;
|
|
|
|
|
ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(bindings, plan, &error)) << error.CStr();
|
|
|
|
|
EXPECT_TRUE(plan.usesMaterialTextures);
|
|
|
|
|
ASSERT_EQ(plan.materialTextureBindings.Size(), 1u);
|
|
|
|
|
EXPECT_EQ(plan.materialTextureBindings[0].name, "_LightMap");
|
|
|
|
|
EXPECT_EQ(plan.materialTextureBindings[0].semantic, BuiltinPassResourceSemantic::MaterialTexture);
|
|
|
|
|
EXPECT_EQ(plan.materialTextureBindings[0].resourceType, ShaderResourceType::Texture2D);
|
|
|
|
|
EXPECT_EQ(plan.materialTextureBindings[0].location.set, 4u);
|
|
|
|
|
EXPECT_EQ(plan.materialTextureBindings[0].location.binding, 1u);
|
|
|
|
|
|
|
|
|
|
std::vector<BuiltinPassSetLayoutMetadata> setLayouts;
|
|
|
|
|
ASSERT_TRUE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)) << error.CStr();
|
|
|
|
|
ASSERT_EQ(setLayouts.size(), 5u);
|
|
|
|
|
EXPECT_TRUE(setLayouts[4].usesTexture);
|
|
|
|
|
EXPECT_TRUE(setLayouts[4].usesMaterialTextures);
|
|
|
|
|
ASSERT_EQ(setLayouts[4].materialTextureBindings.size(), 1u);
|
|
|
|
|
EXPECT_EQ(setLayouts[4].materialTextureBindings[0].name, "_LightMap");
|
|
|
|
|
ASSERT_EQ(setLayouts[4].bindings.size(), 1u);
|
|
|
|
|
EXPECT_EQ(
|
|
|
|
|
static_cast<DescriptorType>(setLayouts[4].bindings[0].type),
|
|
|
|
|
DescriptorType::SRV);
|
|
|
|
|
EXPECT_EQ(setLayouts[4].bindings[0].resourceDimension, ResourceViewDimension::Texture2D);
|
|
|
|
|
}
|
|
|
|
|
|
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)));
|
|
|
|
|
}
|