-
Notifications
You must be signed in to change notification settings - Fork 130
feat(vr-dlss): VR DLSS viewport scaling with periphery TAA #2003
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| // Format-converting fullscreen pixel shader with optional bilinear upscale. | ||
| // Used by TAAReorder to composite between textures of different DXGI formats | ||
| // (e.g. R8G8B8A8_UNORM conductor RTs <-> R11G11B10_FLOAT kMAIN). | ||
| // The GPU's output merger handles format conversion automatically. | ||
| // | ||
| // BILINEAR_UPSCALE variant: upscales render-res content to display-res by | ||
| // mapping output pixel positions through the dynamic resolution scale, | ||
| // like PureDark's dynamicResScale in his blend shader. | ||
|
|
||
| #include "Upscaling/UpscaleVS.hlsl" | ||
|
|
||
| #ifdef PSHADER | ||
|
|
||
| Texture2D<float4> Source : register(t0); | ||
|
|
||
| # ifdef BILINEAR_UPSCALE | ||
|
|
||
| cbuffer CompositeCB : register(b0) | ||
| { | ||
| float2 DynResScale; // renderRes / displayRes (per-eye) | ||
| float2 EyeOffset; // (i * eyeWidth, 0) in texels | ||
| float2 SrcTexSize; // full texture dimensions in texels | ||
| float2 pad; | ||
| }; | ||
|
|
||
| SamplerState LinearSampler : register(s0); | ||
|
|
||
| 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); | ||
| } | ||
|
|
||
| # else | ||
|
|
||
| float4 main(VS_OUTPUT input) : SV_Target | ||
| { | ||
| return Source.Load(int3(input.Position.xy, 0)); | ||
| } | ||
|
|
||
| # endif // BILINEAR_UPSCALE | ||
|
|
||
| #endif // PSHADER |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| /** | ||
| * @file DepthUpscalePS.hlsl | ||
| * @brief Point-sampled depth buffer upscaling for VR depth-based culling | ||
| * | ||
| * When upscaling (FSR/DLSS) is active, the depth buffer is rendered at a lower | ||
| * resolution than the display. Skyrim VR's depth-based culling (OBBOcclusionTesting) | ||
| * reads from the depth buffer to determine object visibility, but with a mismatched | ||
| * resolution, objects may be incorrectly culled (appearing to flicker in/out of view). | ||
| * | ||
| * This shader upscales the low-resolution depth buffer to full resolution using | ||
| * pure point sampling. Previous conservative blending (GatherRed + lerp toward | ||
| * min depth) caused HAM mask bleed: depth == 0 values from the hidden area mesh | ||
| * leaked into valid depth through the 2x2 neighborhood blend, creating artifacts | ||
| * at the mask boundary after DRS upscaling. | ||
| * | ||
| * Based on depth upscaling approach by vrnord | ||
| * https://github.com/vrnord/skyrim-community-shaders-VR-DLSS | ||
| */ | ||
|
|
||
| #include "Upscaling/UpscaleVS.hlsl" | ||
|
|
||
| #if defined(PSHADER) | ||
| # include "Common/FrameBuffer.hlsli" | ||
| # include "Common/SharedData.hlsli" | ||
|
|
||
| typedef VS_OUTPUT PS_INPUT; | ||
|
|
||
| struct PS_OUTPUT | ||
| { | ||
| float Depth: SV_Depth; | ||
| }; | ||
|
|
||
| Texture2D<float> DepthLowRes : register(t0); | ||
|
|
||
| cbuffer DepthUpscaleCB : register(b0) | ||
| { | ||
| float2 SourceDim; // Full texture dimensions (texels) | ||
| float2 InvSourceDim; // 1.0 / SourceDim | ||
| float2 Scale; // resolutionScale (render/display ratio) | ||
| float2 Pad; | ||
| }; | ||
|
|
||
| /** | ||
| * @brief Main pixel shader entry point | ||
| * | ||
| * Pure point-sampled depth upscaling. Maps display-res pixel position to | ||
| * render-res texel and loads directly — no blending, no mask bleed. | ||
| */ | ||
| PS_OUTPUT main(PS_INPUT input) | ||
| { | ||
| PS_OUTPUT psout; | ||
|
|
||
| // Map full-res UV to render-res UV (same transform as the engine's | ||
| // GetDynamicResolutionAdjustedScreenPosition). | ||
| float2 uv = Scale * input.TexCoord; | ||
|
|
||
| // Per-eye clamping for SBS stereo: prevent sampling across the center seam. | ||
| bool isRight = input.TexCoord.x >= 0.5; | ||
| float halfScale = 0.5 * Scale.x; | ||
| uv.x = clamp(uv.x, isRight ? halfScale : 0.0, isRight ? Scale.x : halfScale); | ||
| uv.y = clamp(uv.y, 0.0, Scale.y); | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| // Nearest texel coordinate — pure point sampling, no blending | ||
| int2 texel = int2(floor(uv * SourceDim)); | ||
| psout.Depth = DepthLowRes.Load(int3(texel, 0)); | ||
|
|
||
| return psout; | ||
| } | ||
|
|
||
| #endif | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,41 @@ | ||||||||||||||||||||||
| cbuffer FeatherCB : register(b0) | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| uint CropX; // paste position X in output space | ||||||||||||||||||||||
| uint CropY; // paste position Y in output space | ||||||||||||||||||||||
| uint CropW; // crop width | ||||||||||||||||||||||
| uint CropH; // crop height | ||||||||||||||||||||||
| float FeatherWidth; // feather distance in pixels (inward from crop edge) | ||||||||||||||||||||||
| float3 pad; | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Texture2D<float4> CropTexture : register(t0); // DLSS output (crop-sized, at {0,0}) | ||||||||||||||||||||||
| RWTexture2D<float4> OutputTexture : register(u0); // vrFinalOutput (already filled with periphery) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| [numthreads(8, 8, 1)] void main(uint3 dispatchID : SV_DispatchThreadID) { | ||||||||||||||||||||||
| // dispatchID is in crop-local space (0..CropW-1, 0..CropH-1) | ||||||||||||||||||||||
| int2 cropLocal = int2(dispatchID.xy); | ||||||||||||||||||||||
| if (cropLocal.x >= (int)CropW || cropLocal.y >= (int)CropH) | ||||||||||||||||||||||
| return; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Output pixel = crop-local + paste offset | ||||||||||||||||||||||
| int2 pixel = cropLocal + int2(CropX, CropY); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Distance from nearest crop edge (positive = inside) | ||||||||||||||||||||||
| float distLeft = (float)cropLocal.x; | ||||||||||||||||||||||
| float distRight = (float)(CropW - 1 - cropLocal.x); | ||||||||||||||||||||||
| float distTop = (float)cropLocal.y; | ||||||||||||||||||||||
| float distBottom = (float)(CropH - 1 - cropLocal.y); | ||||||||||||||||||||||
| float distFromEdge = min(min(distLeft, distRight), min(distTop, distBottom)); | ||||||||||||||||||||||
|
Comment on lines
+24
to
+28
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Edge distance calculation differs from pixel shader variant. This compute shader uses 🔧 Suggested fix for consistency // Distance from nearest crop edge (positive = inside)
float distLeft = (float)cropLocal.x;
- float distRight = (float)(CropW - 1 - cropLocal.x);
+ float distRight = (float)(CropW - cropLocal.x);
float distTop = (float)cropLocal.y;
- float distBottom = (float)(CropH - 1 - cropLocal.y);
+ float distBottom = (float)(CropH - cropLocal.y);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. leaving open. Feathered Edge is still being worked on.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
(ノ◕ヮ◕)ノ*:・゚✧ 🧠 Learnings used |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| float4 dlss = CropTexture.Load(int3(cropLocal, 0)); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if (FeatherWidth <= 0.0 || distFromEdge >= FeatherWidth) { | ||||||||||||||||||||||
| // Inside crop interior or no feathering: 100% DLSS | ||||||||||||||||||||||
| OutputTexture[pixel] = dlss; | ||||||||||||||||||||||
| } else { | ||||||||||||||||||||||
| // Feather zone: smooth blend from periphery (TAA-stabilized) to DLSS | ||||||||||||||||||||||
| float blend = smoothstep(0.0, FeatherWidth, distFromEdge); | ||||||||||||||||||||||
| float4 periphery = OutputTexture[pixel]; | ||||||||||||||||||||||
| OutputTexture[pixel] = lerp(periphery, dlss, blend); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| // Feathered DLSS crop composite using hardware alpha blending. | ||
| // Based on PureDark's approach from Skyrim-Upscaler VR (MIT license). | ||
| // | ||
| // The render target already contains TAA'd periphery content. | ||
| // We output float4(DLSSColor, featherAlpha) and let the output merger's | ||
| // SrcAlpha/InvSrcAlpha blend preserve the periphery in the feather zone | ||
| // and outside the crop rect entirely. | ||
|
|
||
| #include "Upscaling/UpscaleVS.hlsl" | ||
|
|
||
| #ifdef PSHADER | ||
|
|
||
| Texture2D<float4> CropTexture : register(t0); | ||
| SamplerState LinearSampler : register(s0); | ||
|
|
||
| cbuffer FeatheredCompositeCB : register(b0) | ||
| { | ||
| float2 CropOrigin; // paste position (x, y) in output-eye pixel coords | ||
| float2 CropSize; // crop width, height in pixels | ||
| float FeatherWidth; // feather distance in pixels (inward from crop edge) | ||
| float _pad0; | ||
| float2 SrcUVOrigin; // UV origin in source texture for this crop region | ||
| float2 SrcUVScale; // UV scale: maps [0,1] crop-local UV to source texture UV range | ||
| }; | ||
|
vrnord marked this conversation as resolved.
|
||
|
|
||
| float4 main(VS_OUTPUT input) : SV_Target | ||
| { | ||
| float2 pixelPos = input.Position.xy; | ||
|
|
||
| // Distance from each edge of the crop rect (positive = inside) | ||
| float distLeft = pixelPos.x - CropOrigin.x; | ||
| float distRight = (CropOrigin.x + CropSize.x) - pixelPos.x; | ||
| float distTop = pixelPos.y - CropOrigin.y; | ||
| float distBottom = (CropOrigin.y + CropSize.y) - pixelPos.y; | ||
|
|
||
| float minDist = min(min(distLeft, distRight), min(distTop, distBottom)); | ||
|
|
||
| // Outside crop rect: fully transparent (hardware blend preserves TAA'd periphery) | ||
| if (minDist <= 0.0) | ||
| return float4(0, 0, 0, 0); | ||
|
|
||
| // Feather alpha: smoothstep ramp from 0 at edge to 1 at FeatherWidth inside | ||
| // (matches the smoothstep from the original CS for visual consistency) | ||
| float alpha = (FeatherWidth > 0.0) ? smoothstep(0.0, FeatherWidth, minDist) : 1.0; | ||
|
|
||
| // Map pixel position to crop-local UV [0,1], then remap to source texture UV. | ||
| // For per-eye textures: SrcUVOrigin=(0,0), SrcUVScale=(1,1) (identity). | ||
| // For SBS textures: SrcUVOrigin/Scale select the correct eye's crop region. | ||
| float2 cropUV = (pixelPos - CropOrigin) / CropSize; | ||
| float2 srcUV = cropUV * SrcUVScale + SrcUVOrigin; | ||
| float3 dlssColor = CropTexture.SampleLevel(LinearSampler, srcUV, 0).rgb; | ||
|
|
||
| return float4(dlssColor, alpha); | ||
| } | ||
|
|
||
| #endif // PSHADER | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| // Forces alpha to 1.0 across the entire texture. | ||
| // Used after DLSS center paste onto submit texture to ensure Scaleform UI renders. | ||
| // DLSS output may have alpha=0 (from R11G11B10→R8G8B8A8 conversion with no alpha source), | ||
| // which can prevent UI compositing in the DLSS center area. | ||
|
|
||
| RWTexture2D<float4> ColorInOut : register(u0); | ||
|
|
||
| [numthreads(8, 8, 1)] void main(uint3 dispatchID : SV_DispatchThreadID) { | ||
| uint w, h; | ||
| ColorInOut.GetDimensions(w, h); | ||
| if (dispatchID.x >= w || dispatchID.y >= h) | ||
| return; | ||
|
|
||
| float4 c = ColorInOut[dispatchID.xy]; | ||
| c.a = 1.0; | ||
| ColorInOut[dispatchID.xy] = c; | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| // Bilinear upscale from render-resolution per-eye buffer to display-resolution per-eye buffer. | ||
| // Used for VR viewport scaling: fills the full eye output with a cheap upscale so the | ||
| // periphery (outside the DLSS-processed center) is not black/empty. | ||
|
|
||
| cbuffer PeripheryFillCB : register(b0) | ||
| { | ||
| uint SrcWidth; | ||
| uint SrcHeight; | ||
| uint DstWidth; | ||
| uint DstHeight; | ||
| }; | ||
|
|
||
| Texture2D<float4> SrcTexture : register(t0); | ||
| SamplerState LinearSampler : register(s0); | ||
| RWTexture2D<float4> DstTexture : register(u0); | ||
|
|
||
| [numthreads(8, 8, 1)] void main(uint3 dispatchID : SV_DispatchThreadID) { | ||
| if (dispatchID.x >= DstWidth || dispatchID.y >= DstHeight) | ||
| return; | ||
|
|
||
| // Normalized UV with half-pixel offset for correct bilinear sampling | ||
| float2 uv = (float2(dispatchID.xy) + 0.5) / float2(DstWidth, DstHeight); | ||
| DstTexture[dispatchID.xy] = SrcTexture.SampleLevel(LinearSampler, uv, 0); | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.