Skip to content

Commit 780153c

Browse files
authored
Merge pull request #2003 from nicolasnoble/ram-viewer
Add ram viewer.
2 parents 87343ff + b9313ed commit 780153c

19 files changed

Lines changed: 1064 additions & 28 deletions

src/core/debug.cc

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "core/psxemulator.h"
2929
#include "core/psxmem.h"
3030
#include "core/r3000a.h"
31+
#include "core/ramlogger.h"
3132
#include "supportpsx/memory.h"
3233

3334
enum {
@@ -157,6 +158,17 @@ void PCSX::Debug::process(uint32_t oldPC, uint32_t newPC, uint32_t oldCode, uint
157158
if (isJALR) markMap(regs.GPR.r[rd], MAP_EXEC_JAL);
158159
}
159160

161+
// RAM logger: record execution
162+
auto *ramLogger = g_emulator->m_ramLogger.get();
163+
const bool ramLoggerEnabled = ramLogger->isEnabled();
164+
const uint32_t cycle = static_cast<uint32_t>(regs.cycle);
165+
if (ramLoggerEnabled) {
166+
PSXAddress pcAddr(normalizeAddress(newPC));
167+
if (pcAddr.type == PSXAddress::Type::RAM) {
168+
ramLogger->recordAccess(pcAddr.physical, 4, RAMLogger::AccessType::Execute, cycle);
169+
}
170+
}
171+
160172
// Are we jumping from a non-kernel address to a kernel address which:
161173
// - is not a jr to $ra (aka a return from a callback)
162174
// - is not a jump to 0xa0 / 0xb0 / 0xc0 (aka the syscall gates)
@@ -213,6 +225,20 @@ void PCSX::Debug::process(uint32_t oldPC, uint32_t newPC, uint32_t oldCode, uint
213225
}
214226
if (m_mapping_w32) markMap(offset, MAP_W32);
215227
}
228+
// RAM logger: record loads and stores
229+
if (ramLoggerEnabled) {
230+
PSXAddress loadStoreAddr(normalizeAddress(offset));
231+
if (loadStoreAddr.type == PSXAddress::Type::RAM) {
232+
if (isLoad) {
233+
unsigned width = (isLB || isLBU) ? 1 : (isLH || isLHU) ? 2 : 4;
234+
ramLogger->recordAccess(loadStoreAddr.physical, width, RAMLogger::AccessType::Read, cycle);
235+
}
236+
if (isStore) {
237+
unsigned width = isSB ? 1 : isSH ? 2 : 4;
238+
ramLogger->recordAccess(loadStoreAddr.physical, width, RAMLogger::AccessType::Write, cycle);
239+
}
240+
}
241+
}
216242
// Are we accessing a kernel address from a non-kernel address, while not in IRQ?
217243
if (!g_emulator->m_cpu->m_inISR && offsetIsInKernel && !wasInKernel) {
218244
if (m_checkKernel) {
@@ -292,6 +318,16 @@ bool PCSX::Debug::triggerBP(Breakpoint* bp, uint32_t address, unsigned width, co
292318
return keepBP;
293319
}
294320

321+
void PCSX::Debug::logDMAAccess(uint32_t address, uint32_t len, bool isWrite) {
322+
auto *ramLogger = g_emulator->m_ramLogger.get();
323+
if (!ramLogger->isEnabled()) return;
324+
PSXAddress addr(normalizeAddress(address));
325+
if (addr.type != PSXAddress::Type::RAM) return;
326+
uint32_t cycle = static_cast<uint32_t>(g_emulator->m_cpu->m_regs.cycle);
327+
auto type = isWrite ? RAMLogger::AccessType::Write : RAMLogger::AccessType::Read;
328+
ramLogger->recordAccess(addr.physical, len, type, cycle);
329+
}
330+
295331
void PCSX::Debug::checkBP(uint32_t address, BreakpointType type, uint32_t width, const char* cause) {
296332
auto& cpu = g_emulator->m_cpu;
297333
auto& regs = cpu->m_regs;

src/core/debug.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,17 @@ class Debug {
4242
void checkDMAread(unsigned c, uint32_t address, uint32_t len) {
4343
std::string cause = fmt::format("DMA channel {} read", c);
4444
checkBP(address, BreakpointType::Read, len, cause.c_str());
45+
logDMAAccess(address, len, false);
4546
}
4647
void checkDMAwrite(unsigned c, uint32_t address, uint32_t len) {
4748
std::string cause = fmt::format("DMA channel {} write", c);
4849
checkBP(address, BreakpointType::Write, len, cause.c_str());
50+
logDMAAccess(address, len, true);
4951
}
5052

5153
private:
5254
void checkBP(uint32_t address, BreakpointType type, uint32_t width, const char* cause = "");
55+
void logDMAAccess(uint32_t address, uint32_t len, bool isWrite);
5356

5457
public:
5558
// call this if PC is being set, like when the emulation is being reset, or when doing fastboot

src/core/psxemulator.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "core/gpu.h"
2828
#include "core/gpulogger.h"
2929
#include "core/gte.h"
30+
#include "core/ramlogger.h"
3031
#include "core/luaiso.h"
3132
#include "core/mdec.h"
3233
#include "core/pad.h"
@@ -62,6 +63,7 @@ PCSX::Emulator::Emulator()
6263
m_gdbServer(new PCSX::GdbServer()),
6364
m_gpuLogger(new PCSX::GPULogger()),
6465
m_gte(new PCSX::GTE()),
66+
m_ramLogger(new PCSX::RAMLogger()),
6567
m_hw(new PCSX::HW()),
6668
m_lua(new PCSX::Lua()),
6769
m_mdec(new PCSX::MDEC()),

src/core/psxemulator.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ class GdbServer;
7474
class GPU;
7575
class GPULogger;
7676
class GTE;
77+
class RAMLogger;
7778
class HW;
7879
class Lua;
7980
class MDEC;
@@ -261,6 +262,7 @@ class Emulator {
261262
std::unique_ptr<GPU> m_gpu;
262263
std::unique_ptr<GPULogger> m_gpuLogger;
263264
std::unique_ptr<GTE> m_gte;
265+
std::unique_ptr<RAMLogger> m_ramLogger;
264266
std::unique_ptr<HW> m_hw;
265267
std::unique_ptr<Lua> m_lua;
266268
std::unique_ptr<MDEC> m_mdec;

src/core/ramlogger.cc

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/***************************************************************************
2+
* Copyright (C) 2026 PCSX-Redux authors *
3+
* *
4+
* This program is free software; you can redistribute it and/or modify *
5+
* it under the terms of the GNU General Public License as published by *
6+
* the Free Software Foundation; either version 2 of the License, or *
7+
* (at your option) any later version. *
8+
* *
9+
* This program is distributed in the hope that it will be useful, *
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12+
* GNU General Public License for more details. *
13+
* *
14+
* You should have received a copy of the GNU General Public License *
15+
* along with this program; if not, write to the *
16+
* Free Software Foundation, Inc., *
17+
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
18+
***************************************************************************/
19+
20+
#include "core/ramlogger.h"
21+
22+
#include "core/psxemulator.h"
23+
#include "core/psxmem.h"
24+
25+
void PCSX::RAMLogger::recordAccess(uint32_t physAddr, unsigned width, AccessType type, uint32_t cycle) {
26+
if (!m_enabled) return;
27+
28+
uint32_t* timestamps = (type == AccessType::Read) ? m_readTimestamps
29+
: (type == AccessType::Write) ? m_writeTimestamps
30+
: m_execTimestamps;
31+
32+
for (unsigned i = 0; i < width; i++) {
33+
uint32_t addr = physAddr + i;
34+
if (addr < c_maxBytes) {
35+
timestamps[addr] = cycle;
36+
}
37+
}
38+
}
39+
40+
void PCSX::RAMLogger::enable() {
41+
if (m_hasResources) {
42+
m_enabled = true;
43+
return;
44+
}
45+
46+
auto setupTex = [](OpenGL::Texture& tex) {
47+
tex.bind();
48+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
49+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
50+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
51+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
52+
};
53+
54+
// Create heatmap textures (GL_R32UI - unsigned integer)
55+
m_readHeatmapTex.create(c_width, c_maxHeight, GL_R32UI);
56+
m_writeHeatmapTex.create(c_width, c_maxHeight, GL_R32UI);
57+
m_execHeatmapTex.create(c_width, c_maxHeight, GL_R32UI);
58+
if (!m_readHeatmapTex.exists() || !m_writeHeatmapTex.exists() || !m_execHeatmapTex.exists()) return;
59+
setupTex(m_readHeatmapTex);
60+
setupTex(m_writeHeatmapTex);
61+
setupTex(m_execHeatmapTex);
62+
63+
// Create RAM data texture (GL_R8)
64+
m_ramTexture.create(c_width, c_maxHeight, GL_R8);
65+
if (!m_ramTexture.exists()) return;
66+
setupTex(m_ramTexture);
67+
68+
// Clear timestamp arrays
69+
memset(m_readTimestamps, 0, sizeof(m_readTimestamps));
70+
memset(m_writeTimestamps, 0, sizeof(m_writeTimestamps));
71+
memset(m_execTimestamps, 0, sizeof(m_execTimestamps));
72+
73+
m_hasResources = true;
74+
m_enabled = true;
75+
}
76+
77+
void PCSX::RAMLogger::disable() {
78+
m_enabled = false;
79+
}
80+
81+
void PCSX::RAMLogger::uploadRAM() {
82+
if (!m_hasResources) return;
83+
bool is8MB = g_emulator->settings.get<Emulator::Setting8MB>();
84+
int height = is8MB ? c_maxHeight : (c_maxHeight / 4);
85+
86+
m_ramTexture.bind();
87+
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
88+
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, c_width, height, GL_RED, GL_UNSIGNED_BYTE, g_emulator->m_mem->m_wram);
89+
}
90+
91+
void PCSX::RAMLogger::uploadHeatmaps() {
92+
if (!m_hasResources) return;
93+
bool is8MB = g_emulator->settings.get<Emulator::Setting8MB>();
94+
int height = is8MB ? c_maxHeight : (c_maxHeight / 4);
95+
96+
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
97+
98+
m_readHeatmapTex.bind();
99+
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, c_width, height, GL_RED_INTEGER, GL_UNSIGNED_INT, m_readTimestamps);
100+
101+
m_writeHeatmapTex.bind();
102+
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, c_width, height, GL_RED_INTEGER, GL_UNSIGNED_INT, m_writeTimestamps);
103+
104+
m_execHeatmapTex.bind();
105+
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, c_width, height, GL_RED_INTEGER, GL_UNSIGNED_INT, m_execTimestamps);
106+
}

src/core/ramlogger.h

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/***************************************************************************
2+
* Copyright (C) 2026 PCSX-Redux authors *
3+
* *
4+
* This program is free software; you can redistribute it and/or modify *
5+
* it under the terms of the GNU General Public License as published by *
6+
* the Free Software Foundation; either version 2 of the License, or *
7+
* (at your option) any later version. *
8+
* *
9+
* This program is distributed in the hope that it will be useful, *
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12+
* GNU General Public License for more details. *
13+
* *
14+
* You should have received a copy of the GNU General Public License *
15+
* along with this program; if not, write to the *
16+
* Free Software Foundation, Inc., *
17+
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
18+
***************************************************************************/
19+
20+
#pragma once
21+
22+
#include <stdint.h>
23+
#include <string.h>
24+
25+
#include "support/opengl.h"
26+
27+
namespace PCSX {
28+
29+
namespace Widgets {
30+
class RAMViewer;
31+
}
32+
33+
class RAMLogger {
34+
public:
35+
enum class AccessType { Read, Write, Execute };
36+
37+
static constexpr int c_width = 2048;
38+
static constexpr int c_maxHeight = 4096; // 8MB / 2048
39+
static constexpr size_t c_maxBytes = c_width * c_maxHeight; // 8MB
40+
41+
void recordAccess(uint32_t physAddr, unsigned width, AccessType type, uint32_t cycle);
42+
43+
void enable();
44+
void disable();
45+
bool isEnabled() const { return m_enabled; }
46+
47+
void uploadRAM();
48+
void uploadHeatmaps();
49+
50+
void bindReadHeatmap() { m_readHeatmapTex.bind(); }
51+
void bindWriteHeatmap() { m_writeHeatmapTex.bind(); }
52+
void bindExecHeatmap() { m_execHeatmapTex.bind(); }
53+
void bindRAMTexture() { m_ramTexture.bind(); }
54+
GLuint getRAMTextureID() { return m_ramTexture.handle(); }
55+
56+
// Configurable decay half-life in cycles (how many cycles until intensity halves)
57+
float m_decayHalfLife = 33868800.0f; // ~1 second at 33.8MHz
58+
59+
private:
60+
bool m_enabled = false;
61+
bool m_hasResources = false;
62+
63+
// Per-byte cycle timestamp arrays (low 32 bits of cycle counter)
64+
uint32_t m_readTimestamps[c_maxBytes] = {};
65+
uint32_t m_writeTimestamps[c_maxBytes] = {};
66+
uint32_t m_execTimestamps[c_maxBytes] = {};
67+
68+
// Heatmap textures (GL_R32UI, 2048 x maxHeight)
69+
OpenGL::Texture m_readHeatmapTex, m_writeHeatmapTex, m_execHeatmapTex;
70+
71+
// Raw RAM texture (GL_R8, 2048 x maxHeight)
72+
OpenGL::Texture m_ramTexture;
73+
74+
friend class Widgets::RAMViewer;
75+
};
76+
77+
} // namespace PCSX

src/core/system.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,10 @@ struct VRAMClick {
112112
float x, y;
113113
VRAMMode vramMode;
114114
};
115+
struct RAMFocus {
116+
uint32_t address;
117+
uint32_t size;
118+
};
115119
} // namespace GUI
116120
struct Keyboard {
117121
int key, scancode, action, mods;

src/gui/gui.cc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ extern "C" {
5454
#include "core/gdb-server.h"
5555
#include "core/gpu.h"
5656
#include "core/gpulogger.h"
57+
#include "core/ramlogger.h"
5758
#include "core/pad.h"
5859
#include "core/psxemulator.h"
5960
#include "core/psxmem.h"
@@ -1406,6 +1407,7 @@ in Configuration->Emulation, restart PCSX-Redux, then try again.)"));
14061407
ImGui::EndMenu();
14071408
}
14081409
ImGui::MenuItem(_("Show Memory Observer"), nullptr, &m_memoryObserver.m_show);
1410+
ImGui::MenuItem(_("Show RAM viewer"), nullptr, &m_ramViewer.m_show);
14091411
ImGui::MenuItem(_("Show Typed Debugger"), nullptr, &m_typedDebugger.m_show);
14101412
ImGui::MenuItem(_("Show Patches"), nullptr, &m_patches.m_show);
14111413
ImGui::MenuItem(_("Show Interrupts Scaler"), nullptr, &m_showInterruptsScaler);
@@ -1582,6 +1584,17 @@ in Configuration->Emulation, restart PCSX-Redux, then try again.)"));
15821584
}
15831585
}
15841586

1587+
if (m_ramViewer.m_show) {
1588+
auto *ramLogger = g_emulator->m_ramLogger.get();
1589+
if (!ramLogger->isEnabled()) ramLogger->enable();
1590+
ramLogger->uploadRAM();
1591+
ramLogger->uploadHeatmaps();
1592+
m_ramViewer.draw(this);
1593+
} else {
1594+
auto *ramLogger = g_emulator->m_ramLogger.get();
1595+
if (ramLogger->isEnabled()) ramLogger->disable();
1596+
}
1597+
15851598
if (m_log.m_show) {
15861599
ImGui::SetNextWindowPos(ImVec2(10, 540), ImGuiCond_FirstUseEver);
15871600
ImGui::SetNextWindowSize(ImVec2(1200, 250), ImGuiCond_FirstUseEver);

src/gui/gui.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
#include "gui/widgets/registers.h"
5858
#include "gui/widgets/shader-editor.h"
5959
#include "gui/widgets/sio1.h"
60+
#include "gui/widgets/ram-viewer.h"
6061
#include "gui/widgets/vram-viewer.h"
6162
#include "imgui.h"
6263
#include "imgui_md/imgui_md.h"
@@ -113,6 +114,7 @@ class GUI final : public UI {
113114
typedef Setting<bool, TYPESTRING("ShowSIO1")> ShowSIO1;
114115
typedef Setting<bool, TYPESTRING("ShowIsoBrowser")> ShowIsoBrowser;
115116
typedef Setting<bool, TYPESTRING("ShowGPULogger")> ShowGPULogger;
117+
typedef Setting<bool, TYPESTRING("ShowRAMViewer")> ShowRAMViewer;
116118
typedef Setting<int, TYPESTRING("WindowPosX"), 0> WindowPosX;
117119
typedef Setting<int, TYPESTRING("WindowPosY"), 0> WindowPosY;
118120
typedef Setting<int, TYPESTRING("WindowSizeX"), 1280> WindowSizeX;
@@ -157,7 +159,8 @@ class GUI final : public UI {
157159
ShowCLUTVRAMViewer, ShowVRAMViewer1, ShowVRAMViewer2, ShowVRAMViewer3, ShowVRAMViewer4, ShowMemoryObserver,
158160
ShowTypedDebugger, ShowPatches, ShowMemcardManager, ShowRegisters, ShowAssembly, ShowDisassembly,
159161
ShowBreakpoints, ShowNamedSaveStates, ShowEvents, ShowHandlers, ShowKernelLog, ShowCallstacks, ShowSIO1,
160-
ShowIsoBrowser, ShowGPULogger, MainFontSize, MonoFontSize, GUITheme, AllowMouseCaptureToggle,
162+
ShowIsoBrowser, ShowGPULogger, ShowRAMViewer, MainFontSize, MonoFontSize, GUITheme,
163+
AllowMouseCaptureToggle,
161164
EnableRawMouseMotion, WidescreenRatio, ShowPIOCartConfig, ShowMemoryEditor1, ShowMemoryEditor2,
162165
ShowMemoryEditor3, ShowMemoryEditor4, ShowMemoryEditor5, ShowMemoryEditor6, ShowMemoryEditor7,
163166
ShowMemoryEditor8, ShowParallelPortEditor, ShowScratchpadEditor, ShowHWRegsEditor, ShowBiosEditor,
@@ -391,6 +394,7 @@ class GUI final : public UI {
391394
{settings.get<ShowVRAMViewer3>().value},
392395
{settings.get<ShowVRAMViewer4>().value}};
393396

397+
Widgets::RAMViewer m_ramViewer = {settings.get<ShowRAMViewer>().value};
394398
Widgets::LuaEditor m_luaEditor = {settings.get<ShowLuaEditor>().value};
395399

396400
Widgets::Events m_events = {settings.get<ShowEvents>().value};

0 commit comments

Comments
 (0)