ImGuiGpuProfiler.cpp 95 KB

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