diff --git a/managed/GameScripts/RenderPipelineApiProbe.cs b/managed/GameScripts/RenderPipelineApiProbe.cs index 4600628a..ba17c50b 100644 --- a/managed/GameScripts/RenderPipelineApiProbe.cs +++ b/managed/GameScripts/RenderPipelineApiProbe.cs @@ -29,6 +29,27 @@ namespace Gameplay } } + public sealed class ManagedForwardRenderPipelineProbeAsset + : ScriptableRenderPipelineAsset + { + public static int CreatePipelineCallCount; + + protected override ScriptableRenderPipeline CreatePipeline() + { + CreatePipelineCallCount++; + return new ManagedForwardRenderPipelineProbe(); + } + + protected override void ConfigureCameraFramePlan( + ScriptableRenderPipelinePlanningContext context) + { + context.ClearPostProcessStage(); + context.RequestPostProcessStage( + CameraFrameColorSource.MainSceneColor, + false); + } + } + public sealed class ManagedPlannedFullscreenRenderPipelineProbeAsset : ScriptableRenderPipelineAsset { @@ -124,6 +145,64 @@ namespace Gameplay } } + public sealed class ManagedForwardRenderPipelineProbe + : ScriptableRenderPipeline + { + public static int SupportsMainSceneCallCount; + public static int SupportsPostProcessCallCount; + public static int RecordMainSceneCallCount; + public static int RecordBuiltinForwardMainSceneCallCount; + public static int RecordPostProcessCallCount; + + protected override bool SupportsStageRenderGraph( + CameraFrameStage stage) + { + if (stage == CameraFrameStage.MainScene) + { + SupportsMainSceneCallCount++; + return true; + } + + if (stage == CameraFrameStage.PostProcess) + { + SupportsPostProcessCallCount++; + return true; + } + + return false; + } + + protected override bool RecordStageRenderGraph( + ScriptableRenderContext context) + { + if (context == null) + { + return false; + } + + if (context.stage == CameraFrameStage.MainScene) + { + RecordMainSceneCallCount++; + bool recorded = context.RecordBuiltinForwardMainScene(); + if (recorded) + { + RecordBuiltinForwardMainSceneCallCount++; + } + + return recorded; + } + + if (context.stage == CameraFrameStage.PostProcess) + { + RecordPostProcessCallCount++; + return context.RecordBuiltinColorScaleFullscreenPass( + new Vector4(1.03f, 0.98f, 0.94f, 1.0f)); + } + + return false; + } + } + public sealed class ManagedPlannedFullscreenRenderPipelineProbe : ScriptableRenderPipeline { @@ -192,4 +271,43 @@ namespace Gameplay typeof(ManagedRenderPipelineProbeAsset); } } + + public sealed class ManagedForwardRenderPipelineRuntimeSelectionProbe + : MonoBehaviour + { + public int ObservedCreatePipelineCallCount; + public int ObservedSupportsMainSceneCallCount; + public int ObservedSupportsPostProcessCallCount; + public int ObservedRecordMainSceneCallCount; + public int ObservedRecordBuiltinForwardMainSceneCallCount; + public int ObservedRecordPostProcessCallCount; + + public void Start() + { + ManagedForwardRenderPipelineProbeAsset.CreatePipelineCallCount = 0; + ManagedForwardRenderPipelineProbe.SupportsMainSceneCallCount = 0; + ManagedForwardRenderPipelineProbe.SupportsPostProcessCallCount = 0; + ManagedForwardRenderPipelineProbe.RecordMainSceneCallCount = 0; + ManagedForwardRenderPipelineProbe.RecordBuiltinForwardMainSceneCallCount = 0; + ManagedForwardRenderPipelineProbe.RecordPostProcessCallCount = 0; + GraphicsSettings.renderPipelineAssetType = + typeof(ManagedForwardRenderPipelineProbeAsset); + } + + public void Update() + { + ObservedCreatePipelineCallCount = + ManagedForwardRenderPipelineProbeAsset.CreatePipelineCallCount; + ObservedSupportsMainSceneCallCount = + ManagedForwardRenderPipelineProbe.SupportsMainSceneCallCount; + ObservedSupportsPostProcessCallCount = + ManagedForwardRenderPipelineProbe.SupportsPostProcessCallCount; + ObservedRecordMainSceneCallCount = + ManagedForwardRenderPipelineProbe.RecordMainSceneCallCount; + ObservedRecordBuiltinForwardMainSceneCallCount = + ManagedForwardRenderPipelineProbe.RecordBuiltinForwardMainSceneCallCount; + ObservedRecordPostProcessCallCount = + ManagedForwardRenderPipelineProbe.RecordPostProcessCallCount; + } + } } diff --git a/tests/fixtures/RenderTestRhiStubs.h b/tests/fixtures/RenderTestRhiStubs.h new file mode 100644 index 00000000..2a6dbe37 --- /dev/null +++ b/tests/fixtures/RenderTestRhiStubs.h @@ -0,0 +1,795 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace XCTest { + +class TestRenderResourceView final : public XCEngine::RHI::RHIResourceView { +public: + TestRenderResourceView( + 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::ResourceViewType::ShaderResource; + XCEngine::RHI::ResourceViewDimension m_dimension = + XCEngine::RHI::ResourceViewDimension::Texture2D; + XCEngine::RHI::Format m_format = + XCEngine::RHI::Format::Unknown; +}; + +class TestRenderTexture final : public XCEngine::RHI::RHITexture { +public: + explicit TestRenderTexture(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 = 1u; + uint32_t m_mipLevels = 1u; + 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 TestRenderSampler 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 TestRenderPipelineLayout final : public XCEngine::RHI::RHIPipelineLayout { +public: + explicit TestRenderPipelineLayout( + const XCEngine::RHI::RHIPipelineLayoutDesc& desc) + : m_desc(desc) { + } + + bool Initialize(const XCEngine::RHI::RHIPipelineLayoutDesc& desc) override { + m_desc = desc; + return true; + } + + void Shutdown() override { + } + + void* GetNativeHandle() override { + return nullptr; + } + +private: + XCEngine::RHI::RHIPipelineLayoutDesc m_desc = {}; +}; + +class TestRenderPipelineState final : public XCEngine::RHI::RHIPipelineState { +public: + explicit TestRenderPipelineState( + const XCEngine::RHI::GraphicsPipelineDesc& desc) + : m_inputLayout(desc.inputLayout) + , m_rasterizerState(desc.rasterizerState) + , m_blendState(desc.blendState) + , m_depthStencilState(desc.depthStencilState) + , m_topologyType(desc.topologyType) { + SetRenderTargetFormats( + desc.renderTargetCount, + desc.renderTargetFormats, + desc.depthStencilFormat); + SetSampleCount(desc.sampleCount); + SetSampleQuality(desc.sampleQuality); + } + + void SetInputLayout(const XCEngine::RHI::InputLayoutDesc& layout) override { + m_inputLayout = layout; + } + + void SetRasterizerState(const XCEngine::RHI::RasterizerDesc& state) override { + m_rasterizerState = state; + } + + void SetBlendState(const XCEngine::RHI::BlendDesc& state) override { + m_blendState = state; + } + + void SetDepthStencilState( + const XCEngine::RHI::DepthStencilStateDesc& state) override { + m_depthStencilState = state; + } + + void SetTopology(uint32_t topologyType) override { + m_topologyType = topologyType; + } + + void SetRenderTargetFormats( + uint32_t count, + const uint32_t* formats, + uint32_t depthFormat) override { + m_renderTargetCount = count; + m_depthStencilFormat = depthFormat; + m_renderTargetFormats.fill(0u); + for (uint32_t index = 0u; + index < count && index < m_renderTargetFormats.size(); + ++index) { + m_renderTargetFormats[index] = formats[index]; + } + } + + void SetSampleCount(uint32_t count) override { + m_sampleCount = count; + } + + void SetSampleQuality(uint32_t quality) override { + m_sampleQuality = quality; + } + + void SetComputeShader(XCEngine::RHI::RHIShader* shader) override { + m_computeShader = shader; + } + + const XCEngine::RHI::RasterizerDesc& GetRasterizerState() const override { + return m_rasterizerState; + } + + const XCEngine::RHI::BlendDesc& GetBlendState() const override { + return m_blendState; + } + + const XCEngine::RHI::DepthStencilStateDesc& GetDepthStencilState() const override { + return m_depthStencilState; + } + + const XCEngine::RHI::InputLayoutDesc& GetInputLayout() const override { + return m_inputLayout; + } + + XCEngine::RHI::PipelineStateHash GetHash() const override { + return {}; + } + + XCEngine::RHI::RHIShader* GetComputeShader() const override { + return m_computeShader; + } + + bool HasComputeShader() const override { + return m_computeShader != nullptr; + } + + bool IsValid() const override { + return true; + } + + void EnsureValid() override { + } + + void Shutdown() override { + } + + void Bind() override { + } + + void Unbind() override { + } + + void* GetNativeHandle() override { + return nullptr; + } + + XCEngine::RHI::PipelineType GetType() const override { + return XCEngine::RHI::PipelineType::Graphics; + } + +private: + XCEngine::RHI::InputLayoutDesc m_inputLayout = {}; + XCEngine::RHI::RasterizerDesc m_rasterizerState = {}; + XCEngine::RHI::BlendDesc m_blendState = {}; + XCEngine::RHI::DepthStencilStateDesc m_depthStencilState = {}; + uint32_t m_topologyType = 0u; + uint32_t m_renderTargetCount = 0u; + std::array m_renderTargetFormats = {}; + uint32_t m_depthStencilFormat = 0u; + uint32_t m_sampleCount = 1u; + uint32_t m_sampleQuality = 0u; + XCEngine::RHI::RHIShader* m_computeShader = nullptr; +}; + +class TestRenderDescriptorSet final : public XCEngine::RHI::RHIDescriptorSet { +public: + explicit TestRenderDescriptorSet( + const XCEngine::RHI::DescriptorSetLayoutDesc& layout) { + if (layout.bindings == nullptr || layout.bindingCount == 0u) { + return; + } + + m_bindings.assign( + layout.bindings, + layout.bindings + layout.bindingCount); + + uint32_t maxBinding = 0u; + for (const XCEngine::RHI::DescriptorSetLayoutBinding& binding : m_bindings) { + maxBinding = std::max(maxBinding, binding.binding); + } + + m_views.resize(static_cast(maxBinding) + 1u, nullptr); + m_samplers.resize(static_cast(maxBinding) + 1u, nullptr); + } + + void Shutdown() override { + } + + void Bind() override { + } + + void Unbind() override { + } + + void Update(uint32_t offset, XCEngine::RHI::RHIResourceView* view) override { + if (offset >= m_views.size()) { + m_views.resize(static_cast(offset) + 1u, nullptr); + } + + m_views[offset] = view; + } + + void UpdateSampler(uint32_t offset, XCEngine::RHI::RHISampler* sampler) override { + if (offset >= m_samplers.size()) { + m_samplers.resize(static_cast(offset) + 1u, nullptr); + } + + m_samplers[offset] = sampler; + } + + void WriteConstant( + uint32_t, + const void* data, + size_t size, + size_t offset = 0u) override { + if (data == nullptr || size == 0u) { + return; + } + + if (m_constantBuffer.size() < offset + size) { + m_constantBuffer.resize(offset + size); + } + + std::memcpy(m_constantBuffer.data() + offset, data, size); + m_constantDirty = true; + } + + uint32_t GetBindingCount() const override { + return static_cast(m_bindings.size()); + } + + const XCEngine::RHI::DescriptorSetLayoutBinding* GetBindings() const override { + return m_bindings.empty() + ? nullptr + : m_bindings.data(); + } + + void* GetConstantBufferData() override { + return m_constantBuffer.empty() + ? nullptr + : m_constantBuffer.data(); + } + + size_t GetConstantBufferSize() const override { + return m_constantBuffer.size(); + } + + bool IsConstantDirty() const override { + return m_constantDirty; + } + + void MarkConstantClean() override { + m_constantDirty = false; + } + +private: + std::vector m_bindings; + std::vector m_views; + std::vector m_samplers; + std::vector m_constantBuffer; + bool m_constantDirty = false; +}; + +class TestRenderDescriptorPool final : public XCEngine::RHI::RHIDescriptorPool { +public: + explicit TestRenderDescriptorPool( + const XCEngine::RHI::DescriptorPoolDesc& desc) + : m_desc(desc) { + } + + bool Initialize(const XCEngine::RHI::DescriptorPoolDesc& desc) override { + m_desc = desc; + return true; + } + + void Shutdown() override { + } + + void* GetNativeHandle() override { + return nullptr; + } + + uint32_t GetDescriptorCount() const override { + return m_desc.descriptorCount; + } + + XCEngine::RHI::DescriptorHeapType GetType() const override { + return m_desc.type; + } + + XCEngine::RHI::RHIDescriptorSet* AllocateSet( + const XCEngine::RHI::DescriptorSetLayoutDesc& layout) override { + return new TestRenderDescriptorSet(layout); + } + + void FreeSet(XCEngine::RHI::RHIDescriptorSet* set) override { + delete set; + } + +private: + XCEngine::RHI::DescriptorPoolDesc m_desc = {}; +}; + +class TestRenderCommandList 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 { + ++setPipelineStateCalls; + } + + 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 { + ++drawCalls; + } + + void DrawIndexed( + uint32_t, + uint32_t, + uint32_t, + int32_t, + uint32_t) override { + ++drawIndexedCalls; + } + + 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; + size_t setPipelineStateCalls = 0u; + size_t drawCalls = 0u; + size_t drawIndexedCalls = 0u; +}; + +class TestRenderCommandQueue 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 TestRenderDevice 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 TestRenderTexture(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; + return new TestRenderPipelineState(desc); + } + + XCEngine::RHI::RHIPipelineLayout* CreatePipelineLayout( + const XCEngine::RHI::RHIPipelineLayoutDesc& desc) override { + ++createPipelineLayoutCalls; + return new TestRenderPipelineLayout(desc); + } + + XCEngine::RHI::RHIFence* CreateFence( + const XCEngine::RHI::FenceDesc&) override { + return nullptr; + } + + XCEngine::RHI::RHISampler* CreateSampler( + const XCEngine::RHI::SamplerDesc&) override { + ++createSamplerCalls; + return new TestRenderSampler(); + } + + 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& desc) override { + ++createDescriptorPoolCalls; + auto* pool = new TestRenderDescriptorPool(desc); + pool->Initialize(desc); + return pool; + } + + XCEngine::RHI::RHIDescriptorSet* CreateDescriptorSet( + XCEngine::RHI::RHIDescriptorPool*, + const XCEngine::RHI::DescriptorSetLayoutDesc& layout) override { + return new TestRenderDescriptorSet(layout); + } + + 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& desc) override { + ++createRenderTargetViewCalls; + return new TestRenderResourceView( + XCEngine::RHI::ResourceViewType::RenderTarget, + desc.dimension, + static_cast(desc.format)); + } + + XCEngine::RHI::RHIResourceView* CreateDepthStencilView( + XCEngine::RHI::RHITexture*, + const XCEngine::RHI::ResourceViewDesc& desc) override { + ++createDepthStencilViewCalls; + return new TestRenderResourceView( + XCEngine::RHI::ResourceViewType::DepthStencil, + desc.dimension, + static_cast(desc.format)); + } + + 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 TestRenderResourceView( + 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& desc) override { + return new TestRenderResourceView( + XCEngine::RHI::ResourceViewType::UnorderedAccess, + desc.dimension, + static_cast(desc.format)); + } + + 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 createTextureCalls = 0u; + size_t createSamplerCalls = 0u; + size_t createRenderTargetViewCalls = 0u; + size_t createDepthStencilViewCalls = 0u; + size_t createShaderResourceViewCalls = 0u; + size_t createDescriptorPoolCalls = 0u; + size_t createPipelineLayoutCalls = 0u; + size_t createPipelineStateCalls = 0u; + +private: + XCEngine::RHI::RHICapabilities m_capabilities = {}; + XCEngine::RHI::RHIDeviceInfo m_deviceInfo = {}; +}; + +inline XCEngine::Rendering::RenderContext CreateRenderContext( + XCEngine::RHI::RHIDevice& device, + XCEngine::RHI::RHICommandList& commandList, + XCEngine::RHI::RHICommandQueue& commandQueue) { + XCEngine::Rendering::RenderContext context = {}; + context.device = &device; + context.commandList = &commandList; + context.commandQueue = &commandQueue; + return context; +} + +} // namespace XCTest diff --git a/tests/scripting/test_mono_script_runtime.cpp b/tests/scripting/test_mono_script_runtime.cpp index 4b933219..0c4aa09e 100644 --- a/tests/scripting/test_mono_script_runtime.cpp +++ b/tests/scripting/test_mono_script_runtime.cpp @@ -18,11 +18,24 @@ #include #include #include +#include +#include #include #include #include #include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -30,13 +43,19 @@ #include #include +#include #include +#include #include #include +#include #include +#include "../Fixtures/RenderTestRhiStubs.h" + using namespace XCEngine::Components; using namespace XCEngine::Scripting; +using namespace XCTest; namespace { @@ -323,6 +342,221 @@ TEST_F(MonoScriptRuntimeTest, ManagedGraphicsSettingsRoundTripsRenderPipelineAss "Gameplay.RenderPipelineApiProbeAsset"); } +TEST_F( + MonoScriptRuntimeTest, + DefaultCameraRendererUsesManagedGraphicsSelectionToRecordMainSceneGraph) { + Scene* runtimeScene = CreateScene("ManagedRenderPipelineDefaultRendererScene"); + GameObject* selectionObject = + runtimeScene->CreateGameObject("ManagedRenderPipelineSelection"); + ScriptComponent* selectionScript = + AddScript( + selectionObject, + "Gameplay", + "ManagedRenderPipelineRuntimeSelectionProbe"); + ASSERT_NE(selectionScript, nullptr); + + engine->OnRuntimeStart(runtimeScene); + engine->OnUpdate(0.016f); + + XCEngine::Rendering::CameraRenderer renderer; + const auto* asset = + dynamic_cast< + const XCEngine::Rendering::Pipelines::ManagedScriptableRenderPipelineAsset*>( + renderer.GetPipelineAsset()); + ASSERT_NE(asset, nullptr); + EXPECT_EQ(asset->GetDescriptor().assemblyName, "GameScripts"); + EXPECT_EQ(asset->GetDescriptor().namespaceName, "Gameplay"); + EXPECT_EQ(asset->GetDescriptor().className, "ManagedRenderPipelineProbeAsset"); + + auto* host = + dynamic_cast( + renderer.GetPipeline()); + ASSERT_NE(host, nullptr); + ASSERT_NE(host->GetStageRecorder(), nullptr); + EXPECT_TRUE( + host->GetStageRecorder()->Initialize( + XCEngine::Rendering::RenderContext{})); + EXPECT_TRUE( + host->SupportsStageRenderGraph( + XCEngine::Rendering::CameraFrameStage::MainScene)); + + XCEngine::Rendering::RenderGraph graph; + XCEngine::Rendering::RenderGraphBuilder graphBuilder(graph); + XCEngine::Rendering::RenderGraphTextureDesc colorDesc = {}; + colorDesc.width = 64u; + colorDesc.height = 64u; + colorDesc.format = + static_cast( + XCEngine::RHI::Format::R8G8B8A8_UNorm); + XCEngine::Rendering::RenderGraphTextureDesc depthDesc = colorDesc; + depthDesc.format = + static_cast( + XCEngine::RHI::Format::D32_Float); + const XCEngine::Rendering::RenderGraphTextureHandle colorTarget = + graphBuilder.CreateTransientTexture("ManagedSelectedMainSceneColor", colorDesc); + const XCEngine::Rendering::RenderGraphTextureHandle depthTarget = + graphBuilder.CreateTransientTexture("ManagedSelectedMainSceneDepth", depthDesc); + + const XCEngine::Rendering::RenderSceneData sceneData = {}; + const XCEngine::Rendering::RenderSurface surface(64u, 64u); + bool executionSucceeded = true; + XCEngine::Rendering::RenderGraphBlackboard blackboard = {}; + const XCEngine::Rendering::RenderPipelineStageRenderGraphContext graphContext = { + graphBuilder, + "ManagedSelectedMainScene", + XCEngine::Rendering::CameraFrameStage::MainScene, + {}, + sceneData, + surface, + nullptr, + nullptr, + XCEngine::RHI::ResourceStates::Common, + {}, + { colorTarget }, + depthTarget, + &executionSucceeded, + &blackboard + }; + + EXPECT_TRUE(host->GetStageRecorder()->RecordStageRenderGraph(graphContext)); + + XCEngine::Rendering::CompiledRenderGraph compiledGraph = {}; + XCEngine::Containers::String errorMessage; + ASSERT_TRUE( + XCEngine::Rendering::RenderGraphCompiler::Compile( + graph, + compiledGraph, + &errorMessage)) + << errorMessage.CStr(); + ASSERT_EQ(compiledGraph.GetPassCount(), 3u); + EXPECT_STREQ( + compiledGraph.GetPassName(0).CStr(), + "ManagedSelectedMainScene.Opaque"); + EXPECT_STREQ( + compiledGraph.GetPassName(1).CStr(), + "ManagedSelectedMainScene.Skybox"); + EXPECT_STREQ( + compiledGraph.GetPassName(2).CStr(), + "ManagedSelectedMainScene.Transparent"); + + host->GetStageRecorder()->Shutdown(); +} + +TEST_F( + MonoScriptRuntimeTest, + DefaultSceneRendererUsesManagedForwardPipelineForPlannedMainSceneAndPostProcessRender) { + Scene* runtimeScene = CreateScene("ManagedForwardRenderPipelineSceneRendererScene"); + GameObject* selectionObject = + runtimeScene->CreateGameObject("ManagedForwardRenderPipelineSelection"); + ScriptComponent* selectionScript = + AddScript( + selectionObject, + "Gameplay", + "ManagedForwardRenderPipelineRuntimeSelectionProbe"); + ASSERT_NE(selectionScript, nullptr); + + GameObject* cameraObject = runtimeScene->CreateGameObject("Camera"); + auto* camera = cameraObject->AddComponent(); + ASSERT_NE(camera, nullptr); + camera->SetPrimary(true); + + engine->OnRuntimeStart(runtimeScene); + engine->OnUpdate(0.016f); + + TestRenderDevice device; + TestRenderCommandList commandList; + TestRenderCommandQueue commandQueue; + TestRenderResourceView colorView( + XCEngine::RHI::ResourceViewType::RenderTarget, + XCEngine::RHI::ResourceViewDimension::Texture2D, + XCEngine::RHI::Format::R8G8B8A8_UNorm); + TestRenderResourceView depthView( + XCEngine::RHI::ResourceViewType::DepthStencil, + XCEngine::RHI::ResourceViewDimension::Texture2D, + XCEngine::RHI::Format::D32_Float); + + const XCEngine::Rendering::RenderContext context = + CreateRenderContext( + device, + commandList, + commandQueue); + XCEngine::Rendering::RenderSurface surface(64u, 64u); + surface.SetColorAttachment(&colorView); + surface.SetDepthAttachment(&depthView); + + XCEngine::Rendering::SceneRenderer renderer; + const auto* pipelineAsset = + dynamic_cast< + const XCEngine::Rendering::Pipelines::ManagedScriptableRenderPipelineAsset*>( + renderer.GetPipelineAsset()); + ASSERT_NE(pipelineAsset, nullptr); + EXPECT_EQ(pipelineAsset->GetDescriptor().assemblyName, "GameScripts"); + EXPECT_EQ(pipelineAsset->GetDescriptor().namespaceName, "Gameplay"); + EXPECT_EQ( + pipelineAsset->GetDescriptor().className, + "ManagedForwardRenderPipelineProbeAsset"); + + const std::vector plans = + renderer.BuildFramePlans( + *runtimeScene, + nullptr, + context, + surface); + ASSERT_EQ(plans.size(), 1u); + EXPECT_TRUE(plans[0].UsesGraphManagedSceneColor()); + EXPECT_TRUE( + plans[0].IsFullscreenStageRequested( + XCEngine::Rendering::CameraFrameStage::PostProcess)); + EXPECT_TRUE(plans[0].IsPostProcessStageValid()); + + ASSERT_TRUE(renderer.Render(plans)); + EXPECT_EQ(commandList.drawCalls, 1u); + EXPECT_GT(device.createTextureCalls, 0u); + EXPECT_GT(device.createPipelineLayoutCalls, 0u); + EXPECT_GT(device.createPipelineStateCalls, 0u); + EXPECT_GT(device.createDescriptorPoolCalls, 0u); + + engine->OnUpdate(0.016f); + + int observedCreatePipelineCallCount = 0; + int observedSupportsMainSceneCallCount = 0; + int observedSupportsPostProcessCallCount = 0; + int observedRecordMainSceneCallCount = 0; + int observedRecordBuiltinForwardMainSceneCallCount = 0; + int observedRecordPostProcessCallCount = 0; + EXPECT_TRUE(runtime->TryGetFieldValue( + selectionScript, + "ObservedCreatePipelineCallCount", + observedCreatePipelineCallCount)); + EXPECT_TRUE(runtime->TryGetFieldValue( + selectionScript, + "ObservedSupportsMainSceneCallCount", + observedSupportsMainSceneCallCount)); + EXPECT_TRUE(runtime->TryGetFieldValue( + selectionScript, + "ObservedSupportsPostProcessCallCount", + observedSupportsPostProcessCallCount)); + EXPECT_TRUE(runtime->TryGetFieldValue( + selectionScript, + "ObservedRecordMainSceneCallCount", + observedRecordMainSceneCallCount)); + EXPECT_TRUE(runtime->TryGetFieldValue( + selectionScript, + "ObservedRecordBuiltinForwardMainSceneCallCount", + observedRecordBuiltinForwardMainSceneCallCount)); + EXPECT_TRUE(runtime->TryGetFieldValue( + selectionScript, + "ObservedRecordPostProcessCallCount", + observedRecordPostProcessCallCount)); + + EXPECT_EQ(observedCreatePipelineCallCount, 1); + EXPECT_GT(observedSupportsMainSceneCallCount, 0); + EXPECT_GT(observedSupportsPostProcessCallCount, 0); + EXPECT_EQ(observedRecordMainSceneCallCount, 1); + EXPECT_EQ(observedRecordBuiltinForwardMainSceneCallCount, 1); + EXPECT_EQ(observedRecordPostProcessCallCount, 1); +} + TEST_F( MonoScriptRuntimeTest, RegistersManagedRenderPipelineBridgeThatCreatesManagedStageRecorders) {