/* * Copyright (c) Contributors to the Open 3D Engine Project. * For complete copyright and license terms please see the LICENSE at the root of this distribution. * * SPDX-License-Identifier: Apache-2.0 OR MIT * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace AZ { namespace RPI { RenderPass::RenderPass(const PassDescriptor& descriptor) : Pass(descriptor) { // Read view tag from pass data const RenderPassData* passData = PassUtils::GetPassData(descriptor); if (passData && !passData->m_pipelineViewTag.IsEmpty()) { SetPipelineViewTag(passData->m_pipelineViewTag); } } RenderPass::~RenderPass() { } const PipelineViewTag& RenderPass::GetPipelineViewTag() const { return m_viewTag; } RHI::RenderAttachmentConfiguration RenderPass::GetRenderAttachmentConfiguration() const { RHI::RenderAttachmentLayoutBuilder builder; auto* pass = builder.AddSubpass(); for (size_t slotIndex = 0; slotIndex < m_attachmentBindings.size(); ++slotIndex) { const PassAttachmentBinding& binding = m_attachmentBindings[slotIndex]; if (!binding.m_attachment) { continue; } // Handle the depth-stencil attachment. There should be only one. if (binding.m_scopeAttachmentUsage == RHI::ScopeAttachmentUsage::DepthStencil) { pass->DepthStencilAttachment(binding.m_attachment->m_descriptor.m_image.m_format); continue; } // Skip bindings that aren't outputs or inputOutputs if (binding.m_slotType != PassSlotType::Output && binding.m_slotType != PassSlotType::InputOutput) { continue; } if (binding.m_scopeAttachmentUsage == RHI::ScopeAttachmentUsage::RenderTarget) { RHI::Format format = binding.m_attachment->m_descriptor.m_image.m_format; pass->RenderTargetAttachment(format); } } RHI::RenderAttachmentLayout layout; [[maybe_unused]] RHI::ResultCode result = builder.End(layout); AZ_Assert(result == RHI::ResultCode::Success, "RenderPass [%s] failed to create render attachment layout", GetPathName().GetCStr()); return RHI::RenderAttachmentConfiguration{ layout, 0 }; } RHI::MultisampleState RenderPass::GetMultisampleState() const { RHI::MultisampleState outputMultiSampleState; bool wasSet = false; for (size_t slotIndex = 0; slotIndex < m_attachmentBindings.size(); ++slotIndex) { const PassAttachmentBinding& binding = m_attachmentBindings[slotIndex]; if (binding.m_slotType != PassSlotType::Output && binding.m_slotType != PassSlotType::InputOutput) { continue; } if (!binding.m_attachment) { continue; } if (binding.m_scopeAttachmentUsage == RHI::ScopeAttachmentUsage::RenderTarget || binding.m_scopeAttachmentUsage == RHI::ScopeAttachmentUsage::DepthStencil) { if (!wasSet) { // save multi-sample state found in the first output color attachment outputMultiSampleState = binding.m_attachment->m_descriptor.m_image.m_multisampleState; wasSet = true; } else if (PassValidation::IsEnabled()) { // return false directly if the current output color attachment has different multi-sample state then previous ones if (outputMultiSampleState != binding.m_attachment->m_descriptor.m_image.m_multisampleState) { AZ_Error("RPI", false, "Pass %s has different multi-sample states within its color attachments", GetPathName().GetCStr()); break; } } else { break; } } } return outputMultiSampleState; } void RenderPass::InitializeInternal() { if (m_shaderResourceGroup != nullptr) { Name autoBind = Name("AutoBind"); Name noBind = Name("NoBind"); for (PassAttachmentBinding& binding : m_attachmentBindings) { const Name& shaderName = binding.m_shaderInputName; PassAttachment* attachment = binding.m_attachment.get(); if (shaderName == autoBind) { binding.m_shaderInputIndex = PassAttachmentBinding::ShaderInputAutoBind; } else if (shaderName == noBind) { binding.m_shaderInputIndex = PassAttachmentBinding::ShaderInputNoBind; } else if (attachment) { if (attachment->GetAttachmentType() == RHI::AttachmentType::Image) { RHI::ShaderInputImageIndex idx = m_shaderResourceGroup->FindShaderInputImageIndex(shaderName); AZ_Error("Pass System", idx.IsValid(), "[Pass %s] Could not retrieve Shader Image Index for SRG variable'%s'", GetName().GetCStr(), shaderName.GetCStr()); binding.m_shaderInputIndex = idx.IsValid() ? static_cast(idx.GetIndex()) : PassAttachmentBinding::ShaderInputNoBind; } else if (attachment->GetAttachmentType() == RHI::AttachmentType::Buffer) { RHI::ShaderInputBufferIndex idx = m_shaderResourceGroup->FindShaderInputBufferIndex(shaderName); AZ_Error("Pass System", idx.IsValid(), "[Pass %s] Could not retrieve Shader Buffer Index for SRG variable '%s'", GetName().GetCStr(), shaderName.GetCStr()); binding.m_shaderInputIndex = idx.IsValid() ? static_cast(idx.GetIndex()) : PassAttachmentBinding::ShaderInputNoBind; } } else { AZ_Error( "Pass System", AZ::RHI::IsNullRenderer(), "[Pass %s] Could not bind shader buffer index '%s' because it has no attachment.", GetName().GetCStr(), shaderName.GetCStr()); binding.m_shaderInputIndex = PassAttachmentBinding::ShaderInputNoBind; } } } } void RenderPass::FrameBeginInternal(FramePrepareParams params) { if (GetScopeId().IsEmpty()) { InitScope(RHI::ScopeId(GetPathName()), m_hardwareQueueClass); } params.m_frameGraphBuilder->ImportScopeProducer(*this); // Read back the ScopeQueries submitted from previous frames ReadbackScopeQueryResults(); CollectSrgs(); PassSystemInterface::Get()->IncrementFrameRenderPassCount(); } void RenderPass::FrameEndInternal() { ResetSrgs(); } void RenderPass::SetupFrameGraphDependencies(RHI::FrameGraphInterface frameGraph) { DeclareAttachmentsToFrameGraph(frameGraph); DeclarePassDependenciesToFrameGraph(frameGraph); AddScopeQueryToFrameGraph(frameGraph); } void RenderPass::BuildCommandList(const RHI::FrameGraphExecuteContext& context) { BeginScopeQuery(context); BuildCommandListInternal(context); EndScopeQuery(context); } void RenderPass::DeclareAttachmentsToFrameGraph(RHI::FrameGraphInterface frameGraph) const { for (const PassAttachmentBinding& attachmentBinding : m_attachmentBindings) { if (attachmentBinding.m_attachment != nullptr && frameGraph.GetAttachmentDatabase().IsAttachmentValid(attachmentBinding.m_attachment->GetAttachmentId())) { switch (attachmentBinding.m_unifiedScopeDesc.GetType()) { case RHI::AttachmentType::Image: { frameGraph.UseAttachment(attachmentBinding.m_unifiedScopeDesc.GetAsImage(), attachmentBinding.GetAttachmentAccess(), attachmentBinding.m_scopeAttachmentUsage); break; } case RHI::AttachmentType::Buffer: { frameGraph.UseAttachment(attachmentBinding.m_unifiedScopeDesc.GetAsBuffer(), attachmentBinding.GetAttachmentAccess(), attachmentBinding.m_scopeAttachmentUsage); break; } default: AZ_Assert(false, "Error, trying to bind an attachment that is neither an image nor a buffer!"); break; } } } } void RenderPass::DeclarePassDependenciesToFrameGraph(RHI::FrameGraphInterface frameGraph) const { for (Pass* pass : m_executeAfterPasses) { RenderPass* renderPass = azrtti_cast(pass); if (renderPass) { frameGraph.ExecuteAfter(GetScopeId()); } } for (Pass* pass : m_executeBeforePasses) { RenderPass* renderPass = azrtti_cast(pass); if (renderPass) { frameGraph.ExecuteBefore(GetScopeId()); } } } void RenderPass::BindAttachment(const RHI::FrameGraphCompileContext& context, PassAttachmentBinding& binding, int16_t& imageIndex, int16_t& bufferIndex) { PassAttachment* attachment = binding.m_attachment.get(); if (attachment) { int16_t inputIndex = binding.m_shaderInputIndex; uint16_t arrayIndex = binding.m_shaderInputArrayIndex; if (attachment->GetAttachmentType() == RHI::AttachmentType::Image) { if (inputIndex == PassAttachmentBinding::ShaderInputAutoBind) { inputIndex = imageIndex; } const RHI::ImageView* imageView = context.GetImageView(attachment->GetAttachmentId(), binding.m_unifiedScopeDesc.GetImageViewDescriptor(), binding.m_scopeAttachmentUsage); if (binding.m_shaderImageDimensionsNameIndex.HasName()) { RHI::Size size = attachment->m_descriptor.m_image.m_size; AZ::Vector4 imageDimensions; imageDimensions.SetX(float(size.m_width)); imageDimensions.SetY(float(size.m_height)); imageDimensions.SetZ(1.0f / float(size.m_width)); imageDimensions.SetW(1.0f / float(size.m_height)); [[maybe_unused]] bool success = m_shaderResourceGroup->SetConstant(binding.m_shaderImageDimensionsNameIndex, imageDimensions); AZ_Assert(success, "Pass [%s] Could not find float4 constant [%s] in Shader Resource Group [%s]", GetPathName().GetCStr(), binding.m_shaderImageDimensionsNameIndex.GetNameForDebug().GetCStr(), m_shaderResourceGroup->GetDatabaseName()); } if (binding.m_shaderInputIndex != PassAttachmentBinding::ShaderInputNoBind && binding.m_scopeAttachmentUsage != RHI::ScopeAttachmentUsage::RenderTarget && binding.m_scopeAttachmentUsage != RHI::ScopeAttachmentUsage::DepthStencil) { m_shaderResourceGroup->SetImageView(RHI::ShaderInputImageIndex(inputIndex), imageView, arrayIndex); ++imageIndex; } } else if (attachment->GetAttachmentType() == RHI::AttachmentType::Buffer) { if (binding.m_shaderInputIndex == PassAttachmentBinding::ShaderInputNoBind) { return; } if (inputIndex == PassAttachmentBinding::ShaderInputAutoBind) { inputIndex = bufferIndex; } const RHI::BufferView* bufferView = context.GetBufferView(attachment->GetAttachmentId(), binding.m_scopeAttachmentUsage); m_shaderResourceGroup->SetBufferView(RHI::ShaderInputBufferIndex(inputIndex), bufferView, arrayIndex); ++bufferIndex; } } } void RenderPass::BindPassSrg(const RHI::FrameGraphCompileContext& context, [[maybe_unused]] Data::Instance& shaderResourceGroup) { AZ_Assert(m_shaderResourceGroup != nullptr, "Passing a null shader resource group to RenderPass::BindPassSrg"); int16_t imageIndex = 0; int16_t bufferIndex = 0; // Bind the input attachments to the SRG for (uint32_t idx = 0; idx < GetInputCount(); ++idx) { PassAttachmentBinding& binding = GetInputBinding(idx); AZ_Assert(binding.m_scopeAttachmentUsage != RHI::ScopeAttachmentUsage::RenderTarget, "Attachment bindings that are inputs cannot have their type set to 'RenderTarget'. Binding in question is %s on pass %s.", binding.m_name.GetCStr(), GetPathName().GetCStr()); BindAttachment(context, binding, imageIndex, bufferIndex); } // Bind the input/output attachments to the SRG for (uint32_t idx = 0; idx < GetInputOutputCount(); ++idx) { PassAttachmentBinding& binding = GetInputOutputBinding(idx); BindAttachment(context, binding, imageIndex, bufferIndex); } // Bind the output attachments to the SRG for (uint32_t idx = 0; idx < GetOutputCount(); ++idx) { PassAttachmentBinding& binding = GetOutputBinding(idx); BindAttachment(context, binding, imageIndex, bufferIndex); } } ViewPtr RenderPass::GetView() const { if (m_flags.m_hasPipelineViewTag && m_pipeline) { const AZStd::vector& views = m_pipeline->GetViews(m_viewTag); if (views.size() > 0) { return views[0]; } } return nullptr; } void RenderPass::CollectSrgs() { // Scene srg const RHI::ShaderResourceGroup* sceneSrg = m_pipeline->GetScene()->GetRHIShaderResourceGroup(); BindSrg(sceneSrg); // View srg ViewPtr view = GetView(); if (view) { BindSrg(view->GetRHIShaderResourceGroup()); } // Pass srg if (m_shaderResourceGroup) { BindSrg(m_shaderResourceGroup->GetRHIShaderResourceGroup()); } } void RenderPass::ResetSrgs() { m_shaderResourceGroupsToBind.clear(); } void RenderPass::BindSrg(const RHI::ShaderResourceGroup* srg) { if (srg) { m_shaderResourceGroupsToBind.push_back(srg); } } void RenderPass::SetSrgsForDraw(RHI::CommandList* commandList) { for (const RHI::ShaderResourceGroup* shaderResourceGroup : m_shaderResourceGroupsToBind) { commandList->SetShaderResourceGroupForDraw(*shaderResourceGroup); } } void RenderPass::SetSrgsForDispatch(RHI::CommandList* commandList) { for (const RHI::ShaderResourceGroup* shaderResourceGroup : m_shaderResourceGroupsToBind) { commandList->SetShaderResourceGroupForDispatch(*shaderResourceGroup); } } void RenderPass::SetPipelineViewTag(const PipelineViewTag& viewTag) { m_viewTag = viewTag; m_flags.m_hasPipelineViewTag = !viewTag.IsEmpty(); } TimestampResult RenderPass::GetTimestampResultInternal() const { return m_timestampResult; } PipelineStatisticsResult RenderPass::GetPipelineStatisticsResultInternal() const { return m_statisticsResult; } Data::Instance RenderPass::GetShaderResourceGroup() { return m_shaderResourceGroup; } RHI::Ptr RenderPass::GetQuery(ScopeQueryType queryType) { uint32_t typeIndex = static_cast(queryType); if (!m_scopeQueries[typeIndex]) { RHI::Ptr query; switch (queryType) { case ScopeQueryType::Timestamp: query = GpuQuerySystemInterface::Get()->CreateQuery( RHI::QueryType::Timestamp, RHI::QueryPoolScopeAttachmentType::Global, RHI::ScopeAttachmentAccess::Write); break; case ScopeQueryType::PipelineStatistics: query = GpuQuerySystemInterface::Get()->CreateQuery( RHI::QueryType::PipelineStatistics, RHI::QueryPoolScopeAttachmentType::Global, RHI::ScopeAttachmentAccess::Write); break; } m_scopeQueries[typeIndex] = query; } return m_scopeQueries[typeIndex]; } template inline void RenderPass::ExecuteOnTimestampQuery(Func&& func) { if (IsTimestampQueryEnabled()) { auto query = GetQuery(ScopeQueryType::Timestamp); if (query) { func(query); } } } template inline void RenderPass::ExecuteOnPipelineStatisticsQuery(Func&& func) { if (IsPipelineStatisticsQueryEnabled()) { auto query = GetQuery(ScopeQueryType::PipelineStatistics); if (query) { func(query); } } } void RenderPass::AddScopeQueryToFrameGraph(RHI::FrameGraphInterface frameGraph) { const auto addToFrameGraph = [&frameGraph](RHI::Ptr query) { query->AddToFrameGraph(frameGraph); }; ExecuteOnTimestampQuery(addToFrameGraph); ExecuteOnPipelineStatisticsQuery(addToFrameGraph); } void RenderPass::BeginScopeQuery(const RHI::FrameGraphExecuteContext& context) { const auto beginQuery = [&context, this](RHI::Ptr query) { if (query->BeginQuery(context) == QueryResultCode::Fail) { AZ_UNUSED(this); // Prevent unused warning in release builds AZ_WarningOnce("RenderPass", false, "BeginScopeQuery failed. Make sure AddScopeQueryToFrameGraph was called in SetupFrameGraphDependencies" " for this pass: %s", this->RTTI_GetTypeName()); } }; if (context.GetCommandListIndex() == 0) { ExecuteOnTimestampQuery(beginQuery); ExecuteOnPipelineStatisticsQuery(beginQuery); } } void RenderPass::EndScopeQuery(const RHI::FrameGraphExecuteContext& context) { const auto endQuery = [&context](RHI::Ptr query) { query->EndQuery(context); }; // This scopy query implmentation should be replaced by // [ATOM-5407] [RHI][Core] - Add GPU timestamp and pipeline statistic support for scopes // For timestamp query, it's okay to execute across different command lists if (context.GetCommandListIndex() == context.GetCommandListCount() - 1) { ExecuteOnTimestampQuery(endQuery); } // For all the other types of queries except timestamp, the query start and end has to be in the same command list // Here only tracks the PipelineStatistics for the first command list due to that we don't know how many queries are // needed when AddScopeQueryToFrameGraph is called. // This implementation leads to an issue that we may not get accurate pipeline statistic data // for passes which were executed with more than one command list if (context.GetCommandListIndex() == 0) { ExecuteOnPipelineStatisticsQuery(endQuery); } } void RenderPass::ReadbackScopeQueryResults() { ExecuteOnTimestampQuery([this](RHI::Ptr query) { const uint32_t TimestampResultQueryCount = 2u; uint64_t timestampResult[TimestampResultQueryCount] = {0}; query->GetLatestResult(×tampResult, sizeof(uint64_t) * TimestampResultQueryCount); m_timestampResult = TimestampResult(timestampResult[0], timestampResult[1], RHI::HardwareQueueClass::Graphics); }); ExecuteOnPipelineStatisticsQuery([this](RHI::Ptr query) { query->GetLatestResult(&m_statisticsResult, sizeof(PipelineStatisticsResult)); }); } } // namespace RPI } // namespace AZ