| 919 | return static_cast<float>(bytes) / (1024.0f * 1024.0f); |
| 920 | } |
| 921 | |
| 922 | void DrawMemoryTab() |
| 923 | { |
| 924 | MemorySample(); |
| 925 | |
| 926 | const Foundation::ProcessMemoryStats stats = Foundation::MemoryProcessStats(); |
| 927 | const float mb = ToMB(stats.used); |
| 928 | const float peakMb = ToMB(s_memPeak); |
| 929 | |
| 930 | ImGui::TextUnformatted("Process heap"); |
| 931 | ImGui::Separator(); |
| 932 | ImGui::Text("Current: %.1f MB", mb); |
| 933 | ImGui::Text("Peak: %.1f MB", peakMb); |
| 934 | if (stats.softLimit || stats.hardLimit) |
| 935 | { |
| 936 | ImGui::Text("Soft (trim): %.0f MB%s", ToMB(stats.softLimit), |
| 937 | stats.softLimit && stats.used > stats.softLimit ? " (OVER — trimming to budgets)" : ""); |
| 938 | if (ImGui::IsItemHovered()) |
| 939 | ImGui::SetTooltip("Pressure watermark. Over it, each cache is trimmed back to its own\n" |
| 940 | "declared budget once per frame (FrameMaintenance). Never refuses."); |
| 941 | ImGui::Text("Hard (evict): %.0f MB%s", ToMB(stats.hardLimit), |
| 942 | stats.hardLimit && stats.used > stats.hardLimit ? " (OVER — evicting caches)" : ""); |
| 943 | if (ImGui::IsItemHovered()) |
| 944 | ImGui::SetTooltip("Eviction target, not a wall. Over it the allocator additionally claws\n" |
| 945 | "memory back with cost-ordered cache eviction — but never refuses an\n" |
| 946 | "allocation: refusing would crash the engine's many unchecked `new` sites."); |
| 947 | } |
| 948 | else |
| 949 | { |
| 950 | ImGui::TextDisabled("No process limit set (unlimited)."); |
| 951 | } |
| 952 | |
| 953 | // Plot — same shape as the profile tab. Y-axis floats around the |
| 954 | // peak; ImPlot would give a nicer presentation but PlotHistogram |
| 955 | // is sufficient and zero-dependency. |
| 956 | char overlay[64]; |
| 957 | snprintf(overlay, sizeof(overlay), "MB used (last %d frames)", kMemHistory); |
| 958 | ImGui::PlotHistogram("##mem_mb", s_memMb, kMemHistory, s_memHead, overlay, 0.0f, peakMb * 1.1f + 1.0f, |
| 959 | ImVec2(0, 80)); |
| 960 | |
| 961 | if (ImGui::Button("Reset peak / history")) |
| 962 | { |
| 963 | for (int i = 0; i < kMemHistory; i++) |
| 964 | s_memMb[i] = 0.0f; |
| 965 | s_memHead = 0; |
| 966 | s_memPeak = stats.used; |
| 967 | } |
| 968 | |
| 969 | // ── Per-subsystem residency (the FreeOnDemand registry) ──────────────── |
| 970 | // One snapshot drives both the count and the table so they can't disagree. |
| 971 | Foundation::MemoryDomainStat domains[32]; |
| 972 | const int n = Foundation::MemorySnapshotDomains(domains, 32); |
| 973 | |
| 974 | ImGui::Spacing(); |
| 975 | ImGui::Text("Subsystems (%d registered)", n); |
| 976 | ImGui::SameLine(); |
| 977 | if (ImGui::Button("Trim caches now")) |
| 978 | Defer([] { Foundation::MemoryEnforceBudgets(); }); |
no test coverage detected