Merge pull request #19734 from hrydgard/pixel-viewer

ImGeDebugger: Memory-as-pixels viewer
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2024-12-15 20:09:06 +01:00
commit d08f45f4a4
5 files changed
+260 -9

No files matched your search

+2 -1
View File
@@ -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();
}
}
+14 -2
View File
@@ -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;
}
@@ -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;
@@ -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();
}
@@ -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();
}
@@ -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);
@@ -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) {
@@ -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);
+4 -1
View File
@@ -137,6 +137,7 @@ struct ImConfig {
bool geStateOpen;
bool schedulerOpen;
bool watchOpen;
bool pixelViewerOpen;
bool memViewOpen[4];
// HLE explorer settings
@@ -182,11 +183,12 @@ public:
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();
@@ -198,6 +200,7 @@ private:
ImGeStateWindow geStateWindow_;
ImMemWindow mem_[4]; // We support 4 separate instances of the memory viewer.
ImStructViewer structViewer_;
ImGePixelViewer pixelViewer_;
ImSnapshotState newSnapshot_;
ImSnapshotState snapshot_;
+207 -5
View File
@@ -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"
@@ -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();
}
@@ -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;
@@ -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());
@@ -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();
@@ -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.
}
}
@@ -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);
+33
View File
@@ -52,6 +52,39 @@ private:
u32 prevState_[256]{};
};
namespace Draw {
class Texture;
}
class ImGePixelViewer {
public:
~ImGePixelViewer();
void Draw(ImConfig &cfg, ImControl &control, GPUDebugInterface *gpuDebug, Draw::DrawContext *draw);
void Snapshot() {
dirty_ = true;
}
void Show(uint32_t address, int width, int height, int stride, GEBufferFormat format) {
addr_ = address;
width_ = width;
height_ = height;
stride_ = stride;
format_ = format;
}
private:
void UpdateTexture(Draw::DrawContext *draw);
Draw::Texture *texture_ = nullptr;
GEBufferFormat format_ = GE_FORMAT_565;
bool dirty_ = true;
bool useAlpha_ = false;
bool showAlpha_ = false;
float scale_ = 1.0f;
uint32_t addr_ = 0x04000000;
uint16_t stride_ = 512;
uint16_t width_ = 480;
uint16_t height_ = 272;
};
class ImGeDebuggerWindow {
public:
void Draw(ImConfig &cfg, ImControl &control, GPUDebugInterface *gpuDebug);