You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/RenderPass.cpp

548 lines
22 KiB
C++

/*
* 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 <Atom/RHI/CommandList.h>
#include <Atom/RHI/FrameGraphAttachmentInterface.h>
#include <Atom/RHI/FrameGraphBuilder.h>
#include <Atom/RHI/FrameGraphCompileContext.h>
#include <Atom/RHI/FrameGraphExecuteContext.h>
#include <Atom/RHI.Reflect/ImageScopeAttachmentDescriptor.h>
#include <Atom/RPI.Reflect/Pass/RenderPassData.h>
#include <Atom/RHI.Reflect/RenderAttachmentLayoutBuilder.h>
#include <Atom/RPI.Public/GpuQuery/Query.h>
#include <Atom/RPI.Public/Pass/PassUtils.h>
#include <Atom/RPI.Public/Pass/RenderPass.h>
#include <Atom/RPI.Public/Pass/Specific/ImageAttachmentPreviewPass.h>
#include <Atom/RPI.Public/RenderPipeline.h>
#include <Atom/RPI.Public/Scene.h>
#include <Atom/RPI.Public/View.h>
namespace AZ
{
namespace RPI
{
RenderPass::RenderPass(const PassDescriptor& descriptor)
: Pass(descriptor)
{
// Read view tag from pass data
const RenderPassData* passData = PassUtils::GetPassData<RenderPassData>(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<int16_t>(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<int16_t>(idx.GetIndex()) : PassAttachmentBinding::ShaderInputNoBind;
}
}
else
{
AZ_Error("Pass System", false, "[Pass %s] Could not bind shader buffer index '%s' because it has no attachment.", GetName().GetCStr(), shaderName.GetCStr());
binding.m_shaderInputIndex = PassAttachmentBinding::ShaderInputNoBind;
}
}
}
// Need to recreate the dest attachment because the source attachment might be changed
if (!m_attachmentCopy.expired())
{
m_attachmentCopy.lock()->InvalidateDestImage();
}
}
void RenderPass::FrameBeginInternal(FramePrepareParams params)
{
if (GetScopeId().IsEmpty())
{
SetScopeId(RHI::ScopeId(GetPathName()));
}
params.m_frameGraphBuilder->ImportScopeProducer(*this);
// Read back the ScopeQueries submitted from previous frames
ReadbackScopeQueryResults();
if (!m_attachmentCopy.expired())
{
m_attachmentCopy.lock()->FrameBegin(params);
}
CollectSrgs();
}
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<RenderPass*>(pass);
if (renderPass)
{
frameGraph.ExecuteAfter(GetScopeId());
}
}
for (Pass* pass : m_executeBeforePasses)
{
RenderPass* renderPass = azrtti_cast<RenderPass*>(pass);
if (renderPass)
{
frameGraph.ExecuteBefore(GetScopeId());
}
}
}
void RenderPass::BindAttachment(const RHI::FrameGraphCompileContext& context, const PassAttachmentBinding& binding, int16_t& imageIndex, int16_t& bufferIndex)
{
if (binding.m_shaderInputIndex == PassAttachmentBinding::ShaderInputNoBind ||
binding.m_scopeAttachmentUsage == RHI::ScopeAttachmentUsage::RenderTarget ||
binding.m_scopeAttachmentUsage == RHI::ScopeAttachmentUsage::DepthStencil)
{
return;
}
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_attachmentUsageIndex);
m_shaderResourceGroup->SetImageView(RHI::ShaderInputImageIndex(inputIndex), imageView, arrayIndex);
++imageIndex;
}
else if (attachment->GetAttachmentType() == RHI::AttachmentType::Buffer)
{
if (inputIndex == PassAttachmentBinding::ShaderInputAutoBind)
{
inputIndex = bufferIndex;
}
const RHI::BufferView* bufferView = context.GetBufferView(attachment->GetAttachmentId(), binding.m_attachmentUsageIndex);
m_shaderResourceGroup->SetBufferView(RHI::ShaderInputBufferIndex(inputIndex), bufferView, arrayIndex);
++bufferIndex;
}
}
}
void RenderPass::BindPassSrg(const RHI::FrameGraphCompileContext& context, [[maybe_unused]] Data::Instance<ShaderResourceGroup>& 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<ViewPtr>& 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<RPI::ShaderResourceGroup> RenderPass::GetShaderResourceGroup()
{
return m_shaderResourceGroup;
}
RHI::Ptr<Query> RenderPass::GetQuery(ScopeQueryType queryType)
{
uint32_t typeIndex = static_cast<uint32_t>(queryType);
if (!m_scopeQueries[typeIndex])
{
RHI::Ptr<Query> 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<typename Func>
inline void RenderPass::ExecuteOnTimestampQuery(Func&& func)
{
if (IsTimestampQueryEnabled())
{
auto query = GetQuery(ScopeQueryType::Timestamp);
if (query)
{
func(query);
}
}
}
template<typename Func>
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)
{
query->AddToFrameGraph(frameGraph);
};
ExecuteOnTimestampQuery(addToFrameGraph);
ExecuteOnPipelineStatisticsQuery(addToFrameGraph);
}
void RenderPass::BeginScopeQuery(const RHI::FrameGraphExecuteContext& context)
{
const auto beginQuery = [&context, this](RHI::Ptr<Query> query)
{
if (query->BeginQuery(context) == QueryResultCode::Fail)
{
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)
{
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> query)
{
const uint32_t TimestampResultQueryCount = 2u;
uint64_t timestampResult[TimestampResultQueryCount] = {0};
query->GetLatestResult(&timestampResult, sizeof(uint64_t) * TimestampResultQueryCount);
m_timestampResult = TimestampResult(timestampResult[0], timestampResult[1], RHI::HardwareQueueClass::Graphics);
});
ExecuteOnPipelineStatisticsQuery([this](RHI::Ptr<Query> query)
{
query->GetLatestResult(&m_statisticsResult, sizeof(PipelineStatisticsResult));
});
}
} // namespace RPI
} // namespace AZ