feat(vr-dlss): VR DLSS viewport scaling with periphery TAA#1983
feat(vr-dlss): VR DLSS viewport scaling with periphery TAA#1983vrnord wants to merge 16 commits into
Conversation
…g, and DLSS fix - Hardware stencil-based Eye 1 pixel culling for forward passes - StereoBlend lateral reprojection from Eye 0 to Eye 1 - AMD CAS post-TAA sharpening with strength slider - RGB fringe suppression for tree alpha test artifacts - Fix UpscaleDepth to use upstream dev version (prevents DLSS crash) - Mip LOD bias control for VR foliage Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Streamline proxy wrapping disrupts VR compositor frame pacing. DLSS works without wrapped interfaces; only frame generation needs them. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace nearest-neighbor integer Load() with hardware bilinear SampleLevel() for color reads during Eye 1 reprojection. Fixes visible "distortion ridges" at iso-depth contours where floor(offset) jumps by 1 pixel. Motion vectors remain integer-sampled via Load() — DLSS requires discrete motion data. The decoupled approach eliminates visual artifacts without affecting temporal accumulation. - Add SampleReprojectedColor() helper using GetDimensions() + UV math - Bind linear sampler at s0 in overwrite mode (lazy-created) - Clamp UVs to active DRS viewport bounds Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Store parallax occlusion mapping (POM) depth offset in Reflectance.w so the stereo reprojection shader can account for parallax displacement when reprojecting Eye 0 → Eye 1. Without this, POM surfaces show incorrect stereo depth. - Enable alpha writes on RT[5] (REFLECTANCE) in deferred blend states - Output pixelOffset to Reflectance.w in Lighting.hlsl (EMAT+PARALLAX) - Initialize pixelOffset=0 in ExtendedMaterials GetParallaxCoords - Add pomDepthScale setting and debugPOMDepth visualization - Disable CAS (force 0) pending proper integration; hide UI slider Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix HLSL/C++ struct mismatch in CPMSettings.pad0: change float1 → bool in HLSL and float → uint in C++ to match the bool fields preceding it. Required after merging VR MIP bias fields with upstream SH ambient fields in SharedDataCB. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reset CommonLibSSE-NG submodule to upstream 4.7.1 (23bf5512). Fix SharedDataCB struct padding: float pad0 → float4 pad0 to match HLSL cbuffer layout (float2 + implicit 8-byte gap before float4 AmbientSHR) and ensure sizeof is a multiple of 16. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Automated formatting by clang-format, prettier, and other hooks. See https://pre-commit.ci for details.
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughImplements comprehensive VR stereo rendering optimizations and DLSS/upscaling integration, introducing stencil-based eye-culling, reprojection, CAS sharpening, per-eye DLSS upscaling with feathered compositing, TAA reordering, and VR-specific MIP bias adjustments across shaders and C++ rendering pipeline. Changes
Sequence Diagram(s)sequenceDiagram
participant CPU as CPU / C++ Renderer
participant Phase1 as Phase 1: Stencil<br/>Classification & Write
participant Phase2 as Phase 2: Depth-Stencil<br/>Cache & Modified DSS
participant Phase3 as Phase 3:<br/>Reprojection
participant PostTAA as Post-TAA:<br/>CAS Sharpening
participant DeferredComp as DeferredComposite<br/>(VR_STEREO_OPT)
CPU->>Phase1: DispatchStencil()<br/>(eye 0 depth analysis)
Phase1->>Phase1: Classify pixels:<br/>MODE_DISOCCLUDED,<br/>MODE_EDGE,<br/>MODE_MAIN,<br/>MODE_FULL_BLEND
Phase1->>Phase1: ExecuteStencilWritePass<br/>(eye 1 stencil buffer)
CPU->>Phase2: GetOrCreateModifiedDSS<br/>(stencil NOT_EQUAL ref=1)
Phase2->>Phase2: Clone & cache DSS<br/>for eye 1 skip
CPU->>DeferredComp: Bind ModeTexture SRV<br/>for right eye early-exit
DeferredComp->>DeferredComp: If eyeIndex==1 &&<br/>mode in {1,2}:<br/>early-return
CPU->>Phase3: DispatchReprojection<br/>(eye 0→eye 1)
Phase3->>Phase3: Read MODE_MAIN pixels<br/>from eye 1
Phase3->>Phase3: Reproject to eye 0<br/>coordinates & sample
Phase3->>Phase3: Write eye 0 color<br/>to eye 1 render target
CPU->>PostTAA: ApplyCAS<br/>(post-TAA output)
PostTAA->>PostTAA: Adaptive sharpening<br/>via cross/diagonal<br/>neighbor analysis
PostTAA->>PostTAA: Write sharpened<br/>result to render target
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly Related PRs
Suggested Reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 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 |
Actionable Suggestions
|
|
✅ A pre-release build is available for this PR: |
There was a problem hiding this comment.
Actionable comments posted: 16
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli (1)
500-514:⚠️ Potential issue | 🟡 MinorPotential discontinuity between blended and fallback
pixelOffsetvalues.Line 500 now blends
pixelOffsettoward0.5asnearBlendToFarapproaches 1.0, but line 513 setspixelOffset = 0when the computation block is skipped entirely. This creates a discontinuity at the transition boundary (whennearBlendToFar >= 1.0), which could cause a visible seam or "pop" at the parallax fade-out distance.Consider updating line 513 to
pixelOffset = 0.5;for consistency with the blend target.Proposed fix
`#if` defined(LANDSCAPE) weights[0] = input.LandBlendWeights1.x; weights[1] = input.LandBlendWeights1.y; weights[2] = input.LandBlendWeights1.z; weights[3] = input.LandBlendWeights1.w; weights[4] = input.LandBlendWeights2.x; weights[5] = input.LandBlendWeights2.y; `#endif` - pixelOffset = 0; + pixelOffset = 0.5; return coords;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@features/Extended` Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli around lines 500 - 514, The fallback branch sets pixelOffset = 0 which creates a discontinuity versus the blended path that lerps pixelOffset toward 0.5 as nearBlendToFar → 1; update the fallback assignment to pixelOffset = 0.5 so the value matches the blend target (change the final pixelOffset = 0 to pixelOffset = 0.5) — keep the return coords as-is so the function continues returning coords while ensuring pixelOffset, nearBlendToFar, and the blended computation (viewDirTS, parallaxAmount) remain continuous at the transition.src/Features/VR/SettingsUI.cpp (1)
326-345:⚠️ Potential issue | 🟡 MinorExplain the two new debug views in the tooltip.
OverwriteandOverwrite Eye1are now selectable, but the help text still ends atEdge Detection, so the last two entries have no in-UI explanation.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Features/VR/SettingsUI.cpp` around lines 326 - 345, The tooltip text for the Debug View Combo (debugModes array and StereoBlendDebugMode) stops at "Edge Detection" and lacks explanations for the new "Overwrite" and "Overwrite Eye1" modes; update the ImGui::Text tooltip block inside the Util::HoverTooltipWrapper() so it appends clear, concise descriptions for "Overwrite" (force use of blended/override color for both eyes) and "Overwrite Eye1" (force override for eye 1 only), matching the style of the existing entries so users can understand the behavior of these two modes.
🧹 Nitpick comments (10)
features/Upscaling/Shaders/Upscaling/DepthUpscalePS.hlsl (1)
35-41: Unused constant buffer memberInvSourceDim.
InvSourceDimis declared but never used in this shader. If it's not needed for future expansion, consider removing it to reduce constant buffer size.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@features/Upscaling/Shaders/Upscaling/DepthUpscalePS.hlsl` around lines 35 - 41, The cbuffer DepthUpscaleCB declares InvSourceDim but it is never referenced; remove the unused member to shrink the constant buffer. Edit the DepthUpscaleCB definition in DepthUpscalePS.hlsl and delete the line declaring float2 InvSourceDim (or replace with a comment placeholder if you expect future use), then rebuild and run shader validation to ensure member layout changes don't break any constant buffer bindings that reference DepthUpscaleCB elsewhere.features/Upscaling/Shaders/Upscaling/FeatheredCompositeCS.hlsl (1)
24-28: Minor: Edge distance calculation differs from PS version.The CS uses
CropW - 1 - cropLocal.xfordistRight, so the rightmost pixel hasdistRight = 0. The PS version (FeatheredCompositePS.hlsl) uses(CropOrigin.x + CropSize.x) - pixelPos.x, giving the rightmost pixeldistRight = 1.This creates a 1-pixel feathering inconsistency at the right/bottom edges between the two paths. If both shaders are intended to produce identical results, consider aligning the formulas.
🔧 Option to align with PS version
- float distRight = (float)(CropW - 1 - cropLocal.x); + float distRight = (float)(CropW - cropLocal.x); - float distBottom = (float)(CropH - 1 - cropLocal.y); + float distBottom = (float)(CropH - cropLocal.y);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@features/Upscaling/Shaders/Upscaling/FeatheredCompositeCS.hlsl` around lines 24 - 28, The right/bottom edge distance math in the compute shader uses "CropW - 1 - cropLocal.x" and "CropH - 1 - cropLocal.y" (variables distRight and distBottom), which yields 0 at the rightmost/bottommost pixel and differs from the pixel shader’s "(CropOrigin + CropSize) - pixelPos" behavior; update distRight and distBottom to use "CropW - cropLocal.x" and "CropH - cropLocal.y" (preserving the float casts) so the right/bottom edges produce 1 like FeatheredCompositePS.hlsl, keeping the rest of the distFromEdge/min logic unchanged.features/Upscaling/Shaders/Upscaling/VRPeripheryFillCS.hlsl (1)
1-4: PR metadata tweak: add explicit issue keyword (and optionally shorten title).Consider adding
Implements#1983`` in the PR description for automatic issue linkage; optionally tighten the title to conventional length/style.As per coding guidelines, feature PRs should include GitHub keywords like "Implements
#123", and conventional-commit titles should follow the documented format/length.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@features/Upscaling/Shaders/Upscaling/VRPeripheryFillCS.hlsl` around lines 1 - 4, Update the PR metadata to include the GitHub issue keyword by adding "Implements `#1983`" to the PR description and, if desired, shorten the PR title to conform to conventional-commit length/style; reference this change to the shader added in VRPeripheryFillCS.hlsl so reviewers can link the feature implementation to the issue and ensure automatic issue linkage.package/Shaders/DistantTree.hlsl (1)
1-1: Consider tightening PR metadata to match repo convention.Suggested title (≤50 chars):
feat(vr-dlss): add periphery taa compositing
Also add explicit issue linkage in PR body, e.g.Implements#1983``.As per coding guidelines, "
**/*: provide suggestions for Conventional Commit Titles ... Length: 50 characters limit ... and Issue References using keywords likeImplements#123``."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@package/Shaders/DistantTree.hlsl` at line 1, Update the PR metadata to follow repo conventions: change the PR title to a concise Conventional Commit style (suggested: "feat(vr-dlss): add periphery taa compositing") and add an explicit issue linkage in the PR body using a keyword like "Implements `#1983`"; ensure the PR description references the relevant shader file (Shaders/DistantTree.hlsl) so reviewers can correlate the change.package/Shaders/VRStereoOptimizations/StencilWritePS.hlsl (1)
36-50: Whitelist the modes that should write stencil.The comment says only
MODE_MAINandMODE_EDGEare meant to survive, but the current blacklist will also write stencil for any future or legacy mode value that isn'tMODE_DISOCCLUDEDorMODE_FULL_BLEND. An allow-list keeps the pass aligned with its contract and prevents accidental Eye 1 culling if another classification value reappears.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@package/Shaders/VRStereoOptimizations/StencilWritePS.hlsl` around lines 36 - 50, Replace the current blacklist approach with an explicit allow-list so only MODE_MAIN and MODE_EDGE write the stencil: in the shader code that inspects the mode variable (currently comparing to MODE_DISOCCLUDED and MODE_FULL_BLEND), change the logic to discard unless mode == MODE_MAIN or mode == MODE_EDGE; preserve the existing sky/HMD depth checks (DepthTexture[modeCoord]) and their discards but ensure they run only after confirming the mode is allowed. This ensures only the intended symbols MODE_MAIN and MODE_EDGE (and the depth checks using DepthTexture and modeCoord) reach stencil-writing.package/Shaders/Common/SharedData.hlsli (1)
60-60: Type change fromfloat1toboolin CPMSettings.The
pad0field changed fromfloattobool. In HLSL,boolis typically 4 bytes in cbuffers, so this should be compatible. However, ensure the corresponding C++ struct usesuint(notbool) for the padding field to maintain size consistency.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@package/Shaders/Common/SharedData.hlsli` at line 60, CPMSettings's pad0 was changed in HLSL from float to bool; update the matching C++ struct field (the pad0 member in the CPMSettings struct) to use an unsigned int type (e.g., uint32_t or unsigned int) instead of bool so the CPU-side layout and size match the HLSL cbuffer bool (4 bytes) and maintain proper packing/alignment with other members defined in SharedData.hlsli.package/Shaders/VR/VRPostProcessCS.hlsl (1)
101-106: Consider documenting or parameterizing the depth tolerance.The hardcoded
0.02in the depth agreement calculation is a magic number that affects blend quality. Consider either:
- Documenting why 0.02 was chosen
- Moving it to the constant buffer for tuning
- float depthAgreement = 1.0 - saturate(abs(centerDepth - otherDepth) / maxDepth / 0.02); + // 0.02 = 2% relative depth tolerance for bilateral weight + float depthAgreement = 1.0 - saturate(abs(centerDepth - otherDepth) / maxDepth / 0.02);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@package/Shaders/VR/VRPostProcessCS.hlsl` around lines 101 - 106, The depth agreement uses a hardcoded tolerance (0.02) in VRPostProcessCS.hlsl which makes tuning difficult; replace this magic number with a named parameter (e.g., DepthTolerance) exposed via the shader constant buffer or add a clearly commented constant at top of the file, then use that symbol in the depthAgreement calculation (the lines computing maxDepth, depthAgreement, and blendWeight) so the tolerance can be documented and adjusted without changing code.package/Shaders/RunGrass.hlsl (1)
876-885: Duplicate VR alpha test logic—consider extracting to a shared macro or function.This VR alpha test adjustment duplicates the pattern from lines 505-514. If the offset value (0.1) changes, both locations must be updated.
♻️ Optional: Extract VR alpha test to a shared helper
Consider adding a helper function in
Common/VR.hlsli:float Stereo::GetVRAlphaTestRef(float alphaRef, uint eyeIndex) { return alphaRef - eyeIndex * 0.1; // or use SharedData::VRAlphaTestThreshold }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@package/Shaders/RunGrass.hlsl` around lines 876 - 885, The VR alpha-test logic duplicates code that adjusts AlphaTestRefRS by Stereo::GetEyeIndexPS; extract this into a shared helper (e.g., Stereo::GetVRAlphaTestRef or a SharedData::VRAlphaTestThreshold-aware helper placed in Common/VR.hlsli) and replace the local calculation in this block (references: AlphaTestRefRS, Stereo::GetEyeIndexPS, diffuseAlpha) with a single call to that helper so the adjustment (currently eyeIndex * 0.1) is defined in one place.src/Features/Upscaling.h (1)
188-194: Add 16-byte alignment guarantee for HLSL constant buffer compatibility.
DlssCompositeCBis used as a GPU constant buffer but lacks thealignas(16)specifier andstatic_assertvalidation thatVRStereoOptParamsinVRStereoOptimizations.hcorrectly uses. HLSL cbuffers require 16-byte alignment.♻️ Proposed fix
- struct DlssCompositeCB + struct alignas(16) DlssCompositeCB { float2 DynResScale; // renderRes / displayRes per-eye float2 EyeOffset; // (i * eyeWidth, 0) float2 SrcTexSize; // full texture dimensions float2 pad; }; + static_assert(sizeof(DlssCompositeCB) % 16 == 0, "DlssCompositeCB must be 16-byte aligned for HLSL cbuffer.");🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Features/Upscaling.h` around lines 188 - 194, DlssCompositeCB is used as a GPU constant buffer but lacks 16-byte alignment; add alignas(16) to the DlssCompositeCB declaration and include a compile-time check similar to VRStereoOptParams (e.g., a static_assert verifying alignof(DlssCompositeCB) == 16 and/or that sizeof(DlssCompositeCB) is a multiple of 16) to ensure HLSL cbuffer compatibility.src/Features/VRStereoOptimizations.h (1)
228-228: DSS cache keyed on raw pointers assumes original states remain valid.The cache uses the original
ID3D11DepthStencilState*as a lookup key. If an external component releases a cached DSS, subsequent lookups could return stale entries pointing to freed memory (dangling reference in the map values, not immediate UB from the key itself).Consider documenting this assumption, or clearing the cache in
Reset()if DSS lifetimes are not guaranteed to outlive the feature.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Features/VRStereoOptimizations.h` at line 228, The dssCache currently uses raw ID3D11DepthStencilState* as the map key which can become invalid if external code releases those objects; either ensure the cache owns strong references or purge entries on reset. Fix by updating the cache to use winrt::com_ptr<ID3D11DepthStencilState> (or another owning handle) as the key/value so the cache holds a reference and prevents dangling pointers, or, if changing key type is undesirable, add logic to clear dssCache in the class Reset() method to remove any potentially freed entries and document the lifetime assumption for ID3D11DepthStencilState objects in VRStereoOptimizations.h.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@features/Upscaling/Shaders/Upscaling/DLSSCompositePS.hlsl`:
- Around line 28-36: The bilinear mapping is using corner coordinates so the
upscale is shifted by half a texel; in main(VS_OUTPUT input) explicitly map
pixel centers before scaling: convert input.Position.xy to center space
(subtract 0.5), apply EyeOffset adjustments and DynResScale, then convert back
to texel/UV space (add 0.5) before dividing by SrcTexSize and calling
Source.SampleLevel with LinearSampler; additionally, when sampling a packed
side-by-side (SBS) source, clamp srcPos.x to the active eye region to prevent
seam bleed (use EyeOffset/X-region/active-eye logic around srcPos.x prior to
computing srcUV).
In `@package/Shaders/Common/VR.hlsli`:
- Around line 630-636: The shared VR vertex helper (GetVRVSOutput / the block
using a_eyeIndex) currently applies a permanent right-eye-only screen-space
jitter by adding kJitterNDC to vsout.VRPosition.xy; remove that global
modification and instead expose an opt-in path: delete or guard the if
(a_eyeIndex == 1) { static const float2 kJitterNDC ... vsout.VRPosition.xy +=
... } from the shared helper and provide either a boolean parameter (e.g., bool
enableEyeJitter) or a separate helper/overload that applies the jitter only when
explicitly requested by a material/pass that needs alpha-tested edge
stabilization, ensuring other callers of GetVRVSOutput are unaffected.
In `@package/Shaders/Lighting.hlsl`:
- Around line 3097-3105: The fringe-suppression block that modifies
baseColor.rgb is currently run after lighting so it never affects
material.BaseColor, color, or outputAlbedo (and thus
psout.Diffuse/psout.Albedo); move or apply this correction earlier so the
corrected baseColor feeds into the lighting/albedo path. Specifically, either
relocate the if (vrFoliageBias < 0) { ... } block to immediately after baseColor
is sampled/derived (before material.BaseColor/color/outputAlbedo are computed)
or replace uses of uncorrected baseColor when computing material.BaseColor,
color, and outputAlbedo with the saturated, bias-scaled version so psout.Diffuse
and psout.Albedo use the corrected RGB. Ensure you reference and update
baseColor, material.BaseColor, color, outputAlbedo, psout.Diffuse, and
psout.Albedo accordingly.
- Around line 3200-3205: The code currently only writes pixelOffset into
psout.Reflectance under the EMAT && (PARALLAX || LANDSCAPE) guard which causes
TRUE_PBR deferred permutations to lose the per-pixel height; update the
conditional so that when PBRParallax has populated pixelOffset (i.e. TRUE_PBR
permutations) it is preserved — for example extend the preprocessor check to
include TRUE_PBR (or remove the PARALLAX-only restriction) so psout.Reflectance
= float4(indirectLobeWeights.specular, (pixelOffset > 0.0) ?
saturate(pixelOffset) : 0.0); is emitted for TRUE_PBR as well; reference
psout.Reflectance, pixelOffset, TRUE_PBR, EMAT, PARALLAX, and LANDSCAPE to
locate the change.
In `@package/Shaders/VR/CASCS.hlsl`:
- Around line 37-47: Border samples are currently read out of bounds using sp +
int2(...) before Source.Load; clamp each neighbor coordinate to the valid
texture extent so edge pixels duplicate/clamp instead of sampling off-image.
Change the nine fetches to compute a clamped coordinate (e.g. neighbor =
clamp(sp + int2(dx,dy), int2(0,0), texSize - int2(1,1))) and pass that clamped
int2 into Source.Load; use DTid/sp to form the base and the existing Source.Load
calls (Source.Load, DTid, sp) to locate and replace the unsafe offsets. Ensure
you obtain the texture dimensions (texSize/texDim via GetDimensions or the
existing size variable) for the clamp bounds.
In `@package/Shaders/VRStereoOptimizations/StencilCS.hlsl`:
- Around line 19-20: The shader currently uses a hardcoded
kDisocclusionThreshold constant instead of the value populated by
VRStereoOptimizations::UpdateConstantBuffer; replace uses of
kDisocclusionThreshold (including the comparisons around lines 79-81) with the
DisocclusionThreshold value read from the constant buffer (cbuffer) so the
shader reads the runtime-configured DisocclusionThreshold set by
UpdateConstantBuffer; locate the cbuffer declaration (where
DisocclusionThreshold is defined) and reference that variable in the comparisons
instead of the static constant.
In `@src/Deferred.cpp`:
- Around line 279-283: When VR is active the code calls
globals::features::vrStereoOptimizations.DispatchStencil() unconditionally; add
the same feature-loaded/enabled guard used in other VR stereo paths (e.g., the
checks around the calls at lines near the other DispatchStencil/optimization
usages) so that you only call
globals::features::vrStereoOptimizations.DispatchStencil() when
globals::features::vrStereoOptimizations.isLoaded() (or the equivalent
loaded/enabled flag) returns true; modify the VR block that wraps
DispatchStencil to first check that vrStereoOptimizations is loaded/enabled
before invoking DispatchStencil().
In `@src/Features/TAAReorder.cpp`:
- Around line 145-157: Reset the per-frame readiness flags at the top of the
hook before any early-return or capture logic so stale true values don't persist
across frames; specifically set g_postPPReady (and the analogous pre/post flags
used elsewhere such as g_prePPReady and any other *_Ready booleans) to false
before calling EnsurePostPPCopy / EnsurePrePPCopy or performing CopyResource,
then let the existing code set them to true only when a valid capture/paste
completes (affecting the blocks around EnsurePostPPCopy, EnsurePrePPCopy,
g_postPPCopy, and their equivalents at the other mentioned locations).
- Around line 119-120: g_diagCounter is never updated so shouldLog always true;
update the diagnostic gate by incrementing or toggling g_diagCounter at the
start/end of the frame or pass where logging is intended (e.g., increment at
frame start and decrement at frame end) and use that counter in
ShouldReorderTAA/shouldLog checks to limit logging; apply the change around the
entry/exit points for ExecutePassHook, BSImagespaceShaderHook, and SubmitHook so
the gate advances once per pass/frame, and ensure the updates to g_diagCounter
are thread-safe if hooks can be invoked concurrently.
- Around line 239-253: Clamp and validate the VR DLSS config values before using
them to compute rectangles: ensure upscaling.settings.vrDlssViewportScale is
clamped to a reasonable (e.g. >0 and <=1) range,
upscaling.settings.vrDlssCropOffsetX is clamped to a safe [-1,1] (or narrower)
range, and upscaling.settings.vrDlssFeatherWidth is clamped to [0,
maxFeatherRatio]; then recompute centerW/centerH, baseCenterX/centerY,
nasalShift and featherPixels using the validated values (these variables appear
where screenSize, eyeW, eyeH, centerW, centerH, baseCenterX, centerY,
nasalShift, featherPixels and FinalizePerEyeOutputs are used); apply the same
validation in the other occurrences noted (around the other diffs at the
referenced spots) so derived crop boxes never exceed eye bounds and math remains
safe.
- Around line 60-73: EnsurePostPPCopy must guard against failed DirectX
allocations: check the HRESULT returned from
globals::d3d::device->CreateTexture2D and CreateShaderResourceView and only set
g_postPPCopy/g_postPPCopySRV when those calls succeed; if either fails, log the
error, release/clear the corresponding smart pointers and do not call
CopyResource(g_postPPCopy.get(), postTex) later. Specifically, update
EnsurePostPPCopy to capture and test HRESULTs from
CreateTexture2D/CreateShaderResourceView, verify g_postPPCopy is non-null before
creating the SRV and before any CopyResource call, and ensure CopyResource usage
(and any subsequent resource-dependent code) early-exits or skips when
g_postPPCopy is null to avoid dereferencing a null texture.
In `@src/Features/Upscaling.cpp`:
- Around line 69-75: The hook hk_D3D11CreateDeviceAndSwapChainUpscaling()
currently reads globals::game::isVR which may be uninitialized at hook time;
replace that cached check with a runtime VR detection call (use
REL::Module::IsVR() or the project's runtime-detection helper) when deciding to
set pSwapChainDesc->SwapEffect and shouldProxy/interface-wrapping logic, and
apply the same change to the other similar branches (the block around lines
referenced in the review) so all device-creation code uses runtime detection
instead of globals::game::isVR to handle SE/AE/VR variants safely.
In `@src/Features/Upscaling/Streamline.cpp`:
- Around line 610-624: The log branch detects periphery TAA via
upscaling.settings.vrPeripheryTAA but FillPeriphery still uses the
default/previous source (vrIntermediateColorIn), so periphery is not coming from
the TAA SRV; update the call to upscaling.FillPeriphery(...) in the
viewportScaling branch to pass the TAA per-eye SRV (upscaling.vrTAAdPerEye[i])
when upscaling.settings.vrPeripheryTAA is true (otherwise pass the
existing/default SRV), ensuring FillPeriphery receives the correct source;
reference FillPeriphery and upscaling.vrTAAdPerEye to locate the change and add
a conditional argument or overload as needed.
In `@src/Features/VRStereoOptimizations.cpp`:
- Around line 173-182: The code arms stencil culling even when the stencil-write
DSV is missing; modify DispatchStencil() to check the actual resource
(stencilWriteReadOnlyDSV or the created DSV handle from
SetupResources/CreateDepthStencilView) before setting stencilActive = true and
before writing NOT_EQUAL marks, and if the DSV is absent skip arming and log a
warning; also ensure the later hook that injects NOT_EQUAL stencil tests
verifies stencilActive (or the DSV presence) before applying tests to avoid
using an uninitialized stencil buffer.
- Around line 44-89: In VRStereoOptimizations::LoadSettings the code calls
o_json["..."].get<>() after only contains(), which can throw on wrong types and
accepts out-of-range values; update each branch (StereoMode,
DisocclusionDepthThreshold, QualityJitterOffset, FoveatedRegionRadius,
FoveatedRegionCenterX/Y, UseEyeTracking, Debug*, FullBlendDistance, MipBiasMode,
MipLodBias, MipBiasNearDist, MipBiasFarDist, AlphaTestThreshold, etc.) to first
check the JSON type (e.g., is_number()/is_boolean()/is_string()), then read and
validate/clamp the value to acceptable ranges (e.g., centers 0–1, radii >=0,
mipBiasMode within allowed enum set, thresholds within sensible bounds); on
invalid type or out-of-range value, fall back to the existing settings.*
defaults (or clamp) and emit a warning/log entry instead of allowing an
exception so State::Load() degrades gracefully. Ensure CASStrength remains
forced to 0.0f as before.
In `@src/State.h`:
- Around line 213-218: The struct has a packing mismatch: C++ declares float4
pad0 while HLSL uses float2 pad0 causing AmbientSHR/AmbientSHG/AmbientSHB
offsets to diverge; fix by making the padding identical in both languages—either
change the C++ pad0 to float2 to match HLSL or change the HLSL pad0 to float4 to
match C++; then update the inline comment near
VRMipBias/VRMipBiasNearDist/VRMipBiasFarDist/VRMipBiasMode/VRAlphaTestThreshold/pad0
to reflect the exact bytes of padding and verify the total bytes before
AmbientSHR are the same in C++ and HLSL.
---
Outside diff comments:
In `@features/Extended`
Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlsli:
- Around line 500-514: The fallback branch sets pixelOffset = 0 which creates a
discontinuity versus the blended path that lerps pixelOffset toward 0.5 as
nearBlendToFar → 1; update the fallback assignment to pixelOffset = 0.5 so the
value matches the blend target (change the final pixelOffset = 0 to pixelOffset
= 0.5) — keep the return coords as-is so the function continues returning coords
while ensuring pixelOffset, nearBlendToFar, and the blended computation
(viewDirTS, parallaxAmount) remain continuous at the transition.
In `@src/Features/VR/SettingsUI.cpp`:
- Around line 326-345: The tooltip text for the Debug View Combo (debugModes
array and StereoBlendDebugMode) stops at "Edge Detection" and lacks explanations
for the new "Overwrite" and "Overwrite Eye1" modes; update the ImGui::Text
tooltip block inside the Util::HoverTooltipWrapper() so it appends clear,
concise descriptions for "Overwrite" (force use of blended/override color for
both eyes) and "Overwrite Eye1" (force override for eye 1 only), matching the
style of the existing entries so users can understand the behavior of these two
modes.
---
Nitpick comments:
In `@features/Upscaling/Shaders/Upscaling/DepthUpscalePS.hlsl`:
- Around line 35-41: The cbuffer DepthUpscaleCB declares InvSourceDim but it is
never referenced; remove the unused member to shrink the constant buffer. Edit
the DepthUpscaleCB definition in DepthUpscalePS.hlsl and delete the line
declaring float2 InvSourceDim (or replace with a comment placeholder if you
expect future use), then rebuild and run shader validation to ensure member
layout changes don't break any constant buffer bindings that reference
DepthUpscaleCB elsewhere.
In `@features/Upscaling/Shaders/Upscaling/FeatheredCompositeCS.hlsl`:
- Around line 24-28: The right/bottom edge distance math in the compute shader
uses "CropW - 1 - cropLocal.x" and "CropH - 1 - cropLocal.y" (variables
distRight and distBottom), which yields 0 at the rightmost/bottommost pixel and
differs from the pixel shader’s "(CropOrigin + CropSize) - pixelPos" behavior;
update distRight and distBottom to use "CropW - cropLocal.x" and "CropH -
cropLocal.y" (preserving the float casts) so the right/bottom edges produce 1
like FeatheredCompositePS.hlsl, keeping the rest of the distFromEdge/min logic
unchanged.
In `@features/Upscaling/Shaders/Upscaling/VRPeripheryFillCS.hlsl`:
- Around line 1-4: Update the PR metadata to include the GitHub issue keyword by
adding "Implements `#1983`" to the PR description and, if desired, shorten the PR
title to conform to conventional-commit length/style; reference this change to
the shader added in VRPeripheryFillCS.hlsl so reviewers can link the feature
implementation to the issue and ensure automatic issue linkage.
In `@package/Shaders/Common/SharedData.hlsli`:
- Line 60: CPMSettings's pad0 was changed in HLSL from float to bool; update the
matching C++ struct field (the pad0 member in the CPMSettings struct) to use an
unsigned int type (e.g., uint32_t or unsigned int) instead of bool so the
CPU-side layout and size match the HLSL cbuffer bool (4 bytes) and maintain
proper packing/alignment with other members defined in SharedData.hlsli.
In `@package/Shaders/DistantTree.hlsl`:
- Line 1: Update the PR metadata to follow repo conventions: change the PR title
to a concise Conventional Commit style (suggested: "feat(vr-dlss): add periphery
taa compositing") and add an explicit issue linkage in the PR body using a
keyword like "Implements `#1983`"; ensure the PR description references the
relevant shader file (Shaders/DistantTree.hlsl) so reviewers can correlate the
change.
In `@package/Shaders/RunGrass.hlsl`:
- Around line 876-885: The VR alpha-test logic duplicates code that adjusts
AlphaTestRefRS by Stereo::GetEyeIndexPS; extract this into a shared helper
(e.g., Stereo::GetVRAlphaTestRef or a SharedData::VRAlphaTestThreshold-aware
helper placed in Common/VR.hlsli) and replace the local calculation in this
block (references: AlphaTestRefRS, Stereo::GetEyeIndexPS, diffuseAlpha) with a
single call to that helper so the adjustment (currently eyeIndex * 0.1) is
defined in one place.
In `@package/Shaders/VR/VRPostProcessCS.hlsl`:
- Around line 101-106: The depth agreement uses a hardcoded tolerance (0.02) in
VRPostProcessCS.hlsl which makes tuning difficult; replace this magic number
with a named parameter (e.g., DepthTolerance) exposed via the shader constant
buffer or add a clearly commented constant at top of the file, then use that
symbol in the depthAgreement calculation (the lines computing maxDepth,
depthAgreement, and blendWeight) so the tolerance can be documented and adjusted
without changing code.
In `@package/Shaders/VRStereoOptimizations/StencilWritePS.hlsl`:
- Around line 36-50: Replace the current blacklist approach with an explicit
allow-list so only MODE_MAIN and MODE_EDGE write the stencil: in the shader code
that inspects the mode variable (currently comparing to MODE_DISOCCLUDED and
MODE_FULL_BLEND), change the logic to discard unless mode == MODE_MAIN or mode
== MODE_EDGE; preserve the existing sky/HMD depth checks
(DepthTexture[modeCoord]) and their discards but ensure they run only after
confirming the mode is allowed. This ensures only the intended symbols MODE_MAIN
and MODE_EDGE (and the depth checks using DepthTexture and modeCoord) reach
stencil-writing.
In `@src/Features/Upscaling.h`:
- Around line 188-194: DlssCompositeCB is used as a GPU constant buffer but
lacks 16-byte alignment; add alignas(16) to the DlssCompositeCB declaration and
include a compile-time check similar to VRStereoOptParams (e.g., a static_assert
verifying alignof(DlssCompositeCB) == 16 and/or that sizeof(DlssCompositeCB) is
a multiple of 16) to ensure HLSL cbuffer compatibility.
In `@src/Features/VRStereoOptimizations.h`:
- Line 228: The dssCache currently uses raw ID3D11DepthStencilState* as the map
key which can become invalid if external code releases those objects; either
ensure the cache owns strong references or purge entries on reset. Fix by
updating the cache to use winrt::com_ptr<ID3D11DepthStencilState> (or another
owning handle) as the key/value so the cache holds a reference and prevents
dangling pointers, or, if changing key type is undesirable, add logic to clear
dssCache in the class Reset() method to remove any potentially freed entries and
document the lifetime assumption for ID3D11DepthStencilState objects in
VRStereoOptimizations.h.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: e0b31891-b61f-425e-a255-f9fdc8bf76c0
📒 Files selected for processing (42)
features/Extended Materials/Shaders/ExtendedMaterials/ExtendedMaterials.hlslifeatures/Upscaling/Shaders/Upscaling/ClearHMDMaskCS.hlslfeatures/Upscaling/Shaders/Upscaling/DLSSCompositePS.hlslfeatures/Upscaling/Shaders/Upscaling/DepthUpscalePS.hlslfeatures/Upscaling/Shaders/Upscaling/FeatheredCompositeCS.hlslfeatures/Upscaling/Shaders/Upscaling/FeatheredCompositePS.hlslfeatures/Upscaling/Shaders/Upscaling/ForceAlphaCS.hlslfeatures/Upscaling/Shaders/Upscaling/VRPeripheryFillCS.hlslfeatures/VR Stereo Optimizations/Shaders/Features/VRStereoOptimizations.inipackage/Shaders/Common/SharedData.hlslipackage/Shaders/Common/VR.hlslipackage/Shaders/DeferredCompositeCS.hlslpackage/Shaders/DistantTree.hlslpackage/Shaders/Lighting.hlslpackage/Shaders/RunGrass.hlslpackage/Shaders/VR/CASCS.hlslpackage/Shaders/VR/StereoBlendCS.hlslpackage/Shaders/VR/VRPostProcessCS.hlslpackage/Shaders/VRStereoOptimizations/ReprojectionCS.hlslpackage/Shaders/VRStereoOptimizations/StencilCS.hlslpackage/Shaders/VRStereoOptimizations/StencilWritePS.hlslpackage/Shaders/VRStereoOptimizations/StencilWriteVS.hlslpackage/Shaders/VRStereoOptimizations/cbuffers.hlslisrc/Deferred.cppsrc/Feature.cppsrc/Features/ExtendedMaterials.hsrc/Features/TAAReorder.cppsrc/Features/TAAReorder.hsrc/Features/Upscaling.cppsrc/Features/Upscaling.hsrc/Features/Upscaling/Streamline.cppsrc/Features/Upscaling/Streamline.hsrc/Features/VR.cppsrc/Features/VR.hsrc/Features/VR/SettingsUI.cppsrc/Features/VR/StereoBlend.cppsrc/Features/VRStereoOptimizations.cppsrc/Features/VRStereoOptimizations.hsrc/Globals.cppsrc/Globals.hsrc/State.cppsrc/State.h
| float4 main(VS_OUTPUT input) : SV_Target | ||
| { | ||
| // Map display-res pixel position to render-res source position. | ||
| // Subtract eye offset, scale to render-res, add eye offset back. | ||
| float2 localPos = input.Position.xy - EyeOffset; | ||
| float2 srcLocal = localPos * DynResScale; | ||
| float2 srcPos = srcLocal + EyeOffset; | ||
| float2 srcUV = srcPos / SrcTexSize; | ||
| return Source.SampleLevel(LinearSampler, srcUV, 0); |
There was a problem hiding this comment.
Fix the bilinear source-coordinate mapping before shipping this.
input.Position.xy is already in destination pixel-center space, so localPos * DynResScale maps corners instead of centers and shifts the whole upscale by half a texel. That will slightly blur/misalign the DLSS/periphery composite. Map centers explicitly before converting to UV; if this samples a packed SBS texture, clamp srcPos.x to the active eye as well to avoid seam bleed.
🐛 Proposed fix
- float2 localPos = input.Position.xy - EyeOffset;
- float2 srcLocal = localPos * DynResScale;
+ float2 localPos = input.Position.xy - EyeOffset;
+ float2 srcLocal = (localPos - float2(0.5, 0.5)) * DynResScale + float2(0.5, 0.5);
float2 srcPos = srcLocal + EyeOffset;
float2 srcUV = srcPos / SrcTexSize;
return Source.SampleLevel(LinearSampler, srcUV, 0);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| float4 main(VS_OUTPUT input) : SV_Target | |
| { | |
| // Map display-res pixel position to render-res source position. | |
| // Subtract eye offset, scale to render-res, add eye offset back. | |
| float2 localPos = input.Position.xy - EyeOffset; | |
| float2 srcLocal = localPos * DynResScale; | |
| float2 srcPos = srcLocal + EyeOffset; | |
| float2 srcUV = srcPos / SrcTexSize; | |
| return Source.SampleLevel(LinearSampler, srcUV, 0); | |
| float4 main(VS_OUTPUT input) : SV_Target | |
| { | |
| // Map display-res pixel position to render-res source position. | |
| // Subtract eye offset, scale to render-res, add eye offset back. | |
| float2 localPos = input.Position.xy - EyeOffset; | |
| float2 srcLocal = (localPos - float2(0.5, 0.5)) * DynResScale + float2(0.5, 0.5); | |
| float2 srcPos = srcLocal + EyeOffset; | |
| float2 srcUV = srcPos / SrcTexSize; | |
| return Source.SampleLevel(LinearSampler, srcUV, 0); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@features/Upscaling/Shaders/Upscaling/DLSSCompositePS.hlsl` around lines 28 -
36, The bilinear mapping is using corner coordinates so the upscale is shifted
by half a texel; in main(VS_OUTPUT input) explicitly map pixel centers before
scaling: convert input.Position.xy to center space (subtract 0.5), apply
EyeOffset adjustments and DynResScale, then convert back to texel/UV space (add
0.5) before dividing by SrcTexSize and calling Source.SampleLevel with
LinearSampler; additionally, when sampling a packed side-by-side (SBS) source,
clamp srcPos.x to the active eye region to prevent seam bleed (use
EyeOffset/X-region/active-eye logic around srcPos.x prior to computing srcUV).
| // Hardcoded ~0.75px diagonal jitter for Eye 1 stereo edge supersampling. | ||
| // Larger offset increases chance of different alpha test outcomes between eyes | ||
| // (tree branches vs sky). NDC for 6304x3088 SBS reference; scales with resolution. | ||
| if (a_eyeIndex == 1) { | ||
| static const float2 kJitterNDC = float2(1.68e-4, -3.44e-4); | ||
| vsout.VRPosition.xy += kJitterNDC * vsout.VRPosition.w; | ||
| } |
There was a problem hiding this comment.
Keep this out of the shared VR vertex helper.
This adds a permanent right-eye-only screen-space shift to every pass that calls GetVRVSOutput, not just the alpha-tested edge cases mentioned in the comment. The Y component in particular creates binocular vertical disparity across the whole frame, which is likely to show up as blur/eye strain in-headset rather than a localized coverage win. Please move this behind a dedicated opt-in path or restrict it to the specific material/pass you're trying to stabilize.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@package/Shaders/Common/VR.hlsli` around lines 630 - 636, The shared VR vertex
helper (GetVRVSOutput / the block using a_eyeIndex) currently applies a
permanent right-eye-only screen-space jitter by adding kJitterNDC to
vsout.VRPosition.xy; remove that global modification and instead expose an
opt-in path: delete or guard the if (a_eyeIndex == 1) { static const float2
kJitterNDC ... vsout.VRPosition.xy += ... } from the shared helper and provide
either a boolean parameter (e.g., bool enableEyeJitter) or a separate
helper/overload that applies the jitter only when explicitly requested by a
material/pass that needs alpha-tested edge stabilization, ensuring other callers
of GetVRVSOutput are unaffected.
| // Suppress RGB fringe contamination from negative MIP bias. | ||
| // Low-alpha texels near the foliage boundary have bright padding bleeding into RGB. | ||
| // Alpha is a direct proxy for contamination — low alpha = more padding contribution. | ||
| // Scale correction by bias strength so close-range (no bias) textures are untouched. | ||
| if (vrFoliageBias < 0) { | ||
| float biasStrength = saturate(vrFoliageBias / min(SharedData::VRMipBias, -0.001)); | ||
| float fringeScale = 5.0; // higher = more aggressive fringe suppression | ||
| baseColor.rgb *= saturate(alpha * lerp(1.0, fringeScale, biasStrength)); | ||
| } |
There was a problem hiding this comment.
Apply the foliage fringe suppression before lighting.
By this point material.BaseColor, color, and outputAlbedo have already been derived from the uncorrected baseColor. Scaling baseColor.rgb here does not reach psout.Diffuse or psout.Albedo, so the new fringe fix is effectively inert for the final shaded color.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@package/Shaders/Lighting.hlsl` around lines 3097 - 3105, The
fringe-suppression block that modifies baseColor.rgb is currently run after
lighting so it never affects material.BaseColor, color, or outputAlbedo (and
thus psout.Diffuse/psout.Albedo); move or apply this correction earlier so the
corrected baseColor feeds into the lighting/albedo path. Specifically, either
relocate the if (vrFoliageBias < 0) { ... } block to immediately after baseColor
is sampled/derived (before material.BaseColor/color/outputAlbedo are computed)
or replace uses of uncorrected baseColor when computing material.BaseColor,
color, and outputAlbedo with the saturated, bias-scaled version so psout.Diffuse
and psout.Albedo use the corrected RGB. Ensure you reference and update
baseColor, material.BaseColor, color, outputAlbedo, psout.Diffuse, and
psout.Albedo accordingly.
| # if defined(EMAT) && (defined(PARALLAX) || defined(LANDSCAPE)) | ||
| psout.Reflectance = float4(indirectLobeWeights.specular, | ||
| (pixelOffset > 0.0) ? saturate(pixelOffset) : 0.0); | ||
| # else | ||
| psout.Reflectance = float4(indirectLobeWeights.specular, 0.0); | ||
| # endif |
There was a problem hiding this comment.
Keep pixelOffset in deferred TRUE_PBR parallax outputs.
PBRParallax still fills pixelOffset above, but this new guard only exports it for PARALLAX and LANDSCAPE. Non-landscape TRUE_PBR permutations therefore always emit 0.0 here, so downstream deferred passes lose the per-pixel height offset.
Suggested fix
-# if defined(EMAT) && (defined(PARALLAX) || defined(LANDSCAPE))
+# if defined(EMAT) && (defined(PARALLAX) || defined(LANDSCAPE) || defined(TRUE_PBR))📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # if defined(EMAT) && (defined(PARALLAX) || defined(LANDSCAPE)) | |
| psout.Reflectance = float4(indirectLobeWeights.specular, | |
| (pixelOffset > 0.0) ? saturate(pixelOffset) : 0.0); | |
| # else | |
| psout.Reflectance = float4(indirectLobeWeights.specular, 0.0); | |
| # endif | |
| # if defined(EMAT) && (defined(PARALLAX) || defined(LANDSCAPE) || defined(TRUE_PBR)) | |
| psout.Reflectance = float4(indirectLobeWeights.specular, | |
| (pixelOffset > 0.0) ? saturate(pixelOffset) : 0.0); | |
| # else | |
| psout.Reflectance = float4(indirectLobeWeights.specular, 0.0); | |
| # endif |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@package/Shaders/Lighting.hlsl` around lines 3200 - 3205, The code currently
only writes pixelOffset into psout.Reflectance under the EMAT && (PARALLAX ||
LANDSCAPE) guard which causes TRUE_PBR deferred permutations to lose the
per-pixel height; update the conditional so that when PBRParallax has populated
pixelOffset (i.e. TRUE_PBR permutations) it is preserved — for example extend
the preprocessor check to include TRUE_PBR (or remove the PARALLAX-only
restriction) so psout.Reflectance = float4(indirectLobeWeights.specular,
(pixelOffset > 0.0) ? saturate(pixelOffset) : 0.0); is emitted for TRUE_PBR as
well; reference psout.Reflectance, pixelOffset, TRUE_PBR, EMAT, PARALLAX, and
LANDSCAPE to locate the change.
| // Fetch 3x3 neighborhood | ||
| int2 sp = int2(DTid.xy); | ||
| float3 a = Source.Load(int3(sp + int2(-1, -1), 0)).rgb; | ||
| float3 b = Source.Load(int3(sp + int2(0, -1), 0)).rgb; | ||
| float3 c = Source.Load(int3(sp + int2(1, -1), 0)).rgb; | ||
| float3 d = Source.Load(int3(sp + int2(-1, 0), 0)).rgb; | ||
| float3 e = Source.Load(int3(sp, 0)).rgb; | ||
| float3 f = Source.Load(int3(sp + int2(1, 0), 0)).rgb; | ||
| float3 g = Source.Load(int3(sp + int2(-1, 1), 0)).rgb; | ||
| float3 h = Source.Load(int3(sp + int2(0, 1), 0)).rgb; | ||
| float3 i = Source.Load(int3(sp + int2(1, 1), 0)).rgb; |
There was a problem hiding this comment.
Clamp the 3x3 neighborhood at the image border.
The first/last row and column currently fetch neighbors outside the source extent, so the sharpen weights for border pixels are built from off-image samples. Clamp sp + offset to [0, texDim - 1] or fall back to a copy on the border.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@package/Shaders/VR/CASCS.hlsl` around lines 37 - 47, Border samples are
currently read out of bounds using sp + int2(...) before Source.Load; clamp each
neighbor coordinate to the valid texture extent so edge pixels duplicate/clamp
instead of sampling off-image. Change the nine fetches to compute a clamped
coordinate (e.g. neighbor = clamp(sp + int2(dx,dy), int2(0,0), texSize -
int2(1,1))) and pass that clamped int2 into Source.Load; use DTid/sp to form the
base and the existing Source.Load calls (Source.Load, DTid, sp) to locate and
replace the unsafe offsets. Ensure you obtain the texture dimensions
(texSize/texDim via GetDimensions or the existing size variable) for the clamp
bounds.
| // Use better swap effect to prevent tearing and improve performance | ||
| pSwapChainDesc->SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; | ||
| // But don't change it for VR as it can affect frame pacing with the VR compositor | ||
| if (!globals::game::isVR) | ||
| pSwapChainDesc->SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; | ||
|
|
||
| bool shouldProxy = !globals::game::isVR; | ||
| if (shouldProxy) |
There was a problem hiding this comment.
Use REL::Module::IsVR() inside the device-creation hook.
hk_D3D11CreateDeviceAndSwapChainUpscaling() can run before globals::ReInit() populates globals::game::isVR. Using the cached flag here can still send VR startup through the non-VR flip-discard/proxy/interface-wrapping path, which is exactly the judder path these branches are trying to avoid.
Suggested fix
- // Use better swap effect to prevent tearing and improve performance
- // But don't change it for VR as it can affect frame pacing with the VR compositor
- if (!globals::game::isVR)
+ const bool isVR = REL::Module::IsVR();
+
+ // Use better swap effect to prevent tearing and improve performance
+ // But don't change it for VR as it can affect frame pacing with the VR compositor
+ if (!isVR)
pSwapChainDesc->SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
- bool shouldProxy = !globals::game::isVR;
+ bool shouldProxy = !isVR;
@@
- if (!globals::game::isVR) {
+ if (!isVR) {
upscaling.UpgradeBackendInterface((void**)&(*ppDevice));
upscaling.UpgradeBackendInterface((void**)&(*ppSwapChain));
}Based on learnings: Applies to src/**/*.{h,hpp,cpp} : Ensure code changes work across SE/AE/VR Skyrim variants using runtime detection and REL::RelocateMember() patterns for compatibility
Also applies to: 151-159
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/Features/Upscaling.cpp` around lines 69 - 75, The hook
hk_D3D11CreateDeviceAndSwapChainUpscaling() currently reads globals::game::isVR
which may be uninitialized at hook time; replace that cached check with a
runtime VR detection call (use REL::Module::IsVR() or the project's
runtime-detection helper) when deciding to set pSwapChainDesc->SwapEffect and
shouldProxy/interface-wrapping logic, and apply the same change to the other
similar branches (the block around lines referenced in the review) so all
device-creation code uses runtime detection instead of globals::game::isVR to
handle SE/AE/VR variants safely.
| // Periphery TAA diagnostic | ||
| if (TAAReorder::g_diagCounter == 0 && viewportScaling && upscaling.settings.vrPeripheryTAA) { | ||
| logger::info("[TAAReorder] Periphery TAA: vrTAAdPerEye[0]={}, g_initialized={} (TAA injected at display RT level)", | ||
| (void*)upscaling.vrTAAdPerEye[0].get(), TAAReorder::g_initialized); | ||
| } | ||
|
|
||
| uint32_t eyeCount = eye0Only ? 1 : 2; | ||
| for (uint32_t i = 0; i < eyeCount; ++i) { | ||
| sl::ViewportHandle vp = (i == 1) ? viewportRight : viewport; | ||
| sl::Extent extentIn{ 0, 0, eyeWidthIn, eyeHeightIn }; | ||
| sl::Extent extentOut{ 0, 0, eyeWidthOut, eyeHeightOut }; | ||
|
|
||
| if (viewportScaling) { | ||
| // Pre-fill composition target with bilinear upscale of full render-res eye. | ||
| // DLSS output is pasted on top in FinalizePerEyeOutputs. | ||
| upscaling.FillPeriphery(i, eyeWidthIn, eyeHeightIn, eyeWidthOut, eyeHeightOut); | ||
| } |
There was a problem hiding this comment.
Pass the TAA periphery SRV into FillPeriphery() when that mode is enabled.
This branch only logs vrTAAdPerEye; the actual fill still defaults to vrIntermediateColorIn, so the periphery continues to come from the pre-TAA DLSS input. As written, vrPeripheryTAA doesn't change the composite source.
Suggested fix
for (uint32_t i = 0; i < eyeCount; ++i) {
sl::ViewportHandle vp = (i == 1) ? viewportRight : viewport;
if (viewportScaling) {
+ ID3D11ShaderResourceView* peripherySRV = nullptr;
+ uint32_t peripherySrcWidth = eyeWidthIn;
+ uint32_t peripherySrcHeight = eyeHeightIn;
+
+ if (upscaling.settings.vrPeripheryTAA && upscaling.vrTAAdPerEye[i]) {
+ peripherySRV = upscaling.vrTAAdPerEye[i]->srv.get();
+ peripherySrcWidth = eyeWidthOut;
+ peripherySrcHeight = eyeHeightOut;
+ }
+
// Pre-fill composition target with bilinear upscale of full render-res eye.
// DLSS output is pasted on top in FinalizePerEyeOutputs.
- upscaling.FillPeriphery(i, eyeWidthIn, eyeHeightIn, eyeWidthOut, eyeHeightOut);
+ upscaling.FillPeriphery(i, peripherySrcWidth, peripherySrcHeight, eyeWidthOut, eyeHeightOut, peripherySRV);
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/Features/Upscaling/Streamline.cpp` around lines 610 - 624, The log branch
detects periphery TAA via upscaling.settings.vrPeripheryTAA but FillPeriphery
still uses the default/previous source (vrIntermediateColorIn), so periphery is
not coming from the TAA SRV; update the call to upscaling.FillPeriphery(...) in
the viewportScaling branch to pass the TAA per-eye SRV
(upscaling.vrTAAdPerEye[i]) when upscaling.settings.vrPeripheryTAA is true
(otherwise pass the existing/default SRV), ensuring FillPeriphery receives the
correct source; reference FillPeriphery and upscaling.vrTAAdPerEye to locate the
change and add a conditional argument or overload as needed.
| void VRStereoOptimizations::LoadSettings(json& o_json) | ||
| { | ||
| if (o_json.contains("StereoMode")) | ||
| settings.stereoMode = o_json["StereoMode"].get<StereoMode>(); | ||
| if (o_json.contains("DisocclusionDepthThreshold")) | ||
| settings.disocclusionDepthThreshold = o_json["DisocclusionDepthThreshold"].get<float>(); | ||
| if (o_json.contains("QualityJitterOffset")) | ||
| settings.qualityJitterOffset = o_json["QualityJitterOffset"].get<float>(); | ||
| if (o_json.contains("FoveatedRegionRadius")) | ||
| settings.foveatedRegionRadius = o_json["FoveatedRegionRadius"].get<float>(); | ||
| if (o_json.contains("FoveatedRegionCenterX")) | ||
| settings.foveatedRegionCenterX = o_json["FoveatedRegionCenterX"].get<float>(); | ||
| if (o_json.contains("FoveatedRegionCenterY")) | ||
| settings.foveatedRegionCenterY = o_json["FoveatedRegionCenterY"].get<float>(); | ||
| if (o_json.contains("UseEyeTracking")) | ||
| settings.useEyeTracking = o_json["UseEyeTracking"].get<bool>(); | ||
| if (o_json.contains("DebugVisualization")) | ||
| settings.debugVisualization = o_json["DebugVisualization"].get<bool>(); | ||
| if (o_json.contains("DebugSkipMerge")) | ||
| settings.debugSkipMerge = o_json["DebugSkipMerge"].get<bool>(); | ||
| if (o_json.contains("DebugForceAllStencil")) | ||
| settings.debugForceAllStencil = o_json["DebugForceAllStencil"].get<bool>(); | ||
| if (o_json.contains("DebugForceAllReprojectCS")) | ||
| settings.debugForceAllReprojectCS = o_json["DebugForceAllReprojectCS"].get<bool>(); | ||
| if (o_json.contains("DebugDepthMap")) | ||
| settings.debugDepthMap = o_json["DebugDepthMap"].get<bool>(); | ||
| if (o_json.contains("FullBlendDistance")) | ||
| settings.fullBlendDistance = o_json["FullBlendDistance"].get<float>(); | ||
| if (o_json.contains("MipBiasMode")) | ||
| settings.mipBiasMode = o_json["MipBiasMode"].get<int>(); | ||
| // Backwards compat: old bool EnableMipBias -> mode 2 (Distant Trees) | ||
| else if (o_json.contains("EnableMipBias") && o_json["EnableMipBias"].get<bool>()) | ||
| settings.mipBiasMode = 2; | ||
| if (o_json.contains("MipLodBias")) | ||
| settings.mipLodBias = o_json["MipLodBias"].get<float>(); | ||
| if (o_json.contains("MipBiasNearDist")) | ||
| settings.mipBiasNearDist = o_json["MipBiasNearDist"].get<float>(); | ||
| if (o_json.contains("MipBiasFarDist")) | ||
| settings.mipBiasFarDist = o_json["MipBiasFarDist"].get<float>(); | ||
| // CAS disabled for now — ignore saved value | ||
| // if (o_json.contains("CASStrength")) | ||
| // settings.casStrength = o_json["CASStrength"].get<float>(); | ||
| settings.casStrength = 0.0f; | ||
| if (o_json.contains("AlphaTestThreshold")) | ||
| settings.alphaTestThreshold = o_json["AlphaTestThreshold"].get<float>(); | ||
| } |
There was a problem hiding this comment.
Harden the JSON load path for the new settings.
These branches only check contains(). A wrong JSON type here will throw on get<>() and fail the whole feature load from State::Load(), and the new numeric fields are currently accepted out of range.
Suggested hardening
- if (o_json.contains("DisocclusionDepthThreshold"))
- settings.disocclusionDepthThreshold = o_json["DisocclusionDepthThreshold"].get<float>();
+ if (auto it = o_json.find("DisocclusionDepthThreshold"); it != o_json.end() && it->is_number())
+ settings.disocclusionDepthThreshold = std::clamp(it->get<float>(), 0.001f, 0.1f);
@@
- if (o_json.contains("MipBiasMode"))
- settings.mipBiasMode = o_json["MipBiasMode"].get<int>();
+ if (auto it = o_json.find("MipBiasMode"); it != o_json.end() && it->is_number_integer())
+ settings.mipBiasMode = std::clamp(it->get<int>(), 0, 2);
@@
- if (o_json.contains("AlphaTestThreshold"))
- settings.alphaTestThreshold = o_json["AlphaTestThreshold"].get<float>();
+ if (auto it = o_json.find("AlphaTestThreshold"); it != o_json.end() && it->is_number())
+ settings.alphaTestThreshold = std::clamp(it->get<float>(), 0.0f, 1.0f);
+
+ settings.mipBiasNearDist = std::max(settings.mipBiasNearDist, 0.0f);
+ settings.mipBiasFarDist = std::max(settings.mipBiasFarDist, settings.mipBiasNearDist);As per coding guidelines: Include proper resource management and graceful degradation for DirectX 11 resources and user input validation to prevent crashes from malformed configurations
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/Features/VRStereoOptimizations.cpp` around lines 44 - 89, In
VRStereoOptimizations::LoadSettings the code calls o_json["..."].get<>() after
only contains(), which can throw on wrong types and accepts out-of-range values;
update each branch (StereoMode, DisocclusionDepthThreshold, QualityJitterOffset,
FoveatedRegionRadius, FoveatedRegionCenterX/Y, UseEyeTracking, Debug*,
FullBlendDistance, MipBiasMode, MipLodBias, MipBiasNearDist, MipBiasFarDist,
AlphaTestThreshold, etc.) to first check the JSON type (e.g.,
is_number()/is_boolean()/is_string()), then read and validate/clamp the value to
acceptable ranges (e.g., centers 0–1, radii >=0, mipBiasMode within allowed enum
set, thresholds within sensible bounds); on invalid type or out-of-range value,
fall back to the existing settings.* defaults (or clamp) and emit a warning/log
entry instead of allowing an exception so State::Load() degrades gracefully.
Ensure CASStrength remains forced to 0.0f as before.
| auto& depthData = renderer->GetDepthStencilData().depthStencils[RE::RENDER_TARGETS_DEPTHSTENCIL::kMAIN]; | ||
| if (depthData.views[0] && depthData.texture) { | ||
| D3D11_DEPTH_STENCIL_VIEW_DESC dsvDesc{}; | ||
| depthData.views[0]->GetDesc(&dsvDesc); | ||
| dsvDesc.Flags = D3D11_DSV_READ_ONLY_DEPTH; | ||
|
|
||
| DX::ThrowIfFailed(device->CreateDepthStencilView(depthData.texture, &dsvDesc, stencilWriteReadOnlyDSV.put())); | ||
| } else { | ||
| logger::warn("[VRStereoOptimizations] Could not create read-only DSV: depth stencil data not available"); | ||
| } |
There was a problem hiding this comment.
Don't arm stencil culling when the stencil-write DSV is unavailable.
SetupResources() already logs that stencilWriteReadOnlyDSV can be missing. DispatchStencil() still proceeds and sets stencilActive = true, so the later hook starts injecting NOT_EQUAL stencil tests against a buffer that never received this frame's marks.
Suggested guard
- if (!stencilCS || !stencilWriteVS || !stencilWritePS || !texPerPixelMode || !paramsCB)
+ if (!stencilCS || !stencilWriteVS || !stencilWritePS || !texPerPixelMode || !paramsCB ||
+ !stencilWriteReadOnlyDSV || !stencilWriteDSS || !stencilWriteRS)
return;As per coding guidelines: Include proper resource management and graceful degradation for DirectX 11 resources and user input validation to prevent crashes from malformed configurations
Also applies to: 400-407, 452-456
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/Features/VRStereoOptimizations.cpp` around lines 173 - 182, The code arms
stencil culling even when the stencil-write DSV is missing; modify
DispatchStencil() to check the actual resource (stencilWriteReadOnlyDSV or the
created DSV handle from SetupResources/CreateDepthStencilView) before setting
stencilActive = true and before writing NOT_EQUAL marks, and if the DSV is
absent skip arming and log a warning; also ensure the later hook that injects
NOT_EQUAL stencil tests verifies stencilActive (or the DSV presence) before
applying tests to avoid using an uninitialized stencil buffer.
| float VRMipBias; | ||
| float VRMipBiasNearDist; | ||
| float VRMipBiasFarDist; | ||
| uint VRMipBiasMode; // 0=Off, 1=All Textures, 2=Distant Trees only | ||
| float VRAlphaTestThreshold; // Alpha test threshold for VR TREE_ANIM (0 = use vanilla) | ||
| float4 pad0; // HLSL: float2 + implicit 8-byte gap before float4 AmbientSHR |
There was a problem hiding this comment.
Critical: C++ and HLSL padding mismatch will cause data corruption.
C++ declares float4 pad0 (16 bytes), but HLSL declares float2 pad0 (8 bytes). This mismatch will cause AmbientSHR, AmbientSHG, and AmbientSHB to be read from incorrect offsets in shaders, resulting in corrupted ambient lighting.
The comment mentions "implicit 8-byte gap" but HLSL cbuffer packing does not automatically insert gaps—it packs sequentially within 16-byte boundaries.
🐛 Fix: Make C++ and HLSL padding match
Either change C++ to match HLSL:
- float4 pad0; // HLSL: float2 + implicit 8-byte gap before float4 AmbientSHR
+ float2 pad0; // Must match HLSL
+ float2 _pad1; // Explicit padding to align AmbientSHROr change HLSL to match C++:
- float2 pad0;
+ float4 pad0;The total padding before AmbientSHR must be identical in both C++ and HLSL (likely needs to be 8 or 12 bytes depending on current offset).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/State.h` around lines 213 - 218, The struct has a packing mismatch: C++
declares float4 pad0 while HLSL uses float2 pad0 causing
AmbientSHR/AmbientSHG/AmbientSHB offsets to diverge; fix by making the padding
identical in both languages—either change the C++ pad0 to float2 to match HLSL
or change the HLSL pad0 to float4 to match C++; then update the inline comment
near
VRMipBias/VRMipBiasNearDist/VRMipBiasFarDist/VRMipBiasMode/VRAlphaTestThreshold/pad0
to reflect the exact bytes of padding and verify the total bytes before
AmbientSHR are the same in C++ and HLSL.
|
Taking to draft until #1982 is merged. |
…hanges Review fixes: - DeferredCompositeCS: only early-out MODE_MAIN (mode==2), not MODE_EDGE - CASCS: add max(mxRGB, 1e-4) guard against division by zero - StereoBlendCS: clamp POM-corrected depth to prevent div-by-zero - StencilCS: use cbuffer DisocclusionThreshold instead of hardcoded constant - VR.hlsli: make Eye 1 jitter resolution-adaptive via FrameBuffer::BufferDim - Lighting.hlsl: include TRUE_PBR in pixelOffset export guard - ExtendedMaterials.hlsli: saturate() clamp on pixelOffset output - VRStereoOptimizations.cpp: add DeactivateStencil() to early-exit paths - VRStereoOptimizations.cpp: add null check for depthSRV - Globals.cpp: scope ClearDepthStencilView hook to main scene DSV only - StereoBlend.cpp: use terrain-aware Util::GetCurrentSceneDepthSRV() Revert Upscaling.cpp/h to upstream dev — all DLSS viewport scaling, TAAReorder, and periphery TAA code belongs exclusively in PR2. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add HLSL shaders for the VR DLSS periphery pipeline: - ClearHMDMaskCS: Enhanced with fallback texture support and configurable depth/color region offsets for viewport scaling - DLSSCompositePS: Format-converting fullscreen copy (point-sample) for pasting DLSS output onto the submit texture - DepthUpscalePS: Depth buffer upscaling with point sampling to preserve depth discontinuities at object edges - FeatheredCompositeCS/PS: Feathered alpha blend at DLSS crop boundary using smoothstep falloff (CS fallback + PS with hardware blend) - ForceAlphaCS: Sets alpha=1.0 on submit texture to fix Scaleform UI rendering after DLSS writes non-opaque alpha values - VRPeripheryFillCS: Bilinear upscale from render-res to display-res for the peripheral region outside the DLSS crop Based in part on techniques from PureDark's Skyrim-Upscaler VR (MIT license: https://github.com/PureDark/Skyrim-Upscaler) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…posite Implement DLSS viewport scaling with configurable crop region: - Add vrDlssCropOffsetX setting (0.0-0.3) to shift DLSS crop toward the nasal edge where visual acuity is highest - Add vrDlssFeatherWidth setting for smooth blend at crop boundary (currently disabled pending fix, default 0.0) - Implement feathered composite via pixel shader with SrcAlpha blend (preserves periphery TAA quality) with CS fallback path - Create RTV on vrFinalOutput for PS-based compositing - Fix stencil clear value (0xFF → 0x00) for VR depth upscale path - Add eye0Only parameter to FinalizePerEyeOutputs for single-eye mode - Clear stencil marks before periphery TAA to prevent interference - Set TAA high-frequency response (fTAAHighFreq=1.0) during periphery pass for improved sharpness Based in part on techniques from PureDark's Skyrim-Upscaler VR (MIT license: https://github.com/PureDark/Skyrim-Upscaler) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Refactor Streamline DLSS to support per-eye evaluation in VR: - Split CheckFrameConstants into per-eye loop with viewport[i] offsets - Add crop-aware subrect calculation when viewport scaling is active - Compute cameraPinholeOffset for asymmetric crop (nasal offset) - Support per-eye intermediate textures for color, depth, motion vectors, reactive mask, and transparency mask inputs - Add colorSourceOverride parameter for TAAReorder post-conductor flow - Fix jitter scaling to use crop dimensions when viewport scaling Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement periphery TAA for VR DLSS viewport scaling by hooking into the BSImagespaceShader render pipeline at the post-conductor level. When DLSS processes only a center crop of each eye, the peripheral region needs temporal anti-aliasing from the native TAA pass. This module reorders the pipeline so TAA runs on the full frame while DLSS processes the crop, then composites the DLSS center onto the TAA'd periphery. Hook architecture (all RVAs are SkyrimVR.exe): - BSImagespaceShaderHook (0x132C827): wraps conductor + Phase 5 TAA, evaluates DLSS on Phase 2A output copy after func() returns - ExecutePassHook (0x012D2540): captures Phase 2A output to g_postPPCopy for DLSS color source - ConductorCallHook (0x1325086): tracks conductor state - ForceTAASetter/TAAStateMachine: pass-through TAA control hooks - DepthStencilRegHook (0x00DC79D0): diagnostic logging for depth target registration - SubmitHook (0x00C53920): diagnostic logging for VR frame submission Based on PureDark's BSImagespaceShader_Hook_VR approach from Skyrim-Upscaler VR (MIT license: https://github.com/PureDark/Skyrim-Upscaler). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
3a155ea to
78e003c
Compare
Automated formatting by clang-format, prettier, and other hooks. See https://pre-commit.ci for details.
Screen Space Shadows: reduced sample count for right eye when VRStereoOptimizations is active. StereoSync toggle exposed in UI. SSGI: bypass stereoSync when VRStereoOptimizations is active to prevent eye 0 contamination from sparse eye 1 data. Mode texture early-out in compute passes for culled eye 1 pixels. Remove dead CASCS.hlsl (CAS feature fully removed). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Automated formatting by clang-format, prettier, and other hooks. See https://pre-commit.ci for details.
Process a configurable central crop through DLSS, reducing GPU cost. Periphery filled with bilinear upscale or TAA'd content via conductor. Feathered composite at crop boundary hides quality transition. Nasal crop offset shifts DLSS region toward nose for higher acuity. Key components: - Per-eye DLSS evaluation with viewport-scaled projection matrices - ClearHMDMask zeroes hidden area mesh pixels before DLSS - VRPeripheryFillCS bilinear upscales render-res to display-res - FeatheredCompositePS alpha-blends DLSS crop onto periphery - TAAReorder conductor architecture for periphery TAA - REL::Module::IsVR() in device-creation hook (timing fix) - Dead code removed (vrTAAdPerEye, vrPreTAACopy, BILINEAR_UPSCALE) Based on PureDark's Skyrim-Upscaler VR conductor architecture (MIT). Depends on: feat(vr): VR stereo reprojection optimizations Replaces PR community-shaders#1983. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Process a configurable central crop through DLSS, reducing GPU cost. Periphery filled with bilinear upscale or TAA'd content via conductor. Feathered composite at crop boundary hides quality transition. Nasal crop offset shifts DLSS region toward nose for higher acuity. Key components: - Per-eye DLSS evaluation with viewport-scaled projection matrices - ClearHMDMask zeroes hidden area mesh pixels before DLSS - VRPeripheryFillCS bilinear upscales render-res to display-res - FeatheredCompositePS alpha-blends DLSS crop onto periphery - TAAReorder conductor architecture for periphery TAA - REL::Module::IsVR() in device-creation hook (timing fix) - Dead code removed (vrTAAdPerEye, vrPreTAACopy, BILINEAR_UPSCALE) Based on PureDark's Skyrim-Upscaler VR conductor architecture (MIT). Depends on: feat(vr): VR stereo reprojection optimizations Replaces PR community-shaders#1983. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Process a configurable central crop through DLSS, reducing GPU cost. Periphery filled with bilinear upscale or TAA'd content via conductor. Feathered composite at crop boundary hides quality transition. Nasal crop offset shifts DLSS region toward nose for higher acuity. Key components: - Per-eye DLSS evaluation with viewport-scaled projection matrices - ClearHMDMask zeroes hidden area mesh pixels before DLSS - VRPeripheryFillCS bilinear upscales render-res to display-res - FeatheredCompositePS alpha-blends DLSS crop onto periphery - TAAReorder conductor architecture for periphery TAA - REL::Module::IsVR() in device-creation hook (timing fix) - Bounds guards in ClearHMDMaskCS and ForceAlphaCS - Per-frame flag resets in TAAReorder - Complete pipeline state save/restore in BSImagespaceShaderHook - Crop parameter clamping before Streamline constants Based on PureDark's Skyrim-Upscaler VR conductor architecture (MIT). Depends on: feat(vr): VR stereo reprojection optimizations Replaces PR community-shaders#1983. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Process a configurable central crop through DLSS, reducing GPU cost. Periphery filled with bilinear upscale or TAA'd content via conductor. Feathered composite at crop boundary hides quality transition. Nasal crop offset shifts DLSS region toward nose for higher acuity. Key components: - Per-eye DLSS evaluation with viewport-scaled projection matrices - ClearHMDMask zeroes hidden area mesh pixels before DLSS - VRPeripheryFillCS bilinear upscales render-res to display-res - FeatheredCompositePS alpha-blends DLSS crop onto periphery - TAAReorder conductor architecture for periphery TAA - REL::Module::IsVR() in device-creation hook (timing fix) - Bounds guards in ClearHMDMaskCS and ForceAlphaCS - Per-frame flag resets in TAAReorder - Complete pipeline state save/restore in BSImagespaceShaderHook - Crop parameter clamping before Streamline constants Based on PureDark's Skyrim-Upscaler VR conductor architecture (MIT). Depends on: feat(vr): VR stereo reprojection optimizations Replaces PR community-shaders#1983. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Process a configurable central crop through DLSS, reducing GPU cost. Periphery filled with bilinear upscale or TAA'd content via conductor. Feathered composite at crop boundary hides quality transition. Nasal crop offset shifts DLSS region toward nose for higher acuity. Key components: - Per-eye DLSS evaluation with viewport-scaled projection matrices - ClearHMDMask zeroes hidden area mesh pixels before DLSS - VRPeripheryFillCS bilinear upscales render-res to display-res - FeatheredCompositePS alpha-blends DLSS crop onto periphery - TAAReorder conductor architecture for periphery TAA - REL::Module::IsVR() in device-creation hook (timing fix) - Bounds guards in ClearHMDMaskCS and ForceAlphaCS - Per-frame flag resets in TAAReorder - Complete pipeline state save/restore in BSImagespaceShaderHook - Crop parameter clamping before Streamline constants Based on PureDark's Skyrim-Upscaler VR conductor architecture (MIT). Depends on: feat(vr): VR stereo reprojection optimizations Replaces PR community-shaders#1983. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Process a configurable central crop through DLSS, reducing GPU cost. Periphery filled with bilinear upscale or TAA'd content via conductor. Feathered composite at crop boundary hides quality transition. Nasal crop offset shifts DLSS region toward nose for higher acuity. Key components: - Per-eye DLSS evaluation with viewport-scaled projection matrices - ClearHMDMask zeroes hidden area mesh pixels before DLSS - VRPeripheryFillCS bilinear upscales render-res to display-res - FeatheredCompositePS alpha-blends DLSS crop onto periphery - TAAReorder conductor architecture for periphery TAA - REL::Module::IsVR() in device-creation hook (timing fix) - Bounds guards in ClearHMDMaskCS and ForceAlphaCS - Per-frame flag resets in TAAReorder - Complete pipeline state save/restore in BSImagespaceShaderHook - Crop parameter clamping before Streamline constants Based on PureDark's Skyrim-Upscaler VR conductor architecture (MIT). Depends on: feat(vr): VR stereo reprojection optimizations Replaces PR community-shaders#1983. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Process a configurable central crop through DLSS, reducing GPU cost. Periphery filled with bilinear upscale or TAA'd content via conductor. Feathered composite at crop boundary hides quality transition. Nasal crop offset shifts DLSS region toward nose for higher acuity. Key components: - Per-eye DLSS evaluation with viewport-scaled projection matrices - ClearHMDMask zeroes hidden area mesh pixels before DLSS - VRPeripheryFillCS bilinear upscales render-res to display-res - FeatheredCompositePS alpha-blends DLSS crop onto periphery - TAAReorder conductor architecture for periphery TAA - REL::Module::IsVR() in device-creation hook (timing fix) - Bounds guards in ClearHMDMaskCS and ForceAlphaCS - Per-frame flag resets in TAAReorder - Complete pipeline state save/restore in BSImagespaceShaderHook - Crop parameter clamping before Streamline constants Based on PureDark's Skyrim-Upscaler VR conductor architecture (MIT). Depends on: feat(vr): VR stereo reprojection optimizations Replaces PR community-shaders#1983. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Process a configurable central crop through DLSS, reducing GPU cost. Periphery filled with bilinear upscale or TAA'd content via conductor. Feathered composite at crop boundary hides quality transition. Nasal crop offset shifts DLSS region toward nose for higher acuity. Key components: - Per-eye DLSS evaluation with viewport-scaled projection matrices - ClearHMDMask zeroes hidden area mesh pixels before DLSS - VRPeripheryFillCS bilinear upscales render-res to display-res - FeatheredCompositePS alpha-blends DLSS crop onto periphery - TAAReorder conductor architecture for periphery TAA - REL::Module::IsVR() in device-creation hook (timing fix) - Bounds guards in ClearHMDMaskCS and ForceAlphaCS - Per-frame flag resets in TAAReorder - Complete pipeline state save/restore in BSImagespaceShaderHook - Crop parameter clamping before Streamline constants Based on PureDark's Skyrim-Upscaler VR conductor architecture (MIT). Depends on: feat(vr): VR stereo reprojection optimizations Replaces PR community-shaders#1983. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Process a configurable central crop through DLSS, reducing GPU cost. Periphery filled with bilinear upscale or TAA'd content via conductor. Feathered composite at crop boundary hides quality transition. Nasal crop offset shifts DLSS region toward nose for higher acuity. Key components: - Per-eye DLSS evaluation with viewport-scaled projection matrices - ClearHMDMask zeroes hidden area mesh pixels before DLSS - VRPeripheryFillCS bilinear upscales render-res to display-res - FeatheredCompositePS alpha-blends DLSS crop onto periphery - TAAReorder conductor architecture for periphery TAA - REL::Module::IsVR() in device-creation hook (timing fix) - Bounds guards in ClearHMDMaskCS and ForceAlphaCS - Per-frame flag resets in TAAReorder - Complete pipeline state save/restore in BSImagespaceShaderHook - Crop parameter clamping before Streamline constants Based on PureDark's Skyrim-Upscaler VR conductor architecture (MIT). Depends on: feat(vr): VR stereo reprojection optimizations Replaces PR community-shaders#1983. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Process a configurable central crop through DLSS, reducing GPU cost. Periphery filled with bilinear upscale or TAA'd content via conductor. Feathered composite at crop boundary hides quality transition. Nasal crop offset shifts DLSS region toward nose for higher acuity. Key components: - Per-eye DLSS evaluation with viewport-scaled projection matrices - ClearHMDMask zeroes hidden area mesh pixels before DLSS - VRPeripheryFillCS bilinear upscales render-res to display-res - FeatheredCompositePS alpha-blends DLSS crop onto periphery - TAAReorder conductor architecture for periphery TAA - REL::Module::IsVR() in device-creation hook (timing fix) - Bounds guards in ClearHMDMaskCS and ForceAlphaCS - Per-frame flag resets in TAAReorder - Complete pipeline state save/restore in BSImagespaceShaderHook - Crop parameter clamping before Streamline constants Based on PureDark's Skyrim-Upscaler VR conductor architecture (MIT). Depends on: feat(vr): VR stereo reprojection optimizations Replaces PR community-shaders#1983. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Process a configurable central crop through DLSS, reducing GPU cost. Periphery filled with bilinear upscale or TAA'd content via conductor. Feathered composite at crop boundary hides quality transition. Nasal crop offset shifts DLSS region toward nose for higher acuity. Key components: - Per-eye DLSS evaluation with viewport-scaled projection matrices - ClearHMDMask zeroes hidden area mesh pixels before DLSS - VRPeripheryFillCS bilinear upscales render-res to display-res - FeatheredCompositePS alpha-blends DLSS crop onto periphery - TAAReorder conductor architecture for periphery TAA - REL::Module::IsVR() in device-creation hook (timing fix) - Bounds guards in ClearHMDMaskCS and ForceAlphaCS - Per-frame flag resets in TAAReorder - Complete pipeline state save/restore in BSImagespaceShaderHook - Crop parameter clamping before Streamline constants Based on PureDark's Skyrim-Upscaler VR conductor architecture (MIT). Depends on: feat(vr): VR stereo reprojection optimizations Replaces PR community-shaders#1983. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Process a configurable central crop through DLSS, reducing GPU cost. Periphery filled with bilinear upscale or TAA'd content via conductor. Feathered composite at crop boundary hides quality transition. Nasal crop offset shifts DLSS region toward nose for higher acuity. Key components: - Per-eye DLSS evaluation with viewport-scaled projection matrices - ClearHMDMask zeroes hidden area mesh pixels before DLSS - VRPeripheryFillCS bilinear upscales render-res to display-res - FeatheredCompositePS alpha-blends DLSS crop onto periphery - TAAReorder conductor architecture for periphery TAA - REL::Module::IsVR() in device-creation hook (timing fix) - Bounds guards in ClearHMDMaskCS and ForceAlphaCS - Per-frame flag resets in TAAReorder - Complete pipeline state save/restore in BSImagespaceShaderHook - Crop parameter clamping before Streamline constants Based on PureDark's Skyrim-Upscaler VR conductor architecture (MIT). Depends on: feat(vr): VR stereo reprojection optimizations Replaces PR community-shaders#1983. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Process a configurable central crop through DLSS, reducing GPU cost. Periphery filled with bilinear upscale or TAA'd content via conductor. Feathered composite at crop boundary hides quality transition. Nasal crop offset shifts DLSS region toward nose for higher acuity. Key components: - Per-eye DLSS evaluation with viewport-scaled projection matrices - ClearHMDMask zeroes hidden area mesh pixels before DLSS - VRPeripheryFillCS bilinear upscales render-res to display-res - FeatheredCompositePS alpha-blends DLSS crop onto periphery - TAAReorder conductor architecture for periphery TAA - REL::Module::IsVR() in device-creation hook (timing fix) - Bounds guards in ClearHMDMaskCS and ForceAlphaCS - Per-frame flag resets in TAAReorder - Complete pipeline state save/restore in BSImagespaceShaderHook - Crop parameter clamping before Streamline constants Based on PureDark's Skyrim-Upscaler VR conductor architecture (MIT). Depends on: feat(vr): VR stereo reprojection optimizations Replaces PR community-shaders#1983. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
Details
VR Upscaling Shaders (2917a5f)
New HLSL shaders for the DLSS viewport scaling pipeline:
DLSSCompositePS.hlsl— format-converting fullscreen copy (R11G11B10F → R16G16B16A16F)DepthUpscalePS.hlsl— nearest-neighbor depth upscale preserving depth discontinuitiesForceAlphaCS.hlsl— sets alpha to 1.0 for DLSS input (DLSS requires alpha channel)VRPeripheryFillCS.hlsl— bilinear upscale from render-res to display-res for periphery regionViewport Scaling & Feathered Composite (1c1bb29)
Core viewport scaling infrastructure:
vrDlssViewportScalesetting (0.5–1.0): fraction of each eye processed by DLSSvrDlssCropOffsetX(0.0–0.3): nasal offset to center DLSS on high-acuity regionStreamline Per-Eye DLSS (6a0e00b)
Extends Streamline DLSS integration for VR per-eye evaluation:
Periphery TAA via TAAReorder (92139c9)
Post-conductor hook system that captures vanilla TAA output for the periphery:
Attribution
The feathered composite and TAAReorder post-conductor techniques are based on PureDark's Skyrim-Upscaler VR (MIT license).
Test plan
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Enhancements
Bug Fixes