ControlMask: per-pixel control of NVIDIA NR
Back to the report · Static analysis · Noisy-domain NR feasibility
Written by Claude Opus 5.5 (Claude Code, high effort) on behalf of GrimsVerk, who skimmed it. The research, the test-build code changes, the measurements and this text are Claude's work.
What the runtime does with it
DLSSNR.ControlMask (+ ControlMaskSubrectBase/Width/Height) is read at every evaluation, in both 310.8 and 310.9.16. Per pixel:
| Channel | Effect |
|---|---|
| r | intensity: the final blend becomes out = orig + sat(Intensity × mask.r) · (NR − orig) |
| g | tone: network input channel 11 = LocalTone × mask.g. The Style colour grade is not scaled by it (the grade uses clamp(LocalTone, 0, 1)) |
| b | structure: AutoMask off → channel 12 = structure × mask.b; AutoMask on → see MaskBlendMode |
| a | unused |
Interaction with NVIDIA's automatic skin/structure mask (AutoMask):
- 310.8: any ControlMask forces AutoMask off.
- 310.9.16:
DLSSNR.MaskBlendMode.- 0: as in 310.8, AutoMask off.
- 1 / 2 / 3: AutoMask stays on, and m = mask.b × structure is combined with NVIDIA's skin (channel 13) and structure (channel 14) values by average / min / max.
With a mask present, the runtime uses the ..._control_mask variants of the fused final kernel (..._full_rect when the subrect covers the whole texture). See the static analysis, section 4.
How the research build makes the mask
The research build is based on GrimsVerk's OptiScaler-NR fork v0.3.8, commit ac313d06; the source is in research-build-source.patch.
- Where: a new mode (14) of the fork's NR compute shader writes an RGBA8 mask at the model's working size every frame, right before NVIDIA's evaluate. The mask is passed with a full-texture subrect; null when the mask is off.
- Inputs:
- t0 = the picture the model gets;
- t2 = the game's depth (guide resolution);
- t3 = the motion vectors.
- Rules, applied in order on top of a base value:
- Far / sky: a soft weight, full at depth ≤ margin and zero at 2 × margin (reversed depth; mirrored for ordinary depth). It is averaged over 5×5 depth texels so silhouettes do not flicker with the per-frame jitter. Blends the base towards the far values.
- Dark: below "dark start" brightness (fully at "dark full"), r and b fade towards the dark values.
- Motion: between "motion start" and "motion full" pixels per frame (at the model's size), r and b fade towards the motion values.
- Cost: +0.13 ms at 4K output with the small model (mask build + NVIDIA's mask kernel variant, measured inside the model timer: 7.26 vs 7.13 ms).
Settings (OptiScaler.ini, section [DlssNr])
| Key | Values | Meaning |
|---|---|---|
ControlMask |
true / false | build and send the mask (D3D12 path; off in spatial-compression mode) |
MaskBlendMode |
0-3 | see above. 2 recommended: NVIDIA's skin handling stays; the mask can only lower structure |
ControlMaskParams |
16 numbers | see below |
ControlMaskParams order, in four groups of four:
| Group | The four numbers |
|---|---|
| 1 | base r, base g, base b, (unused; the depth direction is filled in automatically) |
| 2 | far r, far g, far b, far margin (0 = off; 0.00001 = sky only in reversed-depth games; 0.001 = also distant scenery) |
| 3 | dark start, dark full (brightness 0-1, start > full), dark r, dark b |
| 4 | motion start, motion full (pixels per frame at the model's size, full > start), motion r, motion b |
The default 1,1,1,0, 1,1,1,0, 0,0,1,1, 0,0,1,1 changes nothing. The same values are available as live test-control keys (ControlMask, MaskBlendMode, MaskParam0…MaskParam15) when HudlessTestControl = true. When the mask is created, OptiScaler.log shows a line DLSS-NR control mask WxH: base …, far … margin …, dark …, motion …, MaskBlendMode ….
Example, keep the game's own sky:
ControlMask = true
MaskBlendMode = 2
ControlMaskParams = 1,1,1,0, 0,1,1,0.00001, 0,0,1,1, 0,0,1,1
Example, less NR and structure in the darks: ControlMaskParams = 1,1,1,0, 1,1,1,0, 0.20,0.04,0.4,0.5, 0,0,1,1
Proof (The Witcher 3, frozen photo-mode frames)
Each value is the mean |difference|, in 8-bit units, between the two captures named in the first column. The "noise floor" row is the same setting captured twice.
| Comparison | Courtyard 310.9.16 | Yennefer 310.9.16 | Courtyard 310.8 | Yennefer 310.8 | Meaning |
|---|---|---|---|---|---|
| def_end vs def | 2.03 | 1.41 | 1.55 | 1.37 | noise floor (same setting twice) |
| m1_b0 vs def | 2.04 | 1.49 | 1.62 | 1.50 | all-ones mask, MaskBlendMode 0: no visible change |
| m1_b2 vs def | 2.03 | 1.41 | - | - | all-ones mask, MaskBlendMode 2 (min): no change |
| mr05 vs int05 | 1.57 | 1.36 | 1.47 | 1.41 | red = 0.5 is the same as Intensity 0.5 |
| mr0 vs def | 7.63 | 7.61 | - | - | red = 0: NR fully off where the mask says so |
| mb0_b0 vs str0 | 1.44 | 1.42 | - | - | blue = 0 (blend 0) is the same as Local structure 0 |
| mb0_b1 vs def | 3.04 | 2.13 | - | - | blue = 0, blend 1 (average with NVIDIA's skin/structure mask): half the effect |
| mb0_b3 vs def | 2.03 | 1.41 | - | - | blue = 0, blend 3 (max): no change, NVIDIA's own value wins |
| mg0 vs def | 4.89 | 3.48 | 5.84 | 4.65 | green = 0: tone input off; the style grade stays (unlike Local tone 0) |
| mg05 vs tone05 | 2.37 | 2.87 | - | - | green = 0.5 against Local tone 0.5: close, not identical (grade) |
| mdark vs def | 2.50 | 2.17 | - | - | dark rule (less intensity/structure in the darks) |
| tone05 vs def | 3.65 | 3.25 | - | - | for reference: Local tone 0.5 (no mask) against 1 |
Sky rule (terrace frame with a large sky):
- Edit maps: NR's edit in the sky area drops to nothing; a margin of 0.001 also removes it from everything beyond the terrace wall.
- Flicker: the first hard-edged version raised frame-to-frame flicker from 0.22 to 0.28. The soft 5×5 version brings it to 0.212 against 0.204 without a mask.
- WorkingScale 0.5 (from the INI only): the edit in the top third of the frame dropped from 12.3 to 4.8; the rest is mountains and a tree.
Pictures are in the ControlMask section of the report.
Caveats
- The motion rule is untested, because photo mode has no motion.
- The mask is 8-bit, and it multiplies the global Intensity, Tone and Structure settings rather than replacing them.
- 310.8 has no MaskBlendMode, so any mask there switches AutoMask off; for an all-ones mask, the measured effect is at the noise floor.