CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/Windows/GEDebugger/GEDebugger.cpp
Views: 1401
// Copyright (c) 2012- PPSSPP Project.12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official git repository and contact information can be found at15// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.1617#include <cmath>18#include <functional>19#include <set>20#include <string>21#include <vector>2223#include "Common/CommonWindows.h"24#include <commctrl.h>2526#include "Common/Data/Convert/ColorConv.h"27#include "Common/Data/Encoding/Utf8.h"28#include "Common/Data/Text/Parsers.h"29#include "Common/StringUtils.h"30#include "Common/System/System.h"31#include "Common/System/Request.h"3233#include "Core/Config.h"34#include "Core/Screenshot.h"3536#include "Windows/GEDebugger/GEDebugger.h"37#include "Windows/GEDebugger/SimpleGLWindow.h"38#include "Windows/GEDebugger/CtrlDisplayListView.h"39#include "Windows/GEDebugger/TabDisplayLists.h"40#include "Windows/GEDebugger/TabState.h"41#include "Windows/GEDebugger/TabVertices.h"42#include "Windows/W32Util/ContextMenu.h"43#include "Windows/W32Util/ShellUtil.h"44#include "Windows/InputBox.h"45#include "Windows/MainWindow.h"46#include "Windows/main.h"4748#include "GPU/GPUInterface.h"49#include "GPU/Common/GPUDebugInterface.h"50#include "GPU/Common/GPUStateUtils.h"51#include "GPU/GPUState.h"52#include "GPU/Debugger/Breakpoints.h"53#include "GPU/Debugger/Debugger.h"54#include "GPU/Debugger/Record.h"55#include "GPU/Debugger/Stepping.h"5657using namespace GPUBreakpoints;58using namespace GPUDebug;59using namespace GPUStepping;6061enum PrimaryDisplayType {62PRIMARY_FRAMEBUF,63PRIMARY_DEPTHBUF,64PRIMARY_STENCILBUF,65};6667enum class GEPanelIndex {68LEFT,69RIGHT,70TOPRIGHT,71COUNT,72};7374static void *AddDisplayListTab(GEDebuggerTab *tab, TabControl *tabs, GETabPosition pos, HINSTANCE inst, HWND parent) {75HWND wnd = tabs->AddTabWindow(L"CtrlDisplayListView", tab->name);76return CtrlDisplayListView::getFrom(wnd);77}7879static void RemoveDisplayListTab(GEDebuggerTab *tab, TabControl *tabs, GETabPosition pos, void *ptr) {80CtrlDisplayListView *view = (CtrlDisplayListView *)ptr;81DestroyWindow(view->GetHWND());82}8384static void UpdateDisplayListTab(GEDebuggerTab *tab, TabControl *tabs, GETabPosition pos, void *ptr) {85CtrlDisplayListView *view = (CtrlDisplayListView *)ptr;86DisplayList list;87if (gpuDebug != nullptr && gpuDebug->GetCurrentDisplayList(list)) {88view->setDisplayList(list);89} else {90view->clearDisplayList();91}92}9394template <typename T>95static void *AddStateTab(GEDebuggerTab *tab, TabControl *tabs, GETabPosition pos, HINSTANCE inst, HWND parent) {96T *w = new T(inst, parent);97tabs->AddTabDialog(w, tab->name);98return w;99}100101static void RemoveStateTab(GEDebuggerTab *tab, TabControl *tabs, GETabPosition pos, void *ptr) {102Dialog *view = (Dialog *)ptr;103delete view;104}105106static void UpdateStateTab(GEDebuggerTab *tab, TabControl *tabs, GETabPosition pos, void *ptr) {107Dialog *view = (Dialog *)ptr;108view->Update();109}110111static const std::vector<GEDebuggerTab> defaultTabs = {112{ L"Display List", GETabPosition::LEFT, GETabType::LIST_DISASM, {}, &AddDisplayListTab, &RemoveDisplayListTab, &UpdateDisplayListTab },113{ L"Flags", GETabPosition::LEFT, GETabType::STATE, {}, &AddStateTab<TabStateFlags>, &RemoveStateTab, &UpdateStateTab },114{ L"Light", GETabPosition::LEFT, GETabType::STATE, {}, &AddStateTab<TabStateLighting>, &RemoveStateTab, &UpdateStateTab },115{ L"Texture", GETabPosition::LEFT, GETabType::STATE, {}, &AddStateTab<TabStateTexture>, &RemoveStateTab, &UpdateStateTab },116{ L"Settings", GETabPosition::LEFT, GETabType::STATE, {}, &AddStateTab<TabStateSettings>, &RemoveStateTab, &UpdateStateTab },117{ L"Verts", GETabPosition::LEFT, GETabType::STATE, {}, &AddStateTab<TabVertices>, &RemoveStateTab, &UpdateStateTab },118{ L"Matrices", GETabPosition::LEFT, GETabType::STATE, {}, &AddStateTab<TabMatrices>, &RemoveStateTab, &UpdateStateTab },119{ L"Lists", GETabPosition::LEFT, GETabType::LISTS, {}, &AddStateTab<TabDisplayLists>, &RemoveStateTab, &UpdateStateTab },120{ L"Watch", GETabPosition::LEFT, GETabType::WATCH, {}, &AddStateTab<TabStateWatch>, &RemoveStateTab, &UpdateStateTab },121};122123StepCountDlg::StepCountDlg(HINSTANCE _hInstance, HWND _hParent) : Dialog((LPCSTR)IDD_GEDBG_STEPCOUNT, _hInstance, _hParent) {124DialogManager::AddDlg(this);125126for (int i = 0; i < 4; i++) // Add items 1, 10, 100, 1000127SendMessageA(GetDlgItem(m_hDlg, IDC_GEDBG_STEPCOUNT_COMBO), CB_ADDSTRING, 0, (LPARAM)std::to_string((int)pow(10, i)).c_str());128SetWindowTextA(GetDlgItem(m_hDlg, IDC_GEDBG_STEPCOUNT_COMBO), "1");129}130131StepCountDlg::~StepCountDlg() {132DialogManager::RemoveDlg(this);133}134135void StepCountDlg::Jump(int count, bool relative) {136if (relative && count == 0)137return;138SetBreakNext(BreakNext::COUNT);139SetBreakCount(count, relative);140};141142BOOL StepCountDlg::DlgProc(UINT message, WPARAM wParam, LPARAM lParam) {143int count;144bool relative;145auto GetValue = [&]() {146char str[7]; // +/-99999\0147GetWindowTextA(GetDlgItem(m_hDlg, IDC_GEDBG_STEPCOUNT_COMBO), str, 7);148relative = str[0] == '+' || str[0] == '-';149return TryParse(str, &count);150};151152switch (message) {153case WM_CLOSE:154Show(false);155return TRUE;156case WM_COMMAND:157switch (wParam) {158case IDC_GEDBG_STEPCOUNT_DEC:159if (GetValue())160Jump(-abs(count), true);161return TRUE;162case IDC_GEDBG_STEPCOUNT_INC:163if (GetValue())164Jump(abs(count), true);165return TRUE;166case IDC_GEDBG_STEPCOUNT_JUMP:167if (GetValue())168Jump(abs(count), false);169return TRUE;170case IDOK:171if (GetValue())172Jump(count, relative);173Show(false);174return TRUE;175case IDCANCEL:176SetFocus(m_hParent);177Show(false);178return TRUE;179}180break;181}182return FALSE;183}184185void CGEDebugger::Init() {186SimpleGLWindow::RegisterClass();187CtrlDisplayListView::registerClass();188}189190CGEDebugger::CGEDebugger(HINSTANCE _hInstance, HWND _hParent)191: Dialog((LPCSTR)IDD_GEDEBUGGER, _hInstance, _hParent)192, stepCountDlg(_hInstance, m_hDlg) {193SetMenu(m_hDlg, LoadMenu(_hInstance, MAKEINTRESOURCE(IDR_GEDBG_MENU)));194195// minimum size = a little more than the default196RECT windowRect;197GetWindowRect(m_hDlg, &windowRect);198minWidth_ = windowRect.right-windowRect.left + 10;199minHeight_ = windowRect.bottom-windowRect.top + 10;200201// it's ugly, but .rc coordinates don't match actual pixels and it screws202// up both the size and the aspect ratio203RECT frameRect;204HWND frameWnd = GetDlgItem(m_hDlg,IDC_GEDBG_FRAME);205GetWindowRect(frameWnd,&frameRect);206MapWindowPoints(HWND_DESKTOP,m_hDlg,(LPPOINT)&frameRect,2);207MoveWindow(frameWnd,frameRect.left,frameRect.top,512,272,TRUE);208209tabs = new TabControl(GetDlgItem(m_hDlg, IDC_GEDBG_MAINTAB));210tabsRight_ = new TabControl(GetDlgItem(m_hDlg, IDC_GEDBG_RIGHTTAB));211tabsTR_ = new TabControl(GetDlgItem(m_hDlg, IDC_GEDBG_TOPRIGHTTAB));212213fbTabs = new TabControl(GetDlgItem(m_hDlg, IDC_GEDBG_FBTABS));214fbTabs->SetMinTabWidth(50);215// Must be in the same order as PrimaryDisplayType.216fbTabs->AddTab(NULL, L"Color");217fbTabs->AddTab(NULL, L"Depth");218fbTabs->AddTab(NULL, L"Stencil");219fbTabs->ShowTab(0, true);220221tabStates_ = defaultTabs;222// Restore settings, if any set.223_assert_msg_(defaultTabs.size() <= 32, "Cannot have more than 32 tabs");224if ((g_Config.uGETabsLeft | g_Config.uGETabsRight | g_Config.uGETabsTopRight) != 0) {225for (int i = 0; i < (int)tabStates_.size(); ++i) {226int mask = 1 << i;227tabStates_[i].pos = (GETabPosition)0;228if (g_Config.uGETabsLeft & mask)229tabStates_[i].pos |= GETabPosition::LEFT;230if (g_Config.uGETabsRight & mask)231tabStates_[i].pos |= GETabPosition::RIGHT;232if (g_Config.uGETabsTopRight & mask)233tabStates_[i].pos |= GETabPosition::TOPRIGHT;234// If this is a new tab, add it to left.235if (tabStates_[i].pos == (GETabPosition)0) {236tabStates_[i].pos |= GETabPosition::LEFT;237g_Config.uGETabsLeft |= 1 << i;238}239}240} else {241g_Config.uGETabsLeft = (1 << tabStates_.size()) - 1;242}243for (GEDebuggerTab &tabState : tabStates_) {244AddTab(&tabState, tabState.pos);245}246247if (tabs->Count() > 0)248tabs->ShowTab(0, true);249if (tabsRight_->Count() > 0)250tabsRight_->ShowTab(0, true);251if (tabsTR_->Count() > 0)252tabsTR_->ShowTab(0, true);253254// set window position255int x = g_Config.iGEWindowX == -1 ? windowRect.left : g_Config.iGEWindowX;256int y = g_Config.iGEWindowY == -1 ? windowRect.top : g_Config.iGEWindowY;257int w = g_Config.iGEWindowW == -1 ? minWidth_ : std::max(minWidth_, g_Config.iGEWindowW);258int h = g_Config.iGEWindowH == -1 ? minHeight_ : std::max(minHeight_, g_Config.iGEWindowH);259MoveWindow(m_hDlg,x,y,w,h,FALSE);260261SetTimer(m_hDlg, 1, USER_TIMER_MINIMUM, nullptr);262263UpdateTextureLevel(textureLevel_);264}265266CGEDebugger::~CGEDebugger() {267CleanupPrimPreview();268269for (GEDebuggerTab &tabState : tabStates_) {270RemoveTab(&tabState, GETabPosition::ALL);271}272273delete tabs;274delete tabsRight_;275delete tabsTR_;276delete fbTabs;277}278279void CGEDebugger::SetupPreviews() {280if (primaryWindow == nullptr) {281primaryWindow = SimpleGLWindow::GetFrom(GetDlgItem(m_hDlg, IDC_GEDBG_FRAME));282primaryWindow->Initialize(SimpleGLWindow::ALPHA_IGNORE | SimpleGLWindow::RESIZE_SHRINK_CENTER);283primaryWindow->SetHoverCallback([&] (int x, int y) {284PrimaryPreviewHover(x, y);285});286primaryWindow->SetRightClickMenu(ContextMenuID::GEDBG_PREVIEW, [&] (int cmd, int x, int y) {287HMENU subMenu = GetContextMenu(ContextMenuID::GEDBG_PREVIEW);288switch (cmd) {289case 0:290// Setup.291CheckMenuItem(subMenu, ID_GEDBG_ENABLE_PREVIEW, MF_BYCOMMAND | ((previewsEnabled_ & 1) ? MF_CHECKED : MF_UNCHECKED));292EnableMenuItem(subMenu, ID_GEDBG_TRACK_PIXEL_STOP, primaryTrackX_ == 0xFFFFFFFF ? MF_GRAYED : MF_ENABLED);293break;294case ID_GEDBG_EXPORT_IMAGE:295if (primaryBuffer_)296PreviewExport(primaryBuffer_);297break;298case ID_GEDBG_COPY_IMAGE:299if (primaryBuffer_)300PreviewToClipboard(primaryBuffer_, false);301break;302case ID_GEDBG_COPY_IMAGE_ALPHA:303if (primaryBuffer_)304PreviewToClipboard(primaryBuffer_, true);305break;306case ID_GEDBG_TRACK_PIXEL:307primaryTrackX_ = x;308primaryTrackY_ = y;309break;310case ID_GEDBG_TRACK_PIXEL_STOP:311primaryTrackX_ = 0xFFFFFFFF;312primaryTrackY_ = 0xFFFFFFFF;313break;314case ID_GEDBG_ENABLE_PREVIEW:315previewsEnabled_ ^= 1;316primaryWindow->Redraw();317default:318break;319}320321return true;322});323primaryWindow->SetRedrawCallback([&] {324HandleRedraw(1);325});326primaryWindow->Clear();327}328if (secondWindow == nullptr) {329secondWindow = SimpleGLWindow::GetFrom(GetDlgItem(m_hDlg, IDC_GEDBG_TEX));330secondWindow->Initialize(SimpleGLWindow::ALPHA_BLEND | SimpleGLWindow::RESIZE_SHRINK_CENTER);331secondWindow->SetHoverCallback([&] (int x, int y) {332SecondPreviewHover(x, y);333});334secondWindow->SetRightClickMenu(ContextMenuID::GEDBG_PREVIEW, [&] (int cmd, int x, int y) {335HMENU subMenu = GetContextMenu(ContextMenuID::GEDBG_PREVIEW);336switch (cmd) {337case 0:338// Setup.339CheckMenuItem(subMenu, ID_GEDBG_ENABLE_PREVIEW, MF_BYCOMMAND | ((previewsEnabled_ & 2) ? MF_CHECKED : MF_UNCHECKED));340EnableMenuItem(subMenu, ID_GEDBG_TRACK_PIXEL_STOP, secondTrackX_ == 0xFFFFFFFF ? MF_GRAYED : MF_ENABLED);341break;342case ID_GEDBG_EXPORT_IMAGE:343if (secondBuffer_)344PreviewExport(secondBuffer_);345break;346case ID_GEDBG_COPY_IMAGE:347if (secondBuffer_)348PreviewToClipboard(secondBuffer_, false);349break;350case ID_GEDBG_COPY_IMAGE_ALPHA:351if (secondBuffer_)352PreviewToClipboard(secondBuffer_, true);353break;354case ID_GEDBG_TRACK_PIXEL:355secondTrackX_ = x;356secondTrackY_ = y;357break;358case ID_GEDBG_TRACK_PIXEL_STOP:359secondTrackX_ = 0xFFFFFFFF;360secondTrackY_ = 0xFFFFFFFF;361break;362case ID_GEDBG_ENABLE_PREVIEW:363previewsEnabled_ ^= 2;364secondWindow->Redraw();365default:366break;367}368369return true;370});371secondWindow->SetRedrawCallback([&] {372HandleRedraw(2);373});374secondWindow->Clear();375}376}377378void CGEDebugger::DescribePrimaryPreview(const GPUgstate &state, char desc[256]) {379if (primaryTrackX_ < primaryBuffer_->GetStride() && primaryTrackY_ < primaryBuffer_->GetHeight()) {380u32 pix = primaryBuffer_->GetRawPixel(primaryTrackX_, primaryTrackY_);381DescribePixel(pix, primaryBuffer_->GetFormat(), primaryTrackX_, primaryTrackY_, desc);382return;383}384385if (showClut_) {386// In this case, we're showing the texture here.387if (primaryIsFramebuffer_)388snprintf(desc, 256, "FB Tex L%d: 0x%08x (%dx%d)", textureLevel_, state.getTextureAddress(textureLevel_), state.getTextureWidth(textureLevel_), state.getTextureHeight(textureLevel_));389else390snprintf(desc, 256, "Texture L%d: 0x%08x (%dx%d)", textureLevel_, state.getTextureAddress(textureLevel_), state.getTextureWidth(textureLevel_), state.getTextureHeight(textureLevel_));391return;392}393394_assert_msg_(primaryBuffer_ != nullptr, "Must have a valid primaryBuffer_");395396switch (PrimaryDisplayType(fbTabs->CurrentTabIndex())) {397case PRIMARY_FRAMEBUF:398snprintf(desc, 256, "Color: 0x%08x (%dx%d) fmt %s", state.getFrameBufRawAddress(), primaryBuffer_->GetStride(), primaryBuffer_->GetHeight(), GeBufferFormatToString(state.FrameBufFormat()));399break;400401case PRIMARY_DEPTHBUF:402snprintf(desc, 256, "Depth: 0x%08x (%dx%d)", state.getDepthBufRawAddress(), primaryBuffer_->GetStride(), primaryBuffer_->GetHeight());403break;404405case PRIMARY_STENCILBUF:406snprintf(desc, 256, "Stencil: 0x%08x (%dx%d)", state.getFrameBufRawAddress(), primaryBuffer_->GetStride(), primaryBuffer_->GetHeight());407break;408}409}410411void CGEDebugger::DescribeSecondPreview(const GPUgstate &state, char desc[256]) {412if (secondTrackX_ != 0xFFFFFFFF) {413uint32_t x = secondTrackX_;414uint32_t y = secondTrackY_;415if (showClut_) {416uint32_t clutWidth = secondBuffer_->GetStride() / 16;417x = y * clutWidth + x;418y = 0;419}420421if (x < secondBuffer_->GetStride() && y < secondBuffer_->GetHeight()) {422u32 pix = secondBuffer_->GetRawPixel(x, y);423DescribePixel(pix, secondBuffer_->GetFormat(), x, y, desc);424return;425}426}427428if (showClut_) {429snprintf(desc, 256, "CLUT: 0x%08x (%d)", state.getClutAddress(), state.getClutPaletteFormat());430} else if (secondIsFramebuffer_) {431snprintf(desc, 256, "FB Tex L%d: 0x%08x (%dx%d)", textureLevel_, state.getTextureAddress(textureLevel_), state.getTextureWidth(textureLevel_), state.getTextureHeight(textureLevel_));432} else {433snprintf(desc, 256, "Texture L%d: 0x%08x (%dx%d)", textureLevel_, state.getTextureAddress(textureLevel_), state.getTextureWidth(textureLevel_), state.getTextureHeight(textureLevel_));434}435}436437void CGEDebugger::PreviewExport(const GPUDebugBuffer *dbgBuffer) {438const TCHAR *filter = L"PNG Image (*.png)\0*.png\0JPEG Image (*.jpg)\0*.jpg\0All files\0*.*\0\0";439std::string fn;440if (W32Util::BrowseForFileName(false, GetDlgHandle(), L"Save Preview Image...", nullptr, filter, L"png", fn)) {441ScreenshotFormat fmt = fn.find(".jpg") != fn.npos ? ScreenshotFormat::JPG : ScreenshotFormat::PNG;442443Path filename(fn);444bool saveAlpha = fmt == ScreenshotFormat::PNG;445446u8 *flipbuffer = nullptr;447u32 w = (u32)-1;448u32 h = (u32)-1;449const u8 *buffer = ConvertBufferToScreenshot(*dbgBuffer, saveAlpha, flipbuffer, w, h);450if (buffer != nullptr) {451if (saveAlpha) {452Save8888RGBAScreenshot(filename, buffer, w, h);453} else {454Save888RGBScreenshot(filename, fmt, buffer, w, h);455}456}457delete [] flipbuffer;458}459}460461void CGEDebugger::PreviewToClipboard(const GPUDebugBuffer *dbgBuffer, bool saveAlpha) {462if (!OpenClipboard(GetDlgHandle())) {463return;464}465EmptyClipboard();466467uint8_t *flipbuffer = nullptr;468uint32_t w = (uint32_t)-1;469uint32_t h = (uint32_t)-1;470const uint8_t *buffer = ConvertBufferToScreenshot(*dbgBuffer, saveAlpha, flipbuffer, w, h);471if (buffer == nullptr) {472delete [] flipbuffer;473CloseClipboard();474return;475}476477uint32_t pixelSize = saveAlpha ? 4 : 3;478uint32_t byteStride = pixelSize * w;479while ((byteStride & 3) != 0)480++byteStride;481482// Various apps don't support alpha well, so also copy as PNG.483std::vector<uint8_t> png;484if (saveAlpha) {485// Overallocate if we can.486png.resize(byteStride * h);487Save8888RGBAScreenshot(png, buffer, w, h);488489W32Util::ClipboardData png1("PNG", png.size());490W32Util::ClipboardData png2("image/png", png.size());491if (!png.empty() && png1 && png2) {492memcpy(png1.data, png.data(), png.size());493memcpy(png2.data, png.data(), png.size());494png1.Set();495png2.Set();496}497}498499W32Util::ClipboardData bitmap(CF_DIBV5, sizeof(BITMAPV5HEADER) + byteStride * h);500if (!bitmap) {501delete [] flipbuffer;502CloseClipboard();503return;504}505506BITMAPV5HEADER *header = (BITMAPV5HEADER *)bitmap.data;507header->bV5Size = sizeof(BITMAPV5HEADER);508header->bV5Width = w;509header->bV5Height = h;510header->bV5Planes = 1;511header->bV5BitCount = saveAlpha ? 32 : 24;512header->bV5Compression = saveAlpha ? BI_BITFIELDS : BI_RGB;513header->bV5SizeImage = byteStride * h;514header->bV5CSType = LCS_WINDOWS_COLOR_SPACE;515header->bV5Intent = LCS_GM_GRAPHICS;516517if (saveAlpha) {518header->bV5RedMask = 0x000000FF;519header->bV5GreenMask = 0x0000FF00;520header->bV5BlueMask = 0x00FF0000;521// Only some applications respect the alpha mask...522header->bV5AlphaMask = 0xFF000000;523}524525uint8_t *pixels = (uint8_t *)(header + 1);526for (uint32_t y = 0; y < h; ++y) {527const uint8_t *src = buffer + y * pixelSize * w;528uint8_t *dst = pixels + (h - y - 1) * byteStride;529530if (saveAlpha) {531// No RB swap needed.532memcpy(dst, src, pixelSize * w);533continue;534}535536for (uint32_t x = 0; x < w; ++x) {537// Have to swap B/R again for the bitmap, unfortunate.538dst[0] = src[2];539dst[1] = src[1];540dst[2] = src[0];541src += pixelSize;542dst += pixelSize;543}544}545546delete [] flipbuffer;547548bitmap.Set();549CloseClipboard();550}551552void CGEDebugger::UpdatePreviews() {553auto memLock = Memory::Lock();554if (!PSP_IsInited()) {555return;556}557558GPUgstate state{};559560if (gpuDebug != nullptr) {561state = gpuDebug->GetGState();562}563564updating_ = true;565if (autoFlush_)566GPU_FlushDrawing();567UpdateTextureLevel(textureLevel_);568UpdatePrimaryPreview(state);569UpdateSecondPreview(state);570571u32 primOp = PrimPreviewOp();572if (primOp != 0) {573UpdatePrimPreview(primOp, 3);574}575576wchar_t primCounter[1024]{};577swprintf(primCounter, ARRAY_SIZE(primCounter), L"%d/%d", PrimsThisFrame(), PrimsLastFrame());578SetDlgItemText(m_hDlg, IDC_GEDBG_PRIMCOUNTER, primCounter);579580for (GEDebuggerTab &tabState : tabStates_) {581UpdateTab(&tabState);582}583584updating_ = false;585}586587void CGEDebugger::UpdateTab(GEDebuggerTab *tab) {588auto doUpdate = [&](GETabPosition pos, TabControl *t, GEPanelIndex index) {589if (tab->pos & pos)590tab->update(tab, t, pos, tab->state[(int)index].ptr);591};592593doUpdate(GETabPosition::LEFT, tabs, GEPanelIndex::LEFT);594doUpdate(GETabPosition::RIGHT, tabsRight_, GEPanelIndex::RIGHT);595doUpdate(GETabPosition::TOPRIGHT, tabsTR_, GEPanelIndex::TOPRIGHT);596}597598void CGEDebugger::AddTab(GEDebuggerTab *tab, GETabPosition mask) {599auto doAdd = [&](GETabPosition pos, TabControl *t, GEPanelIndex pindex) {600int index = (int)pindex;601// On init, we still have nullptr, but already have pos, so we use that.602if ((mask & pos) && tab->state[index].ptr == nullptr) {603tab->state[index].index = t->Count();604tab->state[index].ptr = tab->add(tab, t, pos, m_hInstance, m_hDlg);605tab->pos |= pos;606t->ShowTab(tab->state[index].index, true);607if (gpuDebug)608tab->update(tab, t, pos, tab->state[index].ptr);609}610};611612doAdd(GETabPosition::LEFT, tabs, GEPanelIndex::LEFT);613doAdd(GETabPosition::RIGHT, tabsRight_, GEPanelIndex::RIGHT);614doAdd(GETabPosition::TOPRIGHT, tabsTR_, GEPanelIndex::TOPRIGHT);615}616617void CGEDebugger::RemoveTab(GEDebuggerTab *tab, GETabPosition mask) {618auto doRemove = [&](GETabPosition pos, TabControl *t, GEPanelIndex pindex) {619int index = (int)pindex;620if ((tab->pos & pos) && (mask & pos)) {621auto &state = tab->state[index];622_assert_(state.ptr != nullptr);623t->RemoveTab(state.index);624for (auto &tabState : tabStates_) {625if (tabState.state[index].index > state.index)626--tabState.state[index].index;627}628629tab->remove(tab, t, pos, state.ptr);630tab->pos = GETabPosition((int)tab->pos & ~(int)pos);631state.ptr = nullptr;632state.index = -1;633}634};635636doRemove(GETabPosition::LEFT, tabs, GEPanelIndex::LEFT);637doRemove(GETabPosition::RIGHT, tabsRight_, GEPanelIndex::RIGHT);638doRemove(GETabPosition::TOPRIGHT, tabsTR_, GEPanelIndex::TOPRIGHT);639}640641int CGEDebugger::HasTabIndex(GEDebuggerTab *tab, GETabPosition pos) {642int stateIndex = 0;643switch (pos) {644case GETabPosition::LEFT: stateIndex = (int)GEPanelIndex::LEFT; break;645case GETabPosition::RIGHT: stateIndex = (int)GEPanelIndex::RIGHT; break;646case GETabPosition::TOPRIGHT: stateIndex = (int)GEPanelIndex::TOPRIGHT; break;647default: _assert_msg_(false, "Invalid GE tab position"); break;648}649650if (tab->pos & pos) {651auto &state = tab->state[stateIndex];652if (state.ptr == nullptr)653return -1;654return state.index;655}656return -1;657}658659u32 CGEDebugger::TexturePreviewFlags(const GPUgstate &state) {660if (state.isTextureAlphaUsed() && !forceOpaque_) {661return SimpleGLWindow::ALPHA_BLEND | SimpleGLWindow::RESIZE_BEST_CENTER;662} else {663return SimpleGLWindow::RESIZE_BEST_CENTER;664}665}666667void CGEDebugger::UpdatePrimaryPreview(const GPUgstate &state) {668bool bufferResult = false;669u32 flags = SimpleGLWindow::ALPHA_IGNORE | SimpleGLWindow::RESIZE_SHRINK_CENTER;670671SetupPreviews();672673primaryBuffer_ = nullptr;674primaryIsFramebuffer_ = false;675if (showClut_) {676bufferResult = GPU_GetCurrentTexture(primaryBuffer_, textureLevel_, &primaryIsFramebuffer_);677flags = TexturePreviewFlags(state);678if (bufferResult) {679UpdateLastTexture(state.getTextureAddress(textureLevel_));680} else {681UpdateLastTexture((u32)-1);682}683} else {684switch (PrimaryDisplayType(fbTabs->CurrentTabIndex())) {685case PRIMARY_FRAMEBUF:686bufferResult = GPU_GetCurrentFramebuffer(primaryBuffer_, GPU_DBG_FRAMEBUF_RENDER);687break;688689case PRIMARY_DEPTHBUF:690bufferResult = GPU_GetCurrentDepthbuffer(primaryBuffer_);691break;692693case PRIMARY_STENCILBUF:694bufferResult = GPU_GetCurrentStencilbuffer(primaryBuffer_);695break;696}697}698699if (bufferResult && primaryBuffer_ != nullptr) {700auto fmt = SimpleGLWindow::Format(primaryBuffer_->GetFormat());701primaryWindow->SetFlags(flags);702primaryWindow->Draw(primaryBuffer_->GetData(), primaryBuffer_->GetStride(), primaryBuffer_->GetHeight(), primaryBuffer_->GetFlipped(), fmt);703704char desc[256];705wchar_t w_desc[256];706DescribePrimaryPreview(state, desc);707ConvertUTF8ToWString(w_desc, ARRAY_SIZE(w_desc), desc);708709SetDlgItemText(m_hDlg, IDC_GEDBG_FRAMEBUFADDR, w_desc);710} else if (primaryWindow != nullptr) {711primaryWindow->Clear();712primaryBuffer_ = nullptr;713714SetDlgItemText(m_hDlg, IDC_GEDBG_FRAMEBUFADDR, L"Failed");715}716}717718void CGEDebugger::UpdateSecondPreview(const GPUgstate &state) {719bool bufferResult = false;720721SetupPreviews();722723secondBuffer_ = nullptr;724secondIsFramebuffer_ = false;725if (showClut_) {726bufferResult = GPU_GetCurrentClut(secondBuffer_);727} else {728bufferResult = GPU_GetCurrentTexture(secondBuffer_, textureLevel_, &secondIsFramebuffer_);729if (bufferResult) {730UpdateLastTexture(state.getTextureAddress(textureLevel_));731} else {732UpdateLastTexture((u32)-1);733}734}735736if (bufferResult) {737auto fmt = SimpleGLWindow::Format(secondBuffer_->GetFormat());738secondWindow->SetFlags(TexturePreviewFlags(state));739if (showClut_) {740// Reduce the stride so it's easier to see.741secondWindow->Draw(secondBuffer_->GetData(), secondBuffer_->GetStride() / 16, secondBuffer_->GetHeight() * 16, secondBuffer_->GetFlipped(), fmt);742} else {743secondWindow->Draw(secondBuffer_->GetData(), secondBuffer_->GetStride(), secondBuffer_->GetHeight(), secondBuffer_->GetFlipped(), fmt);744}745746char desc[256];747DescribeSecondPreview(state, desc);748wchar_t w_desc[256];749ConvertUTF8ToWString(w_desc, ARRAY_SIZE(w_desc), desc);750SetDlgItemText(m_hDlg, IDC_GEDBG_TEXADDR, w_desc);751} else if (secondWindow != nullptr) {752secondWindow->Clear();753secondBuffer_ = nullptr;754755if (gpuDebug == nullptr || state.isTextureMapEnabled()) {756SetDlgItemText(m_hDlg, IDC_GEDBG_TEXADDR, L"Texture: failed");757} else {758SetDlgItemText(m_hDlg, IDC_GEDBG_TEXADDR, L"Texture: disabled");759}760}761}762763void CGEDebugger::PrimaryPreviewHover(int x, int y) {764if (primaryBuffer_ == nullptr) {765return;766}767768SetupPreviews();769770char desc[256] = {0};771772if (!primaryWindow->HasTex()) {773desc[0] = 0;774} else if (x < 0 || y < 0) {775// This means they left the area.776GPUgstate state{};777if (gpuDebug != nullptr) {778state = gpuDebug->GetGState();779}780DescribePrimaryPreview(state, desc);781} else {782// Coordinates are relative to actual framebuffer size.783u32 pix = primaryBuffer_->GetRawPixel(x, y);784DescribePixel(pix, primaryBuffer_->GetFormat(), x, y, desc);785}786787wchar_t w_desc[256];788ConvertUTF8ToWString(w_desc, ARRAY_SIZE(w_desc), desc);789790SetDlgItemText(m_hDlg, IDC_GEDBG_FRAMEBUFADDR, w_desc);791}792793void CGEDebugger::SecondPreviewHover(int x, int y) {794if (secondBuffer_ == nullptr) {795return;796}797798char desc[256] = {0};799800if (!secondWindow->HasTex()) {801desc[0] = 0;802} else if (x < 0 || y < 0) {803// This means they left the area.804GPUgstate state{};805if (gpuDebug != nullptr) {806state = gpuDebug->GetGState();807}808DescribeSecondPreview(state, desc);809} else {810if (showClut_) {811// Use the clut index, rather than coords.812uint32_t clutWidth = secondBuffer_->GetStride() / 16;813u32 pix = secondBuffer_->GetRawPixel(y * clutWidth + x, 0);814DescribePixel(pix, secondBuffer_->GetFormat(), y * clutWidth + x, 0, desc);815} else {816u32 pix = secondBuffer_->GetRawPixel(x, y);817DescribePixel(pix, secondBuffer_->GetFormat(), x, y, desc);818}819}820wchar_t w_desc[256];821ConvertUTF8ToWString(w_desc, ARRAY_SIZE(w_desc), desc);822SetDlgItemText(m_hDlg, IDC_GEDBG_TEXADDR, w_desc);823}824825void CGEDebugger::DescribePixel(u32 pix, GPUDebugBufferFormat fmt, int x, int y, char desc[256]) {826switch (fmt) {827case GPU_DBG_FORMAT_565:828case GPU_DBG_FORMAT_565_REV:829case GPU_DBG_FORMAT_5551:830case GPU_DBG_FORMAT_5551_REV:831case GPU_DBG_FORMAT_5551_BGRA:832case GPU_DBG_FORMAT_4444:833case GPU_DBG_FORMAT_4444_REV:834case GPU_DBG_FORMAT_4444_BGRA:835case GPU_DBG_FORMAT_8888:836case GPU_DBG_FORMAT_8888_BGRA:837DescribePixelRGBA(pix, fmt, x, y, desc);838break;839840case GPU_DBG_FORMAT_16BIT:841snprintf(desc, 256, "%d,%d: %d / %f", x, y, pix, pix * (1.0f / 65535.0f));842break;843844case GPU_DBG_FORMAT_8BIT:845snprintf(desc, 256, "%d,%d: %d / %f", x, y, pix, pix * (1.0f / 255.0f));846break;847848case GPU_DBG_FORMAT_24BIT_8X:849{850DepthScaleFactors depthScale = GetDepthScaleFactors(gstate_c.UseFlags());851// These are only ever going to be depth values, so let's also show scaled to 16 bit.852snprintf(desc, 256, "%d,%d: %d / %f / %f", x, y, pix & 0x00FFFFFF, (pix & 0x00FFFFFF) * (1.0f / 16777215.0f), depthScale.DecodeToU16((pix & 0x00FFFFFF) * (1.0f / 16777215.0f)));853break;854}855856case GPU_DBG_FORMAT_24BIT_8X_DIV_256:857{858// These are only ever going to be depth values, so let's also show scaled to 16 bit.859int z24 = pix & 0x00FFFFFF;860int z16 = z24 - 0x800000 + 0x8000;861snprintf(desc, 256, "%d,%d: %d / %f", x, y, z16, z16 * (1.0f / 65535.0f));862}863break;864865case GPU_DBG_FORMAT_24X_8BIT:866snprintf(desc, 256, "%d,%d: %d / %f", x, y, (pix >> 24) & 0xFF, ((pix >> 24) & 0xFF) * (1.0f / 255.0f));867break;868869case GPU_DBG_FORMAT_FLOAT: {870float pixf = *(float *)&pix;871DepthScaleFactors depthScale = GetDepthScaleFactors(gstate_c.UseFlags());872snprintf(desc, 256, "%d,%d: %f / %f", x, y, pixf, depthScale.DecodeToU16(pixf));873break;874}875876case GPU_DBG_FORMAT_FLOAT_DIV_256:877{878double z = *(float *)&pix;879int z24 = (int)(z * 16777215.0);880881DepthScaleFactors factors = GetDepthScaleFactors(gstate_c.UseFlags());882// TODO: Use GetDepthScaleFactors here too, verify it's the same.883int z16 = z24 - 0x800000 + 0x8000;884885int z16_2 = factors.DecodeToU16(z);886887snprintf(desc, 256, "%d,%d: %d / %f", x, y, z16, (z - 0.5 + (1.0 / 512.0)) * 256.0);888}889break;890891default:892snprintf(desc, 256, "Unexpected format");893}894}895896void CGEDebugger::DescribePixelRGBA(u32 pix, GPUDebugBufferFormat fmt, int x, int y, char desc[256]) {897u32 r = -1, g = -1, b = -1, a = -1;898899switch (fmt) {900case GPU_DBG_FORMAT_565:901r = Convert5To8((pix >> 0) & 0x1F);902g = Convert6To8((pix >> 5) & 0x3F);903b = Convert5To8((pix >> 11) & 0x1F);904break;905case GPU_DBG_FORMAT_565_REV:906b = Convert5To8((pix >> 0) & 0x1F);907g = Convert6To8((pix >> 5) & 0x3F);908r = Convert5To8((pix >> 11) & 0x1F);909break;910case GPU_DBG_FORMAT_5551:911r = Convert5To8((pix >> 0) & 0x1F);912g = Convert5To8((pix >> 5) & 0x1F);913b = Convert5To8((pix >> 10) & 0x1F);914a = (pix >> 15) & 1 ? 255 : 0;915break;916case GPU_DBG_FORMAT_5551_REV:917a = pix & 1 ? 255 : 0;918b = Convert5To8((pix >> 1) & 0x1F);919g = Convert5To8((pix >> 6) & 0x1F);920r = Convert5To8((pix >> 11) & 0x1F);921break;922case GPU_DBG_FORMAT_5551_BGRA:923b = Convert5To8((pix >> 0) & 0x1F);924g = Convert5To8((pix >> 5) & 0x1F);925r = Convert5To8((pix >> 10) & 0x1F);926a = (pix >> 15) & 1 ? 255 : 0;927break;928case GPU_DBG_FORMAT_4444:929r = Convert4To8((pix >> 0) & 0x0F);930g = Convert4To8((pix >> 4) & 0x0F);931b = Convert4To8((pix >> 8) & 0x0F);932a = Convert4To8((pix >> 12) & 0x0F);933break;934case GPU_DBG_FORMAT_4444_REV:935a = Convert4To8((pix >> 0) & 0x0F);936b = Convert4To8((pix >> 4) & 0x0F);937g = Convert4To8((pix >> 8) & 0x0F);938r = Convert4To8((pix >> 12) & 0x0F);939break;940case GPU_DBG_FORMAT_4444_BGRA:941b = Convert4To8((pix >> 0) & 0x0F);942g = Convert4To8((pix >> 4) & 0x0F);943r = Convert4To8((pix >> 8) & 0x0F);944a = Convert4To8((pix >> 12) & 0x0F);945break;946case GPU_DBG_FORMAT_8888:947r = (pix >> 0) & 0xFF;948g = (pix >> 8) & 0xFF;949b = (pix >> 16) & 0xFF;950a = (pix >> 24) & 0xFF;951break;952case GPU_DBG_FORMAT_8888_BGRA:953b = (pix >> 0) & 0xFF;954g = (pix >> 8) & 0xFF;955r = (pix >> 16) & 0xFF;956a = (pix >> 24) & 0xFF;957break;958959default:960snprintf(desc, 256, "Unexpected format");961return;962}963964snprintf(desc, 256, "%d,%d: r=%d, g=%d, b=%d, a=%d", x, y, r, g, b, a);965}966967void CGEDebugger::UpdateTextureLevel(int level) {968GPUgstate state{};969if (gpuDebug != nullptr) {970state = gpuDebug->GetGState();971}972973int maxValid = 0;974for (int i = 1; i < state.getTextureMaxLevel() + 1; ++i) {975if (state.getTextureAddress(i) != 0) {976maxValid = i;977}978}979980textureLevel_ = std::min(std::max(0, level), maxValid);981EnableWindow(GetDlgItem(m_hDlg, IDC_GEDBG_TEXLEVELDOWN), textureLevel_ > 0);982EnableWindow(GetDlgItem(m_hDlg, IDC_GEDBG_TEXLEVELUP), textureLevel_ < maxValid);983}984985void CGEDebugger::UpdateSize(WORD width, WORD height) {986// only resize the tabs for now987HWND tabControl = GetDlgItem(m_hDlg, IDC_GEDBG_MAINTAB);988HWND tabControlRight = GetDlgItem(m_hDlg, IDC_GEDBG_RIGHTTAB);989HWND tabControlTR = GetDlgItem(m_hDlg, IDC_GEDBG_TOPRIGHTTAB);990991RECT tabRect;992GetWindowRect(tabControl,&tabRect);993MapWindowPoints(HWND_DESKTOP,m_hDlg,(LPPOINT)&tabRect,2);994995// Assume the same gap (tabRect.left) on all sides.996if (tabsRight_ && tabsRight_->Count() == 0) {997tabRect.right = tabRect.left + (width - tabRect.left * 2);998} else {999tabRect.right = tabRect.left + (width / 2 - tabRect.left * 2);1000}1001tabRect.bottom = tabRect.top + (height - tabRect.top - tabRect.left);10021003RECT tabRectRight = tabRect;1004if (tabs && tabsRight_ && tabs->Count() == 0 && tabsRight_->Count() != 0) {1005tabRect.right = tabRect.left;1006tabRect.bottom = tabRect.top;1007} else {1008tabRectRight.left += tabRect.right;1009tabRectRight.right += tabRect.right + tabRect.left;1010}10111012RECT frameRect;1013HWND frameWnd = GetDlgItem(m_hDlg, IDC_GEDBG_FRAME);1014GetWindowRect(frameWnd, &frameRect);1015MapWindowPoints(HWND_DESKTOP, m_hDlg, (LPPOINT)&frameRect, 2);10161017RECT trRect = { frameRect.right + 10, frameRect.top, tabRectRight.right, tabRectRight.top };1018if (tabsTR_ && tabsTR_->Count() == 0) {1019trRect.right = trRect.left;1020trRect.bottom = trRect.top;1021}10221023MoveWindow(tabControl, tabRect.left, tabRect.top, tabRect.right - tabRect.left, tabRect.bottom - tabRect.top, TRUE);1024MoveWindow(tabControlRight, tabRectRight.left, tabRectRight.top, tabRectRight.right - tabRectRight.left, tabRectRight.bottom - tabRectRight.top, TRUE);1025MoveWindow(tabControlTR, trRect.left, trRect.top, trRect.right - trRect.left, trRect.bottom - trRect.top, TRUE);1026}10271028void CGEDebugger::SavePosition() {1029RECT rc;1030// Don't save while we're still loading.1031if (tabs && GetWindowRect(m_hDlg, &rc)) {1032g_Config.iGEWindowX = rc.left;1033g_Config.iGEWindowY = rc.top;1034g_Config.iGEWindowW = rc.right - rc.left;1035g_Config.iGEWindowH = rc.bottom - rc.top;1036}1037}10381039BOOL CGEDebugger::DlgProc(UINT message, WPARAM wParam, LPARAM lParam) {1040switch (message) {1041case WM_INITDIALOG:1042return TRUE;10431044case WM_GETMINMAXINFO:1045{1046MINMAXINFO* minmax = (MINMAXINFO*) lParam;1047minmax->ptMinTrackSize.x = minWidth_;1048minmax->ptMinTrackSize.y = minHeight_;1049}1050return TRUE;10511052case WM_SIZE:1053UpdateSize(LOWORD(lParam), HIWORD(lParam));1054SavePosition();1055return TRUE;10561057case WM_MOVE:1058SavePosition();1059return TRUE;10601061case WM_CLOSE:1062GPUDebug::SetActive(false);10631064stepCountDlg.Show(false);1065Show(false);1066return TRUE;10671068case WM_SHOWWINDOW:1069SetupPreviews();1070break;10711072case WM_ACTIVATE:1073if (wParam == WA_ACTIVE || wParam == WA_CLICKACTIVE) {1074g_activeWindow = WINDOW_GEDEBUGGER;1075} else {1076g_activeWindow = WINDOW_OTHER;1077}1078break;10791080case WM_TIMER:1081if (GPUStepping::IsStepping()) {1082static int lastCounter = 0;1083if (lastCounter != GPUStepping::GetSteppingCounter()) {1084UpdatePreviews();1085lastCounter = GPUStepping::GetSteppingCounter();1086}1087} else if (!PSP_IsInited() && primaryBuffer_) {1088SendMessage(m_hDlg, WM_COMMAND, IDC_GEDBG_RESUME, 0);1089}1090break;10911092case WM_NOTIFY:1093switch (wParam)1094{1095case IDC_GEDBG_MAINTAB:1096tabs->HandleNotify(lParam);1097if (gpuDebug != nullptr) {1098for (GEDebuggerTab &tabState : tabStates_) {1099if (tabState.type == GETabType::LISTS)1100UpdateTab(&tabState);1101}1102}1103CheckTabMessage(tabs, GETabPosition::LEFT, lParam);1104break;1105case IDC_GEDBG_RIGHTTAB:1106tabsRight_->HandleNotify(lParam);1107CheckTabMessage(tabsRight_, GETabPosition::RIGHT, lParam);1108break;1109case IDC_GEDBG_TOPRIGHTTAB:1110tabsTR_->HandleNotify(lParam);1111CheckTabMessage(tabsTR_, GETabPosition::TOPRIGHT, lParam);1112break;1113case IDC_GEDBG_FBTABS:1114fbTabs->HandleNotify(lParam);1115if (GPUDebug::IsActive() && gpuDebug != nullptr) {1116UpdatePreviews();1117}1118break;1119}1120break;11211122case WM_MENUSELECT:1123UpdateMenus();1124break;11251126case WM_COMMAND:1127switch (LOWORD(wParam)) {1128case IDC_GEDBG_STEPDRAW:1129SetBreakNext(BreakNext::DRAW);1130break;11311132case IDC_GEDBG_STEP:1133SetBreakNext(BreakNext::OP);1134break;11351136case IDC_GEDBG_STEPTEX:1137SetBreakNext(BreakNext::TEX);1138break;11391140case IDC_GEDBG_STEPFRAME:1141SetBreakNext(BreakNext::FRAME);1142break;11431144case IDC_GEDBG_STEPVSYNC:1145SetBreakNext(BreakNext::VSYNC);1146break;11471148case IDC_GEDBG_STEPPRIM:1149SetBreakNext(BreakNext::PRIM);1150break;11511152case IDC_GEDBG_STEPCURVE:1153SetBreakNext(BreakNext::CURVE);1154break;11551156case IDC_GEDBG_STEPCOUNT:1157stepCountDlg.Show(true);1158break;11591160case IDC_GEDBG_BREAKTEX:1161{1162GPUDebug::SetActive(true);1163if (!gpuDebug) {1164break;1165}1166const auto state = gpuDebug->GetGState();1167u32 texAddr = state.getTextureAddress(textureLevel_);1168// TODO: Better interface that allows add/remove or something.1169if (InputBox_GetHex(GetModuleHandle(NULL), m_hDlg, L"Texture Address", texAddr, texAddr)) {1170if (IsTextureBreakpoint(texAddr)) {1171RemoveTextureBreakpoint(texAddr);1172} else {1173AddTextureBreakpoint(texAddr);1174}1175}1176}1177break;11781179case IDC_GEDBG_BREAKTARGET:1180{1181GPUDebug::SetActive(true);1182if (!gpuDebug) {1183break;1184}1185const auto state = gpuDebug->GetGState();1186u32 fbAddr = state.getFrameBufRawAddress();1187// TODO: Better interface that allows add/remove or something.1188if (InputBox_GetHex(GetModuleHandle(NULL), m_hDlg, L"Framebuffer Address", fbAddr, fbAddr)) {1189if (IsRenderTargetBreakpoint(fbAddr)) {1190RemoveRenderTargetBreakpoint(fbAddr);1191} else {1192AddRenderTargetBreakpoint(fbAddr);1193}1194}1195}1196break;11971198case IDC_GEDBG_TEXLEVELDOWN:1199UpdateTextureLevel(textureLevel_ - 1);1200if (GPUDebug::IsActive() && gpuDebug != nullptr) {1201UpdatePreviews();1202}1203break;12041205case IDC_GEDBG_TEXLEVELUP:1206UpdateTextureLevel(textureLevel_ + 1);1207if (GPUDebug::IsActive() && gpuDebug != nullptr) {1208UpdatePreviews();1209}1210break;12111212case IDC_GEDBG_RESUME:1213SetupPreviews();1214primaryWindow->Clear();1215secondWindow->Clear();1216SetDlgItemText(m_hDlg, IDC_GEDBG_FRAMEBUFADDR, L"");1217SetDlgItemText(m_hDlg, IDC_GEDBG_TEXADDR, L"");1218SetDlgItemText(m_hDlg, IDC_GEDBG_PRIMCOUNTER, L"");12191220SetBreakNext(BreakNext::NONE);1221break;12221223case IDC_GEDBG_RECORD:1224GPURecord::RecordNextFrame([](const Path &path) {1225// Opens a Windows Explorer window with the file, when done.1226System_ShowFileInFolder(path);1227});1228break;12291230case IDC_GEDBG_FLUSH:1231if (GPUDebug::IsActive() && gpuDebug != nullptr) {1232if (!autoFlush_)1233GPU_FlushDrawing();1234UpdatePreviews();1235}1236break;12371238case IDC_GEDBG_FLUSHAUTO:1239autoFlush_ = !autoFlush_;1240break;12411242case IDC_GEDBG_FORCEOPAQUE:1243if (GPUDebug::IsActive() && gpuDebug != nullptr) {1244forceOpaque_ = SendMessage(GetDlgItem(m_hDlg, IDC_GEDBG_FORCEOPAQUE), BM_GETCHECK, 0, 0) != 0;1245UpdatePreviews();1246}1247break;12481249case IDC_GEDBG_SHOWCLUT:1250if (GPUDebug::IsActive() && gpuDebug != nullptr) {1251showClut_ = SendMessage(GetDlgItem(m_hDlg, IDC_GEDBG_SHOWCLUT), BM_GETCHECK, 0, 0) != 0;1252UpdatePreviews();1253}1254break;12551256case IDC_GEDBG_SETPRIMFILTER:1257{1258std::string value = GPUDebug::GetRestrictPrims();1259if (InputBox_GetString(GetModuleHandle(NULL), m_hDlg, L"Prim counter ranges", value, value)) {1260GPUDebug::SetRestrictPrims(value.c_str());1261}1262break;1263}1264}1265break;12661267case WM_GEDBG_STEPDISPLAYLIST:1268SetBreakNext(BreakNext::OP);1269break;12701271case WM_GEDBG_TOGGLEPCBREAKPOINT:1272{1273GPUDebug::SetActive(true);1274u32 pc = (u32)wParam;1275bool temp;1276bool isBreak = IsAddressBreakpoint(pc, temp);1277if (isBreak && !temp) {1278if (GetAddressBreakpointCond(pc, nullptr)) {1279int ret = MessageBox(m_hDlg, L"This breakpoint has a custom condition.\nDo you want to remove it?", L"Confirmation", MB_YESNO);1280if (ret == IDYES)1281RemoveAddressBreakpoint(pc);1282} else {1283RemoveAddressBreakpoint(pc);1284}1285} else {1286AddAddressBreakpoint(pc);1287}1288}1289break;12901291case WM_GEDBG_RUNTOWPARAM:1292{1293GPUDebug::SetActive(true);1294u32 pc = (u32)wParam;1295AddAddressBreakpoint(pc, true);1296SendMessage(m_hDlg,WM_COMMAND,IDC_GEDBG_RESUME,0);1297}1298break;12991300case WM_GEDBG_SETCMDWPARAM:1301GPU_SetCmdValue((u32)wParam);1302break;13031304case WM_GEDBG_UPDATE_WATCH:1305// Just a notification to update.1306for (GEDebuggerTab &tabState : tabStates_) {1307if (tabState.type == GETabType::WATCH)1308UpdateTab(&tabState);1309}1310break;1311}13121313return FALSE;1314}13151316void CGEDebugger::CheckTabMessage(TabControl *t, GETabPosition pos, LPARAM lParam) {1317NMHDR *msg = (LPNMHDR)lParam;1318if (msg->code != NM_RCLICK)1319return;13201321POINT cursorPos;1322GetCursorPos(&cursorPos);1323int tabIndex = t->HitTest(cursorPos);1324if (tabIndex == -1)1325return;13261327// Find the tabState that was clicked on.1328GEDebuggerTab *tab = nullptr;1329int tabStateIndex = 0;1330for (int i = 0; i < (int)tabStates_.size(); ++i) {1331GEDebuggerTab &tabState = tabStates_[i];1332int foundIndex = HasTabIndex(&tabState, pos);1333if (foundIndex == tabIndex) {1334tab = &tabState;1335tabStateIndex = i;1336break;1337}1338}1339// Shouldn't normally happen... maybe we added some other type of tab.1340if (!tab)1341return;13421343int currentPanels = 0;1344for (int i = 0; i < (int)GEPanelIndex::COUNT; ++i) {1345if (tab->state[i].index != -1 && tab->state[i].ptr)1346currentPanels++;1347}13481349HMENU subMenu = GetContextMenu(ContextMenuID::GEDBG_TABS);1350static const int itemIDs[] = { ID_GEDBG_SHOWONLEFT, ID_GEDBG_SHOWONRIGHT, ID_GEDBG_SHOWONTOPRIGHT };1351for (int i = 0; i < (int)GEPanelIndex::COUNT; ++i) {1352bool active = tab->state[i].index != -1 && tab->state[i].ptr;1353bool disabled = active && currentPanels == 1;1354CheckMenuItem(subMenu, itemIDs[i], active ? MF_CHECKED : MF_UNCHECKED);1355EnableMenuItem(subMenu, itemIDs[i], disabled ? MF_GRAYED : MF_ENABLED);1356}13571358auto toggleState = [&](GEPanelIndex i, GETabPosition pos, uint32_t &configured) {1359auto &state = tab->state[(int)i];1360bool removing = state.index != -1 && state.ptr;1361if (removing) {1362RemoveTab(tab, pos);1363configured &= ~(1 << tabStateIndex);1364} else {1365AddTab(tab, pos);1366configured |= 1 << tabStateIndex;1367}13681369RECT rc;1370GetClientRect(m_hDlg, &rc);1371UpdateSize(rc.right - rc.left, rc.bottom - rc.top);1372};13731374switch (TriggerContextMenu(ContextMenuID::GEDBG_TABS, m_hDlg, ContextPoint::FromCursor())) {1375case ID_GEDBG_SHOWONLEFT:1376toggleState(GEPanelIndex::LEFT, GETabPosition::LEFT, g_Config.uGETabsLeft);1377break;13781379case ID_GEDBG_SHOWONRIGHT:1380toggleState(GEPanelIndex::RIGHT, GETabPosition::RIGHT, g_Config.uGETabsRight);1381break;13821383case ID_GEDBG_SHOWONTOPRIGHT:1384toggleState(GEPanelIndex::TOPRIGHT, GETabPosition::TOPRIGHT, g_Config.uGETabsTopRight);1385break;13861387default:1388// Cancel, that's fine.1389break;1390}1391}13921393void CGEDebugger::UpdateMenus() {1394CheckMenuItem(GetMenu(m_hDlg), IDC_GEDBG_FLUSHAUTO, MF_BYCOMMAND | (autoFlush_ ? MF_CHECKED : MF_UNCHECKED));1395}139613971398