#include #include "Rendering/Internal/ShaderVariantUtils.h" #include #include #include #include "Rendering/Internal/RenderSurfacePipelineUtils.h" #include #include #include #include #include #include #define private public #include #undef private #include #include #include #include #include #include #include #include #include #include #include #include #include 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 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(desc.format)) , m_textureType(static_cast(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(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* 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* 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(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(Format::R32G32B32_Float)); EXPECT_EQ(normal.inputSlot, 0u); EXPECT_EQ(normal.alignedByteOffset, static_cast(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(Format::R32G32_Float)); EXPECT_EQ(texcoord.inputSlot, 0u); EXPECT_EQ(texcoord.alignedByteOffset, static_cast(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(Format::R32G32_Float)); EXPECT_EQ(backTexcoord.inputSlot, 0u); EXPECT_EQ(backTexcoord.alignedByteOffset, static_cast(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(Format::R32G32B32_Float)); EXPECT_EQ(tangent.inputSlot, 0u); EXPECT_EQ(tangent.alignedByteOffset, static_cast(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(Format::R32G32B32_Float)); EXPECT_EQ(bitangent.inputSlot, 0u); EXPECT_EQ(bitangent.alignedByteOffset, static_cast(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(Format::R32G32B32A32_Float)); EXPECT_EQ(color.inputSlot, 0u); EXPECT_EQ(color.alignedByteOffset, static_cast(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(Format::R8G8B8A8_UNorm); colorDesc.textureType = static_cast(XCEngine::RHI::TextureType::Texture2D); colorDesc.sampleCount = 1u; RenderGraphTextureDesc depthDesc = {}; depthDesc.width = 320u; depthDesc.height = 180u; depthDesc.format = static_cast(Format::D24_UNorm_S8_UInt); depthDesc.textureType = static_cast(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 eventLog = {}; auto beforeOpaque = std::make_unique( SceneRenderInjectionPoint::BeforeOpaque, true, &eventLog, "BeforeOpaque"); auto afterTransparent = std::make_unique( SceneRenderInjectionPoint::AfterTransparent, true, &eventLog, "AfterTransparent"); auto inactive = std::make_unique( 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( 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(result.resource); ASSERT_NE(shader, nullptr); shader->m_guid = ResourceGUID(); Material material; material.SetShader(ResourceHandle(shader)); VisibleRenderItem visibleItem = {}; visibleItem.material = &material; visibleItem.renderQueue = static_cast(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(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(pipelineDesc.renderTargetFormats[0]), Format::R16G16B16A16_Float); EXPECT_EQ(static_cast(pipelineDesc.renderTargetFormats[1]), Format::R8G8B8A8_UNorm); EXPECT_EQ(static_cast(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(colorFormats[0]), Format::R16G16B16A16_Float); EXPECT_EQ(static_cast(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(MaterialRenderQueue::Background))); EXPECT_FALSE(IsTransparentRenderQueue(static_cast(MaterialRenderQueue::Geometry))); EXPECT_FALSE(IsTransparentRenderQueue(static_cast(MaterialRenderQueue::AlphaTest))); EXPECT_TRUE(IsTransparentRenderQueue(static_cast(MaterialRenderQueue::Transparent))); EXPECT_TRUE(IsTransparentRenderQueue(static_cast(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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 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(setLayouts[2].bindings[0].type), DescriptorType::SRV); EXPECT_EQ( static_cast(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(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(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 GaussianSplatOrderBuffer", "register(t0)")); EXPECT_TRUE(SourceContainsRegisterBinding( d3d12Source, "StructuredBuffer 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(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 GaussianSplatOrderBuffer", "register(t0, space2)")); EXPECT_TRUE(SourceContainsRegisterBinding( vulkanSource, "StructuredBuffer 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(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(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(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(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 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(setLayouts[2].bindings[0].type), DescriptorType::SRV); EXPECT_EQ( static_cast(setLayouts[2].bindings[1].type), DescriptorType::SRV); EXPECT_EQ( static_cast(setLayouts[2].bindings[2].type), DescriptorType::SRV); EXPECT_EQ( static_cast(setLayouts[2].bindings[3].type), DescriptorType::SRV); EXPECT_EQ( static_cast(setLayouts[4].bindings[0].type), DescriptorType::UAV); EXPECT_EQ( static_cast(setLayouts[4].bindings[1].type), DescriptorType::UAV); EXPECT_EQ( static_cast(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(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 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(setLayouts[2].bindings[0].type), DescriptorType::SRV); EXPECT_EQ( static_cast(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(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 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(setLayouts[4].bindings[0].type), DescriptorType::UAV); EXPECT_EQ( static_cast(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(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 setLayouts; ASSERT_TRUE(TryBuildBuiltinPassSetLayouts(plan, setLayouts, &error)) << error.CStr(); ASSERT_EQ(setLayouts.size(), 5u); RHIPipelineLayoutDesc layoutDesc = {}; layoutDesc.setLayoutCount = static_cast(setLayouts.size()); std::vector layoutViews(setLayouts.size()); for (size_t setIndex = 0; setIndex < setLayouts.size(); ++setIndex) { layoutViews[setIndex].bindingCount = static_cast(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(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(d3d12CompileDesc.source.data()), d3d12CompileDesc.source.size()); EXPECT_TRUE(SourceContainsRegisterBinding( d3d12Source, "cbuffer PerObjectConstants", "register(b0)")); EXPECT_TRUE(SourceContainsRegisterBinding( d3d12Source, "StructuredBuffer GaussianSplatPositions", "register(t0)")); EXPECT_TRUE(SourceContainsRegisterBinding( d3d12Source, "StructuredBuffer GaussianSplatOther", "register(t1)")); EXPECT_TRUE(SourceContainsRegisterBinding( d3d12Source, "StructuredBuffer GaussianSplatColor", "register(t2)")); EXPECT_TRUE(SourceContainsRegisterBinding( d3d12Source, "StructuredBuffer GaussianSplatSH", "register(t3)")); EXPECT_TRUE(SourceContainsRegisterBinding( d3d12Source, "StructuredBuffer GaussianSplatVisibleChunks", "register(t4)")); EXPECT_TRUE(SourceContainsRegisterBinding( d3d12Source, "RWStructuredBuffer GaussianSplatSortDistances", "register(u0)")); EXPECT_TRUE(SourceContainsRegisterBinding( d3d12Source, "RWStructuredBuffer GaussianSplatOrderBuffer", "register(u1)")); EXPECT_TRUE(SourceContainsRegisterBinding( d3d12Source, "RWStructuredBuffer 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(vulkanCompileDesc.source.data()), vulkanCompileDesc.source.size()); EXPECT_TRUE(SourceContainsRegisterBinding( vulkanSource, "cbuffer PerObjectConstants", "register(b0, space0)")); EXPECT_TRUE(SourceContainsRegisterBinding( vulkanSource, "StructuredBuffer GaussianSplatPositions", "register(t0, space2)")); EXPECT_TRUE(SourceContainsRegisterBinding( vulkanSource, "StructuredBuffer GaussianSplatOther", "register(t1, space2)")); EXPECT_TRUE(SourceContainsRegisterBinding( vulkanSource, "StructuredBuffer GaussianSplatColor", "register(t2, space2)")); EXPECT_TRUE(SourceContainsRegisterBinding( vulkanSource, "StructuredBuffer GaussianSplatSH", "register(t3, space2)")); EXPECT_TRUE(SourceContainsRegisterBinding( vulkanSource, "StructuredBuffer GaussianSplatVisibleChunks", "register(t0, space3)")); EXPECT_TRUE(SourceContainsRegisterBinding( vulkanSource, "RWStructuredBuffer GaussianSplatSortDistances", "register(u0, space4)")); EXPECT_TRUE(SourceContainsRegisterBinding( vulkanSource, "RWStructuredBuffer GaussianSplatOrderBuffer", "register(u1, space4)")); EXPECT_TRUE(SourceContainsRegisterBinding( vulkanSource, "RWStructuredBuffer 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(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(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(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 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(setLayouts[2].bindings[0].type), DescriptorType::CBV); EXPECT_EQ( static_cast(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(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(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(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(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(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(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(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(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(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 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(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 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(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(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(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(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(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(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(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(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(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 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(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(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(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(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(Format::R32G32B32_Float)); EXPECT_EQ(position.inputSlot, 0u); EXPECT_EQ(position.alignedByteOffset, static_cast(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(Format::R32G32B32_Float)); EXPECT_EQ(normal.inputSlot, 0u); EXPECT_EQ(normal.alignedByteOffset, static_cast(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(Format::R32G32_Float)); EXPECT_EQ(texcoord.inputSlot, 0u); EXPECT_EQ(texcoord.alignedByteOffset, static_cast(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(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 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(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 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(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 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 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 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 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 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 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(setLayouts[2].bindings[0].type), DescriptorType::SRV); EXPECT_EQ(setLayouts[2].bindings[0].resourceDimension, ResourceViewDimension::StructuredBuffer); } TEST(BuiltinPassLayout_Test, AcceptsRuntimeMaterialRawUavBufferBindings) { Array 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 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(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 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 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(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(offsetof(StaticMeshVertex, position))); EXPECT_EQ(inputLayout.elements[1].alignedByteOffset, static_cast(offsetof(StaticMeshVertex, normal))); EXPECT_EQ(inputLayout.elements[2].alignedByteOffset, static_cast(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(offsetof(StaticMeshVertex, position))); EXPECT_EQ(inputLayout.elements[1].alignedByteOffset, static_cast(offsetof(StaticMeshVertex, normal))); EXPECT_EQ(inputLayout.elements[2].alignedByteOffset, static_cast(offsetof(StaticMeshVertex, uv0))); }