[GHI #2165] GPU Buffer/Image memory visualizer (#2242)

* GPU Memory: basic tree view of pools + heaps
* GPU Memory: pie charts of overall heap usage
* GPU Memory: Implement tabular view
* GPU Memory: final cleanup
* GPU Memory: use AZ_ENUM macro for string conversion

Signed-off-by: Jacob Hilliard <jhlliar@amazon.com>
This commit is contained in:
Jacob Hilliard
2021-07-20 13:41:33 -07:00
committed by GitHub
parent a32a521a80
commit 5c0b6bf50f
8 changed files with 339 additions and 31 deletions
@@ -10,6 +10,8 @@
#include <Atom/RHI.Reflect/Format.h>
#include <Atom/RHI.Reflect/AttachmentEnums.h>
#include <AzCore/Preprocessor/Enum.h>
#include <AzCore/Utils/TypeHash.h>
namespace AZ
@@ -22,47 +24,44 @@ namespace AZ
* A set of combinable flags which inform the system how a buffer is to be
* bound to the pipeline at all stages of its lifetime.
*/
enum class BufferBindFlags : uint32_t
{
None = 0,
AZ_ENUM_CLASS_WITH_UNDERLYING_TYPE(BufferBindFlags, uint32_t,
(None , 0),
/// Supports input assembly access through a IndexBufferView or StreamBufferView. This flag is for buffers that are not updated often
InputAssembly = AZ_BIT(0),
(InputAssembly , AZ_BIT(0)),
/// Supports input assembly access through a IndexBufferView or StreamBufferView. This flag is for buffers that are updated frequently
DynamicInputAssembly = AZ_BIT(1),
(DynamicInputAssembly , AZ_BIT(1)),
/// Supports constant access through a ShaderResourceGroup.
Constant = AZ_BIT(2),
(Constant , AZ_BIT(2)),
/// Supports read access through a ShaderResourceGroup.
ShaderRead = AZ_BIT(3),
(ShaderRead , AZ_BIT(3)),
/// Supports write access through ShaderResourceGroup.
ShaderWrite = AZ_BIT(4),
(ShaderWrite , AZ_BIT(4)),
/// Supports read-write access through a ShaderResourceGroup.
ShaderReadWrite = ShaderRead | ShaderWrite,
(ShaderReadWrite , ShaderRead | ShaderWrite),
/// Supports read access for GPU copy operations.
CopyRead = AZ_BIT(5),
(CopyRead , AZ_BIT(5)),
/// Supports write access for GPU copy operations.
CopyWrite = AZ_BIT(6),
(CopyWrite , AZ_BIT(6)),
/// Supports predication access for conditional rendering.
Predication = AZ_BIT(7),
(Predication , AZ_BIT(7)),
/// Supports indirect buffer access for indirect draw/dispatch.
Indirect = AZ_BIT(8),
(Indirect , AZ_BIT(8)),
/// Supports ray tracing acceleration structure usage.
RayTracingAccelerationStructure = AZ_BIT(9),
(RayTracingAccelerationStructure , AZ_BIT(9)),
/// Supports ray tracing shader table usage.
RayTracingShaderTable = AZ_BIT(10)
};
(RayTracingShaderTable , AZ_BIT(10)));
AZ_DEFINE_ENUM_BITWISE_OPERATORS(AZ::RHI::BufferBindFlags);
@@ -9,43 +9,43 @@
#include <Atom/RHI.Reflect/Base.h>
#include <AzCore/Preprocessor/Enum.h>
namespace AZ
{
namespace RHI
{
//! A set of combinable flags which inform the system how an image is to be
//! bound to the pipeline at all stages of its lifetime.
enum class ImageBindFlags : uint32_t
{
None = 0,
AZ_ENUM_CLASS_WITH_UNDERLYING_TYPE(ImageBindFlags, uint32_t,
(None, 0),
/// Supports read access through a ShaderResourceGroup.
ShaderRead = AZ_BIT(0),
(ShaderRead, AZ_BIT(0)),
/// Supports write access through a ShaderResourceGroup.
ShaderWrite = AZ_BIT(1),
(ShaderWrite, AZ_BIT(1)),
/// Supports read-write access through a ShaderResourceGroup.
ShaderReadWrite = ShaderRead | ShaderWrite,
(ShaderReadWrite, ShaderRead | ShaderWrite),
/// Supports use as a color attachment on a scope.
Color = AZ_BIT(2),
(Color, AZ_BIT(2)),
/// Supports use as depth attachment on a scope.
Depth = AZ_BIT(3),
(Depth, AZ_BIT(3)),
/// Supports use as stencil attachment on a scope.
Stencil = AZ_BIT(4),
(Stencil, AZ_BIT(4)),
/// Supports use as a depth stencil attachment on a scope.
DepthStencil = Depth | Stencil,
(DepthStencil, Depth | Stencil),
/// Supports read access for GPU copy operations.
CopyRead = AZ_BIT(5),
(CopyRead, AZ_BIT(5)),
/// Supports write access for GPU copy operations.
CopyWrite = AZ_BIT(6),
};
(CopyWrite, AZ_BIT(6)));
AZ_DEFINE_ENUM_BITWISE_OPERATORS(AZ::RHI::ImageBindFlags);
@@ -51,6 +51,7 @@ namespace AZ
void ModifyFrameSchedulerStatisticsFlags(RHI::FrameSchedulerStatisticsFlags statisticsFlags, bool enableFlags) override;
const RHI::CpuTimingStatistics* GetCpuTimingStatistics() const override;
const RHI::TransientAttachmentStatistics* GetTransientAttachmentStatistics() const override;
const RHI::MemoryStatistics* GetMemoryStatistics() const override;
const RHI::TransientAttachmentPoolDescriptor* GetTransientAttachmentPoolDescriptor() const override;
ConstPtr<PlatformLimitsDescriptor> GetPlatformLimitsDescriptor() const override;
void QueueRayTracingShaderTableForBuild(RayTracingShaderTable* rayTracingShaderTable) override;
@@ -11,6 +11,7 @@
#include <AzCore/Name/Name.h>
#include <AzCore/EBus/EBus.h>
#include <Atom/RHI.Reflect/FrameSchedulerEnums.h>
#include <Atom/RHI.Reflect/MemoryStatistics.h>
#include <Atom/RHI/DrawListTagRegistry.h>
namespace AZ
@@ -55,6 +56,8 @@ namespace AZ
virtual const RHI::TransientAttachmentStatistics* GetTransientAttachmentStatistics() const = 0;
virtual const RHI::MemoryStatistics* GetMemoryStatistics() const = 0;
virtual const RHI::TransientAttachmentPoolDescriptor* GetTransientAttachmentPoolDescriptor() const = 0;
virtual ConstPtr<PlatformLimitsDescriptor> GetPlatformLimitsDescriptor() const = 0;
@@ -281,6 +281,11 @@ namespace AZ
return m_frameScheduler.GetTransientAttachmentStatistics();
}
const RHI::MemoryStatistics* RHISystem::GetMemoryStatistics() const
{
return m_frameScheduler.GetMemoryStatistics();
}
const AZ::RHI::TransientAttachmentPoolDescriptor* RHISystem::GetTransientAttachmentPoolDescriptor() const
{
return m_frameScheduler.GetTransientAttachmentPoolDescriptor();
@@ -23,7 +23,11 @@ namespace AZ
namespace RHI
{
class ScopeAttachment;
enum class BufferBindFlags : uint32_t;
// NOTE: see BufferDescriptor.h, AZ_ENUM... macro wraps enum within an outer inline namespace.
inline namespace BufferBindFlagsNamespace
{
enum class BufferBindFlags : uint32_t;
}
class BufferView;
class ImageView;
struct BufferSubresourceRange;
@@ -8,9 +8,12 @@
#pragma once
#include <Atom/RHI.Reflect/MemoryStatistics.h>
#include <Atom/RPI.Public/GpuQuery/GpuQueryTypes.h>
#include <AzCore/Name/Name.h>
#include <AzCore/std/containers/array.h>
#include <AzCore/std/containers/variant.h>
#include <AzCore/std/string/string.h>
namespace AZ
@@ -250,6 +253,44 @@ namespace AZ
};
class ImGuiGpuMemoryView
{
public:
// Draw the overall GPU memory profiling window.
void DrawGpuMemoryWindow(bool& draw);
private:
// Draw the heap usage pie chart
void DrawPieChart(const AZ::RHI::MemoryStatistics::Heap& heap);
// Update the saved pointers in m_tableRows according to new data/filters
void UpdateTableRows();
void DrawTable();
// Sort the table according to the appropriate column.
void SortTable(ImGuiTableSortSpecs* sortSpecs);
struct TableRow
{
Name m_parentPoolName;
Name m_bufImgName;
size_t m_sizeInBytes = 0;
AZStd::string m_bindFlags;
};
// Table settings
bool m_includeBuffers = true;
bool m_includeImages = true;
bool m_includeTransientAttachments = true;
ImGuiTextFilter m_nameFilter;
AZStd::vector<TableRow> m_tableRows;
AZStd::vector<AZ::RHI::MemoryStatistics::Pool> m_savedPools;
AZStd::vector<AZ::RHI::MemoryStatistics::Heap> m_savedHeaps;
};
class ImGuiGpuProfiler
{
public:
@@ -275,9 +316,11 @@ namespace AZ
bool m_drawTimestampView = false;
bool m_drawPipelineStatisticsView = false;
bool m_drawGpuMemoryView = false;
ImGuiTimestampView m_timestampView;
ImGuiPipelineStatisticsView m_pipelineStatisticsView;
ImGuiGpuMemoryView m_gpuMemoryView;
};
} //namespace Render
} // namespace AZ
@@ -6,12 +6,15 @@
*
*/
#include <Atom/RHI/RHISystemInterface.h>
#include <Atom/RPI.Public/Pass/ParentPass.h>
#include <Atom/RPI.Public/Pass/RenderPass.h>
#include <Atom/RPI.Public/RenderPipeline.h>
#include <Atom/RPI.Public/RPISystemInterface.h>
#include <Atom/RPI.Public/Scene.h>
#include <AzCore/std/sort.h>
#include <inttypes.h>
namespace AZ
@@ -87,6 +90,38 @@ namespace AZ
}
drawList->AddText(font, font->FontSize, pos, ImGui::GetColorU32(ImGuiCol_Text), text, nullptr, size.x);
}
inline static AZStd::string GetImageBindStrings(AZ::RHI::ImageBindFlags imageBindFlags)
{
AZStd::string imageBindStrings;
for (const auto& flag : AZ::RHI::ImageBindFlagsMembers)
{
if (flag.m_value != AZ::RHI::ImageBindFlags::None && AZ::RHI::CheckBitsAll(imageBindFlags, flag.m_value))
{
imageBindStrings.append(flag.m_string);
imageBindStrings.append(", ");
}
}
return imageBindStrings;
}
inline static AZStd::string GetBufferBindStrings(AZ::RHI::BufferBindFlags bufferBindFlags)
{
AZStd::string bufferBindStrings;
for (const auto& flag : AZ::RHI::BufferBindFlagsMembers)
{
if (flag.m_value != AZ::RHI::BufferBindFlags::None && AZ::RHI::CheckBitsAll(bufferBindFlags, flag.m_value))
{
bufferBindStrings.append(flag.m_string);
bufferBindStrings.append(", ");
}
}
return bufferBindStrings;
}
static constexpr u64 KB = 1024;
static constexpr u64 MB = 1024 * KB;
static constexpr u64 GB = 1024 * MB;
} // namespace GpuProfilerImGuiHelper
// --- PassEntry ---
@@ -1025,6 +1060,219 @@ namespace AZ
}
}
// --- ImGuiGpuMemoryView ---
inline void ImGuiGpuMemoryView::SortTable(ImGuiTableSortSpecs* sortSpecs)
{
const bool ascending = sortSpecs->Specs->SortDirection == ImGuiSortDirection_Ascending;
const ImS16 columnToSort = sortSpecs->Specs->ColumnIndex;
// Sort by the appropriate column in the table
switch (columnToSort)
{
case (0): // Sorting by parent pool name
AZStd::sort(m_tableRows.begin(), m_tableRows.end(),
[ascending](const TableRow& lhs, const TableRow& rhs)
{
const auto lhsParentPool = lhs.m_parentPoolName.GetStringView();
const auto rhsParentPool = rhs.m_parentPoolName.GetStringView();
return ascending ? lhsParentPool < rhsParentPool : lhsParentPool > rhsParentPool;
});
break;
case (1): // Sort by buffer/image name
AZStd::sort(m_tableRows.begin(), m_tableRows.end(),
[ascending](const TableRow& lhs, const TableRow& rhs)
{
const auto lhsName = lhs.m_bufImgName.GetStringView();
const auto rhsName = rhs.m_bufImgName.GetStringView();
return ascending ? lhsName < rhsName : lhsName > rhsName;
});
break;
case (2): // Sort by memory usage
AZStd::sort(m_tableRows.begin(), m_tableRows.end(),
[ascending](const TableRow& lhs, const TableRow& rhs)
{
const float lhsSize = lhs.m_sizeInBytes;
const float rhsSize = rhs.m_sizeInBytes;
return ascending ? lhsSize < rhsSize : lhsSize > rhsSize;
});
break;
}
sortSpecs->SpecsDirty = false;
}
inline void ImGuiGpuMemoryView::DrawTable()
{
if (ImGui::BeginTable("Table", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_Sortable | ImGuiTableFlags_Resizable))
{
ImGui::TableSetupColumn("Parent pool");
ImGui::TableSetupColumn("Name");
ImGui::TableSetupColumn("Size (MB)", 0, 100.0f);
ImGui::TableSetupColumn("BindFlags", ImGuiTableColumnFlags_NoSort);
ImGui::TableHeadersRow();
ImGui::TableNextColumn();
ImGuiTableSortSpecs* sortSpecs = ImGui::TableGetSortSpecs();
if (sortSpecs && sortSpecs->SpecsDirty)
{
SortTable(sortSpecs);
}
// Draw each row in the table
for (const auto& tableRow : m_tableRows)
{
// Don't draw the row if none of the row's text fields pass the filter
if (!m_nameFilter.PassFilter(tableRow.m_parentPoolName.GetCStr())
&& !m_nameFilter.PassFilter(tableRow.m_bufImgName.GetCStr())
&& !m_nameFilter.PassFilter(tableRow.m_bindFlags.c_str()))
{
continue;
}
ImGui::Text(tableRow.m_parentPoolName.GetCStr());
ImGui::TableNextColumn();
ImGui::Text(tableRow.m_bufImgName.GetCStr());
ImGui::TableNextColumn();
ImGui::Text("%.2f", 1.0f * tableRow.m_sizeInBytes / GpuProfilerImGuiHelper::MB);
ImGui::TableNextColumn();
ImGui::Text(tableRow.m_bindFlags.c_str());
ImGui::TableNextColumn();
}
}
ImGui::EndTable();
}
inline void ImGuiGpuMemoryView::UpdateTableRows()
{
// Update the table according to the latest filters applied
m_tableRows.clear();
for (const auto& pool : m_savedPools)
{
Name poolName = pool.m_name.IsEmpty() ? Name("Unnamed pool") : pool.m_name;
// Ignore transient pools
if (!m_includeTransientAttachments && pool.m_name.GetStringView().contains("Transient"))
{
continue;
}
if (m_includeBuffers)
{
for (const auto& buf : pool.m_buffers)
{
const Name bufName = buf.m_name.IsEmpty() ? Name("Unnamed Buffer") : buf.m_name;
const AZStd::string flags = GpuProfilerImGuiHelper::GetBufferBindStrings(buf.m_bindFlags);
m_tableRows.push_back({ poolName, bufName, buf.m_sizeInBytes, flags });
}
}
if (m_includeImages)
{
for (const auto& img : pool.m_images)
{
const Name imgName = img.m_name.IsEmpty() ? Name("Unnamed Image") : img.m_name;
const AZStd::string flags = GpuProfilerImGuiHelper::GetImageBindStrings(img.m_bindFlags);
m_tableRows.push_back({ poolName, imgName, img.m_sizeInBytes, flags });
}
}
}
}
inline void ImGuiGpuMemoryView::DrawPieChart(const AZ::RHI::MemoryStatistics::Heap& heap)
{
if (ImGui::BeginChild("PieChart", {150, 150}, true))
{
ImDrawList* drawList = ImGui::GetWindowDrawList();
const auto [wx, wy] = ImGui::GetWindowPos();
const auto [windowWidth, windowHeight] = ImGui::GetWindowSize();
const ImVec2 center = { wx + windowWidth / 2, wy + windowHeight / 2 };
const float radius = windowWidth / 2 - 10;
// Draw the pie chart
drawList->AddCircleFilled(center, radius, ImGui::GetColorU32({.3, .3, .3, 1}));
const float usagePercent = 1.0f * heap.m_memoryUsage.m_residentInBytes / heap.m_memoryUsage.m_budgetInBytes;
drawList->PathArcTo(center, radius, 0, AZ::Constants::TwoPi * usagePercent); // Clockwise starting from rightmost point
drawList->PathArcTo(center, 0, 0, 0); // To center
drawList->PathArcTo(center, radius, 0, 0); // Back to starting position
drawList->PathFillConvex(ImGui::GetColorU32({ .039, .8, 0.556, 1 }));
ImGui::Text("%.2f%%", usagePercent * 100);
}
ImGui::EndChild();
}
inline void ImGuiGpuMemoryView::DrawGpuMemoryWindow(bool& draw)
{
// Enable GPU memory instrumentation while the window is open. Called every draw frame, but just a bitwise operation so overhead should be low.
auto* rhiSystem = AZ::RHI::RHISystemInterface::Get();
AZ_Assert(rhiSystem != nullptr, "Error in drawing GPU memory window: RHI System Interface was nullptr");
rhiSystem->ModifyFrameSchedulerStatisticsFlags(AZ::RHI::FrameSchedulerStatisticsFlags::GatherMemoryStatistics, draw);
if (!draw)
{
return;
}
ImGui::SetNextWindowSize({ 600, 600 }, ImGuiCond_Once);
if (ImGui::Begin("Gpu Memory Profiler", &draw, ImGuiViewportFlags_None))
{
if (ImGui::Button("Capture"))
{
// Collect and save new GPU memory usage data
const auto* memoryStatistics = rhiSystem->GetMemoryStatistics();
if (memoryStatistics)
{
m_tableRows.clear();
m_savedPools = memoryStatistics->m_pools;
m_savedHeaps = memoryStatistics->m_heaps;
// Collect the data into TableRows, ignoring depending on flags
UpdateTableRows();
}
}
if (ImGui::Checkbox("Show buffers", &m_includeBuffers)
|| ImGui::Checkbox("Show images", &m_includeImages)
|| ImGui::Checkbox("Show transient attachments", &m_includeTransientAttachments))
{
UpdateTableRows();
}
ImGui::Text("Overall heap usage:");
for (const auto& savedHeap : m_savedHeaps)
{
if (ImGui::BeginChild(savedHeap.m_name.GetCStr(), { ImGui::GetWindowWidth() / m_savedHeaps.size(), 250 }), ImGuiWindowFlags_NoScrollbar)
{
ImGui::Text(savedHeap.m_name.GetCStr());
ImGui::Columns(2, "HeapData", true);
ImGui::Text("Resident (MB): ");
ImGui::NextColumn();
ImGui::Text("%.2f", 1.0 * savedHeap.m_memoryUsage.m_residentInBytes.load() / GpuProfilerImGuiHelper::MB);
ImGui::NextColumn();
ImGui::Text("Reserved (MB): ");
ImGui::NextColumn();
ImGui::Text("%.2f", 1.0 * savedHeap.m_memoryUsage.m_reservedInBytes.load() / GpuProfilerImGuiHelper::MB);
ImGui::NextColumn();
ImGui::Text("Budget (MB): ");
ImGui::NextColumn();
ImGui::Text("%.2f", 1.0 * savedHeap.m_memoryUsage.m_budgetInBytes / GpuProfilerImGuiHelper::MB);
ImGui::Columns(1, "PieChartColumn");
DrawPieChart(savedHeap);
}
ImGui::EndChild();
ImGui::SameLine(ImGui::GetWindowWidth() / m_savedHeaps.size());
}
ImGui::NewLine();
ImGui::Separator();
m_nameFilter.Draw("Search");
DrawTable();
}
}
// --- ImGuiGpuProfiler ---
inline void ImGuiGpuProfiler::Draw(bool& draw, RHI::Ptr<RPI::ParentPass> rootPass)
@@ -1045,6 +1293,8 @@ namespace AZ
{
rootPass->SetPipelineStatisticsQueryEnabled(m_drawPipelineStatisticsView);
}
ImGui::Spacing();
ImGui::Checkbox("Enable GpuMemoryView", &m_drawGpuMemoryView);
});
// Draw the PipelineStatistics window.
@@ -1053,6 +1303,9 @@ namespace AZ
// Draw the PipelineStatistics window.
m_pipelineStatisticsView.DrawPipelineStatisticsWindow(m_drawPipelineStatisticsView, rootPassEntryRef, m_passEntryDatabase, rootPass);
// Draw the GpuMemory window.
m_gpuMemoryView.DrawGpuMemoryWindow(m_drawGpuMemoryView);
//closing window
if (wasDraw && !draw)
{