diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanPipelineState.h b/engine/include/XCEngine/RHI/Vulkan/VulkanPipelineState.h index 80a6df2f..13a68ae7 100644 --- a/engine/include/XCEngine/RHI/Vulkan/VulkanPipelineState.h +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanPipelineState.h @@ -45,6 +45,7 @@ public: VkPipeline GetPipeline() const { return m_pipeline; } VkPipelineLayout GetPipelineLayout() const { return m_pipelineLayout; } VkRenderPass GetRenderPass() const { return m_renderPass; } + uint32_t GetRenderTargetCount() const { return m_renderTargetCount; } bool HasDepthStencilAttachment() const { return m_depthStencilFormat != 0 && static_cast(m_depthStencilFormat) != Format::Unknown; } diff --git a/engine/include/XCEngine/Rendering/CameraRenderRequest.h b/engine/include/XCEngine/Rendering/CameraRenderRequest.h index e0f26c27..2a23ee5b 100644 --- a/engine/include/XCEngine/Rendering/CameraRenderRequest.h +++ b/engine/include/XCEngine/Rendering/CameraRenderRequest.h @@ -22,14 +22,14 @@ struct ScenePassRenderRequest { RenderCameraData cameraDataOverride = {}; bool IsRequested() const { - return !surface.GetColorAttachments().empty(); + return surface.GetDepthAttachment() != nullptr || + !surface.GetColorAttachments().empty(); } bool IsValid() const { const std::vector& colorAttachments = surface.GetColorAttachments(); - return !colorAttachments.empty() && - colorAttachments[0] != nullptr && - surface.GetDepthAttachment() != nullptr && + return surface.GetDepthAttachment() != nullptr && + (colorAttachments.empty() || colorAttachments[0] != nullptr) && surface.GetRenderAreaWidth() > 0 && surface.GetRenderAreaHeight() > 0; } diff --git a/engine/include/XCEngine/Rendering/Passes/BuiltinDepthStylePassBase.h b/engine/include/XCEngine/Rendering/Passes/BuiltinDepthStylePassBase.h index 69c9e6c7..845e53ca 100644 --- a/engine/include/XCEngine/Rendering/Passes/BuiltinDepthStylePassBase.h +++ b/engine/include/XCEngine/Rendering/Passes/BuiltinDepthStylePassBase.h @@ -102,11 +102,17 @@ private: Resources::MaterialRenderState renderState; const Resources::Shader* shader = nullptr; Containers::String passName; + uint32_t renderTargetCount = 0; + uint32_t renderTargetFormat = 0; + uint32_t depthStencilFormat = 0; bool operator==(const PipelineStateKey& other) const { return renderState == other.renderState && shader == other.shader && - passName == other.passName; + passName == other.passName && + renderTargetCount == other.renderTargetCount && + renderTargetFormat == other.renderTargetFormat && + depthStencilFormat == other.depthStencilFormat; } }; @@ -115,6 +121,9 @@ private: size_t hash = MaterialRenderStateHash()(key.renderState); hash ^= reinterpret_cast(key.shader) + 0x9e3779b9u + (hash << 6) + (hash >> 2); hash ^= std::hash{}(key.passName) + 0x9e3779b9u + (hash << 6) + (hash >> 2); + hash ^= std::hash{}(key.renderTargetCount) + 0x9e3779b9u + (hash << 6) + (hash >> 2); + hash ^= std::hash{}(key.renderTargetFormat) + 0x9e3779b9u + (hash << 6) + (hash >> 2); + hash ^= std::hash{}(key.depthStencilFormat) + 0x9e3779b9u + (hash << 6) + (hash >> 2); return hash; } }; @@ -133,6 +142,7 @@ private: const ResolvedShaderPass& resolvedShaderPass); RHI::RHIPipelineState* GetOrCreatePipelineState( const RenderContext& context, + const RenderSurface& surface, const Resources::Material* material); bool CreateOwnedDescriptorSet( const BuiltinPassSetLayoutMetadata& setLayout, @@ -145,6 +155,7 @@ private: void DestroyPassResourceLayout(PassResourceLayout& passLayout); bool DrawVisibleItem( const RenderContext& context, + const RenderSurface& surface, const RenderSceneData& sceneData, const VisibleRenderItem& visibleItem); diff --git a/engine/src/RHI/Vulkan/VulkanCommandList.cpp b/engine/src/RHI/Vulkan/VulkanCommandList.cpp index a968d4c2..76ee691f 100644 --- a/engine/src/RHI/Vulkan/VulkanCommandList.cpp +++ b/engine/src/RHI/Vulkan/VulkanCommandList.cpp @@ -1024,8 +1024,12 @@ bool VulkanCommandList::EnsureGraphicsRenderPass() { return false; } + const bool expectsColorAttachment = m_currentPipelineState->GetRenderTargetCount() > 0; auto* colorView = static_cast(m_currentColorTarget); - if (colorView == nullptr || colorView->GetTexture() == nullptr || colorView->GetImageView() == VK_NULL_HANDLE) { + if (expectsColorAttachment && + (colorView == nullptr || + colorView->GetTexture() == nullptr || + colorView->GetImageView() == VK_NULL_HANDLE)) { return false; } @@ -1036,23 +1040,42 @@ bool VulkanCommandList::EnsureGraphicsRenderPass() { return false; } - VulkanTexture* texture = colorView->GetTexture(); - TransitionTexture(texture, ResourceStates::RenderTarget); + if (!expectsColorAttachment && !expectsDepthAttachment) { + return false; + } + + VulkanTexture* colorTexture = expectsColorAttachment ? colorView->GetTexture() : nullptr; + VulkanTexture* depthTexture = expectsDepthAttachment ? depthView->GetTexture() : nullptr; + if (expectsColorAttachment) { + TransitionTexture(colorTexture, ResourceStates::RenderTarget); + } if (expectsDepthAttachment) { - TransitionTexture(depthView->GetTexture(), ResourceStates::DepthWrite); + TransitionTexture(depthTexture, ResourceStates::DepthWrite); } VkFramebufferCreateInfo framebufferInfo = {}; framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; framebufferInfo.renderPass = m_currentPipelineState->GetRenderPass(); - VkImageView attachments[2] = { colorView->GetImageView(), VK_NULL_HANDLE }; - framebufferInfo.attachmentCount = expectsDepthAttachment ? 2 : 1; - if (expectsDepthAttachment) { - attachments[1] = depthView->GetImageView(); + VkImageView attachments[2] = { VK_NULL_HANDLE, VK_NULL_HANDLE }; + uint32_t attachmentCount = 0; + uint32_t framebufferWidth = 0; + uint32_t framebufferHeight = 0; + if (expectsColorAttachment) { + attachments[attachmentCount++] = colorView->GetImageView(); + framebufferWidth = colorTexture->GetWidth(); + framebufferHeight = colorTexture->GetHeight(); } + if (expectsDepthAttachment) { + attachments[attachmentCount++] = depthView->GetImageView(); + if (framebufferWidth == 0 || framebufferHeight == 0) { + framebufferWidth = depthTexture->GetWidth(); + framebufferHeight = depthTexture->GetHeight(); + } + } + framebufferInfo.attachmentCount = attachmentCount; framebufferInfo.pAttachments = attachments; - framebufferInfo.width = texture->GetWidth(); - framebufferInfo.height = texture->GetHeight(); + framebufferInfo.width = framebufferWidth; + framebufferInfo.height = framebufferHeight; framebufferInfo.layers = 1; VkFramebuffer framebuffer = VK_NULL_HANDLE; @@ -1066,7 +1089,7 @@ bool VulkanCommandList::EnsureGraphicsRenderPass() { renderPassInfo.renderPass = m_currentPipelineState->GetRenderPass(); renderPassInfo.framebuffer = framebuffer; renderPassInfo.renderArea.offset = { 0, 0 }; - renderPassInfo.renderArea.extent = { texture->GetWidth(), texture->GetHeight() }; + renderPassInfo.renderArea.extent = { framebufferWidth, framebufferHeight }; renderPassInfo.clearValueCount = 0; renderPassInfo.pClearValues = nullptr; diff --git a/engine/src/RHI/Vulkan/VulkanPipelineState.cpp b/engine/src/RHI/Vulkan/VulkanPipelineState.cpp index 1a0e7fc8..6140c720 100644 --- a/engine/src/RHI/Vulkan/VulkanPipelineState.cpp +++ b/engine/src/RHI/Vulkan/VulkanPipelineState.cpp @@ -65,12 +65,17 @@ bool VulkanPipelineState::Initialize(VulkanDevice* device, const GraphicsPipelin const bool hasVertexShader = HasShaderPayload(desc.vertexShader); const bool hasFragmentShader = HasShaderPayload(desc.fragmentShader); + const bool hasColorAttachment = m_renderTargetCount == 1 && m_renderTargetFormats[0] != 0; + const bool hasDepthAttachment = HasDepthStencilAttachment(); if (!hasVertexShader && !hasFragmentShader) { m_isConfigured = true; return true; } - if (m_renderTargetCount != 1 || m_renderTargetFormats[0] == 0 || !hasVertexShader || !hasFragmentShader) { + if (m_renderTargetCount > 1 || + (!hasColorAttachment && !hasDepthAttachment) || + !hasVertexShader || + !hasFragmentShader) { Shutdown(); return false; } @@ -124,26 +129,29 @@ bool VulkanPipelineState::CreateGraphicsPipeline(const GraphicsPipelineDesc& des return false; } + const bool hasColorAttachment = m_renderTargetCount == 1 && m_renderTargetFormats[0] != 0; + const bool hasDepthAttachment = HasDepthStencilAttachment(); std::vector attachments; - attachments.reserve(2); - - VkAttachmentDescription colorAttachment = {}; - colorAttachment.format = ToVulkanFormat(static_cast(m_renderTargetFormats[0])); - colorAttachment.samples = ToVulkanSampleCount(m_sampleCount); - colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; - colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - colorAttachment.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - colorAttachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - attachments.push_back(colorAttachment); + attachments.reserve(hasDepthAttachment ? 2u : 1u); VkAttachmentReference colorAttachmentRef = {}; - colorAttachmentRef.attachment = 0; - colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + if (hasColorAttachment) { + VkAttachmentDescription colorAttachment = {}; + colorAttachment.format = ToVulkanFormat(static_cast(m_renderTargetFormats[0])); + colorAttachment.samples = ToVulkanSampleCount(m_sampleCount); + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + attachments.push_back(colorAttachment); + + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + } VkAttachmentReference depthAttachmentRef = {}; - const bool hasDepthAttachment = HasDepthStencilAttachment(); if (hasDepthAttachment) { VkAttachmentDescription depthAttachment = {}; depthAttachment.format = ToVulkanFormat(static_cast(m_depthStencilFormat)); @@ -156,14 +164,14 @@ bool VulkanPipelineState::CreateGraphicsPipeline(const GraphicsPipelineDesc& des depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; attachments.push_back(depthAttachment); - depthAttachmentRef.attachment = 1; + depthAttachmentRef.attachment = hasColorAttachment ? 1u : 0u; depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; } VkSubpassDescription subpass = {}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; - subpass.colorAttachmentCount = 1; - subpass.pColorAttachments = &colorAttachmentRef; + subpass.colorAttachmentCount = hasColorAttachment ? 1u : 0u; + subpass.pColorAttachments = hasColorAttachment ? &colorAttachmentRef : nullptr; if (hasDepthAttachment) { subpass.pDepthStencilAttachment = &depthAttachmentRef; } @@ -272,8 +280,8 @@ bool VulkanPipelineState::CreateGraphicsPipeline(const GraphicsPipelineDesc& des VkPipelineColorBlendStateCreateInfo colorBlending = {}; colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; colorBlending.logicOpEnable = VK_FALSE; - colorBlending.attachmentCount = 1; - colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.attachmentCount = hasColorAttachment ? 1u : 0u; + colorBlending.pAttachments = hasColorAttachment ? &colorBlendAttachment : nullptr; VkPipelineDepthStencilStateCreateInfo depthStencil = {}; depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; @@ -372,7 +380,10 @@ void VulkanPipelineState::SetComputeShader(RHIShader* shader) { PipelineStateHash VulkanPipelineState::GetHash() const { PipelineStateHash hash = {}; hash.topologyHash = m_topologyType; - hash.renderTargetHash = m_renderTargetCount ^ (m_renderTargetFormats[0] << 8); + hash.renderTargetHash = + m_renderTargetCount ^ + (m_renderTargetFormats[0] << 8) ^ + (m_depthStencilFormat << 16); return hash; } diff --git a/engine/src/Rendering/CameraRenderer.cpp b/engine/src/Rendering/CameraRenderer.cpp index 0dfa96ec..9eda7bb4 100644 --- a/engine/src/Rendering/CameraRenderer.cpp +++ b/engine/src/Rendering/CameraRenderer.cpp @@ -6,6 +6,9 @@ #include "Rendering/Pipelines/BuiltinForwardPipeline.h" #include "Rendering/RenderPipelineAsset.h" #include "Rendering/RenderSurface.h" +#include "RHI/RHIDevice.h" +#include "RHI/RHIResourceView.h" +#include "RHI/RHITexture.h" #include "Scene/Scene.h" namespace XCEngine { @@ -13,6 +16,36 @@ namespace Rendering { namespace { +struct OwnedShadowSurfaceResources { + RHI::RHITexture* depthTexture = nullptr; + RHI::RHIResourceView* depthView = nullptr; + RenderSurface surface = {}; + + OwnedShadowSurfaceResources() = default; + OwnedShadowSurfaceResources(const OwnedShadowSurfaceResources&) = delete; + OwnedShadowSurfaceResources& operator=(const OwnedShadowSurfaceResources&) = delete; + + ~OwnedShadowSurfaceResources() { + Reset(); + } + + void Reset() { + if (depthView != nullptr) { + depthView->Shutdown(); + delete depthView; + depthView = nullptr; + } + + if (depthTexture != nullptr) { + depthTexture->Shutdown(); + delete depthTexture; + depthTexture = nullptr; + } + + surface = RenderSurface(); + } +}; + bool InitializePassSequence( RenderPassSequence* sequence, const RenderContext& context, @@ -110,6 +143,52 @@ bool ExecuteScenePassRequest( return pass->Execute(passContext); } +bool CreateDirectionalShadowSurface( + const RenderContext& context, + const DirectionalShadowRenderPlan& plan, + OwnedShadowSurfaceResources& outResources) { + if (!context.IsValid() || !plan.IsValid()) { + return false; + } + + RHI::TextureDesc depthDesc = {}; + depthDesc.width = plan.mapWidth; + depthDesc.height = plan.mapHeight; + depthDesc.depth = 1; + depthDesc.mipLevels = 1; + depthDesc.arraySize = 1; + depthDesc.format = static_cast(RHI::Format::D24_UNorm_S8_UInt); + depthDesc.textureType = static_cast(RHI::TextureType::Texture2D); + depthDesc.sampleCount = 1; + depthDesc.sampleQuality = 0; + depthDesc.flags = 0; + + RHI::RHITexture* depthTexture = context.device->CreateTexture(depthDesc); + if (depthTexture == nullptr) { + return false; + } + + RHI::ResourceViewDesc depthViewDesc = {}; + depthViewDesc.format = static_cast(RHI::Format::D24_UNorm_S8_UInt); + depthViewDesc.dimension = RHI::ResourceViewDimension::Texture2D; + depthViewDesc.mipLevel = 0; + + RHI::RHIResourceView* depthView = + context.device->CreateDepthStencilView(depthTexture, depthViewDesc); + if (depthView == nullptr) { + depthTexture->Shutdown(); + delete depthTexture; + return false; + } + + outResources.Reset(); + outResources.depthTexture = depthTexture; + outResources.depthView = depthView; + outResources.surface = RenderSurface(plan.mapWidth, plan.mapHeight); + outResources.surface.SetDepthAttachment(depthView); + return true; +} + } // namespace CameraRenderer::CameraRenderer() @@ -237,14 +316,32 @@ bool CameraRenderer::Render( !request.depthOnly.IsValid()) { return false; } - if (request.shadowCaster.IsRequested() && - !request.shadowCaster.IsValid()) { - return false; - } if (request.objectId.IsRequested() && !request.objectId.IsValid()) { return false; } + + ShadowCasterRenderRequest resolvedShadowCaster = request.shadowCaster; + OwnedShadowSurfaceResources ownedShadowSurface; + if (resolvedShadowCaster.IsRequested()) { + if (!resolvedShadowCaster.IsValid()) { + return false; + } + } else if (request.directionalShadow.IsValid()) { + if (!CreateDirectionalShadowSurface( + request.context, + request.directionalShadow, + ownedShadowSurface)) { + return false; + } + + resolvedShadowCaster.surface = ownedShadowSurface.surface; + if (!resolvedShadowCaster.hasCameraDataOverride) { + resolvedShadowCaster.hasCameraDataOverride = true; + resolvedShadowCaster.cameraDataOverride = request.directionalShadow.cameraData; + } + } + RenderSceneData sceneData = m_sceneExtractor.ExtractForCamera( *request.scene, *request.camera, @@ -279,7 +376,7 @@ bool CameraRenderer::Render( if (!ExecuteScenePassRequest( m_shadowCasterPass.get(), - request.shadowCaster, + resolvedShadowCaster, request.context, sceneData)) { ShutdownPassSequence(request.preScenePasses, preScenePassesInitialized); diff --git a/engine/src/Rendering/Passes/BuiltinDepthStylePassBase.cpp b/engine/src/Rendering/Passes/BuiltinDepthStylePassBase.cpp index 8479ea13..13a32ce2 100644 --- a/engine/src/Rendering/Passes/BuiltinDepthStylePassBase.cpp +++ b/engine/src/Rendering/Passes/BuiltinDepthStylePassBase.cpp @@ -45,19 +45,47 @@ bool IsSupportedPerObjectOnlyBindingPlan(const BuiltinPassResourceBindingPlan& b !bindingPlan.usesSamplers; } +uint32_t ResolveSurfaceColorAttachmentCount(const RenderSurface& surface) { + const std::vector& colorAttachments = surface.GetColorAttachments(); + return (!colorAttachments.empty() && colorAttachments[0] != nullptr) ? 1u : 0u; +} + +RHI::Format ResolveSurfaceColorFormat(const RenderSurface& surface) { + const std::vector& colorAttachments = surface.GetColorAttachments(); + if (colorAttachments.empty() || colorAttachments[0] == nullptr) { + return RHI::Format::Unknown; + } + + return colorAttachments[0]->GetFormat(); +} + +RHI::Format ResolveSurfaceDepthFormat(const RenderSurface& surface) { + if (RHI::RHIResourceView* depthAttachment = surface.GetDepthAttachment(); + depthAttachment != nullptr) { + return depthAttachment->GetFormat(); + } + + return RHI::Format::Unknown; +} + RHI::GraphicsPipelineDesc CreatePipelineDesc( RHI::RHIType backendType, RHI::RHIPipelineLayout* pipelineLayout, const Resources::Shader& shader, const Containers::String& passName, const Resources::Material* material, + const RenderSurface& surface, const RHI::InputLayoutDesc& inputLayout) { RHI::GraphicsPipelineDesc pipelineDesc = {}; pipelineDesc.pipelineLayout = pipelineLayout; pipelineDesc.topologyType = static_cast(RHI::PrimitiveTopologyType::Triangle); - pipelineDesc.renderTargetCount = 1; - pipelineDesc.renderTargetFormats[0] = static_cast(RHI::Format::R8G8B8A8_UNorm); - pipelineDesc.depthStencilFormat = static_cast(RHI::Format::D24_UNorm_S8_UInt); + pipelineDesc.renderTargetCount = ResolveSurfaceColorAttachmentCount(surface); + if (pipelineDesc.renderTargetCount > 0) { + pipelineDesc.renderTargetFormats[0] = + static_cast(ResolveSurfaceColorFormat(surface)); + } + pipelineDesc.depthStencilFormat = + static_cast(ResolveSurfaceDepthFormat(surface)); pipelineDesc.sampleCount = 1; pipelineDesc.inputLayout = inputLayout; ApplyMaterialRenderState(material, pipelineDesc); @@ -138,9 +166,10 @@ bool BuiltinDepthStylePassBase::Execute(const RenderPassContext& context) { } const std::vector& colorAttachments = context.surface.GetColorAttachments(); - if (colorAttachments.empty() || - colorAttachments[0] == nullptr || - context.surface.GetDepthAttachment() == nullptr) { + RHI::RHIResourceView* depthAttachment = context.surface.GetDepthAttachment(); + RHI::RHIResourceView* renderTarget = + (!colorAttachments.empty() ? colorAttachments[0] : nullptr); + if (depthAttachment == nullptr) { return false; } @@ -154,16 +183,20 @@ bool BuiltinDepthStylePassBase::Execute(const RenderPassContext& context) { } RHI::RHICommandList* commandList = context.renderContext.commandList; - RHI::RHIResourceView* renderTarget = colorAttachments[0]; - if (context.surface.IsAutoTransitionEnabled()) { + if (context.surface.IsAutoTransitionEnabled() && renderTarget != nullptr) { commandList->TransitionBarrier( renderTarget, context.surface.GetColorStateBefore(), RHI::ResourceStates::RenderTarget); } - commandList->SetRenderTargets(1, &renderTarget, context.surface.GetDepthAttachment()); + RHI::RHIResourceView* renderTargets[] = { renderTarget }; + const uint32_t renderTargetCount = renderTarget != nullptr ? 1u : 0u; + commandList->SetRenderTargets( + renderTargetCount, + renderTargetCount > 0 ? renderTargets : nullptr, + depthAttachment); const RHI::Viewport viewport = { static_cast(renderArea.x), @@ -185,11 +218,12 @@ bool BuiltinDepthStylePassBase::Execute(const RenderPassContext& context) { const Math::Color clearColor = context.sceneData.cameraData.clearColor; const float clearValues[4] = { clearColor.r, clearColor.g, clearColor.b, clearColor.a }; - if (HasRenderClearFlag(context.sceneData.cameraData.clearFlags, RenderClearFlags::Color)) { + if (renderTarget != nullptr && + HasRenderClearFlag(context.sceneData.cameraData.clearFlags, RenderClearFlags::Color)) { commandList->ClearRenderTarget(renderTarget, clearValues, 1, clearRects); } if (HasRenderClearFlag(context.sceneData.cameraData.clearFlags, RenderClearFlags::Depth)) { - commandList->ClearDepthStencil(context.surface.GetDepthAttachment(), 1.0f, 0, 1, clearRects); + commandList->ClearDepthStencil(depthAttachment, 1.0f, 0, 1, clearRects); } commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList); @@ -199,12 +233,12 @@ bool BuiltinDepthStylePassBase::Execute(const RenderPassContext& context) { continue; } - DrawVisibleItem(context.renderContext, context.sceneData, visibleItem); + DrawVisibleItem(context.renderContext, context.surface, context.sceneData, visibleItem); } commandList->EndRenderPass(); - if (context.surface.IsAutoTransitionEnabled()) { + if (context.surface.IsAutoTransitionEnabled() && renderTarget != nullptr) { commandList->TransitionBarrier( renderTarget, RHI::ResourceStates::RenderTarget, @@ -433,6 +467,7 @@ BuiltinDepthStylePassBase::PassResourceLayout* BuiltinDepthStylePassBase::GetOrC RHI::RHIPipelineState* BuiltinDepthStylePassBase::GetOrCreatePipelineState( const RenderContext& context, + const RenderSurface& surface, const Resources::Material* material) { const ResolvedShaderPass resolvedShaderPass = ResolveSurfaceShaderPass(material); if (resolvedShaderPass.shader == nullptr || resolvedShaderPass.pass == nullptr) { @@ -449,6 +484,9 @@ RHI::RHIPipelineState* BuiltinDepthStylePassBase::GetOrCreatePipelineState( material != nullptr ? material->GetRenderState() : Resources::MaterialRenderState(); pipelineKey.shader = resolvedShaderPass.shader; pipelineKey.passName = resolvedShaderPass.passName; + pipelineKey.renderTargetCount = ResolveSurfaceColorAttachmentCount(surface); + pipelineKey.renderTargetFormat = static_cast(ResolveSurfaceColorFormat(surface)); + pipelineKey.depthStencilFormat = static_cast(ResolveSurfaceDepthFormat(surface)); const auto existing = m_pipelineStates.find(pipelineKey); if (existing != m_pipelineStates.end()) { @@ -461,6 +499,7 @@ RHI::RHIPipelineState* BuiltinDepthStylePassBase::GetOrCreatePipelineState( *resolvedShaderPass.shader, resolvedShaderPass.passName, material, + surface, BuildCommonInputLayout()); RHI::RHIPipelineState* pipelineState = context.device->CreatePipelineState(pipelineDesc); if (pipelineState == nullptr || !pipelineState->IsValid()) { @@ -552,6 +591,7 @@ void BuiltinDepthStylePassBase::DestroyPassResourceLayout(PassResourceLayout& pa bool BuiltinDepthStylePassBase::DrawVisibleItem( const RenderContext& context, + const RenderSurface& surface, const RenderSceneData& sceneData, const VisibleRenderItem& visibleItem) { if (visibleItem.mesh == nullptr || visibleItem.gameObject == nullptr) { @@ -579,7 +619,7 @@ bool BuiltinDepthStylePassBase::DrawVisibleItem( return false; } - RHI::RHIPipelineState* pipelineState = GetOrCreatePipelineState(context, material); + RHI::RHIPipelineState* pipelineState = GetOrCreatePipelineState(context, surface, material); if (pipelineState == nullptr) { return false; } diff --git a/tests/Rendering/unit/test_camera_scene_renderer.cpp b/tests/Rendering/unit/test_camera_scene_renderer.cpp index c43cd3d8..ba2e89e6 100644 --- a/tests/Rendering/unit/test_camera_scene_renderer.cpp +++ b/tests/Rendering/unit/test_camera_scene_renderer.cpp @@ -1,6 +1,11 @@ #include #include +#include +#include +#include +#include +#include #include #include #include @@ -40,6 +45,193 @@ struct MockPipelineState { std::vector eventLog; }; +struct MockShadowAllocationState { + int createTextureCalls = 0; + int shutdownTextureCalls = 0; + int destroyTextureCalls = 0; + int createDepthViewCalls = 0; + int shutdownDepthViewCalls = 0; + int destroyDepthViewCalls = 0; + uint32_t lastTextureWidth = 0; + uint32_t lastTextureHeight = 0; + XCEngine::RHI::Format lastTextureFormat = XCEngine::RHI::Format::Unknown; + XCEngine::RHI::Format lastDepthViewFormat = XCEngine::RHI::Format::Unknown; +}; + +class MockShadowTexture final : public XCEngine::RHI::RHITexture { +public: + MockShadowTexture( + std::shared_ptr state, + uint32_t width, + uint32_t height, + XCEngine::RHI::Format format) + : m_state(std::move(state)) + , m_width(width) + , m_height(height) + , m_format(format) { + } + + ~MockShadowTexture() override { + ++m_state->destroyTextureCalls; + } + + uint32_t GetWidth() const override { return m_width; } + uint32_t GetHeight() const override { return m_height; } + uint32_t GetDepth() const override { return 1; } + uint32_t GetMipLevels() const override { return 1; } + XCEngine::RHI::Format GetFormat() const override { return m_format; } + XCEngine::RHI::TextureType GetTextureType() const override { return XCEngine::RHI::TextureType::Texture2D; } + XCEngine::RHI::ResourceStates GetState() const override { return m_stateValue; } + void SetState(XCEngine::RHI::ResourceStates state) override { m_stateValue = 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 { + ++m_state->shutdownTextureCalls; + } + +private: + std::shared_ptr m_state; + uint32_t m_width = 0; + uint32_t m_height = 0; + XCEngine::RHI::Format m_format = XCEngine::RHI::Format::Unknown; + XCEngine::RHI::ResourceStates m_stateValue = XCEngine::RHI::ResourceStates::DepthWrite; + std::string m_name; +}; + +class MockShadowView final : public XCEngine::RHI::RHIResourceView { +public: + MockShadowView( + std::shared_ptr state, + XCEngine::RHI::ResourceViewType viewType, + XCEngine::RHI::Format format, + XCEngine::RHI::ResourceViewDimension dimension) + : m_state(std::move(state)) + , m_viewType(viewType) + , m_format(format) + , m_dimension(dimension) { + } + + ~MockShadowView() override { + ++m_state->destroyDepthViewCalls; + } + + void Shutdown() override { + ++m_state->shutdownDepthViewCalls; + } + + 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: + std::shared_ptr m_state; + XCEngine::RHI::ResourceViewType m_viewType = XCEngine::RHI::ResourceViewType::DepthStencil; + XCEngine::RHI::Format m_format = XCEngine::RHI::Format::Unknown; + XCEngine::RHI::ResourceViewDimension m_dimension = XCEngine::RHI::ResourceViewDimension::Unknown; +}; + +class MockShadowDevice final : public XCEngine::RHI::RHIDevice { +public: + explicit MockShadowDevice(std::shared_ptr state) + : m_state(std::move(state)) { + } + + 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 { + ++m_state->createTextureCalls; + m_state->lastTextureWidth = desc.width; + m_state->lastTextureHeight = desc.height; + m_state->lastTextureFormat = static_cast(desc.format); + return new MockShadowTexture( + m_state, + desc.width, + desc.height, + static_cast(desc.format)); + } + + 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&) override { return nullptr; } + XCEngine::RHI::RHIPipelineLayout* CreatePipelineLayout(const XCEngine::RHI::RHIPipelineLayoutDesc&) override { return nullptr; } + XCEngine::RHI::RHIFence* CreateFence(const XCEngine::RHI::FenceDesc&) override { return nullptr; } + XCEngine::RHI::RHISampler* CreateSampler(const XCEngine::RHI::SamplerDesc&) override { return nullptr; } + + 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& desc) override { + ++m_state->createDepthViewCalls; + m_state->lastDepthViewFormat = static_cast(desc.format); + return new MockShadowView( + m_state, + XCEngine::RHI::ResourceViewType::DepthStencil, + static_cast(desc.format), + desc.dimension); + } + + XCEngine::RHI::RHIResourceView* CreateShaderResourceView( + XCEngine::RHI::RHITexture*, + 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; } + +private: + std::shared_ptr m_state; + XCEngine::RHI::RHICapabilities m_capabilities = {}; + XCEngine::RHI::RHIDeviceInfo m_deviceInfo = {}; +}; + struct MockPipelineAssetState { int createCalls = 0; std::shared_ptr lastCreatedPipelineState; @@ -416,13 +608,11 @@ TEST(CameraRenderer_Test, ExecutesShadowCasterAndDepthOnlyRequestsBeforeMainPipe request.cameraDepth = camera->GetDepth(); request.shadowCaster.surface = RenderSurface(128, 64); - request.shadowCaster.surface.SetColorAttachment(reinterpret_cast(1)); request.shadowCaster.surface.SetDepthAttachment(reinterpret_cast(2)); request.shadowCaster.hasCameraDataOverride = true; request.shadowCaster.cameraDataOverride.worldPosition = XCEngine::Math::Vector3(7.0f, 8.0f, 9.0f); request.depthOnly.surface = RenderSurface(96, 48); - request.depthOnly.surface.SetColorAttachment(reinterpret_cast(3)); request.depthOnly.surface.SetDepthAttachment(reinterpret_cast(4)); request.depthOnly.hasClearColorOverride = true; request.depthOnly.clearColorOverride = XCEngine::Math::Color(0.3f, 0.2f, 0.1f, 1.0f); @@ -451,6 +641,68 @@ TEST(CameraRenderer_Test, ExecutesShadowCasterAndDepthOnlyRequestsBeforeMainPipe EXPECT_FLOAT_EQ(depthPassRaw->lastClearColor.a, 1.0f); } +TEST(CameraRenderer_Test, AutoAllocatesDirectionalShadowSurfaceFromShadowPlan) { + Scene scene("CameraRendererAutoDirectionalShadowScene"); + + GameObject* cameraObject = scene.CreateGameObject("Camera"); + auto* camera = cameraObject->AddComponent(); + camera->SetPrimary(true); + camera->SetDepth(2.0f); + + auto pipelineState = std::make_shared(); + auto allocationState = std::make_shared(); + MockShadowDevice device(allocationState); + + CameraRenderer renderer( + std::make_unique(pipelineState), + std::make_unique(pipelineState)); + + auto shadowPass = std::make_unique(pipelineState, "shadowCaster"); + MockScenePass* shadowPassRaw = shadowPass.get(); + renderer.SetShadowCasterPass(std::move(shadowPass)); + + RenderContext context = CreateValidContext(); + context.device = &device; + + CameraRenderRequest request; + request.scene = &scene; + request.camera = camera; + request.context = context; + request.surface = RenderSurface(320, 180); + request.cameraDepth = camera->GetDepth(); + request.directionalShadow.enabled = true; + request.directionalShadow.mapWidth = 256; + request.directionalShadow.mapHeight = 128; + request.directionalShadow.cameraData.viewportWidth = 256; + request.directionalShadow.cameraData.viewportHeight = 128; + request.directionalShadow.cameraData.clearFlags = RenderClearFlags::Depth; + request.directionalShadow.cameraData.worldPosition = XCEngine::Math::Vector3(3.0f, 4.0f, 5.0f); + + ASSERT_TRUE(renderer.Render(request)); + EXPECT_EQ( + pipelineState->eventLog, + (std::vector{ + "init:shadowCaster", + "shadowCaster", + "pipeline" })); + EXPECT_EQ(shadowPassRaw->lastViewportWidth, 256u); + EXPECT_EQ(shadowPassRaw->lastViewportHeight, 128u); + EXPECT_EQ(shadowPassRaw->lastSurfaceWidth, 256u); + EXPECT_EQ(shadowPassRaw->lastSurfaceHeight, 128u); + EXPECT_EQ(shadowPassRaw->lastClearFlags, RenderClearFlags::Depth); + EXPECT_EQ(shadowPassRaw->lastWorldPosition, XCEngine::Math::Vector3(3.0f, 4.0f, 5.0f)); + EXPECT_EQ(allocationState->createTextureCalls, 1); + EXPECT_EQ(allocationState->createDepthViewCalls, 1); + EXPECT_EQ(allocationState->lastTextureWidth, 256u); + EXPECT_EQ(allocationState->lastTextureHeight, 128u); + EXPECT_EQ(allocationState->lastTextureFormat, XCEngine::RHI::Format::D24_UNorm_S8_UInt); + EXPECT_EQ(allocationState->lastDepthViewFormat, XCEngine::RHI::Format::D24_UNorm_S8_UInt); + EXPECT_EQ(allocationState->shutdownDepthViewCalls, 1); + EXPECT_EQ(allocationState->shutdownTextureCalls, 1); + EXPECT_EQ(allocationState->destroyDepthViewCalls, 1); + EXPECT_EQ(allocationState->destroyTextureCalls, 1); +} + TEST(CameraRenderer_Test, StopsRenderingWhenShadowCasterRequestIsInvalid) { Scene scene("CameraRendererInvalidShadowScene");