Repository navigation
[mod:dragonage_veilguard] Replace final output pass for HDR10 tone mapping - #716
Pedro-A-D-S wants to merge 14 commits into
Conversation
|
The So the builds can be checked without waiting, I ran the same builds locally on
The follow-up commit only touches the mod README, which the build workflows ignore ( 🤖 Generated with Claude Code |
State that the default is RenoDRT tone mapped to the display peak, that Preset Off restores Vanilla, and that midtones and shadows match the original with Gamma Correction Off. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…nder scale With the render scale under 100% the game draws the final output through 0x3998EA61 instead of 0x4564BB42: a 4x4 Lanczos resample of a packed r10g10b10a2 scene, then the same UI composite, display mapping LUT, BT.2020 PQ and dither. Move the shared tail into common.hlsli and add a replacement for the new variant, so the Tone Mapper applies at 66% (DLSS Ultra Performance). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…Generation With DLSS Frame Generation on the game draws the final output through 0x22E9F0A5: the same Lanczos pass as 0x3998EA61 with two render targets, the final image with the UI and the same image without it (the HUD-less colour buffer frame generation reads). Without a replacement the Tone Mapper had no effect at all with frame generation on. Replace it writing both outputs through the same tone mapping. FinalPass() takes with_ui to skip the UI composite, and the Lanczos scene read moves to lanczos.hlsli so both variants share it. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
ACES gives more contrast than the original (deeper shadows, stronger highlights); RenoDRT stays available for the original look. Peak Brightness gets the same reset button as the other settings. The README documents the three final-pass variants and the measured effect of the ACES default. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ith DLSS Frame Generation With DLSS Frame Generation on and the render scale at 100% the game draws the final output through 0x23DC66B2: the same scene read as 0x4564BB42 with two render targets, the final image with the UI and the same image without it. Replace it through FinalPassWithHudLess(), shared with 0x22E9F0A5, so both frame generation variants go through the same tone mapping. The README lists the four HDR10 variants. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…d in game Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…mple method Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…outs Switching the Upsample Method in the game's settings made it create compute root signatures with descriptor tables of three or more ranges, and injecting the settings buffer into them hung the GPU (device removed, reproduced with and without the DevKit, frame generation on and off, not without the mod). The final pass root signatures only have tables of up to two ranges, so skip the others in on_create_pipeline_layout. The Tone Mapper still applies, and switching between DLSS, XeSS, None and FSR works. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…eplay Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…bove 100% Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On a Solas cutscene frame ACES turns 1.76% of the pixels near white against 0.01% for RenoDRT, which keeps the hue of saturated glows and the original midtones. ACES stays selectable. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
With DLSS Frame Generation on and Render Scale 70% or 85% the game uses a final pass that reads the packed half-float scene with the bilinear sampler and no Lanczos. It was not replaced, so the Tone Mapper did nothing there. UnpackScene() is shared with the Lanczos variants. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
🔵 Needs a closer look
A brand-new mod with five hand-decompiled shader variants, a GPU device-removal workaround via pipeline-layout filtering, and several explicitly unverified scenarios warrants human validation beyond the one minor Preset Off reset gap.
1 open issue
What changed in this PR
New RenoDX mod for Dragon Age: The Veilguard (Frostbite, D3D12). The game natively outputs HDR10 (BT.2020 PQ into an r10g10b10a2_unorm swapchain), so the mod replaces only the final output pass — it performs no swapchain or resource upgrade. For non-Vanilla tone mappers it tone-maps the graded, pre-display-mapping HDR scene directly to the display peak with independent UI brightness, covering all five HDR10 variants of the final pass (direct read, Lanczos-resampled, and Frame-Generation two-target forms). This is a sound approach that preserves the vanilla path and does not inverse-tonemap a completed SDR frame.
Changes:
- Adds the mod scaffold:
addon.cpp,shared.h,metadata.json, andREADME.mdwith tone-mapping/color-grade settings injected atb13space 0, plus a Preset Off path back to Vanilla. - Adds five decompiled final-pass shaders sharing
common.hlsli(scene+UI → display mapping → BT.2020 PQ) andlanczos.hlsli(4×4 Lanczos resample); non-Vanilla paths replace display mapping withToneMapPass. - Configures injection (
expected_constant_buffer_index = 13, space 0,allow_multiple_push_constants,force_pipeline_cloning) and anon_create_pipeline_layoutfilter that skips upscaler compute root signatures with descriptor tables of >2 ranges to avoid a GPU device-removed hang.
RenoDX review checklist
- PR scope/commits tidy: Pass — changes are confined to the new game folder.
- Review comments helpful: Pass.
- Defaults preserve vanilla look: Pass — default RenoDRT documented within ~5% of Vanilla; Preset Off → Vanilla.
- Preset Off / Vanilla-0 behavior clear: Needs comment —
OnPresetOff()leaves four settings unreset (see comment). - No final-frame inverse tonemapping: Pass — tonemaps the pre-display-mapping HDR scene, not an SDR backbuffer.
- Tonemap signals proven: Pass — single-arg
ToneMapPasson a genuinely graded HDR scene (no separate SDR grade). - Linear input to tonemappers: Pass — scene is linear HDR before
ToneMapPass. - Vanilla LUT path domain/sampling: Pass — vanilla display-mapping LUT preserved verbatim in the Vanilla branch.
- Hard clips/saturate don't destroy HDR: Pass — only
max(0,...)/vanilla packing on reads. SwapChainPassnot hiding tonemap: N/A — not used.- Output prefers HDR10/PQ: Pass — native HDR10 PQ output retained.
| File | Description |
|---|---|
src/games/dragonage_veilguard/shared.h |
Injection cbuffer layout (b13) and RenoDX tone-map/grade defines. |
src/games/dragonage_veilguard/common.hlsli |
Shared final-pass body: Vanilla path, tonemap path, PQ encode + dither, two-target helper. |
src/games/dragonage_veilguard/lanczos.hlsli |
Shared 4×4 Lanczos scene resample for below-100% render scale. |
src/games/dragonage_veilguard/final_0x4564BB42.ps_5_0.hlsl |
100% direct-read final pass. |
src/games/dragonage_veilguard/final_0x3998EA61.ps_5_0.hlsl |
Below-100% Lanczos final pass. |
src/games/dragonage_veilguard/final_0x23DC66B2.ps_5_0.hlsl |
100% Frame-Generation two-target pass. |
src/games/dragonage_veilguard/final_0x22E9F0A5.ps_5_0.hlsl |
Below-100% Frame-Generation two-target Lanczos pass. |
src/games/dragonage_veilguard/final_0xE779DCD3.ps_5_0.hlsl |
Bilinear packed-read two-target pass. |
src/games/dragonage_veilguard/addon.cpp |
Settings, injection config, layout filter, and Preset Off handling. |
src/games/dragonage_veilguard/metadata.json |
Mod metadata (Steam 1845910, d3d12, testing). |
src/games/dragonage_veilguard/README.md |
Documentation of passes, behavior, and manual verification. |
Review effort: Balanced
Added new tone mapping settings for improved visuals. Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

What
New mod for Dragon Age: The Veilguard (Frostbite, D3D12, Steam
1845910). With in-game HDR on, the game already writes BT.2020 PQ into anr10g10b10a2_unormswapchain, so this mod replaces only the final output pass and does not upgrade the swapchain or any resource. The game draws that pass through one of five HDR10 shaders depending on the render scale, the window mode and DLSS Frame Generation, and the mod covers all five:0x4564BB42r11g11b10_float, read directly0x3998EA61r10g10b10a2with packed half-float bits, 4x4 Lanczos resample0x23DC66B2r11g11b10_float, read directly, with the same two render targets as the next row0x22E9F0A50xE779DCD3r10g10b10a2like0x22E9F0A5, read with the bilinear sampler, no Lanczos texture; two render targets0x23DC66B2and0x22E9F0A5write both outputs through the same tone mapping (FinalPassWithHudLess()), so the buffer frame generation interpolates matches the displayed image.FinalPass()takeswith_uifor that, and the Lanczos read is shared inlanczos.hlsli.b13, space 0, matching the SM5.0 shaders.addon.cppsetsexpected_constant_buffer_index = 13,expected_constant_buffer_space = 0,allow_multiple_push_constantsandforce_pipeline_cloning(same combination as Dead Space).on_create_pipeline_layoutskips root signatures with a descriptor table of more than two ranges. The game creates those for the upscaler's compute passes when the Upsample Method changes; injectingb13into them ended in a GPU device-removed error. The final-pass root signatures only have tables of one or two ranges.Why
The default injection index put the settings buffer at the first free root slot of each root signature, so the replacement shader read zeros from
b13and stayed on the Vanilla branch. Fixing the index made every setting reach the shader.Below 100% render scale the game uses
0x3998EA61, and with DLSS Frame Generation on it uses0x23DC66B2(100%) or0x22E9F0A5(below 100%). Without replacements the Tone Mapper had no effect there (with Frame Generation on, switching Vanilla, None, ACES and RenoDRT changed nothing on screen).Switching the Upsample Method in the game's settings with the mod loaded hung the GPU (3 of 3 tries, with and without the DevKit, Frame Generation on and off; never without the mod). A log of the layouts created at the switch showed only compute root signatures with tables of 3 to 5 ranges.
A fifth pass,
0xE779DCD3, showed up in windowed 1920x1080 tests at 70%, 85% and 92% with Frame Generation on; it was not replaced, so the Tone Mapper did nothing there. It is the0x23DC66B2body with the scene read from a packedr10g10b10a2texture through the bilinear sampler (UnpackScene()is shared with the Lanczos variants).How it was verified
Build:
cmake --build --preset clang-x64-release --target dragonage_veilguard(the DevKit addon built with the same preset; a Debug/Release mix of the two crashes at load).git diff --checkandclang-format --dry-runare clean.In game (HDR on, Minrathous streets, RT off, DLSS Ultra Performance 66%, DevKit also loaded), ReShade HDR screenshots measured in nits:
ReShade.logshowscbvIndex:13in the injected layouts. Vanilla, None, ACES and RenoDRT produce different frames, with Frame Generation off and on.0x23DC66B2): one menu screenshot with ACES, Peak 1000 has a peak of 963 nits.0x22E9F0A5replacement: peak about 5000 nits (Vanilla). After it, with Peak 1000: ACES 949 to 986 nits, RenoDRT 859 to 873 nits.ReShade.logregisters onlyRenoDX, ACES peak 904 to 968 nits.0xE779DCD3, windowed 1920x1080, Frame Generation on, Render Scale 92%: DevKit reports the add-on shader, and the output peak is 0.725 with RenoDRT against 0.892 with Vanilla (PQ code values).0x23DC66B2, and 94% and 71% (Performance and Ultra Performance) use0x22E9F0A5; both are replaced.Not verified yet
0xA0D84CD4,0xAA75CF5D,0xD45D81E7) were not drawn in any case tested.metadata.jsonstatus istesting.🤖 Generated with Claude Code