diff --git a/Core/MIPS/MIPSVFPUUtils.cpp b/Core/MIPS/MIPSVFPUUtils.cpp index fb2c91c89e..6183190706 100644 --- a/Core/MIPS/MIPSVFPUUtils.cpp +++ b/Core/MIPS/MIPSVFPUUtils.cpp @@ -16,6 +16,7 @@ // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include +#include #include #include "Common/BitScan.h" @@ -684,6 +685,34 @@ float vfpu_h2f_float(u16 h) { return vfpu_float_from_bits(vfpu_h2f(h)); } +const char *const g_vfpuDisplayModeNames[(int)VFPUDisplayMode::COUNT] = { + "Float", "HalfFloat", "Hex", "Bytes", "Shorts", "Ints", +}; + +void FormatVFPUValue(char *out, size_t outSize, u32 value, VFPUDisplayMode mode) { + switch (mode) { + case VFPUDisplayMode::Float: + snprintf(out, outSize, "%f", vfpu_float_from_bits(value)); + break; + case VFPUDisplayMode::HalfFloat: + snprintf(out, outSize, "%g %g", vfpu_h2f_float((u16)(value & 0xFFFF)), vfpu_h2f_float((u16)(value >> 16))); + break; + case VFPUDisplayMode::Hex: + snprintf(out, outSize, "%08x", value); + break; + case VFPUDisplayMode::Bytes: + snprintf(out, outSize, "%02x %02x %02x %02x", value & 0xFF, (value >> 8) & 0xFF, (value >> 16) & 0xFF, value >> 24); + break; + case VFPUDisplayMode::Shorts: + snprintf(out, outSize, "%d %d", (s16)(value & 0xFFFF), (s16)(value >> 16)); + break; + case VFPUDisplayMode::Ints: + default: + snprintf(out, outSize, "%d", (s32)value); + break; + } +} + float vfpu_h2f_lower(float word) { return vfpu_float_from_bits(vfpu_h2f((u16)(vfpu_bits_from_float(word) & 0xFFFF))); } diff --git a/Core/MIPS/MIPSVFPUUtils.h b/Core/MIPS/MIPSVFPUUtils.h index ac5f603344..004a6d0954 100644 --- a/Core/MIPS/MIPSVFPUUtils.h +++ b/Core/MIPS/MIPSVFPUUtils.h @@ -318,6 +318,19 @@ float Float16ToFloat32(unsigned short l); u32 vfpu_h2f(u16 h); // The same, as a float (vfim). float vfpu_h2f_float(u16 h); + +// How the debuggers show a VFPU register. The multi-part modes list the lowest part first. +enum class VFPUDisplayMode { + Float, + HalfFloat, + Hex, + Bytes, + Shorts, + Ints, + COUNT, +}; +extern const char *const g_vfpuDisplayModeNames[(int)VFPUDisplayMode::COUNT]; +void FormatVFPUValue(char *out, size_t outSize, u32 value, VFPUDisplayMode mode); // vh2f of the lower or upper half of a word, with the word and result as float bits. For the JITs. float vfpu_h2f_lower(float word); float vfpu_h2f_upper(float word); diff --git a/UI/ImDebugger/ImDebugger.cpp b/UI/ImDebugger/ImDebugger.cpp index a824729edd..dad09f84dd 100644 --- a/UI/ImDebugger/ImDebugger.cpp +++ b/UI/ImDebugger/ImDebugger.cpp @@ -26,6 +26,7 @@ #include "Core/Debugger/Breakpoints.h" #include "Core/MIPS/MIPSDebugInterface.h" #include "Core/MIPS/MIPSTables.h" +#include "Core/MIPS/MIPSVFPUUtils.h" #include "Core/HW/SimpleAudioDec.h" #include "Core/FileSystems/MetaFileSystem.h" #include "Core/Debugger/SymbolMap.h" @@ -354,12 +355,68 @@ static void DrawFPRs(ImConfig &config, ImControl &control, const MIPSDebugInterf } static void DrawVFPU(ImConfig &config, ImControl &control, const MIPSDebugInterface *mipsDebug, const ImSnapshotState &prev) { - ImGui::SetNextWindowSize(ImVec2(320, 600), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver); if (!ImGui::Begin("VFPU", &config.vfpuOpen)) { ImGui::End(); return; } - ImGui::Text("TODO"); + const bool appearing = ImGui::IsWindowAppearing(); + if (ImGui::BeginTabBar("vfpuMode")) { + for (int i = 0; i < (int)VFPUDisplayMode::COUNT; i++) { + // Restore the saved mode when the window appears. + ImGuiTabItemFlags flags = (appearing && i == config.vfpuDisplayMode) ? ImGuiTabItemFlags_SetSelected : 0; + if (ImGui::BeginTabItem(g_vfpuDisplayModeNames[i], nullptr, flags)) { + config.vfpuDisplayMode = i; + ImGui::EndTabItem(); + } + } + ImGui::EndTabBar(); + } + + bool noDiff = coreState == CORE_RUNNING_CPU || coreState == CORE_STEPPING_GE; + const VFPUDisplayMode mode = (VFPUDisplayMode)config.vfpuDisplayMode; + + // Laid out like the Win32 VFPU window: each column of a matrix is a row vector (Rmnr), each line a + // column vector (Cmc0). + ImGui::BeginChild("vfpuMatrices"); + char temp[64]; + for (int matrix = 0; matrix < 8; matrix++) { + ImGui::PushID(matrix); + if (ImGui::BeginTable("matrix", 5, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) { + snprintf(temp, sizeof(temp), "M%d00", matrix); + ImGui::TableSetupColumn(temp); + for (int column = 0; column < 4; column++) { + snprintf(temp, sizeof(temp), "R%d0%d", matrix, column); + ImGui::TableSetupColumn(temp, ImGuiTableColumnFlags_WidthFixed, ImGui::CalcTextSize("-0000.000000").x); + } + ImGui::TableHeadersRow(); + + for (int row = 0; row < 4; row++) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("C%d%d0", matrix, row); + for (int column = 0; column < 4; column++) { + ImGui::TableNextColumn(); + int index = column * 32 + row + matrix * 4; + u32 value = mipsDebug->GetRegValue(2, index); + // NOTE: Using memcmp to avoid NaN problems. + bool diff = memcmp(&value, &prev.vpr[voffset[index]], 4) != 0 && !noDiff; + if (diff) { + ImGui::PushStyleColor(ImGuiCol_Text, ImDebuggerColor_Diff); + } + FormatVFPUValue(temp, sizeof(temp), value, mode); + ImGui::TextUnformatted(temp); + if (diff) { + ImGui::PopStyleColor(); + } + } + } + ImGui::EndTable(); + } + ImGui::PopID(); + ImGui::Spacing(); + } + ImGui::EndChild(); ImGui::End(); } @@ -3117,6 +3174,7 @@ void ImConfig::SyncConfig(IniFile *ini, bool save) { sync.Sync("gprOpen", &gprOpen, false); sync.Sync("fprOpen", &fprOpen, false); sync.Sync("vfpuOpen", &vfpuOpen, false); + sync.Sync("vfpuDisplayMode", &vfpuDisplayMode, 0); sync.Sync("threadsOpen", &threadsOpen, false); sync.Sync("callstackOpen", &callstackOpen, false); sync.Sync("breakpointsOpen", &breakpointsOpen, false); diff --git a/UI/ImDebugger/ImDebugger.h b/UI/ImDebugger/ImDebugger.h index 9fc31b2382..289d7b216b 100644 --- a/UI/ImDebugger/ImDebugger.h +++ b/UI/ImDebugger/ImDebugger.h @@ -108,6 +108,7 @@ struct ImConfig { int selectedMemCheck = -1; int selectedRegBreakpoint = -1; int selectedAtracCtx = 0; + int vfpuDisplayMode = 0; int selectedMp3Ctx = 0; int selectedAacCtx = 0; int selectedMemoryBlock = 0; diff --git a/Windows/Debugger/Debugger_VFPUDlg.cpp b/Windows/Debugger/Debugger_VFPUDlg.cpp index 6057164e6e..ada3a53b40 100644 --- a/Windows/Debugger/Debugger_VFPUDlg.cpp +++ b/Windows/Debugger/Debugger_VFPUDlg.cpp @@ -1,219 +1,178 @@ #include "Common/CommonWindows.h" #include #include +#include +#include #include "Windows/resource.h" #include "Common/Data/Encoding/Utf8.h" +#include "Common/System/Display.h" -#include "Core/Debugger/SymbolMap.h" +#include "Core/Config.h" +#include "Core/Core.h" +#include "Core/MIPS/MIPS.h" +#include "Core/MIPS/MIPSVFPUUtils.h" #include "Windows/Debugger/Debugger_VFPUDlg.h" -#include "Windows/Debugger/CtrlMemView.h" - -#include "Core/MIPS/MIPS.h" // BAD - CVFPUDlg::CVFPUDlg(HINSTANCE _hInstance, HWND _hParent, DebugInterface *cpu_) : Dialog((LPCSTR)IDD_VFPU, _hInstance,_hParent) { cpu = cpu_; - wchar_t temp[256]; - wsprintf(temp, L"VFPU - R4"); - SetWindowText(m_hDlg,temp); + SetWindowText(m_hDlg, L"VFPU"); ShowWindow(m_hDlg,SW_HIDE); - font = CreateFont(12,0,0,0,FW_DONTCARE,FALSE,FALSE,FALSE,DEFAULT_CHARSET,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,DEFAULT_PITCH, + + // The same font the other debugger views use, scaled by the DPI. + const float fontScale = 1.0f / g_display.dpi_scale_real_y; + int fontHeight = (int)(g_Config.iFontHeight * fontScale); + int fontWidth = std::max((int)(g_Config.iFontWidth * fontScale), 1); + font = CreateFont(fontHeight, fontWidth, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, L"Lucida Console"); HWND tabs = GetDlgItem(m_hDlg, IDC_TABDATATYPE); - TCITEM tcItem; - ZeroMemory (&tcItem,sizeof (tcItem)); - tcItem.mask = TCIF_TEXT; - tcItem.dwState = 0; - tcItem.pszText = (LPTSTR)L"Float"; - tcItem.cchTextMax = (int)wcslen(tcItem.pszText)+1; - tcItem.iImage = 0; - TabCtrl_InsertItem(tabs, TabCtrl_GetItemCount(tabs),&tcItem); - tcItem.pszText = (LPTSTR)L"HalfFloat"; - tcItem.cchTextMax = (int)wcslen(tcItem.pszText)+1; - TabCtrl_InsertItem(tabs, TabCtrl_GetItemCount(tabs),&tcItem); - tcItem.pszText = (LPTSTR)L"Hex"; - tcItem.cchTextMax = (int)wcslen(tcItem.pszText)+1; - TabCtrl_InsertItem(tabs, TabCtrl_GetItemCount(tabs),&tcItem); - tcItem.pszText = (LPTSTR)L"Bytes"; - tcItem.cchTextMax = (int)wcslen(tcItem.pszText)+1; - TabCtrl_InsertItem(tabs, TabCtrl_GetItemCount(tabs),&tcItem); - tcItem.pszText = (LPTSTR)L"Shorts"; - tcItem.cchTextMax = (int)wcslen(tcItem.pszText)+1; - TabCtrl_InsertItem(tabs, TabCtrl_GetItemCount(tabs),&tcItem); - tcItem.pszText = (LPTSTR)L"Ints"; - tcItem.cchTextMax = (int)wcslen(tcItem.pszText)+1; - TabCtrl_InsertItem(tabs, TabCtrl_GetItemCount(tabs),&tcItem); - mode=0; - Size(); + TCITEM tcItem{}; + tcItem.mask = TCIF_TEXT; + for (int i = 0; i < (int)VFPUDisplayMode::COUNT; i++) { + std::wstring name = ConvertUTF8ToWString(g_vfpuDisplayModeNames[i]); + tcItem.pszText = (LPTSTR)name.c_str(); + TabCtrl_InsertItem(tabs, i, &tcItem); + } + mode = 0; + + ComputeLayout(); + + // The dialog template's size is in dialog units, which don't follow the font above, so size the + // window to fit the grid instead. + RECT rc{ 0, 0, layout_.xStart + layout_.columnWidth * 4 + layout_.pad, layout_.yStart + (int)(layout_.rowHeight * 5.5f * 8) }; + AdjustWindowRectEx(&rc, GetWindowLong(m_hDlg, GWL_STYLE), FALSE, GetWindowLong(m_hDlg, GWL_EXSTYLE)); + SetWindowPos(m_hDlg, nullptr, 0, 0, rc.right - rc.left, rc.bottom - rc.top, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE); } - -CVFPUDlg::~CVFPUDlg(void) +CVFPUDlg::~CVFPUDlg() { DeleteObject(font); } -void CVFPUDlg::Update(void) +void CVFPUDlg::ComputeLayout() { + HDC hdc = GetDC(m_hDlg); + HFONT oldFont = (HFONT)SelectObject(hdc, font); + TEXTMETRIC tm{}; + GetTextMetrics(hdc, &tm); + SelectObject(hdc, oldFont); + ReleaseDC(m_hDlg, hdc); + + const int charWidth = std::max((int)tm.tmAveCharWidth, 1); + layout_.pad = std::max(charWidth / 2, 2); + layout_.rowHeight = tm.tmHeight + layout_.pad; + // Labels are like "M000" and "R000". Cells fit the 11 characters of the Bytes and Ints modes, + // longer values get clipped. + layout_.xStart = charWidth * 4 + layout_.pad * 2; + layout_.columnWidth = charWidth * 11 + layout_.pad * 2; + + RECT tabRect; + GetWindowRect(GetDlgItem(m_hDlg, IDC_TABDATATYPE), &tabRect); + MapWindowPoints(HWND_DESKTOP, m_hDlg, (LPPOINT)&tabRect, 2); + layout_.yStart = tabRect.bottom + layout_.pad; +} + +void CVFPUDlg::Update() { if (m_hDlg != NULL) { InvalidateRect(m_hDlg, 0,0); - /* - CtrlMemView *mv = CtrlMemView::getFrom(GetDlgItem(m_hDlg,IDC_MEMVIEW)); - if (mv != NULL) - mv->redraw();*/ - } + } } +void CVFPUDlg::Paint(HDC hdc) { + u32 values[128]; + { + // The registers belong to the CPU thread. See g_frameMutex in Core.h. + std::lock_guard frameGuard(g_frameMutex); + CoreShutdownLock coreLock = Core_LockAgainstShutdown(); + for (int i = 0; i < 128; i++) { + values[i] = currentMIPS->vi[voffset[i]]; + } + } + SetBkMode(hdc, TRANSPARENT); + LOGBRUSH lbr{}; + lbr.lbColor = 0xffffff; + HBRUSH whiteBrush = CreateBrushIndirect(&lbr); + HPEN blackPen = CreatePen(0, 0, 0); + HPEN oldPen = (HPEN)SelectObject(hdc, blackPen); + HBRUSH oldBrush = (HBRUSH)SelectObject(hdc, whiteBrush); + HFONT oldFont = (HFONT)SelectObject(hdc, font); + + const int rowHeight = layout_.rowHeight; + const int columnWidth = layout_.columnWidth; + const int xStart = layout_.xStart; + const int pad = layout_.pad; + + auto drawCell = [&](int x1, int y1, int x2, const char *text) { + Rectangle(hdc, x1, y1, x2 + 1, y1 + rowHeight + 1); + RECT clip{ x1 + 1, y1 + 1, x2, y1 + rowHeight }; + ExtTextOutA(hdc, x1 + pad, y1 + pad / 2 + 1, ETO_CLIPPED, &clip, text, (UINT)strlen(text), nullptr); + }; + + char temp[64]; + for (int matrix = 0; matrix < 8; matrix++) { + int my = layout_.yStart + (int)(matrix * rowHeight * 5.5f); + snprintf(temp, sizeof(temp), "M%i00", matrix); + drawCell(0, my, xStart, temp); + + for (int column = 0; column < 4; column++) { + int x = column * columnWidth + xStart; + snprintf(temp, sizeof(temp), "R%i0%i", matrix, column); + drawCell(x, my, x + columnWidth, temp); + + snprintf(temp, sizeof(temp), "C%i%i0", matrix, column); + drawCell(0, my + rowHeight * (column + 1), xStart, temp); + + for (int row = 0; row < 4; row++) { + FormatVFPUValue(temp, sizeof(temp), values[column * 32 + row + matrix * 4], (VFPUDisplayMode)mode); + drawCell(x, my + rowHeight * (row + 1), x + columnWidth, temp); + } + } + } + + SelectObject(hdc, oldFont); + SelectObject(hdc, oldPen); + SelectObject(hdc, oldBrush); + DeleteObject(blackPen); + DeleteObject(whiteBrush); +} BOOL CVFPUDlg::DlgProc(UINT message, WPARAM wParam, LPARAM lParam) { switch(message) { case WM_INITDIALOG: - { - return TRUE; - } - break; - case WM_COMMAND: - { -// CtrlMemView *mv = CtrlMemView::getFrom(GetDlgItem(m_hDlg,IDC_MEMVIEW)); - //switch (LOWORD(wParam)) - //{ - //} - } - break; + return TRUE; case WM_NOTIFY: { HWND tabs = GetDlgItem(m_hDlg, IDC_TABDATATYPE); - NMHDR* pNotifyMessage = NULL; - pNotifyMessage = (LPNMHDR)lParam; + NMHDR *pNotifyMessage = (LPNMHDR)lParam; if (pNotifyMessage->hwndFrom == tabs) { - mode = TabCtrl_GetCurSel (tabs); + mode = TabCtrl_GetCurSel(tabs); InvalidateRect(m_hDlg,0,0); } break; } - case WM_SIZE: - Size(); - break; - case WM_PAINT: { - HWND wnd = m_hDlg; - RECT rect; - GetClientRect(wnd, &rect); PAINTSTRUCT p; - HDC hdc = BeginPaint(wnd, &p); - SetBkMode(hdc, TRANSPARENT); - DWORD bgColor = 0xffffff; - HPEN nullPen=CreatePen(0,0,bgColor); - HPEN currentPen=CreatePen(0,0,0); - HPEN selPen=CreatePen(0,0,0x808080); - LOGBRUSH lbr; - lbr.lbHatch=0; lbr.lbStyle=0; - lbr.lbColor=bgColor; - HBRUSH nullBrush=CreateBrushIndirect(&lbr); - lbr.lbColor=0xFFEfE8; - HBRUSH currentBrush=CreateBrushIndirect(&lbr); - lbr.lbColor=0x70FF70; - HBRUSH pcBrush=CreateBrushIndirect(&lbr); - HPEN oldPen=(HPEN)SelectObject(hdc,currentPen); - HBRUSH oldBrush=(HBRUSH)SelectObject(hdc,nullBrush); - HFONT oldFont = (HFONT)SelectObject(hdc,(HGDIOBJ)font); - - enum - { - rowHeight = 15, - columnWidth = 80, - xStart = columnWidth/2, - yStart = 30, - }; - - for (int matrix = 0; matrix<8; matrix++) - { - int my = (int)(yStart + matrix * rowHeight * 5.5f); - Rectangle(hdc, 0, my, xStart, my+rowHeight); - char temp[256]; - int temp_len = sprintf_s(temp, "M%i00", matrix); - TextOutA(hdc,3,my+2,temp,temp_len); - Rectangle(hdc,xStart,my+rowHeight,xStart+columnWidth*4,my+5*rowHeight); - - for (int column = 0; column<4; column++) - { - int y = my; - int x = column * columnWidth + xStart; - - Rectangle(hdc, x, y, x + columnWidth, y+rowHeight); - temp_len = sprintf_s(temp, "R%i0%i", matrix, column); - TextOutA(hdc,x+3,y+2,temp,temp_len); - - Rectangle(hdc, 0, y+rowHeight*(column+1), xStart, y+rowHeight*(column+2)); - temp_len = sprintf_s(temp, "C%i%i0", matrix, column); - TextOutA(hdc,3,y+rowHeight*(column+1)+1,temp,temp_len); - - y+=rowHeight; - - for (int row = 0; row<4; row++) - { - float val = currentMIPS->v[voffset[column*32+row+matrix*4]]; - u32 hex = currentMIPS->vi[voffset[column*32+row+matrix*4]]; - switch (mode) - { - case 0: temp_len = sprintf_s(temp,"%f",val); break; -// case 1: temp_len = sprintf_s(temp,"??"); break; - case 2: temp_len = sprintf_s(temp,"0x%08x",hex); break; - default:temp_len = sprintf_s(temp,"%f",val); break; - } - - TextOutA(hdc,x+3,y+2,temp,temp_len); - y+=rowHeight; - } - } - } - - SelectObject(hdc,oldFont); - SelectObject(hdc,oldPen); - SelectObject(hdc,oldBrush); - - DeleteObject(nullPen); - DeleteObject(currentPen); - DeleteObject(selPen); - - DeleteObject(nullBrush); - DeleteObject(pcBrush); - DeleteObject(currentBrush); - + HDC hdc = BeginPaint(m_hDlg, &p); + Paint(hdc); EndPaint(m_hDlg, &p); } break; - + case WM_CLOSE: Show(false); break; } - + return 0; } - -void CVFPUDlg::Goto(u32 addr) -{ - Show(true); - CtrlMemView *mv = CtrlMemView::getFrom(GetDlgItem(CVFPUDlg::m_hDlg,IDC_MEMVIEW)); - mv->gotoAddr(addr & ~3); -} - - -void CVFPUDlg::Size() -{ - RECT rc; - GetClientRect(m_hDlg,&rc); -} diff --git a/Windows/Debugger/Debugger_VFPUDlg.h b/Windows/Debugger/Debugger_VFPUDlg.h index 159b5e3a17..e59c74af26 100644 --- a/Windows/Debugger/Debugger_VFPUDlg.h +++ b/Windows/Debugger/Debugger_VFPUDlg.h @@ -9,13 +9,24 @@ public: CVFPUDlg(HINSTANCE _hInstance, HWND _hParent, DebugInterface *cpu_); ~CVFPUDlg(); - void Goto(u32 addr); void Update() override; - void Size(); private: + void ComputeLayout(); + void Paint(HDC hdc); + BOOL DlgProc(UINT message, WPARAM wParam, LPARAM lParam) override; + + // In pixels, derived from the font. + struct Layout { + int rowHeight; + int columnWidth; + int xStart; + int yStart; + int pad; + }; + DebugInterface *cpu; HFONT font; int mode; - BOOL DlgProc(UINT message, WPARAM wParam, LPARAM lParam) override; + Layout layout_{}; };