Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions src/core/debug.cc
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "core/psxemulator.h"
#include "core/psxmem.h"
#include "core/r3000a.h"
#include "core/ramlogger.h"
#include "supportpsx/memory.h"

enum {
Expand Down Expand Up @@ -157,62 +158,87 @@
if (isJALR) markMap(regs.GPR.r[rd], MAP_EXEC_JAL);
}

// RAM logger: record execution
auto *ramLogger = g_emulator->m_ramLogger.get();
const bool ramLoggerEnabled = ramLogger->isEnabled();
const uint32_t cycle = static_cast<uint32_t>(regs.cycle);
if (ramLoggerEnabled) {
PSXAddress pcAddr(normalizeAddress(newPC));
if (pcAddr.type == PSXAddress::Type::RAM) {
ramLogger->recordAccess(pcAddr.physical, 4, RAMLogger::AccessType::Execute, cycle);
}
}

// Are we jumping from a non-kernel address to a kernel address which:
// - is not a jr to $ra (aka a return from a callback)
// - is not a jump to 0xa0 / 0xb0 / 0xc0 (aka the syscall gates)
// - is not going to the break or exception handler
if ((isJR || isJALR) && !wasInKernel && isTargetInKernel && !isJRToRA && (targetBase != 0x40) &&
(targetBase != 0x80) && (targetBase != 0xa0) && (targetBase != 0xb0) && (targetBase != 0xc0)) {
if (m_checkKernel) {
g_system->printf(_("Kernel checker: Jump from 0x%08x to 0x%08x\n"), oldPC, targetBase);
g_system->pause();
}
}

if (isAnyLoadOrStore) {
if (isLWL || isLWR || isSWR || isSWL) offset &= ~3;
if (isLB || isLBU) {
checkBP(offset, BreakpointType::Read, 1);
if (m_breakmp_r8 && !isMapMarked(offset, MAP_R8)) {
triggerBP(nullptr, offset, 1, _("Read 8 map"));
}
if (m_mapping_r8) markMap(offset, MAP_R8);
}
if (isLH || isLHU) {
checkBP(offset, BreakpointType::Read, 2);
if (m_breakmp_r16 && !isMapMarked(offset, MAP_R16)) {
triggerBP(nullptr, offset, 2, _("Read 16 map"));
}
if (m_mapping_r16) markMap(offset, MAP_R16);
}
if (isLW || isLWR || isLWL || isLWC2) {
checkBP(offset, BreakpointType::Read, 4);
if (m_breakmp_r32 && !isMapMarked(offset, MAP_R32)) {
triggerBP(nullptr, offset, 4, _("Read 32 map"));
}
if (m_mapping_r32) markMap(offset, MAP_R32);
}
if (isSB) {
checkBP(offset, BreakpointType::Write, 1);
if (m_breakmp_w8 && !isMapMarked(offset, MAP_W8)) {
triggerBP(nullptr, offset, 1, _("Write 8 map"));
}
if (m_mapping_w8) markMap(offset, MAP_W8);
}
if (isSH) {
checkBP(offset, BreakpointType::Write, 2);
if (m_breakmp_w16 && !isMapMarked(offset, MAP_W16)) {
triggerBP(nullptr, offset, 2, _("Write 16 map"));
}
if (m_mapping_w16) markMap(offset, MAP_W16);
}
if (isSW || isSWR || isSWL || isSWC2) {
checkBP(offset, BreakpointType::Write, 4);
if (m_breakmp_w32 && !isMapMarked(offset, MAP_W32)) {
triggerBP(nullptr, offset, 4, _("Write 32 map"));
}
if (m_mapping_w32) markMap(offset, MAP_W32);
}
// RAM logger: record loads and stores
if (ramLoggerEnabled) {
PSXAddress loadStoreAddr(normalizeAddress(offset));
if (loadStoreAddr.type == PSXAddress::Type::RAM) {
if (isLoad) {
unsigned width = (isLB || isLBU) ? 1 : (isLH || isLHU) ? 2 : 4;
ramLogger->recordAccess(loadStoreAddr.physical, width, RAMLogger::AccessType::Read, cycle);
}
if (isStore) {
unsigned width = isSB ? 1 : isSH ? 2 : 4;
ramLogger->recordAccess(loadStoreAddr.physical, width, RAMLogger::AccessType::Write, cycle);
}
}
}

Check warning on line 241 in src/core/debug.cc

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (main)

❌ Getting worse: Complex Method

PCSX::Debug::process increases in cyclomatic complexity from 88 to 100, threshold = 9. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.
// Are we accessing a kernel address from a non-kernel address, while not in IRQ?
if (!g_emulator->m_cpu->m_inISR && offsetIsInKernel && !wasInKernel) {
if (m_checkKernel) {
Expand Down Expand Up @@ -292,6 +318,16 @@
return keepBP;
}

void PCSX::Debug::logDMAAccess(uint32_t address, uint32_t len, bool isWrite) {
auto *ramLogger = g_emulator->m_ramLogger.get();
if (!ramLogger->isEnabled()) return;
PSXAddress addr(normalizeAddress(address));
if (addr.type != PSXAddress::Type::RAM) return;
uint32_t cycle = static_cast<uint32_t>(g_emulator->m_cpu->m_regs.cycle);
auto type = isWrite ? RAMLogger::AccessType::Write : RAMLogger::AccessType::Read;
ramLogger->recordAccess(addr.physical, len, type, cycle);
}

void PCSX::Debug::checkBP(uint32_t address, BreakpointType type, uint32_t width, const char* cause) {
auto& cpu = g_emulator->m_cpu;
auto& regs = cpu->m_regs;
Expand Down
3 changes: 3 additions & 0 deletions src/core/debug.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,17 @@ class Debug {
void checkDMAread(unsigned c, uint32_t address, uint32_t len) {
std::string cause = fmt::format("DMA channel {} read", c);
checkBP(address, BreakpointType::Read, len, cause.c_str());
logDMAAccess(address, len, false);
}
void checkDMAwrite(unsigned c, uint32_t address, uint32_t len) {
std::string cause = fmt::format("DMA channel {} write", c);
checkBP(address, BreakpointType::Write, len, cause.c_str());
logDMAAccess(address, len, true);
}

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

public:
// call this if PC is being set, like when the emulation is being reset, or when doing fastboot
Expand Down
2 changes: 2 additions & 0 deletions src/core/psxemulator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "core/gpu.h"
#include "core/gpulogger.h"
#include "core/gte.h"
#include "core/ramlogger.h"
#include "core/luaiso.h"
#include "core/mdec.h"
#include "core/pad.h"
Expand Down Expand Up @@ -62,6 +63,7 @@ PCSX::Emulator::Emulator()
m_gdbServer(new PCSX::GdbServer()),
m_gpuLogger(new PCSX::GPULogger()),
m_gte(new PCSX::GTE()),
m_ramLogger(new PCSX::RAMLogger()),
m_hw(new PCSX::HW()),
m_lua(new PCSX::Lua()),
m_mdec(new PCSX::MDEC()),
Expand Down
2 changes: 2 additions & 0 deletions src/core/psxemulator.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ class GdbServer;
class GPU;
class GPULogger;
class GTE;
class RAMLogger;
class HW;
class Lua;
class MDEC;
Expand Down Expand Up @@ -260,6 +261,7 @@ class Emulator {
std::unique_ptr<GPU> m_gpu;
std::unique_ptr<GPULogger> m_gpuLogger;
std::unique_ptr<GTE> m_gte;
std::unique_ptr<RAMLogger> m_ramLogger;
std::unique_ptr<HW> m_hw;
std::unique_ptr<Lua> m_lua;
std::unique_ptr<MDEC> m_mdec;
Expand Down
106 changes: 106 additions & 0 deletions src/core/ramlogger.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/***************************************************************************
* Copyright (C) 2026 PCSX-Redux authors *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
***************************************************************************/

#include "core/ramlogger.h"

#include "core/psxemulator.h"
#include "core/psxmem.h"

void PCSX::RAMLogger::recordAccess(uint32_t physAddr, unsigned width, AccessType type, uint32_t cycle) {
if (!m_enabled) return;

uint32_t* timestamps = (type == AccessType::Read) ? m_readTimestamps
: (type == AccessType::Write) ? m_writeTimestamps
: m_execTimestamps;

for (unsigned i = 0; i < width; i++) {
uint32_t addr = physAddr + i;
if (addr < c_maxBytes) {
timestamps[addr] = cycle;
}
}
}

void PCSX::RAMLogger::enable() {
if (m_hasResources) {
m_enabled = true;
return;
}

auto setupTex = [](OpenGL::Texture& tex) {
tex.bind();
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
};

// Create heatmap textures (GL_R32UI - unsigned integer)
m_readHeatmapTex.create(c_width, c_maxHeight, GL_R32UI);
m_writeHeatmapTex.create(c_width, c_maxHeight, GL_R32UI);
m_execHeatmapTex.create(c_width, c_maxHeight, GL_R32UI);
if (!m_readHeatmapTex.exists() || !m_writeHeatmapTex.exists() || !m_execHeatmapTex.exists()) return;

Check warning on line 58 in src/core/ramlogger.cc

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (main)

❌ New issue: Complex Conditional

PCSX::RAMLogger::enable has 1 complex conditionals with 2 branches, threshold = 2. A complex conditional is an expression inside a branch (e.g. if, for, while) which consists of multiple, logical operators such as AND/OR. The more logical operators in an expression, the more severe the code smell.
setupTex(m_readHeatmapTex);
setupTex(m_writeHeatmapTex);
setupTex(m_execHeatmapTex);

// Create RAM data texture (GL_R8)
m_ramTexture.create(c_width, c_maxHeight, GL_R8);
if (!m_ramTexture.exists()) return;
setupTex(m_ramTexture);

// Clear timestamp arrays
memset(m_readTimestamps, 0, sizeof(m_readTimestamps));
memset(m_writeTimestamps, 0, sizeof(m_writeTimestamps));
memset(m_execTimestamps, 0, sizeof(m_execTimestamps));

m_hasResources = true;
m_enabled = true;
}

void PCSX::RAMLogger::disable() {
m_enabled = false;
}

void PCSX::RAMLogger::uploadRAM() {
if (!m_hasResources) return;
bool is8MB = g_emulator->settings.get<Emulator::Setting8MB>();
int height = is8MB ? c_maxHeight : (c_maxHeight / 4);

m_ramTexture.bind();
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, c_width, height, GL_RED, GL_UNSIGNED_BYTE, g_emulator->m_mem->m_wram);
}

void PCSX::RAMLogger::uploadHeatmaps() {
if (!m_hasResources) return;
bool is8MB = g_emulator->settings.get<Emulator::Setting8MB>();
int height = is8MB ? c_maxHeight : (c_maxHeight / 4);

glPixelStorei(GL_UNPACK_ALIGNMENT, 4);

m_readHeatmapTex.bind();
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, c_width, height, GL_RED_INTEGER, GL_UNSIGNED_INT, m_readTimestamps);

m_writeHeatmapTex.bind();
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, c_width, height, GL_RED_INTEGER, GL_UNSIGNED_INT, m_writeTimestamps);

m_execHeatmapTex.bind();
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, c_width, height, GL_RED_INTEGER, GL_UNSIGNED_INT, m_execTimestamps);
}
77 changes: 77 additions & 0 deletions src/core/ramlogger.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/***************************************************************************
* Copyright (C) 2026 PCSX-Redux authors *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
***************************************************************************/

#pragma once

#include <stdint.h>
#include <string.h>

#include "support/opengl.h"

namespace PCSX {

namespace Widgets {
class RAMViewer;
}

class RAMLogger {
public:
enum class AccessType { Read, Write, Execute };

static constexpr int c_width = 2048;
static constexpr int c_maxHeight = 4096; // 8MB / 2048
static constexpr size_t c_maxBytes = c_width * c_maxHeight; // 8MB

void recordAccess(uint32_t physAddr, unsigned width, AccessType type, uint32_t cycle);

void enable();
void disable();
bool isEnabled() const { return m_enabled; }

void uploadRAM();
void uploadHeatmaps();

void bindReadHeatmap() { m_readHeatmapTex.bind(); }
void bindWriteHeatmap() { m_writeHeatmapTex.bind(); }
void bindExecHeatmap() { m_execHeatmapTex.bind(); }
void bindRAMTexture() { m_ramTexture.bind(); }
GLuint getRAMTextureID() { return m_ramTexture.handle(); }

// Configurable decay half-life in cycles (how many cycles until intensity halves)
float m_decayHalfLife = 33868800.0f; // ~1 second at 33.8MHz

private:
bool m_enabled = false;
bool m_hasResources = false;

// Per-byte cycle timestamp arrays (low 32 bits of cycle counter)
uint32_t m_readTimestamps[c_maxBytes] = {};
uint32_t m_writeTimestamps[c_maxBytes] = {};
uint32_t m_execTimestamps[c_maxBytes] = {};

// Heatmap textures (GL_R32UI, 2048 x maxHeight)
OpenGL::Texture m_readHeatmapTex, m_writeHeatmapTex, m_execHeatmapTex;

// Raw RAM texture (GL_R8, 2048 x maxHeight)
OpenGL::Texture m_ramTexture;

friend class Widgets::RAMViewer;
};

} // namespace PCSX
4 changes: 4 additions & 0 deletions src/core/system.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ struct VRAMClick {
float x, y;
VRAMMode vramMode;
};
struct RAMFocus {
uint32_t address;
uint32_t size;
};
} // namespace GUI
struct Keyboard {
int key, scancode, action, mods;
Expand Down
13 changes: 13 additions & 0 deletions src/gui/gui.cc
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ extern "C" {
#include "core/gdb-server.h"
#include "core/gpu.h"
#include "core/gpulogger.h"
#include "core/ramlogger.h"
#include "core/pad.h"
#include "core/psxemulator.h"
#include "core/psxmem.h"
Expand Down Expand Up @@ -1406,6 +1407,7 @@ in Configuration->Emulation, restart PCSX-Redux, then try again.)"));
ImGui::EndMenu();
}
ImGui::MenuItem(_("Show Memory Observer"), nullptr, &m_memoryObserver.m_show);
ImGui::MenuItem(_("Show RAM viewer"), nullptr, &m_ramViewer.m_show);
ImGui::MenuItem(_("Show Typed Debugger"), nullptr, &m_typedDebugger.m_show);
ImGui::MenuItem(_("Show Patches"), nullptr, &m_patches.m_show);
ImGui::MenuItem(_("Show Interrupts Scaler"), nullptr, &m_showInterruptsScaler);
Expand Down Expand Up @@ -1582,6 +1584,17 @@ in Configuration->Emulation, restart PCSX-Redux, then try again.)"));
}
}

if (m_ramViewer.m_show) {
auto *ramLogger = g_emulator->m_ramLogger.get();
if (!ramLogger->isEnabled()) ramLogger->enable();
ramLogger->uploadRAM();
ramLogger->uploadHeatmaps();
m_ramViewer.draw(this);
} else {
auto *ramLogger = g_emulator->m_ramLogger.get();
if (ramLogger->isEnabled()) ramLogger->disable();
}

if (m_log.m_show) {
ImGui::SetNextWindowPos(ImVec2(10, 540), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(ImVec2(1200, 250), ImGuiCond_FirstUseEver);
Expand Down
6 changes: 5 additions & 1 deletion src/gui/gui.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
#include "gui/widgets/registers.h"
#include "gui/widgets/shader-editor.h"
#include "gui/widgets/sio1.h"
#include "gui/widgets/ram-viewer.h"
#include "gui/widgets/vram-viewer.h"
#include "imgui.h"
#include "imgui_md/imgui_md.h"
Expand Down Expand Up @@ -113,6 +114,7 @@ class GUI final : public UI {
typedef Setting<bool, TYPESTRING("ShowSIO1")> ShowSIO1;
typedef Setting<bool, TYPESTRING("ShowIsoBrowser")> ShowIsoBrowser;
typedef Setting<bool, TYPESTRING("ShowGPULogger")> ShowGPULogger;
typedef Setting<bool, TYPESTRING("ShowRAMViewer")> ShowRAMViewer;
typedef Setting<int, TYPESTRING("WindowPosX"), 0> WindowPosX;
typedef Setting<int, TYPESTRING("WindowPosY"), 0> WindowPosY;
typedef Setting<int, TYPESTRING("WindowSizeX"), 1280> WindowSizeX;
Expand Down Expand Up @@ -157,7 +159,8 @@ class GUI final : public UI {
ShowCLUTVRAMViewer, ShowVRAMViewer1, ShowVRAMViewer2, ShowVRAMViewer3, ShowVRAMViewer4, ShowMemoryObserver,
ShowTypedDebugger, ShowPatches, ShowMemcardManager, ShowRegisters, ShowAssembly, ShowDisassembly,
ShowBreakpoints, ShowNamedSaveStates, ShowEvents, ShowHandlers, ShowKernelLog, ShowCallstacks, ShowSIO1,
ShowIsoBrowser, ShowGPULogger, MainFontSize, MonoFontSize, GUITheme, AllowMouseCaptureToggle,
ShowIsoBrowser, ShowGPULogger, ShowRAMViewer, MainFontSize, MonoFontSize, GUITheme,
AllowMouseCaptureToggle,
EnableRawMouseMotion, WidescreenRatio, ShowPIOCartConfig, ShowMemoryEditor1, ShowMemoryEditor2,
ShowMemoryEditor3, ShowMemoryEditor4, ShowMemoryEditor5, ShowMemoryEditor6, ShowMemoryEditor7,
ShowMemoryEditor8, ShowParallelPortEditor, ShowScratchpadEditor, ShowHWRegsEditor, ShowBiosEditor,
Expand Down Expand Up @@ -391,6 +394,7 @@ class GUI final : public UI {
{settings.get<ShowVRAMViewer3>().value},
{settings.get<ShowVRAMViewer4>().value}};

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

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