ImGuiGpuProfiler.cpp 92 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <Atom/Utils/ImGuiGpuProfiler.h>
  9. #include <Atom/RHI/RHISystemInterface.h>
  10. #include <Atom/RHI/RHIMemoryStatisticsInterface.h>
  11. #include <Atom/RHI.Reflect/MemoryStatistics.h>
  12. #include <Atom/RPI.Public/Pass/ParentPass.h>
  13. #include <Atom/RPI.Public/Pass/RenderPass.h>
  14. #include <Atom/RPI.Public/RenderPipeline.h>
  15. #include <Atom/RPI.Public/RPISystemInterface.h>
  16. #include <Atom/RPI.Public/Scene.h>
  17. #include <Profiler/ImGuiTreemap.h>
  18. #include <imgui/imgui_internal.h>
  19. #include <AzCore/IO/SystemFile.h>
  20. #include <AzCore/Utils/Utils.h>
  21. #include <AzCore/std/sort.h>
  22. #include <AzCore/std/time.h>
  23. #include <AzCore/JSON/document.h>
  24. #include <AzCore/JSON/stringbuffer.h>
  25. #include <AzCore/JSON/pointer.h>
  26. #include <AzCore/JSON/prettywriter.h>
  27. #include <AzCore/Serialization/Json/JsonSerialization.h>
  28. #include <AzCore/Serialization/Json/JsonUtils.h>
  29. #include <inttypes.h>
  30. namespace AZ
  31. {
  32. namespace Render
  33. {
  34. namespace GpuProfilerImGuiHelper
  35. {
  36. template<typename T>
  37. static void TreeNode(const char* label, ImGuiTreeNodeFlags flags, T&& functor)
  38. {
  39. const bool unrolledTreeNode = ImGui::TreeNodeEx(label, flags);
  40. functor(unrolledTreeNode);
  41. if (unrolledTreeNode)
  42. {
  43. ImGui::TreePop();
  44. }
  45. }
  46. template <typename Functor>
  47. static void Begin(const char* name, bool* open, ImGuiWindowFlags flags, Functor&& functor)
  48. {
  49. if (ImGui::Begin(name, open, flags))
  50. {
  51. functor();
  52. }
  53. ImGui::End();
  54. }
  55. template <typename Functor>
  56. static void BeginChild(const char* text, const ImVec2& size, bool border, ImGuiWindowFlags flags, Functor&& functor)
  57. {
  58. if (ImGui::BeginChild(text, size, border, flags))
  59. {
  60. functor();
  61. }
  62. ImGui::EndChild();
  63. }
  64. static void HoverMarker(const char* text)
  65. {
  66. if (ImGui::IsItemHovered())
  67. {
  68. ImGui::BeginTooltip();
  69. ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
  70. ImGui::TextUnformatted(text);
  71. ImGui::PopTextWrapPos();
  72. ImGui::EndTooltip();
  73. }
  74. }
  75. template <typename Functor>
  76. static void PushStyleColor(ImGuiCol idx, const ImVec4& color, Functor&& functor)
  77. {
  78. ImGui::PushStyleColor(idx, color);
  79. functor();
  80. ImGui::PopStyleColor();
  81. }
  82. template <typename Functor>
  83. static void WrappableSelectable(const char* text, ImVec2 size, bool selected, ImGuiSelectableFlags flags, Functor&& functor)
  84. {
  85. ImFont* font = ImGui::GetFont();
  86. ImDrawList* drawList = ImGui::GetWindowDrawList();
  87. const ImVec2 pos = ImGui::GetCursorScreenPos();
  88. const AZStd::string label = AZStd::string::format("%s%s", "##hidden", text);
  89. if (ImGui::Selectable(label.c_str(), selected, flags, size))
  90. {
  91. functor();
  92. }
  93. drawList->AddText(font, font->FontSize, pos, ImGui::GetColorU32(ImGuiCol_Text), text, nullptr, size.x);
  94. }
  95. static AZStd::string GetImageBindStrings(AZ::RHI::ImageBindFlags imageBindFlags)
  96. {
  97. AZStd::string imageBindStrings;
  98. for (const auto& flag : AZ::RHI::ImageBindFlagsMembers)
  99. {
  100. if (flag.m_value != AZ::RHI::ImageBindFlags::None && AZ::RHI::CheckBitsAll(imageBindFlags, flag.m_value))
  101. {
  102. imageBindStrings.append(flag.m_string);
  103. imageBindStrings.append(", ");
  104. }
  105. }
  106. return imageBindStrings;
  107. }
  108. static AZStd::string GetBufferBindStrings(AZ::RHI::BufferBindFlags bufferBindFlags)
  109. {
  110. AZStd::string bufferBindStrings;
  111. for (const auto& flag : AZ::RHI::BufferBindFlagsMembers)
  112. {
  113. if (flag.m_value != AZ::RHI::BufferBindFlags::None && AZ::RHI::CheckBitsAll(bufferBindFlags, flag.m_value))
  114. {
  115. bufferBindStrings.append(flag.m_string);
  116. bufferBindStrings.append(", ");
  117. }
  118. }
  119. return bufferBindStrings;
  120. }
  121. static constexpr u64 KB = 1024;
  122. static constexpr u64 MB = 1024 * KB;
  123. } // namespace GpuProfilerImGuiHelper
  124. // --- PassEntry ---
  125. PassEntry::PassEntry(const RPI::Pass* pass, PassEntry* parent)
  126. {
  127. m_name = pass->GetName();
  128. m_path = pass->GetPathName();
  129. m_parent = parent;
  130. m_enabled = pass->IsEnabled();
  131. m_timestampEnabled = pass->IsTimestampQueryEnabled();
  132. m_pipelineStatisticsEnabled = pass->IsPipelineStatisticsQueryEnabled();
  133. m_isParent = pass->AsParent() != nullptr;
  134. // [GFX TODO][ATOM-4001] Cache the timestamp and PipelineStatistics results.
  135. // Get the query results from the passes.
  136. m_timestampResult = pass->GetLatestTimestampResult();
  137. const RPI::PipelineStatisticsResult rps = pass->GetLatestPipelineStatisticsResult();
  138. m_pipelineStatistics = { rps.m_vertexCount, rps.m_primitiveCount, rps.m_vertexShaderInvocationCount,
  139. rps.m_rasterizedPrimitiveCount, rps.m_renderedPrimitiveCount, rps.m_pixelShaderInvocationCount, rps.m_computeShaderInvocationCount };
  140. // Disable the entry if it has a parent that is also not enabled.
  141. if (m_parent)
  142. {
  143. m_enabled = pass->IsEnabled() && m_parent->m_enabled;
  144. }
  145. }
  146. void PassEntry::LinkChild(PassEntry* childEntry)
  147. {
  148. m_children.push_back(childEntry);
  149. if (!m_linked && m_parent)
  150. {
  151. m_linked = true;
  152. // Recursively create parent->child references for entries that aren't linked to the root entry yet.
  153. // Effectively walking the tree backwards from the leaf to the root entry, and establishing parent->child references to
  154. // entries that aren't connected to the root entry yet.
  155. m_parent->LinkChild(this);
  156. }
  157. childEntry->m_linked = true;
  158. }
  159. bool PassEntry::IsTimestampEnabled() const
  160. {
  161. return m_enabled && m_timestampEnabled;
  162. }
  163. bool PassEntry::IsPipelineStatisticsEnabled() const
  164. {
  165. return m_enabled && m_pipelineStatisticsEnabled;
  166. }
  167. // --- ImGuiPipelineStatisticsView ---
  168. ImGuiPipelineStatisticsView::ImGuiPipelineStatisticsView() :
  169. m_headerColumnWidth{ 204.0f, 104.0f, 104.0f, 104.0f, 104.0f, 104.0f, 104.0f, 104.0f }
  170. {
  171. }
  172. void ImGuiPipelineStatisticsView::DrawPipelineStatisticsWindow(bool& draw,
  173. const PassEntry* rootPassEntry, AZStd::unordered_map<Name, PassEntry>& passEntryDatabase,
  174. AZ::RHI::Ptr<RPI::ParentPass> rootPass)
  175. {
  176. // Early out if nothing is supposed to be drawn
  177. if (!draw)
  178. {
  179. return;
  180. }
  181. AZ_Assert(rootPassEntry, "RootPassEntry is invalid.");
  182. // The PipelineStatistics attribute names.
  183. static const char* PipelineStatisticsAttributeHeader[HeaderAttributeCount] = {
  184. "Pass Name", "Vertex Count", "Primitive Count", "Vertex Shader Invocation Count", "Rasterized Primitive Count",
  185. "Rendered Primitive Count", "Pixel Shader Invocation Count", "Compute Shader Invocation Count"
  186. };
  187. // Additional filter to exclude passes from the list.
  188. static const AZStd::array<AZStd::string, 2> ExcludeFilter = { "Root", "MainPipeline" };
  189. // Clear the references array from the previous frame.
  190. m_passEntryReferences.clear();
  191. // Filter the PassEntries.
  192. {
  193. m_passEntryReferences.reserve(passEntryDatabase.size());
  194. for (auto& passEntryIt : passEntryDatabase)
  195. {
  196. const PassEntry& passEntry = passEntryIt.second;
  197. // Filter depending on the user input.
  198. if (!m_passFilter.PassFilter(passEntry.m_name.GetCStr()))
  199. {
  200. continue;
  201. }
  202. // Filter out parent passes if necessary.
  203. if (!m_showParentPasses && passEntry.m_isParent)
  204. {
  205. continue;
  206. }
  207. // Filter with the ExcludeFilter.
  208. if (m_excludeFilterEnabled)
  209. {
  210. const auto filterIt = AZStd::find_if(ExcludeFilter.begin(), ExcludeFilter.end(), [&passEntry](const AZStd::string& passName)
  211. {
  212. return passName == passEntry.m_name.GetStringView();
  213. });
  214. if (filterIt != ExcludeFilter.end())
  215. {
  216. continue;
  217. }
  218. }
  219. // Add the PassEntry if it passes both filters.
  220. m_passEntryReferences.push_back(&passEntry);
  221. }
  222. }
  223. // Sort the PassEntries.
  224. SortView();
  225. // Set the window size.
  226. const ImVec2 windowSize(964.0f, 510.0f);
  227. ImGui::SetNextWindowSize(windowSize, ImGuiCond_Once);
  228. // Start drawing the PipelineStatistics window.
  229. if (ImGui::Begin("PipelineStatistics Window", &draw, ImGuiWindowFlags_None))
  230. {
  231. // Pause/unpause the profiling
  232. if (ImGui::Button(m_paused ? "Resume" : "Pause"))
  233. {
  234. m_paused = !m_paused;
  235. rootPass->SetPipelineStatisticsQueryEnabled(!m_paused);
  236. }
  237. ImGui::Columns(2, "HeaderColumns");
  238. // Draw the statistics of the RootPass.
  239. {
  240. ImGui::Text("Information");
  241. ImGui::Spacing();
  242. // General information.
  243. {
  244. // Display total pass count.
  245. const AZStd::string totalPassCountLabel = AZStd::string::format("%s: %u",
  246. "Total Pass Count",
  247. static_cast<uint32_t>(passEntryDatabase.size()));
  248. ImGui::Text("%s", totalPassCountLabel.c_str());
  249. // Display listed pass count.
  250. const AZStd::string listedPassCountLabel = AZStd::string::format("%s: %u",
  251. "Listed Pass Count",
  252. static_cast<uint32_t>(m_passEntryReferences.size()));
  253. ImGui::Text("%s", listedPassCountLabel.c_str());
  254. }
  255. }
  256. ImGui::NextColumn();
  257. // Options
  258. GpuProfilerImGuiHelper::TreeNode("Options", ImGuiTreeNodeFlags_None, [this](bool unrolled)
  259. {
  260. if (unrolled)
  261. {
  262. // Draw the advanced Options node.
  263. ImGui::Checkbox("Enable color-coding", &m_enableColorCoding);
  264. ImGui::Checkbox("Remove RootPasses from the list", &m_excludeFilterEnabled);
  265. ImGui::Checkbox("Show attribute contribution", &m_showAttributeContribution);
  266. ImGui::Checkbox("Show pass' tree state", &m_showPassTreeState);
  267. ImGui::Checkbox("Show disabled passes", &m_showDisabledPasses);
  268. ImGui::Checkbox("Show parent passes", &m_showParentPasses);
  269. }
  270. });
  271. ImGui::Columns(1, "HeaderColumns");
  272. ImGui::Separator();
  273. // Draw the filter.
  274. m_passFilter.Draw("Pass Name Filter");
  275. // Draw the attribute matrix header.
  276. {
  277. ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 4.0f));
  278. ImGui::Columns(HeaderAttributeCount, "PipelineStatisticsHeader", false);
  279. // Calculate the text which requires the most height.
  280. float maxColumnHeight = 0.0f;
  281. for (uint32_t headerIdx = 0u; headerIdx < HeaderAttributeCount; headerIdx++)
  282. {
  283. ImGui::SetColumnWidth(static_cast<int32_t>(headerIdx), m_headerColumnWidth[headerIdx]);
  284. const char* text = PipelineStatisticsAttributeHeader[headerIdx];
  285. const ImVec2 textSize = ImGui::CalcTextSize(text, nullptr, false, m_headerColumnWidth[headerIdx]);
  286. maxColumnHeight = AZStd::max(textSize.y, maxColumnHeight);
  287. }
  288. // Create the header text.
  289. for (uint32_t headerIdx = 0u; headerIdx < HeaderAttributeCount; headerIdx++)
  290. {
  291. const char* text = PipelineStatisticsAttributeHeader[headerIdx];
  292. const ImVec2 selectableSize = { m_headerColumnWidth[headerIdx], maxColumnHeight };
  293. // Sort when the selectable is clicked.
  294. bool columnSelected = (headerIdx == GetSortIndex());
  295. GpuProfilerImGuiHelper::WrappableSelectable(text, selectableSize, columnSelected, ImGuiSelectableFlags_None, [&, this]()
  296. {
  297. // Sort depending on the column index.
  298. const uint32_t sortIndex = GetSortIndex();
  299. // When the sort index is equal to the header index, it means that the same column has been selected, which
  300. // results in sorting the items in a inverted manner depending on the column's attribute.
  301. if (columnSelected)
  302. {
  303. const uint32_t baseSortIndex = sortIndex * SortVariantPerColumn;
  304. m_sortIndex = baseSortIndex + ((m_sortIndex + 1u) % SortVariantPerColumn);
  305. }
  306. else
  307. {
  308. // When the current header index and sort index are different, it means that a different column has been selected,
  309. // which results in sorting the items depending on the most recently selected column's attribute.
  310. m_sortIndex = headerIdx * SortVariantPerColumn;
  311. }
  312. });
  313. ImGui::NextColumn();
  314. }
  315. // Draw the RootPass' attribute row.
  316. CreateAttributeRow(rootPassEntry, nullptr);
  317. ImGui::Columns(1);
  318. ImGui::PopStyleVar();
  319. }
  320. // Draw the child window, consisting of the body of the matrix.
  321. {
  322. ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 4.0f));
  323. const ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoTitleBar;
  324. GpuProfilerImGuiHelper::BeginChild("AttributeMatrix", ImVec2(ImGui::GetWindowContentRegionWidth(), 320), false, window_flags, [&, this]()
  325. {
  326. ImGui::Columns(HeaderAttributeCount, "PipelineStatsisticsBody", false);
  327. for (const auto passEntry : m_passEntryReferences)
  328. {
  329. CreateAttributeRow(passEntry, rootPassEntry);
  330. }
  331. ImGui::Columns(1, "PipelineStatsisticsBody");
  332. });
  333. ImGui::PopStyleVar();
  334. }
  335. }
  336. ImGui::End();
  337. }
  338. void ImGuiPipelineStatisticsView::CreateAttributeRow(const PassEntry* passEntry, const PassEntry* rootEntry)
  339. {
  340. [[maybe_unused]] const uint32_t columnCount = static_cast<uint32_t>(ImGui::GetColumnsCount());
  341. AZ_Assert(columnCount == ImGuiPipelineStatisticsView::HeaderAttributeCount, "The column count needs to match HeaderAttributeCount.");
  342. ImGui::Separator();
  343. // Draw the pass name.
  344. {
  345. AZStd::string passName(passEntry->m_name.GetCStr());
  346. if (m_showPassTreeState)
  347. {
  348. const char* passTreeState = passEntry->m_isParent ? "Parent" : "Child";
  349. passName = AZStd::string::format("%s (%s)", passName.c_str(), passTreeState);
  350. }
  351. ImGui::Text("%s", passName.c_str());
  352. // Show a HoverMarker if the text is bigger than the column.
  353. const ImVec2 textSize = ImGui::CalcTextSize(passName.c_str());
  354. const uint32_t passNameIndex = 0u;
  355. // Set the column width.
  356. ImGui::SetColumnWidth(passNameIndex, m_headerColumnWidth[passNameIndex]);
  357. // Create a hover marker when the pass name exceeds the column width.
  358. if (textSize.x > m_headerColumnWidth[passNameIndex])
  359. {
  360. GpuProfilerImGuiHelper::HoverMarker(passName.c_str());
  361. }
  362. }
  363. ImGui::NextColumn();
  364. // Change the value(hsv) according to the normalized value.
  365. for (int32_t attributeIdx = 0; attributeIdx < PassEntry::PipelineStatisticsAttributeCount; attributeIdx++)
  366. {
  367. // Set the width of the column depending on the header column.
  368. const int32_t attributeHeaderIndex = attributeIdx + 1;
  369. ImGui::SetColumnWidth(attributeHeaderIndex, m_headerColumnWidth[attributeHeaderIndex]);
  370. // Calculate the normalized value if the RootEntry is valid.
  371. float normalized = 0.0f;
  372. if (rootEntry)
  373. {
  374. const double attributeLimit = static_cast<double>(rootEntry->m_pipelineStatistics[attributeIdx]);
  375. const double attribute = static_cast<double>(passEntry->m_pipelineStatistics[attributeIdx]);
  376. normalized = static_cast<float>(attribute / attributeLimit);
  377. }
  378. // Color code the cell depending on the contribution of the attribute to the attribute limit.
  379. ImVec4 rgb = { 0.0f, 0.0f, 0.0f, 1.0f };
  380. if (m_enableColorCoding)
  381. {
  382. // Interpolate in HSV, then convert hsv to rgb.
  383. const ImVec4 hsv = { 161.0f, 95.0f, normalized * 80.0f, 0.0f };
  384. ImGui::ColorConvertHSVtoRGB(hsv.x / 360.0f, hsv.y / 100.0f, hsv.z / 100.0f, rgb.x, rgb.y, rgb.z);
  385. }
  386. // Draw the attribute cell.
  387. GpuProfilerImGuiHelper::PushStyleColor(ImGuiCol_Header, rgb, [&, this]()
  388. {
  389. // Threshold to determine if a text needs to change to black.
  390. const float changeTextColorThreshold = 0.9f;
  391. // Make the text black if the cell becomes too bright.
  392. const bool textColorChanged = m_enableColorCoding && normalized > changeTextColorThreshold;
  393. if (textColorChanged)
  394. {
  395. const ImVec4 black = { 0.0f, 0.0f, 0.0f, 1.0f };
  396. ImGui::PushStyleColor(ImGuiCol_Text, black);
  397. }
  398. AZStd::string label;
  399. if (rootEntry && m_showAttributeContribution)
  400. {
  401. label = AZStd::string::format("%llu (%u%%)",
  402. static_cast<AZ::u64>(passEntry->m_pipelineStatistics[attributeIdx]),
  403. static_cast<uint32_t>(normalized * 100.0f));
  404. }
  405. else
  406. {
  407. label = AZStd::string::format("%llu",
  408. static_cast<AZ::u64>(passEntry->m_pipelineStatistics[attributeIdx]));
  409. }
  410. if (rootEntry)
  411. {
  412. ImGui::Selectable(label.c_str(), true);
  413. }
  414. else
  415. {
  416. ImGui::Text("%s", label.c_str());
  417. }
  418. if (textColorChanged)
  419. {
  420. ImGui::PopStyleColor();
  421. }
  422. });
  423. ImGui::NextColumn();
  424. }
  425. }
  426. void ImGuiPipelineStatisticsView::SortView()
  427. {
  428. const StatisticsSortType sortType = GetSortType();
  429. if (sortType == StatisticsSortType::Alphabetical)
  430. {
  431. // Sort depending on the PassEntry's names.
  432. AZStd::sort(m_passEntryReferences.begin(), m_passEntryReferences.end(), [this](const PassEntry* left, const PassEntry* right)
  433. {
  434. if (IsSortStateInverted())
  435. {
  436. AZStd::swap(left, right);
  437. }
  438. return left->m_name.GetStringView() < right->m_name.GetStringView();
  439. });
  440. }
  441. else if (sortType == StatisticsSortType::Numerical)
  442. {
  443. // Sort depending on a numerical attribute.
  444. AZStd::sort(m_passEntryReferences.begin(), m_passEntryReferences.end(), [this](const PassEntry* left, const PassEntry* right)
  445. {
  446. if (IsSortStateInverted())
  447. {
  448. AZStd::swap(left, right);
  449. }
  450. const uint32_t sortingIndex = GetSortIndex();
  451. AZ_Assert(sortingIndex != 0u, "Trying to sort on name");
  452. return left->m_pipelineStatistics[sortingIndex - 1u] > right->m_pipelineStatistics[sortingIndex - 1u];
  453. });
  454. }
  455. }
  456. uint32_t ImGuiPipelineStatisticsView::GetSortIndex() const
  457. {
  458. return m_sortIndex / SortVariantPerColumn;
  459. }
  460. ImGuiPipelineStatisticsView::StatisticsSortType ImGuiPipelineStatisticsView::GetSortType() const
  461. {
  462. // The first column (Pass Name) is the only column that requires the items to be sorted in an alphabetic manner.
  463. if (GetSortIndex() == 0u)
  464. {
  465. return StatisticsSortType::Alphabetical;
  466. }
  467. else
  468. {
  469. return StatisticsSortType::Numerical;
  470. }
  471. }
  472. bool ImGuiPipelineStatisticsView::IsSortStateInverted() const
  473. {
  474. return m_sortIndex % SortVariantPerColumn;
  475. }
  476. // --- ImGuiTimestampView ---
  477. void ImGuiTimestampView::DrawTimestampWindow(
  478. bool& draw, const PassEntry* rootPassEntry, AZStd::unordered_map<Name, PassEntry>& timestampEntryDatabase,
  479. AZ::RHI::Ptr<RPI::ParentPass> rootPass)
  480. {
  481. // Early out if nothing is supposed to be drawn
  482. if (!draw)
  483. {
  484. return;
  485. }
  486. // Clear the references from the previous frame.
  487. m_passEntryReferences.clear();
  488. // pass entry grid based on its timestamp
  489. AZStd::vector<PassEntry*> sortedPassEntries;
  490. AZStd::vector<AZStd::vector<PassEntry*>> sortedPassGrid;
  491. // Set the child of the parent, only if it passes the filter.
  492. for (auto& passEntryIt : timestampEntryDatabase)
  493. {
  494. PassEntry* passEntry = &passEntryIt.second;
  495. // Collect all pass entries with non-zero durations
  496. if (passEntry->m_timestampResult.GetDurationInTicks() > 0)
  497. {
  498. sortedPassEntries.push_back(passEntry);
  499. }
  500. // Skip the pass if the pass' timestamp duration is 0
  501. if (m_hideZeroPasses && (!passEntry->m_isParent) && passEntry->m_timestampResult.GetDurationInTicks() == 0)
  502. {
  503. continue;
  504. }
  505. // Only add pass if it pass the filter.
  506. if (m_passFilter.PassFilter(passEntry->m_name.GetCStr()))
  507. {
  508. if (passEntry->m_parent && !passEntry->m_linked)
  509. {
  510. passEntry->m_parent->LinkChild(passEntry);
  511. }
  512. AZ_Assert(
  513. m_passEntryReferences.size() < TimestampEntryCount,
  514. "Too many PassEntry references. Increase the size of the array.");
  515. m_passEntryReferences.push_back(passEntry);
  516. }
  517. }
  518. // Sort the pass entries based on their starting time and duration
  519. AZStd::sort(sortedPassEntries.begin(), sortedPassEntries.end(), [](const PassEntry* passEntry1, const PassEntry* passEntry2) {
  520. if (passEntry1->m_timestampResult.GetTimestampBeginInTicks() == passEntry2->m_timestampResult.GetTimestampBeginInTicks())
  521. {
  522. return passEntry1->m_timestampResult.GetDurationInTicks() < passEntry2->m_timestampResult.GetDurationInTicks();
  523. }
  524. return passEntry1->m_timestampResult.GetTimestampBeginInTicks() < passEntry2->m_timestampResult.GetTimestampBeginInTicks();
  525. });
  526. // calculate the total GPU duration.
  527. RPI::TimestampResult gpuTimestamp;
  528. if (sortedPassEntries.size() > 0)
  529. {
  530. gpuTimestamp = sortedPassEntries.front()->m_timestampResult;
  531. gpuTimestamp.Add(sortedPassEntries.back()->m_timestampResult);
  532. }
  533. // Add a pass to the pass grid which none of the pass's timestamp range won't overlap each other.
  534. // Search each row until the pass can be added to the end of row without overlap the previous one.
  535. for (auto& passEntry : sortedPassEntries)
  536. {
  537. auto row = sortedPassGrid.begin();
  538. for (; row != sortedPassGrid.end(); row++)
  539. {
  540. if (row->empty())
  541. {
  542. break;
  543. }
  544. auto last = (*row).back();
  545. if (passEntry->m_timestampResult.GetTimestampBeginInTicks() >=
  546. last->m_timestampResult.GetTimestampBeginInTicks() + last->m_timestampResult.GetDurationInTicks())
  547. {
  548. row->push_back(passEntry);
  549. break;
  550. }
  551. }
  552. if (row == sortedPassGrid.end())
  553. {
  554. sortedPassGrid.emplace_back().push_back(passEntry);
  555. }
  556. }
  557. // Refresh timestamp query
  558. bool needEnable = false;
  559. if (!m_paused)
  560. {
  561. if (m_refreshType == RefreshType::OncePerSecond)
  562. {
  563. auto now = AZStd::GetTimeNowMicroSecond();
  564. if (now - m_lastUpdateTimeMicroSecond > 1000000)
  565. {
  566. needEnable = true;
  567. m_lastUpdateTimeMicroSecond = now;
  568. }
  569. }
  570. else if (m_refreshType == RefreshType::Realtime)
  571. {
  572. needEnable = true;
  573. }
  574. }
  575. if (rootPass->IsTimestampQueryEnabled() != needEnable)
  576. {
  577. rootPass->SetTimestampQueryEnabled(needEnable);
  578. }
  579. const ImVec2 windowSize(680.0f, 620.0f);
  580. ImGui::SetNextWindowSize(windowSize, ImGuiCond_Once);
  581. if (ImGui::Begin("Timestamp View", &draw, ImGuiWindowFlags_None))
  582. {
  583. // Draw the header.
  584. {
  585. // Pause/unpause the profiling
  586. if (ImGui::Button(m_paused? "Resume":"Pause"))
  587. {
  588. m_paused = !m_paused;
  589. }
  590. // Draw the frame time (GPU).
  591. const AZStd::string formattedTimestamp = FormatTimestampLabel(gpuTimestamp.GetDurationInNanoseconds());
  592. const AZStd::string headerFrameTime = AZStd::string::format("Total frame duration (GPU): %s", formattedTimestamp.c_str());
  593. ImGui::Text("%s", headerFrameTime.c_str());
  594. // Draw the viewing option.
  595. ImGui::RadioButton("Hierarchical", reinterpret_cast<int32_t*>(&m_viewType), static_cast<int32_t>(ProfilerViewType::Hierarchical));
  596. ImGui::SameLine();
  597. ImGui::RadioButton("Flat", reinterpret_cast<int32_t*>(&m_viewType), static_cast<int32_t>(ProfilerViewType::Flat));
  598. // Draw the refresh option
  599. ImGui::RadioButton("Realtime", reinterpret_cast<int32_t*>(&m_refreshType), static_cast<int32_t>(RefreshType::Realtime));
  600. ImGui::SameLine();
  601. ImGui::RadioButton("Once Per Second", reinterpret_cast<int32_t*>(&m_refreshType), static_cast<int32_t>(RefreshType::OncePerSecond));
  602. // Show/hide non-parent passes which have zero execution time
  603. ImGui::Checkbox("Hide Zero Cost Passes", &m_hideZeroPasses);
  604. // Show/hide the timeline bar of all the passes which has non-zero execution time
  605. ImGui::Checkbox("Show Timeline", &m_showTimeline);
  606. // Draw advanced options.
  607. const ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_None;
  608. GpuProfilerImGuiHelper::TreeNode("Advanced options", flags, [this](bool unrolled)
  609. {
  610. if (unrolled)
  611. {
  612. // Draw the timestamp metric unit option.
  613. ImGui::RadioButton("Timestamp in ms", reinterpret_cast<int32_t*>(&m_timestampMetricUnit), static_cast<int32_t>(TimestampMetricUnit::Milliseconds));
  614. ImGui::SameLine();
  615. ImGui::RadioButton("Timestamp in ns", reinterpret_cast<int32_t*>(&m_timestampMetricUnit), static_cast<int32_t>(TimestampMetricUnit::Nanoseconds));
  616. // Draw the frame load view option.
  617. ImGui::RadioButton("Frame load in 30 FPS", reinterpret_cast<int32_t*>(&m_frameWorkloadView), static_cast<int32_t>(FrameWorkloadView::FpsView30));
  618. ImGui::SameLine();
  619. ImGui::RadioButton("Frame load in 60 FPS", reinterpret_cast<int32_t*>(&m_frameWorkloadView), static_cast<int32_t>(FrameWorkloadView::FpsView60));
  620. }
  621. });
  622. }
  623. ImGui::Separator();
  624. // Draw the pass entry grid
  625. if (!sortedPassEntries.empty() && m_showTimeline)
  626. {
  627. const float passBarHeight = 20.f;
  628. const float passBarSpace = 3.f;
  629. float areaWidth = ImGui::GetContentRegionAvail().x - 20.f;
  630. if (ImGui::BeginChild("Timeline", ImVec2(areaWidth, (passBarHeight + passBarSpace) * sortedPassGrid.size()), false))
  631. {
  632. // start tick and end tick for the area
  633. uint64_t areaStartTick = sortedPassEntries.front()->m_timestampResult.GetTimestampBeginInTicks();
  634. uint64_t areaEndTick = sortedPassEntries.back()->m_timestampResult.GetTimestampBeginInTicks() +
  635. sortedPassEntries.back()->m_timestampResult.GetDurationInTicks();
  636. uint64_t areaDurationInTicks = areaEndTick - areaStartTick;
  637. float rowStartY = 0.f;
  638. for (auto& row : sortedPassGrid)
  639. {
  640. // row start y
  641. for (auto passEntry : row)
  642. {
  643. // button start and end
  644. float buttonStartX = (passEntry->m_timestampResult.GetTimestampBeginInTicks() - areaStartTick) * areaWidth /
  645. areaDurationInTicks;
  646. float buttonWidth = passEntry->m_timestampResult.GetDurationInTicks() * areaWidth / areaDurationInTicks;
  647. ImGui::SetCursorPosX(buttonStartX);
  648. ImGui::SetCursorPosY(rowStartY);
  649. // Adds a button and the hover colors.
  650. ImGui::Button(passEntry->m_name.GetCStr(), ImVec2(buttonWidth, passBarHeight));
  651. if (ImGui::IsItemHovered())
  652. {
  653. ImGui::BeginTooltip();
  654. ImGui::Text("Name: %s", passEntry->m_name.GetCStr());
  655. ImGui::Text("Path: %s", passEntry->m_path.GetCStr());
  656. ImGui::Text("Duration in ticks: %llu", static_cast<AZ::u64>(passEntry->m_timestampResult.GetDurationInTicks()));
  657. ImGui::Text("Duration in microsecond: %.3f us", passEntry->m_timestampResult.GetDurationInNanoseconds()/1000.f);
  658. ImGui::EndTooltip();
  659. }
  660. }
  661. rowStartY += passBarHeight + passBarSpace;
  662. }
  663. }
  664. ImGui::EndChild();
  665. ImGui::Separator();
  666. }
  667. // Draw the timestamp view.
  668. {
  669. static const AZStd::array<const char*, static_cast<int32_t>(TimestampMetricUnit::Count)> MetricUnitText =
  670. {
  671. {
  672. "ms",
  673. "ns",
  674. }
  675. };
  676. static const AZStd::array<const char*, static_cast<int32_t>(FrameWorkloadView::Count)> FrameWorkloadUnit =
  677. {
  678. {
  679. "30",
  680. "60",
  681. }
  682. };
  683. m_passFilter.Draw("Pass Name Filter");
  684. if (ImGui::BeginChild("Passes"))
  685. {
  686. // Set column settings.
  687. ImGui::Columns(3, "view", false);
  688. ImGui::SetColumnWidth(0, 340.0f);
  689. ImGui::SetColumnWidth(1, 100.0f);
  690. if (m_viewType == ProfilerViewType::Hierarchical)
  691. {
  692. // Set the tab header.
  693. {
  694. ImGui::Text("Pass Names");
  695. ImGui::NextColumn();
  696. // Render the text depending on the metric unit.
  697. {
  698. const int32_t timestampMetricUnitNumeric = static_cast<int32_t>(m_timestampMetricUnit);
  699. const AZStd::string metricUnitText = AZStd::string::format("Time in %s", MetricUnitText[timestampMetricUnitNumeric]);
  700. ImGui::Text("%s", metricUnitText.c_str());
  701. ImGui::NextColumn();
  702. }
  703. // Render the text depending on the metric unit.
  704. {
  705. const int32_t frameWorkloadViewNumeric = static_cast<int32_t>(m_frameWorkloadView);
  706. const AZStd::string frameWorkloadViewText = AZStd::string::format("Frame workload in %s FPS", FrameWorkloadUnit[frameWorkloadViewNumeric]);
  707. ImGui::Text("%s", frameWorkloadViewText.c_str());
  708. ImGui::NextColumn();
  709. }
  710. ImGui::Separator();
  711. }
  712. // Draw the hierarchical view.
  713. DrawHierarchicalView(rootPassEntry);
  714. }
  715. else if (m_viewType == ProfilerViewType::Flat)
  716. {
  717. // Set the tab header.
  718. {
  719. // Check whether it should be sorted by name.
  720. const uint32_t sortType = static_cast<uint32_t>(m_sortType);
  721. AZ_PUSH_DISABLE_WARNING(4296, "-Wunknown-warning-option")
  722. bool sortByName = (sortType >= static_cast<uint32_t>(ProfilerSortType::Alphabetical) &&
  723. (sortType < static_cast<uint32_t>(ProfilerSortType::AlphabeticalCount)));
  724. AZ_POP_DISABLE_WARNING
  725. if (ImGui::Selectable("Pass Names", sortByName))
  726. {
  727. ToggleOrSwitchSortType(ProfilerSortType::Alphabetical, ProfilerSortType::AlphabeticalCount);
  728. }
  729. ImGui::NextColumn();
  730. if (ImGui::Selectable("Time in ms", !sortByName))
  731. {
  732. ToggleOrSwitchSortType(ProfilerSortType::Timestamp, ProfilerSortType::TimestampCount);
  733. }
  734. ImGui::NextColumn();
  735. const int32_t frameWorkloadViewNumeric = static_cast<int32_t>(m_frameWorkloadView);
  736. const AZStd::string frameWorkloadViewText = AZStd::string::format("Frame workload in %s FPS", FrameWorkloadUnit[frameWorkloadViewNumeric]);
  737. ImGui::Text("%s", frameWorkloadViewText.c_str());
  738. ImGui::NextColumn();
  739. }
  740. ImGui::Separator();
  741. // Create the sorting buttons.
  742. SortFlatView();
  743. DrawFlatView();
  744. }
  745. else
  746. {
  747. AZ_Assert(false, "Invalid ViewType.");
  748. }
  749. // Set back to default.
  750. ImGui::Columns(1, "view", false);
  751. }
  752. ImGui::EndChild();
  753. }
  754. }
  755. ImGui::End();
  756. }
  757. void ImGuiTimestampView::DrawFrameWorkloadBar(double value) const
  758. {
  759. // Interpolate the color of the bar depending on the load.
  760. const float fvalue = AZStd::clamp(static_cast<float>(value), 0.0f, 1.0f);
  761. static const Vector3 lowHSV(161.0f / 360.0f, 95.0f / 100.0f, 80.0f / 100.0f);
  762. static const Vector3 highHSV(1.0f / 360.0f, 68.0f / 100.0f, 80.0f / 100.0f);
  763. const Vector3 colorHSV = lowHSV + (highHSV - lowHSV) * fvalue;
  764. ImGui::PushStyleColor(ImGuiCol_PlotHistogram, static_cast<ImVec4>(ImColor::HSV(colorHSV.GetX(), colorHSV.GetY(), colorHSV.GetZ())));
  765. ImGui::ProgressBar(fvalue);
  766. ImGui::PopStyleColor(1);
  767. }
  768. void ImGuiTimestampView::DrawHierarchicalView(const PassEntry* entry) const
  769. {
  770. const AZStd::string entryTime = FormatTimestampLabel(entry->m_interpolatedTimestampInNanoseconds);
  771. const auto drawWorkloadBar = [this](const AZStd::string& entryTime, const PassEntry* entry)
  772. {
  773. ImGui::NextColumn();
  774. if (entry->m_isParent)
  775. {
  776. ImGui::NextColumn();
  777. ImGui::NextColumn();
  778. }
  779. else
  780. {
  781. ImGui::Text("%s", entryTime.c_str());
  782. ImGui::NextColumn();
  783. DrawFrameWorkloadBar(NormalizeFrameWorkload(entry->m_interpolatedTimestampInNanoseconds));
  784. ImGui::NextColumn();
  785. }
  786. };
  787. static const auto createHoverMarker = [](const char* text)
  788. {
  789. const ImVec2 textSize = ImGui::CalcTextSize(text);
  790. const int32_t passNameColumnIndex = 0;
  791. if (textSize.x + ImGui::GetCursorPosX() > ImGui::GetColumnWidth(passNameColumnIndex))
  792. {
  793. GpuProfilerImGuiHelper::HoverMarker(text);
  794. }
  795. };
  796. if (entry->m_children.empty())
  797. {
  798. // Draw the workload bar when it doesn't have children.
  799. ImGui::Text("%s", entry->m_name.GetCStr());
  800. // Show a HoverMarker if the text is bigger than the column.
  801. createHoverMarker(entry->m_name.GetCStr());
  802. drawWorkloadBar(entryTime, entry);
  803. }
  804. else
  805. {
  806. // Recursively create another tree node.
  807. const ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_DefaultOpen;
  808. GpuProfilerImGuiHelper::TreeNode(entry->m_name.GetCStr(), flags, [&drawWorkloadBar, &entryTime, entry, this](bool unrolled)
  809. {
  810. // Show a HoverMarker if the text is bigger than the column.
  811. createHoverMarker(entry->m_name.GetCStr());
  812. drawWorkloadBar(entryTime, entry);
  813. if (unrolled)
  814. {
  815. for (const PassEntry* child : entry->m_children)
  816. {
  817. DrawHierarchicalView(child);
  818. }
  819. }
  820. });
  821. }
  822. }
  823. void ImGuiTimestampView::SortFlatView()
  824. {
  825. const uint32_t ProfilerSortTypeCount = static_cast<uint32_t>(ProfilerSortType::Count);
  826. using SortTypeAndFunctionPair = AZStd::pair<ProfilerSortType, AZStd::function<bool(PassEntry*, PassEntry*)>>;
  827. static const AZStd::array<SortTypeAndFunctionPair, ProfilerSortTypeCount> profilerSortMap =
  828. {
  829. {
  830. AZStd::make_pair(ProfilerSortType::Alphabetical, [](PassEntry* left, PassEntry* right) {return left->m_name.GetStringView() < right->m_name.GetStringView(); }),
  831. AZStd::make_pair(ProfilerSortType::AlphabeticalInverse, [](PassEntry* left, PassEntry* right) {return left->m_name.GetStringView() > right->m_name.GetStringView(); }),
  832. AZStd::make_pair(ProfilerSortType::Timestamp, [](PassEntry* left, PassEntry* right) {return left->m_interpolatedTimestampInNanoseconds > right->m_interpolatedTimestampInNanoseconds; }),
  833. AZStd::make_pair(ProfilerSortType::TimestampInverse, [](PassEntry* left, PassEntry* right) {return left->m_interpolatedTimestampInNanoseconds < right->m_interpolatedTimestampInNanoseconds; })
  834. }
  835. };
  836. auto it = AZStd::find_if(profilerSortMap.begin(), profilerSortMap.end(), [this](const SortTypeAndFunctionPair& sortTypeAndFunctionPair)
  837. {
  838. return sortTypeAndFunctionPair.first == m_sortType;
  839. });
  840. AZ_Assert(it != profilerSortMap.end(), "The functor associated with the SortType doesn't exist");
  841. AZStd::sort(m_passEntryReferences.begin(), m_passEntryReferences.end(), it->second);
  842. }
  843. void ImGuiTimestampView::DrawFlatView() const
  844. {
  845. // Draw the flat view.
  846. for (const PassEntry* entry : m_passEntryReferences)
  847. {
  848. if (entry->m_isParent)
  849. {
  850. continue;
  851. }
  852. const AZStd::string entryTime = FormatTimestampLabel(entry->m_interpolatedTimestampInNanoseconds);
  853. ImGui::Text("%s", entry->m_name.GetCStr());
  854. ImGui::NextColumn();
  855. ImGui::Text("%s", entryTime.c_str());
  856. ImGui::NextColumn();
  857. DrawFrameWorkloadBar(NormalizeFrameWorkload(entry->m_interpolatedTimestampInNanoseconds));
  858. ImGui::NextColumn();
  859. }
  860. }
  861. double ImGuiTimestampView::NanoToMilliseconds(uint64_t nanoseconds) const
  862. {
  863. // Nanoseconds to Milliseconds inverse multiplier (1 / 1000000)
  864. const double inverseMultiplier = 0.000001;
  865. return static_cast<double>(nanoseconds) * inverseMultiplier;
  866. }
  867. void ImGuiTimestampView::ToggleOrSwitchSortType(ProfilerSortType start, ProfilerSortType count)
  868. {
  869. const uint32_t startNumerical = static_cast<uint32_t>(start);
  870. const uint32_t countNumerical = static_cast<uint32_t>(count);
  871. const uint32_t offset = static_cast<uint32_t>(m_sortType) - startNumerical;
  872. if (offset < countNumerical)
  873. {
  874. // Change the sorting order.
  875. m_sortType = static_cast<ProfilerSortType>(((offset + 1u) % countNumerical) + startNumerical);
  876. }
  877. else
  878. {
  879. // Change the sorting type.
  880. m_sortType = start;
  881. }
  882. }
  883. double ImGuiTimestampView::NormalizeFrameWorkload(uint64_t timestamp) const
  884. {
  885. static const AZStd::array<double, static_cast<int32_t>(FrameWorkloadView::Count)> TimestampToViewMap =
  886. {
  887. {
  888. 33000000.0,
  889. 16000000.0,
  890. }
  891. };
  892. const int32_t frameWorkloadViewNumeric = static_cast<int32_t>(m_frameWorkloadView);
  893. AZ_Assert(frameWorkloadViewNumeric <= TimestampToViewMap.size(), "The frame workload view is invalid.");
  894. return static_cast<double>(timestamp) / TimestampToViewMap[frameWorkloadViewNumeric];
  895. }
  896. AZStd::string ImGuiTimestampView::FormatTimestampLabel(uint64_t timestamp) const
  897. {
  898. if (m_timestampMetricUnit == TimestampMetricUnit::Milliseconds)
  899. {
  900. const char* timeFormat = "%.4f %s";
  901. const char* timeType = "ms";
  902. const double timestampInMs = NanoToMilliseconds(timestamp);
  903. return AZStd::string::format(timeFormat, timestampInMs, timeType);
  904. }
  905. else if (m_timestampMetricUnit == TimestampMetricUnit::Nanoseconds)
  906. {
  907. const char* timeFormat = "%llu %s";
  908. const char* timeType = "ns";
  909. return AZStd::string::format(timeFormat, timestamp, timeType);
  910. }
  911. else
  912. {
  913. return AZStd::string("Invalid");
  914. }
  915. }
  916. // --- ImGuiGpuMemoryView ---
  917. ImGuiGpuMemoryView::ImGuiGpuMemoryView()
  918. {
  919. AZ::IO::Path path = AZ::Utils::GetO3deLogsDirectory().c_str();
  920. path /= "MemoryCaptures";
  921. AZ::IO::SystemFile::CreateDir(path.c_str());
  922. m_memoryCapturePath = path.c_str();
  923. }
  924. ImGuiGpuMemoryView::~ImGuiGpuMemoryView()
  925. {
  926. if (m_hostTreemap)
  927. {
  928. if (auto treemapFactory = Profiler::ImGuiTreemapFactory::Interface::Get())
  929. {
  930. treemapFactory->Destroy(m_hostTreemap);
  931. treemapFactory->Destroy(m_deviceTreemap);
  932. }
  933. }
  934. }
  935. void ImGuiGpuMemoryView::SortPoolTable(ImGuiTableSortSpecs* sortSpecs)
  936. {
  937. const bool ascending = sortSpecs->Specs->SortDirection == ImGuiSortDirection_Ascending;
  938. const ImS16 columnToSort = sortSpecs->Specs->ColumnIndex;
  939. // Sort by the appropriate column in the table
  940. switch (columnToSort)
  941. {
  942. case (0): // Sort by pool name
  943. AZStd::sort(m_poolTableRows.begin(), m_poolTableRows.end(),
  944. [ascending](const PoolTableRow& lhs, const PoolTableRow& rhs)
  945. {
  946. const auto lhsParentPool = lhs.m_poolName.GetStringView();
  947. const auto rhsParentPool = rhs.m_poolName.GetStringView();
  948. return ascending ? lhsParentPool < rhsParentPool : lhsParentPool > rhsParentPool;
  949. });
  950. break;
  951. case (1): // Sort by pool type
  952. AZStd::sort(m_poolTableRows.begin(), m_poolTableRows.end(),
  953. [ascending](const PoolTableRow& lhs, const PoolTableRow& rhs)
  954. {
  955. const auto lhsHeapType = lhs.m_deviceHeap ? 0 : 1;
  956. const auto rhsHeapType = rhs.m_deviceHeap ? 0 : 1;
  957. return ascending ? lhsHeapType < rhsHeapType : lhsHeapType > rhsHeapType;
  958. });
  959. break;
  960. case (2): // Sort by budget
  961. AZStd::sort(m_poolTableRows.begin(), m_poolTableRows.end(),
  962. [ascending](const PoolTableRow& lhs, const PoolTableRow& rhs)
  963. {
  964. const float lhsBudget = static_cast<float>(lhs.m_budgetBytes);
  965. const float rhsBudget = static_cast<float>(rhs.m_budgetBytes);
  966. return ascending ? lhsBudget < rhsBudget : lhsBudget > rhsBudget;
  967. });
  968. break;
  969. case (3): // Sort by reservation
  970. AZStd::sort(m_poolTableRows.begin(), m_poolTableRows.end(),
  971. [ascending](const PoolTableRow& lhs, const PoolTableRow& rhs)
  972. {
  973. const float lhsReservation = static_cast<float>(lhs.m_allocatedBytes);
  974. const float rhsReservation = static_cast<float>(rhs.m_allocatedBytes);
  975. return ascending ? lhsReservation < rhsReservation : lhsReservation > rhsReservation;
  976. });
  977. break;
  978. case (4): // Sort by residency
  979. AZStd::sort(m_poolTableRows.begin(), m_poolTableRows.end(),
  980. [ascending](const PoolTableRow& lhs, const PoolTableRow& rhs)
  981. {
  982. const float lhsResidency = static_cast<float>(lhs.m_usedBytes);
  983. const float rhsResidency = static_cast<float>(rhs.m_usedBytes);
  984. return ascending ? lhsResidency < rhsResidency : lhsResidency > rhsResidency;
  985. });
  986. break;
  987. case (5): // Sort by fragmentation
  988. AZStd::sort(m_poolTableRows.begin(), m_poolTableRows.end(),
  989. [ascending](const PoolTableRow& lhs, const PoolTableRow& rhs)
  990. {
  991. const float lhsSize = static_cast<float>(lhs.m_fragmentation);
  992. const float rhsSize = static_cast<float>(rhs.m_fragmentation);
  993. return ascending ? lhsSize < rhsSize : lhsSize > rhsSize;
  994. });
  995. break;
  996. }
  997. sortSpecs->SpecsDirty = false;
  998. }
  999. void ImGuiGpuMemoryView::SortResourceTable(ImGuiTableSortSpecs* sortSpecs)
  1000. {
  1001. const bool ascending = sortSpecs->Specs->SortDirection == ImGuiSortDirection_Ascending;
  1002. const ImS16 columnToSort = sortSpecs->Specs->ColumnIndex;
  1003. // Sort by the appropriate column in the table
  1004. switch (columnToSort)
  1005. {
  1006. case (0): // Sorting by parent pool name
  1007. AZStd::sort(m_resourceTableRows.begin(), m_resourceTableRows.end(),
  1008. [ascending](const ResourceTableRow& lhs, const ResourceTableRow& rhs)
  1009. {
  1010. const auto lhsParentPool = lhs.m_parentPoolName.GetStringView();
  1011. const auto rhsParentPool = rhs.m_parentPoolName.GetStringView();
  1012. return ascending ? lhsParentPool < rhsParentPool : lhsParentPool > rhsParentPool;
  1013. });
  1014. break;
  1015. case (1): // Sort by buffer/image name
  1016. AZStd::sort(m_resourceTableRows.begin(), m_resourceTableRows.end(),
  1017. [ascending](const ResourceTableRow& lhs, const ResourceTableRow& rhs)
  1018. {
  1019. const auto lhsName = lhs.m_bufImgName.GetStringView();
  1020. const auto rhsName = rhs.m_bufImgName.GetStringView();
  1021. return ascending ? lhsName < rhsName : lhsName > rhsName;
  1022. });
  1023. break;
  1024. case (2): // Sort by memory usage
  1025. AZStd::sort(m_resourceTableRows.begin(), m_resourceTableRows.end(),
  1026. [ascending](const ResourceTableRow& lhs, const ResourceTableRow& rhs)
  1027. {
  1028. const float lhsSize = static_cast<float>(lhs.m_sizeInBytes);
  1029. const float rhsSize = static_cast<float>(rhs.m_sizeInBytes);
  1030. return ascending ? lhsSize < rhsSize : lhsSize > rhsSize;
  1031. });
  1032. break;
  1033. case (3): // Sort by fragmentation
  1034. AZStd::sort(m_resourceTableRows.begin(), m_resourceTableRows.end(),
  1035. [ascending](const ResourceTableRow& lhs, const ResourceTableRow& rhs)
  1036. {
  1037. const float lhsSize = static_cast<float>(lhs.m_fragmentation);
  1038. const float rhsSize = static_cast<float>(rhs.m_fragmentation);
  1039. return ascending ? lhsSize < rhsSize : lhsSize > rhsSize;
  1040. });
  1041. break;
  1042. }
  1043. sortSpecs->SpecsDirty = false;
  1044. }
  1045. void ImGuiGpuMemoryView::DrawTables()
  1046. {
  1047. if (m_poolTableRows.empty())
  1048. {
  1049. return;
  1050. }
  1051. if (ImGui::CollapsingHeader("Buffer Pools", ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_Framed))
  1052. {
  1053. if (ImGui::BeginTable("PoolTable", 7, ImGuiTableFlags_Borders | ImGuiTableFlags_Sortable | ImGuiTableFlags_Resizable))
  1054. {
  1055. ImGui::TableSetupColumn("Pool");
  1056. ImGui::TableSetupColumn("Heap Type");
  1057. ImGui::TableSetupColumn("Budget (MB)");
  1058. ImGui::TableSetupColumn("Allocated (MB)");
  1059. ImGui::TableSetupColumn("Used (MB)");
  1060. ImGui::TableSetupColumn("Fragmentation (%)");
  1061. ImGui::TableSetupColumn("Unique (MB)");
  1062. ImGui::TableHeadersRow();
  1063. ImGui::TableNextColumn();
  1064. ImGuiTableSortSpecs* sortSpecs = ImGui::TableGetSortSpecs();
  1065. if (sortSpecs && sortSpecs->SpecsDirty)
  1066. {
  1067. SortPoolTable(sortSpecs);
  1068. }
  1069. for (const auto& tableRow : m_poolTableRows)
  1070. {
  1071. ImGui::Text("%s", tableRow.m_poolName.GetCStr());
  1072. ImGui::TableNextColumn();
  1073. ImGui::Text("%s", tableRow.m_deviceHeap ? "Device" : "Host");
  1074. ImGui::TableNextColumn();
  1075. ImGui::Text("%.4f", 1.0f * tableRow.m_budgetBytes / GpuProfilerImGuiHelper::MB);
  1076. ImGui::TableNextColumn();
  1077. ImGui::Text("%.4f", 1.0f * tableRow.m_allocatedBytes / GpuProfilerImGuiHelper::MB);
  1078. ImGui::TableNextColumn();
  1079. ImGui::Text("%.4f", 1.0f * tableRow.m_usedBytes / GpuProfilerImGuiHelper::MB);
  1080. ImGui::TableNextColumn();
  1081. ImGui::Text("%.4f", tableRow.m_fragmentation);
  1082. ImGui::TableNextColumn();
  1083. ImGui::Text("%.4f", 1.0f * tableRow.m_uniqueBytes / GpuProfilerImGuiHelper::MB);
  1084. ImGui::TableNextColumn();
  1085. }
  1086. }
  1087. ImGui::EndTable();
  1088. }
  1089. if (ImGui::CollapsingHeader("Allocations", ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_Framed))
  1090. {
  1091. if (ImGui::BeginTable("Table", 5, ImGuiTableFlags_Borders | ImGuiTableFlags_Sortable | ImGuiTableFlags_Resizable))
  1092. {
  1093. ImGui::TableSetupColumn("Parent pool");
  1094. ImGui::TableSetupColumn("Name");
  1095. ImGui::TableSetupColumn("Size (MB)");
  1096. ImGui::TableSetupColumn("Fragmentation (%)");
  1097. ImGui::TableSetupColumn("BindFlags", ImGuiTableColumnFlags_NoSort);
  1098. ImGui::TableHeadersRow();
  1099. ImGui::TableNextColumn();
  1100. ImGuiTableSortSpecs* sortSpecs = ImGui::TableGetSortSpecs();
  1101. if (sortSpecs && sortSpecs->SpecsDirty)
  1102. {
  1103. SortResourceTable(sortSpecs);
  1104. }
  1105. // Draw each row in the table
  1106. for (const auto& tableRow : m_resourceTableRows)
  1107. {
  1108. // Don't draw the row if none of the row's text fields pass the filter
  1109. if (!m_nameFilter.PassFilter(tableRow.m_parentPoolName.GetCStr())
  1110. && !m_nameFilter.PassFilter(tableRow.m_bufImgName.GetCStr())
  1111. && !m_nameFilter.PassFilter(tableRow.m_bindFlags.c_str()))
  1112. {
  1113. continue;
  1114. }
  1115. ImGui::Text("%s", tableRow.m_parentPoolName.GetCStr());
  1116. ImGui::TableNextColumn();
  1117. ImGui::Text("%s", tableRow.m_bufImgName.GetCStr());
  1118. ImGui::TableNextColumn();
  1119. ImGui::Text("%.4f", 1.0f * tableRow.m_sizeInBytes / GpuProfilerImGuiHelper::MB);
  1120. ImGui::TableNextColumn();
  1121. ImGui::Text("%.4f", tableRow.m_fragmentation);
  1122. ImGui::TableNextColumn();
  1123. ImGui::Text("%s", tableRow.m_bindFlags.c_str());
  1124. ImGui::TableNextColumn();
  1125. }
  1126. }
  1127. ImGui::EndTable();
  1128. }
  1129. }
  1130. void ImGuiGpuMemoryView::UpdateTableRows()
  1131. {
  1132. // Update the table according to the latest filters applied
  1133. m_poolTableRows.clear();
  1134. m_resourceTableRows.clear();
  1135. for (const auto& pool : m_savedPools)
  1136. {
  1137. Name poolName = pool.m_name.IsEmpty() ? Name("Unnamed pool") : pool.m_name;
  1138. auto& deviceHeapUsage = pool.m_memoryUsage.GetHeapMemoryUsage(AZ::RHI::HeapMemoryLevel::Device);
  1139. auto& hostHeapUsage = pool.m_memoryUsage.GetHeapMemoryUsage(AZ::RHI::HeapMemoryLevel::Host);
  1140. if ((!m_hideEmptyBufferPools || deviceHeapUsage.m_totalResidentInBytes > 0) && deviceHeapUsage.m_totalResidentInBytes < static_cast<size_t>(-1))
  1141. {
  1142. m_poolTableRows.push_back({ poolName, true, deviceHeapUsage.m_budgetInBytes, deviceHeapUsage.m_totalResidentInBytes,
  1143. deviceHeapUsage.m_usedResidentInBytes, deviceHeapUsage.m_fragmentation, deviceHeapUsage.m_uniqueAllocationBytes });
  1144. }
  1145. if ((!m_hideEmptyBufferPools || hostHeapUsage.m_totalResidentInBytes > 0) && hostHeapUsage.m_totalResidentInBytes < static_cast<size_t>(-1))
  1146. {
  1147. m_poolTableRows.push_back({ poolName, false, hostHeapUsage.m_budgetInBytes, hostHeapUsage.m_totalResidentInBytes,
  1148. hostHeapUsage.m_usedResidentInBytes, hostHeapUsage.m_fragmentation, hostHeapUsage.m_uniqueAllocationBytes });
  1149. }
  1150. // Ignore transient pools
  1151. if (!m_includeTransientAttachments && pool.m_name.GetStringView().contains("Transient"))
  1152. {
  1153. continue;
  1154. }
  1155. if (m_includeBuffers)
  1156. {
  1157. for (const auto& buf : pool.m_buffers)
  1158. {
  1159. const Name bufName = buf.m_name.IsEmpty() ? Name("Unnamed Buffer") : buf.m_name;
  1160. const AZStd::string flags = GpuProfilerImGuiHelper::GetBufferBindStrings(buf.m_bindFlags);
  1161. m_resourceTableRows.push_back({ poolName, bufName, buf.m_sizeInBytes, buf.m_fragmentation, flags });
  1162. }
  1163. }
  1164. if (m_includeImages)
  1165. {
  1166. for (const auto& img : pool.m_images)
  1167. {
  1168. const Name imgName = img.m_name.IsEmpty() ? Name("Unnamed Image") : img.m_name;
  1169. const AZStd::string flags = GpuProfilerImGuiHelper::GetImageBindStrings(img.m_bindFlags);
  1170. m_resourceTableRows.push_back({ poolName, imgName, img.m_sizeInBytes, 0.f, flags });
  1171. }
  1172. }
  1173. }
  1174. }
  1175. void ImGuiGpuMemoryView::DrawPieChart(const AZ::RHI::MemoryStatistics::Heap& heap)
  1176. {
  1177. if (ImGui::BeginChild("PieChart", {150, 150}, true))
  1178. {
  1179. ImDrawList* drawList = ImGui::GetWindowDrawList();
  1180. const auto [wx, wy] = ImGui::GetWindowPos();
  1181. const auto [windowWidth, windowHeight] = ImGui::GetWindowSize();
  1182. const ImVec2 center = { wx + windowWidth / 2, wy + windowHeight / 2 };
  1183. const float radius = windowWidth / 2 - 10;
  1184. // Draw the pie chart
  1185. drawList->AddCircleFilled(center, radius, ImGui::GetColorU32({.3, .3, .3, 1}));
  1186. const float usagePercent = 1.0f * heap.m_memoryUsage.m_totalResidentInBytes / heap.m_memoryUsage.m_budgetInBytes;
  1187. drawList->PathArcTo(center, radius, 0, AZ::Constants::TwoPi * usagePercent); // Clockwise starting from rightmost point
  1188. drawList->PathArcTo(center, 0, 0, 0); // To center
  1189. drawList->PathArcTo(center, radius, 0, 0); // Back to starting position
  1190. drawList->PathFillConvex(ImGui::GetColorU32({ .039, .8, 0.556, 1 }));
  1191. ImGui::Text("%.2f%%", usagePercent * 100);
  1192. }
  1193. ImGui::EndChild();
  1194. }
  1195. void ImGuiGpuMemoryView::PerformCapture()
  1196. {
  1197. // Collect and save new GPU memory usage data
  1198. RHI::RHIMemoryStatisticsInterface* rhiMemStats = RHI::RHIMemoryStatisticsInterface::Get();
  1199. const auto* memoryStatistics = rhiMemStats->GetMemoryStatistics();
  1200. if (memoryStatistics)
  1201. {
  1202. m_savedPools = memoryStatistics->m_pools;
  1203. m_savedHeaps = memoryStatistics->m_heaps;
  1204. // Collect the data into TableRows, ignoring depending on flags
  1205. UpdateTableRows();
  1206. UpdateTreemaps();
  1207. }
  1208. }
  1209. void ImGuiGpuMemoryView::DrawGpuMemoryWindow(bool& draw)
  1210. {
  1211. // Enable GPU memory instrumentation while the window is open. Called every draw frame, but just a bitwise operation so overhead should be low.
  1212. auto* rhiSystem = AZ::RHI::RHISystemInterface::Get();
  1213. AZ_Assert(rhiSystem != nullptr, "Error in drawing GPU memory window: RHI System Interface was nullptr");
  1214. rhiSystem->ModifyFrameSchedulerStatisticsFlags(AZ::RHI::FrameSchedulerStatisticsFlags::GatherMemoryStatistics, draw);
  1215. if (!draw)
  1216. {
  1217. return;
  1218. }
  1219. ImGui::SetNextWindowSize({ 600, 600 }, ImGuiCond_Once);
  1220. if (ImGui::Begin("Gpu Memory Profiler", &draw, ImGuiViewportFlags_None))
  1221. {
  1222. if (ImGui::Button("Capture"))
  1223. {
  1224. m_captureMessage.clear();
  1225. m_loadedCapturePath.clear();
  1226. PerformCapture();
  1227. }
  1228. ImGui::SameLine();
  1229. if (ImGui::Button("Save"))
  1230. {
  1231. if (m_savedPools.empty())
  1232. {
  1233. m_captureMessage.clear();
  1234. PerformCapture();
  1235. }
  1236. SaveToJSON();
  1237. }
  1238. ImGui::SameLine();
  1239. constexpr static const char* LoadMemoryCaptureTitle = "Select or input memory capture csv file";
  1240. if (ImGui::Button("Load"))
  1241. {
  1242. m_captureInput[0] = '\0';
  1243. m_captureSelection = 0;
  1244. ImGui::OpenPopup(LoadMemoryCaptureTitle);
  1245. }
  1246. // Always center this window when appearing
  1247. ImVec2 center = ImGui::GetMainViewport()->GetCenter();
  1248. ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
  1249. if (ImGui::BeginPopupModal(LoadMemoryCaptureTitle, nullptr, ImGuiWindowFlags_AlwaysAutoResize))
  1250. {
  1251. AZStd::vector<AZ::IO::Path> captures;
  1252. // Enumerate files in the capture folder
  1253. auto* base = AZ::IO::FileIOBase::GetInstance();
  1254. base->FindFiles(
  1255. m_memoryCapturePath.c_str(), "*.csv",
  1256. [&captures](const char* path)
  1257. {
  1258. captures.emplace_back(path);
  1259. return true;
  1260. });
  1261. base->FindFiles(
  1262. m_memoryCapturePath.c_str(), "*.json",
  1263. [&captures](const char* path)
  1264. {
  1265. captures.emplace_back(path);
  1266. return true;
  1267. });
  1268. if (captures.empty())
  1269. {
  1270. ImGui::Text("No captures found in %s", m_memoryCapturePath.c_str());
  1271. }
  1272. else
  1273. {
  1274. ImGui::Text("Displaying %zu captures found in %s", captures.size(), m_memoryCapturePath.c_str());
  1275. // Sort captures in reverse-chronological order
  1276. AZStd::sort(
  1277. captures.begin(), captures.end(),
  1278. [base](const AZ::IO::Path& lhs, const AZ::IO::Path& rhs)
  1279. {
  1280. return base->ModificationTime(rhs.c_str()) < base->ModificationTime(lhs.c_str());
  1281. });
  1282. // Display 10 entries in a scrolling list box
  1283. if (ImGui::BeginListBox(
  1284. "Memory Captures",
  1285. ImVec2{ ImGui::GetMainViewport()->Size.x * 0.8f, 10 * ImGui::GetTextLineHeightWithSpacing() }))
  1286. {
  1287. for (size_t i = 0; i != captures.size(); ++i)
  1288. {
  1289. bool selected = i == m_captureSelection;
  1290. if (ImGui::Selectable(captures[i].c_str(), selected))
  1291. {
  1292. m_captureSelection = i;
  1293. }
  1294. if (selected)
  1295. {
  1296. ImGui::SetItemDefaultFocus();
  1297. }
  1298. }
  1299. ImGui::EndListBox();
  1300. }
  1301. if (ImGui::Button("Open"))
  1302. {
  1303. if (captures[m_captureSelection].Extension() == ".csv")
  1304. {
  1305. LoadFromCSV(captures[m_captureSelection].c_str());
  1306. }
  1307. else if (captures[m_captureSelection].Extension() == ".json")
  1308. {
  1309. LoadFromJSON(captures[m_captureSelection].c_str());
  1310. }
  1311. ImGui::CloseCurrentPopup();
  1312. }
  1313. }
  1314. // In addition to the directory selection above, provide a means to input a path directly
  1315. ImGui::InputText("File Path", m_captureInput, AZ::IO::MaxPathLength);
  1316. AZStd::string manualInput{ m_captureInput };
  1317. if (manualInput.empty())
  1318. {
  1319. ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.6f);
  1320. ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
  1321. }
  1322. if (ImGui::Button("Open File"))
  1323. {
  1324. LoadFromCSV(manualInput);
  1325. ImGui::CloseCurrentPopup();
  1326. }
  1327. if (manualInput.empty())
  1328. {
  1329. ImGui::PopItemFlag();
  1330. ImGui::PopStyleVar();
  1331. }
  1332. if (ImGui::Button("Cancel"))
  1333. {
  1334. ImGui::CloseCurrentPopup();
  1335. }
  1336. ImGui::EndPopup();
  1337. }
  1338. if (!m_loadedCapturePath.empty())
  1339. {
  1340. ImGui::Text("Viewing data loaded from %s", m_loadedCapturePath.c_str());
  1341. }
  1342. if (!m_captureMessage.empty())
  1343. {
  1344. ImGui::Text("%s", m_captureMessage.c_str());
  1345. }
  1346. if (m_hostTreemap)
  1347. {
  1348. ImGui::Checkbox("Show host memory treemap", &m_showHostTreemap);
  1349. ImGui::SameLine();
  1350. ImGui::Checkbox("Show device memory treemap", &m_showDeviceTreemap);
  1351. if (m_showHostTreemap)
  1352. {
  1353. m_hostTreemap->Render(20, 40, 800, 600);
  1354. }
  1355. if (m_showDeviceTreemap)
  1356. {
  1357. m_deviceTreemap->Render(40, 80, 800, 600);
  1358. }
  1359. }
  1360. if (ImGui::Checkbox("Show buffers", &m_includeBuffers)
  1361. || ImGui::Checkbox("Show images", &m_includeImages)
  1362. || ImGui::Checkbox("Show transient attachments", &m_includeTransientAttachments)
  1363. || ImGui::Checkbox("Hide empty pools", &m_hideEmptyBufferPools))
  1364. {
  1365. UpdateTableRows();
  1366. }
  1367. ImGui::Text("Overall heap usage:");
  1368. const float columnOffset = ImGui::GetWindowWidth() / m_savedHeaps.size();
  1369. float currentX = columnOffset;
  1370. for (const auto& savedHeap : m_savedHeaps)
  1371. {
  1372. if (ImGui::BeginChild(savedHeap.m_name.GetCStr(), { ImGui::GetWindowWidth() / m_savedHeaps.size(), 250 }), ImGuiWindowFlags_NoScrollbar)
  1373. {
  1374. ImGui::Text("%s", savedHeap.m_name.GetCStr());
  1375. ImGui::Columns(2, "HeapData", true);
  1376. ImGui::Text("%s", "Used (MB): ");
  1377. ImGui::NextColumn();
  1378. ImGui::Text("%.2f", 1.0 * savedHeap.m_memoryUsage.m_usedResidentInBytes.load() / GpuProfilerImGuiHelper::MB);
  1379. ImGui::NextColumn();
  1380. ImGui::Text("%s", "Allocated (MB): ");
  1381. ImGui::NextColumn();
  1382. ImGui::Text("%.2f", 1.0 * savedHeap.m_memoryUsage.m_totalResidentInBytes.load() / GpuProfilerImGuiHelper::MB);
  1383. ImGui::NextColumn();
  1384. ImGui::Text("%s", "Budget (MB): ");
  1385. ImGui::NextColumn();
  1386. ImGui::Text("%.2f", 1.0 * savedHeap.m_memoryUsage.m_budgetInBytes / GpuProfilerImGuiHelper::MB);
  1387. ImGui::Columns(1, "PieChartColumn");
  1388. DrawPieChart(savedHeap);
  1389. }
  1390. ImGui::EndChild();
  1391. ImGui::SameLine(currentX);
  1392. currentX += columnOffset;
  1393. }
  1394. ImGui::NewLine();
  1395. ImGui::Separator();
  1396. m_nameFilter.Draw("Search");
  1397. DrawTables();
  1398. }
  1399. ImGui::End();
  1400. }
  1401. void ImGuiGpuMemoryView::UpdateTreemaps()
  1402. {
  1403. if (!m_hostTreemap)
  1404. {
  1405. if (auto treemapFactory = Profiler::ImGuiTreemapFactory::Interface::Get())
  1406. {
  1407. m_hostTreemap = &treemapFactory->Create(AZ::Name{ "Atom Host Memory Treemap" }, "MiB");
  1408. m_hostTreemap->AddMask("Hide Unused", 0);
  1409. m_deviceTreemap = &treemapFactory->Create(AZ::Name{ "Atom Device Memory Treemap" }, "MiB");
  1410. m_deviceTreemap->AddMask("Hide Unused", 0);
  1411. }
  1412. }
  1413. if (m_hostTreemap)
  1414. {
  1415. using Profiler::TreemapNode;
  1416. AZStd::vector<TreemapNode> hostNodes;
  1417. AZStd::vector<TreemapNode> deviceNodes;
  1418. for (auto& pool : m_savedPools)
  1419. {
  1420. size_t hostBytes = pool.m_memoryUsage.GetHeapMemoryUsage(RHI::HeapMemoryLevel::Host).m_totalResidentInBytes;
  1421. size_t hostResidentBytes = pool.m_memoryUsage.GetHeapMemoryUsage(RHI::HeapMemoryLevel::Host).m_usedResidentInBytes;
  1422. size_t deviceBytes = pool.m_memoryUsage.GetHeapMemoryUsage(RHI::HeapMemoryLevel::Device).m_totalResidentInBytes;
  1423. size_t deviceResidentBytes = pool.m_memoryUsage.GetHeapMemoryUsage(RHI::HeapMemoryLevel::Device).m_usedResidentInBytes;
  1424. TreemapNode* poolNode = nullptr;
  1425. // Resource pools are each associated with either a device-local heap, or a host heap. Identify the association and
  1426. // add constiuent buffers and textures as sub-nodes in the corresponding treemap.
  1427. if (hostBytes > 0)
  1428. {
  1429. poolNode = &hostNodes.emplace_back();
  1430. poolNode->m_name = pool.m_name;
  1431. }
  1432. else if (deviceBytes > 0)
  1433. {
  1434. poolNode = &deviceNodes.emplace_back();
  1435. poolNode->m_name = pool.m_name;
  1436. }
  1437. else
  1438. {
  1439. continue;
  1440. }
  1441. const AZ::Name unusedGroup{ "Unused" };
  1442. TreemapNode& unusedNode = poolNode->m_children.emplace_back();
  1443. unusedNode.m_name = "Unused";
  1444. unusedNode.m_group = unusedGroup;
  1445. if (hostBytes > 0)
  1446. {
  1447. unusedNode.m_weight = static_cast<float>(hostBytes - hostResidentBytes) / GpuProfilerImGuiHelper::MB;
  1448. }
  1449. else
  1450. {
  1451. unusedNode.m_weight = static_cast<float>(deviceBytes - deviceResidentBytes) / GpuProfilerImGuiHelper::MB;
  1452. }
  1453. unusedNode.m_tag = 1;
  1454. if (pool.m_buffers.empty() && pool.m_images.empty())
  1455. {
  1456. continue;
  1457. }
  1458. const AZ::Name bufferGroup{ "Buffer" };
  1459. const AZ::Name textureGroup{ "Texture" };
  1460. for (auto& buffer : pool.m_buffers)
  1461. {
  1462. TreemapNode& child = poolNode->m_children.emplace_back();
  1463. child.m_name = buffer.m_name;
  1464. child.m_weight = static_cast<float>(buffer.m_sizeInBytes) / GpuProfilerImGuiHelper::MB;
  1465. child.m_group = bufferGroup;
  1466. }
  1467. for (auto& image : pool.m_images)
  1468. {
  1469. TreemapNode& child = poolNode->m_children.emplace_back();
  1470. child.m_name = image.m_name;
  1471. child.m_weight = static_cast<float>(image.m_sizeInBytes) / GpuProfilerImGuiHelper::MB;
  1472. child.m_group = textureGroup;
  1473. }
  1474. }
  1475. m_hostTreemap->SetRoots(AZStd::move(hostNodes));
  1476. m_deviceTreemap->SetRoots(AZStd::move(deviceNodes));
  1477. }
  1478. }
  1479. void ImGuiGpuMemoryView::SaveToJSON()
  1480. {
  1481. time_t ltime;
  1482. time(&ltime);
  1483. tm today;
  1484. #if AZ_TRAIT_USE_SECURE_CRT_FUNCTIONS
  1485. localtime_s(&today, &ltime);
  1486. #else
  1487. today = *localtime(&ltime);
  1488. #endif
  1489. char sTemp[128];
  1490. strftime(sTemp, sizeof(sTemp), "%Y%m%d.%H%M%S", &today);
  1491. AZStd::string filename = AZStd::string::format("%s/GpuMemoryCapture_%s.json", m_memoryCapturePath.c_str(), sTemp);
  1492. AZ::IO::SystemFile outputFile;
  1493. if (!outputFile.Open(filename.c_str(), AZ::IO::SystemFile::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY))
  1494. {
  1495. m_captureMessage = AZStd::string::format("Failed to open file %s for writing", filename.c_str());
  1496. AZ_Error("ImGuiGpuMemoryView", false, m_captureMessage.c_str());
  1497. return;
  1498. }
  1499. rapidjson::Document doc;
  1500. AZ::RHI::RHIMemoryStatisticsInterface::Get()->WriteResourcePoolInfoToJson(m_savedPools, doc);
  1501. rapidjson::StringBuffer jsonStringBuffer;
  1502. rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(jsonStringBuffer);
  1503. doc.Accept(writer);
  1504. outputFile.Write(jsonStringBuffer.GetString(), jsonStringBuffer.GetSize());
  1505. outputFile.Close();
  1506. m_captureMessage = AZStd::string::format("Wrote memory capture to %s", filename.c_str());
  1507. }
  1508. void ImGuiGpuMemoryView::LoadFromJSON(const AZStd::string& fileName)
  1509. {
  1510. m_loadedCapturePath.clear();
  1511. auto serializeOutcome = JsonSerializationUtils::ReadJsonFile(fileName);
  1512. if (!serializeOutcome.IsSuccess())
  1513. {
  1514. m_captureMessage = AZStd::string::format("Failed to load memory data from %s, error message = \"%s\"",
  1515. fileName.c_str(), serializeOutcome.GetError().c_str());
  1516. AZ_Error("ImGuiGpuMemoryView", false, m_captureMessage.c_str());
  1517. return;
  1518. }
  1519. m_loadedCapturePath = fileName;
  1520. rapidjson::Document& doc = serializeOutcome.GetValue();
  1521. auto loadOutcome = AZ::RHI::RHIMemoryStatisticsInterface::Get()->LoadResourcePoolInfoFromJson(
  1522. m_savedPools, m_savedHeaps, doc, fileName);
  1523. if (!loadOutcome.IsSuccess())
  1524. {
  1525. m_captureMessage = loadOutcome.GetError();
  1526. return;
  1527. }
  1528. // load from json here
  1529. UpdateTableRows();
  1530. UpdateTreemaps();
  1531. }
  1532. // C4702: Unreachable code
  1533. // MSVC 2022 believes that `return true;` below is unreacahable, which is not true.
  1534. #ifdef _MSC_VER
  1535. #pragma warning(push)
  1536. #pragma warning(disable: 4702)
  1537. #endif
  1538. template <typename T>
  1539. bool parseCSVField(const AZStd::string& field, T& out)
  1540. {
  1541. if constexpr (AZStd::is_same_v<T, int>)
  1542. {
  1543. if (azsscanf(field.c_str(), "%i", &out) != 1)
  1544. {
  1545. return false;
  1546. }
  1547. }
  1548. else if constexpr (AZStd::is_same_v<T, uint32_t>)
  1549. {
  1550. if (azsscanf(field.c_str(), "%" PRIu32, &out) != 1)
  1551. {
  1552. return false;
  1553. }
  1554. }
  1555. else if constexpr (AZStd::is_same_v<T, uint64_t>)
  1556. {
  1557. if (azsscanf(field.c_str(), "%" PRIu64, &out) != 1)
  1558. {
  1559. return false;
  1560. }
  1561. }
  1562. else if constexpr (AZStd::is_same_v<T, AZ::Name>)
  1563. {
  1564. out = AZ::Name{ field.c_str() };
  1565. return true;
  1566. }
  1567. else
  1568. {
  1569. return false;
  1570. }
  1571. return true;
  1572. }
  1573. #ifdef _MSC_VER
  1574. #pragma warning(pop)
  1575. #endif
  1576. static constexpr const char* MemoryCSVHeader =
  1577. "Pool Name, Memory Type (0 == Host : 1 == Device), Allocation Name, Allocation Type (0 == Buffer : "
  1578. "1 == Texture), Byte Size, Flags\n";
  1579. static constexpr size_t MemoryCSVFieldCount = 6;
  1580. void ImGuiGpuMemoryView::LoadFromCSV(const AZStd::string& fileName)
  1581. {
  1582. m_loadedCapturePath.clear();
  1583. AZ::IO::SystemFile fileIn;
  1584. if (!fileIn.Open(fileName.c_str(), AZ::IO::SystemFile::SF_OPEN_READ_ONLY))
  1585. {
  1586. return;
  1587. }
  1588. AZStd::string data;
  1589. data.resize_no_construct(fileIn.Length());
  1590. fileIn.Read(fileIn.Length(), data.data());
  1591. AZStd::vector<AZStd::string> lines;
  1592. AZ::StringFunc::Tokenize(data, lines, "\n");
  1593. if (lines.empty())
  1594. {
  1595. m_captureMessage = AZStd::string::format("Attempted to load memory data from %s but file was empty", fileName.c_str());
  1596. AZ_Error("ImGuiGpuMemoryView", false, m_captureMessage.c_str());
  1597. return;
  1598. }
  1599. if (lines[0] + '\n' != MemoryCSVHeader)
  1600. {
  1601. m_captureMessage = AZStd::string::format(
  1602. "Attempted to load memory data from %s but the CSV header (%s) did not match", fileName.c_str(), MemoryCSVHeader);
  1603. AZ_Error("ImGuiGpuMemoryView", false, m_captureMessage.c_str());
  1604. return;
  1605. }
  1606. m_loadedCapturePath = fileName;
  1607. m_savedHeaps.clear();
  1608. m_savedHeaps.resize(2);
  1609. m_savedHeaps[0].m_name = AZ::Name{ "Host Heap" };
  1610. m_savedHeaps[0].m_heapMemoryType = RHI::HeapMemoryLevel::Host;
  1611. m_savedHeaps[1].m_name = AZ::Name{ "Device Heap" };
  1612. m_savedHeaps[1].m_heapMemoryType = RHI::HeapMemoryLevel::Device;
  1613. m_savedPools.clear();
  1614. AZStd::unordered_map<AZ::Name, AZ::RHI::MemoryStatistics::Pool> pools;
  1615. AZStd::vector<AZStd::string> fields;
  1616. fields.reserve(MemoryCSVFieldCount);
  1617. for (size_t i = 1; i != lines.size(); ++i)
  1618. {
  1619. fields.clear();
  1620. const AZStd::string& line = lines[i];
  1621. AZ::Name poolName;
  1622. int memoryType;
  1623. AZ::Name resourceName;
  1624. int resourceType;
  1625. uint64_t byteSize;
  1626. uint32_t bindFlags;
  1627. AZ::StringFunc::Tokenize(line, fields, ",\n", true, true);
  1628. if (fields.size() == MemoryCSVFieldCount && parseCSVField(fields[0], poolName) && parseCSVField(fields[1], memoryType) &&
  1629. parseCSVField(fields[2], resourceName) && parseCSVField(fields[3], resourceType) &&
  1630. parseCSVField(fields[4], byteSize) && parseCSVField(fields[5], bindFlags))
  1631. {
  1632. RHI::MemoryStatistics::Pool* pool;
  1633. auto it = pools.find(poolName);
  1634. if (it == pools.end())
  1635. {
  1636. pool = &pools.try_emplace(poolName).first->second;
  1637. pool->m_name = AZStd::move(poolName);
  1638. }
  1639. else
  1640. {
  1641. pool = &it->second;
  1642. }
  1643. if (memoryType != 0 && memoryType != 1)
  1644. {
  1645. // Unknown memory type
  1646. m_captureMessage = AZStd::string::format(
  1647. "Attempted to load memory data from %s but an unknown memory type was detected (indicating invalid file "
  1648. "format)",
  1649. fileName.c_str());
  1650. AZ_Error("ImGuiGpuMemoryView", false, m_captureMessage.c_str());
  1651. return;
  1652. }
  1653. if (resourceType == 0 /* buffer */)
  1654. {
  1655. RHI::MemoryStatistics::Buffer buffer;
  1656. buffer.m_name = AZStd::move(resourceName);
  1657. buffer.m_bindFlags = static_cast<RHI::BufferBindFlags>(bindFlags);
  1658. buffer.m_sizeInBytes = byteSize;
  1659. pool->m_buffers.push_back(AZStd::move(buffer));
  1660. }
  1661. else if (resourceType == 1 /* image */)
  1662. {
  1663. RHI::MemoryStatistics::Image image;
  1664. image.m_name = AZStd::move(resourceName);
  1665. image.m_bindFlags = static_cast<RHI::ImageBindFlags>(bindFlags);
  1666. image.m_sizeInBytes = byteSize;
  1667. pool->m_images.push_back(AZStd::move(image));
  1668. }
  1669. pool->m_memoryUsage.m_memoryUsagePerLevel[memoryType].m_usedResidentInBytes += byteSize;
  1670. pool->m_memoryUsage.m_memoryUsagePerLevel[memoryType].m_totalResidentInBytes += byteSize;
  1671. // NOTE: This information isn't strictly accurate because we're reconstructing data from a list of
  1672. // allocations.
  1673. m_savedHeaps[memoryType].m_memoryUsage.m_totalResidentInBytes += byteSize;
  1674. m_savedHeaps[memoryType].m_memoryUsage.m_usedResidentInBytes += byteSize;
  1675. }
  1676. else
  1677. {
  1678. m_captureMessage = AZStd::string::format(
  1679. "Attempted to load memory data from %s but a parse error occurred (indicating invalid file "
  1680. "format)",
  1681. fileName.c_str());
  1682. AZ_Error("ImGuiGpuMemoryView", false, m_captureMessage.c_str());
  1683. return;
  1684. }
  1685. }
  1686. for (auto& pool : pools)
  1687. {
  1688. m_savedPools.push_back(AZStd::move(pool.second));
  1689. }
  1690. UpdateTableRows();
  1691. UpdateTreemaps();
  1692. }
  1693. // --- ImGuiGpuProfiler ---
  1694. void ImGuiGpuProfiler::Draw(bool& draw, RHI::Ptr<RPI::ParentPass> rootPass)
  1695. {
  1696. // Update the PassEntry database.
  1697. const PassEntry* rootPassEntryRef = CreatePassEntries(rootPass);
  1698. bool wasDraw = draw;
  1699. GpuProfilerImGuiHelper::Begin("Gpu Profiler", &draw, ImGuiWindowFlags_NoResize, [this, &rootPass]()
  1700. {
  1701. if (ImGui::Checkbox("Enable TimestampView", &m_drawTimestampView))
  1702. {
  1703. rootPass->SetTimestampQueryEnabled(m_drawTimestampView);
  1704. }
  1705. ImGui::Spacing();
  1706. if(ImGui::Checkbox("Enable PipelineStatisticsView", &m_drawPipelineStatisticsView))
  1707. {
  1708. rootPass->SetPipelineStatisticsQueryEnabled(m_drawPipelineStatisticsView);
  1709. }
  1710. ImGui::Spacing();
  1711. ImGui::Checkbox("Enable GpuMemoryView", &m_drawGpuMemoryView);
  1712. });
  1713. // Draw the PipelineStatistics window.
  1714. m_timestampView.DrawTimestampWindow(m_drawTimestampView, rootPassEntryRef, m_passEntryDatabase, rootPass);
  1715. // Draw the PipelineStatistics window.
  1716. m_pipelineStatisticsView.DrawPipelineStatisticsWindow(m_drawPipelineStatisticsView, rootPassEntryRef, m_passEntryDatabase, rootPass);
  1717. // Draw the GpuMemory window.
  1718. m_gpuMemoryView.DrawGpuMemoryWindow(m_drawGpuMemoryView);
  1719. //closing window
  1720. if (wasDraw && !draw)
  1721. {
  1722. rootPass->SetTimestampQueryEnabled(false);
  1723. rootPass->SetPipelineStatisticsQueryEnabled(false);
  1724. }
  1725. }
  1726. void ImGuiGpuProfiler::InterpolatePassEntries(AZStd::unordered_map<Name, PassEntry>& passEntryDatabase, float weight) const
  1727. {
  1728. for (auto& entry : passEntryDatabase)
  1729. {
  1730. const auto oldEntryIt = m_passEntryDatabase.find(entry.second.m_path);
  1731. if (oldEntryIt != m_passEntryDatabase.end())
  1732. {
  1733. // Interpolate the timestamps.
  1734. const double interpolated = Lerp(static_cast<double>(oldEntryIt->second.m_interpolatedTimestampInNanoseconds),
  1735. static_cast<double>(entry.second.m_timestampResult.GetDurationInNanoseconds()),
  1736. static_cast<double>(weight));
  1737. entry.second.m_interpolatedTimestampInNanoseconds = static_cast<uint64_t>(interpolated);
  1738. }
  1739. }
  1740. }
  1741. PassEntry* ImGuiGpuProfiler::CreatePassEntries(RHI::Ptr<RPI::ParentPass> rootPass)
  1742. {
  1743. AZStd::unordered_map<Name, PassEntry> passEntryDatabase;
  1744. const auto addPassEntry = [&passEntryDatabase](const RPI::Pass* pass, PassEntry* parent) -> PassEntry*
  1745. {
  1746. // If parent a nullptr, it's assumed to be the rootpass.
  1747. if (parent == nullptr)
  1748. {
  1749. return &passEntryDatabase[pass->GetPathName()];
  1750. }
  1751. else
  1752. {
  1753. PassEntry entry(pass, parent);
  1754. // Set the time stamp in the database.
  1755. [[maybe_unused]] const auto passEntry = passEntryDatabase.find(entry.m_path);
  1756. AZ_Assert(passEntry == passEntryDatabase.end(), "There already is an entry with the name \"%s\".", entry.m_path.GetCStr());
  1757. // Set the entry in the map.
  1758. PassEntry& entryRef = passEntryDatabase[entry.m_path] = entry;
  1759. return &entryRef;
  1760. }
  1761. };
  1762. // NOTE: Write it all out, can't have recursive functions for lambdas.
  1763. const AZStd::function<void(const RPI::Pass*, PassEntry*)> getPassEntryRecursive = [&addPassEntry, &getPassEntryRecursive](const RPI::Pass* pass, PassEntry* parent) -> void
  1764. {
  1765. // Add new entry to the timestamp map.
  1766. if (pass->IsEnabled())
  1767. {
  1768. const RPI::ParentPass* passAsParent = pass->AsParent();
  1769. PassEntry* entry = addPassEntry(pass, parent);
  1770. // Recur if it's a parent.
  1771. if (passAsParent)
  1772. {
  1773. for (const auto& childPass : passAsParent->GetChildren())
  1774. {
  1775. getPassEntryRecursive(childPass.get(), entry);
  1776. }
  1777. }
  1778. }
  1779. };
  1780. // Set up the root entry.
  1781. PassEntry rootEntry(static_cast<RPI::Pass*>(rootPass.get()), nullptr);
  1782. PassEntry& rootEntryRef = passEntryDatabase[rootPass->GetPathName()] = rootEntry;
  1783. // Create an intermediate structure from the passes.
  1784. // Recursively create the timestamp entries tree.
  1785. getPassEntryRecursive(static_cast<RPI::Pass*>(rootPass.get()), nullptr);
  1786. // Interpolate the old values.
  1787. const float lerpWeight = 0.2f;
  1788. InterpolatePassEntries(passEntryDatabase, lerpWeight);
  1789. // Set the new database.
  1790. m_passEntryDatabase = AZStd::move(passEntryDatabase);
  1791. return &rootEntryRef;
  1792. }
  1793. } // namespace Render
  1794. } // namespace AZ