Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ImGeDebugger: Memory-as-pixels viewer #19734

Merged
merged 2 commits into from
Dec 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion UI/EmuScreen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1693,8 +1693,9 @@ void EmuScreen::runImDebugger() {

ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport(), ImGuiDockNodeFlags_PassthruCentralNode);

imDebugger_->Frame(currentDebugMIPS, gpuDebug);
imDebugger_->Frame(currentDebugMIPS, gpuDebug, draw);

// Convert to drawlists.
ImGui::Render();
}
}
Expand Down
16 changes: 14 additions & 2 deletions UI/ImDebugger/ImDebugger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ void DrawSchedulerView(ImConfig &cfg) {

static void DrawGPRs(ImConfig &config, ImControl &control, const MIPSDebugInterface *mipsDebug, const ImSnapshotState &prev) {
ImGui::SetNextWindowSize(ImVec2(320, 600), ImGuiCond_FirstUseEver);
if (!ImGui::Begin("MIPS GPRs", &config.vfpuOpen)) {
if (!ImGui::Begin("MIPS GPRs", &config.gprOpen)) {
ImGui::End();
return;
}
Expand Down Expand Up @@ -930,7 +930,7 @@ ImDebugger::~ImDebugger() {
cfg_.SaveConfig(ConfigPath());
}

void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebug) {
void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebug, Draw::DrawContext *draw) {
// Snapshot the coreState to avoid inconsistency.
const CoreState coreState = ::coreState;

Expand Down Expand Up @@ -962,6 +962,7 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu
// A GPU step has happened since last time. This means that we should re-center the cursor.
// Snapshot();
lastGpuStepCount_ = GPUStepping::GetSteppingCounter();
SnapshotGPU(gpuDebug);
geDebugger_.NotifyStep();
}

Expand Down Expand Up @@ -1060,6 +1061,7 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu
ImGui::MenuItem("Display Output", nullptr, &cfg_.displayOpen);
ImGui::MenuItem("Textures", nullptr, &cfg_.texturesOpen);
ImGui::MenuItem("Framebuffers", nullptr, &cfg_.framebuffersOpen);
ImGui::MenuItem("Pixel Viewer", nullptr, &cfg_.pixelViewerOpen);
// More to come here...
ImGui::EndMenu();
}
Expand Down Expand Up @@ -1176,6 +1178,10 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu
DrawSchedulerView(cfg_);
}

if (cfg_.pixelViewerOpen) {
pixelViewer_.Draw(cfg_, control, gpuDebug, draw);
}

for (int i = 0; i < 4; i++) {
if (cfg_.memViewOpen[i]) {
mem_[i].Draw(mipsDebug, cfg_, control, i);
Expand Down Expand Up @@ -1219,6 +1225,11 @@ void ImDebugger::Snapshot(MIPSState *mips) {
newSnapshot_.lo = mips->lo;
newSnapshot_.hi = mips->hi;
newSnapshot_.ll = mips->llBit;
pixelViewer_.Snapshot();
}

void ImDebugger::SnapshotGPU(GPUDebugInterface *gpuDebug) {
pixelViewer_.Snapshot();
}

void ImMemWindow::Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, ImControl &control, int index) {
Expand Down Expand Up @@ -1532,6 +1543,7 @@ void ImConfig::SyncConfig(IniFile *ini, bool save) {
sync.Sync("geDebuggerOpen", &geDebuggerOpen, false);
sync.Sync("geStateOpen", &geStateOpen, false);
sync.Sync("schedulerOpen", &schedulerOpen, false);
sync.Sync("pixelViewerOpen", &pixelViewerOpen, false);
for (int i = 0; i < 4; i++) {
char name[64];
snprintf(name, sizeof(name), "memory%dOpen", i + 1);
Expand Down
5 changes: 4 additions & 1 deletion UI/ImDebugger/ImDebugger.h
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ struct ImConfig {
bool geStateOpen;
bool schedulerOpen;
bool watchOpen;
bool pixelViewerOpen;
bool memViewOpen[4];

// HLE explorer settings
Expand Down Expand Up @@ -182,11 +183,12 @@ class ImDebugger {
ImDebugger();
~ImDebugger();

void Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebug);
void Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebug, Draw::DrawContext *draw);

// Should be called just before starting a step or run, so that things can
// save state that they can later compare with, to highlight changes.
void Snapshot(MIPSState *mips);
void SnapshotGPU(GPUDebugInterface *mips);

private:
Path ConfigPath();
Expand All @@ -198,6 +200,7 @@ class ImDebugger {
ImGeStateWindow geStateWindow_;
ImMemWindow mem_[4]; // We support 4 separate instances of the memory viewer.
ImStructViewer structViewer_;
ImGePixelViewer pixelViewer_;

ImSnapshotState newSnapshot_;
ImSnapshotState snapshot_;
Expand Down
212 changes: 207 additions & 5 deletions UI/ImDebugger/ImGe.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "ext/imgui/imgui.h"
#include "ext/imgui/imgui_internal.h"
#include "ext/imgui/imgui_impl_thin3d.h"
#include "Common/Data/Convert/ColorConv.h"
#include "UI/ImDebugger/ImGe.h"
#include "UI/ImDebugger/ImDebugger.h"
#include "GPU/Common/GPUDebugInterface.h"
Expand All @@ -25,7 +26,12 @@ void DrawFramebuffersWindow(ImConfig &cfg, FramebufferManagerCommon *framebuffer
return;
}

framebufferManager->DrawImGuiDebug(cfg.selectedFramebuffer);
if (framebufferManager) {
framebufferManager->DrawImGuiDebug(cfg.selectedFramebuffer);
} else {
// Although technically, we could track them...
ImGui::TextUnformatted("(Framebuffers not available in software mode)");
}

ImGui::End();
}
Expand Down Expand Up @@ -83,6 +89,202 @@ void DrawDebugStatsWindow(ImConfig &cfg) {
ImGui::End();
}

ImGePixelViewer::~ImGePixelViewer() {
texture_->Release();
}

void ImGePixelViewer::Draw(ImConfig &cfg, ImControl &control, GPUDebugInterface *gpuDebug, Draw::DrawContext *draw) {
ImGui::SetNextWindowSize(ImVec2(300, 500), ImGuiCond_FirstUseEver);
if (!ImGui::Begin("Pixel Viewer", &cfg.pixelViewerOpen)) {
ImGui::End();
return;
}

if (dirty_) {
UpdateTexture(draw);
dirty_ = false;
}

if (gpuDebug->GetFramebufferManagerCommon()) {
if (gpuDebug->GetFramebufferManagerCommon()->GetVFBAt(addr_)) {
ImGui::Text("NOTE: There's a hardware framebuffer at %08x.", addr_);
// TODO: Add a button link.
}
}

if (ImGui::BeginChild("left", ImVec2(200.0f, 0.0f))) {
if (ImGui::InputScalar("Address", ImGuiDataType_U32, &addr_, 0, 0, "%08x")) {
dirty_ = true;
}

if (ImGui::BeginCombo("Aspect", GeBufferFormatToString(format_))) {
for (int i = 0; i < 5; i++) {
if (ImGui::Selectable(GeBufferFormatToString((GEBufferFormat)i), i == (int)format_)) {
format_ = (GEBufferFormat)i;
dirty_ = true;
}
}
ImGui::EndCombo();
}

bool alphaPresent = format_ == GE_FORMAT_8888 || format_ == GE_FORMAT_4444 || format_ == GE_FORMAT_5551;

if (!alphaPresent) {
ImGui::BeginDisabled();
}
if (ImGui::Checkbox("Use alpha", &useAlpha_)) {
dirty_ = true;
}
if (ImGui::Checkbox("Show alpha", &showAlpha_)) {
dirty_ = true;
}
if (!alphaPresent) {
ImGui::EndDisabled();
}
if (ImGui::InputScalar("Width", ImGuiDataType_U16, &width_)) {
dirty_ = true;
}
if (ImGui::InputScalar("Height", ImGuiDataType_U16, &height_)) {
dirty_ = true;
}
if (ImGui::InputScalar("Stride", ImGuiDataType_U16, &stride_)) {
dirty_ = true;
}
if (format_ == GE_FORMAT_DEPTH16) {
if (ImGui::SliderFloat("Scale", &scale_, 0.5f, 256.0f, "%.2f", ImGuiSliderFlags_Logarithmic)) {
dirty_ = true;
}
}
if (ImGui::Button("Refresh")) {
dirty_ = true;
}
}
ImGui::EndChild();

ImGui::SameLine();
if (ImGui::BeginChild("right")) {
if (Memory::IsValid4AlignedAddress(addr_)) {
if (texture_) {
ImTextureID texId = ImGui_ImplThin3d_AddTextureTemp(texture_, useAlpha_ ? ImGuiPipeline::TexturedAlphaBlend : ImGuiPipeline::TexturedOpaque);
ImGui::Image(texId, ImVec2((float)width_, (float)height_));
} else {
ImGui::Text("(invalid params: %dx%d, %08x)", width_, height_, addr_);
}
} else {
ImGui::Text("(invalid address %08x)", addr_);
}
}
ImGui::EndChild();
ImGui::End();
}

void ImGePixelViewer::UpdateTexture(Draw::DrawContext *draw) {
if (texture_) {
texture_->Release();
texture_ = nullptr;
}
if (!Memory::IsValid4AlignedAddress(addr_) || width_ == 0 || height_ == 0 || stride_ > 1024 || stride_ == 0) {
INFO_LOG(Log::GeDebugger, "PixelViewer: Bad texture params");
return;
}

int bpp = BufferFormatBytesPerPixel(format_);

int srcBytes = width_ * stride_ * bpp;
if (stride_ > width_)
srcBytes -= stride_ - width_;
if (Memory::ValidSize(addr_, srcBytes) != srcBytes) {
// TODO: Show a message that the address is out of bounds.
return;
}

// Read pixels into a buffer and transform them accordingly.
// For now we convert all formats to RGBA here, for backend compatibility.
uint8_t *buf = new uint8_t[width_ * height_ * 4];

for (int y = 0; y < height_; y++) {
u32 rowAddr = addr_ + y * stride_ * bpp;
const u8 *src = Memory::GetPointerUnchecked(rowAddr);
u8 *dst = buf + y * width_ * 4;
switch (format_) {
case GE_FORMAT_8888:
if (showAlpha_) {
for (int x = 0; x < width_; x++) {
dst[0] = src[3];
dst[1] = src[3];
dst[2] = src[3];
dst[3] = 0xFF;
src += 4;
dst += 4;
}
} else {
memcpy(dst, src, width_ * 4);
}
break;
case GE_FORMAT_565:
// No showAlpha needed (would just be white)
ConvertRGB565ToRGBA8888((u32 *)dst, (const u16 *)src, width_);
break;
case GE_FORMAT_5551:
if (showAlpha_) {
uint32_t *dst32 = (uint32_t *)dst;
uint16_t *src16 = (uint16_t *)dst;
for (int x = 0; x < width_; x++) {
dst[x] = (src16[x] >> 15) ? 0xFFFFFFFF : 0xFF000000;
}
} else {
ConvertRGBA5551ToRGBA8888((u32 *)dst, (const u16 *)src, width_);
}
break;
case GE_FORMAT_4444:
ConvertRGBA4444ToRGBA8888((u32 *)dst, (const u16 *)src, width_);
break;
case GE_FORMAT_DEPTH16:
{
uint16_t *src16 = (uint16_t *)src;
float scale = scale_ / 256.0f;
for (int x = 0; x < width_; x++) {
// Just pick off the upper bits by adding 1 to the byte address
// We don't visualize the lower bits for now, although we could - should add a scale slider like RenderDoc.
float fval = (float)src16[x] * scale;
u8 val;
if (fval < 0.0f) {
val = 0;
} else if (fval >= 255.0f) {
val = 255;
} else {
val = (u8)fval;
}
dst[0] = val;
dst[1] = val;
dst[2] = val;
dst[3] = 0xFF;
dst += 4;
}
break;
}
default:
memset(buf, 0x80, width_ * height_ * 4);
break;
}
}

Draw::TextureDesc desc{ Draw::TextureType::LINEAR2D,
Draw::DataFormat::R8G8B8A8_UNORM,
(int)width_,
(int)height_,
1,
1,
false,
Draw::TextureSwizzle::DEFAULT,
"PixelViewer temp",
{ buf },
nullptr,
};

texture_ = draw->CreateTexture(desc);
}

void ImGeDisasmView::NotifyStep() {
if (followPC_) {
gotoPC_ = true;
Expand Down Expand Up @@ -486,8 +688,7 @@ void ImGeDebuggerWindow::Draw(ImConfig &cfg, ImControl &control, GPUDebugInterfa
if (coreState == CORE_STEPPING_GE) {
FramebufferManagerCommon *fbman = gpuDebug->GetFramebufferManagerCommon();
u32 fbptr = gstate.getFrameBufAddress();
VirtualFramebuffer *vfb = fbman->GetVFBAt(fbptr);

VirtualFramebuffer *vfb = fbman ? fbman->GetVFBAt(fbptr) : nullptr;
if (vfb) {
if (vfb->fbo) {
ImGui::Text("Framebuffer: %s", vfb->fbo->Tag());
Expand Down Expand Up @@ -537,7 +738,7 @@ void ImGeDebuggerWindow::Draw(ImConfig &cfg, ImControl &control, GPUDebugInterfa
ImGui::Text("(texturing not enabled");
} else {
TextureCacheCommon *texcache = gpuDebug->GetTextureCacheCommon();
TexCacheEntry *tex = texcache->SetTexture();
TexCacheEntry *tex = texcache ? texcache->SetTexture() : nullptr;
if (tex) {
ImGui::Text("Texture: ");
texcache->ApplyTexture();
Expand All @@ -561,6 +762,7 @@ void ImGeDebuggerWindow::Draw(ImConfig &cfg, ImControl &control, GPUDebugInterfa
drawList->PopClipRect();
} else {
ImGui::Text("(no valid texture bound)");
// In software mode, we should just decode the texture here.
// TODO: List some of the texture params here.
}
}
Expand Down Expand Up @@ -813,7 +1015,7 @@ void ImGeStateWindow::Draw(ImConfig &cfg, ImControl &control, GPUDebugInterface

// Special handling for pointer and pointer/width entries - create an address control
if (info.fmt == CMD_FMT_PTRWIDTH) {
const u32 val = value | (otherValue & 0x00FF0000) << 8;
const u32 val = (value & 0xFFFFFF) | (otherValue & 0x00FF0000) << 8;
ImClickableAddress(val, control, ImCmd::NONE);
ImGui::SameLine();
ImGui::Text("w=%d", otherValue & 0xFFFF);
Expand Down
Loading
Loading