Instanced Stereo + TSR below 100% screen percentage draws a second, shrunken copy of the view into each VR eye

Summary

With Instanced Stereo enabled and TSR as the primary upscaler, any r.ScreenPercentage below 100 makes each eye’s final image contain a second, smaller copy of the scene. The copy is opaque and hard-edged, and it is the full view shrunk to the internal (pre-upscale) eye size. It sits at the position of the secondary view’s internal-resolution ViewRect. At 50% with 2376x2376 eyes, that is a 1188x1188 copy at (1192, 0) in each eye’s output: the top-right quadrant. It is drawn into the eye images themselves, so it shows both in the headset and in the desktop spectator window.

What Type of Bug are you experiencing?

Rendering (Graphics / Niagara)

Steps to Reproduce

  1. Project settings: Instanced Stereo on (vr.InstancedStereo=True), deferred shading, Anti-Aliasing Method = TSR (r.AntiAliasingMethod=4), OpenXR plugin enabled.
  2. Play in VR Preview on any level.
  3. Console: r.ScreenPercentage 50.
  4. Look at the top right of either eye, in the headset or the spectator window.
  5. Console: r.Test.PrimaryScreenPercentageMethodOverride 1 (spatial upscale): the copy disappears immediately. r.ScreenPercentage 100 also hides it.

Observed in our project. Not yet reduced to a stock template, but nothing project-specific is involved.

Expected Result

Each eye shows only its own view, upscaled to the full eye resolution.

Observed Result

Each eye also shows its view shrunk to the internal resolution and placed at the secondary view’s internal ViewRect: the top-right quadrant at 50%.

DumpGPU (r.DumpGPU.Root *PostProcessing*, images attached):

  • View0 TSR UpdateHistory output (TSR.History.Color slice 3, 2384x2376) is clean (image 2).
  • View0 Tonemap 2376x2376 output (Tonemap, 2384x2376) already contains the copy in [1192..2380]x[0..1188] (image 1). Its inputs are the clean TSR output and BloomY, and its parameters are normal: Color/Output viewport (0,0)-(2376,2376), ColorToBloom = (1,1,0,0).
  • The copy is carried through CompositeDebugPrimitives into ViewFamilyTexture (4752x2376) for both eyes (image 3).

Affects Versions

5.8

Platform(s)

Linux

Upload an image

Video

N/A

For crash reports, include your callstack

N/A

Additional Notes

Likely cause, from 5.8.1 source:

  • PreVisibilityFrameSetup (SceneVisibility.cpp) sets InstanceFactor = 2 for both eye views when Instanced Stereo is enabled.
  • Screen passes draw View.GetStereoPassInstanceFactor() instances: FScreenPassViewInfo (ScreenPass.cpp), and the Tonemap DrawRectangle(..., View.GetStereoPassInstanceFactor()) in PostProcessTonemap.cpp.
  • ScreenPassStereoOutputSetupVS (InstancedStereo.ush) routes InstanceId to SV_ViewportArrayIndex (“Screen passes are only drawn twice so the instance id corresponds to the eye index”).
  • After TSR, post-processing runs per view at output resolution into per-view textures and sets a single viewport (e.g. RHICmdList.SetViewport(0, 0, 0, TargetSize.X, TargetSize.Y, 1) in the Tonemap pass). Instance 1 therefore rasterises into viewport slot 1 as last set by the instanced-stereo scene passes, which is the secondary view’s internal ViewRect. (Inferred from the copy’s exact position and size; the dump does not record viewport state.)
  • At 100% that rect lies outside the per-view texture (x >= 2376 in a 2384-wide target), so the second instance is clipped. Below 100% it lands inside.

Suggested fix direction: per-view screen passes that run after a temporal upscale should draw one instance, or set viewport slot 1 to the view’s own output rect.

Workarounds: vr.InstancedStereo=False; keep r.ScreenPercentage at 100; or use a spatial primary upscaler.

Possibly related: How do you make upscaling work in VR? (UE 5.2, OpenXR): with TSR or FSR, screen percentage “shrinks the viewport” and part of the right eye appears in the left.