1 // Copyright © 2008-2021 Pioneer Developers. See AUTHORS.txt for details
2 // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
3
4 #include "PerfInfo.h"
5 #include "Frame.h"
6 #include "Game.h"
7 #include "LuaPiGui.h"
8 #include "Pi.h"
9 #include "Player.h"
10 #include "Space.h"
11 #include "graphics/Renderer.h"
12 #include "graphics/Stats.h"
13 #include "graphics/Texture.h"
14 #include "lua/Lua.h"
15 #include "lua/LuaManager.h"
16 #include "scenegraph/Model.h"
17 #include "text/TextureFont.h"
18
19 #include <imgui/imgui.h>
20 #include <algorithm>
21 #include <cstddef>
22 #include <fstream>
23 #include <functional>
24 #include <sstream>
25
26 #ifdef _WIN32
27 #include <windows.h>
28 // order of header includes matters, thanks Windows.h!
29 #include <psapi.h>
30 #endif
31
32 // using namespace PiGui;
33 using PerfInfo = PiGui::PerfInfo;
34
35 struct PerfInfo::ImGuiState {
36 bool perfWindowOpen = true;
37 bool updatePause = false;
38 bool metricsWindowOpen = false;
39 uint32_t playerModelDebugFlags = 0;
40
41 bool textureCacheViewerOpen = false;
42
43 std::map<std::string, std::vector<std::string>> indirectionMap;
44
45 bool hasSelectedTexture = false;
46 std::pair<std::string, std::string> selectedTexture;
47 };
48
PerfInfo()49 PerfInfo::PerfInfo() :
50 m_state(new ImGuiState({}))
51 {
52 m_fpsGraph.fill(0.0);
53 m_physFpsGraph.fill(0.0);
54 }
55
~PerfInfo()56 PerfInfo::~PerfInfo()
57 {
58 delete m_state;
59 }
60
61 // ============================================================================
62 //
63 // Hardware Performance Information
64 //
65 // ============================================================================
66
67 #define ignoreLine(f) f.ignore(std::numeric_limits<std::streamsize>::max(), '\n')
GetMemoryInfo()68 static PerfInfo::MemoryInfo GetMemoryInfo()
69 {
70 PerfInfo::MemoryInfo ret{};
71 #if __linux__
72 std::ifstream statusFile("/proc/self/status");
73 while (statusFile.good() && !statusFile.eof()) {
74 if (statusFile.peek() != 'V') {
75 ignoreLine(statusFile);
76 continue;
77 }
78
79 char statName[16] = { 0 };
80 statusFile.getline(statName, 16, ':');
81
82 if (strcmp(statName, "VmHWM") == 0) {
83 statusFile >> ret.peakMemSize;
84 } else if (strcmp(statName, "VmRSS") == 0) {
85 statusFile >> ret.currentMemSize;
86 }
87
88 ignoreLine(statusFile);
89 }
90 statusFile.close();
91
92 #elif _WIN32
93 // Get win32 memory count
94 PROCESS_MEMORY_COUNTERS pmc;
95 if (GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) {
96 // convert from bytes to kilobytes
97 ret.peakMemSize = pmc.PeakWorkingSetSize / 1024;
98 ret.currentMemSize = pmc.WorkingSetSize / 1024;
99 }
100 #elif __APPLE__
101 // TODO: get OSX memory count
102 #endif
103
104 return ret;
105 }
106 #undef ignoreLine
107
Update(float deltaTime,float physTime)108 void PerfInfo::Update(float deltaTime, float physTime)
109 {
110 // Don't accumulate new frames when performance data is paused.
111 if (m_state->updatePause)
112 return;
113
114 // Drop the oldest frame, make room for the new frame.
115 std::move(m_fpsGraph.begin() + 1, m_fpsGraph.end(), m_fpsGraph.begin());
116 std::move(m_physFpsGraph.begin() + 1, m_physFpsGraph.end(), m_physFpsGraph.begin());
117 m_fpsGraph[NUM_FRAMES - 1] = deltaTime;
118 m_physFpsGraph[NUM_FRAMES - 1] = physTime;
119
120 float fpsAccum = 0;
121 frameTimeMax = 0.f;
122 frameTimeMin = 0.f;
123 std::for_each(m_fpsGraph.begin(), m_fpsGraph.end(), [&](float i) {
124 fpsAccum += i;
125 frameTimeMax = std::max(frameTimeMax, i);
126 frameTimeMin = std::min(frameTimeMin, i);
127 });
128 frameTimeAverage = fpsAccum / double(NUM_FRAMES);
129
130 float physFpsAccum = 0;
131 physFrameTimeMax = 0.f;
132 physFrameTimeMin = 0.f;
133 std::for_each(m_physFpsGraph.begin(), m_physFpsGraph.end(), [&](float i) {
134 physFpsAccum += i;
135 physFrameTimeMax = std::max(physFrameTimeMax, i);
136 physFrameTimeMin = std::min(physFrameTimeMin, i);
137 });
138 physFrameTimeAverage = physFpsAccum / double(NUM_FRAMES);
139
140 lastUpdateTime += deltaTime;
141 if (lastUpdateTime > 1000.0) {
142 lastUpdateTime = fmod(lastUpdateTime, 1000.0);
143
144 lua_mem = ::Lua::manager->GetMemoryUsage();
145 process_mem = GetMemoryInfo();
146 }
147 }
148
149 // TODO: evaluate whether this method of tracking FPS is necessary.
UpdateFrameInfo(int fS,int pfS)150 void PerfInfo::UpdateFrameInfo(int fS, int pfS)
151 {
152 framesThisSecond = fS;
153 physFramesThisSecond = pfS;
154 }
155
SetUpdatePause(bool pause)156 void PerfInfo::SetUpdatePause(bool pause)
157 {
158 m_state->updatePause = pause;
159 }
160
161 // ============================================================================
162 //
163 // Main Performance Window
164 //
165 // ============================================================================
166
GetImTextureID(const Graphics::Texture * tex)167 ImTextureID GetImTextureID(const Graphics::Texture *tex)
168 {
169 return reinterpret_cast<ImTextureID>(tex->GetTextureID() | 0UL);
170 }
171
172 namespace ImGui {
Value(const char * prefix,const std::string & str)173 IMGUI_API void Value(const char *prefix, const std::string &str)
174 {
175 ImGui::TextWrapped("%s: %s", prefix, str.c_str());
176 }
177 } // namespace ImGui
178
179 static constexpr double scale_MB = 1024.0 * 1024.0;
180
Draw()181 void PerfInfo::Draw()
182 {
183 if (m_state->perfWindowOpen)
184 DrawPerfWindow();
185
186 if (m_state->textureCacheViewerOpen)
187 DrawTextureCache();
188
189 if (m_state->hasSelectedTexture &&
190 Pi::renderer->GetCachedTexture(m_state->selectedTexture.first, m_state->selectedTexture.second))
191 DrawTextureInspector();
192
193 if (m_state->metricsWindowOpen)
194 ImGui::ShowMetricsWindow(&m_state->metricsWindowOpen);
195 }
196
DrawPerfWindow()197 void PerfInfo::DrawPerfWindow()
198 {
199 if (ImGui::Begin("Performance", nullptr, ImGuiWindowFlags_NoNav)) {
200 ImGui::Text("%.1f fps (%.1f ms) %.1f physics ups (%.1f ms/u)", framesThisSecond, frameTimeAverage, physFramesThisSecond, physFrameTimeAverage);
201 ImGui::PlotLines("Frame Time (ms)", m_fpsGraph.data(), m_fpsGraph.size(), 0, nullptr, 0.0, 33.0, { 0, 60 });
202 ImGui::PlotLines("Update Time (ms)", m_physFpsGraph.data(), m_physFpsGraph.size(), 0, nullptr, 0.0, 10.0, { 0, 25 });
203 if (ImGui::Button(m_state->updatePause ? "Unpause" : "Pause")) {
204 SetUpdatePause(!m_state->updatePause);
205 }
206
207 if (process_mem.currentMemSize)
208 ImGui::Text("%.1f MB process memory usage (%.1f MB peak)", (process_mem.currentMemSize * 1e-3), (process_mem.peakMemSize * 1e-3));
209 ImGui::Text("%.3f MB Lua memory usage", double(lua_mem) / scale_MB);
210 ImGui::Spacing();
211
212 if (ImGui::BeginTabBar("PerfInfoTabs")) {
213 if (ImGui::BeginTabItem("Renderer")) {
214 DrawRendererStats();
215 DrawImGuiStats();
216 ImGui::EndTabItem();
217 }
218
219 if (false && ImGui::BeginTabItem("Input")) {
220 DrawInputDebug();
221 ImGui::EndTabItem();
222 }
223
224 if (Pi::game) {
225 if (Pi::player->GetFlightState() != Ship::HYPERSPACE && ImGui::BeginTabItem("WorldView")) {
226 DrawWorldViewStats();
227 ImGui::EndTabItem();
228 }
229 }
230
231 PiGui::RunHandler(Pi::GetFrameTime(), "debug-tabs");
232
233 ImGui::EndTabBar();
234 }
235 }
236
237 ImGui::End();
238
239 PiGui::RunHandler(Pi::GetFrameTime(), "debug");
240 }
241
DrawRendererStats()242 void PerfInfo::DrawRendererStats()
243 {
244 const Graphics::Stats::TFrameData &stats = Pi::renderer->GetStats().FrameStatsPrevious();
245 const Uint32 numDrawCalls = stats.m_stats[Graphics::Stats::STAT_DRAWCALL];
246 const Uint32 numBuffersCreated = stats.m_stats[Graphics::Stats::STAT_CREATE_BUFFER];
247 const Uint32 numBuffersInUse = stats.m_stats[Graphics::Stats::STAT_BUFFER_INUSE];
248 const Uint32 numDrawTris = stats.m_stats[Graphics::Stats::STAT_DRAWTRIS];
249 const Uint32 numDrawPointSprites = stats.m_stats[Graphics::Stats::STAT_DRAWPOINTSPRITES];
250 const Uint32 numDrawBuildings = stats.m_stats[Graphics::Stats::STAT_BUILDINGS];
251 const Uint32 numDrawCities = stats.m_stats[Graphics::Stats::STAT_CITIES];
252 const Uint32 numDrawGroundStations = stats.m_stats[Graphics::Stats::STAT_GROUNDSTATIONS];
253 const Uint32 numDrawSpaceStations = stats.m_stats[Graphics::Stats::STAT_SPACESTATIONS];
254 const Uint32 numDrawAtmospheres = stats.m_stats[Graphics::Stats::STAT_ATMOSPHERES];
255 const Uint32 numDrawPlanets = stats.m_stats[Graphics::Stats::STAT_PLANETS];
256 const Uint32 numDrawGasGiants = stats.m_stats[Graphics::Stats::STAT_GASGIANTS];
257 const Uint32 numDrawStars = stats.m_stats[Graphics::Stats::STAT_STARS];
258 const Uint32 numDrawShips = stats.m_stats[Graphics::Stats::STAT_SHIPS];
259 const Uint32 numDrawBillBoards = stats.m_stats[Graphics::Stats::STAT_BILLBOARD];
260
261 const Uint32 numTex2ds = stats.m_stats[Graphics::Stats::STAT_NUM_TEXTURE2D];
262 const Uint32 tex2dMemUsage = stats.m_stats[Graphics::Stats::STAT_MEM_TEXTURE2D];
263 const Uint32 numTexCubemaps = stats.m_stats[Graphics::Stats::STAT_NUM_TEXTURECUBE];
264 const Uint32 texCubeMemUsage = stats.m_stats[Graphics::Stats::STAT_MEM_TEXTURECUBE];
265 const Uint32 numTexArray2ds = stats.m_stats[Graphics::Stats::STAT_NUM_TEXTUREARRAY2D];
266 const Uint32 texArray2dMemUsage = stats.m_stats[Graphics::Stats::STAT_MEM_TEXTUREARRAY2D];
267 const Uint32 numCachedTextures = numTex2ds + numTexCubemaps + numTexArray2ds;
268 const Uint32 cachedTextureMemUsage = tex2dMemUsage + texCubeMemUsage + texArray2dMemUsage;
269
270 ImGui::Text("Renderer:");
271 ImGui::Text("%d tris (%.3f M tris/sec), %d GeoPatches, %d glyphs",
272 Pi::statSceneTris, Pi::statSceneTris * framesThisSecond * 1e-6, Pi::statNumPatches, Text::TextureFont::GetGlyphCount());
273 ImGui::Text("%u draw calls (%u tris, %u point sprites, %u billboards)",
274 numDrawCalls, numDrawTris, numDrawPointSprites, numDrawBillBoards);
275 ImGui::Text("%u Buildings, %u Cities, %u Gd.Stations, %u Sp.Stations",
276 numDrawBuildings, numDrawCities, numDrawGroundStations, numDrawSpaceStations);
277 ImGui::Text("%u Atmospheres, %u Planets, %u Gas Giants, %u Stars, %u Ships",
278 numDrawAtmospheres, numDrawPlanets, numDrawGasGiants, numDrawStars, numDrawShips);
279 ImGui::Text("%u Buffers Created (%u in use)", numBuffersCreated, numBuffersInUse);
280 ImGui::Spacing();
281
282 ImGui::Text("%u cached textures, using %.3f MB VRAM", numCachedTextures, double(cachedTextureMemUsage) / scale_MB);
283
284 if (ImGui::Button("Open Texture Cache Visualizer"))
285 m_state->textureCacheViewerOpen = true;
286
287 if (ImGui::Button("Reload Shaders"))
288 Pi::renderer->ReloadShaders();
289
290 ImGui::Text("%u Texture2D in cache (%.3f MB)", numTex2ds, double(tex2dMemUsage) / scale_MB);
291 ImGui::Text("%u Cubemaps in cache (%.3f MB)", numTexCubemaps, double(texCubeMemUsage) / scale_MB);
292 ImGui::Text("%u TextureArray2D in cache (%.3f MB)", numTexArray2ds, double(texArray2dMemUsage) / scale_MB);
293 }
294
DrawWorldViewStats()295 void PerfInfo::DrawWorldViewStats()
296 {
297 vector3d pos = Pi::player->GetPosition();
298 vector3d abs_pos = Pi::player->GetPositionRelTo(Pi::game->GetSpace()->GetRootFrame());
299
300 const FrameId playerFrame = Pi::player->GetFrame();
301
302 ImGui::TextUnformatted(fmt::format("Player Position: {:.5}, {:.5}, {:.5}", pos.x, pos.y, pos.z).c_str());
303 ImGui::TextUnformatted(fmt::format("Absolute Position: {:.5}, {:.5}, {:.5}", abs_pos.x, abs_pos.y, abs_pos.z).c_str());
304
305 const Frame *frame = Frame::GetFrame(playerFrame);
306 const SystemPath &path(frame->GetSystemBody()->GetPath());
307
308 std::string tempStr;
309 tempStr = fmt::format("Relative to frame: {} [{}, {}, {}, {}, {}]",
310 frame->GetLabel(), path.sectorX, path.sectorY, path.sectorZ, path.systemIndex, path.bodyIndex);
311
312 ImGui::TextUnformatted(tempStr.c_str());
313
314 tempStr = fmt::format("Distance from frame: {:.2f} km, rotating: {}, has rotation: {}",
315 pos.Length() / 1000.0, frame->IsRotFrame(), frame->HasRotFrame());
316
317 ImGui::TextUnformatted(tempStr.c_str());
318
319 ImGui::Spacing();
320
321 //Calculate lat/lon for ship position
322 const vector3d dir = pos.NormalizedSafe();
323 const float lat = RAD2DEG(asin(dir.y));
324 const float lon = RAD2DEG(atan2(dir.x, dir.z));
325
326 ImGui::TextUnformatted(fmt::format("Lat / Lon: {:.8} / {:.8}", lat, lon).c_str());
327
328 char aibuf[256];
329 Pi::player->AIGetStatusText(aibuf);
330
331 ImGui::TextUnformatted(aibuf);
332
333 ImGui::Spacing();
334 ImGui::TextUnformatted("Player Model ShowFlags:");
335
336 using Flags = SceneGraph::Model::DebugFlags;
337
338 bool showColl = m_state->playerModelDebugFlags & Flags::DEBUG_COLLMESH;
339 bool showBBox = m_state->playerModelDebugFlags & Flags::DEBUG_BBOX;
340 bool showTags = m_state->playerModelDebugFlags & Flags::DEBUG_TAGS;
341
342 bool changed = ImGui::Checkbox("Show Collision Mesh", &showColl);
343 changed |= ImGui::Checkbox("Show Bounding Box", &showBBox);
344 changed |= ImGui::Checkbox("Show Tag Locations", &showTags);
345
346 /* clang-format off */
347 if (changed) {
348 m_state->playerModelDebugFlags = (showColl ? Flags::DEBUG_COLLMESH : 0)
349 | (showBBox ? Flags::DEBUG_BBOX : 0)
350 | (showTags ? Flags::DEBUG_TAGS : 0);
351 Pi::player->GetModel()->SetDebugFlags(m_state->playerModelDebugFlags);
352 }
353 /* clang-format on */
354
355 if (Pi::player->GetNavTarget() && Pi::player->GetNavTarget()->GetSystemBody()) {
356 const auto *sbody = Pi::player->GetNavTarget()->GetSystemBody();
357 ImGui::TextUnformatted(fmt::format("Name: {}, Population: {}", sbody->GetName(), sbody->GetPopulation() * 1e9).c_str());
358 }
359 }
360
DrawInputDebug()361 void PerfInfo::DrawInputDebug()
362 {
363 std::ostringstream output;
364 auto frameList = Pi::input->GetInputFrames();
365 uint32_t index = 0;
366 for (const auto *frame : frameList) {
367 ImGui::Text("InputFrame %d: modal=%d", index, frame->modal);
368 ImGui::Indent();
369 uint32_t actionNum = 0;
370 for (const auto *action : frame->actions) {
371 ImGui::Text("Action %d: active=%d (%d, %d)", actionNum, action->m_active, action->binding.m_active, action->binding2.m_active);
372 if (action->binding.Enabled()) {
373 output << action->binding;
374 ImGui::TextUnformatted(output.str().c_str());
375 output.str("");
376 }
377 if (action->binding2.Enabled()) {
378 output << action->binding2;
379 ImGui::TextUnformatted(output.str().c_str());
380 output.str("");
381 }
382
383 ImGui::Separator();
384 actionNum++;
385 }
386
387 ImGui::Spacing();
388
389 actionNum = 0;
390 for (const auto *axis : frame->axes) {
391 ImGui::Text("Axis %d: value=%.2f (%d, %d)", actionNum, axis->m_value, axis->positive.m_active, axis->negative.m_active);
392 if (axis->positive.Enabled()) {
393 output << axis->positive;
394 ImGui::TextUnformatted(output.str().c_str());
395 output.str("");
396 }
397
398 if (axis->negative.Enabled()) {
399 output << axis->negative;
400 ImGui::TextUnformatted(output.str().c_str());
401 output.str("");
402 }
403
404 ImGui::Separator();
405 actionNum++;
406 }
407 ImGui::Unindent();
408
409 ImGui::Spacing();
410 index++;
411 }
412 }
413
DrawImGuiStats()414 void PerfInfo::DrawImGuiStats()
415 {
416 ImGui::NewLine();
417 auto &io = ImGui::GetIO();
418 ImGui::Text("ImGui stats:");
419 ImGui::Text("%d verts, %d tris", io.MetricsRenderVertices, io.MetricsRenderIndices / 3);
420 ImGui::Text("%d active windows (%d visible)", io.MetricsActiveWindows, io.MetricsRenderWindows);
421 ImGui::Text("%d current allocations", io.MetricsActiveAllocations);
422
423 if (ImGui::Button("Toggle Metrics Window")) {
424 m_state->metricsWindowOpen = !m_state->metricsWindowOpen;
425 }
426 }
427
DrawStatList(const Perf::Stats::FrameInfo & fi)428 void PerfInfo::DrawStatList(const Perf::Stats::FrameInfo &fi)
429 {
430 ImGui::BeginChild("FrameInfo");
431 ImGui::Columns(2);
432 for (auto &t : fi) {
433 ImGui::Text("%s:", t.first.c_str());
434 ImGui::NextColumn();
435 ImGui::Text("%'d", t.second);
436 ImGui::NextColumn();
437 }
438 ImGui::Columns();
439 ImGui::EndChild();
440 }
441
442 // ============================================================================
443 //
444 // Texture Cache Visualizer
445 //
446 // ============================================================================
447
DrawTexture(PerfInfo::ImGuiState * m_state,const Graphics::Texture * tex)448 bool DrawTexture(PerfInfo::ImGuiState *m_state, const Graphics::Texture *tex)
449 {
450 ImGui::BeginGroup();
451 auto pos0 = ImGui::GetCursorPos();
452
453 const vector2f texUVs = tex->GetDescriptor().texSize;
454 ImGui::Image(GetImTextureID(tex), { 128, 128 }, { 0, 0 }, { texUVs.x, texUVs.y });
455
456 auto pos1 = ImGui::GetCursorPos();
457 ImGui::SetCursorPos(pos0);
458
459 auto texSize = tex->GetDescriptor().dataSize;
460 ImGui::Text("%ux%u", uint32_t(texSize.x), uint32_t(texSize.y));
461 ImGui::Text("%.1f KB", double(tex->GetTextureMemSize()) / 1024.0);
462
463 ImGui::SetCursorPos(pos1);
464 ImGui::EndGroup();
465 return ImGui::IsItemClicked();
466 }
467
DrawTextureCache()468 void PerfInfo::DrawTextureCache()
469 {
470 if (ImGui::Begin("Texture Cache Viewer", &m_state->textureCacheViewerOpen, ImGuiWindowFlags_NoNav)) {
471 for (auto &v : m_state->indirectionMap)
472 // purge any textures which may have changed / evicted, but don't resize the vector
473 // in the average case, this will simply re-fill the vector each frame, a fairly trivial operation.
474 v.second.clear();
475
476 for (const auto &t : Pi::renderer->GetTextureCache()) {
477 const auto &key = t.first;
478 if (t.second->Get()->GetDescriptor().type != Graphics::TEXTURE_2D)
479 continue;
480
481 m_state->indirectionMap[key.first].push_back(key.second);
482 }
483
484 if (ImGui::BeginTabBar("Texture List")) {
485 const int item_width = 128 + ImGui::GetStyle().ItemSpacing.x;
486 for (const auto &t : m_state->indirectionMap) {
487 if (!ImGui::BeginTabItem(t.first.c_str()))
488 continue;
489
490 if (ImGui::BeginChild("##Texture List Scroll")) {
491 const int num_columns = std::max(int(ImGui::GetWindowContentRegionWidth()) / item_width, 1);
492 ImGui::Columns(num_columns);
493 if (num_columns > 1) {
494 for (int idx = 0; idx < num_columns; idx++)
495 ImGui::SetColumnWidth(idx, item_width);
496 }
497
498 for (const std::string &name : t.second) {
499 const auto *ptr = Pi::renderer->GetCachedTexture(t.first, name);
500 if (DrawTexture(m_state, ptr)) {
501 m_state->selectedTexture = { t.first, name };
502 m_state->hasSelectedTexture = true;
503 }
504
505 if (ImGui::IsItemHovered())
506 ImGui::SetTooltip("%s", name.c_str());
507
508 if (num_columns > 1)
509 ImGui::NextColumn();
510 }
511 }
512 ImGui::EndChild();
513
514 ImGui::EndTabItem();
515 }
516
517 ImGui::EndTabBar();
518 }
519 }
520 ImGui::End();
521 }
522
DrawTextureInspector()523 void PerfInfo::DrawTextureInspector()
524 {
525 const auto &selectedTex = m_state->selectedTexture;
526 const auto *tex = Pi::renderer->GetCachedTexture(selectedTex.first, selectedTex.second);
527 if (tex != nullptr && m_state->hasSelectedTexture) {
528 ImGui::SetNextWindowSize({ 300, 400 }, ImGuiCond_Appearing);
529 std::string window_name = selectedTex.second + "###Texture Inspector";
530 if (ImGui::Begin(window_name.c_str(), &m_state->hasSelectedTexture)) {
531 const Graphics::TextureDescriptor &descriptor = tex->GetDescriptor();
532
533 // If we have POT-extended textures, only display the part of the texture corresponding to the actual texture data.
534 const vector2f texUVs = tex->GetDescriptor().texSize;
535 ImGui::Image(GetImTextureID(tex), { 256, 256 }, { 0, 0 }, { texUVs.x, texUVs.y });
536 ImGui::Text("Dimensions: %ux%u", uint32_t(descriptor.dataSize.x), uint32_t(descriptor.dataSize.y));
537 ImGui::Value("Mipmap Count", tex->GetDescriptor().numberOfMipMaps);
538 ImGui::Value("VRAM Size", tex->GetTextureMemSize());
539 if (texUVs.x < 1.0 || texUVs.y < 1.0)
540 ImGui::Text("Original Size: %ux%u", int(texUVs.x * descriptor.dataSize.x), int(texUVs.y * descriptor.dataSize.y));
541
542 ImGui::Spacing();
543 ImGui::Value("Cache Bucket", selectedTex.first);
544 ImGui::Value("Cache ID", selectedTex.second);
545 ImGui::Value("TextureID", tex->GetTextureID());
546 ImGui::Value("RefCount", tex->GetRefCount());
547 }
548 ImGui::End();
549 }
550 }
551