feat(VR): DLSSperf — render engine at upscaled resolution#2357
feat(VR): DLSSperf — render engine at upscaled resolution#2357alandtse wants to merge 5 commits into
Conversation
…upled from Bridge)
DLSSperf hooks BSOpenVR::GetRenderTargetSize so all engine render
targets allocate at RenderRes while DLSS writes its upscaled output
to a private DisplayRes texture. The legacy UpscaleRT round-trip
disappears, VRAM/bandwidth drop, and game menus stop being occluded
by the upscaler output. The Post chain hybrid (BeginPostIntercept /
EndPostIntercept) swaps in the DisplayRes texture + a fake DSV
around tonemap/refraction so the HDR pyramid builds from anti-
aliased content.
This commit lifts the standalone files only:
- src/Features/DLSSperf.{h,cpp}
- features/DLSSperf/Shaders/DLSSperf/BilinearCopyPS.hlsl
Decoupling from the original PR's DlssEnhancer::Bridge:
- Quality mode is read directly from Upscaling::Settings at hook-
install time (BSShaderRenderTargets::Create runs after settings
load).
- Scale is computed via ffxFsr3GetUpscaleRatioFromQualityMode, the
same helper Upscaling.cpp uses in ConfigureUpscaling.
Follow-up commits will land the Globals/Hooks/Upscaling integration
hunks and a user-facing enablement toggle.
Decomposed from community-shaders#2096 (PR-2 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>
Adds the dlssPerf instance to globals::features and registers the ID3D11DeviceContext::Draw vfunc-13 detour. The detour is no-op unless DLSSperf's RT-size hook is live AND its post chain just finished, in which case it forces the scene-fade overlay (the characteristic Draw(30)) to render at displayRes — fixing the "black stamp" partial-screen artifact where a renderRes VP would otherwise stamp onto the displayRes kTOTAL. Decoupled extract from community-shaders#2096 (PR-2 of 4); covers only the DLSSperf- specific Globals additions, not the unrelated TruePBR/Screenshot/ RefreshTES refactors that share the PR's diff. Fix vs original PR: zero-init savedVP and skip the restore when numVP=0 (Copilot review finding — RSGetViewports does not write savedVP if no viewport was set). Co-Authored-By: YtzyFvra <59631290+YtzyFvra@users.noreply.github.com> Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds the 3 DLSSperf-specific hook surfaces: 1. BSGraphics::Renderer::UpdateViewPort detour — corrects the engine's viewport whenever it disagrees with our enlarged RT dimensions: compress displayRes→renderRes for normal world/depth draws, expand renderRes→displayRes after the Post chain finishes (UI + submit-prep). No-op until DLSSperf::IsHookActive flips on. 2. BSShaderRenderTargets::Create — gates DLSSperf::InstallRenderTarget SizeHook on Upscaling::Settings::enableDLSSperf (replacement for the PR's DlssEnhancer::Bridge::BootSequence path), enlarges the Color RTs in the post-chain whitelist (kIMAGESPACE_TEMP_COPY/kTOTAL/kMENUBG) to displayRes via DLSSperf_MaybeEnlargeRT, and calls SetupResources manually since DLSSperf isn't a Feature in the registry. 3. Runtime E8 scanner in Hooks::Install that finds every unhooked CreateRenderTarget call site within Create() and re-writes them through DLSSperf_CreateRT_Thunk so enlargement applies universally, not just to the 6 sites with explicit per-target thunks. Also injects DLSSperf_MaybeEnlargeRT into all 6 existing CreateRenderTarget thunks (Main, Normals, NormalsSwap, MotionVectors, RefractionNormals, UnderwaterMask). The new field Upscaling::Settings::enableDLSSperf (added in the next commit) provides the user-facing gate; without it set, none of this code path activates. Decoupled extract from community-shaders#2096 (PR-2 of 4); skips the unrelated TruePBR/ TESObjectLAND/BSLightingShader refactor hunks that share the PR's diff. Co-Authored-By: YtzyFvra <59631290+YtzyFvra@users.noreply.github.com> Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three Upscaling-side changes complete the DLSSperf wiring: 1. ConfigureUpscaling jitter branch — when DLSSperf::IsHookActive, RTs are already at RenderRes so the DRS-style scale collapses to identity. Jitter is still computed at the real DisplayRes phase ratio so DLSS has enough sub-pixel diversity for the upscale. 2. Main_PostProcessing wrap — replaces the bare engine func() call with a HandlePostProcessing lambda when DLSSperf::ShouldHandlePost. The hybrid Post performs a two-layer struct swap so tonemap + refraction read the DisplayRes testTexture instead of small kMAIN. 3. Upscaling::Settings::enableDLSSperf — new opt-in bool with JSON load/save (added to NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT) and a UI checkbox under Streamline settings, gated on VR + DLSS. Restart required (matches the engine init lifecycle of the hook). DLSSperf.cpp now includes Upscaling.h so the InstallRenderTargetSize Hook can read settings.qualityMode at install time. Plugin C++ compile passes (MSBuild ClCompile target, exit 0). Full link blocked locally by an environmental crash in the FFX shader permutation generator — unrelated to PR-2 changes; reproduces on clean upstream/dev. Decoupled extract from community-shaders#2096 (PR-2 of 4); skips the DlssEnhancer route and per-eye sharpening branches that share the PR's diff. Co-Authored-By: YtzyFvra <59631290+YtzyFvra@users.noreply.github.com> Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds DLSSperf: a VR-only path that captures HMD resolution, overrides engine RT sizes to an upscaled render-eye, allocates D3D11 test/refraction/fake-DS resources, intercepts tonemap/refraction/UI/post passes to render into a test texture, then downsamples it back to the engine main RT. ChangesDLSSperf VR Render-Target Optimization
Sequence Diagram(s)sequenceDiagram
participant Engine as Engine
participant DLSSperf as DLSSperf
participant TestTex as TestTexture
participant MainRT as MainRenderTarget
Engine->>DLSSperf: GetRenderTargetSize() override (HMD size)
DLSSperf->>DLSSperf: Compute render-eye dimensions
Engine->>DLSSperf: TonemapRender_Hook call
DLSSperf->>TestTex: Swap SRV/DSV, render tonemap into TestTexture
Engine->>DLSSperf: RefractionRender_Hook call
DLSSperf->>TestTex: Replay refraction -> TestTexture
Engine->>DLSSperf: UIPassDispatch_Hook (renderMode==24)
DLSSperf->>DLSSperf: Swap fake DSV, force viewport
Engine->>DLSSperf: Main PostProcessing entry
DLSSperf->>DLSSperf: BeginPostIntercept -> copy test→refra, DownscaleToKMain
DLSSperf->>TestTex: Sample TestTexture (BilinearCopyPS)
DLSSperf->>MainRT: Draw downscaled result into MainRT
DLSSperf->>DLSSperf: EndPostIntercept (restore DS/viewport)
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly Related PRs
Suggested Reviewers
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 unit tests (beta)
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 src/Features/DLSSperf.cpp 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.
Actionable comments posted: 2
🧹 Nitpick comments (2)
src/Globals.cpp (1)
390-437: 💤 Low valueConsider extracting the magic vertex count to a named constant.
The
VertexCount == 30check identifies the scene-fade overlay draw. While the block comment explains this, a named constant would make the intent clearer and simplify future searches if this value ever needs to change across game versions.+ // Vertex count for the engine's scene-fade overlay (fade-in/fade-out effect). + // Identified via reverse engineering; may need verification on future game updates. + static constexpr UINT kFadeOverlayVertexCount = 30; + struct ID3D11DeviceContext_Draw { static void thunk(ID3D11DeviceContext* This, UINT VertexCount, UINT StartVertexLocation) { - if (VertexCount == 30 && globals::game::isVR) { + if (VertexCount == kFadeOverlayVertexCount && globals::game::isVR) {🤖 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 390 - 437, Replace the magic literal 30 in the ID3D11DeviceContext_Draw::thunk vertex-count check with a well-named constant (e.g., FADE_OVERLAY_VERTEX_COUNT or kFadeOverlayVertexCount) declared near the struct or in the globals/features dlssPerf scope; update the condition from VertexCount == 30 to VertexCount == FADE_OVERLAY_VERTEX_COUNT and use the constant name in any related comments so the purpose is explicit and easy to find/modify later.features/DLSSperf/Shaders/DLSSperf/BilinearCopyPS.hlsl (1)
16-20: ⚡ Quick winAvoid per-pixel
GetDimensionsin this fullscreen pass.Move
srcSizeto a constant buffer or push constants instead of querying dimensions in every fragment. This optimization saves per-pixel overhead in a hot path with negligible setup cost. Before deployment, validate the shader with hlslkit.🤖 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 `@features/DLSSperf/Shaders/DLSSperf/BilinearCopyPS.hlsl` around lines 16 - 20, The fragment shader currently calls SourceTex.GetDimensions per-pixel inside main(PS_INPUT) to compute srcSize and texelSize; move srcSize (or directly texelSize) into a constant buffer or push constants and read that in main instead of calling GetDimensions, update uses of srcSize/texelSize in main to use the CB/push constant values, and validate the updated BilinearCopyPS shader with hlslkit before deployment to ensure correctness.
🤖 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/Features/DLSSperf.cpp`:
- Around line 41-44: Validate and clamp the config-derived qualityMode and the
resulting scale before using them to compute renderEyeWidth/renderEyeHeight:
read globals::features::upscaling.settings.qualityMode into qualityMode, ensure
it is within the valid FfxFsr3QualityMode range (clamp to the min/max enum
values), call ffxFsr3GetUpscaleRatioFromQualityMode and verify the returned
scale is > 0 and sane (e.g., within expected bounds); if invalid, choose a safe
fallback (set scale = 1.0 or a default qualityMode) and log/error as appropriate
so the division w/scale and h/scale cannot produce zero/negative or overflow
sizes. Ensure all references to qualityMode,
ffxFsr3GetUpscaleRatioFromQualityMode, renderEyeWidth, and renderEyeHeight are
updated to use the validated/clamped values.
- Around line 540-650: DownscaleToKMain currently sets
bilinearCopyVS/bilinearCopyPS and a PS sampler (slot 0) but does not
save/restore the previous VS/PS/sampler bindings; before changing shaders and
samplers, query and store the current VS/PS (use VSGetShader/PSGetShader) and
the sampler at slot 0 (use PSGetSamplers), then after drawing restore them with
VSSetShader/PSSetShader and PSSetSamplers (restore the original sampler array
and count), releasing any COM refs you acquired (same pattern used for
savedGS/savedHS/etc.); ensure you reference bilinearCopyVS, bilinearCopyPS, and
linearSampler when locating the change points.
---
Nitpick comments:
In `@features/DLSSperf/Shaders/DLSSperf/BilinearCopyPS.hlsl`:
- Around line 16-20: The fragment shader currently calls SourceTex.GetDimensions
per-pixel inside main(PS_INPUT) to compute srcSize and texelSize; move srcSize
(or directly texelSize) into a constant buffer or push constants and read that
in main instead of calling GetDimensions, update uses of srcSize/texelSize in
main to use the CB/push constant values, and validate the updated BilinearCopyPS
shader with hlslkit before deployment to ensure correctness.
In `@src/Globals.cpp`:
- Around line 390-437: Replace the magic literal 30 in the
ID3D11DeviceContext_Draw::thunk vertex-count check with a well-named constant
(e.g., FADE_OVERLAY_VERTEX_COUNT or kFadeOverlayVertexCount) declared near the
struct or in the globals/features dlssPerf scope; update the condition from
VertexCount == 30 to VertexCount == FADE_OVERLAY_VERTEX_COUNT and use the
constant name in any related comments so the purpose is explicit and easy to
find/modify later.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: dcf4007f-3775-494b-874b-020b8393503a
📒 Files selected for processing (8)
features/DLSSperf/Shaders/DLSSperf/BilinearCopyPS.hlslsrc/Features/DLSSperf.cppsrc/Features/DLSSperf.hsrc/Features/Upscaling.cppsrc/Features/Upscaling.hsrc/Globals.cppsrc/Globals.hsrc/Hooks.cpp
|
✅ A pre-release build is available for this PR: |
Two CodeRabbit Major findings on scs#2357: 1. InstallRenderTargetSizeHook: validate qualityMode + scale before division. Clamp the raw setting to FfxFsr3QualityMode's [0..4] range, then verify ffxFsr3GetUpscaleRatioFromQualityMode returned a finite positive value. Fail closed (early-return without setting hookActive) if the FFX call returns 0 / inf / NaN — that would otherwise propagate to bogus renderEye dimensions and silently mis-size every engine RT. Logs the offending values for diagnosis. 2. DownscaleToKMain: save and restore VS, PS, and PS sampler 0 alongside the existing GS/HS/DS/blend/depth-stencil/viewport chain. The function runs immediately before enginePost(); leaving our shader bound risks subsequent passes inheriting it if they don't explicitly set every slot. Pattern matches what VRStereoOptimizations does. Adds the corresponding Release() calls to the cleanup tail. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Addressed CodeRabbit's two Majors in cce13c7:
Skipped:
🤖 Reviewed by Claude Code via review-comment audit. |
Summary
VR-only opt-in feature that hooks
BSOpenVR::GetRenderTargetSizeso the entire engine pipeline allocates render targets at upscaled-render resolution (RenderRes = HMD recommended size / DLSS quality scale) while DLSS writes its upscaled output into a private DisplayRes texture. The legacy "engine renders at DisplayRes, then we upscale down" round-trip disappears.User-visible benefit: substantial VRAM and bandwidth savings proportional to the quality-mode scale ratio. Makes Ultra Performance preset (0.33×) viable at 6K HMDs on mid-range GPUs, instead of a low-resolution DLAA fallback. Game menus also stop being occluded by the upscaler output.
What changes
Four commits, organized around the integration boundary:
src/Features/DLSSperf.{h,cpp},features/DLSSperf/Shaders/DLSSperf/BilinearCopyPS.hlslsrc/Globals.{h,cpp}dlssPerfinstance +ID3D11DeviceContext::Drawvfunc-13 hook for the scene-fade overlaysrc/Hooks.cppBSGraphics::Renderer::UpdateViewPortviewport-correction hook + RT enlargement gate inBSShaderRenderTargets::Create+ 6MaybeEnlargeRTinjections + runtime E8 scanner for unhooked CreateRenderTarget call sitessrc/Features/Upscaling.{h,cpp}Settings::enableDLSSperffield + JSON load/save + UI checkbox (VR + DLSS only, restart required) + jitter branch inConfigureUpscaling+Main_PostProcessingHandlePostProcessingwrapDecoupling from the original PR
@YtzyFvra's #2096 routed DLSSperf through
DlssEnhancer::Bridge::BootSequence. This PR removes that coupling so DLSSperf can ship without the larger DlssEnhancer framework:Upscaling::Settings::qualityModeat hook-install time (BSShaderRenderTargets::Createruns after settings load)ffxFsr3GetUpscaleRatioFromQualityMode— the same helperUpscaling.cppalready uses inConfigureUpscalingBot review fix from #2096 baked in
Copilot flagged that
savedVPwas uninitialized beforeRSGetViewportsin the Draw vfunc hook. Zero-initD3D11_VIEWPORT savedVP{}and skip the restore whennumVP == 0.What's NOT in this PR (intentionally)
TruePBRinstance→pointer migration,ScreenshotFeatureremoval from features namespace,RefreshTEScleanup,TESObjectLAND_SetupMaterial/BSLightingShader_SetupMaterialhook changes — none of these are DLSSperf-specificRisks for review
Main_PostProcessingwrap interaction withHDRDisplay: the wrap sits betweenHDRDisplay::RedirectFramebufferandRestoreFramebuffer. Order matters; not tested live with both enabled. Worth a VR test session.Hooks::Installwrites to the SKSE trampoline. If another PR or feature also patches the sameCreateRenderTargetcall sites between the named thunks, ordering matters. Currently no known conflict.enableDLSSperfrequires a game restart to take effect (engine RT allocations happen once at init). UI surfaces a "Requires restart" warning.Test plan
MSBuild ClCompile, exit 0, no errors/warnings)testTexture+refraTempTexlifecycle when toggling at runtime (load game with on, change setting off, restart, verify clean state)Credits
Decomposed from @YtzyFvra's #2096 (PR-2 of 4). All DLSSperf design/implementation is theirs; this PR adapts to ship standalone (Bridge decoupled, scoped to just the DLSSperf hunks of the original PR's diff). Co-authored on commits.
See DLSS-PR-PLAN.md for the full decomposition strategy.
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Configuration