Files
XCEngine/tests/Rendering/unit/test_builtin_forward_pipeline.cpp

3562 lines
138 KiB
C++

#include <gtest/gtest.h>
#include "Rendering/Internal/ShaderVariantUtils.h"
#include <XCEngine/Debug/Logger.h>
#include <XCEngine/Rendering/Builtin/BuiltinPassLayoutUtils.h>
#include <XCEngine/Rendering/Graph/RenderGraphCompiler.h>
#include "Rendering/Internal/RenderSurfacePipelineUtils.h"
#include <XCEngine/Rendering/Passes/BuiltinDepthOnlyPass.h>
#include <XCEngine/Rendering/Passes/BuiltinObjectIdPass.h>
#include <XCEngine/Rendering/Passes/BuiltinShadowCasterPass.h>
#include <array>
#include <functional>
#include <unordered_map>
#define private public
#include <XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h>
#undef private
#include <XCEngine/Rendering/RenderSurface.h>
#include <XCEngine/Resources/BuiltinResources.h>
#include <XCEngine/Resources/Mesh/Mesh.h>
#include <XCEngine/Resources/Shader/Shader.h>
#include <XCEngine/Resources/Shader/ShaderLoader.h>
#include <XCEngine/RHI/RHICommandQueue.h>
#include <XCEngine/RHI/RHIEnums.h>
#include <XCEngine/RHI/OpenGL/OpenGLPipelineLayout.h>
#include <XCEngine/RHI/ShaderCompiler/SpirvShaderCompiler.h>
#include <fstream>
#include <regex>
#include <string>
#include <vector>
using namespace XCEngine::Rendering::Pipelines;
using namespace XCEngine::Rendering::Passes;
using namespace XCEngine::Rendering;
using namespace XCEngine::Containers;
using namespace XCEngine::Resources;
using namespace XCEngine::RHI;
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;
}
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);
}
}
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;
};
class MockForwardPipelineLayout final : public XCEngine::RHI::RHIPipelineLayout {
public:
explicit MockForwardPipelineLayout(const XCEngine::RHI::RHIPipelineLayoutDesc& desc)
: m_desc(desc) {
}
bool Initialize(const XCEngine::RHI::RHIPipelineLayoutDesc& desc) override {
m_desc = desc;
return true;
}
void Shutdown() override {
m_shutdownCalled = true;
}
void* GetNativeHandle() override {
return nullptr;
}
bool m_shutdownCalled = false;
private:
XCEngine::RHI::RHIPipelineLayoutDesc m_desc = {};
};
class MockForwardTexture final : public XCEngine::RHI::RHITexture {
public:
explicit MockForwardTexture(const XCEngine::RHI::TextureDesc& desc)
: m_width(desc.width)
, m_height(desc.height)
, m_depth(desc.depth)
, m_mipLevels(desc.mipLevels)
, m_format(static_cast<XCEngine::RHI::Format>(desc.format))
, m_textureType(static_cast<XCEngine::RHI::TextureType>(desc.textureType)) {
}
uint32_t GetWidth() const override { return m_width; }
uint32_t GetHeight() const override { return m_height; }
uint32_t GetDepth() const override { return m_depth; }
uint32_t GetMipLevels() const override { return m_mipLevels; }
XCEngine::RHI::Format GetFormat() const override { return m_format; }
XCEngine::RHI::TextureType GetTextureType() const override { return m_textureType; }
XCEngine::RHI::ResourceStates GetState() const override { return m_state; }
void SetState(XCEngine::RHI::ResourceStates state) override { m_state = state; }
void* GetNativeHandle() override { return nullptr; }
const std::string& GetName() const override { return m_name; }
void SetName(const std::string& name) override { m_name = name; }
void Shutdown() override {}
private:
uint32_t m_width = 0u;
uint32_t m_height = 0u;
uint32_t m_depth = 0u;
uint32_t m_mipLevels = 0u;
XCEngine::RHI::Format m_format = XCEngine::RHI::Format::Unknown;
XCEngine::RHI::TextureType m_textureType = XCEngine::RHI::TextureType::Texture2D;
XCEngine::RHI::ResourceStates m_state = XCEngine::RHI::ResourceStates::Common;
std::string m_name;
};
class MockForwardSampler final : public XCEngine::RHI::RHISampler {
public:
void Shutdown() override {}
void Bind(unsigned int) override {}
void Unbind(unsigned int) override {}
void* GetNativeHandle() override { return nullptr; }
unsigned int GetID() override { return 0u; }
};
class MockForwardCommandList final : public XCEngine::RHI::RHICommandList {
public:
void Shutdown() override {}
void Reset() override {}
void Close() override {}
void TransitionBarrier(
XCEngine::RHI::RHIResourceView*,
XCEngine::RHI::ResourceStates,
XCEngine::RHI::ResourceStates) override {
}
void BeginRenderPass(
XCEngine::RHI::RHIRenderPass*,
XCEngine::RHI::RHIFramebuffer*,
const XCEngine::RHI::Rect&,
uint32_t,
const XCEngine::RHI::ClearValue*) override {
}
void EndRenderPass() override {
++endRenderPassCalls;
}
void SetShader(XCEngine::RHI::RHIShader*) override {}
void SetPipelineState(XCEngine::RHI::RHIPipelineState*) override {}
void SetGraphicsDescriptorSets(
uint32_t,
uint32_t,
XCEngine::RHI::RHIDescriptorSet**,
XCEngine::RHI::RHIPipelineLayout*) override {
}
void SetComputeDescriptorSets(
uint32_t,
uint32_t,
XCEngine::RHI::RHIDescriptorSet**,
XCEngine::RHI::RHIPipelineLayout*) override {
}
void SetPrimitiveTopology(XCEngine::RHI::PrimitiveTopology) override {}
void SetViewport(const XCEngine::RHI::Viewport&) override {}
void SetViewports(uint32_t, const XCEngine::RHI::Viewport*) override {}
void SetScissorRect(const XCEngine::RHI::Rect&) override {}
void SetScissorRects(uint32_t, const XCEngine::RHI::Rect*) override {}
void SetRenderTargets(
uint32_t,
XCEngine::RHI::RHIResourceView**,
XCEngine::RHI::RHIResourceView*) override {
}
void SetStencilRef(uint8_t) override {}
void SetBlendFactor(const float[4]) override {}
void SetVertexBuffers(
uint32_t,
uint32_t,
XCEngine::RHI::RHIResourceView**,
const uint64_t*,
const uint32_t*) override {
}
void SetIndexBuffer(XCEngine::RHI::RHIResourceView*, uint64_t) override {}
void Draw(uint32_t, uint32_t, uint32_t, uint32_t) override {}
void DrawIndexed(uint32_t, uint32_t, uint32_t, int32_t, uint32_t) override {}
void Clear(float, float, float, float, uint32_t) override {}
void ClearRenderTarget(
XCEngine::RHI::RHIResourceView*,
const float[4],
uint32_t,
const XCEngine::RHI::Rect*) override {
}
void ClearDepthStencil(
XCEngine::RHI::RHIResourceView*,
float,
uint8_t,
uint32_t,
const XCEngine::RHI::Rect*) override {
}
void CopyResource(XCEngine::RHI::RHIResourceView*, XCEngine::RHI::RHIResourceView*) override {}
void Dispatch(uint32_t, uint32_t, uint32_t) override {}
size_t endRenderPassCalls = 0u;
};
class MockForwardCommandQueue final : public XCEngine::RHI::RHICommandQueue {
public:
void Shutdown() override {}
void ExecuteCommandLists(uint32_t, void**) override {}
void Signal(XCEngine::RHI::RHIFence*, uint64_t) override {}
void Wait(XCEngine::RHI::RHIFence*, uint64_t) override {}
uint64_t GetCompletedValue() override { return 0u; }
void WaitForIdle() override {}
XCEngine::RHI::CommandQueueType GetType() const override { return XCEngine::RHI::CommandQueueType::Direct; }
uint64_t GetTimestampFrequency() const override { return 0u; }
void* GetNativeHandle() override { return nullptr; }
void WaitForPreviousFrame() override {}
uint64_t GetCurrentFrame() const override { return 0u; }
};
class MockForwardFailureDevice final : public XCEngine::RHI::RHIDevice {
public:
bool Initialize(const XCEngine::RHI::RHIDeviceDesc&) override { return true; }
void Shutdown() override {}
XCEngine::RHI::RHIBuffer* CreateBuffer(const XCEngine::RHI::BufferDesc&) override { return nullptr; }
XCEngine::RHI::RHITexture* CreateTexture(const XCEngine::RHI::TextureDesc& desc) override {
++createTextureCalls;
return new MockForwardTexture(desc);
}
XCEngine::RHI::RHITexture* CreateTexture(
const XCEngine::RHI::TextureDesc& desc,
const void*,
size_t,
uint32_t) override {
return CreateTexture(desc);
}
XCEngine::RHI::RHISwapChain* CreateSwapChain(
const XCEngine::RHI::SwapChainDesc&,
XCEngine::RHI::RHICommandQueue*) override { return nullptr; }
XCEngine::RHI::RHICommandList* CreateCommandList(const XCEngine::RHI::CommandListDesc&) override { return nullptr; }
XCEngine::RHI::RHICommandQueue* CreateCommandQueue(const XCEngine::RHI::CommandQueueDesc&) override { return nullptr; }
XCEngine::RHI::RHIShader* CreateShader(const XCEngine::RHI::ShaderCompileDesc&) override { return nullptr; }
XCEngine::RHI::RHIPipelineState* CreatePipelineState(const XCEngine::RHI::GraphicsPipelineDesc& desc) override {
++createPipelineStateCalls;
lastPipelineDesc = desc;
return nullptr;
}
XCEngine::RHI::RHIPipelineLayout* CreatePipelineLayout(
const XCEngine::RHI::RHIPipelineLayoutDesc& desc) override {
++createPipelineLayoutCalls;
return new MockForwardPipelineLayout(desc);
}
XCEngine::RHI::RHIFence* CreateFence(const XCEngine::RHI::FenceDesc&) override { return nullptr; }
XCEngine::RHI::RHISampler* CreateSampler(const XCEngine::RHI::SamplerDesc&) override {
++createSamplerCalls;
return new MockForwardSampler();
}
XCEngine::RHI::RHIRenderPass* CreateRenderPass(
uint32_t,
const XCEngine::RHI::AttachmentDesc*,
const XCEngine::RHI::AttachmentDesc*) override { return nullptr; }
XCEngine::RHI::RHIFramebuffer* CreateFramebuffer(
XCEngine::RHI::RHIRenderPass*,
uint32_t,
uint32_t,
uint32_t,
XCEngine::RHI::RHIResourceView**,
XCEngine::RHI::RHIResourceView*) override { return nullptr; }
XCEngine::RHI::RHIDescriptorPool* CreateDescriptorPool(const XCEngine::RHI::DescriptorPoolDesc&) override { return nullptr; }
XCEngine::RHI::RHIDescriptorSet* CreateDescriptorSet(
XCEngine::RHI::RHIDescriptorPool*,
const XCEngine::RHI::DescriptorSetLayoutDesc&) override { return nullptr; }
XCEngine::RHI::RHIResourceView* CreateVertexBufferView(
XCEngine::RHI::RHIBuffer*,
const XCEngine::RHI::ResourceViewDesc&) override { return nullptr; }
XCEngine::RHI::RHIResourceView* CreateIndexBufferView(
XCEngine::RHI::RHIBuffer*,
const XCEngine::RHI::ResourceViewDesc&) override { return nullptr; }
XCEngine::RHI::RHIResourceView* CreateRenderTargetView(
XCEngine::RHI::RHITexture*,
const XCEngine::RHI::ResourceViewDesc&) override { return nullptr; }
XCEngine::RHI::RHIResourceView* CreateDepthStencilView(
XCEngine::RHI::RHITexture*,
const XCEngine::RHI::ResourceViewDesc&) override { return nullptr; }
XCEngine::RHI::RHIResourceView* CreateShaderResourceView(
XCEngine::RHI::RHIBuffer*,
const XCEngine::RHI::ResourceViewDesc&) override { return nullptr; }
XCEngine::RHI::RHIResourceView* CreateShaderResourceView(
XCEngine::RHI::RHITexture*,
const XCEngine::RHI::ResourceViewDesc& desc) override {
++createShaderResourceViewCalls;
return new TestResourceView(
XCEngine::RHI::ResourceViewType::ShaderResource,
desc.dimension,
static_cast<XCEngine::RHI::Format>(desc.format));
}
XCEngine::RHI::RHIResourceView* CreateUnorderedAccessView(
XCEngine::RHI::RHIBuffer*,
const XCEngine::RHI::ResourceViewDesc&) override { return nullptr; }
XCEngine::RHI::RHIResourceView* CreateUnorderedAccessView(
XCEngine::RHI::RHITexture*,
const XCEngine::RHI::ResourceViewDesc&) override { return nullptr; }
const XCEngine::RHI::RHICapabilities& GetCapabilities() const override { return m_capabilities; }
const XCEngine::RHI::RHIDeviceInfo& GetDeviceInfo() const override { return m_deviceInfo; }
void* GetNativeDevice() override { return nullptr; }
size_t createPipelineLayoutCalls = 0u;
size_t createPipelineStateCalls = 0u;
size_t createSamplerCalls = 0u;
size_t createTextureCalls = 0u;
size_t createShaderResourceViewCalls = 0u;
XCEngine::RHI::GraphicsPipelineDesc lastPipelineDesc = {};
private:
XCEngine::RHI::RHICapabilities m_capabilities = {};
XCEngine::RHI::RHIDeviceInfo m_deviceInfo = {};
};
class TestSceneRenderFeaturePass final : public SceneRenderFeaturePass {
public:
explicit TestSceneRenderFeaturePass(
SceneRenderInjectionPoint injectionPoint = SceneRenderInjectionPoint::BeforeTransparent,
bool isActive = true,
std::vector<std::string>* eventLog = nullptr,
const char* label = "TestSceneRenderFeaturePass")
: m_injectionPoint(injectionPoint)
, m_isActive(isActive)
, m_eventLog(eventLog)
, m_label(label != nullptr ? label : "TestSceneRenderFeaturePass") {
}
const char* GetName() const override {
return m_label.c_str();
}
SceneRenderInjectionPoint GetInjectionPoint() const override {
return m_injectionPoint;
}
bool Initialize(const RenderContext&) override {
++initializeCallCount;
RecordEvent("Initialize");
return true;
}
void Shutdown() override {
++shutdownCallCount;
RecordEvent("Shutdown");
}
bool IsActive(const RenderSceneData&) const override {
++isActiveCallCount;
return m_isActive;
}
bool Prepare(const FrameExecutionContext&) override {
++prepareCallCount;
RecordEvent("Prepare");
return true;
}
bool Execute(const RenderPassContext&) override {
++executeCallCount;
RecordEvent("Execute");
return true;
}
size_t initializeCallCount = 0u;
mutable size_t isActiveCallCount = 0u;
size_t prepareCallCount = 0u;
size_t executeCallCount = 0u;
size_t shutdownCallCount = 0u;
private:
void RecordEvent(const char* suffix) {
if (m_eventLog == nullptr || suffix == nullptr) {
return;
}
m_eventLog->push_back(m_label + ":" + suffix);
}
SceneRenderInjectionPoint m_injectionPoint;
bool m_isActive = true;
std::vector<std::string>* m_eventLog = nullptr;
std::string m_label;
};
} // namespace
TEST(BuiltinForwardPipeline_Test, UsesFloat3PositionInputLayoutForStaticMeshVertices) {
const InputLayoutDesc inputLayout = BuiltinForwardPipeline::BuildInputLayout();
ASSERT_EQ(inputLayout.elements.size(), 7u);
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);
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)));
const InputElementDesc& backTexcoord = inputLayout.elements[3];
EXPECT_EQ(backTexcoord.semanticName, "TEXCOORD");
EXPECT_EQ(backTexcoord.semanticIndex, 1u);
EXPECT_EQ(backTexcoord.format, static_cast<uint32_t>(Format::R32G32_Float));
EXPECT_EQ(backTexcoord.inputSlot, 0u);
EXPECT_EQ(backTexcoord.alignedByteOffset, static_cast<uint32_t>(offsetof(StaticMeshVertex, uv1)));
const InputElementDesc& tangent = inputLayout.elements[4];
EXPECT_EQ(tangent.semanticName, "TEXCOORD");
EXPECT_EQ(tangent.semanticIndex, 2u);
EXPECT_EQ(tangent.format, static_cast<uint32_t>(Format::R32G32B32_Float));
EXPECT_EQ(tangent.inputSlot, 0u);
EXPECT_EQ(tangent.alignedByteOffset, static_cast<uint32_t>(offsetof(StaticMeshVertex, tangent)));
const InputElementDesc& bitangent = inputLayout.elements[5];
EXPECT_EQ(bitangent.semanticName, "TEXCOORD");
EXPECT_EQ(bitangent.semanticIndex, 3u);
EXPECT_EQ(bitangent.format, static_cast<uint32_t>(Format::R32G32B32_Float));
EXPECT_EQ(bitangent.inputSlot, 0u);
EXPECT_EQ(bitangent.alignedByteOffset, static_cast<uint32_t>(offsetof(StaticMeshVertex, bitangent)));
const InputElementDesc& color = inputLayout.elements[6];
EXPECT_EQ(color.semanticName, "COLOR");
EXPECT_EQ(color.semanticIndex, 0u);
EXPECT_EQ(color.format, static_cast<uint32_t>(Format::R32G32B32A32_Float));
EXPECT_EQ(color.inputSlot, 0u);
EXPECT_EQ(color.alignedByteOffset, static_cast<uint32_t>(offsetof(StaticMeshVertex, color)));
}
TEST(BuiltinForwardPipeline_Test, RecordsMainSceneGraphPassWithSampledShadowDependency) {
BuiltinForwardPipeline pipeline;
EXPECT_TRUE(pipeline.SupportsMainSceneRenderGraph());
RenderGraph graph = {};
RenderGraphBuilder graphBuilder(graph);
RenderGraphTextureDesc colorDesc = {};
colorDesc.width = 320u;
colorDesc.height = 180u;
colorDesc.format = static_cast<uint32_t>(Format::R8G8B8A8_UNorm);
colorDesc.textureType = static_cast<uint32_t>(XCEngine::RHI::TextureType::Texture2D);
colorDesc.sampleCount = 1u;
RenderGraphTextureDesc depthDesc = {};
depthDesc.width = 320u;
depthDesc.height = 180u;
depthDesc.format = static_cast<uint32_t>(Format::D24_UNorm_S8_UInt);
depthDesc.textureType = static_cast<uint32_t>(XCEngine::RHI::TextureType::Texture2D);
depthDesc.sampleCount = 1u;
TestResourceView colorView(
ResourceViewType::RenderTarget,
ResourceViewDimension::Texture2D,
Format::R8G8B8A8_UNorm);
TestResourceView depthView(
ResourceViewType::DepthStencil,
ResourceViewDimension::Texture2D,
Format::D24_UNorm_S8_UInt);
TestResourceView shadowView(
ResourceViewType::DepthStencil,
ResourceViewDimension::Texture2D,
Format::D24_UNorm_S8_UInt);
const RenderGraphImportedTextureOptions graphManagedImport = {
ResourceStates::Common,
ResourceStates::Common,
true
};
const RenderGraphTextureHandle colorTarget =
graphBuilder.ImportTexture("MainColor", colorDesc, &colorView, graphManagedImport);
const RenderGraphTextureHandle depthTarget =
graphBuilder.ImportTexture("MainDepth", depthDesc, &depthView, graphManagedImport);
const RenderGraphTextureHandle shadowTarget =
graphBuilder.ImportTexture("ShadowMap", depthDesc, &shadowView, graphManagedImport);
RenderSurface surface(320u, 180u);
surface.SetColorAttachment(&colorView);
surface.SetDepthAttachment(&depthView);
RenderContext renderContext = {};
RenderSceneData sceneData = {};
bool executionSucceeded = true;
const RenderPipelineMainSceneRenderGraphContext context = {
graphBuilder,
"MainScene",
renderContext,
sceneData,
surface,
nullptr,
nullptr,
ResourceStates::Common,
{ colorTarget },
depthTarget,
shadowTarget,
&executionSucceeded
};
ASSERT_TRUE(pipeline.RecordMainSceneRenderGraph(context));
CompiledRenderGraph compiledGraph = {};
String errorMessage;
ASSERT_TRUE(RenderGraphCompiler::Compile(graph, compiledGraph, &errorMessage))
<< errorMessage.CStr();
EXPECT_EQ(compiledGraph.GetPassCount(), 3u);
EXPECT_STREQ(compiledGraph.GetPassName(0).CStr(), "MainScene.Opaque");
EXPECT_STREQ(compiledGraph.GetPassName(1).CStr(), "MainScene.Skybox");
EXPECT_STREQ(compiledGraph.GetPassName(2).CStr(), "MainScene.Transparent");
EXPECT_EQ(compiledGraph.GetPassType(0), RenderGraphPassType::Raster);
EXPECT_EQ(compiledGraph.GetPassType(1), RenderGraphPassType::Raster);
EXPECT_EQ(compiledGraph.GetPassType(2), RenderGraphPassType::Raster);
RenderGraphTextureLifetime shadowLifetime = {};
ASSERT_TRUE(compiledGraph.TryGetTextureLifetime(shadowTarget, shadowLifetime));
EXPECT_TRUE(shadowLifetime.used);
EXPECT_EQ(shadowLifetime.firstPassIndex, 0u);
EXPECT_EQ(shadowLifetime.lastPassIndex, 2u);
RenderGraphTextureTransitionPlan shadowPlan = {};
ASSERT_TRUE(compiledGraph.TryGetTextureTransitionPlan(shadowTarget, shadowPlan));
EXPECT_TRUE(shadowPlan.graphOwnsTransitions);
EXPECT_TRUE(shadowPlan.hasFirstAccessState);
EXPECT_EQ(shadowPlan.firstAccessState, ResourceStates::PixelShaderResource);
EXPECT_EQ(shadowPlan.lastAccessState, ResourceStates::PixelShaderResource);
RenderGraphTextureTransitionPlan depthPlan = {};
ASSERT_TRUE(compiledGraph.TryGetTextureTransitionPlan(depthTarget, depthPlan));
EXPECT_EQ(depthPlan.firstAccessState, ResourceStates::DepthWrite);
EXPECT_EQ(depthPlan.lastAccessState, ResourceStates::DepthWrite);
}
TEST(SceneRenderFeaturePass_Test, SupportsExplicitInjectionPointContract) {
TestSceneRenderFeaturePass feature(SceneRenderInjectionPoint::AfterOpaque);
EXPECT_EQ(feature.GetInjectionPoint(), SceneRenderInjectionPoint::AfterOpaque);
EXPECT_TRUE(feature.SupportsInjectionPoint(SceneRenderInjectionPoint::AfterOpaque));
EXPECT_FALSE(feature.SupportsInjectionPoint(SceneRenderInjectionPoint::BeforeTransparent));
}
TEST(BuiltinForwardPipeline_Test, RegistersBuiltinDefaultForwardSceneFeatures) {
BuiltinForwardPipeline pipeline;
ASSERT_EQ(pipeline.GetForwardSceneFeaturePassCount(), 2u);
ASSERT_NE(pipeline.GetForwardSceneFeaturePass(0u), nullptr);
ASSERT_NE(pipeline.GetForwardSceneFeaturePass(1u), nullptr);
EXPECT_STREQ(pipeline.GetForwardSceneFeaturePass(0u)->GetName(), "BuiltinGaussianSplatPass");
EXPECT_STREQ(pipeline.GetForwardSceneFeaturePass(1u)->GetName(), "BuiltinVolumetricPass");
EXPECT_EQ(
pipeline.GetForwardSceneFeaturePass(0u)->GetInjectionPoint(),
SceneRenderInjectionPoint::BeforeTransparent);
EXPECT_EQ(
pipeline.GetForwardSceneFeaturePass(1u)->GetInjectionPoint(),
SceneRenderInjectionPoint::BeforeTransparent);
}
TEST(SceneRenderFeatureHost_Test, FiltersByInjectionPointAndShutsDownInReverseOrder) {
SceneRenderFeatureHost host;
std::vector<std::string> eventLog = {};
auto beforeOpaque = std::make_unique<TestSceneRenderFeaturePass>(
SceneRenderInjectionPoint::BeforeOpaque,
true,
&eventLog,
"BeforeOpaque");
auto afterTransparent = std::make_unique<TestSceneRenderFeaturePass>(
SceneRenderInjectionPoint::AfterTransparent,
true,
&eventLog,
"AfterTransparent");
auto inactive = std::make_unique<TestSceneRenderFeaturePass>(
SceneRenderInjectionPoint::BeforeOpaque,
false,
&eventLog,
"Inactive");
TestSceneRenderFeaturePass* beforeOpaqueRaw = beforeOpaque.get();
TestSceneRenderFeaturePass* afterTransparentRaw = afterTransparent.get();
TestSceneRenderFeaturePass* inactiveRaw = inactive.get();
host.AddFeaturePass(std::move(beforeOpaque));
host.AddFeaturePass(std::move(afterTransparent));
host.AddFeaturePass(std::move(inactive));
RenderContext renderContext = {};
RenderSurface surface(64u, 64u);
RenderSceneData sceneData = {};
const FrameExecutionContext executionContext(renderContext, surface, sceneData);
ASSERT_EQ(host.GetFeaturePassCount(), 3u);
EXPECT_TRUE(host.Initialize(renderContext));
EXPECT_TRUE(host.Prepare(executionContext));
EXPECT_TRUE(host.Execute(executionContext, SceneRenderInjectionPoint::BeforeOpaque));
EXPECT_TRUE(host.Execute(executionContext, SceneRenderInjectionPoint::AfterTransparent));
EXPECT_EQ(beforeOpaqueRaw->initializeCallCount, 1u);
EXPECT_EQ(afterTransparentRaw->initializeCallCount, 1u);
EXPECT_EQ(inactiveRaw->initializeCallCount, 1u);
EXPECT_EQ(beforeOpaqueRaw->prepareCallCount, 1u);
EXPECT_EQ(afterTransparentRaw->prepareCallCount, 1u);
EXPECT_EQ(inactiveRaw->prepareCallCount, 0u);
EXPECT_EQ(beforeOpaqueRaw->executeCallCount, 1u);
EXPECT_EQ(afterTransparentRaw->executeCallCount, 1u);
EXPECT_EQ(inactiveRaw->executeCallCount, 0u);
host.Shutdown();
EXPECT_EQ(beforeOpaqueRaw->shutdownCallCount, 1u);
EXPECT_EQ(afterTransparentRaw->shutdownCallCount, 1u);
EXPECT_EQ(inactiveRaw->shutdownCallCount, 1u);
ASSERT_GE(eventLog.size(), 7u);
EXPECT_EQ(eventLog[eventLog.size() - 1u], "BeforeOpaque:Shutdown");
EXPECT_EQ(eventLog[eventLog.size() - 2u], "AfterTransparent:Shutdown");
EXPECT_EQ(eventLog[eventLog.size() - 3u], "Inactive:Shutdown");
}
TEST(BuiltinForwardPipeline_Test, RegistersAdditionalForwardSceneFeaturePasses) {
BuiltinForwardPipeline pipeline;
const size_t initialFeaturePassCount = pipeline.GetForwardSceneFeaturePassCount();
auto featurePass = std::make_unique<TestSceneRenderFeaturePass>(
SceneRenderInjectionPoint::AfterTransparent);
TestSceneRenderFeaturePass* featurePassRaw = featurePass.get();
pipeline.AddForwardSceneFeaturePass(std::move(featurePass));
ASSERT_EQ(pipeline.GetForwardSceneFeaturePassCount(), initialFeaturePassCount + 1u);
EXPECT_EQ(pipeline.GetForwardSceneFeaturePass(initialFeaturePassCount), featurePassRaw);
ASSERT_NE(pipeline.GetForwardSceneFeaturePass(initialFeaturePassCount), nullptr);
EXPECT_EQ(
pipeline.GetForwardSceneFeaturePass(initialFeaturePassCount)->GetInjectionPoint(),
SceneRenderInjectionPoint::AfterTransparent);
EXPECT_EQ(pipeline.GetForwardSceneFeaturePass(initialFeaturePassCount + 1u), nullptr);
}
TEST(BuiltinForwardPipeline_Test, PropagatesForwardDrawFailureWhenPipelineStateCreationFails) {
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);
shader->m_guid = ResourceGUID();
Material material;
material.SetShader(ResourceHandle<Shader>(shader));
VisibleRenderItem visibleItem = {};
visibleItem.material = &material;
visibleItem.renderQueue = static_cast<int>(MaterialRenderQueue::Geometry);
RenderSceneData sceneData = {};
sceneData.visibleItems.push_back(visibleItem);
TestResourceView color(
ResourceViewType::RenderTarget,
ResourceViewDimension::Texture2D,
Format::R8G8B8A8_UNorm);
RenderSurface surface(64u, 64u);
surface.SetColorAttachment(&color);
MockForwardFailureDevice device;
MockForwardCommandList commandList;
MockForwardCommandQueue commandQueue;
RenderContext context = {};
context.device = &device;
context.commandList = &commandList;
context.commandQueue = &commandQueue;
context.backendType = RHIType::D3D12;
XCEngine::Debug::Logger::Get().SetCategoryEnabled(XCEngine::Debug::LogCategory::Rendering, false);
{
BuiltinForwardPipeline pipeline;
pipeline.m_forwardSceneFeatureHost.m_featurePasses.clear();
EXPECT_FALSE(pipeline.Render(context, surface, sceneData));
EXPECT_EQ(device.createPipelineLayoutCalls, 1u);
EXPECT_EQ(device.createPipelineStateCalls, 1u);
EXPECT_EQ(device.createSamplerCalls, 2u);
EXPECT_EQ(device.createTextureCalls, 2u);
EXPECT_EQ(device.createShaderResourceViewCalls, 2u);
EXPECT_EQ(commandList.endRenderPassCalls, 1u);
EXPECT_EQ(static_cast<Format>(device.lastPipelineDesc.renderTargetFormats[0]), Format::R8G8B8A8_UNorm);
}
ResourceManager::Get().UnloadAll();
ResourceManager::Get().Shutdown();
XCEngine::Debug::Logger::Get().SetCategoryEnabled(XCEngine::Debug::LogCategory::Rendering, true);
}
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);
}
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)));
}
TEST(BuiltinForwardPipeline_Test, BuiltinForwardShaderUsesAuthoringSurfaceContract) {
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 ShaderPropertyDesc* cutoff = shader->FindProperty("_Cutoff");
ASSERT_NE(cutoff, nullptr);
EXPECT_EQ(cutoff->type, ShaderPropertyType::Range);
EXPECT_EQ(cutoff->semantic, "AlphaCutoff");
const ShaderPass* pass = shader->FindPass("ForwardLit");
ASSERT_NE(pass, nullptr);
EXPECT_EQ(pass->resources.Size(), 8u);
EXPECT_TRUE(pass->hasFixedFunctionState);
EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::Back);
EXPECT_TRUE(pass->fixedFunctionState.depthWriteEnable);
EXPECT_EQ(pass->fixedFunctionState.depthFunc, MaterialComparisonFunc::LessEqual);
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);
delete shader;
}
TEST(BuiltinForwardPipeline_Test, BuiltinUnlitShaderUsesAuthoringSurfaceContract) {
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);
EXPECT_EQ(pass->resources.Size(), 4u);
EXPECT_TRUE(pass->hasFixedFunctionState);
EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::Back);
EXPECT_TRUE(pass->fixedFunctionState.depthWriteEnable);
EXPECT_EQ(pass->fixedFunctionState.depthFunc, MaterialComparisonFunc::LessEqual);
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);
delete shader;
}
TEST(BuiltinForwardPipeline_Test, BuiltinUnlitShaderBuildsVulkanRuntimeSourceWithResolvedBindings) {
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);
const ShaderStageVariant* fragmentVariant =
shader->FindVariant(
"Unlit",
XCEngine::Resources::ShaderType::Fragment,
XCEngine::Resources::ShaderBackend::Vulkan);
ASSERT_NE(fragmentVariant, nullptr);
const std::string runtimeSource =
::XCEngine::Rendering::Internal::BuildRuntimeShaderSource(
*pass,
XCEngine::Resources::ShaderBackend::Vulkan,
*fragmentVariant);
EXPECT_TRUE(SourceContainsRegisterBinding(
runtimeSource,
"cbuffer MaterialConstants",
"register(b0, space1)"));
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);
delete shader;
}
TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringUnlitBindingsToDescriptorSpaces) {
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);
const ShaderStageVariant* d3d12Fragment = shader->FindVariant(
"Unlit",
XCEngine::Resources::ShaderType::Fragment,
XCEngine::Resources::ShaderBackend::D3D12);
ASSERT_NE(d3d12Fragment, nullptr);
ShaderCompileDesc d3d12CompileDesc = {};
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
*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 MaterialConstants",
"register(b1)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
d3d12Source,
"Texture2D BaseColorTexture",
"register(t0)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
d3d12Source,
"SamplerState LinearClampSampler",
"register(s0)"));
const ShaderStageVariant* vulkanFragment = shader->FindVariant(
"Unlit",
XCEngine::Resources::ShaderType::Fragment,
XCEngine::Resources::ShaderBackend::Vulkan);
ASSERT_NE(vulkanFragment, nullptr);
ShaderCompileDesc vulkanCompileDesc = {};
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
*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 MaterialConstants",
"register(b0, space1)"));
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);
delete shader;
}
TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringForwardBindingsToDescriptorSpaces) {
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* d3d12Fragment = shader->FindVariant(
"ForwardLit",
XCEngine::Resources::ShaderType::Fragment,
XCEngine::Resources::ShaderBackend::D3D12,
shadowKeywords);
ASSERT_NE(d3d12Fragment, nullptr);
ShaderCompileDesc d3d12CompileDesc = {};
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
*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,
"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);
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 = {};
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
*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,
"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)"));
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 = {};
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
*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 = {};
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
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 = {};
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
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(BuiltinForwardPipeline_Test, BuiltinSkyboxShaderUsesAuthoringContract) {
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);
EXPECT_EQ(pass->resources.Size(), 5u);
EXPECT_TRUE(pass->hasFixedFunctionState);
EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::None);
EXPECT_FALSE(pass->fixedFunctionState.depthWriteEnable);
EXPECT_EQ(pass->fixedFunctionState.depthFunc, MaterialComparisonFunc::LessEqual);
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");
const ShaderPropertyDesc* panoramic = shader->FindProperty("_MainTex");
ASSERT_NE(panoramic, nullptr);
EXPECT_EQ(panoramic->type, ShaderPropertyType::Texture2D);
EXPECT_EQ(panoramic->semantic, "SkyboxPanoramicTexture");
const ShaderPropertyDesc* cubemap = shader->FindProperty("_Tex");
ASSERT_NE(cubemap, nullptr);
EXPECT_EQ(cubemap->type, ShaderPropertyType::TextureCube);
EXPECT_EQ(cubemap->semantic, "SkyboxTexture");
delete shader;
}
TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoadedSkyboxShaderContract) {
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);
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);
delete shader;
}
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);
EXPECT_EQ(pass->resources.Size(), 4u);
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);
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);
const ShaderResourceBindingDesc* viewData =
shader->FindPassResourceBinding("GaussianSplat", "GaussianSplatViewDataBuffer");
ASSERT_NE(viewData, nullptr);
EXPECT_EQ(viewData->type, ShaderResourceType::StructuredBuffer);
EXPECT_EQ(viewData->set, 2u);
EXPECT_EQ(viewData->binding, 1u);
EXPECT_EQ(
ResolveBuiltinPassResourceSemantic(*viewData),
BuiltinPassResourceSemantic::GaussianSplatViewDataBuffer);
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();
ASSERT_EQ(plan.bindings.Size(), 4u);
EXPECT_TRUE(plan.perObject.IsValid());
EXPECT_TRUE(plan.material.IsValid());
EXPECT_TRUE(plan.gaussianSplatOrderBuffer.IsValid());
EXPECT_TRUE(plan.gaussianSplatViewDataBuffer.IsValid());
EXPECT_FALSE(plan.gaussianSplatPositionBuffer.IsValid());
EXPECT_FALSE(plan.gaussianSplatOtherBuffer.IsValid());
EXPECT_FALSE(plan.gaussianSplatColorBuffer.IsValid());
EXPECT_FALSE(plan.gaussianSplatSHBuffer.IsValid());
EXPECT_EQ(plan.perObject.set, 0u);
EXPECT_EQ(plan.material.set, 1u);
EXPECT_EQ(plan.gaussianSplatOrderBuffer.set, 2u);
EXPECT_EQ(plan.gaussianSplatOrderBuffer.binding, 0u);
EXPECT_EQ(plan.gaussianSplatViewDataBuffer.set, 2u);
EXPECT_EQ(plan.gaussianSplatViewDataBuffer.binding, 1u);
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);
EXPECT_TRUE(setLayouts[2].usesGaussianSplatOrderBuffer);
EXPECT_TRUE(setLayouts[2].usesGaussianSplatViewDataBuffer);
EXPECT_FALSE(setLayouts[2].usesGaussianSplatPositionBuffer);
EXPECT_FALSE(setLayouts[2].usesGaussianSplatOtherBuffer);
EXPECT_FALSE(setLayouts[2].usesGaussianSplatColorBuffer);
ASSERT_EQ(setLayouts[2].bindings.size(), 2u);
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(
setLayouts[2].bindings[0].resourceDimension,
ResourceViewDimension::StructuredBuffer);
EXPECT_EQ(
setLayouts[2].bindings[1].resourceDimension,
ResourceViewDimension::StructuredBuffer);
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_NE(d3d12Source.find("float4 gPointScaleParams;"), std::string::npos);
EXPECT_NE(d3d12Source.find("float4 gOpacityScaleParams;"), std::string::npos);
EXPECT_TRUE(SourceContainsRegisterBinding(
d3d12Source,
"StructuredBuffer<uint> GaussianSplatOrderBuffer",
"register(t0)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
d3d12Source,
"StructuredBuffer<GaussianSplatViewData> GaussianSplatViewDataBuffer",
"register(t1)"));
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_NE(vulkanSource.find("float4 gPointScaleParams;"), std::string::npos);
EXPECT_NE(vulkanSource.find("float4 gOpacityScaleParams;"), std::string::npos);
EXPECT_TRUE(SourceContainsRegisterBinding(
vulkanSource,
"StructuredBuffer<uint> GaussianSplatOrderBuffer",
"register(t0, space2)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
vulkanSource,
"StructuredBuffer<GaussianSplatViewData> GaussianSplatViewDataBuffer",
"register(t1, space2)"));
delete shader;
}
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(), 9u);
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* other =
shader->FindPassResourceBinding("GaussianSplatPrepareOrder", "GaussianSplatOther");
ASSERT_NE(other, nullptr);
EXPECT_EQ(other->type, ShaderResourceType::StructuredBuffer);
EXPECT_EQ(other->set, 2u);
EXPECT_EQ(other->binding, 1u);
EXPECT_EQ(
ResolveBuiltinPassResourceSemantic(*other),
BuiltinPassResourceSemantic::GaussianSplatOtherBuffer);
const ShaderResourceBindingDesc* color =
shader->FindPassResourceBinding("GaussianSplatPrepareOrder", "GaussianSplatColor");
ASSERT_NE(color, nullptr);
EXPECT_EQ(color->type, ShaderResourceType::StructuredBuffer);
EXPECT_EQ(color->set, 2u);
EXPECT_EQ(color->binding, 2u);
EXPECT_EQ(
ResolveBuiltinPassResourceSemantic(*color),
BuiltinPassResourceSemantic::GaussianSplatColorBuffer);
const ShaderResourceBindingDesc* sh =
shader->FindPassResourceBinding("GaussianSplatPrepareOrder", "GaussianSplatSH");
ASSERT_NE(sh, nullptr);
EXPECT_EQ(sh->type, ShaderResourceType::StructuredBuffer);
EXPECT_EQ(sh->set, 2u);
EXPECT_EQ(sh->binding, 3u);
EXPECT_EQ(
ResolveBuiltinPassResourceSemantic(*sh),
BuiltinPassResourceSemantic::GaussianSplatSHBuffer);
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* visibleChunks =
shader->FindPassResourceBinding("GaussianSplatPrepareOrder", "GaussianSplatVisibleChunks");
ASSERT_NE(visibleChunks, nullptr);
EXPECT_EQ(visibleChunks->type, ShaderResourceType::StructuredBuffer);
EXPECT_EQ(visibleChunks->set, 3u);
EXPECT_EQ(visibleChunks->binding, 0u);
EXPECT_EQ(
ResolveBuiltinPassResourceSemantic(*visibleChunks),
BuiltinPassResourceSemantic::GaussianSplatVisibleChunkBuffer);
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 ShaderResourceBindingDesc* viewData =
shader->FindPassResourceBinding("GaussianSplatPrepareOrder", "GaussianSplatViewDataBuffer");
ASSERT_NE(viewData, nullptr);
EXPECT_EQ(viewData->type, ShaderResourceType::RWStructuredBuffer);
EXPECT_EQ(viewData->set, 4u);
EXPECT_EQ(viewData->binding, 2u);
EXPECT_EQ(
ResolveBuiltinPassResourceSemantic(*viewData),
BuiltinPassResourceSemantic::GaussianSplatViewDataBuffer);
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, BuiltinGaussianSplatMarkVisibleChunksShaderUsesComputeAuthoringContract) {
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("GaussianSplatMarkVisibleChunks");
ASSERT_NE(pass, nullptr);
EXPECT_EQ(pass->resources.Size(), 3u);
const ShaderResourceBindingDesc* perObject =
shader->FindPassResourceBinding("GaussianSplatMarkVisibleChunks", "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* chunks =
shader->FindPassResourceBinding("GaussianSplatMarkVisibleChunks", "GaussianSplatChunks");
ASSERT_NE(chunks, nullptr);
EXPECT_EQ(chunks->type, ShaderResourceType::StructuredBuffer);
EXPECT_EQ(chunks->set, 2u);
EXPECT_EQ(chunks->binding, 0u);
EXPECT_EQ(
ResolveBuiltinPassResourceSemantic(*chunks),
BuiltinPassResourceSemantic::GaussianSplatChunkBuffer);
const ShaderResourceBindingDesc* visibleChunks =
shader->FindPassResourceBinding("GaussianSplatMarkVisibleChunks", "GaussianSplatVisibleChunks");
ASSERT_NE(visibleChunks, nullptr);
EXPECT_EQ(visibleChunks->type, ShaderResourceType::RWStructuredBuffer);
EXPECT_EQ(visibleChunks->set, 4u);
EXPECT_EQ(visibleChunks->binding, 0u);
EXPECT_EQ(
ResolveBuiltinPassResourceSemantic(*visibleChunks),
BuiltinPassResourceSemantic::GaussianSplatVisibleChunkBuffer);
const ShaderStageVariant* computeVariant = shader->FindVariant(
"GaussianSplatMarkVisibleChunks",
XCEngine::Resources::ShaderType::Compute,
XCEngine::Resources::ShaderBackend::D3D12);
ASSERT_NE(computeVariant, nullptr);
EXPECT_EQ(computeVariant->entryPoint, "GaussianSplatMarkVisibleChunksCS");
delete shader;
}
TEST(BuiltinForwardPipeline_Test, BuiltinGaussianSplatBitonicSortShaderUsesComputeAuthoringContract) {
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("GaussianSplatBitonicSort");
ASSERT_NE(pass, nullptr);
EXPECT_EQ(pass->resources.Size(), 3u);
const ShaderResourceBindingDesc* perObject =
shader->FindPassResourceBinding("GaussianSplatBitonicSort", "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* sortDistances =
shader->FindPassResourceBinding("GaussianSplatBitonicSort", "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("GaussianSplatBitonicSort", "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(
"GaussianSplatBitonicSort",
XCEngine::Resources::ShaderType::Compute,
XCEngine::Resources::ShaderBackend::D3D12);
ASSERT_NE(computeVariant, nullptr);
EXPECT_EQ(computeVariant->entryPoint, "GaussianSplatBitonicSortCS");
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(), 9u);
EXPECT_TRUE(plan.perObject.IsValid());
EXPECT_TRUE(plan.gaussianSplatPositionBuffer.IsValid());
EXPECT_TRUE(plan.gaussianSplatOtherBuffer.IsValid());
EXPECT_TRUE(plan.gaussianSplatColorBuffer.IsValid());
EXPECT_TRUE(plan.gaussianSplatSHBuffer.IsValid());
EXPECT_TRUE(plan.gaussianSplatVisibleChunkBuffer.IsValid());
EXPECT_TRUE(plan.gaussianSplatSortDistanceBuffer.IsValid());
EXPECT_TRUE(plan.gaussianSplatOrderBuffer.IsValid());
EXPECT_TRUE(plan.gaussianSplatViewDataBuffer.IsValid());
EXPECT_EQ(plan.perObject.set, 0u);
EXPECT_EQ(plan.gaussianSplatPositionBuffer.set, 2u);
EXPECT_EQ(plan.gaussianSplatOtherBuffer.set, 2u);
EXPECT_EQ(plan.gaussianSplatOtherBuffer.binding, 1u);
EXPECT_EQ(plan.gaussianSplatColorBuffer.set, 2u);
EXPECT_EQ(plan.gaussianSplatColorBuffer.binding, 2u);
EXPECT_EQ(plan.gaussianSplatSHBuffer.set, 2u);
EXPECT_EQ(plan.gaussianSplatSHBuffer.binding, 3u);
EXPECT_EQ(plan.gaussianSplatVisibleChunkBuffer.set, 3u);
EXPECT_EQ(plan.gaussianSplatVisibleChunkBuffer.binding, 0u);
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.gaussianSplatViewDataBuffer.set, 4u);
EXPECT_EQ(plan.gaussianSplatViewDataBuffer.binding, 2u);
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[2].usesGaussianSplatOtherBuffer);
EXPECT_TRUE(setLayouts[2].usesGaussianSplatColorBuffer);
EXPECT_TRUE(setLayouts[2].usesGaussianSplatSHBuffer);
EXPECT_TRUE(setLayouts[3].usesGaussianSplatVisibleChunkBuffer);
EXPECT_TRUE(setLayouts[4].usesGaussianSplatSortDistanceBuffer);
EXPECT_TRUE(setLayouts[4].usesGaussianSplatOrderBuffer);
EXPECT_TRUE(setLayouts[4].usesGaussianSplatViewDataBuffer);
ASSERT_EQ(setLayouts[2].bindings.size(), 4u);
ASSERT_EQ(setLayouts[3].bindings.size(), 1u);
ASSERT_EQ(setLayouts[4].bindings.size(), 3u);
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);
EXPECT_EQ(
static_cast<DescriptorType>(setLayouts[2].bindings[3].type),
DescriptorType::SRV);
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(
static_cast<DescriptorType>(setLayouts[4].bindings[2].type),
DescriptorType::UAV);
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);
EXPECT_EQ(
setLayouts[2].bindings[3].resourceDimension,
ResourceViewDimension::StructuredBuffer);
EXPECT_EQ(
setLayouts[3].bindings[0].resourceDimension,
ResourceViewDimension::StructuredBuffer);
EXPECT_EQ(
setLayouts[4].bindings[0].resourceDimension,
ResourceViewDimension::StructuredBuffer);
EXPECT_EQ(
setLayouts[4].bindings[1].resourceDimension,
ResourceViewDimension::StructuredBuffer);
EXPECT_EQ(
setLayouts[4].bindings[2].resourceDimension,
ResourceViewDimension::StructuredBuffer);
delete shader;
}
TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoadedGaussianSplatMarkVisibleChunksContract) {
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("GaussianSplatMarkVisibleChunks");
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.perObject.IsValid());
EXPECT_TRUE(plan.gaussianSplatChunkBuffer.IsValid());
EXPECT_TRUE(plan.gaussianSplatVisibleChunkBuffer.IsValid());
EXPECT_EQ(plan.perObject.set, 0u);
EXPECT_EQ(plan.gaussianSplatChunkBuffer.set, 2u);
EXPECT_EQ(plan.gaussianSplatChunkBuffer.binding, 0u);
EXPECT_EQ(plan.gaussianSplatVisibleChunkBuffer.set, 4u);
EXPECT_EQ(plan.gaussianSplatVisibleChunkBuffer.binding, 0u);
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].usesGaussianSplatChunkBuffer);
EXPECT_TRUE(setLayouts[4].usesGaussianSplatVisibleChunkBuffer);
ASSERT_EQ(setLayouts[2].bindings.size(), 1u);
ASSERT_EQ(setLayouts[4].bindings.size(), 1u);
EXPECT_EQ(
static_cast<DescriptorType>(setLayouts[2].bindings[0].type),
DescriptorType::SRV);
EXPECT_EQ(
static_cast<DescriptorType>(setLayouts[4].bindings[0].type),
DescriptorType::UAV);
delete shader;
}
TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoadedGaussianSplatBitonicSortContract) {
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("GaussianSplatBitonicSort");
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.perObject.IsValid());
EXPECT_TRUE(plan.gaussianSplatSortDistanceBuffer.IsValid());
EXPECT_TRUE(plan.gaussianSplatOrderBuffer.IsValid());
EXPECT_FALSE(plan.gaussianSplatViewDataBuffer.IsValid());
EXPECT_EQ(plan.perObject.set, 0u);
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[4].usesGaussianSplatSortDistanceBuffer);
EXPECT_TRUE(setLayouts[4].usesGaussianSplatOrderBuffer);
EXPECT_FALSE(setLayouts[4].usesGaussianSplatViewDataBuffer);
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);
delete shader;
}
TEST(BuiltinForwardPipeline_Test, OpenGLPipelineLayoutUsesUnifiedStorageBufferBindingsForGaussianSplatUtilities) {
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;
ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr();
std::vector<BuiltinPassSetLayoutMetadata> setLayouts;
ASSERT_TRUE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)) << error.CStr();
ASSERT_EQ(setLayouts.size(), 5u);
RHIPipelineLayoutDesc layoutDesc = {};
layoutDesc.setLayoutCount = static_cast<uint32_t>(setLayouts.size());
std::vector<DescriptorSetLayoutDesc> layoutViews(setLayouts.size());
for (size_t setIndex = 0; setIndex < setLayouts.size(); ++setIndex) {
layoutViews[setIndex].bindingCount = static_cast<uint32_t>(setLayouts[setIndex].bindings.size());
layoutViews[setIndex].bindings =
setLayouts[setIndex].bindings.empty() ? nullptr : setLayouts[setIndex].bindings.data();
}
layoutDesc.setLayouts = layoutViews.data();
OpenGLPipelineLayout pipelineLayout;
ASSERT_TRUE(pipelineLayout.Initialize(layoutDesc));
EXPECT_EQ(pipelineLayout.GetConstantBufferBindingPoint(0u, 0u), 0u);
EXPECT_EQ(pipelineLayout.GetShaderResourceBindingPoint(2u, 0u), 0u);
EXPECT_EQ(pipelineLayout.GetShaderResourceBindingPoint(2u, 1u), 1u);
EXPECT_EQ(pipelineLayout.GetShaderResourceBindingPoint(2u, 2u), 2u);
EXPECT_EQ(pipelineLayout.GetShaderResourceBindingPoint(2u, 3u), 3u);
EXPECT_EQ(pipelineLayout.GetShaderResourceBindingPoint(2u, 4u), UINT32_MAX);
EXPECT_EQ(pipelineLayout.GetShaderResourceBindingPoint(3u, 0u), 4u);
EXPECT_EQ(pipelineLayout.GetUnorderedAccessBindingPoint(4u, 0u), 5u);
EXPECT_EQ(pipelineLayout.GetUnorderedAccessBindingPoint(4u, 1u), 6u);
EXPECT_EQ(pipelineLayout.GetUnorderedAccessBindingPoint(4u, 2u), 7u);
pipelineLayout.Shutdown();
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<float4> GaussianSplatPositions",
"register(t0)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
d3d12Source,
"StructuredBuffer<GaussianSplatOtherData> GaussianSplatOther",
"register(t1)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
d3d12Source,
"StructuredBuffer<float4> GaussianSplatColor",
"register(t2)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
d3d12Source,
"StructuredBuffer<GaussianSplatSHData> GaussianSplatSH",
"register(t3)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
d3d12Source,
"StructuredBuffer<uint> GaussianSplatVisibleChunks",
"register(t4)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
d3d12Source,
"RWStructuredBuffer<uint> GaussianSplatSortDistances",
"register(u0)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
d3d12Source,
"RWStructuredBuffer<uint> GaussianSplatOrderBuffer",
"register(u1)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
d3d12Source,
"RWStructuredBuffer<GaussianSplatViewData> GaussianSplatViewDataBuffer",
"register(u2)"));
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<float4> GaussianSplatPositions",
"register(t0, space2)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
vulkanSource,
"StructuredBuffer<GaussianSplatOtherData> GaussianSplatOther",
"register(t1, space2)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
vulkanSource,
"StructuredBuffer<float4> GaussianSplatColor",
"register(t2, space2)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
vulkanSource,
"StructuredBuffer<GaussianSplatSHData> GaussianSplatSH",
"register(t3, space2)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
vulkanSource,
"StructuredBuffer<uint> GaussianSplatVisibleChunks",
"register(t0, space3)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
vulkanSource,
"RWStructuredBuffer<uint> GaussianSplatSortDistances",
"register(u0, space4)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
vulkanSource,
"RWStructuredBuffer<uint> GaussianSplatOrderBuffer",
"register(u1, space4)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
vulkanSource,
"RWStructuredBuffer<GaussianSplatViewData> GaussianSplatViewDataBuffer",
"register(u2, space4)"));
delete shader;
}
TEST(BuiltinForwardPipeline_Test, OpenGLRuntimeTranspilesGaussianSplatUtilitiesComputeVariant) {
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* openGLCompute = shader->FindVariant(
"GaussianSplatPrepareOrder",
XCEngine::Resources::ShaderType::Compute,
XCEngine::Resources::ShaderBackend::OpenGL);
ASSERT_NE(openGLCompute, nullptr);
ShaderCompileDesc compileDesc = {};
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
*pass,
XCEngine::Resources::ShaderBackend::OpenGL,
*openGLCompute,
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;
ASSERT_FALSE(glslSource.empty());
EXPECT_NE(
glslSource.find("layout(binding = 0, std430) readonly buffer type_StructuredBuffer_v4float"),
std::string::npos);
EXPECT_NE(
glslSource.find("layout(binding = 1, std430) readonly buffer type_StructuredBuffer_GaussianSplatOtherData"),
std::string::npos);
EXPECT_NE(
glslSource.find("layout(binding = 2, std430) readonly buffer GaussianSplatColor"),
std::string::npos);
EXPECT_NE(
glslSource.find("layout(binding = 3, std430) readonly buffer type_StructuredBuffer_GaussianSplatSHData"),
std::string::npos);
EXPECT_NE(
glslSource.find("layout(binding = 4, std430) readonly buffer"),
std::string::npos);
EXPECT_NE(
glslSource.find("layout(binding = 5, std430) buffer type_RWStructuredBuffer_uint"),
std::string::npos);
EXPECT_NE(
glslSource.find("layout(binding = 6, std430) buffer GaussianSplatOrderBuffer"),
std::string::npos);
EXPECT_NE(
glslSource.find("layout(binding = 7, std430) buffer type_RWStructuredBuffer_GaussianSplatViewData"),
std::string::npos);
delete shader;
}
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);
EXPECT_EQ(pass->resources.Size(), 4u);
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 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);
const ShaderResourceBindingDesc* volumeData =
shader->FindPassResourceBinding("Volumetric", "VolumeData");
ASSERT_NE(volumeData, nullptr);
EXPECT_EQ(volumeData->type, ShaderResourceType::StructuredBuffer);
EXPECT_EQ(volumeData->set, 2u);
EXPECT_EQ(volumeData->binding, 1u);
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();
ASSERT_EQ(plan.bindings.Size(), 4u);
EXPECT_TRUE(plan.perObject.IsValid());
EXPECT_TRUE(plan.lighting.IsValid());
EXPECT_TRUE(plan.material.IsValid());
EXPECT_TRUE(plan.volumeField.IsValid());
EXPECT_EQ(plan.perObject.set, 0u);
EXPECT_EQ(plan.lighting.set, 1u);
EXPECT_EQ(plan.material.set, 2u);
EXPECT_EQ(plan.volumeField.set, 2u);
EXPECT_EQ(plan.volumeField.binding, 1u);
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].usesLighting);
EXPECT_TRUE(setLayouts[2].usesMaterial);
EXPECT_TRUE(setLayouts[2].usesVolumeField);
ASSERT_EQ(setLayouts[2].bindings.size(), 2u);
EXPECT_EQ(
static_cast<DescriptorType>(setLayouts[2].bindings[0].type),
DescriptorType::CBV);
EXPECT_EQ(
static_cast<DescriptorType>(setLayouts[2].bindings[1].type),
DescriptorType::SRV);
EXPECT_EQ(
setLayouts[2].bindings[1].resourceDimension,
ResourceViewDimension::StructuredBuffer);
delete shader;
}
TEST(BuiltinForwardPipeline_Test, BuiltinFinalColorShaderUsesAuthoringContract) {
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);
EXPECT_EQ(pass->resources.Size(), 3u);
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);
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);
delete shader;
}
TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoadedFinalColorShaderContract) {
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;
}
TEST(BuiltinForwardPipeline_Test, BuiltinColorScalePostProcessShaderUsesAuthoringContract) {
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);
EXPECT_EQ(pass->resources.Size(), 3u);
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;
}
TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoadedColorScalePostProcessShaderContract) {
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;
}
TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringFinalColorBindingsToDescriptorSpaces) {
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 = {};
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
*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 FinalColorConstants",
"register(b0)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
d3d12Source,
"Texture2D SourceColorTexture",
"register(t0)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
d3d12Source,
"SamplerState LinearClampSampler",
"register(s0)"));
EXPECT_EQ(d3d12Source.find("space0"), std::string::npos);
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 =
::XCEngine::Rendering::Internal::BuildRuntimeShaderSource(
*pass,
XCEngine::Resources::ShaderBackend::Vulkan,
*vulkanFragment);
EXPECT_TRUE(SourceContainsRegisterBinding(
runtimeSource,
"cbuffer FinalColorConstants",
"register(b0, space0)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
runtimeSource,
"Texture2D SourceColorTexture",
"register(t0, space1)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
runtimeSource,
"SamplerState LinearClampSampler",
"register(s0, space2)"));
ShaderCompileDesc vulkanCompileDesc = {};
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
*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;
}
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 = {};
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
*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,
"cbuffer LightingConstants",
"register(b1)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
d3d12Source,
"cbuffer MaterialConstants",
"register(b2)"));
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 = {};
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
*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,
"cbuffer LightingConstants",
"register(b0, space1)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
vulkanSource,
"cbuffer MaterialConstants",
"register(b0, space2)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
vulkanSource,
"StructuredBuffer<uint> VolumeData",
"register(t1, space2)"));
delete shader;
}
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 = {};
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
*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;
}
TEST(BuiltinForwardPipeline_Test, VulkanRuntimeCompileDescRewritesAuthoringColorScaleBindingsToDescriptorSpaces) {
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* d3d12Fragment = shader->FindVariant(
"ColorScale",
XCEngine::Resources::ShaderType::Fragment,
XCEngine::Resources::ShaderBackend::D3D12);
ASSERT_NE(d3d12Fragment, nullptr);
ShaderCompileDesc d3d12CompileDesc = {};
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
*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 PostProcessConstants",
"register(b0)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
d3d12Source,
"Texture2D SourceColorTexture",
"register(t0)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
d3d12Source,
"SamplerState LinearClampSampler",
"register(s0)"));
EXPECT_EQ(d3d12Source.find("space0"), std::string::npos);
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 =
::XCEngine::Rendering::Internal::BuildRuntimeShaderSource(
*pass,
XCEngine::Resources::ShaderBackend::Vulkan,
*vulkanFragment);
EXPECT_TRUE(SourceContainsRegisterBinding(
runtimeSource,
"cbuffer PostProcessConstants",
"register(b0, space0)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
runtimeSource,
"Texture2D SourceColorTexture",
"register(t0, space1)"));
EXPECT_TRUE(SourceContainsRegisterBinding(
runtimeSource,
"SamplerState LinearClampSampler",
"register(s0, space2)"));
ShaderCompileDesc vulkanCompileDesc = {};
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
*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("SourceColorTexture.Sample"), std::string::npos);
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 = {};
::XCEngine::Rendering::Internal::ApplyShaderStageVariant(
*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);
delete shader;
}
TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoadedForwardShaderContract) {
ShaderLoader loader;
LoadResult result = loader.Load(GetBuiltinForwardLitShaderPath());
ASSERT_TRUE(result);
ASSERT_NE(result.resource, nullptr);
Shader* shader = static_cast<Shader*>(result.resource);
ASSERT_NE(shader, nullptr);
const ShaderPass* pass = shader->FindPass("ForwardLit");
ASSERT_NE(pass, nullptr);
BuiltinPassResourceBindingPlan plan = {};
String error;
EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, 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);
EXPECT_TRUE(plan.usesConstantBuffers);
EXPECT_TRUE(plan.usesTextures);
EXPECT_TRUE(plan.usesSamplers);
delete shader;
}
TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromBuiltinForwardShaderContract) {
ShaderPass pass = {};
pass.name = "ForwardLit";
ShaderPassTagEntry tag = {};
tag.name = "LightMode";
tag.value = "ForwardLit";
pass.tags.PushBack(tag);
AppendDefaultBuiltinPassResources(pass);
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);
}
TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromLoadedUnlitShaderContract) {
ShaderLoader loader;
LoadResult result = loader.Load(GetBuiltinUnlitShaderPath());
ASSERT_TRUE(result);
ASSERT_NE(result.resource, nullptr);
Shader* shader = static_cast<Shader*>(result.resource);
ASSERT_NE(shader, nullptr);
const ShaderPass* pass = shader->FindPass("Unlit");
ASSERT_NE(pass, nullptr);
BuiltinPassResourceBindingPlan plan = {};
String error;
EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, 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);
EXPECT_TRUE(plan.usesConstantBuffers);
EXPECT_TRUE(plan.usesTextures);
EXPECT_TRUE(plan.usesSamplers);
delete shader;
}
TEST(BuiltinForwardPipeline_Test, UsesLoadedForwardShaderResourceSetIndices) {
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);
EXPECT_EQ(pass->resources.Size(), 8u);
BuiltinPassResourceBindingPlan plan = {};
String error;
EXPECT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr();
ASSERT_EQ(plan.bindings.Size(), 8u);
EXPECT_EQ(plan.perObject.set, 0u);
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);
delete shader;
}
TEST(BuiltinPassLayout_Test, BuildsSharedSetLayoutsFromLoadedForwardShaderResources) {
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);
EXPECT_EQ(pass->resources.Size(), 8u);
BuiltinPassResourceBindingPlan plan = {};
String error;
ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr();
std::vector<BuiltinPassSetLayoutMetadata> setLayouts;
ASSERT_TRUE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)) << error.CStr();
ASSERT_EQ(setLayouts.size(), 8u);
EXPECT_EQ(setLayouts[0].layout.bindingCount, 1u);
EXPECT_FALSE(setLayouts[0].shaderVisible);
EXPECT_TRUE(setLayouts[0].usesPerObject);
EXPECT_FALSE(setLayouts[0].usesMaterial);
EXPECT_EQ(setLayouts[1].layout.bindingCount, 1u);
EXPECT_TRUE(setLayouts[1].usesLighting);
EXPECT_FALSE(setLayouts[1].shaderVisible);
EXPECT_EQ(setLayouts[2].layout.bindingCount, 1u);
EXPECT_TRUE(setLayouts[2].usesMaterial);
EXPECT_FALSE(setLayouts[2].shaderVisible);
EXPECT_EQ(setLayouts[3].layout.bindingCount, 1u);
EXPECT_TRUE(setLayouts[3].usesShadowReceiver);
EXPECT_FALSE(setLayouts[3].shaderVisible);
EXPECT_EQ(setLayouts[4].layout.bindingCount, 1u);
EXPECT_TRUE(setLayouts[4].usesTexture);
EXPECT_TRUE(setLayouts[4].shaderVisible);
EXPECT_EQ(setLayouts[4].heapType, DescriptorHeapType::CBV_SRV_UAV);
EXPECT_EQ(setLayouts[5].layout.bindingCount, 1u);
EXPECT_TRUE(setLayouts[5].usesSampler);
EXPECT_TRUE(setLayouts[5].shaderVisible);
EXPECT_EQ(setLayouts[5].heapType, DescriptorHeapType::Sampler);
EXPECT_EQ(setLayouts[6].layout.bindingCount, 1u);
EXPECT_TRUE(setLayouts[6].usesTexture);
EXPECT_TRUE(setLayouts[6].shaderVisible);
EXPECT_EQ(setLayouts[7].layout.bindingCount, 1u);
EXPECT_TRUE(setLayouts[7].usesSampler);
EXPECT_TRUE(setLayouts[7].shaderVisible);
EXPECT_EQ(setLayouts[7].heapType, DescriptorHeapType::Sampler);
delete shader;
}
TEST(BuiltinDepthStylePass_Test, BuiltinDepthOnlyShaderUsesAuthoringContract) {
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);
EXPECT_EQ(pass->resources.Size(), 4u);
EXPECT_TRUE(pass->hasFixedFunctionState);
EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::Back);
EXPECT_TRUE(pass->fixedFunctionState.depthWriteEnable);
EXPECT_EQ(pass->fixedFunctionState.depthFunc, MaterialComparisonFunc::LessEqual);
delete shader;
}
TEST(BuiltinDepthStylePass_Test, BuiltinShadowCasterShaderUsesAuthoringContract) {
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);
EXPECT_EQ(pass->resources.Size(), 4u);
EXPECT_TRUE(pass->hasFixedFunctionState);
EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::Back);
EXPECT_TRUE(pass->fixedFunctionState.depthWriteEnable);
EXPECT_EQ(pass->fixedFunctionState.depthFunc, MaterialComparisonFunc::LessEqual);
delete shader;
}
TEST(BuiltinObjectIdPass_Test, BuiltinObjectIdShaderUsesAuthoringContract) {
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);
EXPECT_EQ(pass->resources.Size(), 1u);
EXPECT_TRUE(pass->hasFixedFunctionState);
EXPECT_EQ(pass->fixedFunctionState.cullMode, MaterialCullMode::Back);
EXPECT_TRUE(pass->fixedFunctionState.depthWriteEnable);
EXPECT_EQ(pass->fixedFunctionState.depthFunc, MaterialComparisonFunc::LessEqual);
delete shader;
}
TEST(BuiltinObjectIdPass_Test, BuildsBuiltinPassResourceBindingPlanFromBuiltinObjectIdShaderContract) {
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);
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_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);
delete shader;
}
TEST(BuiltinObjectIdPass_Test, BuildsBuiltinPassResourceBindingPlanFromExplicitObjectIdContract) {
ShaderPass pass = {};
pass.name = "ObjectId";
ShaderPassTagEntry tag = {};
tag.name = "LightMode";
tag.value = "ObjectId";
pass.tags.PushBack(tag);
AppendDefaultBuiltinPassResources(pass);
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);
}
TEST(BuiltinDepthStylePass_Test, BuildsBuiltinPassResourceBindingPlanFromExplicitDepthOnlyContract) {
ShaderPass pass = {};
pass.name = "DepthOnly";
ShaderPassTagEntry tag = {};
tag.name = "LightMode";
tag.value = "DepthOnly";
pass.tags.PushBack(tag);
AppendDefaultBuiltinPassResources(pass);
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);
}
TEST(BuiltinObjectIdPass_Test, UsesFloat3PositionInputLayoutForStaticMeshVertices) {
const InputLayoutDesc inputLayout = BuiltinObjectIdPass::BuildInputLayout();
ASSERT_EQ(inputLayout.elements.size(), 3u);
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)));
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)));
}
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);
BuiltinPassResourceBindingPlan plan = {};
String error;
ASSERT_TRUE(TryBuildBuiltinPassResourceBindingPlan(*pass, plan, &error)) << error.CStr();
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);
delete shader;
}
TEST(BuiltinPassLayout_Test, BuildsSharedSetLayoutsFromBuiltinDepthOnlyShaderContract) {
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);
BuiltinPassResourceBindingPlan plan = {};
String error;
ASSERT_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);
EXPECT_TRUE(plan.usesConstantBuffers);
EXPECT_TRUE(plan.usesTextures);
EXPECT_TRUE(plan.usesSamplers);
std::vector<BuiltinPassSetLayoutMetadata> setLayouts;
ASSERT_TRUE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)) << error.CStr();
ASSERT_EQ(setLayouts.size(), 4u);
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_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);
delete shader;
}
TEST(BuiltinPassLayout_Test, BuildsSharedSetLayoutsFromBuiltinShadowCasterShaderContract) {
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);
BuiltinPassResourceBindingPlan plan = {};
String error;
ASSERT_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);
EXPECT_TRUE(plan.usesConstantBuffers);
EXPECT_TRUE(plan.usesTextures);
EXPECT_TRUE(plan.usesSamplers);
std::vector<BuiltinPassSetLayoutMetadata> setLayouts;
ASSERT_TRUE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)) << error.CStr();
ASSERT_EQ(setLayouts.size(), 4u);
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_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);
delete shader;
}
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");
}
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);
}
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);
}
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)));
}