diff --git a/Gems/Atom/RHI/Code/Include/Atom/RHI.Reflect/BufferDescriptor.h b/Gems/Atom/RHI/Code/Include/Atom/RHI.Reflect/BufferDescriptor.h index 323b44485d..80c41bec25 100644 --- a/Gems/Atom/RHI/Code/Include/Atom/RHI.Reflect/BufferDescriptor.h +++ b/Gems/Atom/RHI/Code/Include/Atom/RHI.Reflect/BufferDescriptor.h @@ -10,6 +10,8 @@ #include #include + +#include #include 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); diff --git a/Gems/Atom/RHI/Code/Include/Atom/RHI.Reflect/ImageEnums.h b/Gems/Atom/RHI/Code/Include/Atom/RHI.Reflect/ImageEnums.h index a2e0850ec9..7b83ac377e 100644 --- a/Gems/Atom/RHI/Code/Include/Atom/RHI.Reflect/ImageEnums.h +++ b/Gems/Atom/RHI/Code/Include/Atom/RHI.Reflect/ImageEnums.h @@ -9,43 +9,43 @@ #include +#include + 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); diff --git a/Gems/Atom/RHI/Code/Include/Atom/RHI/RHISystem.h b/Gems/Atom/RHI/Code/Include/Atom/RHI/RHISystem.h index b48f09ba01..e37fd75148 100644 --- a/Gems/Atom/RHI/Code/Include/Atom/RHI/RHISystem.h +++ b/Gems/Atom/RHI/Code/Include/Atom/RHI/RHISystem.h @@ -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 GetPlatformLimitsDescriptor() const override; void QueueRayTracingShaderTableForBuild(RayTracingShaderTable* rayTracingShaderTable) override; diff --git a/Gems/Atom/RHI/Code/Include/Atom/RHI/RHISystemInterface.h b/Gems/Atom/RHI/Code/Include/Atom/RHI/RHISystemInterface.h index fd2feb2dd3..277b974641 100644 --- a/Gems/Atom/RHI/Code/Include/Atom/RHI/RHISystemInterface.h +++ b/Gems/Atom/RHI/Code/Include/Atom/RHI/RHISystemInterface.h @@ -11,6 +11,7 @@ #include #include #include +#include #include 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 GetPlatformLimitsDescriptor() const = 0; diff --git a/Gems/Atom/RHI/Code/Source/RHI/RHISystem.cpp b/Gems/Atom/RHI/Code/Source/RHI/RHISystem.cpp index ef43b12164..083fb87b93 100644 --- a/Gems/Atom/RHI/Code/Source/RHI/RHISystem.cpp +++ b/Gems/Atom/RHI/Code/Source/RHI/RHISystem.cpp @@ -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(); diff --git a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/Vulkan.h b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/Vulkan.h index 2f36a3f22d..51f79f3ac2 100644 --- a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/Vulkan.h +++ b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/Vulkan.h @@ -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; diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiGpuProfiler.h b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiGpuProfiler.h index d6b29be61a..1bed2b5715 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiGpuProfiler.h +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiGpuProfiler.h @@ -8,9 +8,12 @@ #pragma once +#include #include +#include #include +#include #include 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 m_tableRows; + AZStd::vector m_savedPools; + AZStd::vector 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 diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiGpuProfiler.inl b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiGpuProfiler.inl index f2a9257172..ec7b30cd3e 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiGpuProfiler.inl +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiGpuProfiler.inl @@ -6,12 +6,15 @@ * */ +#include #include #include #include #include #include +#include + #include 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 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) {