Mode B jitter VALIDATED: clip-space gl_Position injection (the correct method)
Replaces the viewport-shift jitter (which trembled - emulator viewport jitter on a non-jitter-aware guest cannot accumulate cleanly) with the native-DLSS approach: inject gl_Position.xy += jitterNDC * w into guest vertex shaders via the translator (SupportBuffer.JitterOffset + EmitterContext epilogue). Motion vectors de-jittered by (J_n - J_n-1), negative texture LOD bias to steady the mips. Confirmed in-game: sharp, stable, DLSS accumulates the sub-pixel detail.
This commit is contained in:
@@ -739,16 +739,24 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
Span<ViewportTransform> viewportTransformSpan = _state.State.ViewportTransform.AsSpan();
|
||||
Span<ViewportExtents> viewportExtentsSpan = _state.State.ViewportExtents.AsSpan();
|
||||
|
||||
// DLSS Mode B: shift the resolution-scaled viewport by this frame's sub-pixel jitter. Only the
|
||||
// scaled (main) pass; native passes (UI) keep RenderTargetScale 1 and stay untouched. The offset
|
||||
// is carried to DLSS through the present queue (see DlssJitterState), not tagged on a texture.
|
||||
// No-op (offset 0) unless jitter is enabled, so the default path is unchanged.
|
||||
float jitterX = 0f, jitterY = 0f;
|
||||
// DLSS Mode B: hand the shader this frame's sub-pixel jitter as a clip-space NDC offset, which it
|
||||
// adds to gl_Position scaled by w (the correct, native-DLSS way to jitter -- not a viewport shift).
|
||||
// Only the scaled (main) 3D pass; native passes (UI) keep RenderTargetScale 1 and are NOT jittered.
|
||||
// The offset is 0 unless jitter is enabled, so the default path is unchanged.
|
||||
float jitterNdcX = 0f, jitterNdcY = 0f;
|
||||
if (DlssJitterState.Enabled && _channel.TextureManager.RenderTargetScale != 1f)
|
||||
{
|
||||
jitterX = DlssJitterState.OffsetX;
|
||||
jitterY = DlssJitterState.OffsetY;
|
||||
ref ViewportTransform vp0 = ref viewportTransformSpan[0];
|
||||
float jScale = _channel.TextureManager.RenderTargetScale;
|
||||
float vpWidth = MathF.Abs(vp0.ScaleX) * 2f * jScale;
|
||||
float vpHeight = MathF.Abs(vp0.ScaleY) * 2f * jScale;
|
||||
if (vpWidth > 0f && vpHeight > 0f)
|
||||
{
|
||||
jitterNdcX = DlssJitterState.OffsetX * 2f / vpWidth;
|
||||
jitterNdcY = DlssJitterState.OffsetY * 2f / vpHeight;
|
||||
}
|
||||
}
|
||||
_context.SupportBufferUpdater.SetJitter(jitterNdcX, jitterNdcY);
|
||||
|
||||
for (int index = 0; index < Constants.TotalViewports; index++)
|
||||
{
|
||||
@@ -792,10 +800,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
y *= scale;
|
||||
width *= scale;
|
||||
height *= scale;
|
||||
|
||||
// Sub-pixel jitter is in scaled-render pixels, same space as x/y here (0 unless Mode B).
|
||||
x += jitterX;
|
||||
y += jitterY;
|
||||
}
|
||||
|
||||
Rectangle<float> region = new(x, y, width, height);
|
||||
|
||||
@@ -88,6 +88,23 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
MarkDirty(SupportBuffer.ViewportSizeOffset, SupportBuffer.FieldSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the DLSS Mode B clip-space jitter offset (in NDC) the shader adds to vertex positions
|
||||
/// (scaled by w). Zero unless jitter is enabled, so the default path is unaffected.
|
||||
/// </summary>
|
||||
/// <param name="x">Jitter X in normalized device coordinates</param>
|
||||
/// <param name="y">Jitter Y in normalized device coordinates</param>
|
||||
public void SetJitter(float x, float y)
|
||||
{
|
||||
if (_data.JitterOffset.X != x || _data.JitterOffset.Y != y)
|
||||
{
|
||||
_data.JitterOffset.X = x;
|
||||
_data.JitterOffset.Y = y;
|
||||
|
||||
MarkDirty(SupportBuffer.JitterOffsetOffset, SupportBuffer.FieldSize);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the scale of all output render targets (they should all have the same scale).
|
||||
/// </summary>
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace Ryujinx.Graphics.Shader
|
||||
RenderScale,
|
||||
TfeOffset,
|
||||
TfeVertexCount,
|
||||
JitterOffset,
|
||||
}
|
||||
|
||||
public struct SupportBuffer
|
||||
@@ -42,6 +43,7 @@ namespace Ryujinx.Graphics.Shader
|
||||
public static readonly int ComputeRenderScaleOffset;
|
||||
public static readonly int TfeOffsetOffset;
|
||||
public static readonly int TfeVertexCountOffset;
|
||||
public static readonly int JitterOffsetOffset;
|
||||
|
||||
public const int FragmentIsBgraCount = 8;
|
||||
// One for the render target, 64 for the textures, and 8 for the images.
|
||||
@@ -68,6 +70,7 @@ namespace Ryujinx.Graphics.Shader
|
||||
ComputeRenderScaleOffset = GraphicsRenderScaleOffset + FieldSize;
|
||||
TfeOffsetOffset = OffsetOf(ref instance, ref instance.TfeOffset);
|
||||
TfeVertexCountOffset = OffsetOf(ref instance, ref instance.TfeVertexCount);
|
||||
JitterOffsetOffset = OffsetOf(ref instance, ref instance.JitterOffset);
|
||||
}
|
||||
|
||||
internal static StructureType GetStructureType()
|
||||
@@ -80,7 +83,8 @@ namespace Ryujinx.Graphics.Shader
|
||||
new StructureField(AggregateType.S32, "frag_scale_count"),
|
||||
new StructureField(AggregateType.Array | AggregateType.FP32, "render_scale", RenderScaleMaxCount),
|
||||
new StructureField(AggregateType.Vector4 | AggregateType.S32, "tfe_offset"),
|
||||
new StructureField(AggregateType.S32, "tfe_vertex_count")
|
||||
new StructureField(AggregateType.S32, "tfe_vertex_count"),
|
||||
new StructureField(AggregateType.Vector4 | AggregateType.FP32, "jitter_offset")
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -95,5 +99,6 @@ namespace Ryujinx.Graphics.Shader
|
||||
|
||||
public Vector4<int> TfeOffset;
|
||||
public Vector4<int> TfeVertexCount;
|
||||
public Vector4<float> JitterOffset;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,6 +292,20 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
}
|
||||
}
|
||||
|
||||
// DLSS Mode B clip-space jitter: offset the vertex position by the sub-pixel jitter (in NDC),
|
||||
// scaled by w so it stays a constant pixel shift after the perspective divide -- the correct,
|
||||
// native-DLSS way to jitter, unlike a viewport shift. The offset is 0 unless jitter is enabled,
|
||||
// so the default path is byte-identical.
|
||||
{
|
||||
Operand jpx = this.Load(StorageKind.Output, IoVariable.Position, null, Const(0));
|
||||
Operand jpy = this.Load(StorageKind.Output, IoVariable.Position, null, Const(1));
|
||||
Operand jpw = this.Load(StorageKind.Output, IoVariable.Position, null, Const(3));
|
||||
Operand jox = this.Load(StorageKind.ConstantBuffer, SupportBuffer.Binding, Const((int)SupportBufferField.JitterOffset), Const(0));
|
||||
Operand joy = this.Load(StorageKind.ConstantBuffer, SupportBuffer.Binding, Const((int)SupportBufferField.JitterOffset), Const(1));
|
||||
this.Store(StorageKind.Output, IoVariable.Position, null, Const(0), this.FPFusedMultiplyAdd(jox, jpw, jpx));
|
||||
this.Store(StorageKind.Output, IoVariable.Position, null, Const(1), this.FPFusedMultiplyAdd(joy, jpw, jpy));
|
||||
}
|
||||
|
||||
if (TranslatorContext.Definitions.ViewportTransformDisable)
|
||||
{
|
||||
Operand x = this.Load(StorageKind.Output, IoVariable.Position, null, Const(0));
|
||||
|
||||
@@ -20,10 +20,10 @@ namespace Ryujinx.Graphics.Vulkan.Dlss
|
||||
|
||||
public static bool Enabled => _flag && DlssIntegration.IsEnabled;
|
||||
|
||||
// Magnitude of the jitter offset handed to DLSS, as a multiplier on our scaled-render-pixel value,
|
||||
// to match NGX's expected unit. Live-tunable via RYUJINX_DLSS_JITTER_SCALE so the value can be swept
|
||||
// without recompiling; defaults to 0.25. Read once at startup. NOTE: this scales ONLY the offset
|
||||
// sent to Evaluate -- the motion-field de-jitter stays at full physical magnitude.
|
||||
// Jitter amplitude in render pixels: the Halton offset (+-0.5) is multiplied by this before it is
|
||||
// published. 1.0 = a real +-0.5px sub-pixel jitter (what DLSS wants). Live-tunable via
|
||||
// RYUJINX_DLSS_JITTER_SCALE so a large value (e.g. 10 = +-5px) can be used as a VISIBLE "reticle"
|
||||
// test to confirm the clip-space gl_Position injection is actually moving the image. Read once.
|
||||
private static readonly float _scale = ParseScale();
|
||||
|
||||
public static float Scale => _scale;
|
||||
@@ -39,7 +39,7 @@ namespace Ryujinx.Graphics.Vulkan.Dlss
|
||||
return scale;
|
||||
}
|
||||
|
||||
return 0.5f; // calibrated magic value that locks the image (was swept live from 0.25)
|
||||
return 1.0f; // real +-0.5px sub-pixel jitter by default
|
||||
}
|
||||
|
||||
// Texture mip LOD bias applied to every guest sampler while jitter is on (NVIDIA's DLSS guidance: a
|
||||
@@ -83,8 +83,8 @@ namespace Ryujinx.Graphics.Vulkan.Dlss
|
||||
|
||||
_index = _index % SequenceLength + 1; // 1..N (Halton is undefined at 0)
|
||||
DlssJitterState.Enabled = true;
|
||||
DlssJitterState.OffsetX = Halton(_index, 2) - 0.5f;
|
||||
DlssJitterState.OffsetY = Halton(_index, 3) - 0.5f;
|
||||
DlssJitterState.OffsetX = (Halton(_index, 2) - 0.5f) * _scale;
|
||||
DlssJitterState.OffsetY = (Halton(_index, 3) - 0.5f) * _scale;
|
||||
}
|
||||
|
||||
private static float Halton(uint index, uint radix)
|
||||
|
||||
@@ -295,13 +295,12 @@ namespace Ryujinx.Graphics.Vulkan.Dlss
|
||||
TextureView mvSource = DlssIntegration.MvFilterEnabled ? _motionFiltered : _motion;
|
||||
StreamlineDlss.DlssTexture mvTex = Describe(mvSource, cbs);
|
||||
|
||||
// Grid/NDC-normalization hypothesis: convert the pixel jitter to normalized device coordinates
|
||||
// (span 2.0 across the texture) before handing it to DLSS, keeping the validated signs. NOTE:
|
||||
// Streamline/NGX documents this offset in PIXELS, so normalizing is expected to UNDER-drive DLSS
|
||||
// (offset ~0.0005); kept as the explicit test the user asked for. The de-jitter of the motion
|
||||
// field stays at full pixel magnitude (directive: physical compensation intact).
|
||||
float evalJitterX = (frameJitterX * 2.0f / input.Width) * JitterSignX;
|
||||
float evalJitterY = (frameJitterY * 2.0f / input.Height) * JitterSignY;
|
||||
// DLSS jitter offset in PIXELS (Streamline/NGX convention): the value the frame was actually
|
||||
// rendered with, carried through the present queue (frameJitterX/Y), with the validated signs.
|
||||
// The clip-space injection in the vertex shader shifts the image by exactly this many pixels, so
|
||||
// DLSS is told the matching pixel offset. The de-jitter stays at full pixel magnitude too.
|
||||
float evalJitterX = frameJitterX * JitterSignX;
|
||||
float evalJitterY = frameJitterY * JitterSignY;
|
||||
|
||||
bool ok = StreamlineDlss.Evaluate(
|
||||
(IntPtr)cbs.CommandBuffer.Handle,
|
||||
|
||||
Reference in New Issue
Block a user