Skip to content

feat(ps1): complete Phase 2 GPU capture for #418#645

Merged
fernandotonon merged 2 commits into
masterfrom
feat/ps1-issue-418-phase2
May 20, 2026
Merged

feat(ps1): complete Phase 2 GPU capture for #418#645
fernandotonon merged 2 commits into
masterfrom
feat/ps1-issue-418-phase2

Conversation

@fernandotonon
Copy link
Copy Markdown
Owner

@fernandotonon fernandotonon commented May 20, 2026

Summary

  • Adds Gp0HookDispatch with GP0 opcode dispatch linked directly by the libretro plugin (GPL boundary clean)
  • Completes VRAM upload (0xA0) and read-back (0xC0) hook paths in the parser and RipperHooks
  • Adds Sentry breadcrumb ps1.rip.capture.frame_armed, parser tests for all 7 primitive flavors + CSV round-trip, and disarmed-overhead regression test
  • Removes dead Qt5 X11Extras link that broke Qt6 Linux builds

Test plan

  • xvfb-run ./build_local/bin/UnitTests --gtest_filter="GpuCommandParser*:Gp0HookDispatch*:PsxGpuRamScanner*" — 14/14 pass
  • CI unit-tests-linux + SonarCloud

Closes remaining acceptance criteria for #418.

Made with Cursor

Summary by CodeRabbit

  • Improvements

    • Enhanced GPU command parsing with improved VRAM transfer handling for more accurate capture
    • Optimized capture performance when disabled
    • Added matrix ID tracking for GPU operations
  • Tests

    • Added comprehensive unit tests for GPU command parsing and system memory capture

Review Change Stack

Move GP0 opcode dispatch into Gp0HookDispatch linked by the libretro plugin,
add full A0/C0 VRAM hook handling, Sentry breadcrumb ps1.rip.capture.frame_armed,
parser tests for all seven primitive flavors, and a disarmed-overhead regression test.

Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 20, 2026

Warning

Rate limit exceeded

@fernandotonon has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 29 minutes and 20 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ab77376a-7512-4192-993e-899f7a14d128

📥 Commits

Reviewing files that changed from the base of the PR and between 949b6c7 and b46743f.

📒 Files selected for processing (3)
  • src/PS1/runtime/Gp0HookDispatch_test.cpp
  • src/PS1/runtime/PS1RipManager.cpp
  • src/PS1/runtime/RipperHooks.cpp
📝 Walkthrough

Walkthrough

This PR refactors the PS1 GPU command capture pipeline by introducing Gp0HookDispatch as a centralized dispatch and capture system. GpuCommandParser::Gp0Step gains explicit VRAM read/write tracking, EmuHooks gains a latestMatrixId() virtual method, and both RipperHooks and PsxGpuRamScanner delegate to the new dispatcher instead of implementing capture locally.

Changes

GPU Command Dispatch Refactor

Layer / File(s) Summary
Hook API Extension and GPU Command Parser Enhancement
src/PS1/runtime/EmuHooks.h, src/PS1/runtime/GpuCommandParser.h, src/PS1/runtime/GpuCommandParser.cpp, src/PS1/runtime/GpuCommandParser_test.cpp
EmuHooks gains virtual latestMatrixId() returning UINT32_MAX by default. Gp0Step extends with VRAM operation fields (flags, coordinates, dimensions, pixel pointers). Parser handles 0xA0 (VRAM upload) and 0xC0 (VRAM readback) with explicit coordinate/size decoding and payload tracking. Tests cover mono-quads, shaded-quads, CSV round-trip, and VRAM upload/readback.
Gp0HookDispatch: Centralized GPU Command Dispatch System
src/PS1/runtime/Gp0HookDispatch.h, src/PS1/runtime/Gp0HookDispatch.cpp, src/PS1/runtime/Gp0HookDispatch_test.cpp
New Gp0HookDispatch class with three static methods: dispatchStep routes commands through draw-mode and VRAM hooks while enforcing primitive limits; captureFromSystemRam scans RAM heuristically, parses GP0 steps, deduplicates, and applies filtering; captureFrameFromSystemRam wraps capture with frame events and projection matrix handling. Includes internal helpers for opcode detection, draw-mode application, and dedup key generation. Benchmark test validates disarmed overhead stays under 1% of armed cost.
Hook Integration and RAM Scanner Refactoring
src/PS1/runtime/RipperHooks.h, src/PS1/runtime/RipperHooks.cpp, src/PS1/runtime/PsxGpuRamScanner.h, src/PS1/runtime/PsxGpuRamScanner.cpp
RipperHooks::latestMatrixId() becomes an override. RipperHooks::ingestSystemRamForGpuCapture delegates entirely to Gp0HookDispatch::captureFrameFromSystemRam. Enhanced onVramRead to clamp extents and mark sampled pixels via m_vram->pixel(). PsxGpuRamScanner accepts generic EmuHooks*, removes local parsing loop, and forwards to Gp0HookDispatch.
Libretro Integration
plugins/ps1core_libretro/LibretroEmuCore.h, plugins/ps1core_libretro/LibretroEmuCore.cpp
LibretroEmuCore::captureGpuFromRam() replaces hook instance method call with Gp0HookDispatch::captureFrameFromSystemRam(). Removes RipperHooks forward declaration from header.
Build Configuration and Logging Updates
plugins/ps1core_libretro/CMakeLists.txt, src/PS1/CMakeLists.txt, tests/CMakeLists.txt, src/CMakeLists.txt, src/PS1/runtime/PS1RipManager.cpp
Adds Gp0HookDispatch.cpp to libretro plugin and test executables under ENABLE_PS1_RIP. Removes Qt5 X11 dependency from main CMakeLists. Updates Sentry breadcrumb namespace from ui.action to ps1.rip.capture.frame_armed with armed/disarmed messages.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • fernandotonon/QtMeshEditor#581: Lays down the Phase-2 GP0 capture/hook pipeline with initial GpuCommandParser and EmuHooks/RipperHooks interception API; this PR builds on it by adding Gp0HookDispatch-driven capture and refining VRAM opcode (0xA0/0xC0) decoding.

🐰 A dispatcher hops in, GP0 commands align,
VRAM reads and writes now parse just fine,
Hooks delegate with override flair,
Capture refactored with utmost care!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.35% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: completing GPU capture phase 2 for a specific issue (#418) in the PS1 module.
Description check ✅ Passed The description provides a clear summary of the solution and lists specific technical changes across features, but lacks organization under the template sections and omits detailed technical breakdown.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ps1-issue-418-phase2

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/PS1/runtime/Gp0HookDispatch_test.cpp`:
- Around line 39-42: The two brittle timing assertions using EXPECT_LT on
disarmedNs and armedNs should be replaced: instead of relying on wall-clock
ratios (the EXPECT_LT comparisons), change the test to a deterministic check
that validates the early-return/disarmed path behavior (for example, assert that
the disarmed path did not invoke the scan handler or increment a side-effect
counter) or, if timing must be used, relax and stabilize the criterion (e.g.,
assert disarmedNs <= armedNs and set a much looser ratio threshold). Update the
assertions around the variables disarmedNs and armedNs (and any EXPECT_LT
usages) to implement the deterministic side-effect verification or the relaxed
threshold so CI noise cannot flake the test.

In `@src/PS1/runtime/Gp0HookDispatch.cpp`:
- Around line 59-69: primDedupeKey currently omits per-vertex UVs (and colors),
causing distinct textured primitives with identical geometry to collapse; update
primDedupeKey (and use PrimRecord fields) to incorporate UV coordinates for each
vertex (e.g., prim.uvs or prim.verts[v].u/ .v) into the dedupe string and also
include primitive color/pixel color fields if present (e.g., prim.color or
prim.verts[v].color); restrict adding UVs to textured primitive kinds (check
prim.kind for textured types) to avoid bloating keys for non-textured kinds, and
format/arg them consistently with the existing vertex/attribute concatenation so
the key remains stable.

In `@src/PS1/runtime/PS1RipManager.cpp`:
- Around line 342-346: In PS1RipManager::armCapture replace the current
SentryReporter::addBreadcrumb calls that use the specific category
"ps1.rip.capture.frame_armed" with the required category "ui.action" for both
the armed and disarmed branches, and keep the action detail in the message (e.g.
include "ps1.rip.capture.frame_armed: armed" / "ps1.rip.capture.frame_armed:
disarmed" or otherwise preserve the "armed"/"disarmed" detail) so the breadcrumb
uses SentryReporter::addBreadcrumb("ui.action", <message>) while preserving the
action context.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 381a2284-e5d1-4a7d-85c0-551766f789dd

📥 Commits

Reviewing files that changed from the base of the PR and between 3650a13 and 949b6c7.

📒 Files selected for processing (18)
  • plugins/ps1core_libretro/CMakeLists.txt
  • plugins/ps1core_libretro/LibretroEmuCore.cpp
  • plugins/ps1core_libretro/LibretroEmuCore.h
  • src/CMakeLists.txt
  • src/PS1/CMakeLists.txt
  • src/PS1/runtime/EmuHooks.h
  • src/PS1/runtime/Gp0HookDispatch.cpp
  • src/PS1/runtime/Gp0HookDispatch.h
  • src/PS1/runtime/Gp0HookDispatch_test.cpp
  • src/PS1/runtime/GpuCommandParser.cpp
  • src/PS1/runtime/GpuCommandParser.h
  • src/PS1/runtime/GpuCommandParser_test.cpp
  • src/PS1/runtime/PS1RipManager.cpp
  • src/PS1/runtime/PsxGpuRamScanner.cpp
  • src/PS1/runtime/PsxGpuRamScanner.h
  • src/PS1/runtime/RipperHooks.cpp
  • src/PS1/runtime/RipperHooks.h
  • tests/CMakeLists.txt
💤 Files with no reviewable changes (2)
  • plugins/ps1core_libretro/LibretroEmuCore.h
  • src/CMakeLists.txt

Comment thread src/PS1/runtime/Gp0HookDispatch_test.cpp Outdated
Comment on lines +59 to +69
QString primDedupeKey(const PrimRecord &prim)
{
QString key = QStringLiteral("%1|%2").arg(static_cast<int>(prim.kind)).arg(prim.vertexCount);
for (int v = 0; v < 4; ++v)
key += QStringLiteral("|%1,%2").arg(prim.verts[v].x).arg(prim.verts[v].y);
key += QStringLiteral("|%1,%2|%3|%4")
.arg(prim.tpage)
.arg(prim.clut)
.arg(prim.semiTrans)
.arg(prim.matrixId);
return key;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Dedupe key is too coarse and can drop valid textured primitives.

primDedupeKey ignores UVs (and color), so two different primitives with identical geometry can collapse into one capture entry. Include UVs (at least for textured kinds) in the key to avoid false-positive deduplication.

Proposed patch
 QString primDedupeKey(const PrimRecord &prim)
 {
     QString key = QStringLiteral("%1|%2").arg(static_cast<int>(prim.kind)).arg(prim.vertexCount);
-    for (int v = 0; v < 4; ++v)
-        key += QStringLiteral("|%1,%2").arg(prim.verts[v].x).arg(prim.verts[v].y);
+    for (int v = 0; v < 4; ++v) {
+        key += QStringLiteral("|%1,%2").arg(prim.verts[v].x).arg(prim.verts[v].y);
+        key += QStringLiteral("|%1,%2").arg(prim.verts[v].u).arg(prim.verts[v].v);
+    }
     key += QStringLiteral("|%1,%2|%3|%4")
                .arg(prim.tpage)
                .arg(prim.clut)
                .arg(prim.semiTrans)
                .arg(prim.matrixId);
     return key;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/PS1/runtime/Gp0HookDispatch.cpp` around lines 59 - 69, primDedupeKey
currently omits per-vertex UVs (and colors), causing distinct textured
primitives with identical geometry to collapse; update primDedupeKey (and use
PrimRecord fields) to incorporate UV coordinates for each vertex (e.g., prim.uvs
or prim.verts[v].u/ .v) into the dedupe string and also include primitive
color/pixel color fields if present (e.g., prim.color or prim.verts[v].color);
restrict adding UVs to textured primitive kinds (check prim.kind for textured
types) to avoid bloating keys for non-textured kinds, and format/arg them
consistently with the existing vertex/attribute concatenation so the key remains
stable.

Comment thread src/PS1/runtime/PS1RipManager.cpp Outdated
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 949b6c7a2c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/PS1/runtime/RipperHooks.cpp Outdated
const int xEnd = qMin(static_cast<int>(x) + static_cast<int>(w), VramSnapshot::kWidth);
const int yEnd = qMin(static_cast<int>(y) + static_cast<int>(h), VramSnapshot::kHeight);
for (int yy = static_cast<int>(y); yy < yEnd; ++yy) {
for (int xx = static_cast<int>(x); xx < xEnd; ++xx)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Remove no-op per-pixel loop from VRAM read hook

onVramRead now iterates every pixel in the requested rectangle but discards the value, so it has no observable effect on capture state while adding O(w*h) work per GP0 0xC0 packet. In this codebase the RAM scanner is heuristic (looksLikeGp0Opcode only checks opcode bytes), so false-positive read-back packets are expected; each one can trigger up to a full 1024x512 walk here, which can significantly degrade armed-capture performance without improving correctness.

Useful? React with 👍 / 👎.

Use ui.action breadcrumbs with frame_armed detail, replace flaky timing
benchmark with deterministic disarmed-capture test, and drop O(w*h) VRAM
read-back loop.

Co-authored-by: Cursor <cursoragent@cursor.com>
@sonarqubecloud
Copy link
Copy Markdown

@fernandotonon fernandotonon merged commit e7b5877 into master May 20, 2026
20 checks passed
@fernandotonon fernandotonon deleted the feat/ps1-issue-418-phase2 branch May 20, 2026 19:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant