Files
o3de/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Pass.cpp
T
Qing Tao 9e0756f3c1 ATOM-16656 PassTree tool: ParentPass image attachment preview doesn't work (#5032)
Move imageAttachmentCopy instance from RenderPass to Pass so it can support preview image for all passes but not only for RenderPass.
Fixed an issue with image attachment preview when switching render pipeline with attachment preview on.

Signed-off-by: Qing Tao <qingtao@amazon.com>
2021-10-27 11:28:21 -07:00

1756 lines
66 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 <AzCore/RTTI/RTTI.h>
#include <AzCore/std/string/conversions.h>
#include <AtomCore/Instance/InstanceDatabase.h>
#include <AtomCore/std/containers/vector_set.h>
#include <Atom/RHI/FrameGraphAttachmentInterface.h>
#include <Atom/RHI/FrameGraphBuilder.h>
#include <Atom/RHI/RHIUtils.h>
#include <Atom/RHI.Reflect/Base.h>
#include <Atom/RPI.Public/Buffer/Buffer.h>
#include <Atom/RPI.Public/Image/AttachmentImage.h>
#include <Atom/RPI.Reflect/Image/Image.h>
#include <Atom/RPI.Public/Pass/AttachmentReadback.h>
#include <Atom/RPI.Public/Pass/ParentPass.h>
#include <Atom/RPI.Public/Pass/Pass.h>
#include <Atom/RPI.Public/Pass/PassLibrary.h>
#include <Atom/RPI.Public/Pass/PassDefines.h>
#include <Atom/RPI.Public/Pass/PassSystemInterface.h>
#include <Atom/RPI.Public/Pass/Specific/ImageAttachmentPreviewPass.h>
#include <Atom/RPI.Public/RenderPipeline.h>
#include <Atom/RPI.Reflect/Image/AttachmentImageAsset.h>
#include <Atom/RPI.Reflect/Pass/PassRequest.h>
#include <Atom/RPI.Reflect/Pass/PassTemplate.h>
#include <Atom/RPI.Reflect/Pass/PassName.h>
#include <Atom/RPI.Reflect/Asset/AssetUtils.h>
namespace AZ
{
namespace RPI
{
// --- Constructors ---
Pass::Pass(const PassDescriptor& descriptor)
: m_name(descriptor.m_passName)
, m_path(descriptor.m_passName)
{
AZ_RPI_PASS_ASSERT((descriptor.m_passRequest == nullptr) || (descriptor.m_passTemplate != nullptr),
"Pass::Pass - request is valid but template is nullptr. This is not allowed. Passing a valid passRequest also requires a valid passTemplate.");
m_flags.m_enabled = true;
m_flags.m_timestampQueryEnabled = false;
m_flags.m_pipelineStatisticsQueryEnabled = false;
m_template = descriptor.m_passTemplate;
if (descriptor.m_passRequest != nullptr)
{
// Assert m_template is the same as the one in the pass request
if (PassValidation::IsEnabled())
{
const AZStd::shared_ptr<PassTemplate> passTemplate = PassSystemInterface::Get()->GetPassTemplate(descriptor.m_passRequest->m_templateName);
AZ_RPI_PASS_ASSERT(m_template == passTemplate, "Error: template in PassDescriptor doesn't match template from PassRequest!");
}
m_request = *descriptor.m_passRequest;
m_flags.m_createdByPassRequest = true;
m_flags.m_enabled = m_request.m_passEnabled;
}
PassSystemInterface::Get()->RegisterPass(this);
QueueForBuildAndInitialization();
// Skip reset since the pass just got created
m_state = PassState::Reset;
}
Pass::~Pass()
{
PassSystemInterface::Get()->UnregisterPass(this);
}
PassDescriptor Pass::GetPassDescriptor() const
{
PassDescriptor desc;
desc.m_passName = m_name;
desc.m_passTemplate = m_template ? PassSystemInterface::Get()->GetPassTemplate(m_template->m_name) : nullptr;
desc.m_passRequest = m_flags.m_createdByPassRequest ? &m_request : nullptr;
return desc;
}
// --- Enable/Disable ---
void Pass::SetEnabled(bool enabled)
{
m_flags.m_enabled = enabled;
OnHierarchyChange();
}
bool Pass::IsEnabled() const
{
return m_flags.m_enabled;
}
// --- Error Logging ---
void Pass::LogError(AZStd::string&& message)
{
#if AZ_RPI_ENABLE_PASS_DEBUGGING
AZ::Debug::Trace::Break();
#endif
if (PassValidation::IsEnabled())
{
++m_errors;
if (m_errorMessages.size() < MessageLogLimit)
{
m_errorMessages.push_back(AZStd::move(message));
}
}
}
void Pass::LogWarning(AZStd::string&& message)
{
if (PassValidation::IsEnabled())
{
++m_warnings;
if (m_warningMessages.size() < MessageLogLimit)
{
m_warningMessages.push_back(AZStd::move(message));
}
}
}
// --- Hierarchy functions ---
void Pass::OnHierarchyChange()
{
if (m_parent == nullptr)
{
return;
}
// Set new tree depth and path
m_flags.m_parentEnabled = m_parent->m_flags.m_enabled && (m_parent->m_flags.m_parentEnabled || m_parent->m_parent == nullptr);
m_treeDepth = m_parent->m_treeDepth + 1;
m_path = ConcatPassName(m_parent->m_path, m_name);
m_flags.m_partOfHierarchy = m_parent->m_flags.m_partOfHierarchy;
}
void Pass::RemoveFromParent()
{
AZ_RPI_PASS_ASSERT(m_parent != nullptr, "Trying to remove pass from parent but pointer to the parent pass is null.");
m_parent->RemoveChild(Ptr<Pass>(this));
m_queueState = PassQueueState::NoQueue;
m_state = PassState::Idle;
}
void Pass::OnOrphan()
{
m_parent = nullptr;
m_flags.m_partOfHierarchy = false;
m_treeDepth = 0;
}
// --- Getters & Setters ---
PassState Pass::GetPassState() const
{
return m_state;
}
ParentPass* Pass::GetParent() const
{
return m_parent;
}
ParentPass* Pass::AsParent()
{
return azrtti_cast<ParentPass*>(this);
}
const ParentPass* Pass::AsParent() const
{
return azrtti_cast<const ParentPass*>(this);
}
const Name& Pass::GetName() const
{
return m_name;
}
const Name& Pass::GetPathName() const
{
return m_path;
}
uint32_t Pass::GetTreeDepth() const
{
return m_treeDepth;
}
PassAttachmentBindingListView Pass::GetAttachmentBindings() const
{
return m_attachmentBindings;
}
uint32_t Pass::GetInputCount() const
{
return uint32_t(m_inputBindingIndices.size());
}
uint32_t Pass::GetInputOutputCount() const
{
return uint32_t(m_inputOutputBindingIndices.size());
}
uint32_t Pass::GetOutputCount() const
{
return uint32_t(m_outputBindingIndices.size());
}
PassAttachmentBinding& Pass::GetInputBinding(uint32_t index)
{
uint32_t bindingIndex = m_inputBindingIndices[index];
return m_attachmentBindings[bindingIndex];
}
PassAttachmentBinding& Pass::GetInputOutputBinding(uint32_t index)
{
uint32_t bindingIndex = m_inputOutputBindingIndices[index];
return m_attachmentBindings[bindingIndex];
}
PassAttachmentBinding& Pass::GetOutputBinding(uint32_t index)
{
uint32_t bindingIndex = m_outputBindingIndices[index];
return m_attachmentBindings[bindingIndex];
}
const PassTemplate* Pass::GetPassTemplate() const
{
return m_template.get();
}
void Pass::AddAttachmentBinding(PassAttachmentBinding attachmentBinding)
{
// Add the index of the binding to the input, output or input/output list based on the slot type
switch (attachmentBinding.m_slotType)
{
case PassSlotType::Input:
m_inputBindingIndices.push_back(uint8_t(m_attachmentBindings.size()));
break;
case PassSlotType::InputOutput:
m_inputOutputBindingIndices.push_back(uint8_t(m_attachmentBindings.size()));
break;
case PassSlotType::Output:
m_outputBindingIndices.push_back(uint8_t(m_attachmentBindings.size()));
break;
default:
break;
}
// Add the binding
m_attachmentBindings.push_back(attachmentBinding);
}
// --- Finders ---
Ptr<Pass> Pass::FindAdjacentPass(const Name& passName)
{
// 1. Check This
if (passName == PassNameThis)
{
return Ptr<Pass>(this);
}
// 2. Check Parent
if (m_parent == nullptr)
{
return nullptr;
}
if (passName == PassNameParent || passName == m_parent->GetName())
{
return Ptr<Pass>(m_parent);
}
// 3. Check Siblings
Ptr<Pass> foundPass = m_parent->FindChildPass(passName);
// 4. Check Children
if (!foundPass && AsParent())
{
foundPass = AsParent()->FindChildPass(passName);
}
// Finished search, return
return foundPass;
}
PassAttachmentBinding* Pass::FindAttachmentBinding(const Name& slotName)
{
for (PassAttachmentBinding& binding : m_attachmentBindings)
{
if (slotName == binding.m_name)
{
return &binding;
}
}
return nullptr;
}
const PassAttachmentBinding* Pass::FindAttachmentBinding(const Name& slotName) const
{
for (const PassAttachmentBinding& binding : m_attachmentBindings)
{
if (slotName == binding.m_name)
{
return &binding;
}
}
return nullptr;
}
Ptr<PassAttachment> Pass::FindOwnedAttachment(const Name& attachmentName) const
{
for (const Ptr<PassAttachment>& attachment : m_ownedAttachments)
{
if (attachment->m_name == attachmentName)
{
return attachment;
}
}
return nullptr;
}
Ptr<PassAttachment> Pass::FindAttachment(const Name& slotName) const
{
if (const PassAttachmentBinding* binding = FindAttachmentBinding(slotName))
{
return binding->m_attachment;
}
return FindOwnedAttachment(slotName);
}
const PassAttachmentBinding* Pass::FindAdjacentBinding(const PassAttachmentRef& attachmentRef)
{
// Validate attachmentRef
if (attachmentRef.m_pass.IsEmpty() || attachmentRef.m_attachment.IsEmpty())
{
return nullptr;
}
// Find pass
if (Ptr<Pass> pass = FindAdjacentPass(attachmentRef.m_pass))
{
// Find attachment within pass
return pass->FindAttachmentBinding(attachmentRef.m_attachment);
}
return nullptr;
}
// --- PassTemplate related functions ---
void Pass::CreateBindingsFromTemplate()
{
if (m_template)
{
for (const PassSlot& slot : m_template->m_slots)
{
PassAttachmentBinding binding(slot);
AddAttachmentBinding(binding);
}
}
}
void Pass::AttachBufferToSlot(AZStd::string_view slot, Data::Instance<Buffer> buffer)
{
AttachBufferToSlot(Name(slot), buffer);
}
void Pass::AttachBufferToSlot(const Name& slot, Data::Instance<Buffer> buffer)
{
if (!buffer)
{
return;
}
PassAttachmentBinding* localBinding = FindAttachmentBinding(slot);
if (!localBinding)
{
AZ_RPI_PASS_ERROR(false, "Pass::AttachBufferToSlot - Pass [%s] failed to find slot [%s].",
m_path.GetCStr(), slot.GetCStr());
return;
}
// We can't handle the case that there is already an attachment attached yet.
// We could consider to add it later if there are needs. It may require remove from the owned attachment list and
// handle the connected bindings
if (localBinding->m_attachment)
{
AZ_RPI_PASS_ERROR(false, "Pass::AttachBufferToSlot - Slot [%s] already has attachment [%s].",
slot.GetCStr(), localBinding->m_attachment->m_name.GetCStr());
return;
}
PassBufferAttachmentDesc desc;
desc.m_bufferDescriptor = buffer->GetRHIBuffer()->GetDescriptor();
desc.m_lifetime = RHI::AttachmentLifetimeType::Imported;
desc.m_name = buffer->GetAttachmentId();
Ptr<PassAttachment> attachment = CreateAttachmentFromDesc(desc);
attachment->m_importedResource = buffer;
m_ownedAttachments.push_back(attachment);
localBinding->SetAttachment(attachment);
localBinding->m_originalAttachment = attachment;
}
void Pass::AttachImageToSlot(const Name& slot, Data::Instance<AttachmentImage> image)
{
PassAttachmentBinding* localBinding = FindAttachmentBinding(slot);
if (!localBinding)
{
AZ_RPI_PASS_ERROR(false, "Pass::AttachImageToSlot - Pass [%s] failed to find slot [%s].",
m_path.GetCStr(), slot.GetCStr());
return;
}
// We can't handle the case that there is already an attachment attached yet.
// We could consider to add it later if there are needs. It may require remove from the owned attachment list and
// handle the connected bindings
if (localBinding->m_attachment)
{
AZ_RPI_PASS_ERROR(false, "Pass::AttachImageToSlot - Slot [%s] already has attachment [%s].",
slot.GetCStr(), localBinding->m_attachment->m_name.GetCStr());
return;
}
PassImageAttachmentDesc desc;
desc.m_imageDescriptor = image->GetRHIImage()->GetDescriptor();
desc.m_lifetime = RHI::AttachmentLifetimeType::Imported;
desc.m_name = image->GetAttachmentId();
Ptr<PassAttachment> attachment = CreateAttachmentFromDesc(desc);
attachment->m_importedResource = image;
m_ownedAttachments.push_back(attachment);
localBinding->SetAttachment(attachment);
localBinding->m_originalAttachment = attachment;
}
void Pass::ProcessConnection(const PassConnection& connection, uint32_t slotTypeMask)
{
// Get the input from this pass that forms one end of the connection
PassAttachmentBinding* localBinding = FindAttachmentBinding(connection.m_localSlot);
if (!localBinding)
{
AZ_RPI_PASS_ERROR(false, "Pass::ProcessConnection - Pass [%s] failed to find slot [%s].",
m_path.GetCStr(),
connection.m_localSlot.GetCStr());
return;
}
uint32_t bindingMask = (1 << uint32_t(localBinding->m_slotType));
if (!(bindingMask & slotTypeMask))
{
return;
}
Name connectedPassName = connection.m_attachmentRef.m_pass;
Name connectedSlotName = connection.m_attachmentRef.m_attachment;
Ptr<Pass> connectedPass;
PassAttachmentBinding* connectedBinding = nullptr;
bool foundPass = false;
if (connectedPassName == PassNameThis)
{
foundPass = true;
const Ptr<PassAttachment> attachment = FindOwnedAttachment(connectedSlotName);
AZ_RPI_PASS_ERROR(attachment, "Pass::ProcessConnection - Pass [%s] doesn't own an attachment named [%s].",
m_path.GetCStr(),
connectedSlotName.GetCStr());
localBinding->SetAttachment(attachment);
localBinding->m_originalAttachment = attachment;
return;
}
if (m_parent)
{
if (connectedPassName == PassNameParent)
{
foundPass = true;
// Get the connected binding from the parent
connectedBinding = m_parent->FindAttachmentBinding(connectedSlotName);
bool slotTypeMismatch = connectedBinding != nullptr &&
connectedBinding->m_slotType != localBinding->m_slotType &&
connectedBinding->m_slotType != PassSlotType::InputOutput &&
localBinding->m_slotType != PassSlotType::InputOutput;
if (slotTypeMismatch)
{
AZ_RPI_PASS_ERROR(false, "Pass::ProcessConnection - When connecting to a parent slot, both slots must be of the same type (or one must be InputOutput)");
connectedBinding = nullptr;
}
}
else
{
// Use the connection Name to find a sibling pass
Ptr<Pass> siblingPass = m_parent->FindChildPass(connectedPassName);
foundPass = foundPass || (siblingPass != nullptr);
if (siblingPass)
{
connectedBinding = siblingPass->FindAttachmentBinding(connectedSlotName);
bool slotTypeMismatch = connectedBinding != nullptr &&
connectedBinding->m_slotType == localBinding->m_slotType &&
connectedBinding->m_slotType != PassSlotType::InputOutput;
if (slotTypeMismatch)
{
AZ_RPI_PASS_ERROR(false, "Pass::ProcessConnection - When connecting to a sibling slot, both slots must be of different types (or be InputOutputs)");
connectedBinding = nullptr;
}
}
}
}
ParentPass* asParent = AsParent();
if (!foundPass && asParent)
{
Ptr<Pass> childPass = asParent->FindChildPass(connectedPassName);
foundPass = foundPass || (childPass != nullptr);
if (childPass)
{
connectedBinding = childPass->FindAttachmentBinding(connectedSlotName);
bool slotTypeMismatch = connectedBinding != nullptr &&
connectedBinding->m_slotType != localBinding->m_slotType &&
connectedBinding->m_slotType != PassSlotType::InputOutput &&
localBinding->m_slotType != PassSlotType::InputOutput;
if (slotTypeMismatch)
{
AZ_RPI_PASS_ERROR(false, "Pass::ProcessConnection - When connecting to a child slot, both slots must be of the same type (or one must be InputOutput)");
connectedBinding = nullptr;
}
}
}
if (!connectedBinding)
{
if (!m_flags.m_partOfHierarchy)
{
// [GFX TODO][ATOM-13693]: REMOVE POST R1 - passes not in hierarchy should no longer have this function called
// When view is changing, removal of the passes can occur (cascade shadow passes for example)
// resulting in temporary orphan passes that will be removed over the next frame.
AZ_RPI_PASS_WARNING(false,
"Pass::ProcessConnection - Pass [%s] is no longer part of the heirarchy and about to be removed",
m_path.GetCStr());
}
else if (foundPass)
{
AZ_RPI_PASS_ERROR(false, "Pass::ProcessConnection - Pass [%s] couldn't find a valid binding [%s] on pass [%s].",
m_path.GetCStr(),
connectedSlotName.GetCStr(),
connectedPassName.GetCStr());
}
else
{
AZ_RPI_PASS_ERROR(
false, "Pass::ProcessConnection - Pass [%s] is trying to connect to but could not find neighbor or child pass named [%s].",
m_path.GetCStr(),
connectedPassName.GetCStr());
}
return;
}
localBinding->m_connectedBinding = connectedBinding;
UpdateConnectedBinding(*localBinding);
}
void Pass::ProcessFallbackConnection(const PassFallbackConnection& connection)
{
PassAttachmentBinding* inputBinding = FindAttachmentBinding(connection.m_inputSlotName);
PassAttachmentBinding* outputBinding = FindAttachmentBinding(connection.m_outputSlotName);
if (!outputBinding || !inputBinding)
{
AZ_RPI_PASS_ERROR(inputBinding, "Pass::ProcessFallbackConnection - Pass [%s] failed to find input slot [%s].",
m_path.GetCStr(), connection.m_inputSlotName.GetCStr());
AZ_RPI_PASS_ERROR(outputBinding, "Pass::ProcessFallbackConnection - Pass [%s] failed to find output slot [%s].",
m_path.GetCStr(), connection.m_outputSlotName.GetCStr());
return;
}
bool typesAreValid = inputBinding->m_slotType == PassSlotType::Input && outputBinding->m_slotType == PassSlotType::Output;
if (!typesAreValid)
{
AZ_RPI_PASS_ERROR(inputBinding->m_slotType == PassSlotType::Input, "Pass::ProcessFallbackConnection - Pass [%s] specifies fallback connection input [%s], which is not an input.",
m_path.GetCStr(), connection.m_inputSlotName.GetCStr());
AZ_RPI_PASS_ERROR(outputBinding->m_slotType == PassSlotType::Output, "Pass::ProcessFallbackConnection - Pass [%s] specifies fallback connection output [%s], which is not an output.",
m_path.GetCStr(), connection.m_inputSlotName.GetCStr());
return;
}
outputBinding->m_fallbackBinding = inputBinding;
UpdateConnectedBinding(*outputBinding);
}
template<typename AttachmentDescType>
Ptr<PassAttachment> Pass::CreateAttachmentFromDesc(const AttachmentDescType& desc)
{
Ptr<PassAttachment> attachment = aznew PassAttachment(desc);
// If the attachment is imported, we will create the resource (buffer or image) of this attachment
// from asset referenced in m_assetRef
// The resource instance will be saved in m_importedResource and the attachment id is acquired from resource instance
if (desc.m_lifetime == RHI::AttachmentLifetimeType::Imported)
{
attachment->m_path = desc.m_name;
if (attachment->m_descriptor.m_type == RHI::AttachmentType::Buffer)
{
Data::Asset<BufferAsset> bufferAsset = AssetUtils::LoadAssetById<BufferAsset>(desc.m_assetRef.m_assetId, AssetUtils::TraceLevel::None);
if (bufferAsset.IsReady())
{
Data::Instance<Buffer> buffer = Buffer::FindOrCreate(bufferAsset);
if (buffer)
{
attachment->m_path = buffer->GetAttachmentId();
attachment->m_importedResource = buffer;
}
}
}
else if (attachment->m_descriptor.m_type == RHI::AttachmentType::Image)
{
Data::Asset<AttachmentImageAsset> imageAsset = AssetUtils::LoadAssetById<AttachmentImageAsset>(desc.m_assetRef.m_assetId, AssetUtils::TraceLevel::None);
if (imageAsset.IsReady())
{
Data::Instance<AttachmentImage> image = AttachmentImage::FindOrCreate(imageAsset);
if (image)
{
attachment->m_path = image->GetAttachmentId();
attachment->m_importedResource = image;
}
}
}
else
{
AZ_RPI_PASS_ASSERT(false, "Unsupported imported attachment type");
}
}
else
{
// Only apply path name to transient attachment. Keep the original name for imported attachment
attachment->ComputePathName(m_path);
}
// Setup attachment sources...
if (desc.m_sizeSource.m_source.m_pass == PipelineKeyword) // if source is pipeline
{
attachment->m_renderPipelineSource = m_pipeline;
attachment->m_settingFlags.m_getSizeFromPipeline = true;
attachment->m_sizeMultipliers = desc.m_sizeSource.m_multipliers;
}
else if (const PassAttachmentBinding* source = FindAdjacentBinding(desc.m_sizeSource.m_source))
{
attachment->m_sizeSource = source;
attachment->m_sizeMultipliers = desc.m_sizeSource.m_multipliers;
}
if (desc.m_formatSource.m_pass == PipelineKeyword) // if source is pipeline
{
attachment->m_renderPipelineSource = m_pipeline;
attachment->m_settingFlags.m_getFormatFromPipeline = true;
}
else if (const PassAttachmentBinding* source = FindAdjacentBinding(desc.m_formatSource))
{
attachment->m_formatSource = source;
}
if (desc.m_multisampleSource.m_pass == PipelineKeyword) // if source is pipeline
{
attachment->m_renderPipelineSource = m_pipeline;
attachment->m_settingFlags.m_getMultisampleStateFromPipeline = true;
}
else if (const PassAttachmentBinding* source = FindAdjacentBinding(desc.m_multisampleSource))
{
attachment->m_multisampleSource = source;
}
if (const PassAttachmentBinding* source = FindAdjacentBinding(desc.m_arraySizeSource))
{
attachment->m_arraySizeSource = source;
}
attachment->m_ownerPass = this;
return attachment;
}
template<typename AttachmentDescType>
void Pass::OverrideOrAddAttachment(const AttachmentDescType& desc)
{
bool overrideAttachment = false;
// Search existing attachments
for (size_t i = 0; i < m_ownedAttachments.size(); ++i)
{
// If we find one with the same name
if (m_ownedAttachments[i]->m_name == desc.m_name)
{
// Override it
m_ownedAttachments[i] = CreateAttachmentFromDesc(desc);
overrideAttachment = true;
break;
}
}
// If we didn't override any attachments
if (!overrideAttachment)
{
// Create a new one
m_ownedAttachments.emplace_back(CreateAttachmentFromDesc(desc));
}
}
void Pass::SetupInputsFromRequest()
{
if (m_flags.m_createdByPassRequest)
{
const uint32_t slotTypeMask = (1 << uint32_t(PassSlotType::Input)) | (1 << uint32_t(PassSlotType::InputOutput));
for (const PassConnection& connection : m_request.m_connections)
{
ProcessConnection(connection, slotTypeMask);
}
}
}
void Pass::SetupOutputsFromRequest()
{
if (m_flags.m_createdByPassRequest)
{
const uint32_t slotTypeMask = (1 << uint32_t(PassSlotType::Output));
for (const PassConnection& connection : m_request.m_connections)
{
ProcessConnection(connection, slotTypeMask);
}
}
}
void Pass::SetupPassDependencies()
{
// Get dependencies declared in the PassRequest
if (m_flags.m_createdByPassRequest)
{
for (const Name& passName : m_request.m_executeAfterPasses)
{
Ptr<Pass> executeAfterPass = FindAdjacentPass(passName);
if (executeAfterPass != nullptr)
{
m_executeAfterPasses.push_back(executeAfterPass.get());
}
}
for (const Name& passName : m_request.m_executeBeforePasses)
{
Ptr<Pass> executeBeforePass = FindAdjacentPass(passName);
if (executeBeforePass != nullptr)
{
m_executeBeforePasses.push_back(executeBeforePass.get());
}
}
}
// Inherit dependencies from ParentPass
if (m_parent)
{
for (Pass* pass : m_parent->m_executeAfterPasses)
{
m_executeAfterPasses.push_back(pass);
}
for (Pass* pass : m_parent->m_executeBeforePasses)
{
m_executeBeforePasses.push_back(pass);
}
}
}
void Pass::SetupInputsFromTemplate()
{
if (m_template)
{
const uint32_t slotTypeMask = (1 << uint32_t(PassSlotType::Input)) | (1 << uint32_t(PassSlotType::InputOutput));
for (const PassConnection& outputConnection : m_template->m_connections)
{
ProcessConnection(outputConnection, slotTypeMask);
}
}
}
void Pass::SetupOutputsFromTemplate()
{
if (m_template)
{
const uint32_t slotTypeMask = (1 << uint32_t(PassSlotType::Output));
for (const PassConnection& outputConnection : m_template->m_connections)
{
ProcessConnection(outputConnection, slotTypeMask);
}
for (const PassFallbackConnection& fallbackConnection : m_template->m_fallbackConnections)
{
ProcessFallbackConnection(fallbackConnection);
}
}
}
void Pass::CreateAttachmentsFromTemplate()
{
if (m_template)
{
// Create image attachments
for (const PassImageAttachmentDesc& desc : m_template->m_imageAttachments)
{
m_ownedAttachments.emplace_back(CreateAttachmentFromDesc(desc));
}
// Create buffer attachments
for (const PassBufferAttachmentDesc& desc : m_template->m_bufferAttachments)
{
m_ownedAttachments.emplace_back(CreateAttachmentFromDesc(desc));
}
}
}
void Pass::CreateAttachmentsFromRequest()
{
if (m_flags.m_createdByPassRequest)
{
// Create image attachments
for (const PassImageAttachmentDesc& desc : m_request.m_imageAttachmentOverrides)
{
OverrideOrAddAttachment(desc);
}
// Create buffer attachments
for (const PassBufferAttachmentDesc& desc : m_request.m_bufferAttachmentOverrides)
{
OverrideOrAddAttachment(desc);
}
}
}
// --- Attachment and Binding related functions ---
void Pass::StoreImportedAttachmentReferences()
{
m_importedAttachmentStore.clear();
for (const Ptr<PassAttachment>& attachment : m_ownedAttachments)
{
if (attachment->m_lifetime == RHI::AttachmentLifetimeType::Imported)
{
m_importedAttachmentStore.push_back(attachment);
}
}
}
void Pass::CreateTransientAttachments(RHI::FrameGraphAttachmentInterface attachmentDatabase)
{
for (const Ptr<PassAttachment>& attachment : m_ownedAttachments)
{
if (attachment->m_lifetime == RHI::AttachmentLifetimeType::Transient)
{
switch (attachment->m_descriptor.m_type)
{
case RHI::AttachmentType::Image:
attachmentDatabase.CreateTransientImage(attachment->GetTransientImageDescriptor());
break;
case RHI::AttachmentType::Buffer:
attachmentDatabase.CreateTransientBuffer(attachment->GetTransientBufferDescriptor());
break;
default:
AZ_RPI_PASS_ASSERT(false, "Error, transient attachment is neither an image nor a buffer!");
break;
}
}
}
}
void Pass::ImportAttachments(RHI::FrameGraphAttachmentInterface attachmentDatabase)
{
for (const Ptr<PassAttachment>& attachment : m_ownedAttachments)
{
if (attachment->m_lifetime == RHI::AttachmentLifetimeType::Imported)
{
// make sure to only import the resource one time
RHI::AttachmentId attachmentId = attachment->GetAttachmentId();
const RHI::FrameAttachment* currentAttachment = attachmentDatabase.FindAttachment(attachmentId);
if (azrtti_istypeof<Image>(attachment->m_importedResource.get()))
{
Image* image = static_cast<Image*>(attachment->m_importedResource.get());
if (currentAttachment == nullptr)
{
attachmentDatabase.ImportImage(attachmentId, image->GetRHIImage());
}
else
{
AZ_Assert(currentAttachment->GetResource() == image->GetRHIImage(),
"Importing image attachment named \"%s\" but a different attachment with the "
"same name already exists in the database.\n", attachmentId.GetCStr());
}
}
else if (azrtti_istypeof<Buffer>(attachment->m_importedResource.get()))
{
Buffer* buffer = static_cast<Buffer*>(attachment->m_importedResource.get());
if (currentAttachment == nullptr)
{
attachmentDatabase.ImportBuffer(attachmentId, buffer->GetRHIBuffer());
}
else
{
AZ_Assert(currentAttachment->GetResource() == buffer->GetRHIBuffer(),
"Importing buffer attachment named \"%s\" but a different attachment with the "
"same name already exists in the database.\n", attachmentId.GetCStr());
}
}
else
{
AZ_RPI_PASS_ERROR(false, "Can't import unknown resource type");
}
}
}
}
void Pass::UpdateAttachmentUsageIndices()
{
// We want to find attachments that are used more than once by the same pass
// An example of this could be reading from and writing to different mips of the same texture
// Loop over all attachments bound to this pass
size_t size = m_attachmentBindings.size();
for (size_t i = 0; i < size; ++i)
{
PassAttachmentBinding& binding01 = m_attachmentBindings[i];
// For the outer loop, only consider bindings which are the
// first occurrence of their given attachment in the pass
if (binding01.m_attachmentUsageIndex != 0)
{
continue;
}
// Loop over all subsequent bindings in the pass
uint8_t duplicateCount = 0;
for (size_t j = i + 1; j < size; ++j)
{
PassAttachmentBinding& binding02 = m_attachmentBindings[j];
// Bindings are considered having the same attachment if they are connected to the same binding...
bool haveSameConnection = binding01.m_connectedBinding == binding02.m_connectedBinding;
haveSameConnection = haveSameConnection && binding01.m_connectedBinding != nullptr;
// ... Or if they point to the same attachment
bool isSameAttachment = binding01.m_attachment == binding02.m_attachment;
isSameAttachment = isSameAttachment && binding01.m_attachment != nullptr;
// If binding 01 and binding 02 have the same attachment, update the attachment usage index on binding 02
if (haveSameConnection || isSameAttachment)
{
binding02.m_attachmentUsageIndex = ++duplicateCount;
}
}
}
}
void Pass::UpdateOwnedAttachments()
{
// Update the output attachments to coincide with their source attachments (if specified)
// This involves getting the format and calculating the size from the source attachment
for (Ptr<PassAttachment>& attachment: m_ownedAttachments)
{
attachment->Update();
}
}
void Pass::UpdateConnectedBinding(PassAttachmentBinding& binding)
{
Ptr<PassAttachment> targetAttachment = nullptr;
if (m_state != PassState::Building && !IsEnabled() && binding.m_slotType == PassSlotType::Output && binding.m_fallbackBinding)
{
targetAttachment = binding.m_fallbackBinding->m_attachment;
}
else if(binding.m_connectedBinding)
{
targetAttachment = binding.m_connectedBinding->m_attachment;
}
else if (binding.m_originalAttachment != nullptr)
{
targetAttachment = binding.m_originalAttachment;
}
if (targetAttachment == nullptr)
{
return;
}
// Check whether the template's slot allows this attachment
if (m_template && !m_template->AttachmentFitsSlot(targetAttachment->m_descriptor, binding.m_name))
{
AZ_RPI_PASS_ERROR(false, "Pass::UpdateConnectedBinding - Attachment [%s] did not match the filters of input slot [%s] on pass [%s].",
targetAttachment->m_name.GetCStr(),
binding.m_name.GetCStr(),
m_path.GetCStr());
binding.m_attachment = nullptr;
return;
}
binding.SetAttachment(targetAttachment);
}
void Pass::UpdateConnectedBindings()
{
// Depending on whether a pass is enabled or not, it may switch it's bindings to become a pass-through
// For this reason we update connecting bindings on a per-frame basis
for (PassAttachmentBinding& binding : m_attachmentBindings)
{
UpdateConnectedBinding(binding);
}
}
void Pass::UpdateConnectedInputBindings()
{
for (uint8_t idx : m_inputBindingIndices)
{
UpdateConnectedBinding(m_attachmentBindings[idx]);
}
for (uint8_t idx : m_inputOutputBindingIndices)
{
UpdateConnectedBinding(m_attachmentBindings[idx]);
}
}
void Pass::UpdateConnectedOutputBindings()
{
for (uint8_t idx : m_outputBindingIndices)
{
UpdateConnectedBinding(m_attachmentBindings[idx]);
}
}
// --- Queuing functions with PassSystem ---
void Pass::QueueForBuildAndInitialization()
{
// Don't queue if we're currently building. Don't queue if we're already queued for Build or Removal
if (m_state != PassState::Building &&
m_queueState != PassQueueState::QueuedForBuildAndInitialization &&
m_queueState != PassQueueState::QueuedForRemoval)
{
// NOTE: We only queue for Build here, the queue for Initialization happens at the end of Pass::Build
// (doing it this way is an optimization to minimize the number of passes queued for initialization,
// as many passes will be initialized by their parent passes and thus don't need to be queued)
PassSystemInterface::Get()->QueueForBuild(this);
m_queueState = PassQueueState::QueuedForBuildAndInitialization;
// Transition state
// If we are Rendering, the state will transition [Rendering -> Queued] in Pass::FrameEnd
if (m_state != PassState::Rendering)
{
m_state = PassState::Queued;
}
}
}
void Pass::QueueForInitialization()
{
// Only queue if the pass is not in any queue. Don't queue if we're currently initializing.
if (m_queueState == PassQueueState::NoQueue && m_state != PassState::Initializing)
{
PassSystemInterface::Get()->QueueForInitialization(this);
m_queueState = PassQueueState::QueuedForInitialization;
// Transition state
// If we are Rendering, the state will transition [Rendering -> Queued] in Pass::FrameEnd
// If the state is Built, preserve the state since [Built -> Initializing] is a valid transition
// Preserving PassState::Built lets the pass ignore subsequent build calls in the same frame
if (m_state != PassState::Rendering && m_state != PassState::Built)
{
m_state = PassState::Queued;
}
}
}
void Pass::QueueForRemoval()
{
// Skip only if we're already queued for removal, otherwise proceed.
// QueuedForRemoval overrides QueuedForBuildAndInitialization and QueuedForInitialization.
if (m_queueState != PassQueueState::QueuedForRemoval)
{
PassSystemInterface::Get()->QueueForRemoval(this);
m_queueState = PassQueueState::QueuedForRemoval;
// Transition state
// If we are Rendering, the state will transition [Rendering -> Queued] in Pass::FrameEnd
if (m_state != PassState::Rendering)
{
m_state = PassState::Queued;
}
}
}
// --- Pass behavior functions ---
void Pass::Reset()
{
// Ensure we're in a valid state to reset. This ensures the pass won't be reset multiple times in the same frame.
bool execute = (m_state == PassState::Idle);
execute = execute || (m_state == PassState::Queued && m_queueState == PassQueueState::QueuedForBuildAndInitialization);
execute = execute || (m_state == PassState::Queued && m_queueState == PassQueueState::QueuedForInitialization);
if (!execute)
{
return;
}
m_state = PassState::Resetting;
// Store references to imported attachments to underlying images and buffers aren't deleted during attachment building
StoreImportedAttachmentReferences();
// Clear lists
m_inputBindingIndices.clear();
m_inputOutputBindingIndices.clear();
m_outputBindingIndices.clear();
m_attachmentBindings.clear();
m_ownedAttachments.clear();
m_executeAfterPasses.clear();
m_executeBeforePasses.clear();
ResetInternal();
m_state = PassState::Reset;
}
void Pass::Build(bool calledFromPassSystem)
{
AZ_RPI_BREAK_ON_TARGET_PASS;
// Ensure we're in a valid state to build. This ensures the pass won't be built multiple times in the same frame.
bool execute = (m_state == PassState::Reset);
if (!execute)
{
return;
}
m_state = PassState::Building;
// Bindings, inputs and attachments
CreateBindingsFromTemplate();
SetupInputsFromRequest();
SetupPassDependencies();
CreateAttachmentsFromTemplate();
CreateAttachmentsFromRequest();
SetupInputsFromTemplate();
// Custom pass behavior
BuildInternal();
// Outputs
SetupOutputsFromTemplate();
SetupOutputsFromRequest();
// Update
UpdateConnectedBindings();
UpdateOwnedAttachments();
UpdateAttachmentUsageIndices();
m_state = PassState::Built;
m_queueState = PassQueueState::NoQueue;
// If this pass's Build() wasn't called from the Pass System, then it was called by it's parent pass
// In which case we don't need to queue for initialization because the parent will already be queued
if (calledFromPassSystem)
{
// Queue for Initialization
QueueForInitialization();
}
}
void Pass::Initialize()
{
AZ_RPI_BREAK_ON_TARGET_PASS;
// Ensure we're in a valid state to initialize. This ensures the pass won't be initialized multiple times in the same frame.
bool execute = (m_state == PassState::Idle || m_state == PassState::Built);
execute = execute || (m_state == PassState::Queued && m_queueState == PassQueueState::QueuedForInitialization);
if (!execute)
{
return;
}
m_state = PassState::Initializing;
m_queueState = PassQueueState::NoQueue;
InitializeInternal();
// Need to recreate the dest attachment because the source attachment might be changed
if (!m_attachmentCopy.expired())
{
m_attachmentCopy.lock()->InvalidateDestImage();
}
m_state = PassState::Initialized;
}
void Pass::OnInitializationFinished()
{
m_flags.m_alreadyCreatedChildren = false;
m_importedAttachmentStore.clear();
OnInitializationFinishedInternal();
m_state = PassState::Idle;
}
void Pass::Validate(PassValidationResults& validationResults)
{
if (PassValidation::IsEnabled())
{
// Log passes with missing input
for (uint8_t idx : m_inputBindingIndices)
{
if (!m_attachmentBindings[idx].m_attachment)
{
validationResults.m_passesWithMissingInputs.push_back(this);
break;
}
}
// Log passes with missing input/output
for (uint8_t idx : m_inputOutputBindingIndices)
{
if (!m_attachmentBindings[idx].m_attachment)
{
validationResults.m_passesWithMissingInputOutputs.push_back(this);
break;
}
}
// Log passes with missing output (note that missing output connections are not considered an error)
for (uint8_t idx : m_outputBindingIndices)
{
if (!m_attachmentBindings[idx].m_attachment)
{
validationResults.m_passesWithMissingOutputs.push_back(this);
break;
}
}
if (m_errorMessages.size() > 0)
{
validationResults.m_passesWithErrors.push_back(this);
}
if (m_warningMessages.size() > 0)
{
validationResults.m_passesWithWarnings.push_back(this);
}
}
}
void Pass::FrameBegin(FramePrepareParams params)
{
AZ_RPI_BREAK_ON_TARGET_PASS;
if (!IsEnabled())
{
UpdateConnectedBindings();
return;
}
AZ_Assert(m_state == PassState::Idle, "Pass::FrameBegin - Pass [%s] is attempting to render, but is not in the Idle state.", m_path.GetCStr());
m_state = PassState::Rendering;
UpdateConnectedInputBindings();
UpdateOwnedAttachments();
CreateTransientAttachments(params.m_frameGraphBuilder->GetAttachmentDatabase());
ImportAttachments(params.m_frameGraphBuilder->GetAttachmentDatabase());
// readback attachment with input state
UpdateReadbackAttachment(params, true);
// FrameBeginInternal needs to be the last function be called in FrameBegin because its implementation expects
// all the attachments are imported to database (for example, ImageAttachmentPreview)
FrameBeginInternal(params);
// readback attachment with output state
UpdateReadbackAttachment(params, false);
// update attachment copy for preview
UpdateAttachmentCopy(params);
UpdateConnectedOutputBindings();
}
void Pass::FrameEnd()
{
if (m_state == PassState::Rendering)
{
FrameEndInternal();
m_state = (m_queueState == PassQueueState::NoQueue) ? PassState::Idle : PassState::Queued;
}
}
// --- RenderPipeline, PipelineViewTag and DrawListTag ---
bool Pass::HasDrawListTag() const
{
return m_flags.m_hasDrawListTag;
}
RHI::DrawListTag Pass::GetDrawListTag() const
{
static RHI::DrawListTag invalidTag;
return invalidTag;
}
bool Pass::HasPipelineViewTag() const
{
return m_flags.m_hasPipelineViewTag;
}
const PipelineViewTag& Pass::GetPipelineViewTag() const
{
static PipelineViewTag viewTag;
return viewTag;
}
void Pass::SetRenderPipeline(RenderPipeline* pipeline)
{
m_pipeline = pipeline;
}
RenderPipeline* Pass::GetRenderPipeline() const
{
return m_pipeline;
}
void Pass::ManualPipelineBuildAndInitialize()
{
Build();
Initialize();
OnInitializationFinished();
}
Scene* Pass::GetScene() const
{
if (m_pipeline)
{
return m_pipeline->GetScene();
}
return nullptr;
}
void Pass::GetViewDrawListInfo(RHI::DrawListMask& outDrawListMask, PassesByDrawList& outPassesByDrawList, const PipelineViewTag& viewTag) const
{
// NOTE: we always collect the draw list mask regardless if the pass enabled or not. The reason is we want to keep the view information
// even when pass is disabled so it can continue work correctly when re-enable it.
// Only get the DrawListTag if this pass has a DrawListTag and it's PipelineViewId matches
if (HasPipelineViewTag() && HasDrawListTag() && GetPipelineViewTag() == viewTag)
{
RHI::DrawListTag drawListTag = GetDrawListTag();
if (drawListTag.IsValid() && outPassesByDrawList.find(drawListTag) == outPassesByDrawList.end())
{
outPassesByDrawList[drawListTag] = this;
outDrawListMask.set(drawListTag.GetIndex());
}
}
}
void Pass::GetPipelineViewTags(SortedPipelineViewTags& outTags) const
{
if (HasPipelineViewTag())
{
outTags.insert(GetPipelineViewTag());
}
}
void Pass::SortDrawList(RHI::DrawList& drawList) const
{
RHI::SortDrawList(drawList, m_drawListSortType);
}
// --- Debug & Validation functions ---
bool PassValidationResults::IsValid()
{
if (PassValidation::IsEnabled())
{
// Pass validation fail if there are any passes with build errors or missing inputs (or input/outputs)
return (m_passesWithErrors.size() == 0) && (m_passesWithMissingInputs.size() == 0) && (m_passesWithMissingInputOutputs.size() == 0);
}
else
{
return true;
}
}
TimestampResult Pass::GetLatestTimestampResult() const
{
return GetTimestampResultInternal();
}
PipelineStatisticsResult Pass::GetLatestPipelineStatisticsResult() const
{
return GetPipelineStatisticsResultInternal();
}
TimestampResult Pass::GetTimestampResultInternal() const
{
return TimestampResult();
}
PipelineStatisticsResult Pass::GetPipelineStatisticsResultInternal() const
{
return PipelineStatisticsResult();
}
void Pass::SetTimestampQueryEnabled(bool enable)
{
m_flags.m_timestampQueryEnabled = enable;
}
void Pass::SetPipelineStatisticsQueryEnabled(bool enable)
{
m_flags.m_pipelineStatisticsQueryEnabled = enable;
}
bool Pass::ReadbackAttachment(AZStd::shared_ptr<AttachmentReadback> readback, const Name& slotName, PassAttachmentReadbackOption option)
{
// Return false if it's already readback
if (m_attachmentReadback)
{
AZ_Warning("Pass", false, "ReadbackAttachment: skip readback pass [%s] slot [%s]because there is an another active readback", m_name.GetCStr(), slotName.GetCStr());
return false;
}
uint32_t bindingIndex = 0;
for (auto& binding : m_attachmentBindings)
{
if (slotName == binding.m_name)
{
RHI::AttachmentType type = binding.m_attachment->GetAttachmentType();
if (type == RHI::AttachmentType::Buffer || type == RHI::AttachmentType::Image)
{
RHI::AttachmentId attachmentId = binding.m_attachment->GetAttachmentId();
// Append slot index and pass name so the read back's name won't be same as the attachment used in other passes.
AZStd::string readbackName = AZStd::string::format("%s_%d_%s", attachmentId.GetCStr(),
bindingIndex, GetName().GetCStr());
if (readback->ReadPassAttachment(binding.m_attachment.get(), AZ::Name(readbackName)))
{
m_readbackOption = PassAttachmentReadbackOption::Output;
// The m_readbackOption is only meaningful if the attachment is used for InputOutput.
if (binding.m_slotType == PassSlotType::InputOutput)
{
m_readbackOption = option;
}
m_attachmentReadback = readback;
return true;
}
return false;
}
}
bindingIndex++;
}
AZ_Warning("Pass", false, "ReadbackAttachment: failed to find slot [%s] from pass [%s]", slotName.GetCStr(), m_name.GetCStr());
return false;
}
void Pass::UpdateReadbackAttachment(FramePrepareParams params, bool beforeAddScopes)
{
if (beforeAddScopes == (m_readbackOption == PassAttachmentReadbackOption::Input) && m_attachmentReadback)
{
// Read the attachment for one frame. The reference can be released afterwards
m_attachmentReadback->FrameBegin(params);
m_attachmentReadback = nullptr;
}
}
void Pass::UpdateAttachmentCopy(FramePrepareParams params)
{
if (!m_attachmentCopy.expired())
{
m_attachmentCopy.lock()->FrameBegin(params);
}
}
bool Pass::IsTimestampQueryEnabled() const
{
return m_flags.m_timestampQueryEnabled;
}
bool Pass::IsPipelineStatisticsQueryEnabled() const
{
return m_flags.m_pipelineStatisticsQueryEnabled;
}
void Pass::PrintIndent(AZStd::string& stringOutput, uint32_t indent) const
{
if (PassValidation::IsEnabled())
{
for (uint32_t i = 0; i < indent; ++i)
{
stringOutput += " ";
}
}
}
void Pass::PrintPassName(AZStd::string& stringOutput, uint32_t indent) const
{
if (PassValidation::IsEnabled())
{
stringOutput += "\n";
PrintIndent(stringOutput, indent);
stringOutput += "- ";
stringOutput += m_name.GetStringView();
stringOutput += "\n";
}
}
void Pass::PrintErrors() const
{
if (PassValidation::IsEnabled())
{
PrintMessages(m_errorMessages);
}
}
void Pass::PrintWarnings() const
{
if (PassValidation::IsEnabled())
{
PrintMessages(m_warningMessages);
}
}
void Pass::PrintMessages(const AZStd::vector<AZStd::string>& messages) const
{
if (PassValidation::IsEnabled())
{
AZStd::string stringOutput;
PrintPassName(stringOutput);
for (const AZStd::string& message : messages)
{
PrintIndent(stringOutput, 1);
stringOutput += message;
stringOutput += "\n";
}
AZ_Printf("PassSystem", stringOutput.c_str());
}
}
void Pass::PrintBindingsWithoutAttachments(uint32_t slotTypeMask) const
{
if (PassValidation::IsEnabled())
{
AZStd::string stringOutput;
PrintPassName(stringOutput);
for (const PassAttachmentBinding& binding : m_attachmentBindings)
{
uint32_t bindingMask = (1 << uint32_t(binding.m_slotType));
if ((bindingMask & slotTypeMask) && (binding.m_attachment == nullptr))
{
// Print the name of the slot
PrintIndent(stringOutput, 1);
stringOutput += binding.m_name.GetStringView();
stringOutput += " has no valid attachment\n";
}
}
AZ_Printf("PassSystem", stringOutput.c_str());
}
}
void Pass::DebugPrintBinding(AZStd::string& stringOutput, const PassAttachmentBinding& binding) const
{
if (PassValidation::IsEnabled())
{
// Print the name of the slot
stringOutput += binding.m_name.GetStringView();
// Print the attachment type and size, for example:
// (Image, 1920, 1080) or (Buffer, 4096 bytes)
if (binding.m_attachment != nullptr)
{
stringOutput += " (";
// Images will have the format: AttachmentName (Image, 1920, 1080)
if (binding.m_attachment->m_descriptor.m_type == RHI::AttachmentType::Image)
{
stringOutput += "Image";
RHI::ImageDescriptor& desc = binding.m_attachment->m_descriptor.m_image;
uint32_t dimensions = static_cast<uint32_t>(desc.m_dimension);
for (uint32_t i = 0; i < dimensions; ++i)
{
stringOutput += ", ";
stringOutput += AZStd::to_string(desc.m_size[i]);
}
if (desc.m_multisampleState.m_samples > 1)
{
if (desc.m_multisampleState.m_customPositionsCount > 0)
{
stringOutput += ", Custom_MSAA_";
}
else
{
stringOutput += ", MSAA_";
}
stringOutput += AZStd::to_string(desc.m_multisampleState.m_samples);
stringOutput += "x";
}
}
// Buffers will have the format: AttachmentName (Buffer, 4092 bytes)
else if (binding.m_attachment->m_descriptor.m_type == RHI::AttachmentType::Buffer)
{
stringOutput += "Buffer, ";
stringOutput += AZStd::to_string(binding.m_attachment->m_descriptor.m_buffer.m_byteCount);
stringOutput += " bytes";
}
stringOutput += ")";
}
}
}
void Pass::DebugPrintBindingAndConnection(AZStd::string& stringOutput, uint8_t bindingIndex) const
{
if (PassValidation::IsEnabled())
{
PrintIndent(stringOutput, m_treeDepth + 2);
// Print the Attachment
const PassAttachmentBinding& binding = m_attachmentBindings[bindingIndex];
DebugPrintBinding(stringOutput, binding);
// Print the Attachment it's connected to
if (binding.m_connectedBinding != nullptr)
{
stringOutput += " connected to ";
DebugPrintBinding(stringOutput, *binding.m_connectedBinding);
}
stringOutput += "\n";
}
}
void Pass::DebugPrint() const
{
if (PassValidation::IsEnabled())
{
AZStd::string stringOutput;
PrintPassName(stringOutput, m_treeDepth);
// Print inputs
if (m_inputBindingIndices.size() > 0)
{
PrintIndent(stringOutput, m_treeDepth + 1);
stringOutput += "Inputs:\n";
for (uint8_t inputIndex : m_inputBindingIndices)
{
DebugPrintBindingAndConnection(stringOutput, inputIndex);
}
}
// Print input/outputs
if (m_inputOutputBindingIndices.size() > 0)
{
PrintIndent(stringOutput, m_treeDepth + 1);
stringOutput += "Input/Outputs:\n";
for (uint8_t inputIndex : m_inputOutputBindingIndices)
{
DebugPrintBindingAndConnection(stringOutput, inputIndex);
}
}
// Print outputs
if (m_outputBindingIndices.size() > 0)
{
PrintIndent(stringOutput, m_treeDepth + 1);
stringOutput += "Outputs:\n";
for (uint8_t inputIndex : m_outputBindingIndices)
{
DebugPrintBindingAndConnection(stringOutput, inputIndex);
}
}
AZ_Printf("PassSystem", stringOutput.c_str());
}
}
void PassValidationResults::PrintValidationIfError()
{
if (PassValidation::IsEnabled())
{
if (!IsValid())
{
AZ_Printf("PassSystem", "\n--- PASS VALIDATION FAILURE ---"
"\n--Critical Errors--\n");
AZ_Printf("PassSystem", "\nThere are %d passes with errors:\n", m_passesWithErrors.size());
for (Pass* pass : m_passesWithErrors)
{
pass->PrintErrors();
}
AZ_Printf("PassSystem", "\nThere are %d passes with missing Inputs:\n", m_passesWithMissingInputs.size());
for (Pass* pass : m_passesWithMissingInputs)
{
pass->PrintBindingsWithoutAttachments(uint32_t(PassSlotMask::Input));
}
AZ_Printf("PassSystem", "\nThere are %d passes with missing Inputs/Outputs:\n", m_passesWithMissingInputOutputs.size());
for (Pass* pass : m_passesWithMissingInputOutputs)
{
pass->PrintBindingsWithoutAttachments(uint32_t(PassSlotMask::InputOutput));
}
AZ_Printf("PassSystem", "\n--Non-Critical Errors/Warnings--\n");
AZ_Printf("PassSystem", "\nThere are %d passes with warnings:\n", m_passesWithWarnings.size());
for (Pass* pass : m_passesWithWarnings)
{
pass->PrintWarnings();
}
}
}
}
} // namespace RPI
} // namespace AZ