mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-09-26 16:59:35 -04:00
d91d2c1b75
fix: fix MPEG out of sync with new EE refactor
2276 lines
89 KiB
C++
2276 lines
89 KiB
C++
#include "ps2_debug_panel.h"
|
|
#include "ps2_runtime.h"
|
|
#include "ps2_runtime_macros.h"
|
|
#include "ps2_log.h"
|
|
#include "runtime/ee_scheduler.h"
|
|
|
|
#include <unordered_set>
|
|
#include "Kernel/Syscalls/Helpers/State.h"
|
|
#include "Kernel/Stubs/CD.h"
|
|
#include "Kernel/Stubs/MemoryCard.h"
|
|
#include "Kernel/Stubs/Pad.h"
|
|
|
|
#if defined(PS2X_ENABLE_DEBUG_UI) && !defined(PLATFORM_VITA)
|
|
#include "imgui.h"
|
|
#include "rlImGui.h"
|
|
#include "raylib.h"
|
|
#endif
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <exception>
|
|
#include <iomanip>
|
|
#include <mutex>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace
|
|
{
|
|
#if defined(PS2X_ENABLE_DEBUG_UI) && !defined(PLATFORM_VITA)
|
|
const char *threadStatusName(EeThreadStatus status)
|
|
{
|
|
switch (status)
|
|
{
|
|
case EeThreadStatus::Running:
|
|
return "RUN";
|
|
case EeThreadStatus::Ready:
|
|
return "READY";
|
|
case EeThreadStatus::Waiting:
|
|
return "WAIT";
|
|
case EeThreadStatus::Suspended:
|
|
return "SUSPEND";
|
|
case EeThreadStatus::WaitingSuspended:
|
|
return "WAITSUSP";
|
|
case EeThreadStatus::Dormant:
|
|
return "DORMANT";
|
|
default:
|
|
return "?";
|
|
}
|
|
}
|
|
|
|
const char *waitTypeName(EeWaitReason waitType)
|
|
{
|
|
switch (waitType)
|
|
{
|
|
case EeWaitReason::None:
|
|
return "NONE";
|
|
case EeWaitReason::Sleep:
|
|
return "SLEEP";
|
|
case EeWaitReason::Semaphore:
|
|
return "SEMA";
|
|
case EeWaitReason::EventFlag:
|
|
return "EVENT";
|
|
case EeWaitReason::VSync:
|
|
return "VSYNC";
|
|
case EeWaitReason::External:
|
|
return "EXTERNAL";
|
|
case EeWaitReason::Mpeg:
|
|
return "MPEG";
|
|
default:
|
|
return "?";
|
|
}
|
|
}
|
|
|
|
std::string iopRpcSidName(const ps2x::iop::DebugSnapshot &snapshot, uint32_t sid)
|
|
{
|
|
for (const ps2x::iop::DebugService &service : snapshot.services)
|
|
{
|
|
if (std::find(service.sids.begin(), service.sids.end(), sid) !=
|
|
service.sids.end())
|
|
{
|
|
return service.name;
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
|
|
std::string pressedPadButtons(uint16_t activeLowButtons)
|
|
{
|
|
struct ButtonName
|
|
{
|
|
uint16_t mask;
|
|
const char *name;
|
|
};
|
|
static constexpr ButtonName buttons[] = {
|
|
{1u << 0, "Select"},
|
|
{1u << 1, "L3"},
|
|
{1u << 2, "R3"},
|
|
{1u << 3, "Start"},
|
|
{1u << 4, "Up"},
|
|
{1u << 5, "Right"},
|
|
{1u << 6, "Down"},
|
|
{1u << 7, "Left"},
|
|
{1u << 8, "L2"},
|
|
{1u << 9, "R2"},
|
|
{1u << 10, "L1"},
|
|
{1u << 11, "R1"},
|
|
{1u << 12, "Triangle"},
|
|
{1u << 13, "Circle"},
|
|
{1u << 14, "Cross"},
|
|
{1u << 15, "Square"},
|
|
};
|
|
|
|
std::string out;
|
|
for (const ButtonName &button : buttons)
|
|
{
|
|
if ((activeLowButtons & button.mask) == 0u)
|
|
{
|
|
if (!out.empty())
|
|
{
|
|
out += ", ";
|
|
}
|
|
out += button.name;
|
|
}
|
|
}
|
|
return out.empty() ? std::string("none") : out;
|
|
}
|
|
|
|
std::string rpcDebugFlags(uint32_t flags)
|
|
{
|
|
struct FlagName
|
|
{
|
|
uint32_t mask;
|
|
const char *name;
|
|
};
|
|
static constexpr FlagName names[] = {
|
|
{kSifRpcDebugFlagNowait, "nowait"},
|
|
{kSifRpcDebugFlagHandledByHle, "hle"},
|
|
{kSifRpcDebugFlagCallback, "callback"},
|
|
{kSifRpcDebugFlagMissingClient, "bad-client"},
|
|
{kSifRpcDebugFlagServerDispatch, "server"},
|
|
{kSifRpcDebugFlagUnhandled, "unhandled"},
|
|
{kSifRpcDebugFlagFallbackCopy, "fallback-copy"},
|
|
{kSifRpcDebugFlagFallbackZero, "fallback-zero"},
|
|
};
|
|
|
|
std::string out;
|
|
for (const FlagName &name : names)
|
|
{
|
|
if ((flags & name.mask) != 0u)
|
|
{
|
|
if (!out.empty())
|
|
{
|
|
out += ",";
|
|
}
|
|
out += name.name;
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
std::string rpcPreviewBytes(const uint8_t *bytes, uint32_t count)
|
|
{
|
|
if (!bytes || count == 0u)
|
|
{
|
|
return "";
|
|
}
|
|
|
|
std::string out;
|
|
char item[4] = {};
|
|
for (uint32_t i = 0; i < count; ++i)
|
|
{
|
|
std::snprintf(item, sizeof(item), "%02X", bytes[i]);
|
|
if (!out.empty())
|
|
{
|
|
out.push_back(' ');
|
|
}
|
|
out += item;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
template <typename T>
|
|
bool readGuestObjectNoThrow(uint8_t *rdram, uint32_t addr, T &out)
|
|
{
|
|
if (!rdram || addr == 0u)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
uint8_t *ptr = getMemPtr(rdram, addr);
|
|
if (!ptr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
std::memcpy(&out, ptr, sizeof(T));
|
|
return true;
|
|
}
|
|
|
|
const char *primTypeName(GSPrimType type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case GS_PRIM_POINT:
|
|
return "POINT";
|
|
case GS_PRIM_LINE:
|
|
return "LINE";
|
|
case GS_PRIM_LINESTRIP:
|
|
return "LINE_STRIP";
|
|
case GS_PRIM_TRIANGLE:
|
|
return "TRI";
|
|
case GS_PRIM_TRISTRIP:
|
|
return "TRI_STRIP";
|
|
case GS_PRIM_TRIFAN:
|
|
return "TRI_FAN";
|
|
case GS_PRIM_SPRITE:
|
|
return "SPRITE";
|
|
default:
|
|
return "?";
|
|
}
|
|
}
|
|
|
|
const char *gsDebugEventKindName(GSDebugEventKind kind)
|
|
{
|
|
switch (kind)
|
|
{
|
|
case GSDebugEventKind::GifTag:
|
|
return "GIF";
|
|
case GSDebugEventKind::Register:
|
|
return "REG";
|
|
case GSDebugEventKind::Draw:
|
|
return "DRAW";
|
|
case GSDebugEventKind::Transfer:
|
|
return "XFER";
|
|
case GSDebugEventKind::Present:
|
|
return "PRESENT";
|
|
default:
|
|
return "?";
|
|
}
|
|
}
|
|
|
|
const char *gifFormatName(uint8_t flg)
|
|
{
|
|
switch (flg)
|
|
{
|
|
case GIF_FMT_PACKED:
|
|
return "PACKED";
|
|
case GIF_FMT_REGLIST:
|
|
return "REGLIST";
|
|
case GIF_FMT_IMAGE:
|
|
return "IMAGE";
|
|
case GIF_FMT_DISABLED:
|
|
return "DISABLED";
|
|
default:
|
|
return "?";
|
|
}
|
|
}
|
|
|
|
const char *gsRegName(uint8_t reg)
|
|
{
|
|
switch (reg)
|
|
{
|
|
case GS_REG_PRIM:
|
|
return "PRIM";
|
|
case GS_REG_RGBAQ:
|
|
return "RGBAQ";
|
|
case GS_REG_ST:
|
|
return "ST";
|
|
case GS_REG_UV:
|
|
return "UV";
|
|
case GS_REG_XYZF2:
|
|
return "XYZF2";
|
|
case GS_REG_XYZ2:
|
|
return "XYZ2";
|
|
case GS_REG_TEX0_1:
|
|
return "TEX0_1";
|
|
case GS_REG_TEX0_2:
|
|
return "TEX0_2";
|
|
case GS_REG_CLAMP_1:
|
|
return "CLAMP_1";
|
|
case GS_REG_CLAMP_2:
|
|
return "CLAMP_2";
|
|
case GS_REG_XYZF3:
|
|
return "XYZF3";
|
|
case GS_REG_XYZ3:
|
|
return "XYZ3";
|
|
case GS_REG_TEX1_1:
|
|
return "TEX1_1";
|
|
case GS_REG_TEX1_2:
|
|
return "TEX1_2";
|
|
case GS_REG_TEX2_1:
|
|
return "TEX2_1";
|
|
case GS_REG_TEX2_2:
|
|
return "TEX2_2";
|
|
case GS_REG_XYOFFSET_1:
|
|
return "XYOFFSET_1";
|
|
case GS_REG_XYOFFSET_2:
|
|
return "XYOFFSET_2";
|
|
case GS_REG_PRMODECONT:
|
|
return "PRMODECONT";
|
|
case GS_REG_PRMODE:
|
|
return "PRMODE";
|
|
case GS_REG_TEXCLUT:
|
|
return "TEXCLUT";
|
|
case GS_REG_TEXA:
|
|
return "TEXA";
|
|
case GS_REG_SCISSOR_1:
|
|
return "SCISSOR_1";
|
|
case GS_REG_SCISSOR_2:
|
|
return "SCISSOR_2";
|
|
case GS_REG_ALPHA_1:
|
|
return "ALPHA_1";
|
|
case GS_REG_ALPHA_2:
|
|
return "ALPHA_2";
|
|
case GS_REG_TEST_1:
|
|
return "TEST_1";
|
|
case GS_REG_TEST_2:
|
|
return "TEST_2";
|
|
case GS_REG_FBA_1:
|
|
return "FBA_1";
|
|
case GS_REG_FBA_2:
|
|
return "FBA_2";
|
|
case GS_REG_FRAME_1:
|
|
return "FRAME_1";
|
|
case GS_REG_FRAME_2:
|
|
return "FRAME_2";
|
|
case GS_REG_ZBUF_1:
|
|
return "ZBUF_1";
|
|
case GS_REG_ZBUF_2:
|
|
return "ZBUF_2";
|
|
case GS_REG_BITBLTBUF:
|
|
return "BITBLTBUF";
|
|
case GS_REG_TRXPOS:
|
|
return "TRXPOS";
|
|
case GS_REG_TRXREG:
|
|
return "TRXREG";
|
|
case GS_REG_TRXDIR:
|
|
return "TRXDIR";
|
|
case GS_REG_HWREG:
|
|
return "HWREG";
|
|
case GS_REG_SIGNAL:
|
|
return "SIGNAL";
|
|
case GS_REG_FINISH:
|
|
return "FINISH";
|
|
case GS_REG_LABEL:
|
|
return "LABEL";
|
|
default:
|
|
return "?";
|
|
}
|
|
}
|
|
|
|
void textHex32(const char *label, uint32_t value)
|
|
{
|
|
ImGui::Text("%s: 0x%08X", label, value);
|
|
}
|
|
|
|
void textHex64(const char *label, uint64_t value)
|
|
{
|
|
ImGui::Text("%s: 0x%016llX", label, static_cast<unsigned long long>(value));
|
|
}
|
|
|
|
uint32_t readGuestU32NoThrow(uint8_t *rdram, uint32_t addr)
|
|
{
|
|
if (!rdram || addr == 0u)
|
|
{
|
|
return 0u;
|
|
}
|
|
|
|
uint8_t *ptr = getMemPtr(rdram, addr);
|
|
if (!ptr)
|
|
{
|
|
return 0u;
|
|
}
|
|
|
|
uint32_t value = 0u;
|
|
std::memcpy(&value, ptr, sizeof(value));
|
|
return value;
|
|
}
|
|
|
|
std::string pathToString(const std::filesystem::path &path)
|
|
{
|
|
if (path.empty())
|
|
{
|
|
return std::string("<empty>");
|
|
}
|
|
return path.string();
|
|
}
|
|
|
|
void textPath(const char *label, const std::filesystem::path &path)
|
|
{
|
|
const std::string value = pathToString(path);
|
|
ImGui::Text("%s: %s", label, value.c_str());
|
|
}
|
|
|
|
std::filesystem::path makeDebugDumpPath(const char *name, uint32_t frameIndex)
|
|
{
|
|
static uint32_t s_dumpCounter = 0u;
|
|
std::filesystem::path dir = std::filesystem::current_path() / "ps2_debug_dumps";
|
|
std::filesystem::create_directories(dir);
|
|
|
|
char fileName[128]{};
|
|
std::snprintf(fileName,
|
|
sizeof(fileName),
|
|
"%s_frame_%08X_%04u.txt",
|
|
name,
|
|
frameIndex,
|
|
++s_dumpCounter);
|
|
return dir / fileName;
|
|
}
|
|
|
|
void dumpGsContext(std::ostream &out, const char *label, const GSContext &ctx)
|
|
{
|
|
out << label << "\n";
|
|
out << " FRAME fbp=" << ctx.frame.fbp
|
|
<< " fbw=" << static_cast<unsigned int>(ctx.frame.fbw)
|
|
<< " psm=0x" << std::hex << static_cast<unsigned int>(ctx.frame.psm)
|
|
<< " fbmsk=0x" << ctx.frame.fbmsk << std::dec << "\n";
|
|
out << " ZBUF zbp=" << ctx.zbuf.zbp
|
|
<< " psm=0x" << std::hex << static_cast<unsigned int>(ctx.zbuf.psm) << std::dec
|
|
<< " zmask=" << (ctx.zbuf.zmask ? 1u : 0u) << "\n";
|
|
out << " SCISSOR x=" << ctx.scissor.x0 << ".." << ctx.scissor.x1
|
|
<< " y=" << ctx.scissor.y0 << ".." << ctx.scissor.y1 << "\n";
|
|
out << " TEX0 tbp0=" << ctx.tex0.tbp0
|
|
<< " tbw=" << static_cast<unsigned int>(ctx.tex0.tbw)
|
|
<< " psm=0x" << std::hex << static_cast<unsigned int>(ctx.tex0.psm) << std::dec
|
|
<< " size=" << (1u << ctx.tex0.tw) << "x" << (1u << ctx.tex0.th)
|
|
<< " tcc=" << static_cast<unsigned int>(ctx.tex0.tcc)
|
|
<< " tfx=" << static_cast<unsigned int>(ctx.tex0.tfx)
|
|
<< " cbp=" << ctx.tex0.cbp
|
|
<< " cpsm=0x" << std::hex << static_cast<unsigned int>(ctx.tex0.cpsm) << std::dec
|
|
<< " csm=" << static_cast<unsigned int>(ctx.tex0.csm)
|
|
<< " csa=" << static_cast<unsigned int>(ctx.tex0.csa)
|
|
<< " cld=" << static_cast<unsigned int>(ctx.tex0.cld) << "\n";
|
|
out << " XYOFFSET ofx=" << ctx.xyoffset.ofx << " ofy=" << ctx.xyoffset.ofy << "\n";
|
|
out << " TEST 0x" << std::hex << std::setw(16) << std::setfill('0') << ctx.test << std::setfill(' ') << std::dec
|
|
<< " ATE=" << ((ctx.test >> 0) & 1ull)
|
|
<< " ATST=" << ((ctx.test >> 1) & 7ull)
|
|
<< " AREF=0x" << std::hex << ((ctx.test >> 4) & 0xffull) << std::dec
|
|
<< " AFAIL=" << ((ctx.test >> 12) & 3ull)
|
|
<< " DATE=" << ((ctx.test >> 14) & 1ull)
|
|
<< " DATM=" << ((ctx.test >> 15) & 1ull)
|
|
<< " ZTE=" << ((ctx.test >> 16) & 1ull)
|
|
<< " ZTST=" << ((ctx.test >> 17) & 3ull) << "\n";
|
|
out << " ALPHA 0x" << std::hex << std::setw(16) << std::setfill('0') << ctx.alpha << std::setfill(' ') << std::dec << "\n";
|
|
out << " TEX1 0x" << std::hex << std::setw(16) << std::setfill('0') << ctx.tex1 << std::setfill(' ') << std::dec << "\n";
|
|
out << " CLAMP 0x" << std::hex << std::setw(16) << std::setfill('0') << ctx.clamp << std::setfill(' ') << std::dec << "\n";
|
|
out << " FBA 0x" << std::hex << std::setw(16) << std::setfill('0') << ctx.fba << std::setfill(' ') << std::dec << "\n";
|
|
}
|
|
|
|
bool writeGsDebugDump(const std::filesystem::path &path,
|
|
const GSRegisters ®s,
|
|
const GSDebugSnapshot &gs,
|
|
const std::vector<GSDebugHistoryEntry> &history,
|
|
bool currentFrameOnly,
|
|
bool drawsOnly,
|
|
uint32_t latestFrame,
|
|
std::string &error)
|
|
{
|
|
try
|
|
{
|
|
std::filesystem::create_directories(path.parent_path());
|
|
std::ofstream out(path, std::ios::out | std::ios::trunc);
|
|
if (!out)
|
|
{
|
|
error = "failed to open output file";
|
|
return false;
|
|
}
|
|
|
|
out << "PS2 Runtime GS debug dump\n";
|
|
out << "path: " << path.string() << "\n";
|
|
out << "latestFrame: " << latestFrame << "\n";
|
|
out << "filters: currentFrameOnly=" << (currentFrameOnly ? 1u : 0u)
|
|
<< " drawsOnly=" << (drawsOnly ? 1u : 0u) << "\n\n";
|
|
|
|
out << "[GS private registers]\n";
|
|
out << std::hex << std::setfill('0');
|
|
out << "PMODE 0x" << std::setw(16) << regs.pmode << "\n";
|
|
out << "SMODE2 0x" << std::setw(16) << regs.smode2 << "\n";
|
|
out << "DISPFB1 0x" << std::setw(16) << regs.dispfb1 << "\n";
|
|
out << "DISPLAY1 0x" << std::setw(16) << regs.display1 << "\n";
|
|
out << "DISPFB2 0x" << std::setw(16) << regs.dispfb2 << "\n";
|
|
out << "DISPLAY2 0x" << std::setw(16) << regs.display2 << "\n";
|
|
out << "BGCOLOR 0x" << std::setw(16) << regs.bgcolor << "\n";
|
|
out << "CSR 0x" << std::setw(16) << regs.csr.load() << "\n";
|
|
out << "IMR 0x" << std::setw(16) << regs.imr << "\n";
|
|
out << "BUSDIR 0x" << std::setw(16) << regs.busdir << "\n";
|
|
out << "SIGLBLID 0x" << std::setw(16) << regs.siglblid << "\n\n";
|
|
out << std::dec << std::setfill(' ');
|
|
|
|
out << "[GS draw state]\n";
|
|
out << "PRIM type=" << primTypeName(gs.prim.type)
|
|
<< " iip=" << gs.prim.iip
|
|
<< " tme=" << gs.prim.tme
|
|
<< " fge=" << gs.prim.fge
|
|
<< " abe=" << gs.prim.abe
|
|
<< " aa1=" << gs.prim.aa1
|
|
<< " fst=" << gs.prim.fst
|
|
<< " ctxt=" << gs.prim.ctxt
|
|
<< " fix=" << gs.prim.fix << "\n";
|
|
out << "TEXA ta0=0x" << std::hex << static_cast<unsigned int>(gs.texa.ta0) << std::dec
|
|
<< " aem=" << (gs.texa.aem ? 1u : 0u)
|
|
<< " ta1=0x" << std::hex << static_cast<unsigned int>(gs.texa.ta1) << std::dec
|
|
<< " TEXCLUT cbw=" << gs.texclut.cbw
|
|
<< " cou=" << gs.texclut.cou
|
|
<< " cov=" << gs.texclut.cov << "\n";
|
|
out << "BITBLT sbp=" << gs.bitbltbuf.sbp
|
|
<< " sbw=" << gs.bitbltbuf.sbw
|
|
<< " spsm=0x" << std::hex << static_cast<unsigned int>(gs.bitbltbuf.spsm) << std::dec
|
|
<< " dbp=" << gs.bitbltbuf.dbp
|
|
<< " dbw=" << gs.bitbltbuf.dbw
|
|
<< " dpsm=0x" << std::hex << static_cast<unsigned int>(gs.bitbltbuf.dpsm) << std::dec << "\n";
|
|
out << "TRXPOS ss=(" << gs.trxpos.ssax << "," << gs.trxpos.ssay << ")"
|
|
<< " ds=(" << gs.trxpos.dsax << "," << gs.trxpos.dsay << ")"
|
|
<< " dir=" << gs.trxpos.dir
|
|
<< " TRXREG=" << gs.trxreg.rrw << "x" << gs.trxreg.rrh
|
|
<< " TRXDIR=" << gs.trxdir
|
|
<< " copied=" << gs.transferCopiedPixels << "/" << gs.transferTotalPixels
|
|
<< " at=(" << gs.transferX << "," << gs.transferY << ")\n";
|
|
out << "Host presentation has=" << (gs.hasHostPresentationFrame ? 1u : 0u)
|
|
<< " size=" << gs.hostPresentationWidth << "x" << gs.hostPresentationHeight
|
|
<< " displayFbp=" << gs.hostPresentationDisplayFbp
|
|
<< " sourceFbp=" << gs.hostPresentationSourceFbp
|
|
<< " preferred=" << (gs.hostPresentationUsedPreferred ? 1u : 0u)
|
|
<< " lastDisplayBaseBytes=0x" << std::hex << gs.lastDisplayBaseBytes << std::dec
|
|
<< " localToHostPending=" << gs.localToHostPendingBytes << "\n";
|
|
out << "Preferred source has=" << (gs.hasPreferredDisplaySource ? 1u : 0u)
|
|
<< " frame fbp=" << gs.preferredDisplaySourceFrame.fbp
|
|
<< " fbw=" << gs.preferredDisplaySourceFrame.fbw
|
|
<< " psm=0x" << std::hex << static_cast<unsigned int>(gs.preferredDisplaySourceFrame.psm) << std::dec
|
|
<< " destFbp=" << gs.preferredDisplayDestFbp << "\n\n";
|
|
|
|
out << "[GS context state]\n";
|
|
dumpGsContext(out, "Context 0", gs.ctx[0]);
|
|
dumpGsContext(out, "Context 1", gs.ctx[1]);
|
|
out << "\n";
|
|
|
|
out << "[GS history]\n";
|
|
out << "seq\tframe\ttick\tkind\tprim\tctx\tframe\tzbuf\ttest\talpha\ttex0\txy\tz\ta\tdetail\tpresent\n";
|
|
for (auto it = history.rbegin(); it != history.rend(); ++it)
|
|
{
|
|
const GSDebugHistoryEntry &row = *it;
|
|
if (currentFrameOnly && row.frameIndex != latestFrame)
|
|
{
|
|
continue;
|
|
}
|
|
if (drawsOnly && row.kind != GSDebugEventKind::Draw)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
out << row.seq << '\t'
|
|
<< row.frameIndex << '\t'
|
|
<< row.vsyncTick << '\t'
|
|
<< gsDebugEventKindName(row.kind) << '\t'
|
|
<< primTypeName(row.prim.type) << '\t'
|
|
<< (row.prim.ctxt ? 1u : 0u) << '\t'
|
|
<< "fbp=" << row.frame.fbp << "/fbw=" << static_cast<unsigned int>(row.frame.fbw) << "/psm=0x" << std::hex << static_cast<unsigned int>(row.frame.psm) << std::dec << '\t'
|
|
<< "zbp=" << row.zbuf.zbp << "/psm=0x" << std::hex << static_cast<unsigned int>(row.zbuf.psm) << std::dec << "/m=" << (row.zbuf.zmask ? 1u : 0u) << '\t'
|
|
<< "0x" << std::hex << row.test << std::dec << '\t'
|
|
<< "0x" << std::hex << row.alpha << std::dec << '\t'
|
|
<< "tbp=" << row.tex0.tbp0 << "/psm=0x" << std::hex << static_cast<unsigned int>(row.tex0.psm) << std::dec << "/size=" << (1u << row.tex0.tw) << "x" << (1u << row.tex0.th) << "/cbp=" << row.tex0.cbp << '\t';
|
|
|
|
if (row.kind == GSDebugEventKind::Draw)
|
|
{
|
|
out << row.xMin << ".." << row.xMax << "/" << row.yMin << ".." << row.yMax << '\t'
|
|
<< row.zMin << ".." << row.zMax << '\t'
|
|
<< static_cast<unsigned int>(row.aMin) << ".." << static_cast<unsigned int>(row.aMax) << '\t';
|
|
}
|
|
else
|
|
{
|
|
out << "-\t-\t-\t";
|
|
}
|
|
|
|
if (row.kind == GSDebugEventKind::GifTag)
|
|
{
|
|
out << gifFormatName(row.gifFlg) << " nloop=" << row.gifNloop << " nreg=" << row.gifNreg << " size=" << row.gifSizeBytes;
|
|
}
|
|
else if (row.kind == GSDebugEventKind::Register)
|
|
{
|
|
out << gsRegName(row.reg) << "(0x" << std::hex << static_cast<unsigned int>(row.reg) << ")=0x" << row.regValue << std::dec;
|
|
}
|
|
else if (row.kind == GSDebugEventKind::Transfer)
|
|
{
|
|
out << "TRXDIR=" << row.trxdir << " " << row.trxreg.rrw << "x" << row.trxreg.rrh
|
|
<< " pix=" << row.transferPixels << " sbp=" << row.bitbltbuf.sbp << " dbp=" << row.bitbltbuf.dbp;
|
|
}
|
|
else
|
|
{
|
|
out << "-";
|
|
}
|
|
out << '\t';
|
|
if (row.kind == GSDebugEventKind::Present)
|
|
{
|
|
out << row.width << "x" << row.height
|
|
<< " display=" << row.displayFbp
|
|
<< " source=" << row.sourceFbp
|
|
<< " pref=" << (row.usedPreferred ? 1u : 0u);
|
|
}
|
|
else
|
|
{
|
|
out << "-";
|
|
}
|
|
out << '\n';
|
|
}
|
|
|
|
return true;
|
|
}
|
|
catch (const std::exception &ex)
|
|
{
|
|
error = ex.what();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void drawGsContext(const char *label, const GSContext &ctx)
|
|
{
|
|
if (!ImGui::TreeNode(label))
|
|
{
|
|
return;
|
|
}
|
|
|
|
ImGui::Text("FRAME fbp=%u fbw=%u psm=0x%02X fbmsk=0x%08X",
|
|
ctx.frame.fbp,
|
|
ctx.frame.fbw,
|
|
static_cast<unsigned int>(ctx.frame.psm),
|
|
ctx.frame.fbmsk);
|
|
ImGui::Text("ZBUF zbp=%u psm=0x%02X zmask=%u",
|
|
ctx.zbuf.zbp,
|
|
static_cast<unsigned int>(ctx.zbuf.psm),
|
|
ctx.zbuf.zmask ? 1u : 0u);
|
|
ImGui::Text("SCISSOR x=%u..%u y=%u..%u",
|
|
ctx.scissor.x0,
|
|
ctx.scissor.x1,
|
|
ctx.scissor.y0,
|
|
ctx.scissor.y1);
|
|
ImGui::Text("TEX0 tbp0=%u tbw=%u psm=0x%02X tw=%u th=%u tcc=%u tfx=%u cbp=%u cpsm=0x%02X csm=%u csa=%u cld=%u",
|
|
ctx.tex0.tbp0,
|
|
static_cast<unsigned int>(ctx.tex0.tbw),
|
|
static_cast<unsigned int>(ctx.tex0.psm),
|
|
static_cast<unsigned int>(ctx.tex0.tw),
|
|
static_cast<unsigned int>(ctx.tex0.th),
|
|
static_cast<unsigned int>(ctx.tex0.tcc),
|
|
static_cast<unsigned int>(ctx.tex0.tfx),
|
|
ctx.tex0.cbp,
|
|
static_cast<unsigned int>(ctx.tex0.cpsm),
|
|
static_cast<unsigned int>(ctx.tex0.csm),
|
|
static_cast<unsigned int>(ctx.tex0.csa),
|
|
static_cast<unsigned int>(ctx.tex0.cld));
|
|
ImGui::Text("XYOFFSET ofx=%u ofy=%u", ctx.xyoffset.ofx, ctx.xyoffset.ofy);
|
|
textHex64("TEST", ctx.test);
|
|
ImGui::SameLine();
|
|
ImGui::Text("ATE=%llu ATST=%llu AREF=0x%02llX AFAIL=%llu DATE=%llu DATM=%llu ZTE=%llu ZTST=%llu",
|
|
(ctx.test >> 0) & 1ull,
|
|
(ctx.test >> 1) & 7ull,
|
|
(ctx.test >> 4) & 0xffull,
|
|
(ctx.test >> 12) & 3ull,
|
|
(ctx.test >> 14) & 1ull,
|
|
(ctx.test >> 15) & 1ull,
|
|
(ctx.test >> 16) & 1ull,
|
|
(ctx.test >> 17) & 3ull);
|
|
textHex64("ALPHA", ctx.alpha);
|
|
textHex64("TEX1", ctx.tex1);
|
|
textHex64("CLAMP", ctx.clamp);
|
|
textHex64("FBA", ctx.fba);
|
|
ImGui::TreePop();
|
|
}
|
|
|
|
void drawHexDump(uint8_t *rdram, uint32_t startAddr, uint32_t bytes)
|
|
{
|
|
bytes = std::clamp<uint32_t>(bytes, 16u, 0x1000u);
|
|
const uint32_t alignedStart = startAddr & ~0x0fu;
|
|
for (uint32_t off = 0u; off < bytes; off += 16u)
|
|
{
|
|
const uint32_t addr = alignedStart + off;
|
|
uint8_t *ptr = getMemPtr(rdram, addr);
|
|
if (!ptr)
|
|
{
|
|
ImGui::Text("%08X: <invalid>", addr);
|
|
continue;
|
|
}
|
|
|
|
char hex[16 * 3 + 1]{};
|
|
char ascii[17]{};
|
|
for (uint32_t i = 0u; i < 16u; ++i)
|
|
{
|
|
const uint8_t b = ptr[i];
|
|
std::snprintf(hex + i * 3, sizeof(hex) - i * 3, "%02X ", b);
|
|
ascii[i] = (b >= 32u && b < 127u) ? static_cast<char>(b) : '.';
|
|
}
|
|
ImGui::Text("%08X: %s |%s|", addr, hex, ascii);
|
|
}
|
|
}
|
|
|
|
void drawCpuTab(PS2Runtime &runtime, bool showRegisters)
|
|
{
|
|
const uint32_t pc = runtime.m_debugPc.load(std::memory_order_relaxed);
|
|
const uint32_t ra = runtime.m_debugRa.load(std::memory_order_relaxed);
|
|
const uint32_t sp = runtime.m_debugSp.load(std::memory_order_relaxed);
|
|
const uint32_t gp = runtime.m_debugGp.load(std::memory_order_relaxed);
|
|
|
|
ImGui::Text("Runtime: %s", runtime.isStopRequested() ? "stop requested" : "running");
|
|
ImGui::Text("EE executor: %s", runtime.eeScheduler().isExecutingGuest() ? "guest" : "scheduler");
|
|
ImGui::Separator();
|
|
textHex32("PC", pc);
|
|
ImGui::SameLine();
|
|
textHex32("RA", ra);
|
|
textHex32("SP", sp);
|
|
ImGui::SameLine();
|
|
textHex32("GP", gp);
|
|
|
|
if (ImGui::Button("Copy PC/RA/SP/GP"))
|
|
{
|
|
char buf[256]{};
|
|
std::snprintf(buf, sizeof(buf), "pc=0x%08X ra=0x%08X sp=0x%08X gp=0x%08X", pc, ra, sp, gp);
|
|
ImGui::SetClipboardText(buf);
|
|
}
|
|
|
|
ImGui::TextDisabled("full context snapshot is best-effort while guest is running");
|
|
|
|
if (showRegisters)
|
|
{
|
|
const R5900Context &cpu = runtime.cpu();
|
|
if (ImGui::BeginTable("gpr", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable))
|
|
{
|
|
ImGui::TableSetupColumn("Reg");
|
|
ImGui::TableSetupColumn("Lo32");
|
|
ImGui::TableSetupColumn("Hi32[1]");
|
|
ImGui::TableSetupColumn("Hi64[2:3]");
|
|
ImGui::TableHeadersRow();
|
|
for (int i = 0; i < 32; ++i)
|
|
{
|
|
const uint32_t w0 = static_cast<uint32_t>(_mm_extract_epi32(cpu.r[i], 0));
|
|
const uint32_t w1 = static_cast<uint32_t>(_mm_extract_epi32(cpu.r[i], 1));
|
|
const uint32_t w2 = static_cast<uint32_t>(_mm_extract_epi32(cpu.r[i], 2));
|
|
const uint32_t w3 = static_cast<uint32_t>(_mm_extract_epi32(cpu.r[i], 3));
|
|
ImGui::TableNextRow();
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("r%d", i);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", w0);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", w1);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X_%08X", w3, w2);
|
|
}
|
|
ImGui::EndTable();
|
|
}
|
|
|
|
ImGui::SeparatorText("COP0");
|
|
ImGui::Text("Status=0x%08X Cause=0x%08X EPC=0x%08X Count=0x%08X Compare=0x%08X BadVAddr=0x%08X",
|
|
cpu.cop0_status,
|
|
cpu.cop0_cause,
|
|
cpu.cop0_epc,
|
|
cpu.cop0_count,
|
|
cpu.cop0_compare,
|
|
cpu.cop0_badvaddr);
|
|
}
|
|
}
|
|
|
|
void drawThreadsTab(PS2Runtime &runtime)
|
|
{
|
|
const EeKernelSnapshot snapshot = runtime.eeScheduler().snapshot();
|
|
ImGui::Text("Threads: %zu running=%d", snapshot.threads.size(), snapshot.runningThreadId);
|
|
ImGui::Text("EE cycle: %llu slice end: %llu next event: %llu",
|
|
static_cast<unsigned long long>(snapshot.eeCycle),
|
|
static_cast<unsigned long long>(snapshot.sliceEndCycle),
|
|
static_cast<unsigned long long>(snapshot.nextEventCycle));
|
|
if (ImGui::BeginTable("threads", 11, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY, ImVec2(0, 320)))
|
|
{
|
|
ImGui::TableSetupColumn("ID");
|
|
ImGui::TableSetupColumn("Status");
|
|
ImGui::TableSetupColumn("Wait");
|
|
ImGui::TableSetupColumn("WaitId");
|
|
ImGui::TableSetupColumn("PC");
|
|
ImGui::TableSetupColumn("Entry");
|
|
ImGui::TableSetupColumn("Stack");
|
|
ImGui::TableSetupColumn("GP");
|
|
ImGui::TableSetupColumn("Prio");
|
|
ImGui::TableSetupColumn("Wake");
|
|
ImGui::TableSetupColumn("Susp");
|
|
ImGui::TableHeadersRow();
|
|
for (const EeThreadSnapshot &row : snapshot.threads)
|
|
{
|
|
ImGui::TableNextRow();
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%d", row.id);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%s", threadStatusName(row.status));
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%s", waitTypeName(row.waitReason));
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%d", row.waitId);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", row.pc);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", row.entry);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X/%u", row.stack, row.stackSize);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", row.gp);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%d/%d", row.currentPriority, row.initialPriority);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%u", row.wakeupCount);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%d", row.suspendCount);
|
|
}
|
|
ImGui::EndTable();
|
|
}
|
|
}
|
|
|
|
void drawKernelTab(PS2Runtime &runtime)
|
|
{
|
|
const EeKernelSnapshot snapshot = runtime.eeScheduler().snapshot();
|
|
ImGui::SeparatorText("Semaphores");
|
|
{
|
|
if (ImGui::BeginTable("semas", 7, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable))
|
|
{
|
|
ImGui::TableSetupColumn("ID");
|
|
ImGui::TableSetupColumn("Count");
|
|
ImGui::TableSetupColumn("Max");
|
|
ImGui::TableSetupColumn("Init");
|
|
ImGui::TableSetupColumn("Waiters");
|
|
ImGui::TableSetupColumn("Attr");
|
|
ImGui::TableSetupColumn("Deleted");
|
|
ImGui::TableHeadersRow();
|
|
for (const EeSemaphoreSnapshot &sema : snapshot.semaphores)
|
|
{
|
|
ImGui::TableNextRow();
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%d", sema.id);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%d", sema.count);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%d", sema.maxCount);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("-");
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%u", sema.waiters);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("-");
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0");
|
|
}
|
|
ImGui::EndTable();
|
|
}
|
|
}
|
|
|
|
ImGui::SeparatorText("Event flags");
|
|
{
|
|
if (ImGui::BeginTable("evf", 6, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable))
|
|
{
|
|
ImGui::TableSetupColumn("ID");
|
|
ImGui::TableSetupColumn("Bits");
|
|
ImGui::TableSetupColumn("Init");
|
|
ImGui::TableSetupColumn("Waiters");
|
|
ImGui::TableSetupColumn("Attr");
|
|
ImGui::TableSetupColumn("Deleted");
|
|
ImGui::TableHeadersRow();
|
|
for (const EeEventFlagSnapshot &evf : snapshot.eventFlags)
|
|
{
|
|
ImGui::TableNextRow();
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%d", evf.id);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", evf.bits);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", evf.initBits);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%u", evf.waiters);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", evf.attr);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0");
|
|
}
|
|
ImGui::EndTable();
|
|
}
|
|
}
|
|
}
|
|
|
|
void drawIopTab(PS2Runtime &runtime)
|
|
{
|
|
uint8_t *rdram = runtime.memory().getRDRAM();
|
|
const ps2x::iop::DebugSnapshot iopSnapshot = runtime.iopDebugSnapshot();
|
|
|
|
struct ModuleRow
|
|
{
|
|
int32_t id = 0;
|
|
std::string path;
|
|
std::string pathKey;
|
|
uint32_t refCount = 0;
|
|
bool loaded = false;
|
|
};
|
|
|
|
std::vector<ModuleRow> modules;
|
|
uint32_t moduleLogCount = 0;
|
|
int32_t nextModuleId = 0;
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_sif_module_mutex);
|
|
modules.reserve(g_sif_modules_by_id.size());
|
|
for (const auto &[id, record] : g_sif_modules_by_id)
|
|
{
|
|
ModuleRow row{};
|
|
row.id = id;
|
|
row.path = record.path;
|
|
row.pathKey = record.pathKey;
|
|
row.refCount = record.refCount;
|
|
row.loaded = record.loaded;
|
|
modules.push_back(std::move(row));
|
|
}
|
|
moduleLogCount = g_sif_module_log_count;
|
|
nextModuleId = g_next_sif_module_id;
|
|
}
|
|
std::sort(modules.begin(), modules.end(), [](const ModuleRow &a, const ModuleRow &b)
|
|
{ return a.id < b.id; });
|
|
|
|
struct RpcServerRow
|
|
{
|
|
uint32_t sid = 0;
|
|
uint32_t sdPtr = 0;
|
|
bool readable = false;
|
|
t_SifRpcServerData sd{};
|
|
};
|
|
|
|
struct RpcClientRow
|
|
{
|
|
uint32_t clientPtr = 0;
|
|
bool busy = false;
|
|
uint32_t lastRpc = 0;
|
|
uint32_t sid = 0;
|
|
bool readable = false;
|
|
t_SifRpcClientData cd{};
|
|
};
|
|
|
|
std::vector<RpcServerRow> servers;
|
|
std::vector<RpcClientRow> clients;
|
|
bool rpcInitialized = false;
|
|
uint32_t rpcNextId = 0;
|
|
uint32_t rpcPacketIndex = 0;
|
|
uint32_t rpcServerIndex = 0;
|
|
uint32_t rpcActiveQueue = 0;
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_rpc_mutex);
|
|
rpcInitialized = g_rpc_initialized;
|
|
rpcNextId = g_rpc_next_id;
|
|
rpcPacketIndex = g_rpc_packet_index;
|
|
rpcServerIndex = g_rpc_server_index;
|
|
rpcActiveQueue = g_rpc_active_queue;
|
|
servers.reserve(g_rpc_servers.size());
|
|
for (const auto &[sid, state] : g_rpc_servers)
|
|
{
|
|
RpcServerRow row{};
|
|
row.sid = sid;
|
|
row.sdPtr = state.sd_ptr;
|
|
row.readable = readGuestObjectNoThrow(rdram, state.sd_ptr, row.sd);
|
|
servers.push_back(row);
|
|
}
|
|
|
|
clients.reserve(g_rpc_clients.size());
|
|
for (const auto &[clientPtr, state] : g_rpc_clients)
|
|
{
|
|
RpcClientRow row{};
|
|
row.clientPtr = clientPtr;
|
|
row.busy = state.busy;
|
|
row.lastRpc = state.last_rpc;
|
|
row.sid = state.sid;
|
|
row.readable = readGuestObjectNoThrow(rdram, clientPtr, row.cd);
|
|
clients.push_back(row);
|
|
}
|
|
}
|
|
std::sort(servers.begin(), servers.end(), [](const RpcServerRow &a, const RpcServerRow &b)
|
|
{ return a.sid < b.sid; });
|
|
std::sort(clients.begin(), clients.end(), [](const RpcClientRow &a, const RpcClientRow &b)
|
|
{ return a.clientPtr < b.clientPtr; });
|
|
|
|
auto hasServer = [&](uint32_t sid)
|
|
{
|
|
return std::any_of(servers.begin(), servers.end(), [sid](const RpcServerRow &row)
|
|
{ return row.sid == sid; });
|
|
};
|
|
|
|
ImGui::SeparatorText("IOP / SIF modules");
|
|
ImGui::Text("Tracked modules: %zu loaded=%zu nextId=%d moduleLogs=%u",
|
|
modules.size(),
|
|
static_cast<size_t>(std::count_if(modules.begin(), modules.end(), [](const ModuleRow &row)
|
|
{ return row.loaded; })),
|
|
nextModuleId,
|
|
moduleLogCount);
|
|
ImGui::TextDisabled("This table is fed by SifLoadModule/SifLoadModuleBuffer/SifStopModule tracking.");
|
|
|
|
if (ImGui::BeginTable("iop_modules", 5, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY, ImVec2(0, 180)))
|
|
{
|
|
ImGui::TableSetupColumn("ID");
|
|
ImGui::TableSetupColumn("Loaded");
|
|
ImGui::TableSetupColumn("Refs");
|
|
ImGui::TableSetupColumn("Key");
|
|
ImGui::TableSetupColumn("Path");
|
|
ImGui::TableHeadersRow();
|
|
for (const ModuleRow &row : modules)
|
|
{
|
|
ImGui::TableNextRow();
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%d", row.id);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%s", row.loaded ? "yes" : "no");
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%u", row.refCount);
|
|
ImGui::TableNextColumn();
|
|
ImGui::TextUnformatted(row.pathKey.c_str());
|
|
ImGui::TableNextColumn();
|
|
ImGui::TextUnformatted(row.path.c_str());
|
|
}
|
|
ImGui::EndTable();
|
|
}
|
|
|
|
ImGui::SeparatorText("ps2xIOP HLE services");
|
|
ImGui::Text("Profile: %s provider: %s",
|
|
iopSnapshot.activeProfile.empty()
|
|
? "<core only>"
|
|
: iopSnapshot.activeProfile.c_str(),
|
|
iopSnapshot.activeProvider.empty()
|
|
? "builtin"
|
|
: iopSnapshot.activeProvider.c_str());
|
|
|
|
if (ImGui::BeginTable("iop_hle_services",
|
|
5,
|
|
ImGuiTableFlags_Borders |
|
|
ImGuiTableFlags_RowBg |
|
|
ImGuiTableFlags_Resizable))
|
|
{
|
|
ImGui::TableSetupColumn("Service");
|
|
ImGui::TableSetupColumn("Layer");
|
|
ImGui::TableSetupColumn("SID");
|
|
ImGui::TableSetupColumn("EE server");
|
|
ImGui::TableSetupColumn("Metrics");
|
|
ImGui::TableHeadersRow();
|
|
for (const ps2x::iop::DebugService &service : iopSnapshot.services)
|
|
{
|
|
if (service.sids.empty())
|
|
{
|
|
ImGui::TableNextRow();
|
|
ImGui::TableNextColumn();
|
|
ImGui::TextUnformatted(service.name.c_str());
|
|
ImGui::TableNextColumn();
|
|
ImGui::TextUnformatted(service.profileSpecific ? "profile" : "core");
|
|
ImGui::TableNextColumn();
|
|
ImGui::TextUnformatted("-");
|
|
ImGui::TableNextColumn();
|
|
ImGui::TextUnformatted("-");
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%zu", service.metrics.size());
|
|
continue;
|
|
}
|
|
|
|
for (const uint32_t sid : service.sids)
|
|
{
|
|
ImGui::TableNextRow();
|
|
ImGui::TableNextColumn();
|
|
ImGui::TextUnformatted(service.name.c_str());
|
|
ImGui::TableNextColumn();
|
|
ImGui::TextUnformatted(service.profileSpecific ? "profile" : "core");
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", sid);
|
|
ImGui::TableNextColumn();
|
|
ImGui::TextUnformatted(hasServer(sid) ? "yes" : "no");
|
|
ImGui::TableNextColumn();
|
|
if (service.metrics.empty())
|
|
{
|
|
ImGui::TextUnformatted("-");
|
|
}
|
|
else
|
|
{
|
|
std::string metrics;
|
|
for (const ps2x::iop::DebugMetric &metric : service.metrics)
|
|
{
|
|
if (!metrics.empty())
|
|
{
|
|
metrics += ", ";
|
|
}
|
|
std::ostringstream value;
|
|
value << metric.name << "=";
|
|
if (metric.hexadecimal)
|
|
{
|
|
value << "0x" << std::hex << metric.value;
|
|
}
|
|
else
|
|
{
|
|
value << std::dec << metric.value;
|
|
}
|
|
metrics += value.str();
|
|
}
|
|
ImGui::TextUnformatted(metrics.c_str());
|
|
}
|
|
}
|
|
}
|
|
ImGui::EndTable();
|
|
}
|
|
|
|
for (const std::string &diagnostic : iopSnapshot.diagnostics)
|
|
{
|
|
ImGui::TextDisabled("%s", diagnostic.c_str());
|
|
}
|
|
|
|
ImGui::SeparatorText("SIF RPC state");
|
|
ImGui::Text("initialized=%u servers=%zu clients=%zu nextId=%u packetIndex=%u serverIndex=%u activeQueue=0x%08X",
|
|
rpcInitialized ? 1u : 0u,
|
|
servers.size(),
|
|
clients.size(),
|
|
rpcNextId,
|
|
rpcPacketIndex,
|
|
rpcServerIndex,
|
|
rpcActiveQueue);
|
|
|
|
if (ImGui::BeginTable("rpc_servers", 10, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY, ImVec2(0, 180)))
|
|
{
|
|
ImGui::TableSetupColumn("SID");
|
|
ImGui::TableSetupColumn("Name");
|
|
ImGui::TableSetupColumn("sd*");
|
|
ImGui::TableSetupColumn("func");
|
|
ImGui::TableSetupColumn("buf");
|
|
ImGui::TableSetupColumn("size");
|
|
ImGui::TableSetupColumn("cfunc");
|
|
ImGui::TableSetupColumn("queue");
|
|
ImGui::TableSetupColumn("recv");
|
|
ImGui::TableSetupColumn("rpc#");
|
|
ImGui::TableHeadersRow();
|
|
for (const RpcServerRow &row : servers)
|
|
{
|
|
ImGui::TableNextRow();
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", row.sid);
|
|
ImGui::TableNextColumn();
|
|
const std::string serviceName = iopRpcSidName(iopSnapshot, row.sid);
|
|
ImGui::TextUnformatted(serviceName.c_str());
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", row.sdPtr);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", row.readable ? row.sd.func : 0u);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", row.readable ? row.sd.buf : 0u);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%d", row.readable ? row.sd.size : 0);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", row.readable ? row.sd.cfunc : 0u);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", row.readable ? row.sd.base : 0u);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X/%d", row.readable ? row.sd.recvbuf : 0u, row.readable ? row.sd.rsize : 0);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%d", row.readable ? row.sd.rpc_number : 0);
|
|
}
|
|
ImGui::EndTable();
|
|
}
|
|
|
|
if (ImGui::BeginTable("rpc_clients", 9, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY, ImVec2(0, 160)))
|
|
{
|
|
ImGui::TableSetupColumn("client*");
|
|
ImGui::TableSetupColumn("SID");
|
|
ImGui::TableSetupColumn("Name");
|
|
ImGui::TableSetupColumn("Busy");
|
|
ImGui::TableSetupColumn("LastRpc");
|
|
ImGui::TableSetupColumn("cmd");
|
|
ImGui::TableSetupColumn("buf");
|
|
ImGui::TableSetupColumn("cbuf");
|
|
ImGui::TableSetupColumn("server*");
|
|
ImGui::TableHeadersRow();
|
|
for (const RpcClientRow &row : clients)
|
|
{
|
|
ImGui::TableNextRow();
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", row.clientPtr);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", row.sid);
|
|
ImGui::TableNextColumn();
|
|
const std::string serviceName = iopRpcSidName(iopSnapshot, row.sid);
|
|
ImGui::TextUnformatted(serviceName.c_str());
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%u", row.busy ? 1u : 0u);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", row.lastRpc);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", row.readable ? row.cd.command : 0u);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", row.readable ? row.cd.buf : 0u);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", row.readable ? row.cd.cbuf : 0u);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", row.readable ? row.cd.server : 0u);
|
|
}
|
|
ImGui::EndTable();
|
|
}
|
|
}
|
|
|
|
void drawRpcHistoryTab(PS2Runtime &runtime)
|
|
{
|
|
const ps2x::iop::DebugSnapshot iopSnapshot = runtime.iopDebugSnapshot();
|
|
std::vector<SifRpcDebugEvent> events;
|
|
uint64_t nextSeq = 0;
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_rpc_mutex);
|
|
nextSeq = g_sif_rpc_debug_next_seq;
|
|
events.reserve(kSifRpcDebugHistoryCount);
|
|
for (size_t i = 0; i < kSifRpcDebugHistoryCount; ++i)
|
|
{
|
|
const SifRpcDebugEvent &event = g_sif_rpc_debug_history[i];
|
|
if (event.seq != 0u)
|
|
{
|
|
events.push_back(event);
|
|
}
|
|
}
|
|
}
|
|
std::sort(events.begin(), events.end(), [](const SifRpcDebugEvent &a, const SifRpcDebugEvent &b)
|
|
{ return a.seq > b.seq; });
|
|
|
|
ImGui::Text("RPC events: %zu captured, nextSeq=%llu", events.size(), static_cast<unsigned long long>(nextSeq));
|
|
ImGui::TextDisabled("Ring buffer fed by SifInitRpc/SifBindRpc/SifRegisterRpc/SifCallRpc.");
|
|
|
|
static bool onlyCalls = false;
|
|
static bool hideBindNoise = false;
|
|
ImGui::Checkbox("Only CallRpc", &onlyCalls);
|
|
ImGui::SameLine();
|
|
ImGui::Checkbox("Hide bind/register/init", &hideBindNoise);
|
|
|
|
ImGui::SeparatorText("IOP/RPC tracer");
|
|
ImGui::TextDisabled("Unhandled rows are RPC calls that no HLE service or EE server callback consumed; the runtime used copy/zero fallback.");
|
|
static bool tracerOnlyUnhandled = true;
|
|
ImGui::Checkbox("Only unhandled", &tracerOnlyUnhandled);
|
|
if (ImGui::BeginTable("iop_rpc_tracer", 9,
|
|
ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable |
|
|
ImGuiTableFlags_ScrollY | ImGuiTableFlags_SizingStretchProp,
|
|
ImVec2(0, 220)))
|
|
{
|
|
ImGui::TableSetupColumn("Seq");
|
|
ImGui::TableSetupColumn("SID");
|
|
ImGui::TableSetupColumn("Name");
|
|
ImGui::TableSetupColumn("Rpc#");
|
|
ImGui::TableSetupColumn("PC");
|
|
ImGui::TableSetupColumn("Send");
|
|
ImGui::TableSetupColumn("Recv");
|
|
ImGui::TableSetupColumn("Send[0..15]");
|
|
ImGui::TableSetupColumn("Recv[0..15]");
|
|
ImGui::TableHeadersRow();
|
|
|
|
for (const SifRpcDebugEvent &event : events)
|
|
{
|
|
const char *op = event.op ? event.op : "";
|
|
if (std::strcmp(op, "CallRpc") != 0)
|
|
{
|
|
continue;
|
|
}
|
|
const bool unhandled = (event.flags & kSifRpcDebugFlagUnhandled) != 0u;
|
|
if (tracerOnlyUnhandled && !unhandled)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const std::string sendPreview = rpcPreviewBytes(event.sendPreview, event.sendPreviewSize);
|
|
const std::string recvPreview = rpcPreviewBytes(event.recvPreview, event.recvPreviewSize);
|
|
|
|
ImGui::TableNextRow();
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%llu", static_cast<unsigned long long>(event.seq));
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", event.sid);
|
|
ImGui::TableNextColumn();
|
|
const std::string serviceName = iopRpcSidName(iopSnapshot, event.sid);
|
|
ImGui::TextUnformatted(serviceName.c_str());
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", event.rpcNum);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", event.pc);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X/%u", event.sendBuf, event.sendSize);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X/%u", event.recvBuf, event.recvSize);
|
|
ImGui::TableNextColumn();
|
|
ImGui::TextUnformatted(sendPreview.c_str());
|
|
ImGui::TableNextColumn();
|
|
ImGui::TextUnformatted(recvPreview.c_str());
|
|
}
|
|
ImGui::EndTable();
|
|
}
|
|
|
|
if (ImGui::BeginTable("rpc_history", 20,
|
|
ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable |
|
|
ImGuiTableFlags_ScrollY | ImGuiTableFlags_SizingStretchProp,
|
|
ImVec2(0, 360)))
|
|
{
|
|
ImGui::TableSetupColumn("Seq");
|
|
ImGui::TableSetupColumn("Op");
|
|
ImGui::TableSetupColumn("TID");
|
|
ImGui::TableSetupColumn("PC");
|
|
ImGui::TableSetupColumn("RA");
|
|
ImGui::TableSetupColumn("SID");
|
|
ImGui::TableSetupColumn("Name");
|
|
ImGui::TableSetupColumn("Rpc#");
|
|
ImGui::TableSetupColumn("Client");
|
|
ImGui::TableSetupColumn("Server");
|
|
ImGui::TableSetupColumn("Send");
|
|
ImGui::TableSetupColumn("SSize");
|
|
ImGui::TableSetupColumn("Recv");
|
|
ImGui::TableSetupColumn("RSize");
|
|
ImGui::TableSetupColumn("ResultPtr");
|
|
ImGui::TableSetupColumn("Mode");
|
|
ImGui::TableSetupColumn("EndFunc");
|
|
ImGui::TableSetupColumn("EndParam");
|
|
ImGui::TableSetupColumn("Sema");
|
|
ImGui::TableSetupColumn("Flags");
|
|
ImGui::TableHeadersRow();
|
|
|
|
for (const SifRpcDebugEvent &event : events)
|
|
{
|
|
const char *op = event.op ? event.op : "";
|
|
if (onlyCalls && std::strcmp(op, "CallRpc") != 0)
|
|
{
|
|
continue;
|
|
}
|
|
if (hideBindNoise && std::strcmp(op, "CallRpc") != 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const std::string flags = rpcDebugFlags(event.flags);
|
|
ImGui::TableNextRow();
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%llu", static_cast<unsigned long long>(event.seq));
|
|
ImGui::TableNextColumn();
|
|
ImGui::TextUnformatted(op);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%u", event.threadId);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", event.pc);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", event.ra);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", event.sid);
|
|
ImGui::TableNextColumn();
|
|
const std::string serviceName = iopRpcSidName(iopSnapshot, event.sid);
|
|
ImGui::TextUnformatted(serviceName.c_str());
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", event.rpcNum);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", event.clientPtr);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", event.serverPtr);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", event.sendBuf);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%u", event.sendSize);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", event.recvBuf);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%u", event.recvSize);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", event.resultPtr);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", event.mode);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", event.endFunc);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", event.endParam);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", event.semaId);
|
|
ImGui::TableNextColumn();
|
|
ImGui::TextUnformatted(flags.c_str());
|
|
}
|
|
ImGui::EndTable();
|
|
}
|
|
}
|
|
|
|
void drawPadBytes(const uint8_t *data, size_t size)
|
|
{
|
|
char bytes[3 * 32 + 1]{};
|
|
const size_t count = std::min<size_t>(size, 32u);
|
|
for (size_t i = 0; i < count; ++i)
|
|
{
|
|
std::snprintf(bytes + (i * 3), sizeof(bytes) - (i * 3), "%02X ", data[i]);
|
|
}
|
|
ImGui::TextUnformatted(bytes);
|
|
}
|
|
|
|
void drawPadTab()
|
|
{
|
|
const ps2_stubs::PadDebugSnapshot snapshot = ps2_stubs::getPadDebugSnapshot();
|
|
|
|
ImGui::SeparatorText("PAD global state");
|
|
ImGui::Text("override=%u readLogCount=%d", snapshot.overrideEnabled ? 1u : 0u, snapshot.readLogCount);
|
|
ImGui::SameLine();
|
|
ImGui::Text("override buttons=0x%04X lx=%u ly=%u rx=%u ry=%u",
|
|
snapshot.overrideButtons,
|
|
snapshot.overrideLx,
|
|
snapshot.overrideLy,
|
|
snapshot.overrideRx,
|
|
snapshot.overrideRy);
|
|
ImGui::Text("override pressed: %s", pressedPadButtons(snapshot.overrideButtons).c_str());
|
|
|
|
if (ImGui::BeginTable("pad_ports", 15, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable))
|
|
{
|
|
ImGui::TableSetupColumn("Port");
|
|
ImGui::TableSetupColumn("Slot");
|
|
ImGui::TableSetupColumn("Open");
|
|
ImGui::TableSetupColumn("Mode");
|
|
ImGui::TableSetupColumn("Pressure");
|
|
ImGui::TableSetupColumn("Req");
|
|
ImGui::TableSetupColumn("DMA");
|
|
ImGui::TableSetupColumn("Read#");
|
|
ImGui::TableSetupColumn("ReadAddr");
|
|
ImGui::TableSetupColumn("Source");
|
|
ImGui::TableSetupColumn("Buttons raw");
|
|
ImGui::TableSetupColumn("Pressed");
|
|
ImGui::TableSetupColumn("LX/LY");
|
|
ImGui::TableSetupColumn("RX/RY");
|
|
ImGui::TableSetupColumn("Mask");
|
|
ImGui::TableHeadersRow();
|
|
|
|
for (size_t port = 0; port < ps2_stubs::kPadDebugPortCount; ++port)
|
|
{
|
|
for (size_t slot = 0; slot < ps2_stubs::kPadDebugSlotCount; ++slot)
|
|
{
|
|
const ps2_stubs::PadDebugPortSnapshot &row = snapshot.ports[port][slot];
|
|
ImGui::TableNextRow();
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%zu", port);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%zu", slot);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%u", row.open ? 1u : 0u);
|
|
ImGui::TableNextColumn();
|
|
ImGui::TextUnformatted(row.analogMode ? "analog" : "digital");
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%u", row.pressureEnabled ? 1u : 0u);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", row.reqState);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", row.dmaAddr);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%u", row.readCount);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", row.lastReadDataAddr);
|
|
ImGui::TableNextColumn();
|
|
ImGui::TextUnformatted(row.lastUsedOverride ? "override" : (row.lastUsedBackend ? "backend" : "fallback"));
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%04X", row.lastButtons);
|
|
ImGui::TableNextColumn();
|
|
ImGui::TextUnformatted(pressedPadButtons(row.lastButtons).c_str());
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%u/%u", row.lx, row.ly);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%u/%u", row.rx, row.ry);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%04X", row.buttonMask);
|
|
}
|
|
}
|
|
ImGui::EndTable();
|
|
}
|
|
|
|
ImGui::SeparatorText("Last scePadRead bytes");
|
|
for (size_t port = 0; port < ps2_stubs::kPadDebugPortCount; ++port)
|
|
{
|
|
for (size_t slot = 0; slot < ps2_stubs::kPadDebugSlotCount; ++slot)
|
|
{
|
|
const ps2_stubs::PadDebugPortSnapshot &row = snapshot.ports[port][slot];
|
|
ImGui::Text("port=%zu slot=%zu ok=%u data:", port, slot, row.lastReadOk ? 1u : 0u);
|
|
ImGui::SameLine();
|
|
drawPadBytes(row.lastData, ps2_stubs::kPadDebugDataSize);
|
|
}
|
|
}
|
|
|
|
ImGui::SeparatorText("PS2 active-low button layout");
|
|
ImGui::TextUnformatted("byte[2:3] raw is active-low: 0 means pressed, 1 means released.");
|
|
ImGui::TextUnformatted("bits: Select,L3,R3,Start,Up,Right,Down,Left,L2,R2,L1,R1,Triangle,Circle,Cross,Square");
|
|
}
|
|
|
|
void drawGsTab(PS2Runtime &runtime)
|
|
{
|
|
GSRegisters ®s = runtime.memory().gs();
|
|
const GSDebugSnapshot gs = runtime.gs().getDebugSnapshot();
|
|
|
|
ImGui::SeparatorText("GS private registers");
|
|
if (ImGui::BeginTable("gspriv", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable))
|
|
{
|
|
auto row64 = [](const char *name, uint64_t value)
|
|
{
|
|
ImGui::TableNextRow();
|
|
ImGui::TableNextColumn();
|
|
ImGui::TextUnformatted(name);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%016llX", static_cast<unsigned long long>(value));
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%llu", static_cast<unsigned long long>(value));
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("lo=0x%08X hi=0x%08X", static_cast<uint32_t>(value), static_cast<uint32_t>(value >> 32));
|
|
};
|
|
ImGui::TableSetupColumn("Reg");
|
|
ImGui::TableSetupColumn("Hex64");
|
|
ImGui::TableSetupColumn("Dec");
|
|
ImGui::TableSetupColumn("Split");
|
|
ImGui::TableHeadersRow();
|
|
row64("PMODE", regs.pmode);
|
|
row64("SMODE2", regs.smode2);
|
|
row64("DISPFB1", regs.dispfb1);
|
|
row64("DISPLAY1", regs.display1);
|
|
row64("DISPFB2", regs.dispfb2);
|
|
row64("DISPLAY2", regs.display2);
|
|
row64("BGCOLOR", regs.bgcolor);
|
|
row64("CSR", regs.csr.load());
|
|
row64("IMR", regs.imr);
|
|
row64("BUSDIR", regs.busdir);
|
|
row64("SIGLBLID", regs.siglblid);
|
|
ImGui::EndTable();
|
|
}
|
|
|
|
ImGui::SeparatorText("GS draw state");
|
|
ImGui::Text("PRIM type=%s iip=%u tme=%u fge=%u abe=%u aa1=%u fst=%u ctxt=%u fix=%u",
|
|
primTypeName(gs.prim.type), gs.prim.iip, gs.prim.tme, gs.prim.fge, gs.prim.abe,
|
|
gs.prim.aa1, gs.prim.fst, gs.prim.ctxt, gs.prim.fix);
|
|
ImGui::Text("TEXA ta0=0x%02X aem=%u ta1=0x%02X | TEXCLUT cbw=%u cou=%u cov=%u",
|
|
gs.texa.ta0, gs.texa.aem ? 1u : 0u, gs.texa.ta1, gs.texclut.cbw, gs.texclut.cou, gs.texclut.cov);
|
|
ImGui::Text("BITBLT sbp=%u sbw=%u spsm=0x%02X dbp=%u dbw=%u dpsm=0x%02X",
|
|
gs.bitbltbuf.sbp, gs.bitbltbuf.sbw, gs.bitbltbuf.spsm, gs.bitbltbuf.dbp, gs.bitbltbuf.dbw, gs.bitbltbuf.dpsm);
|
|
ImGui::Text("TRXPOS ss=(%u,%u) ds=(%u,%u) dir=%u | TRXREG %ux%u TRXDIR=%u copied=%u/%u at=(%u,%u)",
|
|
gs.trxpos.ssax, gs.trxpos.ssay, gs.trxpos.dsax, gs.trxpos.dsay, gs.trxpos.dir,
|
|
gs.trxreg.rrw, gs.trxreg.rrh, gs.trxdir, gs.transferCopiedPixels, gs.transferTotalPixels,
|
|
gs.transferX, gs.transferY);
|
|
ImGui::Text("Host presentation: has=%u size=%ux%u displayFbp=%u sourceFbp=%u preferred=%u lastDisplayBaseBytes=0x%08X localToHostPending=%zu",
|
|
gs.hasHostPresentationFrame ? 1u : 0u,
|
|
gs.hostPresentationWidth,
|
|
gs.hostPresentationHeight,
|
|
gs.hostPresentationDisplayFbp,
|
|
gs.hostPresentationSourceFbp,
|
|
gs.hostPresentationUsedPreferred ? 1u : 0u,
|
|
gs.lastDisplayBaseBytes,
|
|
gs.localToHostPendingBytes);
|
|
ImGui::Text("Preferred source: has=%u frame fbp=%u fbw=%u psm=0x%02X destFbp=%u",
|
|
gs.hasPreferredDisplaySource ? 1u : 0u,
|
|
gs.preferredDisplaySourceFrame.fbp,
|
|
gs.preferredDisplaySourceFrame.fbw,
|
|
gs.preferredDisplaySourceFrame.psm,
|
|
gs.preferredDisplayDestFbp);
|
|
|
|
drawGsContext("Context 0", gs.ctx[0]);
|
|
drawGsContext("Context 1", gs.ctx[1]);
|
|
|
|
ImGui::SeparatorText("GS history");
|
|
bool gsHistoryPaused = runtime.gs().isDebugHistoryPaused();
|
|
std::vector<GSDebugHistoryEntry> history = runtime.gs().getDebugHistory();
|
|
uint32_t latestFrame = 0u;
|
|
if (!history.empty())
|
|
{
|
|
latestFrame = history.back().frameIndex;
|
|
}
|
|
|
|
static bool s_gsHistoryCurrentFrameOnly = false;
|
|
static bool s_gsHistoryDrawsOnly = false;
|
|
static std::string s_lastGsDumpPath;
|
|
static std::string s_lastGsDumpError;
|
|
|
|
ImGui::Text("Ring entries: %zu / 512%s", history.size(), gsHistoryPaused ? " (paused)" : "");
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Clear GS history"))
|
|
{
|
|
runtime.gs().clearDebugHistory();
|
|
history.clear();
|
|
latestFrame = 0u;
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Checkbox("Pause capture", &gsHistoryPaused))
|
|
{
|
|
runtime.gs().setDebugHistoryPaused(gsHistoryPaused);
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Dump GS TXT"))
|
|
{
|
|
s_lastGsDumpError.clear();
|
|
const std::filesystem::path dumpPath = makeDebugDumpPath("gs_history", latestFrame);
|
|
if (writeGsDebugDump(dumpPath, regs, gs, history, s_gsHistoryCurrentFrameOnly, s_gsHistoryDrawsOnly, latestFrame, s_lastGsDumpError))
|
|
{
|
|
s_lastGsDumpPath = dumpPath.string();
|
|
ImGui::SetClipboardText(s_lastGsDumpPath.c_str());
|
|
}
|
|
else
|
|
{
|
|
s_lastGsDumpPath.clear();
|
|
}
|
|
}
|
|
if (gsHistoryPaused)
|
|
{
|
|
ImGui::SameLine();
|
|
ImGui::TextDisabled("history capture stopped");
|
|
}
|
|
|
|
ImGui::Checkbox("Current frame only", &s_gsHistoryCurrentFrameOnly);
|
|
ImGui::SameLine();
|
|
ImGui::Checkbox("Draws only", &s_gsHistoryDrawsOnly);
|
|
if (!s_lastGsDumpPath.empty())
|
|
{
|
|
ImGui::TextDisabled("Last dump copied to clipboard: %s", s_lastGsDumpPath.c_str());
|
|
}
|
|
if (!s_lastGsDumpError.empty())
|
|
{
|
|
ImGui::TextColored(ImVec4(1.0f, 0.35f, 0.35f, 1.0f), "Dump failed: %s", s_lastGsDumpError.c_str());
|
|
}
|
|
|
|
if (ImGui::BeginTable("gs_history", 16, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY | ImGuiTableFlags_ScrollX, ImVec2(0, 260)))
|
|
{
|
|
ImGui::TableSetupColumn("Seq");
|
|
ImGui::TableSetupColumn("Frame");
|
|
ImGui::TableSetupColumn("Tick");
|
|
ImGui::TableSetupColumn("Kind");
|
|
ImGui::TableSetupColumn("Prim");
|
|
ImGui::TableSetupColumn("Ctx");
|
|
ImGui::TableSetupColumn("FBP/PSM");
|
|
ImGui::TableSetupColumn("ZBP/PSM");
|
|
ImGui::TableSetupColumn("TEST");
|
|
ImGui::TableSetupColumn("ALPHA");
|
|
ImGui::TableSetupColumn("TEX0");
|
|
ImGui::TableSetupColumn("XY");
|
|
ImGui::TableSetupColumn("Z");
|
|
ImGui::TableSetupColumn("A");
|
|
ImGui::TableSetupColumn("GIF/REG/XFER");
|
|
ImGui::TableSetupColumn("Present");
|
|
ImGui::TableHeadersRow();
|
|
|
|
for (auto it = history.rbegin(); it != history.rend(); ++it)
|
|
{
|
|
const GSDebugHistoryEntry &row = *it;
|
|
if (s_gsHistoryCurrentFrameOnly && row.frameIndex != latestFrame)
|
|
{
|
|
continue;
|
|
}
|
|
if (s_gsHistoryDrawsOnly && row.kind != GSDebugEventKind::Draw)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const uint64_t test = row.test;
|
|
const uint64_t alpha = row.alpha;
|
|
ImGui::TableNextRow();
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%llu", static_cast<unsigned long long>(row.seq));
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%u", row.frameIndex);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%llu", static_cast<unsigned long long>(row.vsyncTick));
|
|
ImGui::TableNextColumn();
|
|
ImGui::TextUnformatted(gsDebugEventKindName(row.kind));
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%s", primTypeName(row.prim.type));
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%u", row.prim.ctxt ? 1u : 0u);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%u/%u/0x%02X", row.frame.fbp, row.frame.fbw, row.frame.psm);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%u/0x%02X m=%u", row.zbuf.zbp, row.zbuf.psm, row.zbuf.zmask ? 1u : 0u);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("ATE=%llu ATST=%llu AREF=%02llX AFAIL=%llu ZTE=%llu ZTST=%llu",
|
|
(test >> 0) & 1ull,
|
|
(test >> 1) & 7ull,
|
|
(test >> 4) & 0xffull,
|
|
(test >> 12) & 3ull,
|
|
(test >> 16) & 1ull,
|
|
(test >> 17) & 3ull);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%016llX", static_cast<unsigned long long>(alpha));
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("tbp=%u psm=0x%02X %ux%u cbp=%u cpsm=0x%02X",
|
|
row.tex0.tbp0,
|
|
row.tex0.psm,
|
|
1u << row.tex0.tw,
|
|
1u << row.tex0.th,
|
|
row.tex0.cbp,
|
|
row.tex0.cpsm);
|
|
ImGui::TableNextColumn();
|
|
if (row.kind == GSDebugEventKind::Draw)
|
|
ImGui::Text("%.1f..%.1f / %.1f..%.1f", row.xMin, row.xMax, row.yMin, row.yMax);
|
|
else
|
|
ImGui::TextUnformatted("-");
|
|
ImGui::TableNextColumn();
|
|
if (row.kind == GSDebugEventKind::Draw)
|
|
ImGui::Text("%.0f..%.0f", row.zMin, row.zMax);
|
|
else
|
|
ImGui::TextUnformatted("-");
|
|
ImGui::TableNextColumn();
|
|
if (row.kind == GSDebugEventKind::Draw)
|
|
ImGui::Text("%u..%u", row.aMin, row.aMax);
|
|
else
|
|
ImGui::TextUnformatted("-");
|
|
ImGui::TableNextColumn();
|
|
if (row.kind == GSDebugEventKind::GifTag)
|
|
{
|
|
ImGui::Text("%s nloop=%u nreg=%u size=%u", gifFormatName(row.gifFlg), row.gifNloop, row.gifNreg, row.gifSizeBytes);
|
|
}
|
|
else if (row.kind == GSDebugEventKind::Register)
|
|
{
|
|
ImGui::Text("%s(0x%02X)=0x%016llX", gsRegName(row.reg), row.reg, static_cast<unsigned long long>(row.regValue));
|
|
}
|
|
else if (row.kind == GSDebugEventKind::Transfer)
|
|
{
|
|
ImGui::Text("TRXDIR=%u %ux%u pix=%u sbp=%u dbp=%u", row.trxdir, row.trxreg.rrw, row.trxreg.rrh, row.transferPixels, row.bitbltbuf.sbp, row.bitbltbuf.dbp);
|
|
}
|
|
else
|
|
{
|
|
ImGui::TextUnformatted("-");
|
|
}
|
|
ImGui::TableNextColumn();
|
|
if (row.kind == GSDebugEventKind::Present)
|
|
{
|
|
ImGui::Text("%ux%u display=%u source=%u pref=%u", row.width, row.height, row.displayFbp, row.sourceFbp, row.usedPreferred ? 1u : 0u);
|
|
}
|
|
else
|
|
{
|
|
ImGui::TextUnformatted("-");
|
|
}
|
|
}
|
|
ImGui::EndTable();
|
|
}
|
|
}
|
|
|
|
void drawFileCdTab(PS2Runtime &runtime)
|
|
{
|
|
(void)runtime;
|
|
|
|
const PS2Runtime::IoPaths &ioPaths = PS2Runtime::getIoPaths();
|
|
ImGui::SeparatorText("Runtime IO paths");
|
|
textPath("ELF", ioPaths.elfPath);
|
|
textPath("ELF dir", ioPaths.elfDirectory);
|
|
textPath("Host root", ioPaths.hostRoot);
|
|
textPath("CD root", ioPaths.cdRoot);
|
|
textPath("CD image", ioPaths.cdImage);
|
|
textPath("MC root", ioPaths.mcRoot);
|
|
|
|
bool pathsInitialized = false;
|
|
std::filesystem::path hostBase;
|
|
std::filesystem::path cdromBase;
|
|
std::filesystem::path hostCwd;
|
|
std::filesystem::path cdromCwd;
|
|
std::string cwdDevice;
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_ps2_path_mutex);
|
|
pathsInitialized = g_ps2_paths_initialized;
|
|
hostBase = g_host_base;
|
|
cdromBase = g_cdrom_base;
|
|
hostCwd = g_host_cwd;
|
|
cdromCwd = g_cdrom_cwd;
|
|
cwdDevice = g_ps2_cwd_device;
|
|
}
|
|
|
|
ImGui::SeparatorText("PS2 path resolver");
|
|
ImGui::Text("initialized=%u cwdDevice=%s", pathsInitialized ? 1u : 0u, cwdDevice.c_str());
|
|
textPath("host base", hostBase);
|
|
textPath("cdrom base", cdromBase);
|
|
textPath("host cwd", hostCwd);
|
|
textPath("cdrom cwd", cdromCwd);
|
|
|
|
struct FdRow
|
|
{
|
|
int fd = 0;
|
|
FILE *file = nullptr;
|
|
};
|
|
|
|
std::vector<FdRow> fds;
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_fd_mutex);
|
|
fds.reserve(g_fileDescriptors.size());
|
|
for (const auto &[fd, file] : g_fileDescriptors)
|
|
{
|
|
fds.push_back(FdRow{fd, file});
|
|
}
|
|
}
|
|
std::sort(fds.begin(), fds.end(), [](const FdRow &a, const FdRow &b)
|
|
{ return a.fd < b.fd; });
|
|
|
|
ImGui::SeparatorText("FileIO descriptors");
|
|
ImGui::Text("Open host FILE* descriptors: %zu", fds.size());
|
|
if (ImGui::BeginTable("fileio_fds", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable, ImVec2(0, 90)))
|
|
{
|
|
ImGui::TableSetupColumn("FD");
|
|
ImGui::TableSetupColumn("FILE*");
|
|
ImGui::TableHeadersRow();
|
|
for (const FdRow &row : fds)
|
|
{
|
|
ImGui::TableNextRow();
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%d", row.fd);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%p", static_cast<void *>(row.file));
|
|
}
|
|
ImGui::EndTable();
|
|
}
|
|
|
|
const ps2_stubs::CdDebugSnapshot cd = ps2_stubs::getCdDebugSnapshot();
|
|
ImGui::SeparatorText("CDVD / sceCd state");
|
|
ImGui::Text("initialized=%u lastError=%d mode=0x%08X streamingLbn=0x%08X endLbn=0x%08X nextPseudoLbn=0x%08X",
|
|
cd.initialized ? 1u : 0u,
|
|
cd.lastError,
|
|
cd.mode,
|
|
cd.streamingLbn,
|
|
cd.streamingEndLbn,
|
|
cd.nextPseudoLbn);
|
|
ImGui::Text("imageValid=%u imageSize=%llu leafIndexBuilt=%u leafIndex=%zu looseIndex=%zu registeredFiles=%zu",
|
|
cd.imageSizeValid ? 1u : 0u,
|
|
static_cast<unsigned long long>(cd.imageSizeBytes),
|
|
cd.leafIndexBuilt ? 1u : 0u,
|
|
cd.leafIndexCount,
|
|
cd.loosePathIndexCount,
|
|
cd.files.size());
|
|
textPath("CD resolved root", cd.cdRoot);
|
|
textPath("CD image path", cd.cdImage);
|
|
textPath("CD image size path", cd.imageSizePath);
|
|
textPath("CD leaf index root", cd.leafIndexRoot);
|
|
|
|
if (ImGui::BeginTable("cd_files", 6, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY, ImVec2(0, 180)))
|
|
{
|
|
ImGui::TableSetupColumn("Key");
|
|
ImGui::TableSetupColumn("Base LBN");
|
|
ImGui::TableSetupColumn("End LBN");
|
|
ImGui::TableSetupColumn("Sectors");
|
|
ImGui::TableSetupColumn("Bytes");
|
|
ImGui::TableSetupColumn("Host path");
|
|
ImGui::TableHeadersRow();
|
|
for (const ps2_stubs::CdDebugFileEntry &row : cd.files)
|
|
{
|
|
const uint32_t endLbn = row.baseLbn + row.sectors;
|
|
const std::string hostPath = pathToString(row.hostPath);
|
|
ImGui::TableNextRow();
|
|
ImGui::TableNextColumn();
|
|
ImGui::TextUnformatted(row.key.c_str());
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", row.baseLbn);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", endLbn);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%u", row.sectors);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%u", row.sizeBytes);
|
|
ImGui::TableNextColumn();
|
|
ImGui::TextUnformatted(hostPath.c_str());
|
|
}
|
|
ImGui::EndTable();
|
|
}
|
|
|
|
const ps2_stubs::MemoryCardDebugSnapshot mc = ps2_stubs::getMemoryCardDebugSnapshot();
|
|
ImGui::SeparatorText("Memory card state");
|
|
ImGui::Text("lastCmd=0x%X lastResult=%d nextFd=%d cvCursor=%d openFiles=%zu",
|
|
mc.lastCmd,
|
|
mc.lastResult,
|
|
mc.nextFd,
|
|
mc.cvFileCursor,
|
|
mc.openFiles.size());
|
|
|
|
if (ImGui::BeginTable("mc_ports", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable))
|
|
{
|
|
ImGui::TableSetupColumn("Port");
|
|
ImGui::TableSetupColumn("Formatted");
|
|
ImGui::TableSetupColumn("Current dir");
|
|
ImGui::TableSetupColumn("Host root");
|
|
ImGui::TableHeadersRow();
|
|
for (const ps2_stubs::MemoryCardDebugPort &port : mc.ports)
|
|
{
|
|
const std::string root = pathToString(port.rootPath);
|
|
ImGui::TableNextRow();
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%d", port.port);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%u", port.formatted ? 1u : 0u);
|
|
ImGui::TableNextColumn();
|
|
ImGui::TextUnformatted(port.currentDir.c_str());
|
|
ImGui::TableNextColumn();
|
|
ImGui::TextUnformatted(root.c_str());
|
|
}
|
|
ImGui::EndTable();
|
|
}
|
|
|
|
if (ImGui::BeginTable("mc_open_files", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY, ImVec2(0, 120)))
|
|
{
|
|
ImGui::TableSetupColumn("FD");
|
|
ImGui::TableSetupColumn("Port");
|
|
ImGui::TableSetupColumn("Host path");
|
|
ImGui::TableHeadersRow();
|
|
for (const ps2_stubs::MemoryCardDebugOpenFile &row : mc.openFiles)
|
|
{
|
|
const std::string path = pathToString(row.hostPath);
|
|
ImGui::TableNextRow();
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%d", row.fd);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%d", row.port);
|
|
ImGui::TableNextColumn();
|
|
ImGui::TextUnformatted(path.c_str());
|
|
}
|
|
ImGui::EndTable();
|
|
}
|
|
}
|
|
|
|
void drawIoTab(PS2Runtime &runtime)
|
|
{
|
|
PS2Memory &mem = runtime.memory();
|
|
ImGui::SeparatorText("DMA/VIF/GIF IO registers");
|
|
struct RegRow
|
|
{
|
|
const char *name;
|
|
uint32_t addr;
|
|
};
|
|
static constexpr RegRow rows[] = {
|
|
{"D_STAT", 0x1000e010u},
|
|
{"VIF0_STAT", 0x10003800u},
|
|
{"VIF0_FBRST", 0x10003810u},
|
|
{"VIF0_ERR", 0x10003830u},
|
|
{"VIF1_STAT", 0x10003c00u},
|
|
{"VIF1_FBRST", 0x10003c10u},
|
|
{"VIF1_ERR", 0x10003c30u},
|
|
{"VIF0_CHCR", 0x10008000u},
|
|
{"VIF0_MADR", 0x10008010u},
|
|
{"VIF0_QWC", 0x10008020u},
|
|
{"VIF0_TADR", 0x10008030u},
|
|
{"VIF1_CHCR", 0x10009000u},
|
|
{"VIF1_MADR", 0x10009010u},
|
|
{"VIF1_QWC", 0x10009020u},
|
|
{"VIF1_TADR", 0x10009030u},
|
|
{"GIF_CHCR", 0x1000a000u},
|
|
{"GIF_MADR", 0x1000a010u},
|
|
{"GIF_QWC", 0x1000a020u},
|
|
{"GIF_TADR", 0x1000a030u},
|
|
{"IPU_FROM_CHCR", 0x1000b000u},
|
|
{"IPU_TO_CHCR", 0x1000b400u},
|
|
};
|
|
|
|
if (ImGui::BeginTable("ioregs", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable))
|
|
{
|
|
ImGui::TableSetupColumn("Name");
|
|
ImGui::TableSetupColumn("Addr");
|
|
ImGui::TableSetupColumn("Value");
|
|
ImGui::TableHeadersRow();
|
|
for (const auto &row : rows)
|
|
{
|
|
ImGui::TableNextRow();
|
|
ImGui::TableNextColumn();
|
|
ImGui::TextUnformatted(row.name);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", row.addr);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("0x%08X", mem.readIORegister(row.addr));
|
|
}
|
|
ImGui::EndTable();
|
|
}
|
|
}
|
|
|
|
bool writeRuntimeLogDump(const std::filesystem::path &path,
|
|
const std::vector<ps2_log::RuntimeLogEntry> &entries,
|
|
ImGuiTextFilter *filter,
|
|
std::string &error)
|
|
{
|
|
try
|
|
{
|
|
std::ofstream out(path, std::ios::out | std::ios::trunc);
|
|
if (!out.is_open())
|
|
{
|
|
error = "failed to open dump path";
|
|
return false;
|
|
}
|
|
|
|
out << "PS2 runtime log dump\n";
|
|
out << "entries=" << entries.size() << "\n";
|
|
out << "filter=" << (filter && filter->IsActive() ? filter->InputBuf : "<none>") << "\n";
|
|
out << "ps2_log_txt=" << ps2_log::log_path() << "\n";
|
|
out << "\n";
|
|
out << "seq\ttext\n";
|
|
|
|
for (const ps2_log::RuntimeLogEntry &entry : entries)
|
|
{
|
|
if (filter && filter->IsActive() && !filter->PassFilter(entry.text.c_str()))
|
|
{
|
|
continue;
|
|
}
|
|
out << entry.seq << '\t' << entry.text;
|
|
if (entry.text.empty() || entry.text.back() != '\n')
|
|
{
|
|
out << '\n';
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
catch (const std::exception &e)
|
|
{
|
|
error = e.what();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void drawLogsTab()
|
|
{
|
|
static ImGuiTextFilter s_logFilter;
|
|
static bool s_autoScroll = true;
|
|
static bool s_wrapText = false;
|
|
static bool s_latestFirst = false;
|
|
static std::string s_lastLogDumpPath;
|
|
static std::string s_lastLogDumpError;
|
|
|
|
bool paused = ps2_log::is_runtime_log_paused();
|
|
std::vector<ps2_log::RuntimeLogEntry> entries = ps2_log::snapshot_runtime_log_entries();
|
|
|
|
ImGui::SeparatorText("Runtime log ring buffer");
|
|
ImGui::Text("Entries: %zu / %zu%s", entries.size(), ps2_log::kMaxRuntimeLogEntries, paused ? " (paused)" : "");
|
|
ImGui::TextDisabled("Captures RUNTIME_LOG(...) calls. Direct std::cerr/std::cout writes are still console-only unless routed through RUNTIME_LOG.");
|
|
ImGui::TextDisabled("Aggressive call trace file: %s", ps2_log::log_path().c_str());
|
|
|
|
if (ImGui::Button("Clear logs"))
|
|
{
|
|
ps2_log::clear_runtime_log_entries();
|
|
entries.clear();
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Checkbox("Pause capture", &paused))
|
|
{
|
|
ps2_log::set_runtime_log_paused(paused);
|
|
}
|
|
ImGui::SameLine();
|
|
ImGui::Checkbox("Auto-scroll", &s_autoScroll);
|
|
ImGui::SameLine();
|
|
ImGui::Checkbox("Wrap", &s_wrapText);
|
|
ImGui::SameLine();
|
|
ImGui::Checkbox("Latest first", &s_latestFirst);
|
|
|
|
s_logFilter.Draw("Filter", 260.0f);
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Copy visible"))
|
|
{
|
|
std::ostringstream out;
|
|
auto appendEntry = [&](const ps2_log::RuntimeLogEntry &entry)
|
|
{
|
|
if (s_logFilter.IsActive() && !s_logFilter.PassFilter(entry.text.c_str()))
|
|
{
|
|
return;
|
|
}
|
|
out << entry.seq << '\t' << entry.text;
|
|
if (entry.text.empty() || entry.text.back() != '\n')
|
|
{
|
|
out << '\n';
|
|
}
|
|
};
|
|
|
|
if (s_latestFirst)
|
|
{
|
|
for (auto it = entries.rbegin(); it != entries.rend(); ++it)
|
|
{
|
|
appendEntry(*it);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (const ps2_log::RuntimeLogEntry &entry : entries)
|
|
{
|
|
appendEntry(entry);
|
|
}
|
|
}
|
|
const std::string text = out.str();
|
|
ImGui::SetClipboardText(text.c_str());
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Dump logs TXT"))
|
|
{
|
|
s_lastLogDumpError.clear();
|
|
const std::filesystem::path dumpPath = makeDebugDumpPath("runtime_log", 0u);
|
|
if (writeRuntimeLogDump(dumpPath, entries, &s_logFilter, s_lastLogDumpError))
|
|
{
|
|
s_lastLogDumpPath = dumpPath.string();
|
|
ImGui::SetClipboardText(s_lastLogDumpPath.c_str());
|
|
}
|
|
else
|
|
{
|
|
s_lastLogDumpPath.clear();
|
|
}
|
|
}
|
|
|
|
if (!s_lastLogDumpPath.empty())
|
|
{
|
|
ImGui::TextDisabled("Last dump: %s", s_lastLogDumpPath.c_str());
|
|
}
|
|
if (!s_lastLogDumpError.empty())
|
|
{
|
|
ImGui::TextColored(ImVec4(1.0f, 0.35f, 0.35f, 1.0f), "Dump failed: %s", s_lastLogDumpError.c_str());
|
|
}
|
|
|
|
ImGui::Separator();
|
|
const ImGuiTableFlags tableFlags = ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY | ImGuiTableFlags_ScrollX;
|
|
if (ImGui::BeginTable("runtime_logs", 2, tableFlags, ImVec2(0, 430)))
|
|
{
|
|
ImGui::TableSetupColumn("Seq", ImGuiTableColumnFlags_WidthFixed, 80.0f);
|
|
ImGui::TableSetupColumn("Text", ImGuiTableColumnFlags_WidthStretch);
|
|
ImGui::TableHeadersRow();
|
|
|
|
auto drawEntry = [&](const ps2_log::RuntimeLogEntry &entry)
|
|
{
|
|
if (s_logFilter.IsActive() && !s_logFilter.PassFilter(entry.text.c_str()))
|
|
{
|
|
return;
|
|
}
|
|
|
|
ImGui::TableNextRow();
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%llu", static_cast<unsigned long long>(entry.seq));
|
|
ImGui::TableNextColumn();
|
|
if (s_wrapText)
|
|
{
|
|
ImGui::TextWrapped("%s", entry.text.c_str());
|
|
}
|
|
else
|
|
{
|
|
ImGui::TextUnformatted(entry.text.c_str());
|
|
}
|
|
};
|
|
|
|
if (s_latestFirst)
|
|
{
|
|
for (auto it = entries.rbegin(); it != entries.rend(); ++it)
|
|
{
|
|
drawEntry(*it);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (const ps2_log::RuntimeLogEntry &entry : entries)
|
|
{
|
|
drawEntry(entry);
|
|
}
|
|
}
|
|
|
|
if (s_autoScroll && !s_latestFirst && ImGui::GetScrollY() >= ImGui::GetScrollMaxY() - 4.0f)
|
|
{
|
|
ImGui::SetScrollHereY(1.0f);
|
|
}
|
|
ImGui::EndTable();
|
|
}
|
|
}
|
|
|
|
#endif
|
|
}
|
|
void PS2DebugPanel::initialize()
|
|
{
|
|
#if defined(PS2X_ENABLE_DEBUG_UI) && !defined(PLATFORM_VITA)
|
|
if (!m_initialized)
|
|
{
|
|
rlImGuiSetup(true);
|
|
m_initialized = true;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void PS2DebugPanel::shutdown()
|
|
{
|
|
#if defined(PS2X_ENABLE_DEBUG_UI) && !defined(PLATFORM_VITA)
|
|
if (m_initialized)
|
|
{
|
|
rlImGuiShutdown();
|
|
m_initialized = false;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void PS2DebugPanel::draw(PS2Runtime &runtime)
|
|
{
|
|
#if defined(PS2X_ENABLE_DEBUG_UI) && !defined(PLATFORM_VITA)
|
|
if (!m_initialized)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (IsKeyPressed(KEY_F1))
|
|
{
|
|
toggleVisible();
|
|
}
|
|
|
|
if (!m_visible)
|
|
{
|
|
return;
|
|
}
|
|
|
|
rlImGuiBegin();
|
|
|
|
ImGui::SetNextWindowSize(ImVec2(780.0f, 620.0f), ImGuiCond_FirstUseEver);
|
|
if (ImGui::Begin("Runtime Debugger", &m_visible, ImGuiWindowFlags_MenuBar))
|
|
{
|
|
if (ImGui::BeginMenuBar())
|
|
{
|
|
ImGui::MenuItem("Registers", nullptr, &m_showRegisters);
|
|
ImGui::EndMenuBar();
|
|
}
|
|
|
|
if (ImGui::BeginTabBar("debug-tabs"))
|
|
{
|
|
if (ImGui::BeginTabItem("CPU"))
|
|
{
|
|
drawCpuTab(runtime, m_showRegisters);
|
|
ImGui::EndTabItem();
|
|
}
|
|
if (ImGui::BeginTabItem("Threads"))
|
|
{
|
|
drawThreadsTab(runtime);
|
|
ImGui::EndTabItem();
|
|
}
|
|
if (ImGui::BeginTabItem("Kernel"))
|
|
{
|
|
drawKernelTab(runtime);
|
|
ImGui::EndTabItem();
|
|
}
|
|
if (ImGui::BeginTabItem("IOP/SIF"))
|
|
{
|
|
drawIopTab(runtime);
|
|
ImGui::EndTabItem();
|
|
}
|
|
if (ImGui::BeginTabItem("RPC History"))
|
|
{
|
|
drawRpcHistoryTab(runtime);
|
|
ImGui::EndTabItem();
|
|
}
|
|
if (ImGui::BeginTabItem("PAD"))
|
|
{
|
|
drawPadTab();
|
|
ImGui::EndTabItem();
|
|
}
|
|
if (ImGui::BeginTabItem("File/CD"))
|
|
{
|
|
drawFileCdTab(runtime);
|
|
ImGui::EndTabItem();
|
|
}
|
|
if (ImGui::BeginTabItem("GS"))
|
|
{
|
|
drawGsTab(runtime);
|
|
ImGui::EndTabItem();
|
|
}
|
|
if (ImGui::BeginTabItem("DMA/IO"))
|
|
{
|
|
drawIoTab(runtime);
|
|
ImGui::EndTabItem();
|
|
}
|
|
if (ImGui::BeginTabItem("Logs"))
|
|
{
|
|
drawLogsTab();
|
|
ImGui::EndTabItem();
|
|
}
|
|
if (ImGui::BeginTabItem("Memory"))
|
|
{
|
|
uint32_t bytes = m_memoryBytes;
|
|
ImGui::InputScalar("Address", ImGuiDataType_U32, &m_memoryAddress, nullptr, nullptr, "%08X", ImGuiInputTextFlags_CharsHexadecimal);
|
|
ImGui::InputScalar("Bytes", ImGuiDataType_U32, &bytes, nullptr, nullptr, "%u");
|
|
m_memoryBytes = std::clamp<unsigned int>(bytes, 16u, 0x1000u);
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("PC"))
|
|
m_memoryAddress = runtime.m_debugPc.load(std::memory_order_relaxed);
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("SP"))
|
|
m_memoryAddress = runtime.m_debugSp.load(std::memory_order_relaxed);
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("GP"))
|
|
m_memoryAddress = runtime.m_debugGp.load(std::memory_order_relaxed);
|
|
ImGui::Separator();
|
|
drawHexDump(runtime.memory().getRDRAM(), m_memoryAddress, m_memoryBytes);
|
|
ImGui::EndTabItem();
|
|
}
|
|
ImGui::EndTabBar();
|
|
}
|
|
}
|
|
ImGui::End();
|
|
|
|
rlImGuiEnd();
|
|
#else
|
|
(void)runtime;
|
|
#endif
|
|
}
|