diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index 687bf3256b05..f0c6b015cbe3 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -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(); } } diff --git a/UI/ImDebugger/ImDebugger.cpp b/UI/ImDebugger/ImDebugger.cpp index 2953a66a6c3c..2285bc3a639a 100644 --- a/UI/ImDebugger/ImDebugger.cpp +++ b/UI/ImDebugger/ImDebugger.cpp @@ -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); diff --git a/UI/ImDebugger/ImDebugger.h b/UI/ImDebugger/ImDebugger.h index 8b2e16739911..5a70c073a17c 100644 --- a/UI/ImDebugger/ImDebugger.h +++ b/UI/ImDebugger/ImDebugger.h @@ -137,6 +137,7 @@ struct ImConfig { bool geStateOpen; bool schedulerOpen; bool watchOpen; + bool pixelViewerOpen; bool memViewOpen[4]; // HLE explorer settings @@ -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(); @@ -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_; diff --git a/UI/ImDebugger/ImGe.cpp b/UI/ImDebugger/ImGe.cpp index ae8193a727a2..d0fa09d3d589 100644 --- a/UI/ImDebugger/ImGe.cpp +++ b/UI/ImDebugger/ImGe.cpp @@ -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); diff --git a/UI/ImDebugger/ImGe.h b/UI/ImDebugger/ImGe.h index 3f11702c37e9..696e64163255 100644 --- a/UI/ImDebugger/ImGe.h +++ b/UI/ImDebugger/ImGe.h @@ -52,6 +52,39 @@ class ImGeStateWindow { 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);