feat(upscaling): generalize VR perf-mode rendering#42
Conversation
Phase 1 of generalizing DLSSperf — the BSOpenVR render-target-size hook plus testTexture redirection is fundamentally upscaler-agnostic once the output target is routed correctly. Opens the install gate, the post-process wrap, the jitter/projection-scale path, and the UI checkbox to FSR alongside DLSS. FidelityFX::Upscale gains an optional colorOut argument so the dispatch site can route FSR's displayRes output to dlssPerf.test Texture instead of the shrunk kMAIN — mirroring Streamline's existing routing for DLSS. CreateFSRResources bridges state->screen Size via dlssPerf.GetDisplayScreenSize() so the FSR3 context is created with the real HMD display extent rather than the polluted renderRes value. FinalizePerEyeOutputs now drives copy dims from the intermediate texture desc so it works correctly when the intermediates were allocated at displayRes through EnsureVR IntermediateTextures' existing size bridge. testTexture, refraTempTex, fakeDS and the IS shader hooks (Tonemap/Refraction/ISCopy) are unchanged — they only depend on testTexture being populated by some upscaler, not on which one. https://claude.ai/code/session_01ApDQfcwz8X8LR8EYXmbz1W
📝 WalkthroughWalkthroughThis PR generalizes the VR "render engine at upscaled resolution" mode from DLSS-only (via ChangesPerfMode Integration
Sequence DiagramThis PR does not generate a sequence diagram because the changes are primarily a refactoring that generalizes existing hook infrastructure and adds optional output routing to an existing upscaling dispatch, rather than introducing new functional flows or multi-component interactions not already present in the codebase. Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
|
No actionable suggestions for changed features. |
There was a problem hiding this comment.
Pull request overview
Generalizes the existing VR “DLSSperf” render-target-size hook and post-processing redirection so it can be used with FSR as well as DLSS, by ensuring FSR can upscale into the same private DisplayRes target (dlssPerf.testTexture) and by correcting sizing assumptions when the engine’s kMAIN is intentionally allocated at render resolution.
Changes:
- Expands the DLSSperf install/UI gating to allow the feature when the resolved upscaler is DLSS or FSR (VR only).
- Adds an optional
a_colorOutdestination toFidelityFX::Upscale()so FSR can write its display-resolution output intodlssPerf.testTexturewhen the engine RTs are shrunk. - Fixes VR output merge/copy sizing by deriving copy dimensions from the intermediate output texture description rather than
state->screenSize(which is intentionally “polluted” under the size hook).
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
src/Hooks.cpp |
Allows DLSSperf render-target-size hook installation when the resolved method is DLSS or FSR. |
src/Features/Upscaling/FidelityFX.h |
Extends FidelityFX::Upscale API with optional output target for DisplayRes routing. |
src/Features/Upscaling/FidelityFX.cpp |
Bridges FSR context creation to true DisplayRes under DLSSperf; routes VR/non-VR dispatch output to a_colorOut. |
src/Features/Upscaling.cpp |
Enables DLSSperf UI for DLSS/FSR; routes FSR output to dlssPerf.testTexture under hook; fixes VR finalize copy dimensions; broadens post-processing interception gate to DLSS/FSR. |
|
✅ A pre-release build is available for this PR: |
Phase 2 of the upscaler-perf-mode generalization. With FSR now also routing through this code path (#42 phase 1), the DLSS-flavored name is misleading. Nothing has shipped under the old name, so this is a clean rename without a JSON migration shim. Renames: class DLSSperf -> PerfMode Upscaling::dlssPerf -> Upscaling::perfMode Settings::enableDLSSperf -> Settings::renderAtUpscaleRes src/Features/Upscaling/DLSSperf.{cpp,h} -> src/Features/Upscaling/PerfMode.{cpp,h} Shaders/Upscaling/DLSSperf/ -> Shaders/Upscaling/PerfMode/ log tag [DLSSperf] -> [PerfMode] restart-field label -> "Render at Upscaled Resolution" The UI checkbox loses its "(DLSSperf)" parenthetical — the label "Render engine at upscaled resolution" is self-describing. The "is active" / "requires DLSS or FSR" banner copy is reworded to use "Render-at-upscaled-resolution" as the noun rather than "PerfMode", so users never see the bare internal name. No behavior change. JSON config files written by older dev builds (none shipped) won't carry the toggle forward — anyone testing the phase-1 branch needs to re-tick the checkbox after this lands. https://claude.ai/code/session_01ApDQfcwz8X8LR8EYXmbz1W
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/Features/Upscaling.cpp (1)
706-713:⚠️ Potential issue | 🟠 Major | ⚡ Quick winUse the correct boot-locked method field when DLSS is unavailable.
When PerfMode is active, this always locks to
bootSnapshot.upscaleMethod. On setups without DLSS, the live selector isupscaleMethodNoDLSS, so this can force the wrong path (DLSS) during runtime.Suggested fix
if (globals::features::upscaling.perfMode.IsHookActive()) - return static_cast<UpscaleMethod>(bootSnapshot.Boot(&Settings::upscaleMethod)); + return streamline.featureDLSS + ? static_cast<UpscaleMethod>(bootSnapshot.Boot(&Settings::upscaleMethod)) + : static_cast<UpscaleMethod>(bootSnapshot.Boot(&Settings::upscaleMethodNoDLSS));🤖 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/Features/Upscaling.cpp` around lines 706 - 713, PerfMode currently always returns bootSnapshot.Boot(&Settings::upscaleMethod) which can lock in the DLSS path even when DLSS is unavailable; change the PerfMode branch to choose between bootSnapshot.Boot(&Settings::upscaleMethod) and bootSnapshot.Boot(&Settings::upscaleMethodNoDLSS) based on the runtime DLSS flag. Specifically, inside the globals::features::upscaling.perfMode.IsHookActive() branch, check streamline.featureDLSS and return bootSnapshot.Boot(&Settings::upscaleMethod) if true, otherwise return bootSnapshot.Boot(&Settings::upscaleMethodNoDLSS) so the boot-locked method matches the appropriate live selector (settings.upscaleMethod vs settings.upscaleMethodNoDLSS).
🧹 Nitpick comments (1)
src/Globals.cpp (1)
408-417: ⚡ Quick winGate fade-overlay hook on resolved method support too.
Line 416 currently installs this vtable hook whenever
renderAtUpscaleResis true, even if resolved mode is TAA/NONE or DLSS is unsupported at runtime. Matching the DLSS/FSR capability gate used inBSShaderRenderTargets_Create::thunkwill avoid unnecessary foreign-hook interop surface.♻️ Proposed fix
- if (globals::game::isVR && globals::features::upscaling.settings.renderAtUpscaleRes) - globals::features::upscaling.perfMode.InstallFadeOverlayHook(a_context); + const auto resolvedUpscaleMethod = globals::features::upscaling.GetUpscaleMethod(); + const bool methodSupportsPerfMode = + resolvedUpscaleMethod == Upscaling::UpscaleMethod::kDLSS || + resolvedUpscaleMethod == Upscaling::UpscaleMethod::kFSR; + if (globals::game::isVR && + globals::features::upscaling.settings.renderAtUpscaleRes && + methodSupportsPerfMode) { + globals::features::upscaling.perfMode.InstallFadeOverlayHook(a_context); + }🤖 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/Globals.cpp` around lines 408 - 417, The hook installation currently only checks globals::game::isVR and globals::features::upscaling.settings.renderAtUpscaleRes; update the condition before calling globals::features::upscaling.perfMode.InstallFadeOverlayHook(a_context) to also verify the resolved upscaling method and runtime support (the same DLSS/FSR capability checks used by BSShaderRenderTargets_Create::thunk). In practice, add the additional predicate that the chosen resolved method is one that requires native upscaling support and that the corresponding runtime capability check (DLSS/FSR availability) passes, so the vtable hook is only installed when renderAtUpscaleRes is true AND the resolved method is supported at runtime.
🤖 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/Features/Upscaling.cpp`:
- Around line 706-713: PerfMode currently always returns
bootSnapshot.Boot(&Settings::upscaleMethod) which can lock in the DLSS path even
when DLSS is unavailable; change the PerfMode branch to choose between
bootSnapshot.Boot(&Settings::upscaleMethod) and
bootSnapshot.Boot(&Settings::upscaleMethodNoDLSS) based on the runtime DLSS
flag. Specifically, inside the
globals::features::upscaling.perfMode.IsHookActive() branch, check
streamline.featureDLSS and return bootSnapshot.Boot(&Settings::upscaleMethod) if
true, otherwise return bootSnapshot.Boot(&Settings::upscaleMethodNoDLSS) so the
boot-locked method matches the appropriate live selector (settings.upscaleMethod
vs settings.upscaleMethodNoDLSS).
---
Nitpick comments:
In `@src/Globals.cpp`:
- Around line 408-417: The hook installation currently only checks
globals::game::isVR and
globals::features::upscaling.settings.renderAtUpscaleRes; update the condition
before calling
globals::features::upscaling.perfMode.InstallFadeOverlayHook(a_context) to also
verify the resolved upscaling method and runtime support (the same DLSS/FSR
capability checks used by BSShaderRenderTargets_Create::thunk). In practice, add
the additional predicate that the chosen resolved method is one that requires
native upscaling support and that the corresponding runtime capability check
(DLSS/FSR availability) passes, so the vtable hook is only installed when
renderAtUpscaleRes is true AND the resolved method is supported at runtime.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: b7a34190-83d6-4ea2-aae6-30a001180919
📒 Files selected for processing (11)
features/Upscaling/Shaders/Upscaling/PerfMode/BoxDownscalePS.hlslfeatures/Upscaling/Shaders/Upscaling/PerfMode/MenuBGBlitPS.hlslsrc/Features/Upscaling.cppsrc/Features/Upscaling.hsrc/Features/Upscaling/FidelityFX.cppsrc/Features/Upscaling/FidelityFX.hsrc/Features/Upscaling/PerfMode.cppsrc/Features/Upscaling/PerfMode.hsrc/Features/Upscaling/Streamline.cppsrc/Globals.cppsrc/Hooks.cpp
Foveated rendering (high-quality visual-center subrect + stretched periphery) is an upscaler-agnostic technique — NVIDIA's VRS, Meta's Quest eye-tracking foveation, Vive Pro 2's FFR all express the same idea. The DLSS-tied naming was historical, not algorithmic. Following PR #42's PerfMode generalization precedent, this PR ships the generic naming with DLSS as the first concrete backend. Mechanical rename: - struct DlssEnhancer -> FoveatedRender - namespace DlssEnhancerImpl -> FoveatedRenderImpl - Member Upscaling::dlssEnhancer -> foveatedRender - src/Features/Upscaling/DlssEnhancer{,.{cpp,h}} -> .../FoveatedRender{,.{cpp,h}} - features/Upscaling/Shaders/Upscaling/DlssEnhancer/ -> .../FoveatedRender/ - Log tag [DLSSENHANCER] -> [FOVEATED] - Tracy zone + JSON settings key The DLSS-specific implementation in Modes.cpp / Preprocess.cpp stays scoped to DLSS this PR. Follow-up PR-3c will: - Replace Core::ExecuteVRDlssCore(Streamline&, ...) with a backend abstraction. - Implement FSR concrete backend (~150-300 LOC, mostly Modes.cpp). - Drop the Preprocess non-DLSS early-return; wire FSR's depth-output UAV variant of EncodeTexturesCS (already exists in Upscaling.cpp). - Hoist the Upscaling.cpp route-active gate to fire for kDLSS or kFSR. No persisted user settings to preserve — PR-3 hasn't shipped. Co-Authored-By: YtzyFvra <59631290+YtzyFvra@users.noreply.github.com>
Merge origin/dev which landed PR #43 (contact shadows) and PR #42 (DLSSperf → PerfMode rename + perf-mode generalization). Conflicts resolved: - src/Features/LightLimitFix.h: merged PerFrame layout to include both ContactShadow* tuning (from #43) and ShadowMapSlots (from this PR). Final layout: 10 uint/float fields (EnableContactShadows, 6 ContactShadow*, 2 visualisation, ShadowMapSlots) + uint pad[2] + uint ClusterSize[4] = 64 bytes (sizeof asserted). - package/Shaders/Common/SharedData.hlsli: mirrored same layout. - src/Features/LightLimitFix.cpp: * NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT now persists both ContactShadow tuning fields (from #43) and ShadowSettings / ShowShadowOverlay (from this PR); EnableLightsVisualisation / LightsVisualisationMode remain intentionally non-persistent debug toggles. * GetCommonBufferData() populates contact-shadow params + the new ShadowMapSlots field. * LoadSettings/SaveSettings keep the ShadowCasterManager INI hooks from this PR (dev did not touch these helpers). * Dropped the dev-side redundant `static constexpr CLUSTER_MAX_LIGHTS / MAX_LIGHTS` globals -- the class statics in LightLimitFix.h are the canonical definitions and all callers already use them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Foveated rendering (high-quality visual-center subrect + stretched periphery) is an upscaler-agnostic technique — NVIDIA's VRS, Meta's Quest eye-tracking foveation, Vive Pro 2's FFR all express the same idea. The DLSS-tied naming was historical, not algorithmic. Following PR #42's PerfMode generalization precedent, this PR ships the generic naming with DLSS as the first concrete backend. Mechanical rename: - struct DlssEnhancer -> FoveatedRender - namespace DlssEnhancerImpl -> FoveatedRenderImpl - Member Upscaling::dlssEnhancer -> foveatedRender - src/Features/Upscaling/DlssEnhancer{,.{cpp,h}} -> .../FoveatedRender{,.{cpp,h}} - features/Upscaling/Shaders/Upscaling/DlssEnhancer/ -> .../FoveatedRender/ - Log tag [DLSSENHANCER] -> [FOVEATED] - Tracy zone + JSON settings key The DLSS-specific implementation in Modes.cpp / Preprocess.cpp stays scoped to DLSS this PR. Follow-up PR-3c will: - Replace Core::ExecuteVRDlssCore(Streamline&, ...) with a backend abstraction. - Implement FSR concrete backend (~150-300 LOC, mostly Modes.cpp). - Drop the Preprocess non-DLSS early-return; wire FSR's depth-output UAV variant of EncodeTexturesCS (already exists in Upscaling.cpp). - Hoist the Upscaling.cpp route-active gate to fire for kDLSS or kFSR. No persisted user settings to preserve — PR-3 hasn't shipped. Co-Authored-By: YtzyFvra <59631290+YtzyFvra@users.noreply.github.com>
Foveated rendering (high-quality visual-center subrect + stretched periphery) is an upscaler-agnostic technique — NVIDIA's VRS, Meta's Quest eye-tracking foveation, Vive Pro 2's FFR all express the same idea. The DLSS-tied naming was historical, not algorithmic. Following PR #42's PerfMode generalization precedent, this PR ships the generic naming with DLSS as the first concrete backend. Mechanical rename: - struct DlssEnhancer -> FoveatedRender - namespace DlssEnhancerImpl -> FoveatedRenderImpl - Member Upscaling::dlssEnhancer -> foveatedRender - src/Features/Upscaling/DlssEnhancer{,.{cpp,h}} -> .../FoveatedRender{,.{cpp,h}} - features/Upscaling/Shaders/Upscaling/DlssEnhancer/ -> .../FoveatedRender/ - Log tag [DLSSENHANCER] -> [FOVEATED] - Tracy zone + JSON settings key The DLSS-specific implementation in Modes.cpp / Preprocess.cpp stays scoped to DLSS this PR. Follow-up PR-3c will: - Replace Core::ExecuteVRDlssCore(Streamline&, ...) with a backend abstraction. - Implement FSR concrete backend (~150-300 LOC, mostly Modes.cpp). - Drop the Preprocess non-DLSS early-return; wire FSR's depth-output UAV variant of EncodeTexturesCS (already exists in Upscaling.cpp). - Hoist the Upscaling.cpp route-active gate to fire for kDLSS or kFSR. No persisted user settings to preserve — PR-3 hasn't shipped. Co-Authored-By: YtzyFvra <59631290+YtzyFvra@users.noreply.github.com>
Foveated rendering (high-quality visual-center subrect + stretched periphery) is an upscaler-agnostic technique — NVIDIA's VRS, Meta's Quest eye-tracking foveation, Vive Pro 2's FFR all express the same idea. The DLSS-tied naming was historical, not algorithmic. Following PR #42's PerfMode generalization precedent, this PR ships the generic naming with DLSS as the first concrete backend. Mechanical rename: - struct DlssEnhancer -> FoveatedRender - namespace DlssEnhancerImpl -> FoveatedRenderImpl - Member Upscaling::dlssEnhancer -> foveatedRender - src/Features/Upscaling/DlssEnhancer{,.{cpp,h}} -> .../FoveatedRender{,.{cpp,h}} - features/Upscaling/Shaders/Upscaling/DlssEnhancer/ -> .../FoveatedRender/ - Log tag [DLSSENHANCER] -> [FOVEATED] - Tracy zone + JSON settings key The DLSS-specific implementation in Modes.cpp / Preprocess.cpp stays scoped to DLSS this PR. Follow-up PR-3c will: - Replace Core::ExecuteVRDlssCore(Streamline&, ...) with a backend abstraction. - Implement FSR concrete backend (~150-300 LOC, mostly Modes.cpp). - Drop the Preprocess non-DLSS early-return; wire FSR's depth-output UAV variant of EncodeTexturesCS (already exists in Upscaling.cpp). - Hoist the Upscaling.cpp route-active gate to fire for kDLSS or kFSR. No persisted user settings to preserve — PR-3 hasn't shipped. Co-Authored-By: YtzyFvra <59631290+YtzyFvra@users.noreply.github.com>
Two-commit PR. Originally DLSSperf was a VR-only opt-in that shrinks the engine's render targets to upscaled-render resolution and redirects the upscaler output into a private displayRes texture, but the mechanism only worked for DLSS. This generalizes it to FSR too, then renames the public surface since "DLSSperf" no longer reflects what it does.
Commits
1.
feat(upscaling): extend DLSSperf VR mode to FSR(nowPerfMode)The render-target shrink + testTexture redirection is fundamentally upscaler-agnostic — the IS shader hooks (Tonemap / Refraction / ISCopy) and the CreateRT thunks only depend on
testTexturebeing populated by some upscaler. Phase 1 opens four gates and routes FSR output:Hooks.cpp) — accept FSR as a separate-output upscaler.Main_PostProcessing) —HandlePostProcessingfires for FSR too.ConfigureUpscaling) — renderRes branch handles FSR.FidelityFX::Upscalegains an optionala_colorOutparameter. The dispatch site inUpscaling::UpscalepassestestTexturewhen the hook is live (mirrors Streamline's existing DLSS routing).CreateFSRResourcesbridgesstate->screenSizeviadlssPerf.GetDisplayScreenSize()so the FSR3 context is created with the real HMD display extent rather than the polluted renderRes value.FinalizePerEyeOutputsnow drives copy dims fromvrIntermediateColorOut[0]->descso it works when the intermediates were allocated at displayRes viaEnsureVRIntermediateTextures' existing size bridge.2.
refactor(upscaling): rename DLSSperf to PerfModeNothing has shipped, so no JSON migration shim:
UI checkbox loses the "(DLSSperf)" parenthetical; the active-state and "requires DLSS or FSR" copy is reworded to use "Render-at-upscaled-resolution" as the noun rather than the bare internal name.
Test plan
Cannot build / verify here (no Windows SDK / MSVC in the harness). Real validation needs:
MaybeBlitMenuBG/ISCopyRender_Hookpaths).testTextureis at displayRes and engine RTs (kMAIN, depth, kMOTION_VECTOR, kTEMPORAL_AA_MASK) are at renderRes when the toggle is on.If first-run regression occurs under FSR, the most likely suspect is
RefractionRender_Hook(it relies on sticky D3D state from the engine's prior pass — DLSS and FSR pre-state should be equivalent but worth checking RenderDoc).https://claude.ai/code/session_01ApDQfcwz8X8LR8EYXmbz1W
Summary by CodeRabbit
New Features
Refactor