Merge pull request #2584 from aws-lumberyard-dev/Atom/antonmic/SrgDebugImprovements

Shader resource group debug improvements
This commit is contained in:
antonmic
2021-08-11 10:37:36 -07:00
committed by GitHub
17 changed files with 307 additions and 47 deletions
@@ -82,6 +82,10 @@ namespace AZ
//! If validation is disabled, true is always returned.
bool ValidateAccess(ShaderInputConstantIndex inputIndex) const;
//! Prints to the console the shader input names specified by input list of indices
//! Will ignore any indices outside of the inputs array bounds
void DebugPrintNames(AZStd::array_view<ShaderInputConstantIndex> constantList) const;
protected:
ConstantsLayout() = default;
@@ -95,7 +99,6 @@ namespace AZ
AZStd::vector<ShaderInputConstantDescriptor> m_inputs;
IdReflectionMapForConstants m_idReflection;
AZStd::vector<Interval> m_intervals;
uint32_t m_sizeInBytes = 0;
HashValue64 m_hash = InvalidHash;
};
@@ -69,6 +69,9 @@ namespace AZ
/// Return the number of entries
size_t Size() const;
// Returns true if size is zero
bool IsEmpty() const;
class NameIdReflectionMapSerializationEvents
: public SerializeContext::IEventHandler
{
@@ -169,6 +172,12 @@ namespace AZ
return m_reflectionMap.size();
}
template <typename IndexType>
bool NameIdReflectionMap<IndexType>::IsEmpty() const
{
return Size() == 0;
}
template <typename IndexType>
void NameIdReflectionMap<IndexType>::Sort()
{
@@ -85,6 +85,14 @@ namespace AZ
//! Returns the constants layout.
const ConstantsLayout* GetLayout() const;
//! Returns whether other constant data and this have the same value at the specified shader input index
bool ConstantIsEqual(const ConstantsData& other, ShaderInputConstantIndex inputIndex) const;
//! Performs a diff between this and input constant data and returns a list of all the shader input indices
//! for which the constants are not the same between the two. If one of the two has more constants than the
//! other, these additional constants will be added to the end of the returned list.
AZStd::vector<ShaderInputConstantIndex> GetIndicesOfDifferingConstants(const ConstantsData& other) const;
private:
enum class ValidateConstantAccessExpect : uint32_t
{
@@ -173,6 +173,9 @@ namespace AZ
//! Different platforms might follow different packing rules for the internally-managed SRG constant buffer.
AZStd::array_view<uint8_t> GetConstantData() const;
//! Returns the underlying ConstantsData struct
const ConstantsData& GetConstantsData() const;
//! Returns the shader resource layout for this group.
const ShaderResourceGroupLayout* GetLayout() const;
@@ -0,0 +1,31 @@
/*
* 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
*
*/
#pragma once
namespace AZ
{
namespace RHI
{
class ConstantsData;
struct DrawItem;
class ShaderResourceGroup;
/// Given a ShaderResourceGroup and a reference ConstantsData input, this function will fetch the ConstantsData on the SRG and compare it
/// to the reference ConstantsData. It will print the names of any constants that are different between the two.
/// The parameter updateReferenceData can be used to set the reference data to the SRG's constant data after the comparison. This is
/// useful for keeping track of differences in between calls to the function, such as between frames.
void PrintConstantDataDiff(const ShaderResourceGroup& shaderResourceGroup, ConstantsData& referenceData, bool updateReferenceData = false);
/// Given a DrawItem, an SRG binding slot on that draw item and a reference ConstantsData input, this function will fetch the ConstantsData
/// from the draw item's SRG at the binding slot and compare it to the reference ConstantsData. It will print the names of any constants
/// that are different between the two.
/// The parameter updateReferenceData can be used to set the reference data to the draw item's constant data after the comparison. This is
/// useful for keeping track of differences in between calls to the function, such as between frames.
void PrintConstantDataDiff(const DrawItem& drawItem, ConstantsData& referenceData, uint32_t srgBindingSlot, bool updateReferenceData = false);
}
}
@@ -18,10 +18,9 @@ namespace AZ
if (SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context))
{
serializeContext->Class<ConstantsLayout>()
->Version(0)
->Version(1) // Version 1: Adding debug helper functions to Shader Resource Groups
->Field("m_inputs", &ConstantsLayout::m_inputs)
->Field("m_idReflection", &ConstantsLayout::m_idReflection)
->Field("m_intervals", &ConstantsLayout::m_intervals)
->Field("m_sizeInBytes", &ConstantsLayout::m_sizeInBytes)
->Field("m_hash", &ConstantsLayout::m_hash);
}
@@ -43,7 +42,6 @@ namespace AZ
{
m_inputs.clear();
m_idReflection.Clear();
m_intervals.clear();
m_sizeInBytes = 0;
m_hash = InvalidHash;
}
@@ -67,11 +65,8 @@ namespace AZ
return false;
}
// The constant data size is the maximum offset + size from the start of the struct.
constantDataSize = AZStd::max(constantDataSize, constantDescriptor.m_constantByteOffset + constantDescriptor.m_constantByteCount);
// Add the [min, max) interval for the inline constant.
m_intervals.emplace_back(constantDescriptor.m_constantByteOffset, constantDescriptor.m_constantByteOffset + constantDescriptor.m_constantByteCount);
uint32_t end = constantDescriptor.m_constantByteOffset + constantDescriptor.m_constantByteCount;
constantDataSize = AZStd::max(constantDataSize, end);
++constantInputIndex;
m_hash = TypeHash64(constantDescriptor.GetHash(), m_hash);
@@ -100,7 +95,10 @@ namespace AZ
Interval ConstantsLayout::GetInterval(ShaderInputConstantIndex inputIndex) const
{
return m_intervals[inputIndex.GetIndex()];
const ShaderInputConstantDescriptor& desc = GetShaderInput(inputIndex);
uint32_t start = desc.m_constantByteOffset;
uint32_t end = start + desc.m_constantByteCount;
return Interval(start, end);
}
const ShaderInputConstantDescriptor& ConstantsLayout::GetShaderInput(ShaderInputConstantIndex inputIndex) const
@@ -139,8 +137,8 @@ namespace AZ
{
if (!m_sizeInBytes)
{
AZ_Assert(m_intervals.empty(), "Constants size is not valid.");
return m_intervals.empty();
AZ_Assert(m_idReflection.IsEmpty(), "Constants size is not valid.");
return m_idReflection.IsEmpty();
}
}
@@ -149,5 +147,20 @@ namespace AZ
return true;
}
void ConstantsLayout::DebugPrintNames(AZStd::array_view<ShaderInputConstantIndex> constantList) const
{
AZStd::string output;
for (const ShaderInputConstantIndex& constantIdx : constantList)
{
if (constantIdx.GetIndex() < m_inputs.size())
{
output += m_inputs[constantIdx.GetIndex()].m_name.GetCStr();
output += " - ";
}
}
AZ_Printf("RHI", output.c_str());
}
}
}
@@ -405,5 +405,71 @@ namespace AZ
AZ_Assert(m_layout, "Constants layout is null");
return m_layout.get();
}
bool ConstantsData::ConstantIsEqual(const ConstantsData& other, ShaderInputConstantIndex inputIndex) const
{
AZStd::array_view<uint8_t> myConstant = GetConstantRaw(inputIndex);
AZStd::array_view<uint8_t> otherConstant = other.GetConstantRaw(inputIndex);
// If they point to the same data, they are equal
if (myConstant == otherConstant)
{
return true;
}
// If they point to data of different size, they are not equal
if (myConstant.size() != otherConstant.size())
{
return false;
}
// If they point to differing data of same size, compare the data
// Note: due to small size of data this loop will be faster than a mem compare
for(uint32_t i = 0; i < myConstant.size(); ++i)
{
if (myConstant[i] != otherConstant[i])
{
return false;
}
}
// Arrays point to different locations in memory but all bytes match, return true
return true;
}
AZStd::vector<ShaderInputConstantIndex> ConstantsData::GetIndicesOfDifferingConstants(const ConstantsData& other) const
{
AZStd::vector<ShaderInputConstantIndex> differingIndices;
if (m_layout == nullptr || other.m_layout == nullptr)
{
return differingIndices;
}
AZStd::array_view<ShaderInputConstantDescriptor> myShaderInputs = m_layout->GetShaderInputList();
AZStd::array_view<ShaderInputConstantDescriptor> otherShaderInputs = other.m_layout->GetShaderInputList();
size_t minSize = AZStd::min(myShaderInputs.size(), otherShaderInputs.size());
size_t maxSize = AZStd::max(myShaderInputs.size(), otherShaderInputs.size());
for (size_t idx = 0; idx < minSize; ++idx)
{
const ShaderInputConstantIndex inputIndex(idx);
if (!ConstantIsEqual(other, inputIndex))
{
differingIndices.push_back(inputIndex);
}
}
// If sizes are different, add difference at the end
for (size_t idx = minSize; idx < maxSize; ++idx)
{
const ShaderInputConstantIndex inputIndex(idx);
differingIndices.push_back(inputIndex);
}
return differingIndices;
}
}
}
@@ -335,5 +335,10 @@ namespace AZ
return m_constantsData.GetConstantData();
}
const ConstantsData& ShaderResourceGroupData::GetConstantsData() const
{
return m_constantsData;
}
} // namespace RHI
} // namespace AZ
@@ -0,0 +1,59 @@
/*
* 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/ConstantsData.h>
#include <Atom/RHI/DrawItem.h>
#include <Atom/RHI/ShaderResourceGroup.h>
#include <Atom/RHI/ShaderResourceGroupDebug.h>
namespace AZ
{
namespace RHI
{
void PrintConstantDataDiff(const ShaderResourceGroup& shaderResourceGroup, ConstantsData& referenceData, bool updateReferenceData)
{
const RHI::ConstantsData& currentData = shaderResourceGroup.GetData().GetConstantsData();
AZStd::vector<RHI::ShaderInputConstantIndex> differingIndices = currentData.GetIndicesOfDifferingConstants(referenceData);
if (differingIndices.size() > 0)
{
AZ_Printf("RHI", "Detected different SRG values for the following fields:\n");
if (currentData.GetLayout())
{
currentData.GetLayout()->DebugPrintNames(differingIndices);
}
}
if (updateReferenceData)
{
referenceData = currentData;
}
}
void PrintConstantDataDiff(const DrawItem& drawItem, ConstantsData& referenceData, uint32_t srgBindingSlot, bool updateReferenceData)
{
int srgIndex = -1;
for (uint32_t i = 0; i < drawItem.m_shaderResourceGroupCount; ++i)
{
if (drawItem.m_shaderResourceGroups[i]->GetBindingSlot() == srgBindingSlot)
{
srgIndex = i;
break;
}
}
if (srgIndex != -1)
{
const ShaderResourceGroup& srg = *drawItem.m_shaderResourceGroups[srgIndex];
PrintConstantDataDiff(srg, referenceData, updateReferenceData);
}
}
}
}
@@ -157,10 +157,12 @@ set(FILES
Source/RHI/ScopeAttachment.cpp
Include/Atom/RHI/ShaderResourceGroup.h
Include/Atom/RHI/ShaderResourceGroupData.h
Include/Atom/RHI/ShaderResourceGroupDebug.h
Include/Atom/RHI/ShaderResourceGroupInvalidateRegistry.h
Include/Atom/RHI/ShaderResourceGroupPool.h
Source/RHI/ShaderResourceGroup.cpp
Source/RHI/ShaderResourceGroupData.cpp
Source/RHI/ShaderResourceGroupDebug.cpp
Source/RHI/ShaderResourceGroupInvalidateRegistry.cpp
Source/RHI/ShaderResourceGroupPool.cpp
Include/Atom/RHI/MemoryStatisticsBuilder.h
@@ -75,6 +75,12 @@ namespace AZ
const AZ::Name& GetTargetedPassDebuggingName() const override;
void ConnectEvent(OnReadyLoadTemplatesEvent::Handler& handler) override;
PassSystemState GetState() const override;
SwapChainPass* FindSwapChainPass(AzFramework::NativeWindowHandle windowHandle) const override;
// PassSystemInterface statistics related functions
void IncrementFrameDrawItemCount(u32 numDrawItems) override;
void IncrementFrameRenderPassCount() override;
PassSystemFrameStatistics GetFrameStatistics() override;
// PassSystemInterface factory related functions...
void AddPassCreator(Name className, PassCreator createFunction) override;
@@ -93,7 +99,6 @@ namespace AZ
void RegisterPass(Pass* pass) override;
void UnregisterPass(Pass* pass) override;
AZStd::vector<Pass*> FindPasses(const PassFilter& passFilter) const override;
SwapChainPass* FindSwapChainPass(AzFramework::NativeWindowHandle windowHandle) const override;
private:
// Returns the root of the pass tree hierarchy
@@ -116,6 +121,9 @@ namespace AZ
void QueueForRemoval(Pass* pass) override;
void QueueForInitialization(Pass* pass) override;
// Resets the frame statistic counters
void ResetFrameStatistics();
// Lists for queuing passes for various function calls
// Name of the list reflects the pass function it will call
AZStd::vector< Ptr<Pass> > m_buildPassList;
@@ -141,13 +149,16 @@ namespace AZ
AZ::Name m_targetedPassDebugName;
// Counts the number of passes
int32_t m_passCounter = 0;
u32 m_passCounter = 0;
// Events
OnReadyLoadTemplatesEvent m_loadTemplatesEvent;
// Used to track what phase of execution the pass system is in
PassSystemState m_state = PassSystemState::Unitialized;
// Counters used to gather statistics about the frame
PassSystemFrameStatistics m_frameStatistics;
};
} // namespace RPI
} // namespace AZ
@@ -67,6 +67,14 @@ namespace AZ
FrameEnd,
};
//! Frame counters used for collecting statistics
struct PassSystemFrameStatistics
{
u32 m_numRenderPassesExecuted = 0;
u32 m_totalDrawItemsRendered = 0;
u32 m_maxDrawItemsRenderedInAPass = 0;
};
class PassSystemInterface
{
friend class Pass;
@@ -116,6 +124,27 @@ namespace AZ
virtual void SetTargetedPassDebuggingName(const AZ::Name& targetPassName) = 0;
virtual const AZ::Name& GetTargetedPassDebuggingName() const = 0;
//! Find the SwapChainPass associated with window Handle
virtual SwapChainPass* FindSwapChainPass(AzFramework::NativeWindowHandle windowHandle) const = 0;
using OnReadyLoadTemplatesEvent = AZ::Event<>;
//! Connect a handler to listen to the event that the pass system is ready to load pass templates
//! The event is triggered when pass system is initialized and asset system is ready.
//! The handler can add new pass templates or load pass template mappings from assets
virtual void ConnectEvent(OnReadyLoadTemplatesEvent::Handler& handler) = 0;
virtual PassSystemState GetState() const = 0;
// Passes call this function to notify the pass system that they are drawing X draw items this frame
// Used for Pass System statistics
virtual void IncrementFrameDrawItemCount(u32 numDrawItems) = 0;
// Increments the counter for the number of render passes executed this frame (does not include passes that are disabled)
virtual void IncrementFrameRenderPassCount() = 0;
// Get frame statistics from the Pass System
virtual PassSystemFrameStatistics GetFrameStatistics() = 0;
// --- Pass Factory related functionality ---
//! Directly creates a pass given a PassDescriptor
@@ -172,17 +201,6 @@ namespace AZ
//! Find matching passes from registered passes with specified filter
virtual AZStd::vector<Pass*> FindPasses(const PassFilter& passFilter) const = 0;
//! Find the SwapChainPass associated with window Handle
virtual SwapChainPass* FindSwapChainPass(AzFramework::NativeWindowHandle windowHandle) const = 0;
using OnReadyLoadTemplatesEvent = AZ::Event<>;
//! Connect a handler to listen to the event that the pass system is ready to load pass templates
//! The event is triggered when pass system is initialized and asset system is ready.
//! The handler can add new pass templates or load pass template mappings from assets
virtual void ConnectEvent(OnReadyLoadTemplatesEvent::Handler& handler) = 0;
virtual PassSystemState GetState() const = 0;
private:
// These functions are only meant to be used by the Pass class
@@ -200,7 +218,6 @@ namespace AZ
//! Unregisters the pass with the pass library. Called in the Pass destructor.
virtual void UnregisterPass(Pass* pass) = 0;
};
namespace PassSystemEvents
@@ -38,11 +38,13 @@ namespace AZ
void SetDrawListTag(Name drawListName);
void SetPipelineStateDataIndex(u32 index);
void SetPipelineStateDataIndex(uint32_t index);
//! Expose shader resource group.
ShaderResourceGroup* GetShaderResourceGroup();
uint32_t GetDrawItemCount();
protected:
explicit RasterPass(const PassDescriptor& descriptor);
@@ -76,6 +78,7 @@ namespace AZ
RHI::Viewport m_viewportState;
bool m_overrideScissorSate = false;
bool m_overrideViewportState = false;
uint32_t m_drawItemCount = 0;
};
} // namespace RPI
} // namespace AZ
@@ -310,6 +310,7 @@ namespace AZ
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzRender);
AZ_ATOM_PROFILE_FUNCTION("RPI", "PassSystem: FrameUpdate");
ResetFrameStatistics();
ProcessQueuedChanges();
m_state = PassSystemState::Rendering;
@@ -398,6 +399,29 @@ namespace AZ
handler.Connect(m_loadTemplatesEvent);
}
void PassSystem::ResetFrameStatistics()
{
m_frameStatistics.m_numRenderPassesExecuted = 0;
m_frameStatistics.m_totalDrawItemsRendered = 0;
m_frameStatistics.m_maxDrawItemsRenderedInAPass = 0;
}
PassSystemFrameStatistics PassSystem::GetFrameStatistics()
{
return m_frameStatistics;
}
void PassSystem::IncrementFrameDrawItemCount(u32 numDrawItems)
{
m_frameStatistics.m_totalDrawItemsRendered += numDrawItems;
m_frameStatistics.m_maxDrawItemsRenderedInAPass = AZStd::max(m_frameStatistics.m_maxDrawItemsRenderedInAPass, numDrawItems);
}
void PassSystem::IncrementFrameRenderPassCount()
{
++m_frameStatistics.m_numRenderPassesExecuted;
}
// --- Pass Factory Functions ---
void PassSystem::AddPassCreator(Name className, PassCreator createFunction)
@@ -104,7 +104,7 @@ namespace AZ
m_flags.m_hasDrawListTag = true;
}
void RasterPass::SetPipelineStateDataIndex(u32 index)
void RasterPass::SetPipelineStateDataIndex(uint32_t index)
{
m_pipelineStateDataIndex.m_index = index;
}
@@ -114,6 +114,11 @@ namespace AZ
return m_shaderResourceGroup.get();
}
uint32_t RasterPass::GetDrawItemCount()
{
return m_drawItemCount;
}
// --- Pass behaviour overrides ---
void RasterPass::Validate(PassValidationResults& validationResults)
@@ -154,17 +159,21 @@ namespace AZ
// Assert the view has our draw list (the view's DrawlistTags are collected from passes using its viewTag)
AZ_Assert(view->HasDrawListTag(m_drawListTag), "View's DrawListTags out of sync with pass'. ");
// Draw List
viewDrawList = view->GetDrawList(m_drawListTag);
}
// clean up data
m_drawListView = {};
m_combinedDrawList.clear();
m_drawItemCount = 0;
// draw list from view was sorted and if it's the only draw list then we can use it directly
if (viewDrawList.size() > 0 && drawLists.size() == 0)
{
m_drawListView = viewDrawList;
m_drawItemCount += static_cast<uint32_t>(viewDrawList.size());
PassSystemInterface::Get()->IncrementFrameDrawItemCount(m_drawItemCount);
return;
}
@@ -172,12 +181,12 @@ namespace AZ
drawLists.push_back(viewDrawList);
// combine draw items from mutiple draw lists to one draw list and sort it.
size_t itemCount = 0;
for (auto drawList : drawLists)
{
itemCount += drawList.size();
m_drawItemCount += static_cast<uint32_t>(drawList.size());
}
m_combinedDrawList.resize(itemCount);
PassSystemInterface::Get()->IncrementFrameDrawItemCount(m_drawItemCount);
m_combinedDrawList.resize(m_drawItemCount);
RHI::DrawItemProperties* currentBuffer = m_combinedDrawList.data();
for (auto drawList : drawLists)
{
@@ -202,7 +211,7 @@ namespace AZ
void RasterPass::SetupFrameGraphDependencies(RHI::FrameGraphInterface frameGraph)
{
RenderPass::SetupFrameGraphDependencies(frameGraph);
frameGraph.SetEstimatedItemCount(static_cast<u32>(m_drawListView.size()));
frameGraph.SetEstimatedItemCount(static_cast<uint32_t>(m_drawListView.size()));
}
void RasterPass::CompileResources(const RHI::FrameGraphCompileContext& context)
@@ -201,6 +201,8 @@ namespace AZ
m_attachmentCopy.lock()->FrameBegin(params);
}
CollectSrgs();
PassSystemInterface::Get()->IncrementFrameRenderPassCount();
}
@@ -230,25 +230,20 @@ namespace AZ::Render
AZ::RPI::ViewportContextPtr viewportContext = GetViewportContext();
auto rootPass = viewportContext->GetCurrentPipeline()->GetRootPass();
const RPI::PipelineStatisticsResult stats = rootPass->GetLatestPipelineStatisticsResult();
AZStd::function<int(const AZ::RPI::Ptr<AZ::RPI::Pass>)> containingPassCount = [&containingPassCount](const AZ::RPI::Ptr<AZ::RPI::Pass> pass)
{
int count = 1;
if (auto passAsParent = pass->AsParent())
{
for (const auto& child : passAsParent->GetChildren())
{
count += containingPassCount(child);
}
}
return count;
};
const int numPasses = containingPassCount(rootPass);
RPI::PassSystemFrameStatistics passSystemFrameStatistics = AZ::RPI::PassSystemInterface::Get()->GetFrameStatistics();
DrawLine(AZStd::string::format(
"Total Passes: %d Vertex Count: %lld Primitive Count: %lld",
numPasses,
"RenderPasses: %d Vertex Count: %lld Primitive Count: %lld",
passSystemFrameStatistics.m_numRenderPassesExecuted,
aznumeric_cast<long long>(stats.m_vertexCount),
aznumeric_cast<long long>(stats.m_primitiveCount)
));
DrawLine(AZStd::string::format(
"Total Draw Item Count: %d Max Draw Items in a Pass: %d",
passSystemFrameStatistics.m_totalDrawItemsRendered,
passSystemFrameStatistics.m_maxDrawItemsRenderedInAPass
));
}
void AtomViewportDisplayInfoSystemComponent::UpdateFramerate()