Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/UI/ImDebugger/ImGe.cpp
3186 views
1
#include "ext/imgui/imgui.h"
2
#include "ext/imgui/imgui_internal.h"
3
#include "ext/imgui/imgui_extras.h"
4
#include "ext/imgui/imgui_impl_thin3d.h"
5
#include "Common/Data/Convert/ColorConv.h"
6
#include "UI/ImDebugger/ImGe.h"
7
#include "UI/ImDebugger/ImDebugger.h"
8
#include "GPU/Common/GPUDebugInterface.h"
9
#include "GPU/Common/FramebufferManagerCommon.h"
10
#include "GPU/Common/TextureCacheCommon.h"
11
#include "GPU/Common/VertexDecoderCommon.h"
12
13
#include "Core/HLE/sceDisplay.h"
14
#include "Core/HW/Display.h"
15
#include "Common/StringUtils.h"
16
#include "GPU/Debugger/State.h"
17
#include "GPU/Debugger/GECommandTable.h"
18
#include "GPU/Debugger/Breakpoints.h"
19
#include "GPU/Debugger/Stepping.h"
20
#include "GPU/Debugger/Debugger.h"
21
#include "GPU/GPUState.h"
22
23
void DrawFramebuffersWindow(ImConfig &cfg, FramebufferManagerCommon *framebufferManager) {
24
ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
25
if (!ImGui::Begin("Framebuffers", &cfg.framebuffersOpen)) {
26
ImGui::End();
27
return;
28
}
29
30
if (!framebufferManager) {
31
ImGui::TextUnformatted("(Framebuffers not available in software mode)");
32
ImGui::End();
33
return;
34
}
35
36
ImGui::BeginTable("framebuffers", 4);
37
ImGui::TableSetupColumn("Tag", ImGuiTableColumnFlags_WidthFixed);
38
ImGui::TableSetupColumn("Color Addr", ImGuiTableColumnFlags_WidthFixed);
39
ImGui::TableSetupColumn("Depth Addr", ImGuiTableColumnFlags_WidthFixed);
40
ImGui::TableSetupColumn("Size", ImGuiTableColumnFlags_WidthFixed);
41
42
ImGui::TableHeadersRow();
43
44
const std::vector<VirtualFramebuffer *> &vfbs = framebufferManager->GetVFBs();
45
46
for (int i = 0; i < (int)vfbs.size(); i++) {
47
ImGui::TableNextRow();
48
ImGui::TableNextColumn();
49
50
auto &vfb = vfbs[i];
51
52
const char *tag = vfb->fbo ? vfb->fbo->Tag() : "(no tag)";
53
54
ImGui::PushID(i);
55
if (ImGui::Selectable(tag, cfg.selectedFramebuffer == i, ImGuiSelectableFlags_AllowDoubleClick | ImGuiSelectableFlags_SpanAllColumns)) {
56
cfg.selectedFramebuffer = i;
57
}
58
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
59
cfg.selectedFramebuffer = i;
60
ImGui::OpenPopup("framebufferPopup");
61
}
62
ImGui::TableNextColumn();
63
ImGui::Text("%08x", vfb->fb_address);
64
ImGui::TableNextColumn();
65
ImGui::Text("%08x", vfb->z_address);
66
ImGui::TableNextColumn();
67
ImGui::Text("%dx%d", vfb->width, vfb->height);
68
if (ImGui::BeginPopup("framebufferPopup")) {
69
ImGui::Text("Framebuffer: %s", tag);
70
ImGui::EndPopup();
71
}
72
ImGui::PopID();
73
}
74
ImGui::EndTable();
75
76
// Fix out-of-bounds issues when framebuffers are removed.
77
if (cfg.selectedFramebuffer >= vfbs.size()) {
78
cfg.selectedFramebuffer = -1;
79
}
80
81
if (cfg.selectedFramebuffer != -1) {
82
// Now, draw the image of the selected framebuffer.
83
Draw::Framebuffer *fb = vfbs[cfg.selectedFramebuffer]->fbo;
84
ImTextureID texId = ImGui_ImplThin3d_AddFBAsTextureTemp(fb, Draw::Aspect::COLOR_BIT, ImGuiPipeline::TexturedOpaque);
85
ImGui::Image(texId, ImVec2(fb->Width(), fb->Height()));
86
}
87
88
ImGui::End();
89
}
90
91
void DrawTexturesWindow(ImConfig &cfg, TextureCacheCommon *textureCache) {
92
ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
93
if (!ImGui::Begin("Textures", &cfg.texturesOpen)) {
94
ImGui::End();
95
return;
96
}
97
98
if (!textureCache) {
99
ImGui::TextUnformatted("Texture cache not available");
100
ImGui::End();
101
return;
102
}
103
104
ImVec2 avail = ImGui::GetContentRegionAvail();
105
auto &style = ImGui::GetStyle();
106
ImGui::BeginChild("left", ImVec2(140.0f, 0.0f), ImGuiChildFlags_ResizeX);
107
float window_visible_x2 = ImGui::GetCursorScreenPos().x + ImGui::GetContentRegionAvail().x;
108
109
// Global texture stats
110
int replacementStateCounts[(int)ReplacementState::COUNT]{};
111
if (!textureCache->SecondCache().empty()) {
112
ImGui::Text("Primary Cache");
113
}
114
115
for (auto &iter : textureCache->Cache()) {
116
u64 id = iter.first;
117
const TexCacheEntry *entry = iter.second.get();
118
void *nativeView = textureCache->GetNativeTextureView(iter.second.get(), true);
119
int w = 128;
120
int h = 128;
121
122
if (entry->replacedTexture) {
123
replacementStateCounts[(int)entry->replacedTexture->State()]++;
124
}
125
126
ImTextureID texId = ImGui_ImplThin3d_AddNativeTextureTemp(nativeView);
127
float last_button_x2 = ImGui::GetItemRectMax().x;
128
float next_button_x2 = last_button_x2 + style.ItemSpacing.x + w; // Expected position if next button was on same line
129
if (next_button_x2 < window_visible_x2)
130
ImGui::SameLine();
131
132
float x = ImGui::GetCursorPosX();
133
if (ImGui::Selectable(("##Image" + std::to_string(id)).c_str(), cfg.selectedTexAddr == id, 0, ImVec2(w, h))) {
134
cfg.selectedTexAddr = id; // Update the selected index if clicked
135
}
136
137
ImGui::SameLine();
138
ImGui::SetCursorPosX(x + 2.0f);
139
ImGui::Image(texId, ImVec2(128, 128));
140
}
141
142
if (!textureCache->SecondCache().empty()) {
143
ImGui::Text("Secondary Cache (%d): TODO", (int)textureCache->SecondCache().size());
144
// TODO
145
}
146
147
ImGui::EndChild();
148
149
ImGui::SameLine();
150
ImGui::BeginChild("right", ImVec2(0.f, 0.0f));
151
if (ImGui::CollapsingHeader("Texture", nullptr, ImGuiTreeNodeFlags_DefaultOpen)) {
152
if (cfg.selectedTexAddr) {
153
auto iter = textureCache->Cache().find(cfg.selectedTexAddr);
154
if (iter != textureCache->Cache().end()) {
155
void *nativeView = textureCache->GetNativeTextureView(iter->second.get(), true);
156
ImTextureID texId = ImGui_ImplThin3d_AddNativeTextureTemp(nativeView);
157
const TexCacheEntry *entry = iter->second.get();
158
int dim = entry->dim;
159
int w = dimWidth(dim);
160
int h = dimHeight(dim);
161
ImGui::Image(texId, ImVec2(w, h));
162
ImGui::Text("%08x: %dx%d, %d mips, %s", (uint32_t)(cfg.selectedTexAddr & 0xFFFFFFFF), w, h, entry->maxLevel + 1, GeTextureFormatToString((GETextureFormat)entry->format));
163
ImGui::Text("Stride: %d", entry->bufw);
164
ImGui::Text("Status: %08x", entry->status); // TODO: Show the flags
165
ImGui::Text("Hash: %08x", entry->fullhash);
166
ImGui::Text("CLUT Hash: %08x", entry->cluthash);
167
ImGui::Text("Minihash: %08x", entry->minihash);
168
ImGui::Text("MaxSeenV: %08x", entry->maxSeenV);
169
if (entry->replacedTexture) {
170
if (ImGui::CollapsingHeader("Replacement", ImGuiTreeNodeFlags_DefaultOpen)) {
171
const auto &desc = entry->replacedTexture->Desc();
172
ImGui::Text("State: %s", StateString(entry->replacedTexture->State()));
173
// ImGui::Text("Original: %dx%d (%dx%d)", desc.w, desc.h, desc.newW, desc.newH);
174
if (entry->replacedTexture->State() == ReplacementState::ACTIVE) {
175
int w, h;
176
entry->replacedTexture->GetSize(0, &w, &h);
177
int numLevels = entry->replacedTexture->NumLevels();
178
ImGui::Text("Replaced: %dx%d, %d mip levels", w, h, numLevels);
179
ImGui::Text("Level 0 size: %d bytes, format: %s", entry->replacedTexture->GetLevelDataSizeAfterCopy(0), Draw::DataFormatToString(entry->replacedTexture->Format()));
180
}
181
ImGui::Text("Key: %08x_%08x", (u32)(desc.cachekey >> 32), (u32)desc.cachekey);
182
ImGui::Text("Hashfiles: %s", desc.hashfiles.c_str());
183
ImGui::Text("Base: %s", desc.basePath.c_str());
184
ImGui::Text("Alpha status: %02x", entry->replacedTexture->AlphaStatus());
185
}
186
} else {
187
ImGui::Text("Not replaced");
188
}
189
ImGui::Text("Frames until next full hash: %08x", entry->framesUntilNextFullHash); // TODO: Show the flags
190
} else {
191
cfg.selectedTexAddr = 0;
192
}
193
} else {
194
ImGui::Text("(no texture selected)");
195
}
196
}
197
198
if (ImGui::CollapsingHeader("Texture Cache State", nullptr, ImGuiTreeNodeFlags_DefaultOpen)) {
199
ImGui::Text("Cache: %d textures, size est %d", (int)textureCache->Cache().size(), (int)textureCache->CacheSizeEstimate());
200
if (!textureCache->SecondCache().empty()) {
201
ImGui::Text("Second: %d textures, size est %d", (int)textureCache->SecondCache().size(), (int)textureCache->SecondCacheSizeEstimate());
202
}
203
/*
204
ImGui::Text("Standard/shader scale factor: %d/%d", standardScaleFactor_, shaderScaleFactor_);
205
ImGui::Text("Texels scaled this frame: %d", texelsScaledThisFrame_);
206
ImGui::Text("Low memory mode: %d", (int)lowMemoryMode_);
207
*/
208
if (ImGui::CollapsingHeader("Texture Replacement", ImGuiTreeNodeFlags_DefaultOpen)) {
209
// ImGui::Text("Frame time/budget: %0.3f/%0.3f ms", replacementTimeThisFrame_ * 1000.0f, replacementFrameBudgetSeconds_ * 1000.0f);
210
ImGui::Text("UNLOADED: %d PENDING: %d NOT_FOUND: %d ACTIVE: %d CANCEL_INIT: %d",
211
replacementStateCounts[(int)ReplacementState::UNLOADED],
212
replacementStateCounts[(int)ReplacementState::PENDING],
213
replacementStateCounts[(int)ReplacementState::NOT_FOUND],
214
replacementStateCounts[(int)ReplacementState::ACTIVE],
215
replacementStateCounts[(int)ReplacementState::CANCEL_INIT]);
216
}
217
if (textureCache->Videos().size()) {
218
if (ImGui::CollapsingHeader("Tracked video playback memory")) {
219
for (auto &video : textureCache->Videos()) {
220
ImGui::Text("%08x: %d flips, size = %d", video.addr, video.flips, video.size);
221
}
222
}
223
}
224
}
225
226
ImGui::EndChild();
227
ImGui::End();
228
}
229
230
void DrawDisplayWindow(ImConfig &cfg, FramebufferManagerCommon *framebufferManager) {
231
ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
232
if (!ImGui::Begin("Display", &cfg.displayOpen)) {
233
ImGui::End();
234
return;
235
}
236
237
if (framebufferManager) {
238
ImGui::Checkbox("Display latched", &cfg.displayLatched);
239
240
PSPPointer<u8> topaddr;
241
u32 linesize;
242
u32 pixelFormat;
243
244
__DisplayGetFramebuf(&topaddr, &linesize, &pixelFormat, cfg.displayLatched);
245
246
VirtualFramebuffer *fb = framebufferManager->GetVFBAt(topaddr.ptr);
247
if (fb && fb->fbo) {
248
ImTextureID texId = ImGui_ImplThin3d_AddFBAsTextureTemp(fb->fbo, Draw::Aspect::COLOR_BIT, ImGuiPipeline::TexturedOpaque);
249
ImGui::Image(texId, ImVec2(fb->width, fb->height));
250
ImGui::Text("%s - %08x", fb->fbo->Tag(), topaddr.ptr);
251
} else {
252
// TODO: Sometimes we should display RAM here.
253
ImGui::Text("Framebuffer not available to display");
254
}
255
} else {
256
// TODO: We should implement this anyway for software mode.
257
// In the meantime, use the pixel viewer.
258
ImGui::TextUnformatted("Framebuffer manager not available");
259
}
260
261
ImGui::End();
262
}
263
264
// Note: This is not exclusively graphics.
265
void DrawDebugStatsWindow(ImConfig &cfg) {
266
ImGui::SetNextWindowSize(ImVec2(300, 500), ImGuiCond_FirstUseEver);
267
if (!ImGui::Begin("Debug Stats", &cfg.debugStatsOpen)) {
268
ImGui::End();
269
return;
270
}
271
char statbuf[4096];
272
__DisplayGetDebugStats(statbuf, sizeof(statbuf));
273
ImGui::TextUnformatted(statbuf);
274
ImGui::End();
275
}
276
277
void ImGePixelViewerWindow::Draw(ImConfig &cfg, ImControl &control, GPUDebugInterface *gpuDebug, Draw::DrawContext *draw) {
278
ImGui::SetNextWindowSize(ImVec2(600, 500), ImGuiCond_FirstUseEver);
279
if (!ImGui::Begin("Pixel Viewer", &cfg.pixelViewerOpen)) {
280
ImGui::End();
281
return;
282
}
283
284
if (gpuDebug->GetFramebufferManagerCommon()) {
285
if (gpuDebug->GetFramebufferManagerCommon()->GetVFBAt(viewer_.addr)) {
286
ImGui::Text("NOTE: There's a hardware framebuffer at %08x.", viewer_.addr);
287
// TODO: Add a button link.
288
}
289
}
290
291
if (ImGui::BeginChild("left", ImVec2(200.0f, 0.0f))) {
292
if (ImGui::InputScalar("Address", ImGuiDataType_U32, &viewer_.addr, 0, 0, "%08x")) {
293
viewer_.Snapshot();
294
}
295
296
if (ImGui::BeginCombo("Aspect", GeBufferFormatToString(viewer_.format))) {
297
for (int i = 0; i < 5; i++) {
298
if (ImGui::Selectable(GeBufferFormatToString((GEBufferFormat)i), i == (int)viewer_.format)) {
299
viewer_.format = (GEBufferFormat)i;
300
viewer_.Snapshot();
301
}
302
}
303
ImGui::EndCombo();
304
}
305
306
bool alphaPresent = viewer_.format == GE_FORMAT_8888 || viewer_.format == GE_FORMAT_4444 || viewer_.format == GE_FORMAT_5551;
307
308
if (!alphaPresent) {
309
ImGui::BeginDisabled();
310
}
311
if (ImGui::Checkbox("Use alpha", &viewer_.useAlpha)) {
312
viewer_.Snapshot();
313
}
314
if (ImGui::Checkbox("Show alpha", &viewer_.showAlpha)) {
315
viewer_.Snapshot();
316
}
317
if (!alphaPresent) {
318
ImGui::EndDisabled();
319
}
320
if (ImGui::InputScalar("Width", ImGuiDataType_U16, &viewer_.width)) {
321
viewer_.Snapshot();
322
}
323
if (ImGui::InputScalar("Height", ImGuiDataType_U16, &viewer_.height)) {
324
viewer_.Snapshot();
325
}
326
if (ImGui::InputScalar("Stride", ImGuiDataType_U16, &viewer_.stride)) {
327
viewer_.Snapshot();
328
}
329
if (viewer_.format == GE_FORMAT_DEPTH16) {
330
if (ImGui::SliderFloat("Scale", &viewer_.scale, 0.5f, 256.0f, "%.2f", ImGuiSliderFlags_Logarithmic)) {
331
viewer_.Snapshot();
332
}
333
}
334
if (ImGui::Button("Refresh")) {
335
viewer_.Snapshot();
336
}
337
if (ImGui::Button("Show cur depth")) {
338
viewer_.addr = gstate.getDepthBufRawAddress() | 0x04000000;
339
viewer_.format = GE_FORMAT_DEPTH16;
340
viewer_.stride = gstate.DepthBufStride();
341
viewer_.width = viewer_.stride;
342
viewer_.Snapshot();
343
}
344
if (ImGui::Button("Show cur color")) {
345
viewer_.addr = gstate.getFrameBufAddress();
346
viewer_.format = gstate.FrameBufFormat();
347
viewer_.stride = gstate.FrameBufStride();
348
viewer_.width = viewer_.stride;
349
viewer_.Snapshot();
350
}
351
ImGui::Checkbox("Realtime", &cfg.realtimePixelPreview);
352
}
353
ImGui::EndChild();
354
355
if (cfg.realtimePixelPreview) {
356
viewer_.Snapshot();
357
}
358
359
ImGui::SameLine();
360
if (ImGui::BeginChild("right")) {
361
ImVec2 p0 = ImGui::GetCursorScreenPos();
362
viewer_.Draw(gpuDebug, draw, 1.0f);
363
if (ImGui::IsItemHovered()) {
364
int x = (int)(ImGui::GetMousePos().x - p0.x);
365
int y = (int)(ImGui::GetMousePos().y - p0.y);
366
char temp[128];
367
if (viewer_.FormatValueAt(temp, sizeof(temp), x, y)) {
368
ImGui::Text("(%d, %d): %s", x, y, temp);
369
} else {
370
ImGui::Text("%d, %d: N/A", x, y);
371
}
372
} else {
373
ImGui::TextUnformatted("(no pixel hovered)");
374
}
375
}
376
ImGui::EndChild();
377
ImGui::End();
378
}
379
380
ImGePixelViewer::~ImGePixelViewer() {
381
if (texture_)
382
texture_->Release();
383
}
384
385
void ImGePixelViewer::DeviceLost() {
386
if (texture_) {
387
texture_->Release();
388
texture_ = nullptr;
389
}
390
}
391
392
bool ImGePixelViewer::Draw(GPUDebugInterface *gpuDebug, Draw::DrawContext *draw, float zoom) {
393
if (dirty_) {
394
UpdateTexture(draw);
395
dirty_ = false;
396
}
397
398
if (Memory::IsValid4AlignedAddress(addr)) {
399
if (texture_) {
400
ImTextureID texId = ImGui_ImplThin3d_AddTextureTemp(texture_, useAlpha ? ImGuiPipeline::TexturedAlphaBlend : ImGuiPipeline::TexturedOpaque);
401
ImGui::Image(texId, ImVec2((float)width * zoom, (float)height * zoom));
402
return true;
403
} else {
404
ImGui::Text("(invalid params: %dx%d, %08x)", width, height, addr);
405
}
406
} else {
407
ImGui::Text("(invalid address %08x)", addr);
408
}
409
return false;
410
}
411
412
bool ImGePixelViewer::FormatValueAt(char *buf, size_t bufSize, int x, int y) const {
413
// Go look directly in RAM.
414
int bpp = BufferFormatBytesPerPixel(format);
415
u32 pixelAddr = addr + (y * stride + x) * bpp;
416
switch (format) {
417
case GE_FORMAT_8888:
418
snprintf(buf, bufSize, "%08x", Memory::Read_U32(pixelAddr));
419
break;
420
case GE_FORMAT_4444:
421
{
422
u16 raw = Memory::Read_U16(pixelAddr);
423
snprintf(buf, bufSize, "%08x (raw: %04x)", RGBA4444ToRGBA8888(raw), raw);
424
break;
425
}
426
case GE_FORMAT_565:
427
{
428
u16 raw = Memory::Read_U16(pixelAddr);
429
snprintf(buf, bufSize, "%08x (raw: %04x)", RGB565ToRGBA8888(raw), raw);
430
break;
431
}
432
case GE_FORMAT_5551:
433
{
434
u16 raw = Memory::Read_U16(pixelAddr);
435
snprintf(buf, bufSize, "%08x (raw: %04x)", RGBA5551ToRGBA8888(raw), raw);
436
break;
437
}
438
case GE_FORMAT_DEPTH16:
439
{
440
u16 raw = Memory::Read_U16(pixelAddr);
441
snprintf(buf, bufSize, "%0.4f (raw: %04x / %d)", (float)raw / 65535.0f, raw, raw);
442
break;
443
}
444
default:
445
snprintf(buf, bufSize, "N/A");
446
return false;
447
}
448
return true;
449
}
450
451
void ImGePixelViewer::UpdateTexture(Draw::DrawContext *draw) {
452
if (texture_) {
453
texture_->Release();
454
texture_ = nullptr;
455
}
456
if (!Memory::IsValid4AlignedAddress(addr) || width == 0 || height == 0 || stride > 1024 || stride == 0) {
457
// TODO: Show a warning triangle or something.
458
return;
459
}
460
461
int bpp = BufferFormatBytesPerPixel(format);
462
463
int srcBytes = width * stride * bpp;
464
if (stride > width) {
465
srcBytes -= stride - width;
466
}
467
if (Memory::ValidSize(addr, srcBytes) != srcBytes) {
468
// TODO: Show a message that the address is out of bounds.
469
return;
470
}
471
472
// Read pixels into a buffer and transform them accordingly.
473
// For now we convert all formats to RGBA here, for backend compatibility.
474
uint8_t *buf = new uint8_t[width * height * 4];
475
476
for (int y = 0; y < height; y++) {
477
u32 rowAddr = addr + y * stride * bpp;
478
const u8 *src = Memory::GetPointerUnchecked(rowAddr);
479
u8 *dst = buf + y * width * 4;
480
switch (format) {
481
case GE_FORMAT_8888:
482
if (showAlpha) {
483
for (int x = 0; x < width; x++) {
484
dst[0] = src[3];
485
dst[1] = src[3];
486
dst[2] = src[3];
487
dst[3] = 0xFF;
488
src += 4;
489
dst += 4;
490
}
491
} else {
492
memcpy(dst, src, width * 4);
493
}
494
break;
495
case GE_FORMAT_565:
496
// No showAlpha needed (would just be white)
497
ConvertRGB565ToRGBA8888((u32 *)dst, (const u16 *)src, width);
498
break;
499
case GE_FORMAT_5551:
500
if (showAlpha) {
501
uint32_t *dst32 = (uint32_t *)dst;
502
uint16_t *src16 = (uint16_t *)dst;
503
for (int x = 0; x < width; x++) {
504
dst32[x] = (src16[x] >> 15) ? 0xFFFFFFFF : 0xFF000000;
505
}
506
} else {
507
ConvertRGBA5551ToRGBA8888((u32 *)dst, (const u16 *)src, width);
508
}
509
break;
510
case GE_FORMAT_4444:
511
ConvertRGBA4444ToRGBA8888((u32 *)dst, (const u16 *)src, width);
512
break;
513
case GE_FORMAT_DEPTH16:
514
{
515
uint16_t *src16 = (uint16_t *)src;
516
float scale = this->scale / 256.0f;
517
for (int x = 0; x < width; x++) {
518
// Just pick off the upper bits by adding 1 to the byte address
519
// We don't visualize the lower bits for now, although we could - should add a scale slider like RenderDoc.
520
float fval = (float)src16[x] * scale;
521
u8 val;
522
if (fval < 0.0f) {
523
val = 0;
524
} else if (fval >= 255.0f) {
525
val = 255;
526
} else {
527
val = (u8)fval;
528
}
529
dst[0] = val;
530
dst[1] = val;
531
dst[2] = val;
532
dst[3] = 0xFF;
533
dst += 4;
534
}
535
break;
536
}
537
default:
538
memset(buf, 0x80, width * height * 4);
539
break;
540
}
541
}
542
543
Draw::TextureDesc desc{ Draw::TextureType::LINEAR2D,
544
Draw::DataFormat::R8G8B8A8_UNORM,
545
(int)width,
546
(int)height,
547
1,
548
1,
549
false,
550
Draw::TextureSwizzle::DEFAULT,
551
"PixelViewer temp",
552
{ buf },
553
nullptr,
554
};
555
556
texture_ = draw->CreateTexture(desc);
557
}
558
559
ImGeReadbackViewer::ImGeReadbackViewer() {
560
// These are only forward declared in the header, so we initialize them here.
561
aspect = Draw::Aspect::COLOR_BIT;
562
readbackFmt_ = Draw::DataFormat::UNDEFINED;
563
}
564
565
ImGeReadbackViewer::~ImGeReadbackViewer() {
566
if (texture_)
567
texture_->Release();
568
delete[] data_;
569
}
570
571
void ImGeReadbackViewer::DeviceLost() {
572
if (texture_) {
573
texture_->Release();
574
texture_ = nullptr;
575
}
576
}
577
578
bool ImGeReadbackViewer::Draw(GPUDebugInterface *gpuDebug, Draw::DrawContext *draw, float zoom) {
579
FramebufferManagerCommon *fbmanager = gpuDebug->GetFramebufferManagerCommon();
580
if (!vfb || !vfb->fbo || !fbmanager) {
581
ImGui::TextUnformatted("(N/A)");
582
return false;
583
}
584
585
if (dirty_) {
586
dirty_ = false;
587
588
delete[] data_;
589
int w = vfb->fbo->Width();
590
int h = vfb->fbo->Height();
591
int rbBpp = 4;
592
switch (aspect) {
593
case Draw::Aspect::COLOR_BIT:
594
readbackFmt_ = Draw::DataFormat::R8G8B8A8_UNORM;
595
break;
596
case Draw::Aspect::DEPTH_BIT:
597
// TODO: Add fallback
598
readbackFmt_ = Draw::DataFormat::D32F;
599
break;
600
case Draw::Aspect::STENCIL_BIT:
601
readbackFmt_ = Draw::DataFormat::S8;
602
rbBpp = 1;
603
break;
604
default:
605
break;
606
}
607
608
data_ = new uint8_t[w * h * rbBpp];
609
draw->CopyFramebufferToMemory(vfb->fbo, aspect, 0, 0, w, h, readbackFmt_, data_, w, Draw::ReadbackMode::BLOCK, "debugger");
610
611
if (texture_) {
612
texture_->Release();
613
texture_ = nullptr;
614
}
615
616
// For now, we just draw the color texture. The others we convert.
617
if (aspect != Draw::Aspect::COLOR_BIT) {
618
uint8_t *texData = data_;
619
if (aspect == Draw::Aspect::DEPTH_BIT && scale != 1.0f) {
620
texData = new uint8_t[w * h * rbBpp];
621
// Apply scale
622
float *ptr = (float *)data_;
623
float *tptr = (float *)texData;
624
for (int i = 0; i < w * h; i++) {
625
tptr[i] = ptr[i] * scale;
626
}
627
}
628
629
Draw::DataFormat fmt = rbBpp == 1 ? Draw::DataFormat::R8_UNORM : Draw::DataFormat::R32_FLOAT;
630
Draw::TextureDesc desc{ Draw::TextureType::LINEAR2D,
631
fmt,
632
(int)w,
633
(int)h,
634
1,
635
1,
636
false,
637
Draw::DataFormatNumChannels(fmt) == 1 ? Draw::TextureSwizzle::R8_AS_GRAYSCALE: Draw::TextureSwizzle::DEFAULT,
638
"PixelViewer temp",
639
{ texData },
640
nullptr,
641
};
642
643
texture_ = draw->CreateTexture(desc);
644
645
if (texData != data_) {
646
delete[] texData;
647
}
648
}
649
}
650
651
ImTextureID texId;
652
if (texture_) {
653
texId = ImGui_ImplThin3d_AddTextureTemp(texture_, ImGuiPipeline::TexturedOpaque);
654
} else {
655
texId = ImGui_ImplThin3d_AddFBAsTextureTemp(vfb->fbo, Draw::Aspect::COLOR_BIT, ImGuiPipeline::TexturedOpaque);
656
}
657
ImGui::Image(texId, ImVec2((float)vfb->fbo->Width() * zoom, (float)vfb->fbo->Height() * zoom));
658
return true;
659
}
660
661
bool ImGeReadbackViewer::FormatValueAt(char *buf, size_t bufSize, int x, int y) const {
662
if (!vfb || !vfb->fbo || !data_) {
663
snprintf(buf, bufSize, "N/A");
664
return true;
665
}
666
int bpp = (int)Draw::DataFormatSizeInBytes(readbackFmt_);
667
int offset = (y * vfb->fbo->Width() + x) * bpp;
668
switch (readbackFmt_) {
669
case Draw::DataFormat::R8G8B8A8_UNORM:
670
{
671
const uint32_t *read32 = (const uint32_t *)(data_ + offset);
672
snprintf(buf, bufSize, "%08x", *read32);
673
return true;
674
}
675
case Draw::DataFormat::D32F:
676
{
677
const float *read = (const float *)(data_ + offset);
678
float value = *read;
679
int ivalue = *read * 65535.0f;
680
snprintf(buf, bufSize, "%0.4f (raw: %04x / %d)", *read, ivalue, ivalue);
681
return true;
682
}
683
case Draw::DataFormat::S8:
684
{
685
uint8_t value = data_[offset];
686
snprintf(buf, bufSize, "%d (%02x)", value, value);
687
return true;
688
}
689
default:
690
return false;
691
}
692
}
693
694
void ImGeDisasmView::NotifyStep() {
695
if (followPC_) {
696
gotoPC_ = true;
697
}
698
}
699
700
void ImGeDisasmView::Draw(GPUDebugInterface *gpuDebug) {
701
const u32 branchColor = 0xFFA0FFFF;
702
const u32 gteColor = 0xFFFFEFA0;
703
704
static ImVec2 scrolling(0.0f, 0.0f);
705
706
ImGui_PushFixedFont();
707
708
ImVec2 canvas_p0 = ImGui::GetCursorScreenPos(); // ImDrawList API uses screen coordinates!
709
ImVec2 canvas_sz = ImGui::GetContentRegionAvail(); // Resize canvas to what's available
710
int lineHeight = (int)ImGui::GetTextLineHeightWithSpacing();
711
if (canvas_sz.x < 50.0f) canvas_sz.x = 50.0f;
712
if (canvas_sz.y < 50.0f) canvas_sz.y = 50.0f;
713
canvas_sz.y -= lineHeight * 2;
714
715
// This will catch our interactions
716
bool pressed = ImGui::InvisibleButton("canvas", canvas_sz, ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight);
717
ImGui::SetItemKeyOwner(ImGuiKey_MouseWheelY);
718
719
ImVec2 canvas_p1 = ImVec2(canvas_p0.x + canvas_sz.x, canvas_p0.y + canvas_sz.y);
720
721
// Draw border and background color
722
ImGuiIO& io = ImGui::GetIO();
723
ImDrawList* draw_list = ImGui::GetWindowDrawList();
724
draw_list->PushClipRect(canvas_p0, canvas_p1, true);
725
726
draw_list->AddRectFilled(canvas_p0, canvas_p1, IM_COL32(25, 25, 25, 255));
727
728
int numLines = (int)(canvas_sz.y / lineHeight + 1.0f);
729
u32 instrWidth = 4;
730
u32 windowStartAddr = selectedAddr_ - (numLines / 2) * instrWidth;
731
732
DisplayList displayList;
733
u32 pc = 0xFFFFFFFF;
734
u32 stallAddr = 0xFFFFFFFF;
735
if (gpuDebug->GetCurrentDisplayList(displayList)) {
736
pc = displayList.pc;
737
stallAddr = displayList.stall;
738
}
739
740
if (pc != 0xFFFFFFFF && gotoPC_) {
741
selectedAddr_ = pc;
742
gotoPC_ = false;
743
}
744
745
float pcY = canvas_p0.y + ((pc - windowStartAddr) / instrWidth) * lineHeight;
746
draw_list->AddRectFilled(ImVec2(canvas_p0.x, pcY), ImVec2(canvas_p1.x, pcY + lineHeight), IM_COL32(0x10, 0x70, 0x10, 255));
747
float stallY = canvas_p0.y + ((stallAddr - windowStartAddr) / instrWidth) * lineHeight;
748
draw_list->AddRectFilled(ImVec2(canvas_p0.x, stallY), ImVec2(canvas_p1.x, stallY + lineHeight), IM_COL32(0x70, 0x20, 0x10, 255));
749
u32 addr = windowStartAddr;
750
for (int line = 0; line < numLines; line++) {
751
char addrBuffer[128];
752
snprintf(addrBuffer, sizeof(addrBuffer), "%08x", addr);
753
754
ImVec2 lineStart = ImVec2(canvas_p0.x + lineHeight + 8, canvas_p0.y + line * lineHeight);
755
ImVec2 opcodeStart = ImVec2(canvas_p0.x + 120, canvas_p0.y + line * lineHeight);
756
ImVec2 descStart = ImVec2(canvas_p0.x + 220, canvas_p0.y + line * lineHeight);
757
ImVec2 liveStart = ImVec2(canvas_p0.x + 250, canvas_p0.y + line * lineHeight);
758
if (Memory::IsValid4AlignedAddress(addr)) {
759
draw_list->AddText(lineStart, 0xFFC0C0C0, addrBuffer);
760
761
u32 opcode = Memory::Read_U32(addr);
762
GPUDebugOp op = gpuDebug->DisassembleOp(addr, opcode);
763
u32 color = 0xFFFFFFFF;
764
char temp[16];
765
snprintf(temp, sizeof(temp), "%08x", op.op);
766
draw_list->AddText(opcodeStart, color, temp);
767
draw_list->AddText(descStart, color, op.desc.data(), op.desc.data() + op.desc.size());
768
// if (selectedAddr_ == addr && strlen(disMeta.liveInfo)) {
769
// draw_list->AddText(liveStart, 0xFFFFFFFF, disMeta.liveInfo);
770
// }
771
772
bool bp = gpuDebug->GetBreakpoints()->IsAddressBreakpoint(addr);
773
if (bp) {
774
draw_list->AddCircleFilled(ImVec2(canvas_p0.x + lineHeight * 0.5f, lineStart.y + lineHeight * 0.5f), lineHeight * 0.45f, 0xFF0000FF, 12);
775
}
776
} else {
777
draw_list->AddText(lineStart, 0xFF808080, addrBuffer);
778
}
779
addr += instrWidth;
780
}
781
782
// Draw a rectangle around the selected line.
783
int selectedY = canvas_p0.y + ((selectedAddr_ - windowStartAddr) / instrWidth) * lineHeight;
784
draw_list->AddRect(ImVec2(canvas_p0.x, selectedY), ImVec2(canvas_p1.x, selectedY + lineHeight), IM_COL32(255, 255, 255, 255));
785
if (dragAddr_ != selectedAddr_ && dragAddr_ != INVALID_ADDR) {
786
int dragY = canvas_p0.y + ((dragAddr_ - windowStartAddr) / instrWidth) * lineHeight;
787
draw_list->AddRect(ImVec2(canvas_p0.x, dragY), ImVec2(canvas_p1.x, dragY + lineHeight), IM_COL32(128, 128, 128, 255));
788
}
789
790
const bool is_hovered = ImGui::IsItemHovered(); // Hovered
791
const bool is_active = ImGui::IsItemActive(); // Held
792
const ImVec2 mouse_pos_in_canvas(io.MousePos.x - canvas_p0.x, io.MousePos.y - canvas_p0.y);
793
794
if (is_active) {
795
dragAddr_ = windowStartAddr + ((int)mouse_pos_in_canvas.y / lineHeight) * instrWidth;
796
}
797
798
if (pressed) {
799
if (io.MousePos.x < canvas_p0.x + lineHeight) {
800
// Toggle breakpoint
801
if (!gpuDebug->GetBreakpoints()->IsAddressBreakpoint(dragAddr_)) {
802
gpuDebug->GetBreakpoints()->AddAddressBreakpoint(dragAddr_);
803
} else {
804
gpuDebug->GetBreakpoints()->RemoveAddressBreakpoint(dragAddr_);
805
}
806
bpPopup_ = true;
807
} else {
808
selectedAddr_ = dragAddr_;
809
bpPopup_ = false;
810
}
811
}
812
ImGui_PopFont();
813
draw_list->PopClipRect();
814
815
// Context menu (under default mouse threshold)
816
ImGui::OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
817
if (ImGui::BeginPopup("context")) {
818
if (bpPopup_) {
819
if (ImGui::MenuItem("Remove breakpoint", NULL, false)) {
820
gpuDebug->GetBreakpoints()->RemoveAddressBreakpoint(dragAddr_);
821
}
822
} else if (Memory::IsValid4AlignedAddress(dragAddr_)) {
823
char buffer[64];
824
u32 opcode = Memory::Read_U32(dragAddr_);
825
GPUDebugOp op = gpuDebug->DisassembleOp(dragAddr_, opcode);
826
// affect dragAddr_?
827
if (ImGui::MenuItem("Copy Address", NULL, false)) {
828
snprintf(buffer, sizeof(buffer), "%08x", dragAddr_);
829
ImGui::SetClipboardText(buffer);
830
INFO_LOG(Log::G3D, "Copied '%s'", buffer);
831
}
832
if (ImGui::MenuItem("Copy Instruction Hex", NULL, false)) {
833
snprintf(buffer, sizeof(buffer), "%08x", (u32)op.op);
834
ImGui::SetClipboardText(buffer);
835
INFO_LOG(Log::G3D, "Copied '%s'", buffer);
836
}
837
/*
838
if (meta.instructionFlags & IF_BRANCHFIXED) {
839
if (ImGui::MenuItem("Follow Branch")) {
840
u32 target = GetBranchTarget(meta.opcode, dragAddr_, meta.instructionFlags);
841
if (target != 0xFFFFFFFF) {
842
selectedAddr_ = target;
843
}
844
}
845
}*/
846
}
847
ImGui::EndPopup();
848
}
849
850
if (pressed) {
851
// INFO_LOG(Log::UI, "Pressed");
852
}
853
}
854
855
static const char *DLStateToString(DisplayListState state) {
856
switch (state) {
857
case PSP_GE_DL_STATE_NONE: return "None";
858
case PSP_GE_DL_STATE_QUEUED: return "Queued";
859
case PSP_GE_DL_STATE_RUNNING: return "Running";
860
case PSP_GE_DL_STATE_COMPLETED: return "Completed";
861
case PSP_GE_DL_STATE_PAUSED: return "Paused";
862
default: return "N/A (bad)";
863
}
864
}
865
866
static void DrawPreviewPrimitive(ImDrawList *drawList, ImVec2 p0, GEPrimitiveType prim, const std::vector<u16> &indices, const std::vector<GPUDebugVertex> &verts, int count, bool uvToPos, float sx = 1.0f, float sy = 1.0f) {
867
if (count) {
868
auto x = [sx, uvToPos](const GPUDebugVertex &vert) {
869
return sx * (uvToPos ? vert.u : vert.x);
870
};
871
auto y = [sy, uvToPos](const GPUDebugVertex &vert) {
872
return sy * (uvToPos ? vert.v : vert.y);
873
};
874
875
// TODO: Maybe not the best idea to use the heavy AddTriangleFilled API instead of just adding raw triangles.
876
switch (prim) {
877
case GE_PRIM_TRIANGLES:
878
case GE_PRIM_RECTANGLES:
879
{
880
for (int i = 0; i < count - 2; i += 3) {
881
const auto &v1 = indices.empty() ? verts[i] : verts[indices[i]];
882
const auto &v2 = indices.empty() ? verts[i + 1] : verts[indices[i + 1]];
883
const auto &v3 = indices.empty() ? verts[i + 2] : verts[indices[i + 2]];
884
drawList->AddTriangleFilled(
885
ImVec2(p0.x + x(v1), p0.y + y(v1)),
886
ImVec2(p0.x + x(v2), p0.y + y(v2)),
887
ImVec2(p0.x + x(v3), p0.y + y(v3)), ImColor(0x600000FF));
888
}
889
break;
890
}
891
case GE_PRIM_TRIANGLE_FAN:
892
{
893
for (int i = 0; i < count - 2; i++) {
894
const auto &v1 = indices.empty() ? verts[0] : verts[indices[0]];
895
const auto &v2 = indices.empty() ? verts[i + 1] : verts[indices[i + 1]];
896
const auto &v3 = indices.empty() ? verts[i + 2] : verts[indices[i + 2]];
897
drawList->AddTriangleFilled(
898
ImVec2(p0.x + x(v1), p0.y + y(v1)),
899
ImVec2(p0.x + x(v2), p0.y + y(v2)),
900
ImVec2(p0.x + x(v3), p0.y + y(v3)), ImColor(0x600000FF));
901
}
902
break;
903
}
904
case GE_PRIM_TRIANGLE_STRIP:
905
{
906
int t = 2;
907
for (int i = 0; i < count - 2; i++) {
908
int i0 = i;
909
int i1 = i + t;
910
int i2 = i + (t ^ 3);
911
const auto &v1 = indices.empty() ? verts[i0] : verts[indices[i0]];
912
const auto &v2 = indices.empty() ? verts[i1] : verts[indices[i1]];
913
const auto &v3 = indices.empty() ? verts[i2] : verts[indices[i2]];
914
drawList->AddTriangleFilled(
915
ImVec2(p0.x + x(v1), p0.y + y(v1)),
916
ImVec2(p0.x + x(v2), p0.y + y(v2)),
917
ImVec2(p0.x + x(v3), p0.y + y(v3)), ImColor(0x600000FF));
918
t ^= 3;
919
}
920
break;
921
}
922
case GE_PRIM_LINES:
923
{
924
for (int i = 0; i < count - 1; i += 2) {
925
const auto &v1 = indices.empty() ? verts[i] : verts[indices[i]];
926
const auto &v2 = indices.empty() ? verts[i + 1] : verts[indices[i + 1]];
927
drawList->AddLine(
928
ImVec2(p0.x + x(v1), p0.y + y(v1)),
929
ImVec2(p0.x + x(v2), p0.y + y(v2)), ImColor(0x600000FF));
930
}
931
break;
932
}
933
case GE_PRIM_LINE_STRIP:
934
{
935
for (int i = 0; i < count - 2; i++) {
936
const auto &v1 = indices.empty() ? verts[i] : verts[indices[i]];
937
const auto &v2 = indices.empty() ? verts[i + 1] : verts[indices[i + 1]];
938
drawList->AddLine(
939
ImVec2(p0.x + x(v1), p0.y + y(v1)),
940
ImVec2(p0.x + x(v2), p0.y + y(v2)), ImColor(0x600000FF));
941
}
942
break;
943
}
944
default:
945
// TODO: Support lines etc.
946
break;
947
}
948
}
949
}
950
951
ImGeDebuggerWindow::ImGeDebuggerWindow() {
952
selectedAspect_ = Draw::Aspect::COLOR_BIT;
953
}
954
955
void ImGeDebuggerWindow::DeviceLost() {
956
rbViewer_.DeviceLost();
957
swViewer_.DeviceLost();
958
}
959
960
void ImGeDebuggerWindow::NotifyStep() {
961
reloadPreview_ = true;
962
disasmView_.NotifyStep();
963
964
// In software mode, or written back to RAM, the alpha channel is the stencil channel
965
switch (selectedAspect_) {
966
case Draw::Aspect::COLOR_BIT:
967
case Draw::Aspect::STENCIL_BIT:
968
swViewer_.width = gstate.FrameBufStride();
969
// Height heuristic
970
swViewer_.height = gstate.getScissorY2() + 1 - gstate.getScissorY1(); // Just guessing the height, we have no reliable way to tell
971
swViewer_.format = gstate.FrameBufFormat();
972
swViewer_.addr = gstate.getFrameBufAddress();
973
swViewer_.showAlpha = selectedAspect_ == Draw::Aspect::STENCIL_BIT;
974
swViewer_.useAlpha = false;
975
swViewer_.Snapshot();
976
break;
977
case Draw::Aspect::DEPTH_BIT:
978
swViewer_.width = gstate.DepthBufStride();
979
swViewer_.height = gstate.getScissorY2() + 1 - gstate.getScissorY1(); // Just guessing the height, we have no reliable way to tell
980
swViewer_.format = GE_FORMAT_DEPTH16;
981
swViewer_.addr = gstate.getDepthBufAddress();
982
swViewer_.showAlpha = false;
983
swViewer_.useAlpha = false;
984
break;
985
default:
986
break;
987
}
988
989
FramebufferManagerCommon *fbman = gpuDebug->GetFramebufferManagerCommon();
990
if (fbman) {
991
rbViewer_.vfb = fbman->GetExactVFB(gstate.getFrameBufAddress(), gstate.FrameBufStride(), gstate.FrameBufFormat());
992
rbViewer_.aspect = selectedAspect_;
993
}
994
rbViewer_.Snapshot();
995
}
996
997
void ImGeDebuggerWindow::Draw(ImConfig &cfg, ImControl &control, GPUDebugInterface *gpuDebug, Draw::DrawContext *draw) {
998
ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
999
if (!ImGui::Begin(Title(), &cfg.geDebuggerOpen)) {
1000
ImGui::End();
1001
return;
1002
}
1003
1004
ImGui::BeginDisabled(coreState != CORE_STEPPING_GE);
1005
if (ImGui::Button("Run/Resume")) {
1006
// Core_Resume();
1007
gpuDebug->SetBreakNext(GPUDebug::BreakNext::DEBUG_RUN);
1008
}
1009
ImGui::SameLine();
1010
if (ImGui::Button("...")) {
1011
ImGui::OpenPopup("dotdotdot");
1012
}
1013
if (ImGui::BeginPopup("dotdotdot")) {
1014
if (ImGui::MenuItem("RunFast")) {
1015
gpuDebug->ClearBreakNext();
1016
Core_Resume();
1017
}
1018
ImGui::EndPopup();
1019
}
1020
ImGui::EndDisabled();
1021
ImGui::SameLine();
1022
ImGui::TextUnformatted("Break:");
1023
ImGui::SameLine();
1024
//if (ImGui::Button("Frame")) {
1025
// TODO: This doesn't work correctly.
1026
// GPUDebug::SetBreakNext(GPUDebug::BreakNext::FRAME);
1027
//}
1028
1029
bool disableStepButtons = gpuDebug->GetBreakNext() != GPUDebug::BreakNext::NONE && gpuDebug->GetBreakNext() != GPUDebug::BreakNext::DEBUG_RUN;
1030
1031
if (disableStepButtons) {
1032
ImGui::BeginDisabled();
1033
}
1034
ImGui::SameLine();
1035
if (ImGui::RepeatButtonShift("Tex")) {
1036
gpuDebug->SetBreakNext(GPUDebug::BreakNext::TEX);
1037
}
1038
ImGui::SameLine();
1039
if (ImGui::RepeatButtonShift("Prim")) {
1040
gpuDebug->SetBreakNext(GPUDebug::BreakNext::PRIM);
1041
}
1042
ImGui::SameLine();
1043
if (ImGui::RepeatButtonShift("Draw")) {
1044
gpuDebug->SetBreakNext(GPUDebug::BreakNext::DRAW);
1045
}
1046
ImGui::SameLine();
1047
if (ImGui::Button("Block xfer")) {
1048
gpuDebug->SetBreakNext(GPUDebug::BreakNext::BLOCK_TRANSFER);
1049
}
1050
ImGui::SameLine();
1051
if (ImGui::Button("Curve")) {
1052
gpuDebug->SetBreakNext(GPUDebug::BreakNext::CURVE);
1053
}
1054
ImGui::SameLine();
1055
if (ImGui::RepeatButtonShift("Single step")) {
1056
gpuDebug->SetBreakNext(GPUDebug::BreakNext::OP);
1057
}
1058
if (disableStepButtons) {
1059
ImGui::EndDisabled();
1060
}
1061
1062
ImGui::Text("%d/%d", gpuDebug->PrimsThisFrame(), gpuDebug->PrimsLastFrame());
1063
1064
if (disableStepButtons) {
1065
ImGui::BeginDisabled();
1066
}
1067
ImGui::SameLine();
1068
ImGui::SetNextItemWidth(160.0f);
1069
ImGui::InputInt("Number", &cfg.breakCount);
1070
1071
ImGui::SameLine();
1072
if (ImGui::Button("Break on #")) {
1073
gpuDebug->SetBreakNext(GPUDebug::BreakNext::COUNT);
1074
gpuDebug->SetBreakCount(cfg.breakCount);
1075
}
1076
ImGui::SameLine();
1077
if (ImGui::Button("Step by")) {
1078
gpuDebug->SetBreakNext(GPUDebug::BreakNext::COUNT);
1079
gpuDebug->SetBreakCount(cfg.breakCount, true); // relative
1080
}
1081
if (disableStepButtons) {
1082
ImGui::EndDisabled();
1083
}
1084
1085
// Display any pending step event.
1086
if (gpuDebug->GetBreakNext() != GPUDebug::BreakNext::NONE && gpuDebug->GetBreakNext() != GPUDebug::BreakNext::DEBUG_RUN) {
1087
if (showBannerInFrames_ > 0) {
1088
showBannerInFrames_--;
1089
}
1090
if (showBannerInFrames_ == 0) {
1091
ImGui::Text("Step pending: %s", GPUDebug::BreakNextToString(gpuDebug->GetBreakNext()));
1092
ImGui::SameLine();
1093
if (gpuDebug->GetBreakNext() == GPUDebug::BreakNext::COUNT) {
1094
ImGui::Text("(%d)", gpuDebug->GetBreakCount());
1095
ImGui::SameLine();
1096
}
1097
if (ImGui::Button("Cancel step")) {
1098
gpuDebug->ClearBreakNext();
1099
}
1100
}
1101
} else {
1102
showBannerInFrames_ = 2;
1103
}
1104
1105
// Line break
1106
if (ImGui::Button("Goto PC")) {
1107
disasmView_.GotoPC();
1108
}
1109
ImGui::SameLine();
1110
if (ImGui::Button("Settings")) {
1111
ImGui::OpenPopup("disSettings");
1112
}
1113
if (ImGui::BeginPopup("disSettings")) {
1114
ImGui::Checkbox("Follow PC", &disasmView_.followPC_);
1115
ImGui::EndPopup();
1116
}
1117
1118
// First, let's list any active display lists in the left column, on top of the disassembly.
1119
1120
ImGui::BeginChild("left pane", ImVec2(400, 0), ImGuiChildFlags_Borders | ImGuiChildFlags_ResizeX);
1121
1122
if (ImGui::CollapsingHeader("Display lists")) {
1123
for (auto index : gpuDebug->GetDisplayListQueue()) {
1124
const auto &list = gpuDebug->GetDisplayList(index);
1125
char title[64];
1126
snprintf(title, sizeof(title), "List %d", list.id);
1127
if (ImGui::CollapsingHeader(title, ImGuiTreeNodeFlags_DefaultOpen)) {
1128
ImGui::Text("State: %s", DLStateToString(list.state));
1129
ImGui::TextUnformatted("PC:");
1130
ImGui::SameLine();
1131
ImClickableValue("pc", list.pc, control, ImCmd::SHOW_IN_GE_DISASM);
1132
ImGui::Text("StartPC:");
1133
ImGui::SameLine();
1134
ImClickableValue("startpc", list.startpc, control, ImCmd::SHOW_IN_GE_DISASM);
1135
if (list.pendingInterrupt) {
1136
ImGui::TextUnformatted("(Pending interrupt)");
1137
}
1138
if (list.stall) {
1139
ImGui::TextUnformatted("Stall addr:");
1140
ImGui::SameLine();
1141
ImClickableValue("stall", list.pc, control, ImCmd::SHOW_IN_GE_DISASM);
1142
}
1143
ImGui::Text("Stack depth: %d", (int)list.stackptr);
1144
ImGui::Text("BBOX result: %d", (int)list.bboxResult);
1145
}
1146
}
1147
}
1148
1149
// Display the disassembly view.
1150
disasmView_.Draw(gpuDebug);
1151
1152
ImGui::EndChild();
1153
1154
ImGui::SameLine();
1155
1156
u32 op = 0;
1157
DisplayList list;
1158
bool isOnBlockTransfer = false;
1159
if (gpuDebug->GetCurrentDisplayList(list)) {
1160
op = Memory::Read_U32(list.pc);
1161
1162
// TODO: Also add support for block transfer previews!
1163
1164
bool isOnPrim = false;
1165
switch (op >> 24) {
1166
case GE_CMD_PRIM:
1167
isOnPrim = true;
1168
if (reloadPreview_) {
1169
GetPrimPreview(op, previewPrim_, previewVertices_, previewIndices_, previewCount_);
1170
reloadPreview_ = false;
1171
}
1172
break;
1173
case GE_CMD_TRANSFERSTART:
1174
isOnBlockTransfer = true;
1175
break;
1176
default:
1177
// Disable the current preview.
1178
previewCount_ = 0;
1179
break;
1180
}
1181
1182
}
1183
1184
ImGui::BeginChild("texture/fb view");
1185
1186
ImDrawList *drawList = ImGui::GetWindowDrawList();
1187
1188
if (coreState == CORE_STEPPING_GE) {
1189
if (isOnBlockTransfer) {
1190
ImGui::Text("Block transfer! Proper preview coming in the future.\n");
1191
ImGui::Text("%08x -> %08x, %d bpp (strides: %d, %d)", gstate.getTransferSrcAddress(), gstate.getTransferDstAddress(), gstate.getTransferBpp(), gstate.getTransferSrcStride(), gstate.getTransferDstStride());
1192
ImGui::Text("%dx%d pixels", gstate.getTransferWidth(), gstate.getTransferHeight());
1193
ImGui::Text("Src pos: %d, %d", gstate.getTransferSrcX(), gstate.getTransferSrcY());
1194
ImGui::Text("Dst pos: %d, %d", gstate.getTransferDstX(), gstate.getTransferDstY());
1195
ImGui::Text("Total bytes to transfer: %d", gstate.getTransferWidth() * gstate.getTransferHeight() * gstate.getTransferBpp());
1196
} else {
1197
// Visualize prim by default (even if we're not directly on a prim instruction).
1198
VirtualFramebuffer *vfb = rbViewer_.vfb;
1199
if (vfb) {
1200
if (vfb->fbo) {
1201
ImGui::Text("Framebuffer: %s", vfb->fbo->Tag());
1202
} else {
1203
ImGui::Text("Framebuffer");
1204
}
1205
ImGui::SameLine();
1206
}
1207
ImGui::SetNextItemWidth(200.0f);
1208
ImGui::SliderFloat("Zoom", &previewZoom_, 0.125f, 2.f, "%.3f", ImGuiSliderFlags_Logarithmic);
1209
1210
// Use selectable instead of tab bar so we can get events (haven't figured that out).
1211
static const Draw::Aspect aspects[3] = { Draw::Aspect::COLOR_BIT, Draw::Aspect::DEPTH_BIT, Draw::Aspect::STENCIL_BIT, };
1212
static const char *const aspectNames[3] = { "Color", "Depth", "Stencil" };
1213
for (int i = 0; i < ARRAY_SIZE(aspects); i++) {
1214
if (i != 0)
1215
ImGui::SameLine();
1216
if (ImGui::Selectable(aspectNames[i], aspects[i] == selectedAspect_, 0, ImVec2(120.0f, 0.0f))) {
1217
selectedAspect_ = aspects[i];
1218
NotifyStep();
1219
}
1220
}
1221
1222
if (selectedAspect_ == Draw::Aspect::DEPTH_BIT) {
1223
float minimum = 0.5f;
1224
float maximum = 256.0f;
1225
ImGui::SameLine();
1226
ImGui::SetNextItemWidth(200.0f);
1227
if (ImGui::DragFloat("Z value scale", &rbViewer_.scale, 1.0f, 0.5f, 256.0f, "%0.2f", ImGuiSliderFlags_Logarithmic)) {
1228
rbViewer_.Snapshot();
1229
swViewer_.Snapshot();
1230
}
1231
}
1232
1233
const ImVec2 p0 = ImGui::GetCursorScreenPos();
1234
ImVec2 p1;
1235
float scale = 1.0f;
1236
if (vfb && vfb->fbo) {
1237
scale = vfb->renderScaleFactor;
1238
p1 = ImVec2(p0.x + vfb->fbo->Width() * previewZoom_, p0.y + vfb->fbo->Height() * previewZoom_);
1239
} else {
1240
// Guess
1241
p1 = ImVec2(p0.x + swViewer_.width, p0.y + swViewer_.height);
1242
}
1243
1244
// Draw border and background color
1245
drawList->PushClipRect(p0, p1, true);
1246
1247
PixelLookup *lookup = nullptr;
1248
if (vfb) {
1249
rbViewer_.Draw(gpuDebug, draw, previewZoom_);
1250
lookup = &rbViewer_;
1251
// ImTextureID texId = ImGui_ImplThin3d_AddFBAsTextureTemp(vfb->fbo, Draw::Aspect::COLOR_BIT, ImGuiPipeline::TexturedOpaque);
1252
// ImGui::Image(texId, ImVec2(vfb->width, vfb->height));
1253
} else {
1254
swViewer_.Draw(gpuDebug, draw, previewZoom_);
1255
lookup = &swViewer_;
1256
}
1257
1258
// Draw vertex preview on top!
1259
DrawPreviewPrimitive(drawList, p0, previewPrim_, previewIndices_, previewVertices_, previewCount_, false, scale * previewZoom_, scale * previewZoom_);
1260
1261
drawList->PopClipRect();
1262
1263
if (ImGui::IsItemHovered()) {
1264
int x = (int)(ImGui::GetMousePos().x - p0.x) * previewZoom_;
1265
int y = (int)(ImGui::GetMousePos().y - p0.y) * previewZoom_;
1266
char temp[128];
1267
if (lookup->FormatValueAt(temp, sizeof(temp), x, y)) {
1268
ImGui::Text("(%d, %d): %s", x, y, temp);
1269
} else {
1270
ImGui::Text("%d, %d: N/A", x, y);
1271
}
1272
} else {
1273
ImGui::TextUnformatted("(no pixel hovered)");
1274
}
1275
1276
if (vfb && vfb->fbo) {
1277
ImGui::Text("VFB %dx%d (emulated: %dx%d)", vfb->width, vfb->height, vfb->fbo->Width(), vfb->fbo->Height());
1278
} else {
1279
// Use the swViewer_!
1280
ImGui::Text("Raw FB: %08x (%s)", gstate.getFrameBufRawAddress(), GeBufferFormatToString(gstate.FrameBufFormat()));
1281
}
1282
1283
if (gstate.isModeClear()) {
1284
ImGui::Text("(clear mode - texturing not used)");
1285
} else if (!gstate.isTextureMapEnabled()) {
1286
ImGui::Text("(texturing not enabled");
1287
} else {
1288
TextureCacheCommon *texcache = gpuDebug->GetTextureCacheCommon();
1289
TexCacheEntry *tex = texcache ? texcache->SetTexture() : nullptr;
1290
if (tex) {
1291
ImGui::Text("Texture: %08x", tex->addr);
1292
texcache->ApplyTexture(false);
1293
1294
void *nativeView = texcache->GetNativeTextureView(tex, true);
1295
ImTextureID texId = ImGui_ImplThin3d_AddNativeTextureTemp(nativeView);
1296
1297
float texW = dimWidth(tex->dim);
1298
float texH = dimHeight(tex->dim);
1299
1300
const ImVec2 p0 = ImGui::GetCursorScreenPos();
1301
const ImVec2 sz = ImGui::GetContentRegionAvail();
1302
const ImVec2 p1 = ImVec2(p0.x + texW, p0.y + texH);
1303
1304
// Draw border and background color
1305
drawList->PushClipRect(p0, p1, true);
1306
1307
ImGui::Image(texId, ImVec2(texW, texH));
1308
DrawPreviewPrimitive(drawList, p0, previewPrim_, previewIndices_, previewVertices_, previewCount_, true, texW, texH);
1309
1310
drawList->PopClipRect();
1311
1312
} else {
1313
ImGui::Text("(no valid texture bound)");
1314
// In software mode, we should just decode the texture here.
1315
// TODO: List some of the texture params here.
1316
}
1317
}
1318
1319
// Let's display the current CLUT.
1320
}
1321
} else {
1322
ImGui::Text("Click the buttons above (Tex, etc) to stop");
1323
}
1324
1325
ImGui::EndChild();
1326
1327
ImGui::End();
1328
}
1329
1330
struct StateItem {
1331
bool header; GECommand cmd; const char *title; bool closedByDefault;
1332
};
1333
1334
static const StateItem g_rasterState[] = {
1335
{true, GE_CMD_NOP, "Framebuffer"},
1336
{false, GE_CMD_FRAMEBUFPTR},
1337
{false, GE_CMD_FRAMEBUFPIXFORMAT},
1338
{false, GE_CMD_CLEARMODE},
1339
1340
{true, GE_CMD_ZTESTENABLE},
1341
{false, GE_CMD_ZBUFPTR},
1342
{false, GE_CMD_ZTEST},
1343
{false, GE_CMD_ZWRITEDISABLE},
1344
1345
{true, GE_CMD_STENCILTESTENABLE},
1346
{false, GE_CMD_STENCILTEST},
1347
{false, GE_CMD_STENCILOP},
1348
1349
{true, GE_CMD_ALPHABLENDENABLE},
1350
{false, GE_CMD_BLENDMODE},
1351
{false, GE_CMD_BLENDFIXEDA},
1352
{false, GE_CMD_BLENDFIXEDB},
1353
1354
{true, GE_CMD_ALPHATESTENABLE},
1355
{false, GE_CMD_ALPHATEST},
1356
1357
{true, GE_CMD_COLORTESTENABLE},
1358
{false, GE_CMD_COLORTEST},
1359
{false, GE_CMD_COLORTESTMASK},
1360
1361
{true, GE_CMD_FOGENABLE},
1362
{false, GE_CMD_FOGCOLOR},
1363
{false, GE_CMD_FOG1},
1364
{false, GE_CMD_FOG2},
1365
1366
{true, GE_CMD_CULLFACEENABLE},
1367
{false, GE_CMD_CULL},
1368
1369
{true, GE_CMD_LOGICOPENABLE},
1370
{false, GE_CMD_LOGICOP},
1371
1372
{true, GE_CMD_NOP, "Clipping/Clamping"},
1373
{false, GE_CMD_MINZ},
1374
{false, GE_CMD_MAXZ},
1375
{false, GE_CMD_DEPTHCLAMPENABLE},
1376
1377
{true, GE_CMD_NOP, "Other raster state"},
1378
{false, GE_CMD_MASKRGB},
1379
{false, GE_CMD_MASKALPHA},
1380
{false, GE_CMD_SCISSOR1},
1381
{false, GE_CMD_REGION1},
1382
{false, GE_CMD_OFFSETX},
1383
{false, GE_CMD_DITH0},
1384
{false, GE_CMD_DITH1},
1385
{false, GE_CMD_DITH2},
1386
{false, GE_CMD_DITH3},
1387
};
1388
1389
static const StateItem g_textureState[] = {
1390
{true, GE_CMD_TEXTUREMAPENABLE},
1391
{false, GE_CMD_TEXADDR0},
1392
{false, GE_CMD_TEXSIZE0},
1393
{false, GE_CMD_TEXENVCOLOR},
1394
{false, GE_CMD_TEXMAPMODE},
1395
{false, GE_CMD_TEXSHADELS},
1396
{false, GE_CMD_TEXFORMAT},
1397
{false, GE_CMD_CLUTFORMAT},
1398
{false, GE_CMD_TEXFILTER},
1399
{false, GE_CMD_TEXWRAP},
1400
{false, GE_CMD_TEXLEVEL},
1401
{false, GE_CMD_TEXFUNC},
1402
{false, GE_CMD_TEXLODSLOPE},
1403
1404
{false, GE_CMD_TEXSCALEU},
1405
{false, GE_CMD_TEXSCALEV},
1406
{false, GE_CMD_TEXOFFSETU},
1407
{false, GE_CMD_TEXOFFSETV},
1408
1409
{true, GE_CMD_NOP, "Additional mips", true},
1410
{false, GE_CMD_TEXADDR1},
1411
{false, GE_CMD_TEXADDR2},
1412
{false, GE_CMD_TEXADDR3},
1413
{false, GE_CMD_TEXADDR4},
1414
{false, GE_CMD_TEXADDR5},
1415
{false, GE_CMD_TEXADDR6},
1416
{false, GE_CMD_TEXADDR7},
1417
{false, GE_CMD_TEXSIZE1},
1418
{false, GE_CMD_TEXSIZE2},
1419
{false, GE_CMD_TEXSIZE3},
1420
{false, GE_CMD_TEXSIZE4},
1421
{false, GE_CMD_TEXSIZE5},
1422
{false, GE_CMD_TEXSIZE6},
1423
{false, GE_CMD_TEXSIZE7},
1424
};
1425
1426
static const StateItem g_lightingState[] = {
1427
{false, GE_CMD_AMBIENTCOLOR},
1428
{false, GE_CMD_AMBIENTALPHA},
1429
{false, GE_CMD_MATERIALUPDATE},
1430
{false, GE_CMD_MATERIALEMISSIVE},
1431
{false, GE_CMD_MATERIALAMBIENT},
1432
{false, GE_CMD_MATERIALDIFFUSE},
1433
{false, GE_CMD_MATERIALALPHA},
1434
{false, GE_CMD_MATERIALSPECULAR},
1435
{false, GE_CMD_MATERIALSPECULARCOEF},
1436
{false, GE_CMD_REVERSENORMAL},
1437
{false, GE_CMD_SHADEMODE},
1438
{false, GE_CMD_LIGHTMODE},
1439
{false, GE_CMD_LIGHTTYPE0},
1440
{false, GE_CMD_LIGHTTYPE1},
1441
{false, GE_CMD_LIGHTTYPE2},
1442
{false, GE_CMD_LIGHTTYPE3},
1443
{false, GE_CMD_LX0},
1444
{false, GE_CMD_LX1},
1445
{false, GE_CMD_LX2},
1446
{false, GE_CMD_LX3},
1447
{false, GE_CMD_LDX0},
1448
{false, GE_CMD_LDX1},
1449
{false, GE_CMD_LDX2},
1450
{false, GE_CMD_LDX3},
1451
{false, GE_CMD_LKA0},
1452
{false, GE_CMD_LKA1},
1453
{false, GE_CMD_LKA2},
1454
{false, GE_CMD_LKA3},
1455
{false, GE_CMD_LKS0},
1456
{false, GE_CMD_LKS1},
1457
{false, GE_CMD_LKS2},
1458
{false, GE_CMD_LKS3},
1459
{false, GE_CMD_LKO0},
1460
{false, GE_CMD_LKO1},
1461
{false, GE_CMD_LKO2},
1462
{false, GE_CMD_LKO3},
1463
{false, GE_CMD_LAC0},
1464
{false, GE_CMD_LDC0},
1465
{false, GE_CMD_LSC0},
1466
{false, GE_CMD_LAC1},
1467
{false, GE_CMD_LDC1},
1468
{false, GE_CMD_LSC1},
1469
{false, GE_CMD_LAC2},
1470
{false, GE_CMD_LDC2},
1471
{false, GE_CMD_LSC2},
1472
{false, GE_CMD_LAC3},
1473
{false, GE_CMD_LDC3},
1474
{false, GE_CMD_LSC3},
1475
};
1476
1477
static const StateItem g_vertexState[] = {
1478
{true, GE_CMD_NOP, "Vertex type and transform"},
1479
{false, GE_CMD_VERTEXTYPE},
1480
{false, GE_CMD_VADDR},
1481
{false, GE_CMD_IADDR},
1482
{false, GE_CMD_OFFSETADDR},
1483
{false, GE_CMD_VIEWPORTXSCALE},
1484
{false, GE_CMD_VIEWPORTXCENTER},
1485
{false, GE_CMD_MORPHWEIGHT0},
1486
{false, GE_CMD_MORPHWEIGHT1},
1487
{false, GE_CMD_MORPHWEIGHT2},
1488
{false, GE_CMD_MORPHWEIGHT3},
1489
{false, GE_CMD_MORPHWEIGHT4},
1490
{false, GE_CMD_MORPHWEIGHT5},
1491
{false, GE_CMD_MORPHWEIGHT6},
1492
{false, GE_CMD_MORPHWEIGHT7},
1493
{false, GE_CMD_TEXSCALEU},
1494
{false, GE_CMD_TEXSCALEV},
1495
{false, GE_CMD_TEXOFFSETU},
1496
{false, GE_CMD_TEXOFFSETV},
1497
1498
{true, GE_CMD_NOP, "Tessellation"},
1499
{false, GE_CMD_PATCHPRIMITIVE},
1500
{false, GE_CMD_PATCHDIVISION},
1501
{false, GE_CMD_PATCHCULLENABLE},
1502
{false, GE_CMD_PATCHFACING},
1503
};
1504
1505
void ImGeStateWindow::Snapshot() {
1506
// Not needed for now, we have GPUStepping::LastState()
1507
}
1508
1509
// TODO: Separate window or merge into Ge debugger?
1510
void ImGeStateWindow::Draw(ImConfig &cfg, ImControl &control, GPUDebugInterface *gpuDebug) {
1511
ImGui::SetNextWindowSize(ImVec2(300, 500), ImGuiCond_FirstUseEver);
1512
if (!ImGui::Begin("GE State", &cfg.geStateOpen)) {
1513
ImGui::End();
1514
return;
1515
}
1516
if (ImGui::BeginTabBar("GeRegs", ImGuiTabBarFlags_None)) {
1517
auto buildStateTab = [&](const char *tabName, const StateItem *rows, size_t numRows) {
1518
if (ImGui::BeginTabItem(tabName)) {
1519
if (ImGui::BeginTable("fpr", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH)) {
1520
ImGui::TableSetupColumn("State", ImGuiTableColumnFlags_WidthFixed);
1521
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch);
1522
ImGui::TableSetupColumn("bkpt", ImGuiTableColumnFlags_WidthFixed);
1523
ImGui::TableHeadersRow();
1524
1525
bool anySection = false;
1526
bool sectionOpen = false;
1527
for (size_t i = 0; i < numRows; i++) {
1528
const GECmdInfo &info = GECmdInfoByCmd(rows[i].cmd);
1529
const GPUgstate &lastState = GPUStepping::LastState();
1530
bool diff = lastState.cmdmem[rows[i].cmd] != gstate.cmdmem[rows[i].cmd];
1531
1532
if (rows[i].header) {
1533
anySection = true;
1534
if (sectionOpen) {
1535
ImGui::TreePop();
1536
}
1537
ImGui::TableNextRow();
1538
ImGui::TableNextColumn();
1539
sectionOpen = ImGui::TreeNodeEx(rows[i].cmd ? info.uiName : rows[i].title, rows[i].closedByDefault ? 0 : ImGuiTreeNodeFlags_DefaultOpen);
1540
ImGui::TableNextColumn();
1541
} else {
1542
if (!sectionOpen && anySection) {
1543
continue;
1544
}
1545
ImGui::TableNextRow();
1546
ImGui::TableNextColumn();
1547
}
1548
1549
const bool enabled = info.enableCmd == 0 || (gstate.cmdmem[info.enableCmd] & 1) == 1;
1550
if (diff) {
1551
ImGui::PushStyleColor(ImGuiCol_Text, enabled ? ImDebuggerColor_Diff : ImDebuggerColor_DiffAlpha);
1552
} else if (!enabled) {
1553
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 128));
1554
}
1555
if (!rows[i].header) {
1556
ImGui::TextUnformatted(info.uiName);
1557
ImGui::TableNextColumn();
1558
}
1559
if (rows[i].cmd != GE_CMD_NOP) {
1560
char temp[128], temp2[128];
1561
1562
const u32 value = gstate.cmdmem[info.cmd];
1563
const u32 otherValue = gstate.cmdmem[info.otherCmd];
1564
1565
// Special handling for pointer and pointer/width entries - create an address control
1566
if (info.fmt == CMD_FMT_PTRWIDTH) {
1567
const u32 val = (value & 0xFFFFFF) | (otherValue & 0x00FF0000) << 8;
1568
ImClickableValue(info.uiName, val, control, ImCmd::NONE);
1569
ImGui::SameLine();
1570
ImGui::Text("w=%d", otherValue & 0xFFFF);
1571
} else {
1572
FormatStateRow(gpuDebug, temp, sizeof(temp), info.fmt, value, true, otherValue, gstate.cmdmem[info.otherCmd2]);
1573
ImGui::TextUnformatted(temp);
1574
}
1575
if (diff && ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort)) {
1576
FormatStateRow(gpuDebug, temp2, sizeof(temp2), info.fmt, lastState.cmdmem[info.cmd], true, lastState.cmdmem[info.otherCmd], lastState.cmdmem[info.otherCmd2]);
1577
ImGui::SetTooltip("Previous: %s", temp2);
1578
}
1579
}
1580
if (diff || !enabled)
1581
ImGui::PopStyleColor();
1582
}
1583
if (sectionOpen) {
1584
ImGui::TreePop();
1585
}
1586
1587
ImGui::EndTable();
1588
}
1589
ImGui::EndTabItem();
1590
}
1591
};
1592
1593
buildStateTab("Raster", g_rasterState, ARRAY_SIZE(g_rasterState));
1594
buildStateTab("Texture", g_textureState, ARRAY_SIZE(g_textureState));
1595
buildStateTab("Lighting", g_lightingState, ARRAY_SIZE(g_lightingState));
1596
buildStateTab("Transform/Tess", g_vertexState, ARRAY_SIZE(g_vertexState));
1597
1598
if (ImGui::BeginTabItem("Matrices")) {
1599
auto visMatrix = [](const char *name, const float *data, bool is4x4) {
1600
ImGui::TextUnformatted(name);
1601
int stride = (is4x4 ? 4 : 3);
1602
if (ImGui::BeginTable(name, stride, ImGuiTableFlags_Borders, ImVec2(90.0f * stride, 0.0f))) {
1603
for (int row = 0; row < 4; ++row) {
1604
ImGui::TableNextRow();
1605
for (int col = 0; col < stride; ++col) {
1606
ImGui::TableSetColumnIndex(col);
1607
ImGui::Text("%.4f", data[row * stride + col]);
1608
}
1609
}
1610
ImGui::EndTable();
1611
}
1612
};
1613
1614
if (ImGui::CollapsingHeader("Common", ImGuiTreeNodeFlags_DefaultOpen)) {
1615
if (gstate.isModeThrough()) {
1616
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 160));
1617
ImGui::TextUnformatted("Through mode: No matrices are used");
1618
}
1619
visMatrix("World", gstate.worldMatrix, false);
1620
visMatrix("View", gstate.viewMatrix, false);
1621
visMatrix("Proj", gstate.projMatrix, true);
1622
visMatrix("Tex", gstate.tgenMatrix, false);
1623
if (gstate.isModeThrough()) {
1624
ImGui::PopStyleColor();
1625
}
1626
}
1627
1628
if (ImGui::CollapsingHeader("Bone matrices")) {
1629
for (int i = 0; i < 8; i++) {
1630
char n[16];
1631
snprintf(n, sizeof(n), "Bone %d", i);
1632
visMatrix(n, gstate.boneMatrix + 12 * i, false);
1633
}
1634
}
1635
1636
ImGui::EndTabItem();
1637
}
1638
ImGui::EndTabBar();
1639
}
1640
ImGui::End();
1641
}
1642
1643
void DrawImGeVertsWindow(ImConfig &cfg, ImControl &control, GPUDebugInterface *gpuDebug) {
1644
ImGui::SetNextWindowSize(ImVec2(300, 500), ImGuiCond_FirstUseEver);
1645
if (!ImGui::Begin("GE Vertices", &cfg.geVertsOpen)) {
1646
ImGui::End();
1647
return;
1648
}
1649
const ImGuiTableFlags tableFlags =
1650
ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_ScrollY;
1651
if (ImGui::BeginTabBar("vertexmode", ImGuiTabBarFlags_None)) {
1652
auto state = gpuDebug->GetGState();
1653
char fmtTemp[256];
1654
FormatStateRow(gpuDebug, fmtTemp, sizeof(fmtTemp), CMD_FMT_VERTEXTYPE, state.vertType, true, false, false);
1655
ImGui::TextUnformatted(fmtTemp);
1656
// Let's see if it's fast enough to just do all this each frame.
1657
int rowCount_ = gpuDebug->GetCurrentPrimCount();
1658
std::vector<GPUDebugVertex> vertices;
1659
std::vector<u16> indices;
1660
if (!gpuDebug->GetCurrentDrawAsDebugVertices(rowCount_, vertices, indices)) {
1661
rowCount_ = 0;
1662
}
1663
auto buildVertexTable = [&](bool raw) {
1664
// Ignore indices for now.
1665
if (ImGui::BeginTable("rawverts", VERTEXLIST_COL_COUNT + 1, tableFlags)) {
1666
static VertexDecoder decoder;
1667
u32 vertTypeID = GetVertTypeID(state.vertType, state.getUVGenMode(), true);
1668
VertexDecoderOptions options{};
1669
decoder.SetVertexType(vertTypeID, options);
1670
1671
static const char * const colNames[] = {
1672
"Index",
1673
"X",
1674
"Y",
1675
"Z",
1676
"U",
1677
"V",
1678
"Color",
1679
"NX",
1680
"NY",
1681
"NZ",
1682
};
1683
for (int i = 0; i < ARRAY_SIZE(colNames); i++) {
1684
ImGui::TableSetupColumn(colNames[i], ImGuiTableColumnFlags_WidthFixed, 0.0f, i);
1685
}
1686
ImGui::TableSetupScrollFreeze(0, 1); // Make header row always visible
1687
ImGui::TableHeadersRow();
1688
1689
ImGuiListClipper clipper;
1690
_dbg_assert_(rowCount_ >= 0);
1691
clipper.Begin(rowCount_);
1692
while (clipper.Step()) {
1693
for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) {
1694
int index = indices.empty() ? i : indices[i];
1695
ImGui::TableNextRow();
1696
ImGui::TableNextColumn();
1697
1698
ImGui::PushID(i);
1699
ImGui::Text("%d", index);
1700
for (int column = 0; column < VERTEXLIST_COL_COUNT; column++) {
1701
ImGui::TableNextColumn();
1702
char temp[36];
1703
if (raw) {
1704
FormatVertColRaw(&decoder, temp, sizeof(temp), index, column);
1705
} else {
1706
FormatVertCol(temp, sizeof(temp), vertices[index], column);
1707
}
1708
ImGui::TextUnformatted(temp);
1709
}
1710
ImGui::PopID();
1711
}
1712
}
1713
clipper.End();
1714
1715
ImGui::EndTable();
1716
}
1717
};
1718
1719
if (ImGui::BeginTabItem("Raw")) {
1720
buildVertexTable(true);
1721
ImGui::EndTabItem();
1722
}
1723
if (ImGui::BeginTabItem("Transformed")) {
1724
buildVertexTable(false);
1725
ImGui::EndTabItem();
1726
}
1727
// TODO: Let's not include columns for which we have no data.
1728
ImGui::EndTabBar();
1729
}
1730
ImGui::End();
1731
}
1732
1733