#include "Rendering/Passes/BuiltinVectorFullscreenPass.h" #include "Core/Asset/ResourceManager.h" #include "Debug/Logger.h" #include "Rendering/Builtin/BuiltinPassLayoutUtils.h" #include "Rendering/Internal/RenderSurfacePipelineUtils.h" #include "Rendering/Internal/ShaderVariantUtils.h" #include "Rendering/RenderSurface.h" #include #include "Resources/BuiltinResources.h" #include "RHI/RHICommandList.h" #include "RHI/RHIDescriptorPool.h" #include "RHI/RHIDescriptorSet.h" #include "RHI/RHIDevice.h" #include "RHI/RHIPipelineLayout.h" #include "RHI/RHIPipelineState.h" #include "RHI/RHIResourceView.h" #include "RHI/RHISampler.h" #include #include #include namespace XCEngine { namespace Rendering { namespace Passes { namespace { struct PostProcessConstants { Math::Vector4 vectorPayload = Math::Vector4::One(); }; const Resources::ShaderPass* FindCompatiblePass( const Resources::Shader& shader, Resources::ShaderBackend backend, const Containers::String& preferredPassName) { if (!preferredPassName.Empty()) { const Resources::ShaderPass* preferredPass = shader.FindPass(preferredPassName); if (preferredPass != nullptr && ::XCEngine::Rendering::Internal::ShaderPassHasGraphicsVariants( shader, preferredPass->name, backend)) { return preferredPass; } return nullptr; } const Resources::ShaderPass* colorScalePass = shader.FindPass("ColorScale"); if (colorScalePass != nullptr && ::XCEngine::Rendering::Internal::ShaderPassHasGraphicsVariants( shader, colorScalePass->name, backend)) { return colorScalePass; } if (shader.GetPassCount() > 0 && ::XCEngine::Rendering::Internal::ShaderPassHasGraphicsVariants( shader, shader.GetPasses()[0].name, backend)) { return &shader.GetPasses()[0]; } return nullptr; } bool UsesContiguousDescriptorSets( const BuiltinPassResourceBindingPlan& bindingPlan) { if (bindingPlan.bindings.Empty()) { return true; } std::vector usedSets( static_cast(bindingPlan.maxSetIndex) + 1u, false); Core::uint32 minSet = UINT32_MAX; Core::uint32 maxSet = 0u; for (const BuiltinPassResourceBindingDesc& binding : bindingPlan.bindings) { usedSets[binding.location.set] = true; minSet = std::min(minSet, binding.location.set); maxSet = std::max(maxSet, binding.location.set); } for (Core::uint32 setIndex = minSet; setIndex <= maxSet; ++setIndex) { if (!usedSets[setIndex]) { return false; } } return true; } bool IsSupportedFullscreenBindingPlan( const BuiltinPassResourceBindingPlan& bindingPlan) { if (!UsesContiguousDescriptorSets(bindingPlan)) { return false; } for (const BuiltinPassResourceBindingDesc& binding : bindingPlan.bindings) { switch (binding.semantic) { case BuiltinPassResourceSemantic::PassConstants: case BuiltinPassResourceSemantic::SourceColorTexture: case BuiltinPassResourceSemantic::MaterialTexture: case BuiltinPassResourceSemantic::LinearClampSampler: break; default: return false; } } return true; } const RenderPassContext::TextureBindingView* FindTextureBindingView( const RenderPassContext& context, const BuiltinPassResourceBindingDesc& bindingDesc) { auto matchesName = [&bindingDesc]( const RenderPassContext::TextureBindingView& bindingView) { return bindingView.resourceName == bindingDesc.name; }; const auto it = std::find_if( context.textureBindings.begin(), context.textureBindings.end(), matchesName); return it != context.textureBindings.end() ? &(*it) : nullptr; } RHI::GraphicsPipelineDesc CreatePipelineDesc( RHI::RHIType backendType, RHI::RHIPipelineLayout* pipelineLayout, const Resources::Shader& shader, const Containers::String& passName, const RenderSurface& surface) { RHI::GraphicsPipelineDesc pipelineDesc = {}; pipelineDesc.pipelineLayout = pipelineLayout; pipelineDesc.topologyType = static_cast( RHI::PrimitiveTopologyType::Triangle); ::XCEngine::Rendering::Internal:: ApplySingleColorAttachmentPropertiesToGraphicsPipelineDesc( surface, pipelineDesc); pipelineDesc.depthStencilFormat = static_cast( RHI::Format::Unknown); pipelineDesc.rasterizerState.fillMode = static_cast( RHI::FillMode::Solid); pipelineDesc.rasterizerState.cullMode = static_cast( RHI::CullMode::None); pipelineDesc.rasterizerState.frontFace = static_cast(RHI::FrontFace::CounterClockwise); pipelineDesc.rasterizerState.depthClipEnable = true; pipelineDesc.blendState.blendEnable = false; pipelineDesc.blendState.srcBlend = static_cast( RHI::BlendFactor::One); pipelineDesc.blendState.dstBlend = static_cast( RHI::BlendFactor::Zero); pipelineDesc.blendState.srcBlendAlpha = static_cast(RHI::BlendFactor::One); pipelineDesc.blendState.dstBlendAlpha = static_cast(RHI::BlendFactor::Zero); pipelineDesc.blendState.blendOp = static_cast( RHI::BlendOp::Add); pipelineDesc.blendState.blendOpAlpha = static_cast( RHI::BlendOp::Add); pipelineDesc.blendState.colorWriteMask = static_cast(RHI::ColorWriteMask::All); pipelineDesc.depthStencilState.depthTestEnable = false; pipelineDesc.depthStencilState.depthWriteEnable = false; pipelineDesc.depthStencilState.depthFunc = static_cast( RHI::ComparisonFunc::Always); const Resources::ShaderPass* shaderPass = shader.FindPass(passName); const Resources::ShaderBackend backend = ::XCEngine::Rendering::Internal::ToShaderBackend( backendType); if (const Resources::ShaderStageVariant* vertexVariant = shader.FindVariant( passName, Resources::ShaderType::Vertex, backend)) { if (shaderPass != nullptr) { ::XCEngine::Rendering::Internal::ApplyShaderStageVariant( shader.GetPath(), *shaderPass, backend, *vertexVariant, pipelineDesc.vertexShader); } } if (const Resources::ShaderStageVariant* fragmentVariant = shader.FindVariant( passName, Resources::ShaderType::Fragment, backend)) { if (shaderPass != nullptr) { ::XCEngine::Rendering::Internal::ApplyShaderStageVariant( shader.GetPath(), *shaderPass, backend, *fragmentVariant, pipelineDesc.fragmentShader); } } return pipelineDesc; } } // namespace BuiltinVectorFullscreenPass::BuiltinVectorFullscreenPass( const Math::Vector4& vectorPayload, Containers::String shaderPath, Containers::String preferredPassName) : m_shaderPath(std::move(shaderPath)) , m_preferredPassName(std::move(preferredPassName)) , m_vectorPayload(vectorPayload) { if (m_shaderPath.Empty()) { m_shaderPath = Resources::GetBuiltinColorScalePostProcessShaderPath(); } } BuiltinVectorFullscreenPass::~BuiltinVectorFullscreenPass() { Shutdown(); } const char* BuiltinVectorFullscreenPass::GetName() const { return "BuiltinVectorFullscreenPass"; } bool BuiltinVectorFullscreenPass::SupportsRenderGraph() const { return true; } bool BuiltinVectorFullscreenPass::RecordRenderGraph( const RenderPassRenderGraphContext& context) { return RecordSourceColorFullscreenRasterPass( *this, context); } bool BuiltinVectorFullscreenPass::Execute( const RenderPassContext& context) { if (!context.renderContext.IsValid() || !::XCEngine::Rendering::Internal::HasSingleColorAttachment( context.surface)) { return false; } const std::vector& colorAttachments = context.surface.GetColorAttachments(); if (colorAttachments.empty() || colorAttachments[0] == nullptr) { return false; } const Math::RectInt renderArea = context.surface.GetRenderArea(); if (renderArea.width <= 0 || renderArea.height <= 0) { return false; } if (!EnsureInitialized( context.renderContext, context.surface)) { return false; } if (m_passLayout.sourceColorTexture.IsValid() && (context.sourceSurface == nullptr || context.sourceColorView == nullptr)) { return false; } if (!UpdateDescriptorSets(context)) { return false; } RHI::RHICommandList* commandList = context.renderContext.commandList; if (commandList == nullptr) { return false; } RHI::RHIResourceView* renderTarget = colorAttachments[0]; const bool autoTransitionSource = m_passLayout.sourceColorTexture.IsValid() && context.sourceSurface != nullptr && context.sourceSurface->IsAutoTransitionEnabled(); if (context.surface.IsAutoTransitionEnabled()) { commandList->TransitionBarrier( renderTarget, context.surface.GetColorStateBefore(), RHI::ResourceStates::RenderTarget); } if (m_passLayout.sourceColorTexture.IsValid()) { if (autoTransitionSource) { commandList->TransitionBarrier( context.sourceColorView, context.sourceColorState, RHI::ResourceStates::PixelShaderResource); } else if (context.sourceColorState != RHI::ResourceStates::PixelShaderResource) { return false; } } commandList->SetRenderTargets(1, &renderTarget, nullptr); const RHI::Viewport viewport = { static_cast(renderArea.x), static_cast(renderArea.y), static_cast(renderArea.width), static_cast(renderArea.height), 0.0f, 1.0f }; const RHI::Rect scissorRect = { renderArea.x, renderArea.y, renderArea.x + renderArea.width, renderArea.y + renderArea.height }; commandList->SetViewport(viewport); commandList->SetScissorRect(scissorRect); commandList->SetPrimitiveTopology( RHI::PrimitiveTopology::TriangleList); commandList->SetPipelineState(m_pipelineState); if (m_passLayout.descriptorSetCount > 0u) { std::vector descriptorSets( m_passLayout.descriptorSetCount, nullptr); for (Core::uint32 descriptorOffset = 0u; descriptorOffset < m_passLayout.descriptorSetCount; ++descriptorOffset) { const Core::uint32 setIndex = m_passLayout.firstDescriptorSet + descriptorOffset; if (setIndex >= m_passLayout.descriptorSets.size() || m_passLayout.descriptorSets[setIndex].set == nullptr) { return false; } descriptorSets[descriptorOffset] = m_passLayout.descriptorSets[setIndex].set; } commandList->SetGraphicsDescriptorSets( m_passLayout.firstDescriptorSet, m_passLayout.descriptorSetCount, descriptorSets.data(), m_passLayout.pipelineLayout); } commandList->Draw(3, 1, 0, 0); commandList->EndRenderPass(); if (context.surface.IsAutoTransitionEnabled()) { commandList->TransitionBarrier( renderTarget, RHI::ResourceStates::RenderTarget, context.surface.GetColorStateAfter()); } if (m_passLayout.sourceColorTexture.IsValid() && autoTransitionSource) { commandList->TransitionBarrier( context.sourceColorView, RHI::ResourceStates::PixelShaderResource, context.sourceColorState); } return true; } void BuiltinVectorFullscreenPass::Shutdown() { DestroyResources(); } void BuiltinVectorFullscreenPass::SetVectorPayload( const Math::Vector4& vectorPayload) { m_vectorPayload = vectorPayload; } const Math::Vector4& BuiltinVectorFullscreenPass::GetVectorPayload() const { return m_vectorPayload; } void BuiltinVectorFullscreenPass::SetShaderPath( const Containers::String& shaderPath) { if (m_shaderPath == shaderPath) { return; } DestroyResources(); m_shaderPath = shaderPath; } const Containers::String& BuiltinVectorFullscreenPass::GetShaderPath() const { return m_shaderPath; } void BuiltinVectorFullscreenPass::SetPreferredPassName( const Containers::String& preferredPassName) { if (m_preferredPassName == preferredPassName) { return; } DestroyResources(); m_preferredPassName = preferredPassName; } const Containers::String& BuiltinVectorFullscreenPass::GetPreferredPassName() const { return m_preferredPassName; } bool BuiltinVectorFullscreenPass::EnsureInitialized( const RenderContext& renderContext, const RenderSurface& surface) { const RHI::Format renderTargetFormat = ::XCEngine::Rendering::Internal::ResolveSurfaceColorFormat( surface, 0u); const uint32_t renderTargetSampleCount = ::XCEngine::Rendering::Internal::ResolveSurfaceSampleCount( surface); const uint32_t renderTargetSampleQuality = ::XCEngine::Rendering::Internal::ResolveSurfaceSampleQuality( surface); if (m_device == renderContext.device && m_backendType == renderContext.backendType && m_renderTargetFormat == renderTargetFormat && m_renderTargetSampleCount == renderTargetSampleCount && m_renderTargetSampleQuality == renderTargetSampleQuality && m_passLayout.pipelineLayout != nullptr && m_pipelineState != nullptr && m_sampler != nullptr) { return true; } DestroyResources(); return CreateResources(renderContext, surface); } bool BuiltinVectorFullscreenPass::CreateResources( const RenderContext& renderContext, const RenderSurface& surface) { if (!renderContext.IsValid()) { return false; } RHI::Format renderTargetFormat = RHI::Format::Unknown; if (!::XCEngine::Rendering::Internal:: TryResolveSingleColorAttachmentFormat( surface, renderTargetFormat)) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, "BuiltinVectorFullscreenPass requires exactly one " "valid destination color attachment"); return false; } if (m_shaderPath.Empty()) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, "BuiltinVectorFullscreenPass requires a shader path " "before resource creation"); return false; } m_shader = Resources::ResourceManager::Get().Load( m_shaderPath); if (!m_shader.IsValid()) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, "BuiltinVectorFullscreenPass failed to load " "configured fullscreen shader resource"); DestroyResources(); return false; } const Resources::ShaderBackend backend = ::XCEngine::Rendering::Internal::ToShaderBackend( renderContext.backendType); const Resources::ShaderPass* selectedPass = FindCompatiblePass( *m_shader.Get(), backend, m_preferredPassName); if (selectedPass == nullptr) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, m_preferredPassName.Empty() ? "BuiltinVectorFullscreenPass could not resolve " "a compatible fullscreen shader pass" : "BuiltinVectorFullscreenPass could not resolve " "the configured fullscreen shader pass"); DestroyResources(); return false; } BuiltinPassResourceBindingPlan bindingPlan = {}; Containers::String bindingPlanError; if (!TryBuildBuiltinPassResourceBindingPlan( *selectedPass, bindingPlan, &bindingPlanError)) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, (Containers::String( "BuiltinVectorFullscreenPass failed to " "resolve shader resource bindings: ") + bindingPlanError) .CStr()); DestroyResources(); return false; } if (!IsSupportedFullscreenBindingPlan(bindingPlan)) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, "BuiltinVectorFullscreenPass only supports " "PassConstants, SourceColorTexture, " "MaterialTexture, and LinearClampSampler " "resource semantics"); DestroyResources(); return false; } std::vector setLayouts; Containers::String setLayoutError; if (!TryBuildBuiltinPassSetLayouts( bindingPlan, setLayouts, &setLayoutError)) { Debug::Logger::Get().Error( Debug::LogCategory::Rendering, (Containers::String( "BuiltinVectorFullscreenPass failed to build " "descriptor set layouts: ") + setLayoutError) .CStr()); DestroyResources(); return false; } m_device = renderContext.device; m_backendType = renderContext.backendType; m_renderTargetFormat = renderTargetFormat; m_renderTargetSampleCount = ::XCEngine::Rendering::Internal::ResolveSurfaceSampleCount( surface); m_renderTargetSampleQuality = ::XCEngine::Rendering::Internal:: ResolveSurfaceSampleQuality(surface); m_passLayout.setLayouts = std::move(setLayouts); m_passLayout.descriptorSets.resize( m_passLayout.setLayouts.size()); m_passLayout.boundSetStates.resize( m_passLayout.setLayouts.size()); m_passLayout.passConstants = bindingPlan.passConstants; m_passLayout.sourceColorTexture = bindingPlan.sourceColorTexture; m_passLayout.linearClampSampler = bindingPlan.linearClampSampler; m_passLayout.firstDescriptorSet = bindingPlan.firstDescriptorSet; m_passLayout.descriptorSetCount = bindingPlan.descriptorSetCount; std::vector nativeSetLayouts( m_passLayout.setLayouts.size()); for (size_t setIndex = 0u; setIndex < m_passLayout.setLayouts.size(); ++setIndex) { nativeSetLayouts[setIndex] = m_passLayout.setLayouts[setIndex].layout; } RHI::RHIPipelineLayoutDesc pipelineLayoutDesc = {}; pipelineLayoutDesc.setLayouts = nativeSetLayouts.empty() ? nullptr : nativeSetLayouts.data(); pipelineLayoutDesc.setLayoutCount = static_cast(nativeSetLayouts.size()); m_passLayout.pipelineLayout = m_device->CreatePipelineLayout(pipelineLayoutDesc); if (m_passLayout.pipelineLayout == nullptr) { DestroyResources(); return false; } RHI::SamplerDesc samplerDesc = {}; samplerDesc.filter = static_cast(RHI::FilterMode::Linear); samplerDesc.addressU = static_cast( RHI::TextureAddressMode::Clamp); samplerDesc.addressV = static_cast( RHI::TextureAddressMode::Clamp); samplerDesc.addressW = static_cast( RHI::TextureAddressMode::Clamp); samplerDesc.mipLodBias = 0.0f; samplerDesc.maxAnisotropy = 1; samplerDesc.comparisonFunc = static_cast( RHI::ComparisonFunc::Always); samplerDesc.minLod = 0.0f; samplerDesc.maxLod = 1000.0f; m_sampler = m_device->CreateSampler(samplerDesc); if (m_sampler == nullptr) { DestroyResources(); return false; } for (size_t setIndex = 0u; setIndex < m_passLayout.setLayouts.size(); ++setIndex) { const BuiltinPassSetLayoutMetadata& setLayout = m_passLayout.setLayouts[setIndex]; if (setLayout.bindings.empty()) { continue; } if (!CreateOwnedDescriptorSet( setLayout, m_passLayout.descriptorSets[setIndex])) { DestroyResources(); return false; } if (setLayout.usesSampler) { if (!setLayout.usesLinearClampSampler || !m_passLayout.linearClampSampler.IsValid() || m_passLayout.linearClampSampler.set != setIndex) { DestroyResources(); return false; } m_passLayout.descriptorSets[setIndex] .set->UpdateSampler( m_passLayout.linearClampSampler.binding, m_sampler); } } m_pipelineState = m_device->CreatePipelineState( CreatePipelineDesc( m_backendType, m_passLayout.pipelineLayout, *m_shader.Get(), selectedPass->name, surface)); if (m_pipelineState == nullptr || !m_pipelineState->IsValid()) { DestroyResources(); return false; } return true; } bool BuiltinVectorFullscreenPass::CreateOwnedDescriptorSet( const BuiltinPassSetLayoutMetadata& setLayout, OwnedDescriptorSet& descriptorSet) { RHI::DescriptorPoolDesc poolDesc = {}; poolDesc.type = setLayout.heapType; poolDesc.descriptorCount = CountBuiltinPassHeapDescriptors( setLayout.heapType, setLayout.bindings); poolDesc.shaderVisible = setLayout.shaderVisible; descriptorSet.pool = m_device->CreateDescriptorPool(poolDesc); if (descriptorSet.pool == nullptr) { return false; } descriptorSet.set = descriptorSet.pool->AllocateSet(setLayout.layout); if (descriptorSet.set == nullptr) { DestroyOwnedDescriptorSet(descriptorSet); return false; } return true; } bool BuiltinVectorFullscreenPass::UpdateDescriptorSets( const RenderPassContext& context) { for (size_t setIndex = 0u; setIndex < m_passLayout.setLayouts.size(); ++setIndex) { const BuiltinPassSetLayoutMetadata& setLayout = m_passLayout.setLayouts[setIndex]; if (setLayout.bindings.empty()) { continue; } OwnedDescriptorSet& descriptorSet = m_passLayout.descriptorSets[setIndex]; BoundSetState& boundSetState = m_passLayout.boundSetStates[setIndex]; if (descriptorSet.set == nullptr) { return false; } if (setLayout.usesPassConstants) { if (!m_passLayout.passConstants.IsValid() || m_passLayout.passConstants.set != setIndex) { return false; } const PostProcessConstants constants = { m_vectorPayload }; descriptorSet.set->WriteConstant( m_passLayout.passConstants.binding, &constants, sizeof(constants)); } if (setLayout.usesSourceColorTexture) { if (context.sourceColorView == nullptr || !m_passLayout.sourceColorTexture.IsValid() || m_passLayout.sourceColorTexture.set != setIndex) { return false; } if (boundSetState.sourceColorTextureView != context.sourceColorView) { descriptorSet.set->Update( m_passLayout.sourceColorTexture.binding, context.sourceColorView); boundSetState.sourceColorTextureView = context.sourceColorView; } } if (setLayout.usesMaterialTextures) { if (boundSetState.materialTextureViews.size() != setLayout.materialTextureBindings.size()) { boundSetState.materialTextureViews.assign( setLayout.materialTextureBindings.size(), nullptr); } for (size_t bindingIndex = 0u; bindingIndex < setLayout.materialTextureBindings.size(); ++bindingIndex) { const BuiltinPassResourceBindingDesc& textureBinding = setLayout.materialTextureBindings [bindingIndex]; RHI::RHIResourceView* resolvedTextureView = ResolveExtraTextureView( context, textureBinding); if (resolvedTextureView == nullptr) { return false; } if (boundSetState.materialTextureViews [bindingIndex] != resolvedTextureView) { descriptorSet.set->Update( textureBinding.location.binding, resolvedTextureView); boundSetState.materialTextureViews [bindingIndex] = resolvedTextureView; } } } else if (!boundSetState.materialTextureViews .empty()) { boundSetState.materialTextureViews.clear(); } } return true; } RHI::RHIResourceView* BuiltinVectorFullscreenPass::ResolveExtraTextureView( const RenderPassContext& context, const BuiltinPassResourceBindingDesc& bindingDesc) const { const RenderPassContext::TextureBindingView* bindingView = FindTextureBindingView( context, bindingDesc); return bindingView != nullptr ? bindingView->resourceView : nullptr; } void BuiltinVectorFullscreenPass::DestroyResources() { if (m_pipelineState != nullptr) { m_pipelineState->Shutdown(); delete m_pipelineState; m_pipelineState = nullptr; } DestroyPassResourceLayout(); if (m_sampler != nullptr) { m_sampler->Shutdown(); delete m_sampler; m_sampler = nullptr; } m_shader.Reset(); m_device = nullptr; m_backendType = RHI::RHIType::D3D12; m_renderTargetFormat = RHI::Format::Unknown; m_renderTargetSampleCount = 1u; m_renderTargetSampleQuality = 0u; } void BuiltinVectorFullscreenPass::DestroyPassResourceLayout() { for (OwnedDescriptorSet& descriptorSet : m_passLayout.descriptorSets) { DestroyOwnedDescriptorSet(descriptorSet); } m_passLayout.descriptorSets.clear(); m_passLayout.boundSetStates.clear(); m_passLayout.setLayouts.clear(); if (m_passLayout.pipelineLayout != nullptr) { m_passLayout.pipelineLayout->Shutdown(); delete m_passLayout.pipelineLayout; m_passLayout.pipelineLayout = nullptr; } m_passLayout.passConstants = {}; m_passLayout.sourceColorTexture = {}; m_passLayout.linearClampSampler = {}; m_passLayout.firstDescriptorSet = 0u; m_passLayout.descriptorSetCount = 0u; } void BuiltinVectorFullscreenPass::DestroyOwnedDescriptorSet( OwnedDescriptorSet& descriptorSet) { if (descriptorSet.set != nullptr) { descriptorSet.set->Shutdown(); delete descriptorSet.set; descriptorSet.set = nullptr; } if (descriptorSet.pool != nullptr) { descriptorSet.pool->Shutdown(); delete descriptorSet.pool; descriptorSet.pool = nullptr; } } } // namespace Passes } // namespace Rendering } // namespace XCEngine