Skip to content

Add DLSS/FSR quality presets - #560

Merged
xrSimpAI merged 12 commits into
OGSR:mainfrom
brodrigz:feature/render-scale-presets
Sep 12, 2026
Merged

xrSimpAI merged 12 commits into
OGSR:mainfrom
brodrigz:feature/render-scale-presets

Conversation

@brodrigz

@brodrigz brodrigz commented Sep 3, 2026 •

Copy link
Copy Markdown
Contributor

Add DLSS and FSR 3 quality presets

Summary

Adds quality/upscaling modes for DLSS and FSR3 instead of only exposing the native-resolution AA modes.

IX-Ray's existing render implementation was used as reference for the general approach, the quality preset model, and treating render resolution and output resolution as separate things. This is not a port of that code. Most of the work was dealing with assumptions specific to OGSR's render and post-processing pipeline.

Both upscalers now expose the usual quality levels:

  • Native / AA: 1.00x render scale
  • Quality: ~0.67x
  • Balanced: ~0.58x
  • Performance: 0.50x
  • Ultra Performance: ~0.33x

These are per-axis render-resolution factors, not total pixel-count factors.

The exact internal dimensions come from the respective SDKs rather than hardcoded numbers. DLSS uses NGX_DLSS_GET_OPTIMAL_SETTINGS(), and FSR3 uses ffxFsr3UpscalerGetRenderResolutionFromQualityMode().

Render and display resolution domains

CRenderTarget now keeps separate physical render and display dimensions.

The render domain is the resolution used for the scene, G-buffer, depth, velocity, and the input to the temporal upscaler.

The display domain is the final output resolution and the resolution used by the post-upscale pipeline.

IX-Ray already has the same basic distinction in its render scaling path, and that was a useful reference for the general model. OGSR, however, had quite a few places where Device.dwWidth/dwHeight, the current render target dimensions, and the expected shader input dimensions were interchangeable, because they had always been the same. That is no longer true.

Added explicit render/display dimensions and helpers for the active resolution, viewport dimensions, and render-to-display ratio.

The main scene targets now use the selected physical render resolution where appropriate, while targets that belong after temporal upscaling stay display-sized.

Shader constants were added for:

  • render_res
  • display_res
  • render_subrect

screen_res remains available and follows the currently active domain.

Depth buffers and other resources which are physically render-sized now use render_res when converting normalized coordinates to pixel coordinates, instead of assuming the display resolution.

This includes assorted depth-dependent shader paths such as motion blur, thermal vision, and depth blur.

DLSS / FSR3 quality modes

Added:

  • r_aa_dlss_quality
  • r_aa_fsr3_quality

For DLSS, the selected quality level is mapped to the matching NGX performance/quality mode and passed to NGX_DLSS_GET_OPTIMAL_SETTINGS().

The render size returned by NGX is then used as the physical scene resolution, with the device resolution kept as the DLSS target/output resolution.

IX-Ray's DLSS wrapper was a reference here, particularly its use of separate renderSize and displaySize values when creating the feature, instead of treating DLSS as a native-resolution post effect.

OGSR does not derive the DLSS input resolution from a fixed scale table. It asks NGX for the recommended dimensions for the requested mode.

If that query fails, DLSS falls back to DLAA rather than creating an upscaling feature that uses native-sized resources with a non-native quality mode.

FSR3 does the same job through the FidelityFX API, using ffxFsr3UpscalerGetRenderResolutionFromQualityMode() to calculate its physical render resolution.

The FSR3 context is created with separate render and output dimensions.

Its auxiliary resources were previously created from hardcoded D3D11 texture descriptions. They are now created from the descriptions returned by ffxFsr3UpscalerGetSharedResourceDescriptions(), including the dimensions, formats, and usage flags requested by the SDK.

Added the required FFX-to-DXGI format mapping for those resources.

The FSR3 context also enables the HDR input flag used by the renderer. The FSR3 resource setup follows the current FidelityFX API and the existing OGSR FSR3 integration; it is not from IX-Ray.

Changing either quality setting requires applying video settings or running vid_restart, because quality now changes the physical dimensions of a fairly large number of render targets.

Trying to rebuild all of those in the middle of a frame sounded like a good way to create several new bugs while fixing one, so changing the console value live just reports that a restart/apply is required.

Post-processing boundary

Added a display-sized $user$postprocess0 target which acts as the boundary between scene/temporal rendering and the regular post-processing stack.

Previously a lot of post effects read directly from $user$generic0.

That worked while generic0 was always native-sized. It does not work when generic0 is, for example, a 1920x1080 scene which DLSS has just turned into a 3840x2160 output.

After temporal upscaling, the display-sized result is moved into postprocess0, and post effects which operate on the final image read from that instead.

The affected paths include:

  • normal combine
  • CAS
  • both screen-space sunshaft implementations
  • blur
  • DOF
  • fake scopes
  • gas mask effects
  • LUT
  • night vision
  • rain drops

The corresponding C++ phases were adjusted to render using the destination target dimensions instead of assuming everything is still running at the scene resolution.

Scene/depth inputs remain render-sized where appropriate. Only the image being post-processed moves into the display domain.

This is mostly OGSR-specific fallout from introducing a real render/output resolution split. There was not much useful code to borrow from IX-Ray here, because the two post-processing pipelines are structured differently.

Fallback path

There also needs to be a sane failure path once the scene is physically smaller than the output.

CopyResource cannot scale a texture.

Added a small temporal_resolve pass for cases where the renderer has already rendered the scene at reduced resolution but the selected temporal upscaler fails.

Instead of copying incompatible resources or leaving a smaller image occupying a corner of the screen, the scene is stretched into the display-sized postprocess target and rendering can continue.

The runtime fallback order is now:

DLSS -> FSR3 -> TAA

A failed DLSS dispatch therefore tries FSR3, and a failed FSR3 dispatch drops back to TAA rather than continuing with a half-initialized temporal mode.

Viewports and render targets

Fullscreen triangle and quad helpers now explicitly set the viewport from the dimensions of the destination render target.

Several passes previously inherited or reconstructed their viewport from assumptions about the screen resolution. Once the scene resolution and the destination resolution differ, that can result in only part of a render target being written, or a display-sized pass still using the smaller scene viewport.

The normal/near/far viewport paths were also changed to use the renderer's current target dimensions.

Render target slot 3 is explicitly cleared when changing RTs as well, avoiding stale bindings while moving between the render and display domains.

A few D3D11-invalid combinations also showed up once color and depth resources stopped having identical dimensions.

Those were separated where the depth target wasn't actually required:

  • the 3D scope reticle is composited at display resolution without binding the render-sized scene depth;
  • occlusion queries use the scene depth without unnecessarily binding the display-sized base color target;
  • lens flares render in the display domain without binding the smaller scene depth buffer.

This is renderer cleanup that the resolution split exposed, not something specific to DLSS or FSR.

Upscale-specific fixes

Blur. After upscale, phase_blur writes half/quarter/eighth-resolution display targets. The old vertex shader rebuilt clip space from pixel coordinates times screen_res, which stamped the full scene into the top-left of $user$blur_2 and showed up as a faint glowing mini-view through SSFX bloom. Clip space is now derived from the pass UVs.

Lens flares. rt_flares is produced and consumed after temporal upscaling, so it is now display-sized and no longer binds the render-sized scene depth. The flare shader already does its own depth lookup. This is a domain cleanup, not the bloom-stamp fix above.

Sunshafts. Screen-space sunshafts also run after upscale. Those targets are created from GetDisplayWidth() / GetDisplayHeight(). Depth still uses render_res; color/shaft sampling uses screen_res.

Temporal history

Added an explicit temporal-history reset path shared by TAA, DLSS, and FSR3.

History is reset when rendering is skipped and when the camera movement looks like a discontinuity/cut instead of normal motion.

DLSS receives this through InReset.

FSR3 receives the equivalent reset dispatch parameter.

TAA refreshes its previous-frame resource.

IX-Ray was a sanity check here: its DLSS evaluation path passes a camera-reset flag to NGX and scales motion vectors from the internal render dimensions rather than the display dimensions.

OGSR already had most of the required temporal inputs, but they now have to consistently refer to the physical render domain.

Without the reset, teleports and camera cuts at reduced render resolutions can leave history belonging to an entirely different view.

Texture LOD

The sampler mip bias is adjusted when the physical scene is rendered below native resolution.

The adjustment is based on the current render-to-display scale.

Otherwise texture sampling still chooses mip levels as if rasterization were happening at full display resolution, which adds detail and noise that the lower-resolution scene cannot really represent.

Native-resolution modes keep the existing mip bias unchanged.

3D scopes

The existing separate 3D-scope temporal path is kept.

The main scene uses the selected DLSS/FSR3 quality mode and corresponding physical render dimensions.

The scope path keeps its own render/output dimensions and native-AA handling, so the existing r_3dss_scale_factor remains independent from the main-screen upscaling preset.

This avoids tying scope supersampling/upscaling to the main scene quality setting.

UI

Added DLSS and FSR3 quality controls to the advanced video options, including English and Russian strings/descriptions.

Only the relevant quality row is shown:

  • DLSS selected -> DLSS quality
  • FSR3 selected -> FSR3 quality
  • anything else -> neither

The rows update when the AA mode is changed and when a graphics preset is applied.

The descriptions also mention that applying video settings / vid_restart is required, since these options now change render-target dimensions rather than just an upscaler parameter.

Compatibility / native modes

The native modes remain the equivalent of the previous behavior:

  • DLSS -> DLAA at native resolution
  • FSR3 -> Native AA at native resolution

At a 1.00x render scale there is no render/display size difference, so the new resolution-domain handling should collapse back to the old native-resolution case. Most of the size of this change is finding all the places where the renderer treated the physical scene resolution as the final output resolution.

Known issues

FSR3 on Quality and bellow can show mild vertical banding while standing still, It goes away with camera motion.
does not happens with DLSS, probably due to known FSR3 still-frame reconstruction behavior rather than a render/display size mismatch.

Test plan

  • DLSS DLAA and FSR3 Native AA: no native-resolution regression
  • DLSS/FSR3 Quality and Balanced: full-screen image, no corner stamps
  • Apply video settings / vid_restart after changing quality
  • SSFX bloom, screen-space sunshafts, lens flares, DOF
  • TAA and SMAA still work
  • Camera cut does not keep old temporal history

Screenshots

DLSS DLAA

image

DLSS Quality

image

FSR3 Native

image

FSR3 Quality

image

@brodrigz
brodrigz marked this pull request as ready for review September 3, 2026 03:00
@xrSimpAI

xrSimpAI commented Sep 3, 2026

Copy link
Copy Markdown
Member

Looks good👍 I'll test it and let you know if I encounter any bugs.

@xrSimpAI

xrSimpAI commented Sep 3, 2026

Copy link
Copy Markdown
Member

First minor issue: if you set DLSS to "ultra performance" (or any other than DLAA) and start a new game/load a game, the loading screen will "freeze" and only appear at the end of the loading.

@brodrigz

brodrigz commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

First minor issue: if you set DLSS to "ultra performance" (or any other than DLAA) and start a new game/load a game, the loading screen will "freeze" and only appear at the end of the loading.

fixed by binding the display backbuffer and viewport before drawing the loading screen

@xrSimpAI

xrSimpAI commented Sep 4, 2026

Copy link
Copy Markdown
Member

I found a bug with volumetric smoke (it can be found in the X-18 lab). On DLAA (native resolution), it looks like this (ok)
ss_admin_2026-09-04_12-49-51-(l04u_labx18)

if you lower the render resolution, it looks like this:
ss_admin_2026-09-04_12-50-29-(l04u_labx18)

I think you need to replace screen_res in the fluid* shaders.

@xrSimpAI

xrSimpAI commented Sep 4, 2026

Copy link
Copy Markdown
Member

There's also the issue with the NVG/Thermal Imager.
DLSS (and FSR) "eats" all the interference effects applied by the thermal imager/NVG shaders, so we didn't use DLSS at all if the this effects was working.
Jitter isn't applied and DLSS/FSR/TAA unused:

if (ps_pnv_mode < 2 && (ps_r_pp_aa_mode == DLSS || ps_r_pp_aa_mode == FSR3 || ps_r_pp_aa_mode == TAA || ps_r2_ls_flags.test(R2FLAG_DBG_TAA_JITTER_ENABLE)))
and
if (ps_pnv_mode > 1) // skip AA for heatvision

In your version, dlss is always used:
https://github.com/brodrigz/OGSR-R3-Custom/blob/6ac33223cdc0521c4c1421ebb2aa76e539660754/ogsr_engine/Layers/xrRender/RenderTargetPhaseAA.cpp#L801

We also tried adding upscaling and ran into the same problem. I don't know if it can be solved or worked around. Maybe Claude can come up with something (you use it, right?)

You can look at the thermal imager in the original SoC by using the following console commands:

r_pnv_alfa_vignete 1.
r_pnv_gain_current 2.
r_pnv_gain_offset 1.
r_pnv_glitch 0.
r_pnv_mode 2
r_pnv_noise 0.05
r_pnv_num_tubes 4.
r_pnv_position 100.
r_pnv_radius 0.5
r_pnv_scanlines 0.05
r_pnv_scintillation 0.998
r_pnv_size_vignet 0.1 
r_pnv_washout_thresh 0.1 

heat_fade_distance  13, 20, 0, 0
heat_vision_blurring 1, 1, 1.1, 1.1
heat_vision_steps -0.5, 0, 0, 1

@xrSimpAI

xrSimpAI commented Sep 4, 2026

Copy link
Copy Markdown
Member

However, maybe I'll just throw out these old crutches added for thermal imager.
It will be without interference, but otherwise I didn’t find any problems.

@brodrigz

brodrigz commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

There's also the issue with the NVG/Thermal Imager. DLSS (and FSR) "eats" all the interference effects applied by the thermal imager/NVG shaders, so we didn't use DLSS at all if the this effects was working. Jitter isn't applied and DLSS/FSR/TAA unused:

if (ps_pnv_mode < 2 && (ps_r_pp_aa_mode == DLSS || ps_r_pp_aa_mode == FSR3 || ps_r_pp_aa_mode == TAA || ps_r2_ls_flags.test(R2FLAG_DBG_TAA_JITTER_ENABLE)))

and

if (ps_pnv_mode > 1) // skip AA for heatvision

In your version, dlss is always used: brodrigz/OGSR-R3-Custom@6ac3322/ogsr_engine/Layers/xrRender/RenderTargetPhaseAA.cpp#L801

We also tried adding upscaling and ran into the same problem. I don't know if it can be solved or worked around. Maybe Claude can come up with something (you use it, right?)

You can look at the thermal imager in the original SoC by using the following console commands:

r_pnv_alfa_vignete 1.
r_pnv_gain_current 2.
r_pnv_gain_offset 1.
r_pnv_glitch 0.
r_pnv_mode 2
r_pnv_noise 0.05
r_pnv_num_tubes 4.
r_pnv_position 100.
r_pnv_radius 0.5
r_pnv_scanlines 0.05
r_pnv_scintillation 0.998
r_pnv_size_vignet 0.1 
r_pnv_washout_thresh 0.1 

heat_fade_distance  13, 20, 0, 0
heat_vision_blurring 1, 1, 1.1, 1.1
heat_vision_steps -0.5, 0, 0, 1

Fixed by moving the NV/Thermal effects after the upscaling pass, I did keep thermal's glow/fake color before upscaling as otherwise it would look awfully pixelated.

Here's the results compared to latest release:

3.564 AA Off

ss_l4marr_2026-09-04_20-43-04-(l01_escape) ss_l4marr_2026-09-04_20-46-25-(l01_escape)

This PR DLAA

ss_l4marr_2026-09-04_20-38-10-(l01_escape) DLAA

This PR DLSS Quality

Quality

This PR DLSS Balanced

ss_l4marr_2026-09-04_20-38-25-(l01_escape)

This PR DLSS Ultra Performance

ss_l4marr_2026-09-04_22-43-14-(l01_escape)

@brodrigz

brodrigz commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

I found a bug with volumetric smoke (it can be found in the X-18 lab). On DLAA (native resolution), it looks like this (ok) ss_admin_2026-09-04_12-49-51-(l04u_labx18)

if you lower the render resolution, it looks like this: ss_admin_2026-09-04_12-50-29-(l04u_labx18)

I think you need to replace screen_res in the fluid* shaders.

I think it was fixed by passing GetRenderWidth/Height into RTWidth/RTHeight, but mine don't have that light source in your pic.

Release 3.564

OG

This PR DLSS Balanced

balanced

@xrSimpAI

xrSimpAI commented Sep 5, 2026

Copy link
Copy Markdown
Member

Wow, you fixed all the bugs I noticed. It's simply incredible. Can we chat on Discord? I think with your help we can do a lot of interesting things and fix a lot of bugs in OGSR.

@brodrigz

brodrigz commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

Adding another known issue, screen-space sunshafts can show visibly pixelated occlusion edges around the weapon/hand model when using upscaling below native resolution, likely to do with a sunshaft depth-mask resolution mismatch.

DLSS Ultra performance

image

@xrSimpAI
xrSimpAI merged commit 42e3735 into OGSR:main Sep 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants