Skip to content

feat(utils): add stereo support to Util::Subrect::Controller#2356

Closed
alandtse wants to merge 2 commits into
community-shaders:devfrom
alandtse:pr1-stereo-subrect
Closed

feat(utils): add stereo support to Util::Subrect::Controller#2356
alandtse wants to merge 2 commits into
community-shaders:devfrom
alandtse:pr1-stereo-subrect

Conversation

@alandtse
Copy link
Copy Markdown
Collaborator

@alandtse alandtse commented May 16, 2026

Summary

Opt-in stereo extension to the screenshot subrect controller so future stereo-aware consumers (the DLSS Enhancer foveated subrect framework decomposed from #2096) can track a mirrored right-eye UV without forking the util that ScreenshotFeature already ships.

Mono callers (ScreenshotFeature) are unaffected: SaveSettings emits bit-identical JSON, LoadSettings round-trips the legacy schema, and the DrawEditor signature is unchanged.

What changes

New API on Util::Subrect::Controller:

  • SetStereoEnabled(bool) / IsStereoEnabled() — opt-in toggle (default off)
  • GetRightEyeUV() — folds onto the primary UV in mono mode so callers can stay agnostic of stereo state
  • GetStereoPixelRegions(fullW, fullH) — resolves both eyes against per-eye half-width for SBS textures
  • Preset gains an optional rightUV{} field (default-init when designated initializers omit it)

Behavior:

  • In stereo mode, drag/slider edits in DrawEditor auto-mirror the primary UV to the right eye around x=0.5 (HMD nose-side overlap symmetry)
  • SaveSettings only emits the new CropRight* and preset right_uv keys when stereo is enabled — preserves existing on-disk shape
  • LoadSettings mirrors left → right only for the legacy upgrade case (CropX/Y/W/H present, no CropRight*), so a seeded preset's explicit right-eye UV survives a fresh load

The header is now self-contained (forward-declares D3D11 types, includes nlohmann/json directly, declares its own using json = nlohmann::json;). This lets a unit-test target compile it without the plugin PCH.

Tests

Adds a new tests/cpp/ target with a Catch2 unit test executable (cpp_tests). Reuses Catch2 via FetchContent dedup with tests/shaders. New CMake target run_cpp_tests.

9 test cases, 33 assertions, covering:

  • Mono back-compat: LoadSettings round-trips legacy JSON unchanged; SaveSettings emits no right-eye keys
  • Stereo round-trip: explicit asymmetric right-eye preserved through save/load
  • Legacy upgrade: stereo mode mirrors CropX/Y/W/H to right eye when no CropRight* keys present
  • GetStereoPixelRegions SBS half-width split + mono-mode fallback (identical eyes)
  • MirrorUVHorizontal math via SetStereoEnabled

Tests caught a real bug in my initial LoadSettings logic — an unconditional post-load SyncRightUV was stomping on a preset's explicit right-eye value when JSON had no top-level Crop* keys. Now correctly gated on hasExplicitLeft && !hasExplicitRight.

Why this PR exists

Decomposed from @YtzyFvra's #2096 — the original PR forked this util into its own Subrect:: namespace, which conflicted with #2092's later landing of Util::Subrect::Controller for screenshot. This PR establishes the shared API so the DLSS Enhancer subrect work (PR-3 of the decomposition) can consume it cleanly without a second fork.

See DLSS-PR-PLAN.md for the full decomposition strategy. PR-2 (DLSSperf) and PR-3 (DlssEnhancer foveated subrect) are on my fork awaiting separate review.

Test plan

  • Unit tests pass (33 assertions / 9 cases)
  • Mono-mode behavior verified by reading every ScreenshotFeature call site against the new code paths — identical observable behavior
  • Manual smoke test: launch SkyrimVR, take a cropped screenshot via each preset (Left Eye / Right Eye / Full Frame), reload, verify crop persisted

Credits

Decomposed from @YtzyFvra's #2096 (PR-1 of 4). Stereo API design is theirs; this PR adapts it to extend dev's existing Util::Subrect rather than fork it. Co-authored on commit.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Stereo-aware sub-rectangle cropping with independent left/right eye UVs, toggleable stereo mode, and automatic horizontal mirroring from left→right
    • Editor preset save/load now preserves per-eye UVs and mirrors/syncs right-eye when enabling stereo
    • Per-eye pixel region calculation for side-by-side textures
  • Tests

    • Added comprehensive C++ unit tests validating stereo behavior, legacy compatibility, and pixel-region splitting

Review Change Stack

Adds an opt-in stereo extension to the screenshot's subrect controller
so stereo-aware consumers (the DLSS Enhancer subrect framework) can
track a mirrored right-eye UV alongside the existing primary UV without
forking the util. Mono callers (ScreenshotFeature) are unaffected:
SaveSettings emits bit-identical JSON, LoadSettings round-trips the
legacy schema, and the DrawEditor signature is unchanged.

New API:
- SetStereoEnabled(bool) / IsStereoEnabled()
- GetRightEyeUV() — folds onto primary UV in mono mode
- GetStereoPixelRegions(fullW, fullH) — resolves both eyes against
  per-eye half-width for SBS textures
- Preset gains an optional rightUV{} field

In stereo mode, drag/slider edits auto-mirror the primary UV to the
right eye around x=0.5 (HMD nose-side overlap symmetry). LoadSettings
mirrors left → right only for the legacy upgrade case (CropX/Y/W/H
present, no CropRight*), so a seeded preset's explicit right-eye UV
survives a fresh load.

The header is now self-contained (forward-declares D3D11 types,
includes nlohmann/json), so a test target can compile it without the
plugin PCH.

Adds a C++ unit test target (tests/cpp) covering mono back-compat,
stereo round-trip, legacy upgrade mirror, and per-eye pixel-region
math. 9 test cases, 33 assertions. Reuses Catch2 via FetchContent
dedup with tests/shaders. New CMake target run_cpp_tests.

Decomposed from community-shaders#2096 (PR-1 of 4); see DLSS-PR-PLAN.md.

Co-Authored-By: YtzyFvra <59631290+YtzyFvra@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 16, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: cb0fdd02-815d-4d88-9ccf-c7f18e8c002a

📥 Commits

Reviewing files that changed from the base of the PR and between 3df2186 and 499ce35.

📒 Files selected for processing (2)
  • src/Utils/Subrect.cpp
  • tests/cpp/test_subrect.cpp
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/cpp/test_subrect.cpp
  • src/Utils/Subrect.cpp

📝 Walkthrough

Walkthrough

Adds stereo-aware right-eye UVs to Util::Subrect::Controller with mirroring, persistence, editor sync, SBS per-eye pixel-region resolution, and a Catch2 C++ test suite plus optional CMake wiring and a run target.

Changes

Stereo Sub-Rectangle Cropping Support

Layer / File(s) Summary
Header API Definition
src/Utils/Subrect.h
Adds json alias, D3D forward-decls, StereoPixelRegions, Preset.rightUV, Controller stereo API: SetStereoEnabled(), IsStereoEnabled(), GetStereoPixelRegions(), GetRightEyeUV(), internal currentRightUV/stereoEnabled, and SyncRightUV() declaration.
Core Implementation Foundation
src/Utils/Subrect.cpp
Adds includes, MirrorUVHorizontal helper and SetStereoEnabled() implementation; basic mirroring and clamping utilities used by controller.
Settings Persistence
src/Utils/Subrect.cpp
LoadSettings reads optional CropRight* keys and per-preset right_uv (legacy mirror fallback); SaveSettings writes CropRight* and right_uv when stereo enabled.
Editor Integration & UV Lifecycle
src/Utils/Subrect.cpp
EnsureDefaultPreset initializes currentRightUV; "Save Preset" writes both UVs; ClampCurrentUV/ApplyPreset handle right-eye; DrawEditor calls SyncRightUV() after slider/drag edits.
Stereo Pixel Region Resolution
src/Utils/Subrect.cpp
GetStereoPixelRegions() splits SBS width and returns left/right PixelRegion using currentUV and currentRightUV (falls back to currentUV in mono).
CMake Test Infrastructure
CMakeLists.txt, tests/cpp/CMakeLists.txt, tests/cpp/test_main.cpp
Root CMake optionally includes tests/cpp, defines run_cpp_tests if cpp_tests target exists; tests/cpp CMakeLists fetches Catch2, builds cpp_tests (compiling Subrect.cpp into tests), and registers CTest; test_main.cpp provides test executable entrypoint.
Unit Tests
tests/cpp/test_subrect.cpp
Catch2 suite covering mono defaults, Save/Load mono and stereo, legacy mirroring, toggle behavior, SBS pixel-region halves per eye, identical regions in mono, regression for preset right_uv, and mirror-math correctness.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • doodlum
  • jiayev
  • davo0411

Poem

I nibbled code with careful paws,
Mirrored eyes and cloned UV laws,
When sliders move and presets save,
Both eyes behave — no buggy cave.
Tests hop in to keep the peace. 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.00% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(utils): add stereo support to Util::Subrect::Controller' accurately describes the main change: adding stereo functionality to the Subrect utility's Controller class.
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

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 OpenGrep (1.20.0)

OpenGrep fatal error (exit code 2): [00.12][ERROR]: Error: exception Unix_error: No such file or directory stat tests/cpp/test_subrect.cpp
Raised by primitive operation at UTmp.replace_named_pipe_by_regular_file_if_needed in file "libs/commons/UTmp.ml", line 145, characters 8-27
Called from Scan_CLI.replace_target_roots_by_regular_files_where_needed.(fun) in file "src/osemgrep/cli_scan/Scan_CLI.ml", lines 1086-1087, characters 19-65
Called from List_.fast_map in file "libs/commons/List_.ml", line 81, characters 17-20
Called from S


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.

@github-actions
Copy link
Copy Markdown

No actionable suggestions for changed features.

Copy link
Copy Markdown
Contributor

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/Utils/Subrect.cpp (1)

211-218: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preset save omits right-eye UV in stereo mode.

When saving a new preset while stereo is enabled, only currentUV is stored. The currentRightUV is not captured, so the saved preset will have a default-initialized rightUV. When re-applied, the preset loses the user's right-eye crop.

Proposed fix
 		if (ImGui::Button("Save Preset")) {
 			std::string presetName = newPresetName;
 			if (!presetName.empty()) {
-				presets.push_back(Preset{ .name = presetName, .uv = currentUV });
+				presets.push_back(Preset{ .name = presetName, .uv = currentUV, .rightUV = currentRightUV });
 				selectedPresetIndex = static_cast<int>(presets.size()) - 1;
 				newPresetName[0] = '\0';
 			}
 		}
🤖 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/Utils/Subrect.cpp` around lines 211 - 218, The Save Preset button handler
saves only currentUV and omits the right-eye UV, so update the push to presets
in the ImGui::Button("Save Preset") block to construct the Preset with both .uv
= currentUV and .rightUV = currentRightUV (use the Preset aggregate initialized
where presets.push_back(Preset{ .name = presetName, .uv = currentUV }) is
currently used), leaving selectedPresetIndex and newPresetName handling
unchanged; this ensures a saved preset captures both eyes' UVs and will restore
the right-eye crop when re-applied.
🧹 Nitpick comments (2)
CMakeLists.txt (2)

1052-1061: ⚡ Quick win

Hook run_cpp_tests into package/deploy flows to prevent untested artifacts

Right now C++ tests are runnable but not enforced before packaging/deployment, so regressions can slip through even when shader tests are gated.

Proposed CMake wiring
 if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tests/cpp/CMakeLists.txt")
     enable_testing()
     add_subdirectory(tests/cpp)
     if(TARGET cpp_tests)
         add_custom_target(
             run_cpp_tests
             COMMAND $<TARGET_FILE:cpp_tests> --reporter compact
             DEPENDS cpp_tests
             WORKING_DIRECTORY $<TARGET_FILE_DIR:cpp_tests>
             COMMENT "Running C++ unit tests..."
             VERBATIM
         )
+        # Keep packaging/deploy parity with shader test gating.
+        if(TARGET "Package-Core")
+            add_dependencies("Package-Core" run_cpp_tests)
+        endif()
+        if(TARGET "Package-AIO-Manual")
+            add_dependencies("Package-AIO-Manual" run_cpp_tests)
+        endif()
+        if(TARGET DEPLOY_ALL)
+            add_dependencies(DEPLOY_ALL run_cpp_tests)
+        endif()
     endif()
 endif()
🤖 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 `@CMakeLists.txt` around lines 1052 - 1061, The C++ test target run_cpp_tests
is defined but not run as part of packaging/deploy; make the package/deploy
targets depend on it so artifacts are only produced after tests pass. Locate the
add_custom_target for run_cpp_tests and add add_dependencies(<package_target>
run_cpp_tests) and add_dependencies(<deploy_target> run_cpp_tests) (or include
run_cpp_tests in the DEPENDS list of your existing package/deploy
add_custom_target/add_library calls) so that the package and deploy targets
(replace <package_target>/<deploy_target> with your actual package/deploy target
names) will fail if cpp_tests fails; ensure WORKING_DIRECTORY and VERBATIM
remain unchanged for run_cpp_tests.

1047-1049: 💤 Low value

PR metadata polish: shorten title and add issue keyword

Suggested title (≤50 chars): feat(utils): add stereo subrect support
Suggested footer/body: Implements #2096``

As per coding guidelines, "Conventional Commit Titles ... Length: 50 characters limit for title" and "Issue References ... 'Implements #123' for features."

🤖 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 `@CMakeLists.txt` around lines 1047 - 1049, Update the PR metadata: shorten the
pull request title to "feat(utils): add stereo subrect support" (≤50 chars) and
add "Implements `#2096`" to the PR body or footer to reference the issue; apply
this change in the repository hosting UI or the commit/PR description so the
title and footer match the conventional commit and issue reference guidelines.
🤖 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.

Outside diff comments:
In `@src/Utils/Subrect.cpp`:
- Around line 211-218: The Save Preset button handler saves only currentUV and
omits the right-eye UV, so update the push to presets in the ImGui::Button("Save
Preset") block to construct the Preset with both .uv = currentUV and .rightUV =
currentRightUV (use the Preset aggregate initialized where
presets.push_back(Preset{ .name = presetName, .uv = currentUV }) is currently
used), leaving selectedPresetIndex and newPresetName handling unchanged; this
ensures a saved preset captures both eyes' UVs and will restore the right-eye
crop when re-applied.

---

Nitpick comments:
In `@CMakeLists.txt`:
- Around line 1052-1061: The C++ test target run_cpp_tests is defined but not
run as part of packaging/deploy; make the package/deploy targets depend on it so
artifacts are only produced after tests pass. Locate the add_custom_target for
run_cpp_tests and add add_dependencies(<package_target> run_cpp_tests) and
add_dependencies(<deploy_target> run_cpp_tests) (or include run_cpp_tests in the
DEPENDS list of your existing package/deploy add_custom_target/add_library
calls) so that the package and deploy targets (replace
<package_target>/<deploy_target> with your actual package/deploy target names)
will fail if cpp_tests fails; ensure WORKING_DIRECTORY and VERBATIM remain
unchanged for run_cpp_tests.
- Around line 1047-1049: Update the PR metadata: shorten the pull request title
to "feat(utils): add stereo subrect support" (≤50 chars) and add "Implements
`#2096`" to the PR body or footer to reference the issue; apply this change in the
repository hosting UI or the commit/PR description so the title and footer match
the conventional commit and issue reference guidelines.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c77719ee-a87d-4734-9545-48915b36eda5

📥 Commits

Reviewing files that changed from the base of the PR and between 8c9b18c and 3df2186.

📒 Files selected for processing (6)
  • CMakeLists.txt
  • src/Utils/Subrect.cpp
  • src/Utils/Subrect.h
  • tests/cpp/CMakeLists.txt
  • tests/cpp/test_main.cpp
  • tests/cpp/test_subrect.cpp

CodeRabbit Major @ scs#2356: the Save Preset button only stored
currentUV, so a saved preset's rightUV was default-initialized.
Re-applying that preset would zero out the user's right-eye crop
in stereo mode.

Fix by extending the Preset constructor at the push site to capture
both eyes. In mono mode currentRightUV stays at default; in stereo
mode the user's actual right-eye UV is now persisted.

Adds a regression test guarding that SaveSettings emits right_uv
keys in CropPresets when stereo is enabled. Bumps the suite to
35 assertions / 10 test cases.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 16, 2026

✅ A pre-release build is available for this PR:
Download

@alandtse
Copy link
Copy Markdown
Collaborator Author

Addressed CodeRabbit's Major in 499ce35:

  • Subrect.cpp:211-218 Save Preset now captures currentRightUV. Previously a stereo-mode preset save dropped the right eye, so re-applying that preset would zero out the user's right-eye crop. Added a regression test in test_subrect.cpp (Stereo preset save captures right-eye UV) — suite is now 35 assertions / 10 cases, all passing.

🤖 Reviewed by Claude Code via review-comment audit.

@alandtse alandtse added the deferred Defer to next cycle label May 16, 2026
@alandtse alandtse closed this May 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

deferred Defer to next cycle

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant