Fork touchpoints
July 30, 2026 · View on GitHub
This index lists every upstream file the fork touches. It is the authoritative inventory of fork-vs-upstream surface area, maintained as fork edits are added or removed.
See docs/CONTRIBUTING.md for the fork-touchpoint
discipline this index supports.
Conventions
Fork-owned file naming
- Fork-owned files use the
rtx_fork_*prefix with a subsystem suffix (e.g.rtx_fork_api_entry.cpp,rtx_fork_atmosphere.cpp,rtx_fork_overlay.cpp,rtx_fork_light.cpp). Single prefix keeps the convention simple andgrep-friendly. - All fork-owned files live under
src/dxvk/rtx_render/(or the subsystem-appropriate equivalent directory). - Hook functions are declared in the
fork_hooks::namespace (src/dxvk/rtx_render/rtx_fork_hooks.h) and implemented in their respective fork-owned.cppfiles.
Fridge-list invariant
Every edit to an upstream file must have a fridge-list entry in the same commit. The PR-template bullet reminds contributors; a future CI check will enforce it if discipline slips.
Entry types
- Hook — upstream file contains a one-line call into fork-owned code. The fork logic itself lives in the fork-owned file referenced by the entry.
- Inline tweak — upstream file contains a small fork-introduced change (typically <= 20 LOC) that was not worth lifting into its own fork file.
Upstream files touched by the fork
public/include/remix/remix.h
Pre-refactor fork footprint: +101 / -9 LOC (audit 2026-04-18)
Category: migrate
-
Block at
Interfaceclass (method declarations) — ~13 LOC, planned targetN/A (public header)inN/A (public header). Declares fork-added C++ wrapper methods:CreateMeshBatched,GetUIState,SetUIState,AddTextureHash,RemoveTextureHash,dxvk_GetTextureHash,CreateLightBatched,UpdateLightDefinition,SetGameValue. -
Block at
Interface::CreateMeshBatched(inline definition) — ~9 LOC, planned targetN/A (public header)inN/A (public header). Inline C++ wrapper that callsm_CInterface.CreateMeshBatchedfor the batched mesh submission API slot. -
Block at
Interface::GetUIState/Interface::SetUIState(inline definitions) — ~16 LOC, planned targetN/A (public header)inN/A (public header). Inline C++ wrappers for UI state query/set API, guarding on nullptr slot before dispatching. -
Block at
Interface::AddTextureHash/Interface::RemoveTextureHash(inline definitions) — ~16 LOC, planned targetN/A (public header)inN/A (public header). Inline C++ wrappers for texture-hash category mutation API. -
Block at
Interface::dxvk_GetTextureHash(inline definition) — ~13 LOC, planned targetN/A (public header)inN/A (public header). Inline C++ wrapper that retrieves the DXVK image hash for a D3D9 texture via the fork's dxvk-specific extension slot. -
Block at
Interface::CreateLightBatched/Interface::UpdateLightDefinition(inline definitions) — ~22 LOC, planned targetN/A (public header)inN/A (public header). Inline C++ wrappers for batched light creation and deferred light-definition update. -
Block at
UIStateenum (file scope) — ~5 LOC, planned targetN/A (public header)inN/A (public header). AddsUIStateC++ enum mirroringremixapi_UIStatefor the C++ API surface. -
Block at
Interface::SetGameValue(declaration + inline definition) — ~8 LOC, planned targetN/A (public header)inN/A (public header). C++ wrapper for theremixapi_SetGameValueC API slot introduced in workstream 10 (plugin-injected game-state write). Wrapper guards on nullptr vtable slot before dispatching, matching theSetConfigVariableshape. Companion readers are graph componentsGameValueReadBool/GameValueReadNumber; backing store lives inrtx_fork_game_state.h. -
Block at
remixapi_Interfacestatic_assert updates (file scope) — ~3 LOC (three separate assert sizes), planned targetN/A (public header)inN/A (public header). Updatessizeof(remixapi_Interface)static_asserts in the C++ header to match each successive vtable extension (208 → 240 → 272 → 280 → 288).
public/include/remix/remix_c.h
Pre-refactor fork footprint: +154 / -29 LOC (audit 2026-04-18)
Category: migrate
-
Block at
REMIXAPI_VERSION_MAJOR/MINOR/PATCH(file scope) — ~3 LOC, planned targetN/A (public header)inN/A (public header). Sets the Remix Plus ABI version to0.1000.0(reserved MINOR1000), distinct from stock NVIDIA0.6.x. BecauseisVersionCompatibletreats each minor as breaking while MAJOR==0, this makes the runtime reject binaries built against stock Remix0.6.xor older Remix Plus0.6.x— whoseremixapi_Interfacelayout and category-bit ABI differ. Bump MINOR on any further breaking ABI change. -
Block at
remixapi_StructTypeenum (file scope) — ~3 LOC, planned targetN/A (public header)inN/A (public header). AddsREMIXAPI_STRUCT_TYPE_TEXTURE_INFO,INSTANCE_INFO_PARTICLE_SYSTEM_EXT, andINSTANCE_INFO_GPU_INSTANCING_EXTenumerators. -
Block at
remixapi_TextureHandletypedef (file scope) — ~1 LOC, planned targetN/A (public header)inN/A (public header). Declares the opaqueremixapi_TextureHandle_T*handle type for the texture upload API. -
Block at
REMIXAPI_INSTANCE_CATEGORY_BIT_*enum (file scope) — ~16 LOC, planned targetN/A (public header)inN/A (public header). Bit values match upstream NVIDIA exactly (reverted 2026-06-27 from an earlier fork build that shiftedIGNORE_ALPHA_CHANNELto bit 8 to mirror the internalInstanceCategoriesorder). The C↔internal mapping intoRtCategories()is by-name, so the public bit values are free to match upstream and now do. No remaining fork delta — the enum now matches upstream exactly. (The misleadingly-namedLEGACY_EMISSIVEalias of bit 24 /SMOOTH_NORMALSwas removed 2026-06-28: its name implied emissive behavior but it routed toSmoothNormals, so callers got a silent wrong-category result; removing it converts that into a compile error.) -
Block at
IDirect3DTexture9forward declaration (file scope) — ~1 LOC, planned targetN/A (public header)inN/A (public header). Forward-declaresIDirect3DTexture9so the dxvk-extension function signatures compile without pulling in d3d9 headers. -
Block at
remixapi_InstanceInfo.isDynamicfield (struct) — ~1 LOC, planned targetN/A (public header)inN/A (public header). AddsisDynamicbool field toremixapi_InstanceInfoto control temporal accumulation behavior. -
Block at
remixapi_InstanceInfo.ignoreViewModelfield (struct) — ~1 LOC, planned targetN/A (public header)inN/A (public header). AddsignoreViewModelbool field toremixapi_LightInfoso API-submitted lights can opt out of view-model geometry lighting. -
Block at
PFN_remixapi_AddTextureHash/PFN_remixapi_RemoveTextureHashtypedefs (file scope) — ~8 LOC, planned targetN/A (public header)inN/A (public header). Declares function-pointer types for texture-hash category mutation (add/remove a texture hash from a named option set). -
Block at
remixapi_Formatenum +remixapi_TextureInfostruct (file scope) — ~28 LOC, planned targetN/A (public header)inN/A (public header). Declares the texture upload type system: format enum mapping to VkFormat values, and theremixapi_TextureInfostruct carrying pixel data forCreateTexture. -
Block at
PFN_remixapi_CreateTexture/PFN_remixapi_DestroyTexturetypedefs (file scope) — ~6 LOC, planned targetN/A (public header)inN/A (public header). Declares function-pointer types for the texture upload/destroy lifecycle. -
Block at
PFN_remixapi_dxvk_GetTextureHashtypedef (file scope) — ~4 LOC, planned targetN/A (public header)inN/A (public header). Declares the dxvk-specific extension function to retrieve the GPU image hash from a D3D9 texture object. -
Block at
PFN_remixapi_CreateMeshBatchedtypedef (file scope) — ~7 LOC, planned targetN/A (public header)inN/A (public header). Declares the batched mesh-creation function-pointer type and stub comment noting slot is currently nullptr. -
Block at
remixapi_UIStateenum +remixapi_GetUIState/remixapi_SetUIStatedeclarations (file scope) — ~16 LOC, planned targetN/A (public header)inN/A (public header). Declares the UI state enum and the two API entry points for reading/setting the ImGui overlay visibility level. -
Block at
PFN_remixapi_DrawScreenOverlaytypedef (file scope) — ~9 LOC, planned targetN/A (public header)inN/A (public header). Declares the function-pointer type for compositing a plugin-supplied pixel buffer over the final frame. -
Block at
PFN_remixapi_BridgeCallback+PFN_remixapi_RegisterCallbacks+PFN_remixapi_AutoInstancePersistentLights+PFN_remixapi_UpdateLightDefinition(file scope) — ~22 LOC, planned targetN/A (public header)inN/A (public header). Declares frame-boundary callback registration, the persistent-light auto-instance helper, and the deferred light-definition update function. -
Block at
PFN_remixapi_CreateLightBatchedtypedef (file scope) — ~4 LOC, planned targetN/A (public header)inN/A (public header). Declares the batched light-creation function-pointer type. -
Block at
PFN_remixapi_dxvk_GetSharedD3D11TextureHandletypedef (file scope) — ~5 LOC, planned targetN/A (public header)inN/A (public header). Declares the dxvk-specific extension function for the DX11 shared-texture export path (stub in this fork; slot populated for ABI layout compatibility). -
Block at
PFN_remixapi_SetGameValuetypedef (file scope) — ~14 LOC (including the contract doc block), planned targetN/A (public header)inN/A (public header). Declares the function-pointer type for the plugin-injected game-state write API introduced in workstream 10. The entrypoint stores a single string/string pair under a caller-chosen key in a fork-owned thread-safe map; graph componentsGameValueReadBool/GameValueReadNumberread those values by name. The contract doc block above the typedef describes key/value semantics, validation, and lifetime (store survivesShutdown/ re-init). -
Block at
remixapi_Interfacevtable additions (struct fields) — ~15 LOC spread across the vtable struct, planned targetN/A (public header)inN/A (public header). Appends new function-pointer slots toremixapi_Interface:AddTextureHash,RemoveTextureHash,CreateTexture,DestroyTexture,dxvk_GetTextureHash,CreateMeshBatched,GetUIState/SetUIState,DrawScreenOverlay,RegisterCallbacks,AutoInstancePersistentLights,UpdateLightDefinition,CreateLightBatched,dxvk_GetSharedD3D11TextureHandle,SetGameValue. 2026-06-27: the upstreamSetCameraMediumMaterialslot was moved out of the middle of the struct (it had been inherited betweenSetupCameraandDrawInstance) to immediately afterPresent, mirroring upstream's canonical tail layout (upstream2bac8874); fork slots remain appended after it. Size-neutral move — thesizeofsentinel is unchanged. An append-at-end warning comment was restored above the struct.
src/d3d9/d3d9_device.cpp
Pre-refactor fork footprint: +5 / -5 LOC (audit 2026-04-18)
Category: index-only
- Inline tweak at
D3D9DeviceEx::PresentEx(~line 3748) — 4-line addition for [RTX-Diag] entry log on PresentEx. Logs hwnd override and swapchain pointer at the top ofPresentExto correlate with the Remix API present chain during diagnostics.
src/d3d9/d3d9_rtx.cpp
Pre-refactor fork footprint: +11 / -11 LOC (audit 2026-04-18)
Category: index-only
- Inline tweak at
D3D9Rtx::EndFrame(~line 1216) — 5-line addition for [RTX-Diag] entry log on EndFrame. Logs targetImage pointer and callInjectRtx flag at the top of EndFrame, plus a second log after the CS lambda is emitted, to trace the frame-end dispatch chain.
src/d3d9/d3d9_swapchain.cpp
Pre-refactor fork footprint: +14 / -4 LOC (audit 2026-04-18)
Category: index-only
-
Inline tweak at
D3D9SwapChainEx::Present(~line 441) — 4-line addition for [RTX-Diag] entry log. LogshDestWindowOverridepointer at the top ofD3D9SwapChainEx::Presentto trace the native present path during diagnostics. -
Inline tweak at
D3D9SwapChainEx::Present(after remix API call site) — 6-line addition forremixapi_AutoInstancePersistentLightsflush. Callsremixapi_AutoInstancePersistentLights()once per frame on the native D3D9 present path so persistent lights submitted via the Remix C API are auto-instanced even when the caller bypassesremixapi_Present. The callee early-outs cheaply for native-only consumers (see its body entry below), so this call is effectively free unless the C-API light path is in use.
src/d3d9/d3d9_swapchain_external.cpp
Pre-refactor fork footprint: +5 / -5 LOC (audit 2026-04-18)
Category: index-only
- Inline tweak at
D3D9SwapchainExternal::Present(~line 44) — 4-line addition for [RTX-Diag] entry log. Logs VkImage pointer and framebuffer extent at the top ofD3D9SwapchainExternal::Presentto trace the external swapchain present path during diagnostics.
src/dxvk/dxvk_limits.h
Category: index-only
- Inline tweak at the
MaxPushConstantSizeenum value (~line 21) — 1-LOC value change + multi-line rationale comment. BumpsMaxPushConstantSizefrom 128 to 256. The fork-side tonemap apply args struct (ToneMappingApplyToneMappingArgsinsrc/dxvk/shaders/rtx/pass/tonemap/tonemapping.h) grew to 144 bytes across Workstream 2 commits 3–4 (Hable + AgX operator params). With the old cap,DxvkContext::pushConstants()silently overflowed the per-bank storage by 16 bytes on every Global / Direct tonemap dispatch, corrupting adjacent bank state and crashing the NVIDIA driver later. Every RTX-class GPU reportsmaxPushConstantsSize >= 256; pipeline layouts derive per-shader push-constant ranges from shader reflection, so the larger cap is harmless to existing shaders.
src/dxvk/imgui/dxvk_imgui.cpp
Pre-refactor fork footprint: +236 / -71 LOC (audit 2026-04-18)
Post-refactor footprint: 3 hook call sites + #include "rtx_render/rtx_fork_hooks.h" (migrated 2026-04-18)
Category: migrate
-
Block at
ImGUI::wndProcHandler(top of function + dispatch points) — ~30 LOC, planned targetfork_hooks::wndProcHandlerDiaginrtx_fork_overlay.cpp. REVERTED before migration (commit 664a9ba4). Not present in current file. No hook created. -
Block at
ImGUI::processHotkeys(top) — ~10 LOC, planned targetfork_hooks::processHotkeysDiaginrtx_fork_overlay.cpp. REVERTED before migration (commit 664a9ba4). Not present in current file. No hook created. -
Block at
ImGUI::checkHotkeyState(alt-chord logging branch) — ~22 LOC, planned targetfork_hooks::checkHotkeyStateDiaginrtx_fork_overlay.cpp. REVERTED before migration (commit 664a9ba4). Not present in current file. No hook created. -
Block at
ImGUI::wndProcHandler(context pin at entry) — ~2 LOC. Migrated tofork_hooks::imguiContextPininrtx_fork_overlay.cpp. Pins ImGui and ImPlot contexts at the top ofwndProcHandlerto prevent context corruption when plugin activity drifts GImGui off the dev menu's context between frames. Call site passesm_contextandm_plotContextdirectly — no friend declaration needed. -
Block at
ImGUI::showRenderingSettings(sky mode UI section) — ~154 LOC. Migrated tofork_hooks::showAtmosphereUIinrtx_fork_atmosphere.cpp. Adds the Sky Mode combo (Skybox Rasterization / Numos), atmosphere preset buttons (Earth, Mars, Clear Sky, Polluted/Hazy, Alien World, Desert Planet), and the full atmosphere parameter tree (sun, density sliders, advanced coefficients) under the Sky Tuning collapsing header. TheskyModeCombostatic was moved fromdxvk_imgui.cppinto the fork-owned atmosphere file. No friend declaration needed. -
Block at
ImGUI::showMainMenu(wrapper tab handling) — ~6 LOC. Partially migrated tofork_hooks::wrapperTabDrawinrtx_fork_overlay.cpp. ThekTab_Wrapperguard (remixapi_imgui_HasDrawCallback()check +continue) remains as an inline tweak in the tab loop (structural control flow, not extractable). The case body (remixapi_imgui_InvokeDrawCallback()) is wrapped asfork_hooks::wrapperTabDraw(). No friend declaration needed. -
Hook at tonemapper ImGui settings →
fork_hooks::showTonemapOperatorUI/fork_hooks::showLocalTonemapOperatorUIinrtx_fork_tonemap.cpp. Also remove the standaloneRemixGui::Checkbox("Use Legacy ACES", ...)at ~line 3888 — its RtxOptionrtx.useLegacyACESis being deleted. Operator combo + per-operator sliders replace the old ACES checkbox. "Use Legacy ACES" reachable via TonemapOperator::ACESLegacy enum value. -
Inline tweak at
ImGUI::showRenderingSettings"Tonemapping" header — removed theTonemapping Modecombo (Global / Local / Direct) and the standalone "User Brightness" / "User Brightness EV Range" sliders. The header body is now a single always-visiblemetaToneMapping().showImguiSettings()call between two separators. Tuning Mode (tone curve sliders) is also removed from the panel. 2026-05-13 tonemap refactor: mode selector removed; operator dropdown is now the primary control. 2026-05-15: local tonemap path removed entirely, so no per-path UI gate remains.
src/dxvk/imgui/dxvk_imgui_about.cpp
Category: index-only
Rationale: Fork additions are string-literal entries in the in-game About panel's "GitHub Contributors" list — they live inside a curly-braced initializer list and can't be lifted into a separate TU.
- Inline tweak at
ImGuiAbout::Credits::Creditsconstructor (GitHub Contributors string list, ~lines 91-107) — one string literal per fork contributor, sorted alphabetically per the inline comment. Per CONTRIBUTING.md, contributors add their own entry when their PR adds something visible. Tracked here per the fridge-list invariant. Each entry is in the format"FirstName 'Handle' LastName"or handle-only ("BrunchyChineapple","Dayton 'watbulb'"). The list is the canonical record of community contributors visible in the About panel.
src/dxvk/imgui/dxvk_imgui.h
Pre-refactor fork footprint: +2 / -1 LOC (audit 2026-04-18)
Category: index-only
- Inline tweak at
tabNamesarray /kTab_Wrapperenum (inImGUIclass) (~line 112) — 2-line addition for wrapper tab constant and name. AddskTab_Wrapperenumerator and "Plugin" entry totabNames[]to expose the plugin-drawn ImGui tab in the Remix dev menu.
src/dxvk/imgui/imgui_impl_win32.cpp
Pre-refactor fork footprint: +1 / -1 LOC (audit 2026-04-18)
Category: index-only
- Inline tweak at
ImGui_ImplWin32_UpdateMouseCursor(~line 236) — 1-line modification toGetAsyncKeyStatecall. Changes key-state sampling to useGetAsyncKeyState(async hardware state) instead of the previous synchronous variant to fix key-state detection on the overlay's WndProc path.
src/dxvk/dxvk_limits.h
Category: inline-tweak
- Inline tweak at
MaxPushConstantSizeconstant — increased from128to256.ToneMappingApplyToneMappingArgsgrew past the original 128-byte limit once per-operator parameter blocks were added (current size 176 B — seetonemapping.hstatic_assert). Changing to 256 keeps the constant larger than any current push-constant struct and stays within the 256-byte push-constant size supported by all target GPUs.
src/dxvk/meson.build
Pre-refactor fork footprint: +4 / -0 LOC (audit 2026-04-18)
Category: index-only
-
Inline tweak at
dxvk_srcfiles list (~line 187) — 2-line addition registering atmosphere sources. Registersrtx_render/rtx_atmosphere.cppandrtx_render/rtx_atmosphere.hin the DXVK build. -
Inline tweak at
dxvk_srcfiles list (~line 400) — 2-line addition registering ImGui export sources. Registersimgui/imgui_remix_exports.cppandimgui/imgui_remix_exports.hin the DXVK build. -
Inline tweak — register
src/dxvk/rtx_render/rtx_fork_tonemap.cppin the rtx_render source list. The fork-owned tonemap operator headers (aces.slangh,adaptation_v1.slangh,agx.slangh,fork_tonemap_operators.slangh,gt7.slangh,hable.slangh,lottes.slangh,neutwo.slangh,psycho17.slangh) live undersrc/dxvk/shaders/rtx/pass/tonemap/and are picked up via the shader-include glob; no explicit meson.build entry is required for those. Fork-owned tonemap module. -
Inline tweak at
dxvk_srcfiles list (rtx_render block) — 2-line addition registering weather sources. Registers'rtx_render/rtx_fork_weather.cpp'and'rtx_render/rtx_fork_weather.h'in the DXVK build source list.
src/dxvk/rtx_render/graph/rtx_component_list.h
Category: index-only
- Inline tweak at
components/include list (~line 56) — 2-line addition. Not migrated: the include manifest is the intended extension point for new components, and adding two alphabetically-placed#includelines is the canonical way to register fork-owned graph components. Registerscomponents/game_value_read_bool.handcomponents/game_value_read_number.hin the component manifest. Both are fork-owned Sense components introduced in workstream 10 (plugin-injected game-state readers); their backing store is the fork-ownedrtx_fork_game_state.h.
src/dxvk/rtx_render/rtx_camera_manager.cpp
Pre-refactor fork footprint: +10 / -10 LOC (audit 2026-04-18)
Category: index-only
- Inline tweak at
CameraManager::processExternalCamera(~line 179) — 6-line addition for [RTX-Diag] entry log. Logs the old and newframeLastTouchedvalues when an external camera is processed, to diagnose camera-validity skip conditions.
src/dxvk/rtx_render/rtx_context.cpp
Pre-refactor fork footprint: +211 / -26 LOC (audit 2026-04-18)
Post-refactor footprint: 5 hook call sites + #include "rtx_fork_hooks.h" (migrated 2026-04-18)
Note on Block 4: The audit listed this as "physical atmosphere sky skip + diag logs" in injectRTX. The [RTX-Diag] portion was reverted before migration (commits ff61f77d and b0d2da33); what remains is only the 5-line early-return guard in rasterizeSky. Migrated as fork_hooks::injectRtxAtmosphereSkySkip. The audit's method name (injectRTX) was incorrect — the actual call site is rasterizeSky.
Note on Block 6: The endFrame [RTX-Diag] log block was reverted (commit b0d2da33) before this migration ran. endFrameDiag was not implemented; no hook call site exists in endFrame.
-
Hook at
RtxContext::RtxContextconstructor (atmosphere init) →fork_hooks::initAtmosphereinrtx_fork_atmosphere.cppConstructs theRtxAtmosphereinstance duringRtxContextinitialization. -
Hook at
RtxContext::updateRaytraceArgsConstantBuffer(sky mode + atmosphere section) →fork_hooks::updateAtmosphereConstantsinrtx_fork_atmosphere.cppSetsconstants.skyMode, detects sky mode transitions (clearing skybox buffers on switch to Numos), and callsm_atmosphere->initialize/computeLuts/getAtmosphereArgsto populate the atmosphere constant block. -
Hook at
RtxContext::bindCommonRayTracingResources(atmosphere LUT bindings) →fork_hooks::bindAtmosphereLutsinrtx_fork_atmosphere.cppEnsures the atmosphere object is initialized and binds the three atmosphere LUT textures (BINDING_ATMOSPHERE_TRANSMITTANCE_LUT,BINDING_ATMOSPHERE_MULTISCATTERING_LUT,BINDING_ATMOSPHERE_SKY_VIEW_LUT) for all passes that declare them in common_bindings. -
Hook at
RtxContext::rasterizeSky(physical atmosphere sky skip) →fork_hooks::injectRtxAtmosphereSkySkipinrtx_fork_atmosphere.cppReturns early from rasterized sky rendering when Numos mode is active. No private-member access; no friend declaration required. -
Hook at
RtxContext::dispatchScreenOverlay(method body + ScreenOverlayShader class) →fork_hooks::dispatchScreenOverlayinrtx_fork_overlay.cppScreenOverlayShaderlifted tortx_fork_overlay.cpp;dispatchScreenOverlayis now a one-line delegate. The hook alpha-composites a plugin-uploaded RGBA buffer over the final tone-mapped image using the compute shader. -
Inline tweak at
RtxContext::dispatchTonemapping— removed theTonemappingMode::Global || TonemappingMode::Directdispatch gate; the tonemapper now always runs (always operator-only). TheDxvkLocalToneMappingdispatch block was removed entirely (2026-05-15). 2026-05-13 tonemap refactor: global tone curve removed. 2026-05-15: local tonemapper removed. -
Inline tweak at
(file scope)(weather header include) — 1-line addition near the existingrtx_fork_*.hincludes. Adds#include "rtx_fork_weather.h"soWeatherBlenderand thefork_weathernamespace are available in this translation unit. -
Inline tweak at
RtxContext::RtxContextconstructor (weather blender init) — ~1 LOC. Addsm_weatherBlender = std::make_unique<fork_weather::WeatherBlender>();so the blender is constructed alongside the atmosphere object. -
Hook at
RtxContextper-frame entry (weather blender update) —fork_hooks::updateWeatherBlenderinrtx_fork_weather.cpp. Callsfork_hooks::updateWeatherBlender(*this, GlobalTime::get().deltaTime())once per frame so the blender can read trigger keys, advance the lerp timeline, and write blended values to the Derived RTX_OPTION layer.
src/dxvk/rtx_render/rtx_context.h
Pre-refactor fork footprint: +26 / -0 LOC (audit 2026-04-18) Post-refactor fork footprint: +26 / -0 LOC inline tweaks + 4 friend declarations added (migrated 2026-04-18)
-
Inline tweak at
RtxContextclass (member declarations — atmosphere) — ~4 LOC. Addsm_lastSkyModeandm_atmospheremember fields toRtxContext. These remain as class members; the fork_hooks functions access them via friend declarations. -
Inline tweak at
RtxContext::setScreenOverlayDataanddispatchScreenOverlaydeclarations — ~5 LOC. Declares the two overlay-path methods.setScreenOverlayDataremains a standalone public method;dispatchScreenOverlayis now a one-line delegate tofork_hooks::dispatchScreenOverlay. -
Inline tweak at
RtxContextclass (member declarations — screen overlay state) — ~11 LOC. AddsScreenOverlayFramestruct,m_pendingScreenOverlay,m_screenOverlayImage,m_screenOverlayView,m_screenOverlayWidth,m_screenOverlayHeight, andm_screenOverlayFormat. These remain as class members accessed via friend declarations. -
Inline tweak at
RtxContextclass body (just before closing};) — 4-line block offrienddeclarations plus a forward-declaration block above the class. Grantsfork_hooks::initAtmosphere,fork_hooks::updateAtmosphereConstants,fork_hooks::bindAtmosphereLuts, andfork_hooks::dispatchScreenOverlayaccess to private members. -
Inline tweak at
(file scope)(weather forward declarations) — ~2 LOC above the class definition. Addsnamespace fork_weather { class WeatherBlender; }forward declaration and avoid updateWeatherBlender(class RtxContext& ctx, float deltaTimeSeconds)forward declaration inside thefork_hooksnamespace block, so the private member and friend declaration below can reference the type. -
Inline tweak at
RtxContextclass body (private member declarations — weather) — ~1 LOC. Addsstd::unique_ptr<fork_weather::WeatherBlender> m_weatherBlender;as a private member ofRtxContext. -
Inline tweak at
RtxContextclass body (friend declarations block) — ~1 LOC addition to the existing friend block. Addsfriend void fork_hooks::updateWeatherBlender(RtxContext&, float);so the hook can access the privatem_weatherBlendermember.
src/dxvk/rtx_render/rtx_game_capturer.cpp
Pre-refactor fork footprint: +94 / -28 LOC (audit 2026-04-18)
Post-refactor footprint: 2 hook call sites + inline tweaks + #include "rtx_fork_hooks.h" (migrated 2026-04-18)
Note on Block 1 (materialLookupHash selection): The material-lookup-hash block is pervasive inline tweaks throughout GameCapturer::newInstance (computing materialLookupHash, keying bIsNewMat, the captureMaterial call, meshes[meshHash]->matHash, and instance.matHash). Lifting this into a hook would require threading too many in/out parameters. Kept as inline tweaks; tracked below.
-
Inline tweak at
GameCapturer::newInstance(materialLookupHash computation and usage) — ~7 LOC distributed through the function. ComputesmaterialLookupHash = material.getHash()and substitutes it for the raw BLASmatHashin thebIsNewMatguard, thecaptureMaterialcall,meshes[meshHash]->matHash, andinstance.matHash, so USD capture references align with runtime replacement lookup for API-submitted materials. -
Hook at
GameCapturer::captureMaterial(method body) →fork_hooks::captureMaterialApiPathinrtx_fork_capture.cppGameCapturer::captureMaterialis now a one-line delegate; all logic lives in the hook. Exports the albedo texture for both D3D9 materials (color texture valid — direct export) and API-submitted materials (fallback: resolves texture hash via the texture-manager table and exports by hash). Access to privatem_exporterandm_pCapis granted via afrienddeclaration — see thertx_game_capturer.hentry below. -
Hook at
GameCapturer::prepExport(coord-system transform block) →fork_hooks::captureCoordSystemSkipinrtx_fork_capture.cppSkips the view/proj handedness inversion for the global USD export transform when the game is configured as a left-handed coordinate system, since API-submitted geometry is already in consistent Y-up space.
src/dxvk/rtx_render/rtx_game_capturer.h
Post-refactor fork footprint: forward decl + friend declaration (added 2026-04-18)
Category: index-only
-
Inline tweak at file scope (just before
class GameCapturer) — 13-line forward declaration offork_hooks::captureMaterialApiPathso the friend declaration insideGameCapturercan name the fork-owned hook. Companion to thertx_fork_capture.cpphook that needs private-member access tom_exporterandm_pCap. -
Inline tweak at
GameCapturerclass body (top of class, beforepublic:) — 4-linefrienddeclaration grantingfork_hooks::captureMaterialApiPathaccess to private members. Canonical pattern for hooks that must read/write private upstream state — one inline tweak per such hook, tracked here.
src/dxvk/rtx_render/rtx_light_manager.cpp
Pre-refactor fork footprint: +126 / -12 LOC (audit 2026-04-18)
Post-refactor footprint: 7 hook call sites + #include "rtx_fork_hooks.h" (migrated 2026-04-18)
Category: migrate
-
Hook at
LightManager::prepareSceneData(pending-mutation flush block) →fork_hooks::flushPendingLightMutationsinrtx_fork_light.cppAt frame start, applies queued external-light erases (clearing replacement links), applies queued updates (erase-then-emplace to handle union-type changes), registers pending active-light activations, and auto-instances all persistent lights. Access to private members granted viafrienddeclaration — seertx_light_manager.h. -
Hook at
LightManager::updateExternallyTrackedLight(indexed static-sleep path) →fork_hooks::updateLightStaticSleepinrtx_fork_light.cppShared static-sleep logic: tracksisStaticCount, skips copy when motionless for N frames, always updates dynamic lights. RestoresexternallyTrackedLightIdwhen externalId is valid. -
Hook at
LightManager::addExternalLight(hash-map static-sleep path) →fork_hooks::updateLightStaticSleepinrtx_fork_light.cppSecond call site for the same hook (Block 2's two-copies reduction). PasseskInvalidExternallyTrackedLightIdso the id-restore branch is skipped. -
Hook at
LightManager::addExternalLight(new-light emplace branch) →fork_hooks::setExternalLightEmplaceinrtx_fork_light.cppEmplaces the new external light and stampsframeLastTouched. Access tom_externalLightsviafrienddeclaration. -
Hook at
LightManager::removeExternalLight(queue erase) →fork_hooks::disableExternalLightQueueinrtx_fork_light.cppQueues the handle for deferred erase instead of immediate removal. Access tom_pendingExternalLightErasesviafrienddeclaration. -
Hook at
LightManager::registerPersistentExternalLight→fork_hooks::registerPersistentLightinrtx_fork_light.cppInserts the handle intom_persistentExternalLights. Access viafrienddeclaration. -
Hook at
LightManager::unregisterPersistentExternalLight→fork_hooks::unregisterPersistentLightinrtx_fork_light.cppRemoves the handle fromm_persistentExternalLights. Access viafrienddeclaration. -
Hook at
LightManager::queueAutoInstancePersistent→fork_hooks::queueAutoInstancePersistentinrtx_fork_light.cppCopies all persistent-light handles intom_pendingExternalActiveLights. Access viafrienddeclaration.
src/dxvk/rtx_render/rtx_light_manager.h
Pre-refactor fork footprint: +10 / -0 LOC (audit 2026-04-18)
Post-refactor footprint: forward decls + friend declarations (updated 2026-04-18)
Category: index-only
-
Inline tweak at
LightManagerclass (public method declarations) (~line 101) — 4-line addition. DeclaresregisterPersistentExternalLight,unregisterPersistentExternalLight, andqueueAutoInstancePersistentin the public API ofLightManager. -
Inline tweak at
LightManagerclass (private member declarations) (~line 129) — 6-line addition. Adds four deferred-mutation member containers:m_pendingExternalLightErases,m_pendingExternalLightUpdates,m_pendingExternalActiveLights, andm_persistentExternalLights. -
Inline tweak at file scope (just before
struct LightManager) — forward declarations of all sevenfork_hooks::functions that needLightManagerorRtLightaccess, plusstruct RtLightforward decl. Required so thefrienddeclarations inside the class can name the fork-owned hooks. -
Inline tweak at
LightManagerclass body (top of class, beforepublic:) — 7-line block offrienddeclarations granting the light hooks access to private members. Canonical pattern for hooks that must read/write private upstream state — onefriendline per hook, tracked here.
src/dxvk/rtx_render/rtx_lights.cpp
Pre-refactor fork footprint: +25 / -17 LOC (audit 2026-04-18) Post-refactor fork footprint: +25 / -17 LOC inline tweaks (reclassified 2026-04-18)
Category: index-only
Rationale: All fork changes are signature modifications and single-line flag-packing additions woven into the middle of existing writeGPUData function bodies, plus a one-line copy in the copy constructor. There is no standalone block to lift; the ignoreViewModel parameter is intrinsic to each function's signature and the flag-set line (if (ignoreViewModel) flags |= 1 << 1;) is inseparably interleaved with the upstream flags-assembly code. A hook would require passing the entire flags word in and out, making it structurally equivalent to rewriting each function — not a meaningful extraction.
-
Inline tweak at
RtSphereLight::writeGPUData(ignoreViewModel parameter + bit 1 flag) — ~3 LOC. Addsbool ignoreViewModel = falseparameter; sets bit 1 of the flags word when set. Companion signature change tracked inrtx_lights.h. -
Inline tweak at
RtRectLight::writeGPUData(ignoreViewModel parameter + bit 1 flag) — ~3 LOC. Same pattern for rect lights. -
Inline tweak at
RtDiskLight::writeGPUData(ignoreViewModel parameter + bit 1 flag) — ~3 LOC. Same pattern for disk lights. -
Inline tweak at
RtCylinderLight::writeGPUData(ignoreViewModel parameter + bit 1 flag) — ~3 LOC. Same pattern for cylinder lights; refactors the previously directwriteGPUHelpercall to use a localflagsvariable so the bit can be conditionally set. -
Inline tweak at
RtDistantLight::writeGPUData(ignoreViewModel parameter + bit 1 flag) — ~4 LOC. Same pattern for distant lights; same refactor of the direct helper call to a local flags variable. -
Inline tweak at
RtLight::writeGPUData(dispatch passesthis->ignoreViewModel) — ~5 LOC (5 call-site updates). Each per-typewriteGPUDatacall now forwardsthis->ignoreViewModelas the third argument. -
Inline tweak at
RtLight::copyFrom(ignoreViewModel copy) — ~1 LOC. CopiesignoreViewModelincopyFrom, called by the copy constructor.
src/dxvk/rtx_render/rtx_lights.h
Pre-refactor fork footprint: +6 / -5 LOC (audit 2026-04-18)
Category: index-only
-
Inline tweak at
RtSphereLight::writeGPUDatadeclaration (~line 133) — 1-line modification to addignoreViewModeldefault parameter. Addsbool ignoreViewModel = falseparameter to thewriteGPUDatadeclaration. -
Inline tweak at
RtRectLight::writeGPUDatadeclaration (~line 197) — 1-line modification (same pattern). Same default parameter addition forRtRectLight. -
Inline tweak at
RtDiskLight::writeGPUDatadeclaration (~line 275) — 1-line modification (same pattern). Same default parameter addition forRtDiskLight. -
Inline tweak at
RtCylinderLight::writeGPUDatadeclaration (~line 350) — 1-line modification (same pattern). Same default parameter addition forRtCylinderLight. -
Inline tweak at
RtDistantLight::writeGPUDatadeclaration (~line 412) — 1-line modification (same pattern). Same default parameter addition forRtDistantLight. -
Inline tweak at
RtLightstruct (~line 645) — 1-line addition ofignoreViewModelfield. Addsbool ignoreViewModel = falsemember toRtLightto carry the per-light view-model exclusion flag across the GPU data write.
src/dxvk/rtx_render/rtx_instance_manager.cpp
Category: index-only
- Inline tweak at
InstanceManager::onInstanceUpdatedsurface-meta block (~line 988, besideisAnimatedWater) — REVERTED 2026-06-19, comment-only. Previously setcurrentInstance.surface.isDecalCategoryfor the cloud-shadow zenith gate. The gate and flag were deleted when the cloud shadow moved onto the sun term in the NEE (no geometry test needed); only a "removed" comment remains.
src/dxvk/rtx_render/rtx_materials.h
Category: index-only
- Inline tweak at
RtSurfaceflags block (~line 336) +RtSurface::writeGPUDataflags0packing (~line 121) — REVERTED 2026-06-19, comment-only. Previously addedbool isDecalCategorypacked intoflags0bit 2 for the cloud-shadow zenith gate. Removed with the gate;flags0bit 2 is free again. Only "removed" comments remain.
src/dxvk/rtx_render/rtx_options.h
Pre-refactor fork footprint: +32 / -0 LOC (audit 2026-04-18) Post-refactor fork footprint: +32 / -0 LOC inline tweaks (reclassified 2026-04-18)
Category: index-only
Rationale: All fork additions are an enum definition and RTX_OPTION(...) macro declarations inside the RtxOptions class body. RTX_OPTION expands to an inline static member declaration — it is structurally part of the class definition and cannot be lifted into a separate TU or wrapped in a hook. There is no function body to extract.
-
Inline tweak at
(file scope namespace dxvk)(SkyMode enum) — ~5 LOC. Declares theSkyModeenum class (SkyboxRasterization = 0,Numos = 1). Required byRtxOptions::skyModebelow and by atmosphere hook code inrtx_fork_atmosphere.cpp(via thertx_options.hinclude chain). -
Inline tweak at
RtxOptionsclass body (skyMode RTX_OPTION) — ~2 LOC. DeclaresRTX_OPTION("rtx", SkyMode, skyMode, SkyMode::SkyboxRasterization, ...)immediately after the existing sky-related options block. Consumed byfork_hooks::updateAtmosphereConstantsinrtx_fork_atmosphere.cpp. -
Inline tweak at
RtxOptionsclass body (atmosphere RTX_OPTIONs block) — ~25 LOC for the original 17 options + ~5 LOC for night-sky + ~52 LOC for theDECLARE_MOON_OPTIONS(N)macro and 4 invocations + ~11 LOC for the cloud block + ~13 LOC for the cloud-enhancement block (includingcloudVerticalProfile,cloudCurvature). Sun/star position fields (sunElevation,sunRotation,starBrightness,starRotation) and per-moon pose fields (elevationN,rotationN,phaseN) are game-drivable per-frame but persist when saved (runtime push is the last writer; cold start uses the saved value until any push lands). Cloud defaults tuned from artist iteration. Declares the original 17 atmosphere tuning options under thertx.atmosphereprefix (sunDisc,sunSize,sunIntensity,sunElevation,sunRotation,altitude,airDensity,aerosolDensity,ozoneDensity,planetRadius,atmosphereThickness,mieAnisotropy,rayleighScattering,mieScattering,ozoneAbsorption,ozoneLayerAltitude,ozoneLayerWidth,sunIlluminance), plus the night-sky block (starBrightness,starDensity,starTwinkleSpeed,nightSkyBrightness,nightSkyColor), plus a per-moon block declared via theDECLARE_MOON_OPTIONS(N)macro forNin0..MAX_MOONS-1(each block:enabledN,angularRadiusN,brightnessN,colorN,surfaceStyleN,craterDensityN,surfaceContrastN,surfaceNoiseScaleN,darkSideBrightnessN,roughnessAmountN, plus pose fieldselevationN/rotationN/phaseN), plus a cloud block (cloudEnabled,cloudDensity,cloudAltitude,cloudScale,cloudColor,cloudWindSpeed,cloudWindDirection,cloudShadowStrength,cloudAnisotropy, plus NoSave-flaggedcloudCoveragefor game-driven weather), plus a cloud-enhancement block (cloudViewSamples,cloudThickness,cloudDetailWeight,cloudShadowTint,cloudShadowTintStrength,cloudSunsetWarmth,cloudVariance,cloudVarianceScale,cloudVerticalProfile,cloudCurvature) for volumetric ray-march tuning, color polish, vertical-shape character, and sky-dome curvature. All consumed byRtxAtmosphere::getAtmosphereArgs()and the atmosphere UI hook inrtx_fork_atmosphere.cpp. -
Inline tweak at
RtxOptionsclass body (cloud spatial-variation block) — +21 / -11 net LOC. AddscloudTypeMean,cloudTypeSpread,cloudTypeNoiseScale,cloudCoverageMean,cloudCoverageSpread,cloudCoverageNoiseScale,cloudAnvilBias,cloudWindShearStrengthRTX_OPTIONs (Nubis-style spatial variation, spec 2026-05-06;cloudWindShearStrengthadded as a tunable knob on the existing wind-shear UV perturbation insampleCloudDensity). Replaces retiredcloudCoverage,cloudVariance,cloudVarianceScale,cloudVerticalProfile. -
Inline tweak at
RtxOptionsclass body (cloud aerial-perspective extinction) — +13 LOC. AddscloudAerialExtinctionPerKmRTX_OPTION (default 0.2) under thertx.atmosphereprefix (2026-05-16). Distance-based atmospheric extinction applied to cloud samples:aerialT = exp(-cloudAerialExtinctionPerKm * t)multiplies both per-step radiance contribution AND per-step extinction so horizon-grazing rays don't accumulate through ~100 km of cloud volume into a solid white wall. Applied identically inmarchCloudSlab(cloud_render.comp.slang) and the legacy analyticalevalCloudspath (atmosphere_sky.slangh) so the two render modes stay visually consistent. Setting the option to 0 reverts to legacy (no aerial perspective) behavior. Consumed byRtxAtmosphere::getAtmosphereArgs()— the new field repurposes the existingpad_cloudWorley_0slot inAtmosphereArgs, preserving 16-byte struct alignment — and the atmosphere UI hook inrtx_fork_atmosphere.cpp. -
Inline tweak at
RtxOptionsclass body (moon-lighting strength sliders + cloud-look shape) — +10 LOC (Phase 1) + +18 LOC (Phase 3 Task 1) + +28 LOC (Phase 3 Task 2). Phase 1 (2026-05-07) addedmoonNeeStrength(world-side master, default 1.0) andmoonAtmosphericCouplingStrength(sky-side, default 1.0) RTX_OPTIONs. Phase 3 Task 1 (2026-05-08) added per-path stylistic multipliers:surfaceMoonBrightness(default 50.0),cloudMoonBrightness(default 2.0),haloMoonBrightness(default 15.0) — empirically tuned by in-game testing on 2026-05-08 against the Fallout: New Vegas test scene atm.brightness=1.0. Setting all three to 1.0 reverts to architecturally-pure physical-baseline output. Phase 3 Task 2 (2026-05-08) exposed five cloud-look + halo shape constants previously hardcoded inatmosphere_sky.slangh:moonCloudDiffuseGain(0.10),moonCloudPhaseGain(0.30),moonCloudAnisotropy(0.85),moonHaloMagnitude(0.0015),moonAmbientAirglow(0.0015). Defaults preserved; exposure is for in-game tuning without shader rebuilds. All consumed acrossevalAtmosphereRadiance,evalClouds,evalMoonDisk's halo, andsampleAtmosphereMoonLight. -
Inline tweak at
RtxOptionsclass body (Phase 2 default migration) — net 0 LOC, value/text changes only. Phase 2 (2026-05-08) shifts the per-moonbrightness##Ndefault from 4.0 → 1.0 (physical neutral; was magic-number magnitude-cheat) and the per-mooncolor##Ndefault from (0.85, 0.87, 0.92) → (0.12, 0.12, 0.12) (neutral lunar Bond albedo; the prior cool-blue tint was magnitude-cheating). Retires thecloudMoonBrightnessRTX_OPTION (its job -- scaling the cloud path's magic-number magnitude -- was eliminated by the Phase 2 unified physical irradiance scaffold). See2026-05-08-moon-physical-irradiance-design.md. -
Inline tweak at
RtxOptionsclass body (cloud-look master multipliers) — +8 LOC. Sky/moon ImGui simplification pass (2026-05-21) addedmoonSilverLiningIntensityandmoonHaloGlowStrengthRTX_OPTIONs (both default 1.0). Applied C++-side inRtxAtmosphere::getAtmosphereArgs()as multipliers on the existing cloud-look + halo + airglow knob values, so the five Phase-3-Task-2 fine knobs collapse to three ImGui sliders (Silver Lining Intensity / Sharpness / Halo Glow) while the underlying ratio constants remain.conf-tunable. Shaders unchanged — masters apply at args population. Default 1.0 = byte-identical to pre-2026-05-21 behavior. See2026-05-20-sky-moon-imgui-persistence-and-reorg-design.md. -
Inline tweak at
(file scope)(weather header include) -- 1-line addition near the existingrtx_fork_*.hincludes. Adds#include "rtx_fork_weather.h"so theDECLARE_ALL_WEATHER_PRESETS()macro is in scope before it is used inside theRtxOptionsclass body. -
Inline tweak at
RtxOptionsclass body (weather preset RTX_OPTION block) -- 1-line macro invocation + 14-line undef block. InvokesDECLARE_ALL_WEATHER_PRESETS()inside theRtxOptionsstruct body to expand all 504 RTX_OPTION declarations (12 presets x 42 fields). The 14#undeflines immediately following clean up the binder macros so they do not leak into downstream includes. -
Inline tweak -- cloud RTX_OPTION audit cleanup (2026-05-19). Removes three dead-knob RTX_OPTIONs whose values were written into
AtmosphereArgsbut never read by any shader:cloudScale(pre-3D-texture era; replaced bycloudNoiseTileKm),cloudDetailWeight(legacy FBM detail-fade; Nubis Cubed sampler has no detail-vs-base split), andcloudWindShearStrength(legacy analytical-only wind shear; textured sampler intentionally drops it). Also flipscloudShadowStrengthdefault0.0→1.0so the voxel-grid cloud-on-terrain shadow system (cloudVoxelShadowsEnable,cloudShadowMarchStrength,cloudShadowFactorStrength) is not silently muted at boot. Args struct slots preserved aspadDead*placeholders to maintain 16-byte alignment until a repack pass; the 24 weather-preset entries (12 presets x 2 fields) and the matchingWeatherSnapshotplumbing inrtx_fork_weather.{h,cpp}are dropped along with the RTX_OPTION declarations and the ImGui sliders. -
Inline tweak -- cloud system iteration #2 (2026-05-19, second pass). Coverage gate floor lowered from 0.15 to 0.0 in
sampleCloudDensityTexturedso max-Coverage truly fills the sky (was clipping the bottom 15% of the noise range). Aerial perspective split into two independent RTX_OPTIONs:cloudAerialHazePerKm(renamed fromcloudAerialExtinctionPerKm; dims cloud radiance with distance — visual softness) and newcloudAerialFadePerKm(softens cloud extinction with distance — prevents horizon white wall). Two new layer-2 spread RTX_OPTIONs (cloudLayer2CoverageSpread,cloudLayer2TypeSpread) plus a shader-side seed-offset (cloudLayer2NoiseSeed, default 1000) added tosmoothNoise2Dcalls for layer 2 so the two layers generate fully decorrelated horizontal weather patterns instead of stacking. Position-shift approach considered first but reverted becausecomputeCloudHeightFractionuseslength()against planet center, so any XZ shift > ~10 km pushes samples out of the slab via spherical-distance inflation. Four previously hidden knobs re-added to the menu (cloudCoverageNoiseScale,cloudTypeNoiseScale,cloudCurvature,cloudPhaseG2) since they were silently driving the look at their defaults. 16 RTX_OPTION defaults retuned from in-game FNV tuning. Args slot recycling: pad6/pad7/three padDead* slots are now all live fields (cloudLayer2NoiseSeed,cloudAerialFadePerKm,cloudLayer2CoverageSpread,cloudLayer2TypeSpread,cloudMsScale). Struct size unchanged. -
Inline tweak — remove
rtx.useLegacyACES+rtx.showLegacyACESOptionRtxOptions (superseded byTonemapOperator::ACESNarkowiczenum value). Both options live at thertxnamespace (notrtx.tonemap); removed in the enum refactor. -
Inline tweak — remove
TonemappingModeenum (Global / Local / Direct) andtonemappingModeRTX_OPTION. The dynamic tone curve (histogram + curve passes) is removed; the apply pass dispatches the operator directly. Local tonemapping (DxvkLocalToneMapping,useLocalToneMappingRTX_OPTION,rtx.localtonemap.*) removed entirely on 2026-05-15. The vestigialdirectOperatorModeCB field was removed in the 2026-05-XX cleanup along with the dead histogram / tone-curve dispatch passes and the ACES enum rename (ACES/ACESLegacy→ACESHill/ACESNarkowicz). 2026-05-13 tonemap refactor: simplified from three-mode selector to global operator dropdown. 2026-05-15: local tonemap path removed entirely. 2026-05-XX: dead-code cleanup + snake_case shader rename. -
Inline tweak — weather-preset cold-default alignment (2026-05-26). Ten cold defaults aligned to
WEATHER_PRESET_VALUES_overcastinrtx_fork_weather.h(the macro the codebase comments call "current default look"):cloudShadowStrength1.0 → 0.10,cloudCoverageMean0.85 → 0.64,cloudCoverageSpread1.0 → 0.16,cloudTypeMean0.75 → 0.5,cloudTypeSpread0.5 → 0.2,cloudTypeNoiseScale0.001 → 0.0034,cloudDensity1.65 → 1.8,cloudThickness2.75 → 3.05,aerosolDensity1.0 → 1.1,sunIlluminance(20,20,20) → (15,15,15). Fixes the regression introduced by the 2026-05-19cloudShadowStrength0→1 flip: users sitting on the dormant "(none / dormant)" weather preset saw ground geometry crushed dark by full-strength cloud-voxel shadows over 85% default coverage. The dormant blender path leaves cold RTX_OPTIONs untouched, so the cold values themselves had to move.
src/dxvk/rtx_render/rtx_global_volumetrics.cpp
Category: index-only
-
Inline tweak at
RtxGlobalVolumetrics::dispatch(volumeArgs population block) — 2 LOC (2026-05-26; Numos-gated 2026-07-04). PopulatesvolumeArgs.fogSunVisibilityGainandvolumeArgs.volumetricConsumerGainfrom the matching RTX_OPTIONs, forced to 1.0 unlessRtxOptions::skyMode() == SkyMode::Numos— both gains are Numos-atmosphere compensations and must not perturb stock volumetrics in rasterized-sky mode. Adjacent to the existingvolumetricFogAnisotropypopulate. Companion to the fields involume_args.hand the options inrtx_global_volumetrics.h. -
Inline tweak at
RtxGlobalVolumetrics::showImguiSettings(advanced material section) — 3 widgets (2026-05-26/2026-06-28/2026-07-05). Fork sliders "Fog Sun Visibility Gain", "Atmosphere Sun Fog Scale", and "Volumetric Consumer Gain" alongside the stock advanced material widgets, exposing the fork gain options for tuning.
src/dxvk/rtx_render/rtx_global_volumetrics.h
Pre-refactor fork footprint: N/A — value-only cold-default tweaks
Category: index-only
-
Inline tweak — weather-preset cold-default alignment (2026-05-26). Three RTX_OPTION cold defaults aligned to the
WEATHER_PRESET_VALUES_overcastblock inrtx_fork_weather.h:transmittanceColor(0.999, 0.999, 0.999) → (0.995, 0.995, 0.995),transmittanceMeasurementDistanceMeters200.0 → 500.0,anisotropy0.0 → 0.05 (mapped fromvolumetricAnisotropyin the preset). Companion to the matchingrtx_options.hblock — same rationale: the dormant "(none / dormant)" weather preset path leaves cold RTX_OPTIONs untouched, so the cold defaults themselves had to move to match overcast. -
Inline tweak — new
fogSunVisibilityGainRTX_OPTION (2026-05-26; default lowered 5.0→1.0 on 2026-06-15).rtx.volumetrics.fogSunVisibilityGain(default 1.0, range 0.0–50.0) replaces the historical hardcoded artistic gain (x5 with a misleading "10x" comment in the gmod-rtx port) that was previously baked into the per-cache-write expression in fork-ownedatmosphere_common.slangh. Default is now physical (1.0 = no boost) rather than the gmod-era ~5x. Read byvolume_composite_helpers.slangh::integrateVolumetricNEE(consumer-side fog application only — surface consumers still read the cache straight). Companions:rtx_global_volumetrics.cpp(CB populate),volume_args.h(CB field), submodule fork edit atrtxdi-sdk/include/volumetrics/rtx/algorithm/volume_composite_helpers.slangh.
src/dxvk/rtx_render/rtx_overlay_window.cpp
Pre-refactor fork footprint: +57 / -35 LOC (audit 2026-04-18)
Post-refactor footprint: 1 hook call site + 1 #include "rtx_fork_hooks.h" (migrated 2026-04-18)
Note on diag blocks: The fridge list originally listed 3 [RTX-Diag] blocks (blocks 2-4). These were introduced and then immediately reverted (commit 664a9ba4 reverted 0d590fb4) before this migration ran. They are not present in the current file; no action was taken. Only block 1 (keyboard-forward) was active and required migration.
- Hook at
GameOverlay::gameWndProcHandler(after hwnd guard) →fork_hooks::overlayInputForwardinrtx_fork_overlay.cppForwards keyboard (WM_KEY*, WM_CHAR, WM_SYSCHAR) AND mouse (WM_MOUSEMOVE, WM_{L,R,M,X}BUTTON*, WM_MOUSE{,H}WHEEL) messages toImGui_ImplWin32_WndProcHandleron the legacy WndProc path so ImGui keyboard + mouse state stays in sync when a game menu captures raw input or when the plugin HUD pulls focus via the Remix API. Mouse coords in lParam are translated from gameHwnd to overlayHwnd client-space when the two differ; wheel lParam is screen-space and forwards without translation. Access to privatem_hwndis granted via afrienddeclaration — see thertx_overlay_window.hentry below. Previously namedoverlayKeyboardForward(keyboard-only); renamed + expanded 2026-04-19 when the plugin-API mouse-input bug was diagnosed.
src/dxvk/rtx_render/rtx_overlay_window.h
Post-refactor fork footprint: forward decl + friend declaration (added 2026-04-18)
Category: index-only
-
Inline tweak at file scope (just before
class GameOverlay) — 6-line forward declaration offork_hooks::overlayInputForwardso the friend declaration insideGameOverlaycan name the fork-owned hook. Companion to thertx_fork_overlay.cpphook that needs private-member access tom_hwnd. Renamed fromoverlayKeyboardForwardon 2026-04-19 when the hook's scope expanded to cover mouse messages. -
Inline tweak at
GameOverlayclass body (top of class, beforepublic:) — 3-linefrienddeclaration grantingfork_hooks::overlayInputForwardaccess tom_hwnd. Canonical pattern for hooks that must read/write private upstream state — one inline tweak per such hook, tracked here.
src/dxvk/rtx_render/rtx_remix_api.cpp
Pre-refactor fork footprint: +1277 / -118 LOC (audit 2026-04-18)
Post-refactor footprint (fully migrated — migrations #7a, #7b, #7c, #7d done): 23 hook call sites + 1 #include "rtx_fork_hooks.h" + inline tweaks listed below. All extractable fork blocks have been migrated to rtx_fork_api_entry.cpp.
-
Inline tweak at
(file scope)(includes block) — ~8 LOC added. Not migrated: include lines don't get hooks — they either stay inline or the fork-owned file pulls them for its own code. Tracked here per the fridge-list invariant. Adds includes fordxvk_objects.h,dxvk_imgui.h,rtx_context.h,rtx_option_layer.h,util_hash_set_layer.h,xxhash.h,algorithm, andd3d9_texture.hto support fork-added API functions, plusrtx_fork_hooks.hadded in migration #7a andrtx_fork_game_state.hadded in workstream 10 for theremixapi_SetGameValueentry point. -
Inline tweak at
(file scope)(PendingScreenOverlaystruct +s_pendingScreenOverlay) — Removed in migration #7b. Both the struct and the optional are now defined exclusively inrtx_fork_api_entry.cpp(anonymous namespace). A comment marking the removal remains in the upstream file for auditability. The struct held staging buffer, dimensions, format, and opacity; the optional was the hand-off point between the API thread (writer:drawScreenOverlay) and the render thread (reader:presentScreenOverlayFlush). Both now live in the fork-owned TU. -
Inline tweak at
(anonymous namespace)—s_inFrame,s_beginCallback,s_endCallback,s_presentCallback— Removed in migration #7c. All four vars now live inrtx_fork_api_entry.cpp(anonymous namespace). A comment block marking the removal remains in the upstream file for auditability. Previously used inline at 6 call sites in rtx_remix_api.cpp (DrawInstance, DrawLightInstance, Shutdown, Present×3). All call sites are now one-liner hook delegates. -
Hook at
convert::toRtMaterialFinalized::preloadTexturelambda (inside theMaterialDataType::Opaque/Translucent/Portaltexture preload path) →fork_hooks::textureHashPathLookupinrtx_fork_api_entry.cpp(migrated 2026-04-18, migration #7a). Adds a "0x..." hex-path shortcut inside the upstream texture-path resolver so API-uploaded textures can be referenced by hash string in material JSON without creating a real file path. Hook returns true and writes the resolvedTextureRefwhen the path parses as hex and matches a registered texture; caller returns immediately. Falls through to the normal AssetDataManager lookup otherwise. -
Hook at
(anonymous namespace)remixapi_AddTextureHash/remixapi_RemoveTextureHash→fork_hooks::mutateTextureHashOptioninrtx_fork_api_entry.cpp(migrated 2026-04-18, migration #7a). Looks up anRtxOption<fast_unordered_set>by full option name and adds or removes a hash via the user config layer. Call sites acquires_mutexthen delegate to the hook (which internally takes the RtxOption update mutex — lock order documented alongsides_mutex). The call-site signature replaced the localTextureHashMutationenum with a plainbool addparameter. -
Inline tweak at
convert::toRtDrawState(skinning hash computation) — 1 LOC, not worth a hook. Not migrated. CallsskinningData.computeHash()on the prototype after building skinning data so the skinning hash participates in geometry deduplication. -
Inline tweak at
convert::toRtDrawState(blend-weight/index buffer stride fix) — 2 LOC across two call sites, not worth a hook. Not migrated. FixesblendWeightBufferandblendIndicesBufferstrides to usebonesPerVertex-based byte widths rather than fixed-width placeholders. -
Inline tweak at
remixapi_SetupCamera(devLock RAII guard) — 1 LOC. Not extracted to a hook. TheLockDevice()guard is scope-tied to the function body (its destructor must run at end-of-function), so a hook cannot own it without rewriting the entire function. Tracked here per the fridge-list invariant. Addsauto devLock = remixDevice->LockDevice()so the EmitCs call that submits external camera data is race-safe. -
RETIRED 2026-07-29 (upstream sync) — the
remixapi_DrawInstancedevLock RAII guard is gone. Upstream restructured the function to serialize on the api-levels_mutexinstead of the device lock, hoisting thetoRtDrawStateconversion outside the lock and emitting withEmitCs<false>(AllowFlush=false). The fork'sLockDevice()guard was dropped in favour of upstream's scheme;remixapi_SetupCameraabove still carries its own guard because upstream did not restructure that one.remixapi_DrawInstanceis now upstream's function verbatim plus the two fork hook calls below. -
Hook at
remixapi_DrawInstance(beginScene dispatch) →fork_hooks::notifyBeginSceneinrtx_fork_api_entry.cpp(migrated 2026-04-18, migration #7c). One-liner call. Atomically exchangess_inFrameto true and firess_beginCallbackon the first frame submission. -
Hook at
remixapi_DrawLightInstance(beginScene dispatch) →fork_hooks::notifyBeginSceneinrtx_fork_api_entry.cpp(migrated 2026-04-18, migration #7c). Same hook as DrawInstance; lights-only frames also trigger the beginScene callback. -
Hook at
remixapi_Shutdown(callback + frame-state clear) →fork_hooks::shutdownCallbacksinrtx_fork_api_entry.cpp(migrated 2026-04-18, migration #7c). One-liner call replacing the 4-line null/false reset. Clearss_beginCallback,s_endCallback,s_presentCallback, ands_inFrame. -
Hook at
remixapi_Present(screen overlay flush — inner namespace path) →fork_hooks::presentScreenOverlayFlushinrtx_fork_api_entry.cpp(migrated 2026-04-18, migration #7b). One-liner call to the fork-owned flush hook. State (PendingScreenOverlay + s_pendingScreenOverlay) was unified in the same migration. -
Hook at
remixapi_Present(endScene callback, before native Present) →fork_hooks::presentEndSceneDispatchinrtx_fork_api_entry.cpp(migrated 2026-04-18, migration #7c). Firess_endCallbackifs_inFrameis set, immediately before the nativeremixDevice->Present()call. -
Hook at
remixapi_Present(present callback + s_inFrame reset, after native Present) →fork_hooks::presentCallbackDispatchinrtx_fork_api_entry.cpp(migrated 2026-04-18, migration #7c). Firess_presentCallbackand resetss_inFrameto false after a successful native Present. -
Hook at
extern "C"remixapi_AutoInstancePersistentLights(screen overlay flush path) →fork_hooks::presentScreenOverlayFlushinrtx_fork_api_entry.cpp(migrated 2026-04-18, migration #7b). Same flush hook called on the C-export AutoInstancePersistentLights path, which also drains the pending overlay once per frame. -
Hook at
remixapi_DrawScreenOverlay(function body) →fork_hooks::drawScreenOverlayinrtx_fork_api_entry.cpp(migrated 2026-04-18, migration #7b). Upstream wrapper acquires device + mutex, then delegates to the hook. The reverted [RTX-Diag] FIRST-CALL log block is absent (never present in current HEAD). -
Hook at
remixapi_GetUIState(function body) →fork_hooks::getUiStateinrtx_fork_api_entry.cpp(migrated 2026-04-18, migration #7b). One-liner delegate passingtryAsDxvk()to the hook. -
Hook at
remixapi_SetUIState(function body) →fork_hooks::setUiStateinrtx_fork_api_entry.cpp(migrated 2026-04-18, migration #7b). One-liner delegate. -
Hook at
remixapi_dxvk_GetSharedD3D11TextureHandle(function body) →fork_hooks::getSharedD3D11TextureHandleinrtx_fork_api_entry.cpp(migrated 2026-04-18, migration #7b). Stub returning GENERAL_FAILURE. One-liner delegate. -
Hook at
remixapi_dxvk_GetTextureHash(function body) →fork_hooks::dxvkGetTextureHashinrtx_fork_api_entry.cpp(migrated 2026-04-18, migration #7b). One-liner delegate. -
Hook at
remixapi_CreateTexture(function body) →fork_hooks::createTextureinrtx_fork_api_entry.cpp(migrated 2026-04-18, migration #7b). Upstream wrapper acquires s_mutex, then delegates. Full GPU resource creation lives in the hook. -
Hook at
remixapi_DestroyTexture(function body) →fork_hooks::destroyTextureinrtx_fork_api_entry.cpp(migrated 2026-04-18, migration #7b). Upstream wrapper acquires s_mutex, then delegates. -
Hook at
remixapi_RegisterCallbacks(function body) →fork_hooks::registerCallbacksinrtx_fork_api_entry.cpp(migrated 2026-04-18, migration #7c). One-liner delegate. Body now lives in the fork-owned TU where the callback state vars live. -
Hook at
remixapi_RequestVramCompaction(function body) →fork_hooks::requestVramCompactioninrtx_fork_api_entry.cpp(migrated 2026-04-20, migration #7d). One-liner delegate passingtryAsDxvk()to the hook. Hook does its own null check and sets SceneManager's atomic VRAM-compaction flag; render thread consumes it in manageTextureVram. Lock-free —s_mutexnot taken. -
Hook at
remixapi_RequestTextureVramFree(function body) →fork_hooks::requestTextureVramFreeinrtx_fork_api_entry.cpp(migrated 2026-04-20, migration #7d). One-liner delegate. Hook sets SceneManager's atomic texture-VRAM-free flag; the render-thread tick callstextureManager.clear(), matching the DX9 scene-transition behavior exposed to plugins. Lock-free —s_mutexnot taken. -
Hook at
remixapi_GetVramStats(function body) →fork_hooks::getVramStatsinrtx_fork_api_entry.cpp(migrated 2026-04-20, migration #7d). One-liner delegate. Hook fillsremixapi_VramStatswith per-category DXVK totals plus driver-view heap info (driverAllocatedBytes/driverBudgetBytes) and the fork-sideRtxTextureManager::getTextureTable().size()(forkTextureCacheCount). Driver-view numbers match Task Manager / nvidia-smi; the gap vstotalAllocatedBytesexposes non-DXVK allocations (NGX, RT pipeline state, descriptor pools, NRC). -
Inline tweak at
(anonymous namespace)frame-boundary callback infrastructure —s_pendingLightCreates,s_pendingLightUpdates,s_pendingDomeUpdates,s_pendingLightDestroys,s_pendingMeshCreates,s_handlesDeletedThisFrame. Not migrated. The pending-queue state stays in upstream because it is accessed by too many anonymous-namespace functions (flushPendingMeshes,remixapi_CreateMeshBatched,remixapi_CreateLight,remixapi_DestroyLight,remixapi_Present,remixapi_UpdateLightDefinition) — moving it would require either lifting all those callers or exposing a wide accessor surface. Tracked here per the fridge-list invariant. -
Inline tweak at
remixapi_AutoInstancePersistentLights/remixapi_UpdateLightDefinitionbodies (extern-C fork-owned functions) — not extracted to hooks. These areREMIXAPI-exported entry points; their bodies are the fork's implementation of those API calls. The pending-queue state they access is documented as staying inline above. Tracked here per the fridge-list invariant. 2026-06-27:remixapi_AutoInstancePersistentLightsnow early-outs (skips the per-frameLockDevice+EmitCs) when no C-API scene work is queued and no external light has ever been registered, gated on the file-scope stickys_externalLightApiUsed(set inremixapi_CreateLight,remixapi_CreateLightBatched,remixapi_UpdateLightDefinition). Fixes native-only consumers seeing all lights flicker from the empty per-frame dispatch on the native present path. The self-gating overlay flush still runs. -
Inline tweak at bit-24 category routing (in
toRtCategories) — ~1 LOC. Not migrated. RoutesREMIXAPI_INSTANCE_CATEGORY_BIT_SMOOTH_NORMALS(bit 24, upstream name) toInstanceCategories::SmoothNormals. (The misleadingly-namedLEGACY_EMISSIVEalias of this bit was removed 2026-06-28 — it routed toSmoothNormalsdespite its name, a silent footgun.) -
Inline tweak at
(anonymous namespace)remixapi_SetGameValue— ~15 LOC. Not migrated (fork-owned store + direct inline body fits the surroundingremixapi_SetConfigVariablepattern; no anonymous-namespace state to share with other TUs). Implements the plugin-injected game-state write API introduced in workstream 10. Validates args, constructsstd::stringcopies of the incoming C strings, and forwards todxvk::fork_game_state::GameStateStore::get().set(key, value). Does not takes_mutex— the store owns its own lock, and funnelling high-frequency plugin writes through the API-wide mutex has no benefit. -
Block at
extern "C"vtable init block (fork-added anonymous-namespace slots) — ~11 LOC inline assignment block inremixapi_InitializeLibrary. Not fully hookable: the anonymous-namespace function pointers have internal linkage and cannot be named from another TU. Tracked here per the fridge-list invariant. The three extern-C-linked fork slots (RegisterCallbacks, AutoInstancePersistentLights, UpdateLightDefinition) are assigned viafork_hooks::remixApiVtableInit(migrated 2026-04-18, migration #7c). Registers all fork-added API functions into theremixapi_Interfacevtable. The inline block assigns the anonymous-namespace slots (includingSetGameValueadded in workstream 10); the hook fills the three externally-linked ones. -
Inline tweak at
extern "C"vtable size static_assert — 1 LOC. Not migrated (fridge-listed). Thestatic_assert(sizeof(interf) == 288, ...)sentinel is the final value in the chain (208 → 240 → 272 → 280 → 288 across five workstreams). Retained inline inremixapi_InitializeLibraryas a size sentinel.
src/dxvk/rtx_render/rtx_remix_specialization.inl
Pre-refactor fork footprint: +3 / -1 LOC (audit 2026-04-18)
Category: index-only
-
Inline tweak at
pnext::detailspecialization list (~line 95) — 2-line addition. Addsremixapi_CameraInfoParameterizedEXTandremixapi_TextureInfoto thepnexttype-list sopnext::chaincan traverse these new struct types. -
Inline tweak at
pnext::detail::ToEnumspecialization (~line 123) — 1-line addition. Mapsremixapi_TextureInfotoREMIXAPI_STRUCT_TYPE_TEXTURE_INFOin the sType enum specialization table.
src/dxvk/rtx_render/rtx_resources.cpp
Pre-refactor fork footprint: +18 / -0 LOC (audit 2026-04-18)
Category: index-only
-
Inline tweak at
Resources::getAtmosphereTransmittanceLut(new method body) (~line 808) — 5-line addition. Stub accessor returningm_atmosphereTransmittanceLut; LUT is populated byRtxAtmosphere. -
Inline tweak at
Resources::getAtmosphereMultiscatteringLut(new method body) (~line 813) — 5-line addition. Stub accessor returningm_atmosphereMultiscatteringLut. -
Inline tweak at
Resources::getAtmosphereSkyViewLut(new method body) (~line 818) — 5-line addition. Stub accessor returningm_atmosphereSkyViewLut.
src/dxvk/rtx_render/rtx_resources.h
Pre-refactor fork footprint: +7 / -0 LOC (audit 2026-04-18)
Category: index-only
-
Inline tweak at
Resourcesclass (public method declarations) (~line 393) — 3-line addition. DeclaresgetAtmosphereTransmittanceLut,getAtmosphereMultiscatteringLut, andgetAtmosphereSkyViewLuton theResourcesclass. -
Inline tweak at
Resourcesclass (private member fields) (~line 469) — 4-line addition. Addsm_atmosphereTransmittanceLut,m_atmosphereMultiscatteringLut, andm_atmosphereSkyViewLutstorage fields toResources.
src/dxvk/rtx_render/rtx_scene_manager.cpp
Pre-refactor footprint: +73 / -2 LOC (migrated 2026-04-18)
Post-refactor footprint: 4 hook call sites + 1 #include "rtx_fork_hooks.h"
-
Hook at
SceneManager::submitExternalDraw(before submesh loop) →fork_hooks::externalDrawMeshReplacementinrtx_fork_submit.cppChecks for USD mesh/light replacements keyed on the API mesh handle hash; call site handles the early-exit +drawReplacementsdispatch since those are private SceneManager methods. 2026-07-29 (upstream sync): the call site's replacement-template setup now goes throughDrawCallState::modifyGeometryData()instead of writingreplacementDrawCall.geometryDatadirectly — upstream movedgeometryDatainto the private section behind that accessor, andSceneManageris not a friend ofDrawCallState(the fork hooks andD3D9Rtxare). Same formodifyMaterialData()at thesetHashOverridesite below. -
Hook at
SceneManager::submitExternalDraw(insideif (material != nullptr), beforesetHashOverride) →fork_hooks::externalDrawMaterialReplacementinrtx_fork_submit.cppChecks for USD material replacements viagetReplacementMaterial()and updates thematerialpointer in-place if one is found. -
Hook at
SceneManager::submitExternalDraw(insideif (material != nullptr), aftersetHashOverride) →fork_hooks::externalDrawTextureCategoriesinrtx_fork_submit.cppResolves albedo texture hash from the API material's opaque data and auto-applies all texture-based instance categories (Sky, Ignore, WorldUI, WorldMatte, Particle, Beam, DecalStatic, Terrain, AnimatedWater, IgnoreLights, IgnoreAntiCulling, IgnoreMotionBlur, Hidden). -
Hook at
SceneManager::submitExternalDraw(after particle setup, beforeprocessDrawCallState) →fork_hooks::externalDrawObjectPickinginrtx_fork_submit.cppStores per-draw texture hash metadata inm_drawCallMetawhen object picking is active. Access to the privatem_drawCallMetamember is granted via afrienddeclaration — see thertx_scene_manager.hentry below.
src/dxvk/rtx_render/rtx_scene_manager.h
Post-refactor fork footprint: forward decl + friend declaration (added 2026-04-18)
Category: index-only
-
Inline tweak at file scope (just before
class SceneManager) — 9-line forward declaration offork_hooks::externalDrawObjectPickingso the friend declaration insideSceneManagercan name the fork-owned hook. Companion to thertx_fork_submit.cpphook that needs private-member access tom_drawCallMeta. -
Inline tweak at
SceneManagerclass body (top of class, beforepublic:) — 5-linefrienddeclaration grantingfork_hooks::externalDrawObjectPickingaccess tom_drawCallMeta. Canonical pattern for hooks that must read/write private upstream state — one inline tweak per such hook, tracked here.
src/dxvk/rtx_render/rtx_sky.h
Pre-refactor fork footprint: +6 / -0 LOC (audit 2026-04-18)
Category: index-only
- Inline tweak at
tryHandleSky(~line 145) — 6-line addition for physical atmosphere sky skip. ReturnsTryHandleSkyResult::SkipSubmitearly for any draw withcameraType == CameraType::Skywhen Numos mode is active, preventing rasterized skybox geometry from being submitted.
src/dxvk/rtx_render/rtx_tone_mapping.cpp
- Hook calls at
DxvkToneMapping::dispatchApplyToneMapping(args-population) andDxvkToneMapping::showImguiSettings(ImGui panel) →fork_hooks::populateTonemapOperatorArgs+fork_hooks::showTonemapOperatorUIinrtx_fork_tonemap.cpp. Routes global tonemap through the fork operator dispatcher.
src/dxvk/rtx_render/rtx_tone_mapping.h
- Inline tweak — remove
rtx.tonemap.finalizeWithACESRtxOption (superseded byrtx.tonemap.tonemapOperatorinrtx_fork_tonemap.cpp); add#include "rtx_fork_tonemap.h". Adopts the fork operator enum.
src/dxvk/shaders/rtx/algorithm/geometry_resolver.slangh
Pre-refactor fork footprint: +171 / -1 LOC (audit 2026-04-18)
Category: migrate
-
Block at
geometryResolverVertex(miss handler — sky radiance branch) — ~30 LOC (active) + ~60 LOC (commented-out deprecated decals-on-sky block), planned targetfork_hooks::geoResolverAtmosphereMissinrtx_fork_atmosphere.slangh. Adds a conditional atmosphere sky-radiance evaluation (evalSkyRadiance) in the geometry-resolver miss path whencb.skyMode == 1, selecting between dome light, physical atmosphere, and skybox rasterization. The commented-out block documents the deprecatedenableDecalsOnSkyfeature. Cloud temporal smoothing (2026-05-09): the primary view ray's evalSkyRadiance call now passesenableCloudTemporalSmoothing=trueplus the motion-vector + screen-extent args needed to reproject and EMA-blend the cloud layer against the previous frame's history at slots 206/207. PSR and indirect callers continue to pass false (their pixelCoord refers to a non-primary direction; reusing primary screen-space cloud history would smear). Sky-matte primary sampling (2026-07-05): in skybox-rasterization mode (skyMode==0, no dome light) camera rays now sample the screen-spaceSkyMatteatcameraPixelCoordinateToScreenUVinstead of theSkyProbecubemap — since the composite-side SkyMatte add was removed (double-sky fix), the resolver write is the visible sky, and the cube parameterization's gradient discontinuity showed as seams along face edges; the matte is the game's own raster for that exact pixel. Rays withdirectionAlteredset (crossed a ray portal, so their direction no longer matches the rasterized view) keep the direction-based probe, as do PSR and indirect misses. -
Block at
geometryResolverVertex(hit path — occluder comment block) — ~42 LOC (fully commented out), planned targetfork_hooks::geoResolverOccluderinrtx_fork_atmosphere.slangh. Preserves the design for the deprecatedisOccludersurface property that would have shown sky behind occluder surfaces; kept commented for future reference. -
Block at
geometryPSRResolverVertex(PSR hit — atmosphere sky radiance) — ~9 LOC, planned targetfork_hooks::geoResolverPsrAtmosphereinrtx_fork_atmosphere.slangh. Adds atmosphere sky-radiance evaluation in the PSR resolver's emissive radiance accumulation path when physical atmosphere mode is active. Volumetric attenuation (2026-07-04): the three sky branches (dome light / Numos atmosphere / SkyProbe) were unified into a commonskyRadiancethat is multiplied bycalcVolumetricAttenuationbefore accumulation, matching the primary-miss path — fixes reflected sky on water being brighter than the directly-visible sky under fog. -
Block at
geometryPSRResolverVertex(PSR hit — occluder comment block) — ~45 LOC (fully commented out), planned targetfork_hooks::geoResolverPsrOccluderinrtx_fork_atmosphere.slangh. Same occluder design-preservation comment block for the PSR path. -
Block at
(file scope)(atmosphere include) — ~4 LOC, planned targetfork_hooks::atmosphereIncludeinrtx_fork_atmosphere.slangh. Adds#include "rtx/pass/atmosphere/atmosphere_common.slangh"at the top of the file, plus#include "rtx/pass/atmosphere/atmosphere_sky.slangh"gated by#ifdef ATMOSPHERE_AVAILABLEfor the sky-radiance evaluation paths.
src/dxvk/shaders/rtx/algorithm/integrator_direct.slangh
Pre-refactor fork footprint: +120 / -2 LOC (audit 2026-04-18)
Category: migrate
-
Block at
(file scope)(atmosphere include) — ~1 LOC, planned targetfork_hooks::atmosphereIncludeinrtx_fork_atmosphere.slangh. Adds#include "rtx/pass/atmosphere/atmosphere_common.slangh". 2026-06-27: moved this include ahead ofrtxcr_material.slanghso the SSS NEE code can callsampleCloudGroundShadow_OptionBfor the SSS cloud-shadow fold (see the rtxcr_material.slangh touchpoint). -
Block at
evalAtmosphereSunNEE(full function) — ~40 LOC, planned targetfork_hooks::evalAtmosphereSunNEEDirectinrtx_fork_atmosphere.slangh. Implements primary-bounce sun NEE for physical atmosphere: samples sun direction + cone angle, traces multiple jittered shadow rays for soft shadows, averages visibility, evaluates BRDF split-weight, and accumulates diffuse/specular sun radiance. As of the 2026-06-19 sun-only cloud-shadow re-architecture this function no longer touches clouds at all — the cloud-on-terrain shadow folds onto the sun's radiance insidesampleAtmosphereSunLight(atmosphere_common.slangh), so the per-pixelPrimaryCloudShadowFactorwrite, the sealed-interior zenith up-ray gate, and the viewmodel/decal/normal-flip origin corrections were all deleted here. 2026-06-20: taught the shadow ray about thin-opaque subsurface — it now traces withvisibilityModeEnableSubsurfaceMaterialsand skips theNdotL <= 0early-out (both the up-front check and the per-sample jitter skip) for thin-opaque materials, so backlit translucency works under the physical-atmosphere sun (it is the only sun NEE in skyMode==1; the SSS-capable standard path only runs for RTXDI/RIS lights). -
Block at
evalAtmosphereMoonNEE(full function) — ~100 LOC, planned targetfork_hooks::evalAtmosphereMoonNEEDirectinrtx_fork_atmosphere.slangh. Primary-bounce moon NEE -- mirror of evalAtmosphereSunNEE for the moon. CallssampleAtmosphereMoonLightwith a u_pick blue-noise sample so one of the enabled, above-horizon moons is importance-picked per ray (weight = brightness × phaseGlow × elevation). Soft-shadow cone jitter viagetJitteredSunDirection(direction-agnostic). Accumulated contribution divided bymoonSample.solidAnglePdf(discrete pick PDF) so multi-moon importance sampling stays unbiased over many frames. Added by 2026-05-07 moon sun-parity workstream. 2026-06-20: same thin-opaque subsurface fix as the sun path (visibilityModeEnableSubsurfaceMaterials+ thin-opaque-gatedNdotL <= 0early-outs) so moonlit translucency matches. -
Block at
integrateDirectPath(atmosphere sun NEE call site) — ~14 LOC, planned targetfork_hooks::directPathAtmosphereSunCallinrtx_fork_atmosphere.slangh. CallsevalAtmosphereSunNEEin the direct-path integrator whencb.skyMode == 1. 2026-06-21 (experiment branch): the gate also requiresdebugSkyBisectFlagsbit 2 clear — when the sun/moon are injected as real distant lights (rtx.atmosphere.useDirectionalLights), bit 2 is set and this bespoke sun+moon NEE block is skipped to avoid double-counting. -
Block at
integrateDirectPath(atmosphere moon NEE call site) — ~12 LOC, planned targetfork_hooks::directPathAtmosphereMoonCallinrtx_fork_atmosphere.slangh. CallsevalAtmosphereMoonNEEimmediately afterevalAtmosphereSunNEEin the direct-path integrator whencb.skyMode == 1. Sun and moon NEE are independent samples -- both can be valid at twilight, both invalid during pure daytime / pure-night-with-no-moons; each early-outs cheaply when invalid. Added by 2026-05-07 moon sun-parity workstream. -
Block at
integrateDirectPath(sky radiance miss branch) — ~8 LOC, planned targetfork_hooks::directPathAtmosphereMissinrtx_fork_atmosphere.slangh. Adds#ifdef ATMOSPHERE_AVAILABLEbranch in the miss sky-radiance evaluation to callevalSkyRadiancein physical atmosphere mode. -
Block at
integrateDirectPath/sampleLightRIScall sites (customIndex for view-model) — ~3 LOC, planned targetfork_hooks::directPathViewModelCustomIndexinrtx_fork_light.slangh. Synthesizes acustomIndexcarryingCUSTOM_INDEX_IS_VIEW_MODELfromgeometryFlags.isViewModeland threads it through toevalDirectLightingcall sites so view-model geometry skipsignoreViewModellights.
src/dxvk/shaders/rtx/algorithm/integrator_indirect.slangh
Pre-refactor fork footprint: +138 / -4 LOC (audit 2026-04-18)
Category: migrate
-
Block at
(file scope)(atmosphere include) — ~4 LOC, planned targetfork_hooks::atmosphereIncludeinrtx_fork_atmosphere.slangh. Adds#include "rtx/pass/atmosphere/atmosphere_common.slangh", plus#include "rtx/pass/atmosphere/atmosphere_sky.slangh"gated by#ifdef ATMOSPHERE_AVAILABLEfor the sky-radiance evaluation in the indirect-path miss handler. -
Block at
evalAtmosphereSunNEESecondary(full function) — ~100 LOC, planned targetfork_hooks::evalAtmosphereSunNEEIndirectinrtx_fork_atmosphere.slangh. Secondary-bounce variant of the atmosphere sun NEE function: uses half the sample count for performance, otherwise identical structure to the direct-path version. -
Block at
evalAtmosphereMoonNEESecondary(full function) — ~100 LOC, planned targetfork_hooks::evalAtmosphereMoonNEEIndirectinrtx_fork_atmosphere.slangh. Secondary-bounce variant of the moon NEE function -- mirror of evalAtmosphereSunNEESecondary. Same structure as the direct-path moon NEE but with the indirect shadow-mask flags, half the sample count, and additivediffuseLight/specularLightaccumulation (no throughput multiplier; caller folds throughput in). Added by 2026-05-07 moon sun-parity workstream. -
Block at
integratePathVertex(atmosphere moon NEE call site) — ~16 LOC, planned targetfork_hooks::indirectPathAtmosphereMoonCallinrtx_fork_atmosphere.slangh. CallsevalAtmosphereMoonNEESecondaryafter the existing sun call whencb.skyMode == 1 && isNeeEnabledOnBounce. Accumulates the returned diffuseLight + specularLight via accumulateRadiance. Added by 2026-05-07 moon sun-parity workstream. -
Block at
integratePathVertex(atmosphere sky radiance in miss) — ~8 LOC, planned targetfork_hooks::indirectPathAtmosphereMissinrtx_fork_atmosphere.slangh. Adds the#ifdef ATMOSPHERE_AVAILABLEsky-radiance branch in the indirect path miss handler. -
Block at
integratePathVertex(secondary bounce atmosphere sun NEE call) — ~18 LOC, planned targetfork_hooks::indirectPathAtmosphereSunCallinrtx_fork_atmosphere.slangh. CallsevalAtmosphereSunNEESecondaryfor secondary bounces when physical atmosphere mode is active and NEE is enabled on the bounce. -
Block at
integratePathVertex(customIndex for view-model lights) — ~4 LOC, planned targetfork_hooks::indirectPathViewModelCustomIndexinrtx_fork_light.slangh. SynthesizescustomIndexfromrayInteraction.isViewModelat both RTXDI and advanced-RIS call sites in the indirect path.
src/dxvk/shaders/rtx/algorithm/lighting.slangh
Pre-refactor fork footprint: +27 / -5 LOC (audit 2026-04-18)
Category: migrate
-
Block at
sampleLightRTXDIsignature + ignoreViewModel filter — ~12 LOC, planned targetfork_hooks::sampleLightViewModelFilterinrtx_fork_light.slangh. Adds acustomIndexparameter (default 0) tosampleLightRTXDIand inserts a guard that returns false when the sampled reservoir light hasignoreViewModelset and the caller'scustomIndexhasCUSTOM_INDEX_IS_VIEW_MODEL. -
Block at
sampleLightAdvancedRISsignature + ignoreViewModel filter — ~10 LOC, planned targetfork_hooks::sampleLightViewModelFilterinrtx_fork_light.slangh. SamecustomIndexparameter andignoreViewModelskip guard in the advanced-RIS sampling loop. -
Block at
sampleLightRISdispatch (propagate customIndex) — ~3 LOC, planned targetfork_hooks::sampleLightViewModelFilterinrtx_fork_light.slangh. Updates thesampleLightRISdispatcher to passcustomIndexthrough tosampleLightAdvancedRIS.
src/dxvk/shaders/rtx/algorithm/rtxcr/rtxcr_material.slangh
Pre-refactor fork footprint: +11 / -4 LOC (audit 2026-04-18)
Category: index-only
-
Inline tweak at
evalSssDiffusionProfile(~line 167) — 3-line addition for view-model customIndex. SynthesizescustomIndexfromgeometryFlags.isViewModeland threads it to the SSS diffusion-profile light-sampling calls. -
Inline tweak at
evalSingleScatteringTransmission(first call site, ~line 323) — 3-line addition for view-model customIndex. Same customIndex pattern for the first single-scattering transmission light sample. -
Inline tweak at
evalSingleScatteringTransmission(second call site, ~line 423) — 3-line addition for view-model customIndex. Same customIndex pattern for the second single-scattering transmission light sample. -
Block at file scope (
sssApplyAtmosphereCloudShadowhelper) + 3 call sites — fork — 2026-06-27. Folds the cloud-on-terrain shadow onto SSS NEE. The opaque direct/indirect integrators fold the per-pixel cloud transmittance onto the flagged atmosphere sun'slightSample.radiance(theatmosphereCloudShadowedreal-light fold), butevalSssDiffusionProfile/evalSingleScatteringTransmissionsample their own lights internally, so the diffusion-profile, transmission, and single-scattering terms stayed fully sunlit under heavy cloud while surrounding opaque diffuse darkened. Adds a file-localsssApplyAtmosphereCloudShadow(inout LightSample, lightIdx, surfacePos)mirroring the integrator fold (sameskyMode==1/cloudVoxelShadowsEnable/atmosphereCloudShadowed/cloudShadowFactorStrengthgating) and calls it at all three SSS light-sample sites. Depends onsampleCloudGroundShadow_OptionBfrom atmosphere_common.slangh, which is why the integrator_direct.slangh include of atmosphere_common was reordered ahead of this header.
src/dxvk/shaders/rtx/concept/light/light.h
Pre-refactor fork footprint: +1 / -0 LOC (audit 2026-04-18)
Category: index-only
- Inline tweak at
DecodedPolymorphicLightstruct (~line 56) — 1-line addition ofignoreViewModelfield. Addsbool ignoreViewModelto the decoded-light struct so the GPU-side light filter can read the flag after decode.
src/dxvk/shaders/rtx/concept/light/polymorphic_light.slangh
Pre-refactor fork footprint: +2 / -0 LOC (audit 2026-04-18)
Category: index-only
- Inline tweak at
decodePolymorphicLight(~line 60) — 1-line addition for ignoreViewModel decode. Extracts bit 1 of the flags word intodecodedPolymorphicLight.ignoreViewModelduring polymorphic-light decode.
src/dxvk/shaders/rtx/concept/surface/surface.h
Category: index-only
- Inline tweak at
Surfaceflags properties (~line 306, afterisVertexColorBakedLighting) — REVERTED 2026-06-19, comment-only. Previously added theSurface::isDecalCategoryproperty (data0b.zbit 2) read by the cloud-shadow zenith gate. Removed with the gate; bit 2 is free again. Only a "freed" comment remains.
src/dxvk/shaders/rtx/pass/common_binding_indices.h
Pre-refactor fork footprint: +9 / -1 LOC (audit 2026-04-18)
Category: index-only
-
Inline tweak at
(file scope)(atmosphere binding index defines) (~line 49) — 3-line addition. DefinesBINDING_ATMOSPHERE_TRANSMITTANCE_LUT(200),BINDING_ATMOSPHERE_MULTISCATTERING_LUT(201), andBINDING_ATMOSPHERE_SKY_VIEW_LUT(202) at high slot numbers to avoid conflicts with pass-specific bindings. -
Inline tweak at
COMMON_BINDING_DEFINITION_LISTmacro (~line 96) — 3-line addition for common-binding list. AddsTEXTURE2Dentries for the three atmosphere LUT bindings to the common-binding definition macro so they appear in all passes that include common_bindings. -
Inline tweak at
(file scope)(atmosphere binding index defines) (~line 56) andCOMMON_RAYTRACING_BINDINGSmacro (~line 103) — Stage C addition. AddsBINDING_ATMOSPHERE_CLOUD_NOISE_3D = 203and a correspondingTEXTURE3Dentry in the macro list for the prebaked 3D cloud noise volume (256³ R8 Perlin). No consumer yet; resource and bake pass land in subsequent Stage C tasks. -
Inline tweak at
(file scope)(atmosphere binding index defines, ~line 57) andCOMMON_RAYTRACING_BINDINGSmacro (~line 104) — Stage C Task 8a addition. AddsBINDING_ATMOSPHERE_CLOUD_NOISE_SAMPLER = 204and a correspondingSAMPLERentry in the macro list. The linear/REPEAT sampler is bound alongside the cloud noise SRV inbindAtmosphereLutsand consumed bysampleCloudDensityTexturedat call sites (Task 8b). -
Inline tweak at
(file scope)(atmosphere binding index defines, ~line 58) andCOMMON_RAYTRACING_BINDINGSmacro (~line 108) — FAST-noise jitter (2026-05-09). AddsBINDING_ATMOSPHERE_FAST_NOISE = 205and a correspondingTEXTURE2DARRAYentry in the macro list. Resource is the EA Importance-Sampled FAST noise (128×128×32 RG8) uploaded once byRtxFastNoiseand bound inbindAtmosphereLuts. Consumed by thefastJitter()helper inatmosphere_common.slanghfor cloud view-march jitter (channel x) and sun-shadow tap jitter (channel y). -
Inline tweak at
(file scope)(atmosphere binding index defines) andCOMMON_RAYTRACING_BINDINGSmacro — cloud history temporal smoothing (2026-05-09, age channel added 2026-05-13). AddsBINDING_ATMOSPHERE_CLOUD_HISTORY_PREV = 206(TEXTURE2D) andBINDING_ATMOSPHERE_CLOUD_HISTORY_CURR = 207(RW_TEXTURE2D). RGBA16F screen-space ping-pong owned byRtxAtmosphereand bound inbindAtmosphereLuts. Consumed byevalSkyRadianceinatmosphere_sky.slanghwhen called withenableCloudTemporalSmoothing=true(currently only the primary view ray ingeometry_resolver.slanghmiss path). Smooths per-frame FAST-noise jitter variance to give DLSS a stable signal. The age-channel companionBINDING_ATMOSPHERE_CLOUD_HISTORY_FRAME_ID_PREV = 212(TEXTURE2D) andBINDING_ATMOSPHERE_CLOUD_HISTORY_FRAME_ID_CURR = 213(RW_TEXTURE2D) carries the frame index at which each pixel was last refreshed by the sky-miss path; R16_UINT, cleared to 0xFFFF "never written" sentinel at allocation. The shader rejects history whose stored frame-id !=(frameIdx - 1) & 0xFFFFu, which fixes the multi-frame bright-trail ghosting that the alpha-only disocclusion guard previously left exposed once the 2026-05-13 Nubis Cubed rewrite drove cloud radiance higher. -
Inline tweak at
(file scope)(atmosphere binding index defines) andCOMMON_RAYTRACING_BINDINGSmacro — cloud voxel grids (Nubis Cubed 2023, 2026-05-12). AddsBINDING_ATMOSPHERE_CLOUD_D_SUN = 210andBINDING_ATMOSPHERE_CLOUD_D_AMBIENT = 211, bothTEXTURE3D. 256x256x32 R16F camera-centered tile-wrapped voxel grids storing summed optical depth along the sun direction (D_sun) and zenith (D_ambient). Round-robin baked every 8 frames bycloud_sun_density_grid.comp.slang/cloud_ambient_density_grid.comp.slangdispatched fromRtxAtmosphere::computeLuts; bound viafork_hooks::bindAtmosphereLuts. Sampled at shade time viasampleDSun/sampleDAmbienthelpers inatmosphere_common.slangh. No consumer in this commit — the Nubis Cubed cloud-lighting rewrite (C4-C6 of the 2026-05-12 workstream) reads them. -
Inline tweak at
COMMON_BINDING_DEFINITION_LISTmacro (~line 91) — 1-line addition for sampler readback buffer. AddsRW_STRUCTURED_BUFFER(BINDING_SAMPLER_READBACK_BUFFER)to the common binding list (upstream omission fixed).
src/dxvk/shaders/rtx/pass/common_bindings.slangh
Pre-refactor fork footprint: +10 / -0 LOC (audit 2026-04-18)
Category: index-only
-
Inline tweak at
(file scope)(atmosphere LUT texture declarations) (~line 118) — 7-line addition. DeclaresAtmosphereTransmittanceLut,AtmosphereMultiscatteringLut, andAtmosphereSkyViewLutasTexture2Dresources bound at the three atmosphere binding slots. -
Inline tweak at
(file scope)(atmosphere FAST-noise texture declaration) (~line 138) — 2-line addition (2026-05-09). DeclaresAtmosphereFastNoiseas aTexture2DArray<float2>resource bound atBINDING_ATMOSPHERE_FAST_NOISE(slot 205). Used by thefastJitter()helper inatmosphere_common.slanghfor cloud ray-march sample-distribution jitter.
src/dxvk/shaders/rtx/pass/composite/composite.comp.slang
Category: migrate
-
Inline tweak at
applySkyContribution(sky-miss composition) — 2026-07-05. Removes the SkyMatteSkyLight.SampleLevelelse-branch on primary miss. Since sync1448e4e77,geometry_resolver.slanghalready writes rasterized sky (skyMode==0; SkyMatte for camera rays / SkyProbe for portal-crossed rays since 2026-07-05) or NumosevalSkyRadiance(skyMode==1) intoSharedRadianceon primary miss; composite was double-counting the same sky via SkyMatte. Commit49229eda4fixed the double sky by removing the geometry_resolver branch, which broke DLSS-RR particle ordering — this composite-side skip restores official83150626efparticle behavior while keeping single sky. Dome-light path unchanged. -
Block at
compositePixel(cloud-shadow application) — REMOVED 2026-06-19, comment-only. The entire screen-space cloud-shadow application in composite is gone. The indirect multiply was removed 2026-06-18 (issue #37, double-count + geometry-blind). The direct multiply (pow(PrimaryCloudShadowFactor, cloudShadowFactorStrength)onto post-denoise primary direct radiance) was removed 2026-06-19 when the cloud shadow was re-architected onto the SUN's radiance pre-denoise insidesampleAtmosphereSunLight— it now darkens only the sun (correct indoors for all surface types, no per-pixel gate). ThePrimaryCloudShadowFactortexture binding is also removed; only a removal comment remains in the shader.
src/dxvk/shaders/rtx/pass/composite/composite_args.h
Category: index-only
- Inline tweak — cloud-shadow CB fields, now both reserved pads. Two former cloud-shadow CB slots are now reserved
float pad1/float pad2(CB layout ABI-unchanged).pad1heldcloudShadowFactorStrengthuntil 2026-06-19, when the cloud shadow moved onto the sun term in the NEE and the composite application was deleted (the knob now lives inatmosphere_args.h::cloudShadowFactorStrength, reusing the formerpad_artistic0slot there).pad2heldcloudShadowIndirectStrengthuntil 2026-06-18 (issue #37 indirect multiply removal). Rename-to-pad rather than delete keeps the slots ABI-stable.
src/dxvk/shaders/rtx/pass/gbuffer/gbuffer.slang
Pre-refactor fork footprint: +16 / -0 LOC (audit 2026-04-18)
Category: index-only
- Inline tweak at
(file scope module-level pragmas)(ATMOSPHERE_AVAILABLE defines) (~lines 35–249) — 16-line addition spread across many pragmas. Adds//!> ATMOSPHERE_AVAILABLESlang module dependency annotation lines so the gbuffer module can access atmosphere functionality when the define is active.
src/dxvk/shaders/rtx/pass/gbuffer/gbuffer_miss.rmiss.slang
Pre-refactor fork footprint: +1 / -0 LOC (audit 2026-04-18)
Category: index-only
- Inline tweak at
(file scope)(~line 44) — 1-line addition. Adds#define ATMOSPHERE_AVAILABLEso the gbuffer miss shader can reference atmosphere evaluation functions.
src/dxvk/shaders/rtx/pass/gbuffer/gbuffer_psr_miss.rmiss.slang
Pre-refactor fork footprint: +1 / -0 LOC (audit 2026-04-18)
Category: index-only
- Inline tweak at
(file scope)(~line 44) — 1-line addition. Adds#define ATMOSPHERE_AVAILABLEso the gbuffer PSR miss shader can reference atmosphere evaluation functions.
src/dxvk/shaders/rtx/pass/integrate/integrate_direct.slang
Category: index-only
- Inline tweak at
(file scope)(~line 36) — 1-line addition. Adds#define ATMOSPHERE_AVAILABLEso the direct-integration pass compiles against the realevalCloudGroundShadowbody. The macro gates a binding-free fallback intended for atmosphere LUT compute shaders that lack the cloud-noise SRV; this pass already includescommon_bindings.slangh(which declaresAtmosphereCloudNoise3D+ sampler), so the fallback over-suppresses cloud shadow on terrain surface NEE. Without this define,evalAtmosphereSunNEE → sampleAtmosphereSunLight → getTransmittanceToSun → evalCloudGroundShadowshort-circuits to1.0and terrain never darkens under clouds regardless ofcloudShadowStrength.
src/dxvk/shaders/rtx/pass/integrate/integrate_indirect.slang
Category: index-only
- Inline tweak at
(file scope)(~line 233) — 1-line addition. Adds#define ATMOSPHERE_AVAILABLEso the indirect-integration pass evaluates the realevalCloudGroundShadowfor secondary-bounce surface NEE (evalAtmosphereSunNEESecondary). Same rationale asintegrate_direct.slang: the cloud-noise SRV is bound viacommon_bindings.slanghhere, so the binding-free fallback is unnecessary.
src/dxvk/shaders/rtx/pass/integrate/integrate_indirect_closesthit.rchit.slang
Category: index-only
- Inline tweak at
(file scope)(~line 241) — 1-line addition. Adds#define ATMOSPHERE_AVAILABLEso the closest-hit variant of indirect integration evaluates the realevalCloudGroundShadow. Same rationale as the siblingintegrate_indirect.slangentry —common_bindings.slanghprovides the cloud-noise SRV.
src/dxvk/shaders/rtx/pass/integrate/integrate_indirect_miss.rmiss.slang
Pre-refactor fork footprint: +1 / -0 LOC (audit 2026-04-18)
Category: index-only
- Inline tweak at
(file scope)(~line 66) — 1-line addition. Adds#define ATMOSPHERE_AVAILABLEso the indirect miss shader can evaluate atmosphere sky radiance on rays that miss all geometry.
src/dxvk/shaders/rtx/pass/raytrace_args.h
Pre-refactor fork footprint: +3 / -0 LOC (audit 2026-04-18)
Category: index-only
-
Inline tweak at
RaytraceArgsstruct (atmosphereArgs + skyMode) (~lines 153, 363) — 2-line addition. AddsAtmosphereArgs atmosphereArgsanduint skyModefields toRaytraceArgsso the atmosphere parameters and sky mode flag are available in all ray-tracing passes via the constant buffer. -
Inline tweak at
(file scope)(atmosphere args include) (~line 35) — 1-line addition. Adds#include "rtx/pass/atmosphere/atmosphere_args.h"soAtmosphereArgsis defined.
src/dxvk/shaders/rtx/pass/rtxdi/restir_gi_reuse_binding_indices.h
Pre-refactor fork footprint: +20 / -20 LOC (audit 2026-04-18)
Category: migrate
- Block at
(file scope)(binding index renumbering) — ~20 LOC replacing 20 LOC, planned targetfork_hooks::restirGiBindingIndicesinrtx_fork_atmosphere.slangh. Renumbers the ReSTIR GI reuse pass binding indices (WORLD_SHADING_NORMAL_INPUT through RESERVOIR_INPUT_OUTPUT) to make room for the three atmosphere LUT bindings at slots 200-202, avoiding conflicts introduced by the common-bindings expansion.
src/dxvk/shaders/rtx/pass/tonemap/aces.slangh
- Fork-owned — new file. ACES operator implementations:
acesHill(Stephen Hill ACES fit) andacesNarkowicz(Krzysztof Narkowicz ACES approximation). Included byfork_tonemap_operators.slangh; dispatched astonemapOperatorACESHill/tonemapOperatorACESNarkowicz. Fork-owned ACES operator implementations.
src/dxvk/shaders/rtx/pass/tonemap/adaptation_v1.slangh
- Fork-owned — new file. Tiny helper namespace
adaptation::v1exposingExponentialBlendandAdaptAsymmetricfor asymmetric exponential eye adaptation (light-tau / dark-tau). Consumed byauto_exposure.comp.slang. Renodx-attributed (Carlos Lopez Jr., MIT 2025). Fork-owned eye-adaptation primitive.
src/dxvk/shaders/rtx/pass/tonemap/agx.slangh
- Fork-owned — new file (renamed from
AgX.hlsl). AgX Minimal display rendering transform by Benjamin Wrensch (MIT 2024) —agxMinimalToneMapping(color, saturation, look). Depends onneutwo.slanghfor the max-channel pre-scale that normalizes HDR input into the curve's [0, 1] domain. Included byfork_tonemap_operators.slangh. Fork-owned AgX operator implementation (Minimal variant).
src/dxvk/shaders/rtx/pass/tonemap/auto_exposure.comp.slang
- Block at
(file scope)— full rewrite of the resolve stage to a perceptual observer model. Reads the log-Yf histogram emitted byauto_exposure_histogram.comp.slang, computes a count-weighted log mean of Yf as the adapted scene level, and derives the target exposure scale from a first-site cone-contrast lawexposure = targetAdaptedYf / (Y_adapt + Y_noise)withY_noise = 0.0032(Stockman & Brainard 2010 cone-system noise floor — caps the dark-scene boost without an arbitrary clamp).targetAdaptedYf(default 0.18, i.e. mid-gray reflectance) is a push-constant sourced fromcb.targetAdaptedYf(RtxOptionrtx.autoExposure.targetAdaptedYf). The raw target is then clamped tocb.maxExposure(RtxOptionrtx.autoExposure.maxExposure, default 8.0) before the temporal blend so the smoother converges to the cap directly. Asymmetric exponential blending then runs in log-exposure space so the time-constants are invariant to absolute scene level. Spatial center-weighting (the previously-per-bin log-Yf Gaussian) was lifted intoauto_exposure_histogram.comp.slang; bin counts arrive pre-weighted so this pass is a plain count-weighted log mean. Replaces the prior Naka-Rushton-in-resolve form (per-bin Gaussian +1 / (L + sigma)). Auto-exposure resolve now shares an achromatic basis (Yf) with the psycho17 tonemap operator and exposes a tunable mid-gray target plus a hard exposure cap.
src/dxvk/shaders/rtx/pass/tonemap/auto_exposure_histogram.comp.slang
- Inline tweak at
inputToHistogramBucket— bin Stockman-Sharpe CIE 170-2 luminosity Yf (viarenodx::tonemap::psycho::yf::from_BT709) instead of BT.709 photometric luminance, so the resolve pass and the psycho17 observer share a single physiological achromatic measure. Also collapses NaN / negative inputs to bin 0 via!(yf >= eps). Adds#include "rtx/pass/tonemap/psycho17.slangh"for the Yf helper. Yf is rescaled bykYfRefWhite(Yf-of-BT.709-white, ~1.0504) so 18% linear reflectance lands at Yf == 0.18 exactly — without itrtx.autoExposure.targetAdaptedYf = 0.18would silently target ~0.171 reflectance. - Inline tweak at
main— replaces the unweightedInterlockedAdd(g_localData[bin], 1)with spatial Gaussian center-weighted metering. Distance is normalised by the shorter screen axis so the falloff is circular in pixel space and the horizontal periphery on 16:9 / 21:9 doesn't pin adaptation on the sky; sigma = 0.25 of the short axis. Per-pixel weight is fixed-point (gauss × 256, floored to 1u) so any pixel still nudges the histogram and the resolve'ssum(weight*logYf) / sum(weight)cancels the scale. Histogram now lives in observer-model space (normalised so 18% reflectance ⇔ Yf 0.18) and meters center-weighted instead of uniformly — adaptation tracks the subject the gaze fixates on rather than the unweighted scene mean.
src/dxvk/shaders/rtx/pass/tonemap/fork_tonemap_operators.slangh
- Fork-owned — new file. Hosts the
applyTonemapOperator(uint op, float3 color, bool suppressBlackLevelClamp, ..., float3 adaptiveStateBT709)dispatcher. Branches on the operator enum and forwards into the operator-specific headers (aces.slangh,hable.slangh,agx.slangh,lottes.slangh,psycho17.slangh,gt7.slangh). The trailingadaptiveStateBT709parameter carries the perceptual-AE observer adaptive state into psycho17'scurrent_adaptive_state_bt709/current_background_state_bt709slots; the other operators ignore it. Fork-owned shader header: operator dispatch lives here so upstream passes shrink to one-line calls.
src/dxvk/shaders/rtx/pass/tonemap/gt7.slangh
- Fork-owned — new file (2026-05-XX). Slang port of the Polyphony Digital "GT7 Tone Mapping" reference (MIT 2025, SIGGRAPH 2025 supplemental). SDR mode, peak hardcoded to 1.0, ICtCp UCS. Wired into the dispatcher via
tonemapOperatorGT7(= 7). Fork-owned GT7 operator implementation.
src/dxvk/shaders/rtx/pass/tonemap/hable.slangh
- Fork-owned — new file. Hable Filmic (Uncharted 2) tonemap operator —
hableFilmicToneMapping(color, exposureBias, A, B, C, D, E, F, W). Split out offork_tonemap_operators.slanghso each operator has its own header. Included byfork_tonemap_operators.slangh. Fork-owned Hable Filmic operator implementation.
src/dxvk/shaders/rtx/pass/tonemap/lottes.slangh
- Fork-owned — new file (renamed from
Lottes.hlsl). Lottes 2016 tonemap operator —lottesToneMapping(color, hdrMax, contrast, shoulder, midIn, midOut). Lottes shares Hable Filmic's 8 param slots in the shader args struct (the two operators are mutually exclusive); slot mapping is documented at the struct definition intonemapping.h. Included byfork_tonemap_operators.slangh. Fork-owned Lottes operator implementation.
src/dxvk/shaders/rtx/pass/tonemap/neutwo.slangh
- Fork-owned — new file. Slang port of the renodx "Neutwo" max-channel pre-scale helper —
neutwo_ComputeMaxChannelScale(color)returns a channel-coherent scale that brings HDR-range input into the [0, 1] curve domain;neutwo_Neutwo(x) = x * rsqrt(x*x + 1)is the underlying saturation kernel. Currently consumed byagx.slangh. Renodx-attributed (Carlos Lopez Jr., MIT 2025). Fork-owned curve-normalization helper.
src/dxvk/shaders/rtx/pass/tonemap/psycho17.slangh
- Fork-owned — new file. Self-contained Slang port of the renodx "Psycho Test 17" operator and its required color-pipeline dependencies (Stockman-Sharpe LMS, CIE 170-2 MacLeod-Boynton + gamut, Naka-Rushton, color grading). Dispatched as
tonemapOperatorPsycho17(UI label:PsychoV17_Beta). Renodx-attributed (Carlos Lopez Jr., MIT 2025). Fork-owned Psycho Test 17 operator implementation.
src/dxvk/shaders/rtx/utility/pq.slangh
- Fork-owned — new file (2026-05-XX). Shared SMPTE ST.2084 (PQ) constants +
PQDecode/PQEncode(vec3, donut-attributed) + scalarpq_eotfSt2084/pq_inverseEotfSt2084(GT7-style, frame-buffer units). Extracted fromtemporal_aa.comp.slangso both the TAA pass and the GT7 tonemap operator can share the same math. Fork-owned shared PQ math.
src/dxvk/shaders/rtx/pass/temporal_aa/temporal_aa.comp.slang
- Inline tweak at the include block + PQ constants/helpers (~lines 22-83) — replaced the inlined PQ constants and
PQDecode/PQEncodedefinitions with#include "rtx/utility/pq.slangh"so the GT7 tonemap operator can share them. The function signatures and constant values are unchanged. PQ helpers extracted to a shared header; donut attribution preserved inutility/pq.slangh.
src/dxvk/shaders/rtx/pass/tonemap/tonemapping.h
- Inline tweak at
(file scope)(operator constants) — addtonemapOperatorNone/tonemapOperatorACESHill/tonemapOperatorACESNarkowicz/tonemapOperatorHableFilmic/tonemapOperatorAgX/tonemapOperatorLottes/tonemapOperatorPsycho17/tonemapOperatorGT7(renamed from the originaltonemapOperatorACES/tonemapOperatorACESLegacyin the 2026-05-XX cleanup;Psycho11was renamed toPsycho17when the operator was replaced with a port of renodx Psycho Test 17 — seesrc/dxvk/shaders/rtx/pass/tonemap/psycho17.slanghfor the MIT attribution to Carlos Lopez Jr. The UI dropdown label isPsychoV17_Beta.tonemapOperatorGT7was added 2026-05-XX for the Polyphony Digital GT7 SDR port — seesrc/dxvk/shaders/rtx/pass/tonemap/gt7.slangh). - Inline tweak at
ToneMappingAutoExposureArgs— doc comment updated to describe the new perceptual pipeline (log2-Yf histogram + geometric mean + first-site cone-contrast law + log-space asymmetric blend) instead of the previous BT.709-luminance + Gaussian + Naka-Rushton-in-resolve description. Fields: 2 of the 3 trailinguint padslots were replaced withfloat targetAdaptedYf(mid-gray adaptation target, sourced fromrtx.autoExposure.targetAdaptedYf) andfloat maxExposure(hard ceiling on the auto-exposure multiplier, sourced fromrtx.autoExposure.maxExposure); 1uint pad0remains to preserve the 20-byte struct size. - Inline tweak at
ToneMappingApplyToneMappingArgsstruct — swapfinalizeWithACES/useLegacyACESuints fortonemapOperator+ per-operator param blocks (Hable, AgX, Psycho17). The vestigialdirectOperatorModefield and the legacy histogram / tone-curve bindings (TONEMAPPING_HISTOGRAM_*,TONEMAPPING_TONE_CURVE_*,TONEMAPPING_APPLY_TONEMAPPING_TONE_CURVE_INPUT) and the structsToneMappingHistogramArgs/ToneMappingCurveArgswere removed in the 2026-05-XX cleanup.static_assert(sizeof(...) == 176)pins the current struct size: AgX block is 16 B (saturation + look + 2 pad floats) since the AgX Minimal operator only consumes those two fields; Psycho17 block is 64 B (14 floats + 2 trailing pad floats for 16-byte alignment). Global tonemap shader-shared header adopts the operator enum.
src/dxvk/shaders/rtx/pass/tonemap/tonemapping_apply_tonemapping.comp.slang
-
Inline tweak at
applyToneMapping— replaceif (cb.finalizeWithACES) { color = ACESFilm(color, cb.useLegacyACES); }withcolor = applyTonemapOperator(cb.tonemapOperator, color, false, ..., adaptiveStateBT709);. Add#include "rtx/pass/tonemap/fork_tonemap_operators.slangh". The 2026-05-XX cleanup also stripped the dead helpers (reinhardToneMapper,filmicToneMapper,dynamicToneMapper,lumaAverage,setSaturationAverage) and theInToneCurvebinding.adaptiveStateBT709is the observer adaptive state in post-AE-exposure BT.709 space — currently(0.18, 0.18, 0.18)because the perceptual auto-exposure brings the geometric-mean scene Yf to mid-gray; consumed by psycho17, ignored by other operators. Global apply pass routes through the fork dispatcher for operator selection. -
Inline tweak at
applyToneMapping(dither gate) — fork — 2026-06-27 (experimental). Addscb.performSRGBConversion > 0 &&to theenableDitheringargument of the inlineditherTo8Bitcall. The fork's tonemap refactor defers sRGB conversion + dithering to the dedicatedsrgb_ditherpass, calling the tonemapper withperformSRGBConversion=false. The inline dither was left active, so it added its ±0.5/255 perturbation in LINEAR space; the laterlinearToGammainsrgb_ditheramplifies that ~13x in the near-black sRGB toe, producing a heavy boiling grain visible only in dark regions (plus a redundant double-dither with the dedicated pass). Gating onperformSRGBConversiondisables the inline dither in the current pipeline (the gamma-spacesrgb_ditherpass owns dithering) and keeps it correct should the tonemapper ever do its own sRGB encode. VALIDATED in-game 2026-06-27 — dark-region grain gone.
src/dxvk/shaders/rtx/pass/volume_args.h
Category: index-only
- Inline tweak at the
VolumeArgsstruct tail (replaces thevec2 pad0slot) — 1 LOC delta (2026-05-26). Addsfloat fogSunVisibilityGain;consuming 4 bytes of the existing 8-bytepad0slot;pad0shrinks tofloat pad0;to preserve 16-byte struct alignment. Consumed by the submodule fork edit atrtxdi-sdk/include/volumetrics/rtx/algorithm/volume_composite_helpers.slangh(named knob replaces the previous hardcoded* 10.0fartistic gain at the fog-render consumer site). Companions:rtx_global_volumetrics.{h,cpp}.
src/dxvk/rtx_render/rtx_fork_atmosphere.cpp
Category: fork-owned (modifications by weather preset workstream)
Note: This is a fork-owned file. It is listed here because the weather
preset workstream added a call site inside showAtmosphereUI(), extending
the fork-owned ImGui surface.
-
Inline tweak at
fork_hooks::showAtmosphereUI(weather UI call site) — ~1 LOC. Callsfork_hooks::showWeatherUI()between the Moons and Clouds collapsing-header tree blocks so the Weather Presets panel appears in the correct visual position in the Atmosphere dev menu. -
Block (anonymous namespace) + call from
fork_hooks::updateAtmosphereConstants(directional sun/moon lights — experiment, branchexperiment/atmosphere-directional-sun, 2026-06-21) — ~180 LOC. Whenrtx.atmosphere.useDirectionalLightsis on and skyMode==Numos, injects the sun + each enabled moon as externally-trackedRtDistantLights (viaLightManager::createExternallyTrackedLight/updateExternallyTrackedLight) so they go through the standard NEE/RTXDI path and SSS/decals/viewmodels are handled by the unified pipeline. Radiance is the CPU port ofsampleAtmosphereSunLight/sampleAtmosphereMoonLight÷ π (distant-light irradiance convention); transmittance is the CPU port of the closed-formgetAtmosphericTransmittanceForDirso sunset reddening is preserved. The bespokeevalAtmosphereSunNEE/MoonNEEare gated off viadebugSkyBisectFlagsbit 2. KNOWN v1 limitation: no cloud-on-terrain shadow.
src/dxvk/rtx_render/rtx_fork_hooks.h
Category: fork-owned (forward declaration additions)
Note: This is a fork-owned file. It is listed here because the weather
preset workstream added two new forward declarations to the fork_hooks
namespace block.
- Inline tweak at
fork_hooksnamespace block (forward declarations) — ~2 LOC. Addsvoid updateWeatherBlender(class RtxContext& ctx, float deltaTimeSeconds)andvoid showWeatherUI()forward declarations. These allowrtx_context.cppandrtx_fork_atmosphere.cppto call the weather hook without including the fullrtx_fork_weather.hheader at those call sites.
submodules/rtxdi/rtxdi-sdk/include/volumetrics/rtx/algorithm/volume_integrator.slangh
Category: submodule (fork-controlled — RemixProjGroup/RTXDI branch remix)
Note: This file lives in the fork-controlled RTXDI submodule. Edits land
via PR/commit to RemixProjGroup/RTXDI branch remix and then a sibling
dxvk-remix commit bumps the submodule pointer (mirror of 96c56d5). The
audit script scripts/audit-fork-touchpoints.sh does NOT inspect submodule
files; this entry exists for rebase-safety / human discoverability.
-
Inline tweak at the top of the file (atmosphere helper include) — 1-line addition. Adds
#include "rtx/pass/atmosphere/atmosphere_common.slangh"so the per-froxel atmosphere-sun NEE block and the sky-ambient hemisphere integration block can call atmosphere helpers (sampleAtmosphereSunLightVolume,sampleSkyAmbientForVolume,hgPhase). -
REMOVED 2026-06-28 — the bespoke atmosphere sun NEE block (
cb.skyMode == 1, ~35 LOC, originally CattaRappa RTXDI commit2ff8c57) that built anAtmosphereVolumeSunSampleviasampleAtmosphereSunLightVolume, traced its own visibility ray, and added the sun toradianceSH. Why removed: since the directional-light graduation the atmosphere sun/moons are real RemixRtDistantLights sampled by the volume NEE/ReSTIR loop like any light (no distant-light exclusion —rtx_light_manager.cppgives every non-empty typevolumeRISSampleCount ≥ 1), so this block double-counted the sun into the froxel cache (once via NEE, once here) with mismatched phase/visibility math — making volumetric fog impossible to balance (the user had to crankfogSunVisibilityGainto 0.01 and it still looked wrong; issue #35). This mirrors the earlier removal of the bespoke surface sun NEE.atmosphereSunVolumetricRadianceScale(which scaled only this block) was left behind with no consumer and was removed 2026-07-26: the option, itsAtmosphereArgsslot (nowpadRetired11), the "Atmosphere Sun Fog Scale" ImGui slider, and theatmosphereSunFogScaleweather-preset field across all 12 presets. Usertx.volumetrics.fogSunVisibilityGainto scale fog in-scattering. -
Inline addition 2026-06-28 —
applyAtmosphereCloudShadowVolume(inout RAB_LightSample, uint lightIdx, vec3 froxelWorldPos)helper + one call in each NEE branch (ReSTIR + non-ReSTIR fallback) ofintegrateVolume, beforeevalNEE. Preserves cloud-on-terrain shadowing on volumetric god-rays now that the bespoke block (which applied it) is gone. Mirrors the surface fold inintegrator_direct.slangh: whenskyMode == 1 && cloudVoxelShadowsEnableand the sampled light'satmosphereCloudShadowedflag (distant-light GPU flag bit 2) is set, multiplies the light sample's radiance bypow(sampleCloudGroundShadow_OptionB(froxelPos, dirToLight, …), cloudShadowFactorStrength). Guards againstRTXDI_INVALID_LIGHT_INDEX. Reuses only bindings the removed block already required (lights[], atmosphere helpers via the file's existingATMOSPHERE_AVAILABLEdefine). -
Inline tweak at the end of
integrateVolume(sky-ambient hemisphere integration block,cb.skyMode == 1 && cloudSkyAmbientStrength > 0) — ~50 LOC. (Workstream 2026-05-12.) Fixed 6-direction upper-hemisphere integration (zenith + 5 mid-elevation at 30° elevation, 72° azimuth spacing) ofsampleSkyAmbientForVolume(dir, args, AtmosphereSkyViewLut, AtmosphereCloudSkyTransmittanceLut, sampler)weighted by HG phase against the volumetric anisotropy (0.3). Results scaled bycloudSkyAmbientStrength, firefly-filtered, and stored as a single SH entry with zenith as the dominant direction. Gated oncb.skyMode == 1and on the strength knob being > 0 so the baseline ships with zero behavior change (cloudSkyAmbientStrengthdefault = 0). Consumes the sky-view LUT (slot 202), cloud-sky-transmittance LUT (slot 208), and the cloud-noise sampler (slot 204 — REPEAT, correct on azimuth, never sampled below-horizon). Seedocs/superpowers/specs/2026-05-12-volumetric-sky-ambient-design.md.
Commit C4 — Cloud render compute pass + Nubis Cubed equations (fork — 2026-05-12)
The C4 commit of the 2026-05-12 cloud-lighting workstream lands the per-
pixel screen-space cloud raymarch with the Nubis Cubed 2023 lighting
equations (paper pp. 137, 142) and a debug view (enum 876) for standalone
A/B against the existing analytical evalClouds rendering. No production
consumer yet — the sky-miss composite still calls analytical clouds; the
composite gate lands in C5.
-
src/dxvk/shaders/rtx/pass/atmosphere/cloud_render.comp.slang— new file (fork-owned). Per-pixel view-direction raymarch through the cloud slab using the Nubis Cubed lighting equations. Reconstructs viewDir from CPU-pushed Y-up basis vectors (cloudRenderForwardYUp/RightYUp/UpYUpinAtmosphereArgs, pre-scaled by tan(halfFovX/Y) and aspect). Intersects the curvature-adjusted base/top cloud shells (intersectSphere) to get [tEntry, tExit]; marches with per-pixel FAST-noise jitter. At each density-passing sample callsevalNubisCubedSamplefor the page-137 two-HG-lobe direct term + page-142 ambient exp(-D_ambient). Writes premultiplied rgb + view-ray transmittance alpha to AtmosphereCloudRender at slot 209. -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h— fork-owned additions. Adds 8 floats of Nubis Cubed lighting params (cloudPhaseG1/G2,cloudMsSunDotMax,cloudMsSigmaShallow/Deep,cloudMsSdfDepth) +cloudRenderFrameIdx+ pad; plus 3 × (vec3 + pad) for the cloud-render camera basis (cloudRenderForwardYUp,cloudRenderRightYUp,cloudRenderUpYUp). All consumed exclusively bycloud_render.comp.slang; the basis is pushed CPU-side fromupdateAtmosphereConstantsbeforecomputeLutsruns so the values land in m_constantsBuffer in time. -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_common.slangh— fork-owned additions. Adds five Nubis Cubed lighting helpers (~120 LOC) right aftersampleDAmbient:sampleDimProfile(proxy oncloudTypeProfile),sampleCloudSdf(slab-distance + density-weighted-depth proxy, returns negative-inside meters clamped to [-cloudMsSdfDepth4, 0]),hgPhaseNubis(paper-flavored HG with denom guard — distinct from the existinghgPhaseto avoid perturbing non-Nubis callers),NubisCubedLightingstruct, andevalNubisCubedSample(the paper-page-137 two-HG-lobe direct term + page-142 ambient exp(-D_ambient)). CallssampleDSun/sampleDAmbientfrom C1.* -
src/dxvk/shaders/rtx/pass/common_binding_indices.h— index-only, fork. AddsBINDING_ATMOSPHERE_CLOUD_RENDER_RT = 209and aTEXTURE2Dentry inCOMMON_RAYTRACING_BINDINGS. Slot 209 was reserved between 208 (cloud-sky-transmittance LUT) and 210 (cloud D_sun); fills the gap. -
src/dxvk/shaders/rtx/pass/common_bindings.slangh— index-only, fork. DeclaresTexture2D<float4> AtmosphereCloudRenderat slot 209 with a 6-line comment block. Consumed by the cloud render RT debug view (enum 876) and — in C5 — by the sky-miss composite path. -
src/dxvk/rtx_render/rtx_atmosphere.h— fork-owned additions. Addsm_cloudRenderRT(Resources::Resource),m_cloudRenderExtent(VkExtent2D),m_cloudRenderForwardYUp/RightYUp/UpYUp(Vector3),m_cloudRenderFrameIdx(uint32_t); plus public methodsgetCloudRenderRT(),ensureCloudRenderRT(ctx, downscaleExtent),setCloudRenderCameraBasis(forward, right, up, frameIdx); plus privatedispatchCloudRender(ctx). -
src/dxvk/rtx_render/rtx_atmosphere.cpp— fork-owned additions. Adds#include <rtx_shaders/cloud_render.h>, theCloudRenderShaderManagedShader class (7-slot binding parameter list),ensureCloudRenderRT(resize-aware alloc of RGBA16F at downscale extent),setCloudRenderCameraBasis(member-state setter),dispatchCloudRender(rebuilds the args buffer, binds the 7 slots, dispatches 8×8 thread groups), populates the 6 Nubis Cubed lighting fields + 3-basis-vector camera fields + frameIdx intoAtmosphereArgsingetAtmosphereArgs(), callsdispatchCloudRender(ctx)fromcomputeLutsafter the voxel grid bakes, and binds slot 209 (BINDING_ATMOSPHERE_CLOUD_RENDER_RT) inbindResources(which mirrors the activebindAtmosphereLutssite). -
src/dxvk/rtx_render/rtx_options.h— fork-owned additions. Adds 6RTX_OPTIONdeclarations in thertx.atmospherecluster:cloudPhaseG1(default 0.8),cloudPhaseG2(0.3),cloudMsSunDotMax(0.9),cloudMsSigmaShallow(0.25),cloudMsSigmaDeep(0.05),cloudMsSdfDepth(128.0 meters). All surface as ImGui sliders in the Nubis Cubed Lighting collapsing block. -
src/dxvk/rtx_render/rtx_fork_atmosphere.cpp— fork-owned additions. InupdateAtmosphereConstants: reads RtCamera basis (forward/right/up + position) + fov + aspect, applies isZUp swap, pre-scales right/up by tan(halfFovX/Y) and aspect ratio, and pushes viasetCloudRenderCameraBasisbeforecomputeLuts. Also callsensureCloudRenderRTwith the current downscale extent. InbindAtmosphereLuts: adds the cloud render RT bind at slot 209. Adds newgetCloudRenderRT(ctx)accessor for the debug view. Adds a "Nubis Cubed Lighting (fork — 2026-05-12)" ImGui collapsing header inside the Clouds tree with 6 sliders mapping to the 6 new RTX_OPTIONs. -
src/dxvk/rtx_render/rtx_context.h— fork-touchpoint inline tweak. Adds forward declarationResources::Resource fork_hooks::getCloudRenderRT(RtxContext&)and a matchingfriendline insideclass RtxContext. Mirrors the existing getCloudDSun / getCloudDAmbient pattern. -
src/dxvk/shaders/rtx/utility/debug_view_indices.h— index-only, fork. AddsDEBUG_VIEW_CLOUD_RENDER_RT = 876with a 4-line comment block. -
src/dxvk/shaders/rtx/pass/debug_view/debug_view.comp.slang— fork-owned addition. Adds a[[vk::binding]]-decoratedTexture2D<float4> DebugViewCloudRenderRTdeclaration and acase DEBUG_VIEW_CLOUD_RENDER_RTarm in the main switch that samples the RT via Load and returns its rgb (alpha is the view-ray transmittance, not relevant to the standalone debug view). -
src/dxvk/shaders/rtx/pass/debug_view/debug_view_binding_indices.h— index-only, fork. (Inventory substitution: not listed in the Task 4 spec, but structurally required for the debug view case to access the cloud render RT — mirrors the D_sun/D_ambient pattern at slots 35/36.) AddsDEBUG_VIEW_BINDING_CLOUD_RENDER_RT_INPUT = 37. -
src/dxvk/rtx_render/rtx_debug_view.cpp— fork-owned addition. Adds aTEXTURE2D(DEBUG_VIEW_BINDING_CLOUD_RENDER_RT_INPUT)line in the debug-view shader's BEGIN_PARAMETER block, binds the cloud render RT each dispatch viafork_hooks::getCloudRenderRT, and adds a label + multi-line description block to the debug-view selector list ("Atmosphere: Cloud Render RT (Nubis Cubed)").
Commit C5 — Sky-miss composite of cloud RT (gated, default-off) (fork — 2026-05-12)
The C5 commit wires the Nubis Cubed cloud render RT (from C4) as the
primary-ray sky-miss cloud source, gated by a default-off RTX_OPTION
(cloudRenderRTEnable). With the gate off, rendering is bit-identical
to pre-C5 — analytical evalClouds continues to run at every site.
With the gate on, primary-ray sky-miss reads from the prerendered RT
while indirect, PSR, and reflection rays continue to use analytical
clouds (the RT is at primary-ray pixel coordinates, sampling it for a
non-primary ray direction would return the wrong cloud).
-
src/dxvk/rtx_render/rtx_options.h— fork-owned addition. AddsRTX_OPTION("rtx.atmosphere", bool, cloudRenderRTEnable, false, …)in thertx.atmospherecluster directly after the C4 Nubis Cubed lighting options. Default false; flipped on in C7 after visual gate. -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h— fork-owned addition. Addsuint cloudRenderRTEnableplus threeuintpads at the end ofAtmosphereArgsfor 16-byte alignment. Sits after the C4 cloud-render camera basis block; no existing field offsets change. -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_sky.slangh— fork-owned addition. InevalSkyRadiance: adds a trailing default-falsebool isPrimaryRayparameter, and a primary-ray-only branch that readsAtmosphereCloudRender.Load(int3(pixelCoord, 0))and inverts its transmittance alpha into opacity (vec4(rgb, 1 - cloudRT.a)) so the downstream temporal-smoothing / mix composite operates uniformly on either source. Gate isargs.cloudRenderRTEnable != 0u && isPrimaryRay. Falls through to analyticalevalCloudswhen the gate is off OR the caller is non-primary. -
src/dxvk/shaders/rtx/algorithm/geometry_resolver.slangh— fork-touchpoint inline tweak. Primary sky-miss call site (cb.skyMode == 1block, formerly ending at thehistoryResolutionargument) now passes/*isPrimaryRay=*/ trueas the trailing argument toevalSkyRadiance. The PSR call site (line ~2553) keeps its 5-argument call shape and getsisPrimaryRay=falsevia default. ~1 LOC. -
src/dxvk/rtx_render/rtx_atmosphere.cpp— fork-owned addition. IngetAtmosphereArgs()(right after the C4 camera-basis populate block): setsargs.cloudRenderRTEnablefromRtxOptions::cloudRenderRTEnable()and zeros the three pad slots. ~5 LOC. -
src/dxvk/rtx_render/rtx_fork_atmosphere.cpp— fork-owned addition. Adds a "Master gate (C5)" separator +RemixGui::Checkbox("Composite cloud RT at sky-miss", …)widget at the end of the "Nubis Cubed Lighting" ImGui collapsing header (just below the MS SDF Depth slider). Wired toRtxOptions::cloudRenderRTEnableObject(); tooltip explains the primary-ray-only behavior. ~8 LOC.
Commit C6 — Voxel-grid cloud-on-terrain shadows at NEE (gated) (fork — 2026-05-12)
The C6 commit wires the C3 helper sampleCloudGroundShadow_OptionB into the
production surface and volumetric NEE entry points via a multiplicative
ratio correction that replaces the legacy evalCloudGroundShadow
uniform-dimmer with the rich 3D D_sun voxel-grid lookup. Terrain
gains cumulus-shaped drifting shadow patches that match the cloud
positions overhead. Gated on a default-off RTX_OPTION
(cloudVoxelShadowsEnable).
This commit also fixes two pre-existing concerns in the C3 helper that
the diagnostic surfaced: (1) units mismatch — the helper assumed
worldPos was in km, but the G-buffer feeds it in engine game units; the
helper now converts via the new worldUnitsPerKm field. (2)
camera-relative-vs-world-absolute frame — the voxel grid is
camera-centered, so the helper now subtracts the camera position (pushed
CPU-side via the new setCloudShadowCameraPosition setter) before the
cloudVoxelWorldToUVW call. The wire-in is intentionally at the NEE
entry points (NOT at getTransmittanceToSun) so the sentinel-position
getTransmittanceToSun call from computeGroundReflectionAnalytical
continues to consume the legacy uniform-dimmer shadow — preserving the
cloud-shadow-map post-mortem's hard-won correctness invariant.
-
src/dxvk/rtx_render/rtx_options.h— fork-owned addition. Adds twoRTX_OPTIONdeclarations in thertx.atmospherecluster directly after the C5cloudRenderRTEnable:cloudVoxelShadowsEnable(default false) andcloudShadowMarchStrength(default 1.0). The strength knob is the Beer-Lambert exponent multiplier insidesampleCloudGroundShadow_OptionB; the C3 commit had to substitute a literal because the field didn't exist on main. -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h— fork-owned addition. Adds two 16-byte rows at the end ofAtmosphereArgsafter the C5 block:(cloudVoxelShadowsEnable, cloudShadowMarchStrength, worldUnitsPerKm, pad_c6_0)and(cameraWorldPosYUpKm.xyz, pad_c6_1). All consumed exclusively bysampleCloudGroundShadow_OptionB; the camera world position is pushed CPU-side fromupdateAtmosphereConstantsmirroring the existingsetCloudRenderCameraBasispattern. -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_common.slangh— fork-owned additions. Two changes. (1) InsidesampleCloudGroundShadow_OptionB_impl: replaces the C3 unit-naive math with aworldPos * (1 / worldUnitsPerKm)game-units → km conversion, acloudEntryPosKm - args.cameraWorldPosYUpKmcamera-relative reframe beforecloudVoxelWorldToUVW, and foldsargs.cloudShadowMarchStrengthinto the Beer-Lambert exponent (replacing the C3 literal). The legacycloudShadowStrengthmix at the end is preserved. (2) Adds an#ifdef ATMOSPHERE_AVAILABLE-gated ratio correction block inside bothsampleAtmosphereSunLight(surface NEE, afterresult.radianceis set) andsampleAtmosphereSunLightVolume(volumetric NEE, afterresult.radianceis set) that — whenargs.cloudVoxelShadowsEnable != 0u— divides out theevalCloudGroundShadowcontribution baked into the analytical path and multiplies in thesampleCloudGroundShadow_OptionBresult. Skips the correction when the old shadow is below 0.001 to guard against divide-by-zero. -
src/dxvk/shaders/rtx/pass/volumetrics/volume_integrate.comp.slang— fork-owned addition. Adds#define ATMOSPHERE_AVAILABLEbefore thecommon_bindings.slanghinclude so the atmosphere helpers consumed by the volumetric pass (sampleAtmosphereSunLightVolume → sampleCloudGroundShadow_OptionB, plus the existingsampleSkyAmbientForVolume,sampleDSun,fastJitter) resolve to bound globals. Matches the#definealready present inintegrate_direct.slangandintegrate_indirect.slang. -
src/dxvk/shaders/rtx/pass/volumetrics/volume_restir.comp.slang— fork-owned addition. Same#define ATMOSPHERE_AVAILABLEaddition asvolume_integrate.comp.slang; the four ReSTIR-stage variants (INITIAL / VISIBILITY / TEMPORAL / SPATIAL_REUSE) all need the cloud voxel-grid bindings available for the per-froxel atmosphere-sun NEE path. -
src/dxvk/rtx_render/rtx_atmosphere.h— fork-owned additions. AddsVector3 m_cameraWorldPosYUpKm(default zero) member and publicsetCloudShadowCameraPosition(Vector3)setter to support the per-frame push of the camera world position fromfork_hooks::updateAtmosphereConstantsahead ofcomputeLuts. Mirrors the existingsetCloudRenderCameraBasisplumbing. -
src/dxvk/rtx_render/rtx_atmosphere.cpp— fork-owned additions. ImplementssetCloudShadowCameraPosition(member-state setter). IngetAtmosphereArgs()(right after the C5 sky-miss-composite block): populates the four new C6 fields — gate + strength from RTX_OPTIONs,worldUnitsPerKm = 100000 * sceneScale(canonical conversion), andcameraWorldPosYUpKmfrom the cached member. -
src/dxvk/rtx_render/rtx_fork_atmosphere.cpp— fork-owned additions. InupdateAtmosphereConstants(immediately aftersetCloudRenderCameraBasis): reads the camera world position in game units, applies the sametoYUpswap used for the basis vectors, converts to km viakmPerWorldUnit = 1 / (100000 * sceneScale), and pushes viasetCloudShadowCameraPosition. In the "Nubis Cubed Lighting" ImGui collapsing block (after the Master gate (C5) section): adds a "Cloud-on-terrain shadows (C6)" separator + checkbox bound tocloudVoxelShadowsEnableObject()+ aDragFloatslider bound tocloudShadowMarchStrengthObject()with tooltips for both. ~25 LOC total.
Workstream — Cloud system slides 1+3 lift + samplePos parallax fix (fork — 2026-05-15)
Three lifts from "Real-Time Rendering of Volumetric Clouds in Red Dead Redemption 2" (Bauer et al., SIGGRAPH 2019) sit on top of the shipped Nubis Cubed pipeline, plus a one-line bug fix that unblocks all three visually:
- Slide 3 — Cloud height LUT. 64×128 RG8 baked once at startup
by
cloud_height_lut_baker.comp.slang. R = per-altitude density envelope multiplier. G = per-altitude coverage threshold scale (the lever with visible silhouette teeth — lowers the coverage gate near cumulus tops to widen the mushroom-cap horizontally). Sampled bycloud_render.comp.slangvia the newcloudHeightProfileFullhelper inatmosphere_common.slangh; the proceduralcloudTypeProfileis the fallback for the voxel grid bakers and the analytical evalClouds path. - Slide 1 — Two-layer cloud map. The per-pixel march body is
extracted into a
marchCloudSlabhelper with slab altitude / thickness / type / coverage / density-scale parameters. Layer 1 (primary cumulus) marches first; layer 2 (cirrus deck by default) marches after with residual transmittance. Voxel-grid shadows, ground-shadow NEE, and moon shadows remain layer-1-only — cirrus is optically thin enough that the precompute cost isn't justified. - Schneider15 Worley carve. Three new periodic-3D-Worley helpers
in
atmosphere_common.slanghfeed the noise bake. Worley FBM is subtracted from the Perlin base to carve cell silhouettes (chunky cauliflower cumulus instead of smooth Perlin pancakes). samplePosparallax fix. The per-step world position was being computed asviewDirYUp * t(camera implicitly at origin), gluing the noise field to the camera and letting only wind translate it. AnchoringsamplePostoargs.cameraWorldPosYUpKmgives real parallax between near and far parts of the volume as the player moves through the world. The three lifts above were inert without this; together they restore the depth cues that make clouds read as 3D volumes instead of cardboard cutouts translating across the sky.
-
src/dxvk/shaders/rtx/pass/atmosphere/cloud_height_lut_baker.comp.slang— fork-only addition. New one-shot bake compute pass. 8×8 thread groups over a 64×128 R8G8 RWTexture2D. R channel emits the per-type density envelope (trapezoid + Gaussian anvil bump for type > 0.6); G channel emits the per-type coverage threshold scale (1.0 = no effect, drops to ~0.30 at hf ≈ 0.80 for cumulus). Curves are tuned so type values 0 / 0.5 / 1 land close to the proceduralcloudTypeProfileshape — keeps default-on visual parity with pre-LUT scenes. -
src/dxvk/shaders/rtx/pass/atmosphere/cloud_render.comp.slang— fork-owned additions. Adds two binding slots (10 =Texture2D<float2>height LUT, 11 =SamplerStatelinear/CLAMP); definesATMOSPHERE_CLOUD_HEIGHT_LUT_AVAILABLEbefore the common header socloudHeightProfileFullresolves to LUT samples. Extracts the per-pixel raymarch into amarchCloudSlab(slabAltKm, slabThickKm, slabTypeMean, slabCoverageMean, slabDensityScale, ctx, args, inout accumColor, inout viewTransmittance)helper plus aCloudShadeContextstruct bundling the sun/moon/sky precomputes.main()calls the helper once for layer 1 (always) and once for layer 2 (whenargs.cloudLayer2Enable != 0u). AnchorssamplePos = args.cameraWorldPosYUpKm + viewDirYUp * t(parallax fix). The "trivially clear sky" early-out widens tomax(layer1, layer2)coverage so a cirrus-only preset doesn't get short-circuited. -
src/dxvk/shaders/rtx/pass/atmosphere/rtx_cloud_noise_baker.comp.slang— fork-owned additions. Adds aworleyFbm3DPeriodic(worldPosKm * worleyFreq, worleyOctaves, 5.0f, basePeriodWorley)tap alongside the existing Perlin base + detail FBM, withworleyFreq/worleyOctavesfromargs.cloudWorleyFrequency/cloudWorleyOctaves. Output issaturate(baseDensity - worley * args.cloudWorleyCarveStrength)instead of the old smooth Perlin sum. -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_common.slangh— fork-owned additions. Adds three periodic-3D-Worley helpers (worleyFeaturePoint3D,worleyNoise3DPeriodic,worleyFbm3DPeriodic) used only by the bake. AddscloudHeightProfileFull(vec2) +sampleCloudHeightLUTgated byATMOSPHERE_CLOUD_HEIGHT_LUT_AVAILABLE, with a procedural fallback via the existingcloudTypeProfile. ExtendssampleCloudDensityTexturedandsampleCloudDensityForShadowwith slab-parametric overloads (slab altitude / thickness / density-scale) plus thin args-default wrappers preserving the original signature; the slab-parametric versions apply the LUT G channel to the coverage-threshold step and the LUT R channel to the density envelope. Also switches the Y texcoord in both density samplers from slab-relative to isotropic/ args.cloudNoiseTileKm(correctness alignment with the isotropic bake). -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h— fork-owned additions. Adds two 16-byte rows after the C6 camera block:(cloudHeightLutEnable, cloudLayer2Enable, cloudLayer2Altitude, cloudLayer2Thickness)and(cloudLayer2TypeMean, cloudLayer2CoverageMean, cloudLayer2DensityScale, pad_cloudLayer2_0), plus one row for Worley(cloudWorleyCarveStrength, cloudWorleyFrequency, cloudWorleyOctaves, pad_cloudWorley_0). -
src/dxvk/rtx_render/rtx_atmosphere.cpp— fork-owned additions. Adds#include <rtx_shaders/cloud_height_lut_baker.h>, theCloudHeightLutBakerShaderManagedShader class (singleRW_TEXTURE2D(0)), anddispatchCloudHeightLutBake(one-shot, called frominitialize()right afterdispatchCloudNoise3DBake). Allocatesm_cloudHeightLut(64×128 R8G8_UNORM) increateLutResources. IndispatchCloudRender: creates a linear/CLAMPheightLutSampler, binds slot 10 (LUT view) + slot 11 (sampler), and adds the resource-tracking line. IngetAtmosphereArgs: populates the height LUT toggle + six layer-2 fields + three Worley fields from RTX_OPTIONs. -
src/dxvk/rtx_render/rtx_atmosphere.h— fork-owned additions. AddsResources::Resource m_cloudHeightLutmember,getCloudHeightLut()accessor,dispatchCloudHeightLutBakedeclaration, andkCloudHeightLutWidth = 64/kCloudHeightLutHeight = 128constants. -
src/dxvk/rtx_render/rtx_options.h— fork-owned additions. Adds 10 new RTX_OPTIONs in thertx.atmospherecluster:cloudHeightLutEnable(default on),cloudLayer2Enable(default off) + 5 layer-2 tuning floats, andcloudWorleyCarveStrength(0.6) /cloudWorleyFrequency(1.0) /cloudWorleyOctaves(3). The Worley trio is tagged "CHANGE APPLIES ON GAME RELAUNCH" since the noise bake is one-shot at init. -
src/dxvk/rtx_render/rtx_fork_atmosphere.cpp— fork-owned additions. Adds Height LUT (1 checkbox), Layer 2 (1 checkbox + 5 sliders), and Worley carve (2DragFloat+ 1DragInt) ImGui subsections inside the existing Clouds tree, each with tooltips describing the slide source and the relaunch requirement where applicable.
Workstream — Multiscattering: preset-faithful default + physical-blend knob (fork — 2026-05-26)
Owen's 3e37062b (fix(atmosphere): fix multiscattering to match reference two-term model) on canonical replaced the numerical hemisphere integration in computeMultiscattering with an analytical-only fit using heavily blue-biased coefficients (vec3(0.217, 0.347, 0.594) * 0.02), and switched evalAtmosphereRadiance to sample the multiscattering LUT instead of calling the inline analytical helpers. Two consequences:
- Cloud-vs-sky color mismatch at sunset.
cloud_render.comp.slangreads the sky-view LUT at the sun direction as the warm ambient source for cumulus. The new bake pumped extra blue energy into the LUT at every elevation, so the cumulus ambient term lost its warm tint and read white against an orange sky. - Preset color washed across all defaults. With the LUT consumed by
evalAtmosphereRadiance, the hemisphere integration's wavelength bias amplified each preset's Rayleigh into the sky (Earth too blue, Desert blue-ish, Mars desaturated). The previously-inlinegetAnalyticalMultiscatteringwas a tame curve fit that the presets were calibrated against.
Fork resolution: restore the numerical hemisphere integration in the LUT bake AND keep the inline analytical multiscattering as the default in evalAtmosphereRadiance, with a per-preset knob to blend in the LUT-based physical version when realism is wanted.
-
src/dxvk/shaders/rtx/pass/atmosphere/multiscattering_lut.comp.slang— restoration to pre-3e37062bstate. RestorescomputeMultiscatteringto the 64-direction × 20-march-sample hemisphere integration (Hillaire 2nd-order scattering primary term) plus ground reflection plus analytical fit. RestorescomputeAnalyticalMultiscatteringcoefficients to the toned-downvec3(0.35, 0.38, 0.45) × 0.01("more neutral, less blue-heavy") tuning that prevents purple cast when combined with sunset orange. -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_common.slangh— fork-owned inline tweak. InsideevalAtmosphereRadiance's per-sample loop: replaces the post-3e37062bLUT-only multiscattering with alerp(contribAnalytical, contribLut, args.multiScatterPhysicalStrength)blend.contribAnalyticalcalls the existing inlinegetAnalyticalMultiscattering(preset-faithful, the pre-3e37062bshape);contribLutsamples the multiscattering LUT (the now-restored hemisphere integration). Default 0.0 = byte-identical to pre-3e37062b; 1.0 = full physical. -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h— fork-owned addition. Replaces the existinguint pad2slot at the end of the LUT-dims 16-byte row withfloat multiScatterPhysicalStrength. No struct layout change; existing field offsets unchanged. -
src/dxvk/rtx_render/rtx_options.h— fork-owned addition. AddsRTX_OPTION("rtx.atmosphere", float, multiScatterPhysicalStrength, 0.0f, …)immediately aftersunIlluminancein thertx.atmospherecluster. -
src/dxvk/rtx_render/rtx_atmosphere.cpp— fork-owned addition. InsidegetAtmosphereArgs(): setsargs.multiScatterPhysicalStrength = RtxOptions::multiScatterPhysicalStrength(). Removes the now-staleargs.pad2 = 0write (slot is the new typed field). ~2 LOC net. -
src/dxvk/rtx_render/rtx_fork_atmosphere.cpp— fork-owned addition. Adds aRemixGui::DragFloat("Multiscatter Physical Strength", …, 0.0f, 1.0f, "%.2f", sliderFlags)widget at the end of the Atmosphere → Advanced ImGui tree (right after Ozone Layer Width), with a tooltip explaining the artistic-vs-physical tradeoff. ~6 LOC.
Workstream — Cloud realism: edge detail erosion, bottom darkening, vertical stretch (fork — 2026-06-10)
Three perceptual cloud upgrades, all live-tunable, all reverting to the prior look at their zero/identity values. (1) Edge detail erosion — a second tap of the prebaked Worley-carved noise volume at cloudDetailScale× the base frequency perturbs the density field before the coverage gate, displacing the silhouette iso-surface with 60–500 m cauliflower teeth while saturated cores stay solid. (2) Bottom darkening — a vertical light gradient on the Nubis Cubed multi-scatter and ambient terms; the paper's M term barely attenuates (sigma_ms ≈ 0.05–0.25) and pow(1 - dim_profile, 0.5) is top/bottom symmetric, so undersides previously read uniformly lit. The direct beam is exempt so silver linings survive. (3) Vertical stretch — lowers the Y noise-sample frequency relative to horizontal so cloud bodies become convective columns rather than round blobs (towering cumulus). New args ride in repurposed pad slots; constant-buffer layout unchanged.
-
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_common.slangh— fork-owned additions. InsampleCloudDensityTextured: step 4b applies the detail-erosion tap pre-threshold (density += strength * 0.6 * (detailNoise - 0.6), erosion-biased), and the texcoord mapping divides Y frequency bycloudVerticalStretch.sampleCloudDensityForShadowapplies the identical Y-stretch (the baked D_sun / D_ambient grids must describe the same shapes the view-march renders) but skips the detail tap (cheap path; un-eroded grids over-shadow edges conservatively). InevalNubisCubedSample:bottomGradient = mix(1 - cloudBottomDarkening, 1, smoothstep(0, cloudBottomDarkeningHeight, heightFraction))multiplies the multi-scatterMandL_ambientterms only. -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h— fork-owned additions. Repurposespad_cloudVoxel0..2ascloudBottomDarkening/cloudBottomDarkeningHeight/cloudDetailStrength,pad_nubisCubed0ascloudDetailScale, andpad_cloudLayer2_0ascloudVerticalStretch. No layout change (same pattern ascloudNoiseWarpStrength). -
src/dxvk/rtx_render/rtx_options.h— fork-owned additions. Adds 5 RTX_OPTIONs to thertx.atmospherecluster:cloudDetailStrength(0.6),cloudDetailScale(4.3),cloudBottomDarkening(0.55),cloudBottomDarkeningHeight(0.65),cloudVerticalStretch(1.6). -
src/dxvk/rtx_render/rtx_atmosphere.cpp— fork-owned additions. InsidegetAtmosphereArgs(): populates the five new args from RtxOptions, replacing the former pad zero-writes. ~5 LOC net. -
src/dxvk/rtx_render/rtx_fork_atmosphere.cpp— fork-owned additions. Adds "Edge Detail" + "Vertical Stretch"DragFloats to the Clouds → Coverage & Shape ImGui section and "Bottom Darkening" to Clouds → Lighting. The fine-tune knobs (cloudDetailScale,cloudBottomDarkeningHeight) stay user.conf-only per the 2026-05-19 menu-simplification discipline.
Workstream — Cloud march/bake perf pass + vertical-coherence rework (fork — 2026-06-10)
Performance pass on the per-frame cloud noise evaluation, each piece validated in-game via a step-by-step bisect ladder after an earlier all-at-once landing broke the cloudscape (root causes: an uncompensated warp-amplitude loss and config drift contaminating the first bisect — see the cloud-realism memory notes). Also replaces the rev-1 cloudVerticalStretch Y-domain stretch (stacked-puffs artifact) with a fixed-slice coherence blend, shipped EXPERIMENTAL/default-inert because its look (vertical smearing at high values) isn't approved either.
-
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_common.slangh— fork-owned additions. (1) Anti-tile warp refactored into offset form (computeCloudNoiseWarpOffsetKm) and HOISTED to once per ray in the two voxel-grid bake integrals (was 4 perlin evals per tap × 8 taps × 2.1M voxels × 2 grids per frame; the warp's ~25 km shortest wavelength makes per-tap recompute pure waste).sampleCloudDensityForShadowgains awarpOffsetKmcaller-provided parameter. (2) Warp octaves switched from constant-zperlinNoise3Dplanes to newperlinNoise2D(4 corners, branch-free gradient pick, ~2.5× cheaper) withkWarp2DGain = 1.4compensating the unit-vs-sqrt(2) gradient amplitude — the uncompensated version visibly weakened the warp's load-bearing field shred. (3) The per-sample 720 cycles/km analytic perlin detail is removed (1.4 m wavelength vs ≥100 m steps = unresolvable grain), preserving its hidden −0.05 mean bias as a constant. (4)cloudVerticalStretchrev 2: fixed-Y-slice coherence blend with variance re-expansion anchored on the bake's ~0.4 mean, in BOTH density samplers so the self-shadow grids match. -
src/dxvk/shaders/rtx/pass/atmosphere/cloud_render.comp.slang— fork-owned additions. Coverage/type Worley control fields hoisted out of the march loop: evaluated at slab entry + exit (4 evals/ray), lerped per step (was 2 × 9-hash evals per step × 32 steps). -
src/dxvk/rtx_render/rtx_options.h— fork-owned addition.cloudVerticalStretchdefault 1.6 → 1.0 (bit-exact identity) and description rewritten to mark the feature EXPERIMENTAL pending a sky-system-level solution to towering cumulus.
Workstream — Secondary-ray cloud LUT (fork — 2026-06-10, perf)
Every indirect / PSR / reflection ray reaching sky-miss previously ran the full analytical evalClouds march in evalSkyRadiance — a hidden per-ray cost estimated to rival the visible cloud pass. Those rays now sample a 256×128 RGBA16F dome LUT (azimuth × elevation, horizon-concentrated elevation = (π/2)·v²) baked once per frame with the same Nubis Cubed march the visible cloud RT uses. Deliberate look change: secondary rays now see the same clouds the primaries see instead of the legacy Wrenninge analytical approximation; rtx.atmosphere.cloudSecondaryLutEnable = False restores the legacy per-ray march. Cloud parallax across scene-scale ray-origin offsets is negligible versus km-scale cloud distances — the same camera-anchored approximation the per-frame voxel grids already make.
-
src/dxvk/shaders/rtx/pass/atmosphere/cloud_march_common.slangh— NEW fork-owned file. Shared cloud-march library extracted fromcloud_render.comp.slang:CloudShadeContext+buildCloudShadeContext(the pixel-independent sun/sky/moon precompute that previously lived in main()),sampleCloudSunOpticalDepth_local,marchCloudSlab(with generictMinClamp/tMaxClampsegment params; 0/0 reproduces the pre-extraction march bit-for-bit), and themarchCloudLayerslayer-1+2 wrapper. Included afteratmosphere_common.slanghunder the established binding-then-include layout. -
src/dxvk/shaders/rtx/pass/atmosphere/cloud_secondary_lut.comp.slang— NEW fork-owned file. Per-frame dome LUT bake. Bindings 0–11 in lockstep withcloud_render.comp.slang(slot 6 is its own RW output). Marches the full slab per dome texel atcloudViewSamplessteps via the sharedmarchCloudLayers; clear-sky early-out mirrorscloud_render; frame-constant per-texel jitter keeps the LUT temporally stable. -
src/dxvk/shaders/rtx/pass/atmosphere/cloud_render.comp.slang— fork-owned changes. March implementation moved tocloud_march_common.slangh; main() now builds the shade context viabuildCloudShadeContextand callsmarchCloudLayerswith 0/0 clamps — behavior-neutral extraction, bit-identical output. -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_common.slangh— fork-owned addition.cloudDomeUvToDir/cloudDomeDirToUvdome-direction mapping shared by the LUT writer and theevalSkyRadiancesampler (lives here because the sample side does not include the march library). -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_sky.slangh— fork-owned addition. New middle branch inevalSkyRadiance's cloud-layer selection: non-primary rays withcloudSecondaryLutEnablesampleAtmosphereCloudSecondaryLutviacloudDomeDirToUv+ the sky-view sampler (REPEAT-U wraps the azimuth seam) and reconcile alpha exactly like the existing cloud-RT branch (opacity = 1 − transmittance). Below-horizon directions clamp to the horizon row. -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h— fork-owned addition. Repurposespad_c5_0asuint cloudSecondaryLutEnable. No layout change (same pattern ascloudNoiseWarpStrength). -
src/dxvk/shaders/rtx/pass/common_binding_indices.h— upstream-touched, inline tweak (~12 LOC). AddsBINDING_ATMOSPHERE_CLOUD_SECONDARY_LUT 215next to the other fork atmosphere bindings and appendsTEXTURE2D(BINDING_ATMOSPHERE_CLOUD_SECONDARY_LUT)toCOMMON_RAYTRACING_BINDINGS. -
src/dxvk/shaders/rtx/pass/common_bindings.slangh— upstream-touched, inline tweak (~10 LOC). DeclaresTexture2D<float4> AtmosphereCloudSecondaryLutat the new binding, alongside the other fork atmosphere declarations. -
src/dxvk/rtx_render/rtx_options.h— fork-owned addition. AddsRTX_OPTION("rtx.atmosphere", bool, cloudSecondaryLutEnable, true, …)directly before the C5cloudRenderRTEnable. -
src/dxvk/rtx_render/rtx_atmosphere.h/rtx_atmosphere.cpp— fork-owned additions.CloudSecondaryLutShaderclass,m_cloudSecondaryLutresource (256×128 RGBA16F) +getCloudSecondaryLut()accessor +kCloudSecondaryLut*constants,dispatchCloudSecondaryLut(per-frame, after the voxel-grid bakes behind the existing write→read barrier, gated on the option),getAtmosphereArgspopulates the gate from RtxOptions, andbindResourcesbinds the new slot. -
src/dxvk/rtx_render/rtx_fork_atmosphere.cpp— fork-owned additions.fork_hooks::bindAtmosphereLutsbinds the LUT atBINDING_ATMOSPHERE_CLOUD_SECONDARY_LUT; adds the "Fast Cloud Reflections" checkbox to the Clouds ImGui tree.
Workstream — Half-res cloud render RT (fork — 2026-06-11, perf)
The visible cloud march ran once per DLSS-input pixel at up to 32 steps. Clouds are soft, low-frequency content, so the cloud RT is now allocated at cloudRenderResolutionScale (default 0.5) of the downscale extent and bilinearly upsampled at the sky-miss composite — ~4× fewer marched pixels at the default. The temporal-smoothing path runs after the upsample at full downscale resolution, so its stabilization is unchanged. Scale 1.0 lands the sample uv on texel centers of a same-size RT and matches the legacy Load to float precision (live A/B via the "Cloud Render Scale" slider).
-
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_sky.slangh— fork-owned change. The primary-ray cloud-RT branch inevalSkyRadiancenormalizes pixelCoord byargs.cloudRenderFullDimX/Yand bilinearly samples the RT via the sky-view sampler (uv clamped a half-texel inside so screen content never wraps through REPEAT-U); falls back to the legacyLoadwhile the published extent is still zero (first frames). -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h— fork-owned addition. Repurposespad_c5_1/2ascloudRenderFullDimX/Y(the downscale extent the RT is composited into). No layout change. -
src/dxvk/rtx_render/rtx_options.h— fork-owned addition. AddsRTX_OPTION("rtx.atmosphere", float, cloudRenderResolutionScale, 0.5f, …). -
src/dxvk/rtx_render/rtx_atmosphere.h/rtx_atmosphere.cpp— fork-owned additions.ensureCloudRenderRTrecords the full downscale extent in newm_cloudRenderFullExtentand allocates the RT at the clamped scale (a live scale change reallocates via the existing extent-mismatch path);getAtmosphereArgspublishes the full extent. Dispatch group math already keys off the (now scaled)m_cloudRenderExtent. -
src/dxvk/rtx_render/rtx_fork_atmosphere.cpp— fork-owned addition. Adds the "Cloud Render Scale" DragFloat to the Clouds ImGui tree.
Workstream — Cloud noise hex de-tiling, stage A (fork — 2026-06-11)
Root-cause fix for the prebaked noise volume's periodic repeat (the artifact the anti-tile warp was added to hide). The horizontal plane is partitioned into an equilateral-triangle lattice (golden-ratio multiple of the tile period, so lattice and texture can't resonate); each vertex carries a hash-derived random texture-space XZ offset, and samples blend their triangle's three offset taps with Heitz & Neyret 2018's variance-preserving operator (mean/variance of the field preserved → look unchanged; periodicity destroyed). Weights are pow-4 sharpened so triangle interiors (~70% of area) take a 1-tap fast path; only edge bands pay 3 trilinear taps. Ships alongside the UNCHANGED anti-tile warp — stage B walks the warp down with bake-frequency compensation after in-game validation (the warp's frequency-multiplying Jacobian is load-bearing for the look; see the cloud-realism memory notes).
-
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_common.slangh— fork-owned additions. NewCloudHexTile/computeCloudHexTile(skewed triangle grid + per-vertex hash offsets + pow-4 weight sharpening + dominant-vertex sort),kCloudNoiseFieldMean(0.4 — shared anchor with the vertical-coherence re-expansion), andsampleCloudNoiseHexBlend(3-tap variance-preserving blend).sampleCloudDensityTextured's base tap branches: hex fast path = tricubic at the dominant offset, blend zones = 3-tap trilinear blend, off = legacy tricubic.sampleCloudDensityForShadowmirrors the same lattice/offsets/blend trilinearly so the baked D_sun / D_ambient grids shadow the same de-tiled field. The vertical-coherence footprint slice follows the dominant vertex's offset in both samplers. The detail-erosion tap stays single-tap (its sub-3 km repeat is not the visible artifact; revisit at stage B). -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h— fork-owned addition. RepurposespadCloudLook0asfloat cloudHexTilingEnable. No layout change. -
src/dxvk/rtx_render/rtx_options.h— fork-owned addition. AddsRTX_OPTION("rtx.atmosphere", bool, cloudHexTilingEnable, true, …)beforecloudNoiseWarpStrength. -
src/dxvk/rtx_render/rtx_atmosphere.cpp— fork-owned addition.getAtmosphereArgspopulates the gate from RtxOptions (replacing the former pad zero-write). -
src/dxvk/rtx_render/rtx_fork_atmosphere.cpp— fork-owned addition. Adds the "Seamless Cloud Field" checkbox to the Clouds ImGui tree, next to the Anti-Tile Warp slider it will eventually retire.
Workstream — Cloud noise hex de-tiling, stage B enabler (fork — 2026-06-11)
Exposes the bake's base/detail FBM frequency as a live-rebake multiplier so the anti-tile-warp walk-down can be tuned interactively: the warp's Jacobian multiplies the field's horizontal frequency 2-3x and the tuned look depends on that shred, so as cloudNoiseWarpStrength comes down, cloudNoiseBaseFreqScale goes up to fold equivalent frequency content into the bake itself. Default 1.0 = bit-identical bake; the actual walk-down is an in-game tuning session, not a code change.
-
src/dxvk/shaders/rtx/pass/atmosphere/rtx_cloud_noise_baker.comp.slang— fork-owned change.baseFreq/detailFreqbecome0.5 / 2.0 × clamp(args.cloudNoiseBaseFreqScale, 0.25, 4)(4x ratio preserved); the periodic-lattice snapping already handles non-default products. -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h— fork-owned addition. RepurposespadCloudLook1asfloat cloudNoiseBaseFreqScale. No layout change. -
src/dxvk/rtx_render/rtx_options.h— fork-owned addition. AddsRTX_OPTION("rtx.atmosphere", float, cloudNoiseBaseFreqScale, 1.0f, …). -
src/dxvk/rtx_render/rtx_atmosphere.h/rtx_atmosphere.cpp— fork-owned additions.getAtmosphereArgspopulates the new field;needsCloudNoiseRebake/cacheCloudNoiseBakeInputsgain the new input (m_cachedBaseFreqScale) so dragging the slider re-bakes the volume live. -
src/dxvk/rtx_render/rtx_fork_atmosphere.cpp— fork-owned addition. Adds the "Noise Frequency" DragFloat next to the Anti-Tile Warp slider with the walk-down pairing explained in its tooltip.
Workstream — Cloud noise hex de-tiling, stage B landing (fork — 2026-06-11)
The in-game warp walk-down validated cleanly at warp 0 with the bake frequency left at its default 1.0 — with hex-tiling killing the repeat at the source, no frequency compensation turned out to be needed. The warp default is retired accordingly; the knob survives as a pure organic-distortion effect. Warp 0 also takes the early-out in applyCloudNoiseAntiTileWarp, refunding 4 perlin2D evaluations per march sample.
src/dxvk/rtx_render/rtx_options.h— fork-owned change.cloudNoiseWarpStrengthdefault 0.75 → 0.0; doc strings for it andcloudNoiseBaseFreqScaleupdated to describe the post-retirement roles (warp = optional look knob, hex-tiling owns de-tiling).
Workstream — Anti-tile warp removal (fork — 2026-06-11)
Follow-up to the stage B landing: with the warp retired at 0 and hex de-tiling user-validated as the root-cause fix, the entire warp implementation is dead code and is removed. All deleted code was fork-owned; no upstream lines return.
-
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_common.slangh— fork-owned removal. DeletesperlinGradient2D/perlinNoise2D(warp-only helpers),computeCloudNoiseWarpOffsetKm/applyCloudNoiseAntiTileWarp, the warp call insampleCloudDensityTextured, thewarpOffsetKmparameter from bothsampleCloudDensityForShadowoverloads, and the per-ray warp hoists in the D_sun / D_ambient bake integrals. -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h— fork-owned change.cloudNoiseWarpStrengthreverts to padding (padCloudC2, its original slot) — CB layout unchanged. -
src/dxvk/rtx_render/rtx_options.h— fork-owned removal. Drops thecloudNoiseWarpStrengthRTX_OPTION;cloudHexTilingEnable/cloudNoiseBaseFreqScaledoc strings scrubbed of warp pairing references. -
src/dxvk/rtx_render/rtx_atmosphere.cpp— fork-owned change.getAtmosphereArgszeroespadCloudC2instead of populating the warp strength. -
src/dxvk/rtx_render/rtx_fork_atmosphere.cpp— fork-owned removal. Drops the "Anti-Tile Warp" slider from the Clouds ImGui tree; "Seamless Cloud Field" / "Noise Frequency" tooltips scrubbed of warp references. -
src/dxvk/shaders/rtx/pass/atmosphere/rtx_cloud_noise_baker.comp.slang— comment-only cleanup (walk-down rationale removed).
Workstream — Sky perf: split sky-LUT cache keys (fork — 2026-06-11)
First change of the non-cloud sky optimization workstream. The three sky LUT bakes (transmittance / multiscatter / sky-view) shared one memcmp gate over the whole normalized AtmosphereArgs, with two per-frame failure modes: the game-driven sidereal starRotation (pushed every frame at night, feeds no LUT bake) re-baked the full cascade every frame, and a moving time-of-day sun re-baked the heavy transmittance + multiscatter pair (multiscatter alone: 32×32 texels × 64 dirs × 20 steps of transmittance taps) even though neither reads sun direction. Each bake now compares a key normalized down to the fields it actually reads. Gated by skyLutCacheKeySplitEnable (default on; legacy single-gate path preserved verbatim for A/B).
-
src/dxvk/rtx_render/rtx_options.h— fork-owned addition. AddsRTX_OPTION("rtx.atmosphere", bool, skyLutCacheKeySplitEnable, true, …)next to the other atmosphere perf gates. -
src/dxvk/rtx_render/rtx_atmosphere.h/rtx_atmosphere.cpp— fork-owned additions. NewnormalizeForSkyViewLutKey(base normalize + star / Milky Way fields zeroed) andnormalizeForTransmittanceMsKey(additionally zeroes sun direction / illuminance / disk size, Mie g, MS blend weight, and all moon fields), cached asm_cachedSkyViewKey/m_cachedTransmittanceMsKey.computeLutsgates the transmittance+MS pair and the sky-view bake independently (tms-dirty implies sky-view-dirty; barrier ordering unchanged); both branches keep the other path's caches coherent so the option can be toggled live without a spurious re-bake. -
src/dxvk/rtx_render/rtx_fork_atmosphere.cpp— fork-owned addition. Adds the "Minimal Sky LUT Re-bakes" checkbox to the Atmosphere → Advanced ImGui tree.
Note: rtx.atmosphere.cloudNoiseWarpStrength lines in existing user confs become unknown-option no-ops after this change.
Workstream — Sky perf: per-dispatch bisect toggles (fork — 2026-06-11, diagnostic)
Measurement aid for the sky perf workstream. The atmosphere pass runs several per-frame dispatches that no production option can skip — dispatchCloudRender in particular runs whenever its RT is valid, independent of cloudRenderRTEnable — so frame-time A/B via the production toggles mis-attributes their cost. Three default-ON debugDispatch* toggles let a live ImGui session skip each dispatch individually and read the frame-time delta. Skipping leaves consumers reading stale data (frozen clouds / shadows): diagnostic only, defaults unchanged rendering.
-
src/dxvk/rtx_render/rtx_options.h— fork-owned additions.debugDispatchCloudVoxelGrids/debugDispatchCloudRender/debugDispatchCloudSkyTransmittance, all default true. -
src/dxvk/rtx_render/rtx_atmosphere.cpp— fork-owned change.computeLutswraps the D_sun + D_ambient bakes, the cloud render dispatch, and the cloud-sky-transmittance bake in their respective toggles (barriers move inside the gates; ordering unchanged when enabled). -
src/dxvk/rtx_render/rtx_fork_atmosphere.cpp— fork-owned addition. New "Perf Bisect (Diagnostic)" ImGui tree with the three checkboxes + pointers to the existing production toggles useful in the same session.
Workstream — Sky perf: NEE shadow-ray budget clamps (fork — 2026-06-11)
Third change of the sky perf workstream — the first to target where the milliseconds actually are. Bisect results showed all atmosphere compute dispatches together ≈ the known 2 ms cloud budget; the remaining ~2.7 ms (day) / ~3.7 ms (night) of the skyMode A/B delta lives in the integrators: with physical atmosphere on, sun NEE traces an anisotropy-driven 1–12 soft-shadow visibility rays per primary pixel (getSunSoftShadowParams; ~4 at typical Mie g) plus half that per indirect bounce vertex, and moon NEE adds a constant 4 (+2 indirect) at night. The denoised pipeline temporally converges one blue-noise-jittered ray per frame, so the loops are oversampled. Two clamp knobs cap the counts at the single source (sampleAtmosphereSunLight / sampleAtmosphereMoonLight), so primary and the half-rate secondary paths both inherit. Defaults 0 = legacy uncapped (bit-identical); set 1 for the perf win after visual validation.
-
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h— fork-owned change. RepurposespadMoonNee0/padMoonNee1asuint sunShadowMaxSamples/uint moonShadowMaxSamples. No layout change. -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_common.slangh— fork-owned additions. Clamp applied aftergetSunSoftShadowParamsinsampleAtmosphereSunLightand after the constant-4 assignment insampleAtmosphereMoonLight; 0 = uncapped. -
src/dxvk/rtx_render/rtx_options.h— fork-owned additions.RTX_OPTION("rtx.atmosphere", int, sunShadowMaxSamples, 0, …)/moonShadowMaxSamples. -
src/dxvk/rtx_render/rtx_atmosphere.cpp— fork-owned change.getAtmosphereArgspopulates the two fields (replacing the pad zero-writes). -
src/dxvk/rtx_render/rtx_fork_atmosphere.cpp— fork-owned addition. "Sun Shadow Ray Cap" / "Moon Shadow Ray Cap" DragInts in the Atmosphere → Advanced ImGui tree.
Workstream — Sky perf: shader-path bisect toggles (fork — 2026-06-11, diagnostic)
Extends the per-dispatch bisect kit into the integrators: with the dispatch costs known (~1.8 ms ≈ cloud budget) and the NEE rays capped, the residual skyMode delta splits across two shader paths that host-side toggles can't reach. Two default-true diagnostic options pack into one CB gate word (debugSkyBisectFlags, former padMoonNee2 slot — layout unchanged): bit 0 skips atmosphere sun+moon NEE at both integrator call sites (sun/moon lighting goes black), bit 1 makes evalSkyRadiance return flat grey immediately (isolates the full per-ray miss-path cost; the skipped temporal-history write self-heals on re-enable via the frame-id age check). Defaults = bits clear = production paths, bit-identical.
-
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h— fork-owned change. RepurposespadMoonNee2asuint debugSkyBisectFlags. No layout change. -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_sky.slangh— fork-owned addition. Flat-grey early return at the top ofevalSkyRadiancebehind bit 1. -
src/dxvk/shaders/rtx/algorithm/integrator_direct.slangh/integrator_indirect.slangh— fork-owned change (upstream files, one-line condition edits). Thecb.skyMode == 1atmosphere-NEE gates additionally require bit 0 clear. -
src/dxvk/rtx_render/rtx_options.h— fork-owned additions.debugEnableAtmosphereNee/debugEnableSkyMissShading, both default true. -
src/dxvk/rtx_render/rtx_atmosphere.cpp— fork-owned change.getAtmosphereArgspacks the two options into the flag word. -
src/dxvk/rtx_render/rtx_fork_atmosphere.cpp— fork-owned addition. "Atmosphere Sun/Moon NEE" / "Full Sky Miss Shading" checkboxes in the Perf Bisect (Diagnostic) ImGui tree.
Follow-up (same day): debugDispatchSkyLuts (rtx_options.h, default true) freezes the whole LUT bake cascade — the last untoggleable GPU piece of the atmosphere; with a moving time-of-day sun the sky-view LUT re-bakes every frame by design, and this measures that. computeLuts short-circuits the gate block when off (rtx_atmosphere.cpp); "Sky LUT Bakes" checkbox added to the Perf Bisect tree (rtx_fork_atmosphere.cpp).
Workstream — Sky perf: sky-view re-bake granularity (fork — 2026-06-11)
Production fix for the per-frame sky-view re-bake the frozen-cascade bisect identified (objective frame-time improvement, no visual hit). With a continuously-animating time-of-day sun, the sky-view cache key sees a new sunDirection every frame. skyViewRebakeGranularityDeg (default 0 = legacy every-frame behavior; recommended 0.1) quantizes the sun and moon directions INSIDE the sky-view cache key, so continuous motion flips the memcmp only when a direction crosses a granularity step — the bake itself always uses exact current values, so each re-bake is exact and staleness between re-bakes is bounded by the step angle. All non-direction fields stay exact (sliders / presets re-bake immediately). Applies to the split-key path (skyLutCacheKeySplitEnable, on by default).
-
src/dxvk/rtx_render/rtx_options.h— fork-owned addition.RTX_OPTION("rtx.atmosphere", float, skyViewRebakeGranularityDeg, 0.0f, …). -
src/dxvk/rtx_render/rtx_atmosphere.cpp— fork-owned addition.quantizeDirComponenthelper;normalizeForSkyViewLutKeyquantizessunDirection+ all moon directions by the option's step (no-op at 0). The transmittance/MS key is unaffected (it zeroes those fields outright). -
src/dxvk/rtx_render/rtx_fork_atmosphere.cpp— fork-owned addition. "Sky Re-bake Granularity" DragFloat in the Atmosphere → Advanced ImGui tree.
Workstream — Sky perf: pin validated defaults + retire workstream UI (fork — 2026-06-11)
Closing commit of the sky perf workstream. In-game validation confirmed the two production levers (sun/moon NEE shadow-ray caps at 1, sky-view re-bake granularity at 0.1°) deliver the measured wins with no visual hit, so their defaults are pinned and the workstream's ImGui surface is retired per the user's call ("this is in a good enough spot now"). All options remain conf-tunable; only the UI is removed.
-
src/dxvk/rtx_render/rtx_options.h— fork-owned change. Defaults:sunShadowMaxSamples0 → 1,moonShadowMaxSamples0 → 1,skyViewRebakeGranularityDeg0.0 → 0.1. Doc strings note the validation; 0 still selects legacy behavior via conf. -
src/dxvk/rtx_render/rtx_fork_atmosphere.cpp— fork-owned removal. Drops from the UI: the "Minimal Sky LUT Re-bakes" checkbox, the "Sun/Moon Shadow Ray Cap" DragInts, the "Sky Re-bake Granularity" DragFloat (Atmosphere → Advanced), and the entire "Perf Bisect (Diagnostic)" tree. The underlying options (skyLutCacheKeySplitEnable, the caps, the granularity, and the sixdebug*bisect toggles) stay declared and conf-tunable for future regression hunting.
Workstream — Cloud perf: voxel-grid re-bake granularity (fork — 2026-06-11)
Post-workstream follow-up at the user's request: the perf-bisect freeze of the per-frame D_sun / D_ambient bakes showed a large win with no short-term visual change — the staleness only accumulates as wind / camera / sun move. Same pattern as the sky-view fix: a cache key (normalizeForVoxelGridKey) quantizes the grid bake's motion inputs — wind scroll + camera position by cloudVoxelGridRebakeGranularityKm, sun/moon directions by the existing skyViewRebakeGranularityDeg — so the grids re-bake once per step of actual motion instead of every frame, staleness bounded by the step. Cloud parameter changes stay exact in the key (same-frame re-bake) and a cloud-noise re-bake force-clears the cached key. In-game validated at 0.1 km (default): ~0.7 ms saved, no visible stepping in cloud lighting or terrain shadows; 0 = legacy every-frame via conf. Note this lever lives on the cloud side — full-rate bakes were the deliberate 2026-05-19 decision against the stuttery fixed-cadence 8-frame round-robin; motion-quantized bounded-error steps passed where fixed cadence failed. Conf-only like the other workstream knobs (no UI).
-
src/dxvk/rtx_render/rtx_options.h— fork-owned addition.RTX_OPTION("rtx.atmosphere", float, cloudVoxelGridRebakeGranularityKm, 0.1f, …). -
src/dxvk/rtx_render/rtx_atmosphere.h/rtx_atmosphere.cpp— fork-owned additions.normalizeForVoxelGridKey(sky-view key base + wind/camera re-injected quantized),m_cachedVoxelGridKey, the dirty gate around the two grid dispatches incomputeLuts, and the force-clear on cloud-noise re-bake.
Workstream — Per-column cloud model (column-shaping rework) (fork — 2026-06-12)
Root-cause rework of the "stacked separated layers" read that made the volumetric cloud system look like flat 2D planes. Previously EVERY vertical shaping signal — the height-LUT density envelope (which at cumulus types even baked TWO density peaks separated by a thinner neck), the coverage-threshold scale band, the anvil pow gate, the Nubis dim profile and the bottom-darkening gradient — keyed on the GLOBAL slab height fraction, so every cloud in the sky shared one vertical recipe pinned to absolute altitude (flat global base/top planes, horizontal lighting bands), while the thresholded isotropic 3D noise placed mass independently per altitude (vertically disconnected stacked puffs within a column). The rework introduces a baked 512×512 RGBA8 cloud placement map (R = Worley-cell cluster field, G = per-cloud top-height jitter, B = base lift; tiled at cloudNoiseTileKm, de-tiled by the SAME hex lattice as the 3D noise so cluster and texture travel together) and derives per-column cloud presence + local base/top from it; the sample height is re-normalized to each column's own [base, top] and ALL vertical shaping plus the Nubis lighting proxies key on that per-cloud height. The 3D noise keeps its texture/erosion role but no longer decides placement. Gated by cloudColumnShapingEnable (default on; legacy global-slab path preserved bit-exact for A/B).
-
src/dxvk/shaders/rtx/pass/atmosphere/cloud_placement_map_baker.comp.slang— NEW fork-owned file. Periodic 2D Worley + value-noise FBM construction of the cluster / top-jitter / base-lift fields; integer cells-per-tile snapping fromcloudCellSizeKmso the map tiles seamlessly. -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_common.slangh— fork-owned additions + changes. NewkCloudPlacementFieldMean,sampleCloudPlacement(hex-de-tiled tap; R variance-preserving blend, G/B weighted average),CloudColumn/computeCloudColumn(coverage remap of the cluster field over acloudColumnFeatherband; base lift; presence^cloudColumnTopShape× jitter top). Both density samplers hoist the hex tile, sample placement, early-out outside the column (before any 3D taps — a perf win at partial coverage), re-normalize the height fraction to the column span, and gate on per-column presence instead of weather coverage; the primary overload returns the per-cloudshapeHeightFractionvia a new out param. Both samplers + both voxel-bake optical-depth helpers gain aTexture2D<float4> placementMapparameter. -
src/dxvk/shaders/rtx/pass/atmosphere/cloud_march_common.slangh— fork-owned change.marchCloudSlabconsumes the out-param overload and passes the per-cloudshapeHftoevalNubisCubedSample, so the dim profile / SDF proxy / bottom darkening track each cloud's own base/top instead of painting global altitude bands. The moon-shadow tap plumbs the placement map. -
src/dxvk/shaders/rtx/pass/atmosphere/cloud_height_lut_baker.comp.slang— fork-owned change. Gains the args CB (binding 0; output moves to 1) and a second curve family for column mode: single-lobe envelope (smoothstep rise, fall reaching exactly 0 at hf=1 for cumulus) replacing the legacy trapezoid + secondary anvil bump — the two-peak profile was itself a baked-in stacked-strata structure. Legacy family preserved verbatim for the gate-off path; G channel (threshold widening near each cloud's own top) unchanged and now carries the anvil look alone in column mode. -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_sky.slangh— fork-owned change.evalClouds+sampleCloudSunOpticalDepthpassAtmosphereCloudPlacementMap(from common bindings) into the density sampler — the analytical fallback gets the column model automatically. -
src/dxvk/shaders/rtx/pass/atmosphere/cloud_render.comp.slang/cloud_secondary_lut.comp.slang— fork-owned additions. DeclareAtmosphereCloudPlacementMapat local binding 12 (sampled with the slot-2 cloud-noise sampler). -
src/dxvk/shaders/rtx/pass/atmosphere/cloud_sun_density_grid.comp.slang/cloud_ambient_density_grid.comp.slang— fork-owned additions. Declare the placement map at local binding 4 and pass it through the optical-depth helpers so the baked D_sun / D_ambient grids shadow the same column shapes the view march renders. -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h— fork-owned change. RepurposespadCloudLook2→cloudColumnShapingEnable,padCloudC2→cloudCellSizeKm,pad_cr0..2→cloudColumnTopVariation/cloudColumnTopShape/cloudColumnBaseVariation,pad_c6_0→cloudColumnFeather. No CB layout change. -
src/dxvk/shaders/rtx/pass/common_binding_indices.h— fork-owned addition (index-only). AddsBINDING_ATMOSPHERE_CLOUD_PLACEMENT_MAP = 216+ aTEXTURE2Dentry inCOMMON_RAYTRACING_BINDINGS. -
src/dxvk/shaders/rtx/pass/common_bindings.slangh— fork-owned addition (index-only). DeclaresAtmosphereCloudPlacementMapat the new slot for the raytrace TUs (analytical evalClouds fallback). -
src/dxvk/rtx_render/rtx_options.h— fork-owned additions.cloudColumnShapingEnable(true),cloudCellSizeKm(2.0),cloudColumnTopVariation(0.45),cloudColumnTopShape(0.6),cloudColumnBaseVariation(0.12),cloudColumnFeather(0.35). -
src/dxvk/rtx_render/rtx_atmosphere.h/rtx_atmosphere.cpp— fork-owned additions + changes.m_cloudPlacementMap(512² RGBA8) +kCloudPlacementMapSize,dispatchCloudPlacementMapBake(init + live re-bake vianeedsCloudPlacementRebakeoncloudCellSizeKm/cloudNoiseTileKm), height-LUT re-bake oncloudColumnShapingEnableflip (m_cachedHeightLutColumnMode; the baker now binds the args CB at 0 / output at 1), voxel-grid key force-clear when either shape input re-bakes, placement binds: slot 12 indispatchCloudRender/dispatchCloudSecondaryLut, slot 4 in both voxel-grid bakes,BINDING_ATMOSPHERE_CLOUD_PLACEMENT_MAPinbindResources, andgetAtmosphereArgsfills for the six repurposed fields. Shader-class parameter lists updated accordingly. -
src/dxvk/rtx_render/rtx_fork_atmosphere.cpp— fork-owned additions.bindAtmosphereLutsbinds the placement map at the common slot; new "Cloud Columns" ImGui group (Volumetric Cloud Columns checkbox + Cloud Cell Size / Top Variation / Top Shape / Base Undulation / Edge Feather sliders); Vertical Stretch tooltip notes columns supersede it.
Workstream — Per-column underside contrast (column-shaping rev 2) (fork — 2026-06-12)
First in-game pass on the column model reported a residual "layered" read on the overcast underside. Diagnosis: the deck base's dominant light is the Nubis multi-scatter M term, which is horizontally near-uniform at production settings (sigma_ms is tiny so exp(-sigma_ms·D_sun) ≈ 1; the bottom-darkening gradient is a pure function of height) — so the underside renders as one flat-lit sheet with only fine erosion lace on top: a dark lace plane over a bright plane reads as two stacked 2D layers. Real deck undersides carry km-scale cellular relief because base brightness follows per-column water depth (thick columns occlude downwelling light, thin columns transmit it). The fix scales the bottom-darkening amount by each sample's column span (already derived by the column model), so the underside brightness is driven by the same placement field that shapes the clouds — moving with wind, matching the baked shadows.
-
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_common.slangh— fork-owned changes.sampleCloudDensityTextured's primary overload gains anout float columnSpan(topFrac − baseFrac; 1.0 legacy);evalNubisCubedSamplegains acolumnSpanparameter and scalescloudBottomDarkeningbymix(1, span, cloudColumnUndersideContrast)before building the bottom gradient. Contrast 0 or span 1 reproduce the uniform legacy darkening exactly. -
src/dxvk/shaders/rtx/pass/atmosphere/cloud_march_common.slangh— fork-owned change.marchCloudSlabthreads the span from the density sampler into the lighting call. -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h— fork-owned change. Repurposespad_c6_1→cloudColumnUndersideContrast. No CB layout change. -
src/dxvk/rtx_render/rtx_options.h— fork-owned addition.RTX_OPTION("rtx.atmosphere", float, cloudColumnUndersideContrast, 0.65f, …). -
src/dxvk/rtx_render/rtx_atmosphere.cpp— fork-owned change.getAtmosphereArgsfills the new field (replacing the pad zero-write). -
src/dxvk/rtx_render/rtx_fork_atmosphere.cpp— fork-owned addition. "Underside Contrast" DragFloat in the Cloud Columns ImGui group.
(Superseded the same day by rev 3 below — the flat per-column scaling produced flat dark smudges in-game; the option and its mechanism were replaced before any release.)
Workstream — Analytic downwelling light field (column-shaping rev 3) (fork — 2026-06-12)
Second in-game pass: rev 2's span-scaled darkening produced big FLAT dark smudges — user verdict "worse… changed the point at which this becomes visible, never fixing the root cause." Root cause, finally named precisely: the underside LIGHT FIELD has no 3D structure. Under a deck, T_primary ≈ 0; the dominant M term varies ≤~20-40% (sigma_ms 0.05–0.25 over D_sun ≈ 2) and that residual comes from the 8-tap voxel-grid D_sun (km-scale mush); ambient's exp(-D_ambient) saturates to ~0. With per-point illumination near-constant, the only visible pattern is opacity silhouetted against a flat backdrop — every density/shaping change just swaps the wallpaper. Rev 3 computes the downwelling light analytically from the column model: the height LUT gains a B channel holding the cumulative envelope integral from each per-cloud height to the cloud top, so downTau = ∫envelope × columnSpan × cloudThickness × cloudDensity is the exact macroscopic water above any sample at full resolution (no voxel grid, no taps); exp(-cloudUndersideLightSigma × downTau) (a diffusion-flavored sigma, far below beam extinction) lights the multi-scatter and ambient terms. Underside brightness now varies continuously with the actual water distribution — dark cores, bright thin spots, in-cell gradients. Legacy mode (column shaping off, or sigma 0) keeps the constant bottom-darkening gradient bit-for-bit.
-
src/dxvk/shaders/rtx/pass/atmosphere/cloud_height_lut_baker.comp.slang— fork-owned change. Output RG8 → RGBA8; B = midpoint-rule ∫_hf^1 envelope(u) du of the active curve family (re-baked on mode flip as before). -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_common.slangh— fork-owned changes. NewcloudTypeProfileIntegralFromTop(closed-form trapezoid integral, the no-LUT fallback) +cloudHeightProfileDownIntegral(LUT B channel / fallback).evalNubisCubedSample: rev 2's spanFactor block replaced by theverticalLightselector — column mode = analytic Beer-Lambert on downTau, legacy = the constant gradient; M multipliesverticalLight; ambient =ambient_shape × verticalLightin column mode (replacing the saturatingexp(-D_ambient)tap — same quantity, better estimate; multiplying both would double-count). -
src/dxvk/shaders/rtx/pass/atmosphere/cloud_render.comp.slang/cloud_secondary_lut.comp.slang— fork-owned change.AtmosphereCloudHeightLutdeclarationsTexture2D<float2>→Texture2D<float4>. -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h— fork-owned change.cloudColumnUndersideContrast(rev 2, pad_c6_1) →cloudUndersideLightSigma. No CB layout change. -
src/dxvk/rtx_render/rtx_options.h— fork-owned change. DropscloudColumnUndersideContrast; addsRTX_OPTION("rtx.atmosphere", float, cloudUndersideLightSigma, 0.12f, …). -
src/dxvk/rtx_render/rtx_atmosphere.cpp— fork-owned changes. Height LUT resource format R8G8 → R8G8B8A8; args fill swaps to the new option. -
src/dxvk/rtx_render/rtx_fork_atmosphere.cpp— fork-owned change. "Underside Contrast" slider replaced by "Underside Shading" (sigma). Tooltip notes it supersedes Bottom Darkening while Cloud Columns are on.
Workstream — Adaptive cloud-march sampling (fork — 2026-06-12)
Companion fix shipped with column-shaping rev 3, prompted by a community diagram suggesting the march be confined to the shell volume — it already is (marchCloudSlab intersects base + top shells), but the adjacent real defect is the FIXED step count across that volume: 32 uniform steps over a span that varies from ~4 km (zenith) to 50+ km (horizon-grazing through the curved shell) puts ~1.6 km steps against ~2 km cloud features at low elevations. The aliasing, averaged by jitter + the temporal EMA, renders as soft horizontal BANDS concentrated toward the horizon — a direct contributor to the "stacked layers" read. The march now holds a target step LENGTH: count = span / cloudViewStepKm, floored at cloudViewSamples (zenith cost unchanged) and capped at cloudViewSamplesMax; the column model's pre-tap early-outs keep added steps cheap where the slab is empty. cloudViewStepKm = 0 restores the legacy fixed count exactly.
-
src/dxvk/shaders/rtx/pass/atmosphere/cloud_march_common.slangh— fork-owned change.marchCloudSlabcomputeseffSampleCountfrom the clamped span and the step target; loop + rayFrac use it. Applies to both consumers (cloud render RT + secondary dome LUT). -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h— fork-owned change. Repurposespad_cloudSunsetAmbient0→cloudViewStepKmandpadStarCloud0→cloudViewSamplesMax. No CB layout change. -
src/dxvk/rtx_render/rtx_options.h— fork-owned additions.cloudViewStepKm(0.3) +cloudViewSamplesMax(128). -
src/dxvk/rtx_render/rtx_atmosphere.cpp— fork-owned change.getAtmosphereArgsfills both (replacing the pad zero-writes). -
src/dxvk/rtx_render/rtx_fork_atmosphere.cpp— fork-owned addition. "March Step Size" DragFloat next to Cloud Render Scale; the cap stays conf-only. -
Clarity pass (same day, post-validation): the in-game fix was user-validated with an accepted perf cost, so the controls now state it plainly: slider renamed "Cloud Sample Spacing" with a PERFORMANCE paragraph in the tooltip (cost scales up to cap/32 ≈ 4x on horizon-heavy views at defaults; how to trade it back), and the cap is exposed as a "Max Cloud Samples" DragInt (the perf governor). Option docstrings updated to match.
Workstream — Night cloud-lighting knob rebase (fork — 2026-06-14)
Night clouds read too bright. The earlier suspicion (the moon-cloud directional term) was wrong: the dominant contributor is the STAR-coupling term, added uniformly and uncolored to every cloud sample (nightLight += nightSkyColor × starBrightness × starAmbientCouplingStrength × nightFactor), so it lifts the whole deck — and tints it blue via nightSkyColor — across the entire sky, not just near the moon. At the old default (0.03 × starBrightness 0.5 × nightSkyColor) it produced ~0.006 blue, larger and more pervasive than the moon's ~0.003 silver-lining peak. The knobs were also mis-scaled: the sub-0.01 night-radiance smallness lived inside the user-facing gains, so the only sane values were ~0.001 ("having 0.001 be the only reasonable one is painful"). Fix = rebase to O(1): the smallness moves into internal shader constants so the knobs read as a "multiple of the calibrated night level," and the star default drops to a user-tested 0.25. moonAmbientAirglow is unchanged in effect (it was never the problem). Both the RT march and the legacy analytical evalClouds get the identical edit so the day→night crossfade stays bit-matched.
-
src/dxvk/shaders/rtx/pass/atmosphere/cloud_march_common.slangh— fork-owned change.buildCloudShadeContextnightLight: internalkStarCloudCoupling = 0.008fandkMoonAirglowScale = 0.0015ffactored out of the star-coupling and moon-airglow terms so the user-facing gains become O(1). -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_sky.slangh— fork-owned change. IdenticalkStarCloudCoupling/kMoonAirglowScalefactoring in the legacyevalCloudsnightLight block (crossfade parity with the RT path). -
src/dxvk/rtx_render/rtx_options.h— fork-owned change.starAmbientCouplingStrengthdefault 0.03 → 0.25;moonAmbientAirglowdefault 0.0015 → 1.0; both docstrings rewritten as "multiple of the calibrated night level." -
src/dxvk/rtx_render/rtx_fork_atmosphere.cpp— fork-owned change. "Star Ambient Coupling" DragFloat range 0–0.1 / %.4f → 0–3 / %.2f so the O(1) default isn't clamped; tooltip updated.
Workstream — Cloud edge detail via threshold modulation (rev 4) (fork — 2026-06-14)
The additive edge-detail (rev 3) was trapped in a thin blobby rind hugging the silhouette: its containment window (smoothstep(coverageThreshold - 0.15, coverageThreshold, density)) keyed on the pre-detail base density and was one-sided, so detail could only displace the iso-surface from INSIDE — it could never grow billows outward. Widening the window traded the rind for a half-res smear halo (the additive positive lobe lifts a broad sub-threshold clear-sky margin into faint density on soft edges, which the half-res render + bilinear upsample smear). Rev 4 changes the mechanism: the detail tap now wobbles the COVERAGE THRESHOLD (the gate position) instead of the density field, zero-mean about kCloudNoiseFieldMean (0.4). This beats both modes at once — (a) the threshold can drop below the base value so billows grow OUTWARD past the silhouette (no rind), and (b) the gate self-localizes the effect so far-outside samples stay hard-0 (no faint margin to smear) and far-inside stay 1 (no interior holes); the modulation only bites in the gate transition band, so no explicit edge window is needed. Unlike the rejected rev-2 erosion remap it never touches the noise field — it only moves the gate, the same slide-3 anvil lever step 4a uses at low frequency. User-validated in-game ("this is perfect"). Supersedes the prior rind-vs-smear tradeoff; rev 3's window and the rejected tunable-reach knob are both gone.
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_common.slangh— fork-owned change.sampleCloudDensityTexturedstep 4b: additivedensity +=+edgeWindowreplaced bycoverageThreshold -= cloudDetailStrength * 0.5 * (detailNoise - kCloudNoiseFieldMean), clamped≥ 0. InternalkEdgeDetailThreshold = 0.5(wobble→threshold scale). No new options;cloudDetailStrength = 0is bit-identical. Shadow sampler still skips detail.
Workstream — Artistic sunset color controls (fork — 2026-06-14)
Two artistic knobs to recover sunset warmth/saturation lost when commit 3e37062b moved sunset reddening onto the physical Hillaire two-term LUT model: the broadband multiscatter "fill" reads pale-blue and desaturates the warm single-scatter, so the physically-correct sunset renders undersaturated. Both apply inside evalAtmosphereRadiance (the sky-view LUT bake integral), so the baked LUT carries them and clouds inherit the warmer ambient for free (cloud warm ambient samples the sky-view LUT). Both default to 1.0 = physical (no change). sunsetSaturation ramps in only near the horizon (off above ~24°, sin 0.4) so midday sky is untouched; multiScatterStrength is a global scale on the fill term. This is artistic control on top of the physical model — NOT a revert to the pre-3e37062b analytical air-mass reddening.
-
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_common.slangh— fork-owned additions. InevalAtmosphereRadiance's per-sample loop:multiScatterContrib *= args.multiScatterStrengthafter the analytical/LUT blend. Beforereturn L: a luma-preserving saturation boostlerp(vec3(luma), L, satGain)wheresatGain = lerp(1, args.sunsetSaturation, 1 - smoothstep(0, 0.4, sunDir.y)). Both no-ops at the 1.0 defaults. -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h— fork-owned additions. Repurposes the trailingpad_cloudEdge1slot asmultiScatterStrength(completes the cloud-edge 16-byte row, layout unchanged), then appends a new 16-byte rowsunsetSaturation+pad_artistic0..2. CB grows by one row; all prior field offsets unchanged. -
src/dxvk/rtx_render/rtx_options.h— fork-owned additions. AddsmultiScatterStrength(1.0) andsunsetSaturation(1.0) RTX_OPTIONs to thertx.atmospherecluster, immediately aftermultiScatterPhysicalStrength. -
src/dxvk/rtx_render/rtx_atmosphere.cpp— fork-owned additions.getAtmosphereArgs(): sets both args from RtxOptions unconditionally (aftermultiScatterPhysicalStrength), so the sky reddens with clouds disabled.normalizeForTransmittanceMsKey(): zeroes both (likemultiScatterPhysicalStrength) — they feed only the sky-view bake, not the transmittance/MS LUTs, so changing them must not re-bake the heavy pair. -
src/dxvk/rtx_render/rtx_fork_atmosphere.cpp— fork-owned additions. Adds "Multiscatter Strength" (0–2) and "Sunset Saturation" (0–3)DragFloats to the Atmosphere → Advanced ImGui tree, immediately after "Multiscatter Physical Strength", each with an explanatory tooltip. ~10 LOC.
Workstream — Sun-only cloud-on-terrain shadow re-architecture (fork — 2026-06-19)
Moves the cloud-on-terrain shadow from a post-denoise screen-space texture
(multiplied onto the combined direct buffer in composite) to a pre-denoise
fold-in on the SUN's radiance inside the sun NEE. This deletes the whole
geometry-blindness compensation stack (sealed-interior zenith up-ray gate,
viewmodel/decal origin correction, camera-side normal flip, dusk/dawn horizon
gate) and the screen-space plumbing it required. Several of the removed
screen-space pieces (the PrimaryCloudShadowFactor resource + its binding
indices/tables, debug view 878) were never previously inventoried — a prior
fridge-list gap, now closed by removal.
-
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_common.slangh— fork-owned change.AtmosphereSunSample: drops thecloudShadowFactormember.sampleAtmosphereSunLight: folds the cloud shadow onto the sun byresult.radiance *= pow(sampleCloudGroundShadow_OptionB(...), args.cloudShadowFactorStrength)(gated oncloudVoxelShadowsEnable); the early issue-#37 factor computation and the long screen-space wire-in comment are gone.sampleAtmosphereSunLightVolume: samepow(..., cloudShadowFactorStrength)curve added for parity (was a plain multiply).sampleCloudGroundShadow_OptionB_impl: horizon gate removed (the grazing-sun OD blowup now only darkens the already-attenuated sun, never lamps). -
src/dxvk/shaders/rtx/algorithm/integrator_direct.slangh— fork-owned change.evalAtmosphereSunNEE: deletes the sealed-interior zenith up-ray gate, the viewmodel/decal camera-origin branches, the triangle-normal flip, and thePrimaryCloudShadowFactor[pixelCoordinate]write. The cloud shadow now arrives folded intosunSample.radiance. -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h— fork-owned change. Repurposes the trailingpad_artistic0slot ascloudShadowFactorStrength(CB layout ABI-unchanged); the knob moved here from composite_args.h. -
src/dxvk/rtx_render/rtx_atmosphere.cpp— fork-owned change.getAtmosphereArgs(): populatesargs.cloudShadowFactorStrength = max(RtxOptions::cloudShadowFactorStrength(), 0)in the cloud-shadow block. -
src/dxvk/shaders/rtx/pass/composite/composite.comp.slang— fork-owned change. Removes the post-denoisepow(PrimaryCloudShadowFactor, cloudShadowFactorStrength)multiply on primary direct radiance (and the texelFetch). Only an architecture comment remains. -
src/dxvk/shaders/rtx/pass/composite/composite_args.h+src/dxvk/rtx_render/rtx_composite.cpp— fork-owned change.cloudShadowFactorStrengthCB slot renamed to reservedpad1(ABI-stable); the composite populate of it is removed. -
src/dxvk/shaders/rtx/pass/composite/composite_bindings.slangh+composite_binding_indices.h+rtx_composite.cpp— index-only, fork. Removes thePrimaryCloudShadowFactorTexture2Ddeclaration, the twoTEXTURE2D(COMPOSITE_PRIMARY_CLOUD_SHADOW_FACTOR_INPUT)parameter-list entries, and thebindResourceView. Binding slot 19 left reserved (number not reused). -
src/dxvk/shaders/rtx/pass/integrate/integrate_direct.slangh+integrate_direct_bindings.slangh+integrate_direct_binding_indices.h+rtx_pathtracer_integrate_direct.cpp— index-only, fork. Removes the per-framePrimaryCloudShadowFactorclear, theRWTexture2D<float> PrimaryCloudShadowFactordeclaration, theRW_TEXTURE2Dparameter-list entry, and thebindResourceView. Binding slot 73 left reserved. -
src/dxvk/rtx_render/rtx_resources.cpp+rtx_resources.h— index-only, fork. Removes them_primaryCloudShadowFactorResourcemember and itscreateImageResourcealloc. -
src/dxvk/shaders/rtx/utility/debug_view_indices.h+debug_view.comp.slang+debug_view_binding_indices.h+rtx_debug_view.cpp— index-only, fork. Removes debug view 878 (DEBUG_VIEW_CLOUD_SHADOW_FACTOR_RAW): the enum define (number burned, not reused), the case arm, theDebugViewPrimaryCloudShadowFactordeclaration, the binding-index define (slot 38 reserved), theTEXTURE2Dparameter-list entry, thebindResourceView, and the selector-list label. Views 875/877 (D_sun grid reads) stay. -
src/dxvk/rtx_render/rtx_instance_manager.cpp+rtx_materials.h+src/dxvk/shaders/rtx/concept/surface/surface.h— index-only, fork (REVERTED). Reverts theSurface::isDecalCategoryflag (CPU set,flags0bit 2 pack, shader property) added 2026-06-18; bit 2 is free again. Only "removed/freed" comments remain.
Workstream — Cloud ImGui simplification (fork — 2026-06-15)
Restructured the flat ~45-control Clouds menu (7 non-collapsible TextDisabled dividers, everything always expanded) into a workflow-grouped tree of collapsible sub-nodes. No options, CB fields, or shader behavior change — pure ImGui reorganization.
src/dxvk/rtx_render/rtx_fork_atmosphere.cpp— fork-owned change.Cloudsnode rebuilt as:Basic(open by default: Coverage, Cloud Type, Density, Altitude, Depth, Color) ·Shaping ▸ {Variation, Detail & Edges, Columns}·Lighting·Wind·Layer 2·Performance·Horizon & Haze(Curvature moved here from "Look"). TheColorcontrol is aColorEdit3swatch/picker. Mode-inert controls are greyed viaImGui::BeginDisabled: the six Columns sliders whencloudColumnShapingEnableis off,Bottom Darkeningwhen it is on (Underside Shading supersedes it), and the Layer 2 body whencloudLayer2Enableis off. The "Vertical Stretch" slider (cloudVerticalStretch) is removed from the menu — the option stays conf-only (experimental / superseded by Columns). All tooltips preserved.
Workstream — Remove legacy cloud paths + realistic sun-gated underside darkening (fork — 2026-06-19)
Two dead cloud code paths were removed and the column model made the only path: (1) the legacy analytical evalClouds view-march in atmosphere_sky.slangh (already compile-gated out behind ATMOSPHERE_CLOUD_VIEW_MARCH, used as a runtime A/B fallback that was never the validated default — clouds come from the cloud_render compute pass on primary rays and the secondary dome LUT on indirect/PSR/reflection rays), and (2) the legacy global-slab shaping (cloudColumnShapingEnable == false): the trapezoid+anvil height-profile curve family, the gate-off branches in the density/bake samplers, and the constant bottom-darkening gradient. With the legacy gradient gone, bottom darkening was reworked into the column model as a physically-motivated underside light field driven by cloud density and sun position: the analytic downwelling exp(-cloudUndersideLightSigma × downTau) (density / overlying-water term, unchanged shape) now scales by cloudBottomDarkening (overall strength) × smoothstep(0, 0.35, sunElev) so the darkening is strongest at high sun and fades out toward the horizon — the low sun rakes under the deck and the warm ambient / silver-lining terms light the bases (classic lit-orange sunset undersides). cloudBottomDarkening default 0.55 → 1.0 so the high-sun look matches the validated rev-3 column underside bit-for-bit; only low-sun behavior is new. The legacy ambient exp(-D_ambient) tap is gone (the analytic verticalLight replaces it), so sampleDAmbient is no longer called from evalNubisCubedSample.
-
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_sky.slangh— fork-owned change. DeletedevalClouds(the whole#ifdef ATMOSPHERE_CLOUD_VIEW_MARCHblock) and its only consumersampleCloudSunOpticalDepth. The cloud-sourceelsebranch inevalSkyRadiance(both RT + secondary-LUT off) is now an unconditional transparentvec4(0)fallback, no#ifdef. StaleevalCloudscomment references updated. -
src/dxvk/shaders/rtx/pass/atmosphere/cloud_height_lut_baker.comp.slang— fork-owned change. Deleted the legacydensityEnvelope(trapezoid+anvil) R-curve and thecloudColumnShapingEnablemode select;densityEnvelopeColumnis now used unconditionally for both R and the B integral.coverageThresholdScale(G) unchanged. -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_common.slangh— fork-owned changes. Dropped thecloudColumnShapingEnablegate insampleCloudDensityTexturedand the bake-mirror sampler (column derivation now unconditional).evalNubisCubedSample:verticalLightreworked from the column/legacyif-elseto the single realistic sun-gated form (,kUndersideSunFadeElev = 0.35); ambient term is nowambient_shape × verticalLightunconditionally;sampleDAmbientcall removed (unused). -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h— fork-owned change.cloudColumnShapingEnable→padCloudLook2andcloudBottomDarkeningHeight→pad_cloudVoxel1(both reverted to pad placeholders). No CB layout change. -
src/dxvk/rtx_render/rtx_options.h— fork-owned change. RemovedcloudColumnShapingEnableandcloudBottomDarkeningHeightRTX_OPTIONs.cloudBottomDarkeningdefault 0.55 → 1.0, description reworked (sun-gated strength).cloudUndersideLightSigmadescription reworked (no more legacy-gradient / column-mode wording). -
src/dxvk/rtx_render/rtx_atmosphere.cpp/.h— fork-owned changes. Removed them_cachedHeightLutColumnModemember + the height-LUT flip-rebake incomputeLuts(the LUT bakes one curve family once at init). Dropped thecloudColumnShapingEnable/cloudBottomDarkeningHeightargs fills. Updated the bake doc comments. -
src/dxvk/rtx_render/rtx_fork_atmosphere.cpp— fork-owned change. Removed the "Volumetric Cloud Columns" checkbox and theBeginDisabled/EndDisabledgating around the Columns sliders and Bottom Darkening (all always enabled now). Reworked the Bottom Darkening + Underside Shading tooltips (strength × shape; sunset fade). -
RtxOptions.md— hand-edited to match (removed the two options, updatedcloudBottomDarkening/cloudUndersideLightSigma); regenerate in-app for canonical form.
Workstream — Cloud sky-dome ambient fill (clouds reflect the sky) (fork — 2026-06-19)
First half of the sky↔cloud color-coupling work. Symptom: under a bright blue daytime sky the cloud undersides read dark / gloomy, and clouds never take on the surrounding sky's color, because the cloud ambient samples the sky-view LUT in only TWO directions — toward the sun (warm) and the anti-sun horizon (cool) — and buildCloudShadeContext deliberately skips the overhead/zenith sky (the old comment feared zenith would blue-tint sunset cumulus). So the large bright sky dome that lights real cloud bottoms from below/around is never sampled. Fix: add a zenith sky-view sample and a new ambient term, the "sky-dome fill," that the underside picks up WITHOUT the bottom-darkening attenuation (that skylight reaches the base from below/around, not filtered down through the overlying water). It reads the zenith color, so a bright daytime dome lifts gloomy undersides and tints them with the actual sky; at sunset the zenith sample is dim so the term self-fades and the warm top-down ambient carries the look (the validated sunset is preserved bit-for-bit at fill 0, and nearly so at the 0.5 default since zenith→0 there). The companion sky←clouds half (cloud radiance bleeding into the visible sky) is still pending.
-
src/dxvk/shaders/rtx/pass/atmosphere/cloud_march_common.slangh— fork-owned changes.CloudShadeContextgainsskyRadianceZenith;buildCloudShadeContextsamplessampleSkyAmbientForVolume(vec3(0,1,0), …) * dayFactor(same cloud-occlusion + day gate as warm/cool) and assigns it; theevalNubisCubedSamplecall passes it through. -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_common.slangh— fork-owned change.evalNubisCubedSamplegains askyRadianceZenithparam; ambient split intotopAmbient = ambient_shape × verticalLight × skyRadiance(legacy, attenuated) +domeFill = ambient_shape × cloudSkyAmbientFill × skyRadianceZenith(NOT attenuated by verticalLight);result.ambient = topAmbient + domeFill. -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h— fork-owned change.pad_cloudVoxel1(the freed cloudBottomDarkeningHeight slot) →cloudSkyAmbientFill. No CB layout change. -
src/dxvk/rtx_render/rtx_options.h/rtx_atmosphere.cpp— fork-owned changes. NewRTX_OPTION(float, cloudSkyAmbientFill, 0.5f, …);getAtmosphereArgsfillsargs.cloudSkyAmbientFill. -
src/dxvk/rtx_render/rtx_fork_atmosphere.cpp— fork-owned change. "Sky Fill" DragFloat added to the Clouds → Lighting tree after Bottom Darkening, with tooltip. -
RtxOptions.md— hand-edited to addcloudSkyAmbientFill; regenerate in-app for canonical form.
Workstream — Sky-reflects-clouds bleed (sky picks up cloud color) (fork — 2026-06-19)
Second half of the sky↔cloud color coupling. Symptom: the visible sky and the cloud layer are composited independently (radiance = mix(sky, cloud.rgb, cloud.a) in evalSkyRadiance), so a colored deck never tints the clear sky — leaving vivid blue gaps between orange sunset clouds (and a too-clean sky around grey overcast). Fix: before the cloud composite, add a fraction of the LOCAL cloud radiance to radiance as colored inscatter. The source is the secondary cloud dome LUT (AtmosphereCloudSecondaryLut) sampled in the view direction — it is low-res (256×128), hence inherently smooth, giving a neighborhood-averaged cloud color per direction (bright/colored next to a cloud, ~0 in open sky far from any) with no extra blur pass. Because it is added BEFORE the composite, the composite's own (1 - cloudAlpha) factor keeps the bleed in the visible-sky fraction and out of opaque cloud cores. Gated on the secondary LUT being baked (default on) and cloudSkyBleedStrength > 0. Pairs with the clouds←sky "sky-dome fill" (Sky Fill); together the two close the loop so clouds and sky share color in both directions at all times of day.
-
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_sky.slangh— fork-owned change. InevalSkyRadiance, after the cloud-source selection and before the temporal-smoothing/composite: sample the secondary dome LUT atcloudDomeDirToUv(viewDirYUp)andradiance += cloudSkyBleedStrength * bleedCloud.rgb. -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h— fork-owned change.padCloudLook2(the freed cloudColumnShapingEnable slot) →cloudSkyBleedStrength. No CB layout change. -
src/dxvk/rtx_render/rtx_options.h/rtx_atmosphere.cpp— fork-owned changes. NewRTX_OPTION(float, cloudSkyBleedStrength, 0.3f, …);getAtmosphereArgsfillsargs.cloudSkyBleedStrength. -
src/dxvk/rtx_render/rtx_fork_atmosphere.cpp— fork-owned change. "Sky Cloud Bleed" DragFloat added to the Clouds → Lighting tree after "Sky Fill", with tooltip. -
RtxOptions.md— hand-edited to addcloudSkyBleedStrength; regenerate in-app for canonical form.
Workstream — Sky color correctness: physical multiscatter default (fork — 2026-06-19)
The clear sky read wrong vs reality at all times of day: over-saturated/wrong blue hue, no warm horizon, and a flat zenith→horizon gradient. Two Opus research passes converged on the cause: the DEFAULT multiscatter path was the analytical inline fit (multiScatterPhysicalStrength defaulted to 0), which is a flat, isotropic (no phase, no transmittance), strongly blue-biased fill added uniformly in every direction — it raises the floor everywhere (flattening the gradient) and desaturates the warm horizon. The sunsetSaturation knob had been added purely to band-aid this (a desaturation mask is itself the tell the base output was wrong). Fix: make the physical Hillaire multiscatter LUT the default and retire the band-aid. Also fixed a real dimensional bug in the analytical path (kept for A/B): its computeGroundReflectionAnalytical / computeAirMultiscatteringAnalytical baked sunIlluminance in, then the caller multiplied by sunIlluminance again — sunIlluminance² (≈225× at the default 15) — whereas the LUT-baker twins correctly omit it. Separately, the user's per-game config had non-physical coefficient overrides (Mie 4× low, Rayleigh ~35% low, Mie g 0.99); the code defaults are already the canonical Bruneton/Hillaire values, so those were corrected in the game's rtx.conf (not in-repo).
-
src/dxvk/rtx_render/rtx_options.h— fork-owned changes.multiScatterPhysicalStrengthdefault 0.0 → 1.0 (physical LUT is now the default multiscatter);sunsetSaturationdefault 1.0 → (briefly 0.5) → 1.0 (band-aid retired now that the base is correct). Descriptions rewritten. -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_common.slangh— fork-owned change.computeGroundReflectionAnalytical/computeAirMultiscatteringAnalyticalno longer bake insunIlluminance(the caller applies it once, matching the physical LUT twins andcontribLut); fixes thesunIlluminance²double-count on the analytical A/B path. Call-site comment added so it isn't re-introduced.
Workstream — Sky-reflects-clouds bleed: mip-blur fix (fork — 2026-06-19)
The first cut of the sky<-clouds bleed sampled the 256x128 secondary cloud dome LUT at mip 0 and added it to the sky. Result: cloud EDGES read pixelated/faceted (a single bilinear tap of a near-binary low-res signal steps across the LUT's coarse texels at silhouettes, and the coarse dome silhouette is misaligned with the sharp screen-space cloud RT), and the sky "barely changed color" (a true gap samples ~0 cloud in its exact direction — no neighborhood spread). An Opus debug pass identified both and prescribed a pre-blurred coarse-mip source. Fix: give the secondary LUT a mip chain, regenerate it (Gaussian) each frame after the bake, and sample a coarse mip (mip 4 = 16x8) for the bleed — one tap = a wide neighborhood blur, which removes the facets AND spreads cloud color smoothly into the gaps next to clouds. Re-enabled by default (cloudSkyBleedStrength 0 -> 0.15).
-
src/dxvk/rtx_render/rtx_atmosphere.h/rtx_atmosphere.cpp— fork-owned changes.m_cloudSecondaryLutbecomes anRtxMipmap::Resource(6 mips, 256x128 -> 8x4) viaRtxMipmap::createResource; the bake binds.views[0](mip 0) for the storage write; after the dispatch a write->read barrier +RtxMipmap::updateMipmap(..., Gaussian)fills mips 1..5 (ctx is the RtxContext, cast from the DxvkContext param). Added#include "rtx_mipmap.h". -
src/dxvk/rtx_render/rtx_fork_atmosphere.cpp— fork-owned change. The shared sky-view sampler gainsmipmapLodMax = VK_LOD_CLAMP_NONEso explicit-LOD mip sampling works (harmless for the mip-less sky-view LUT / cloud RT). -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_sky.slangh— fork-owned change. The bleed samplesAtmosphereCloudSecondaryLutatkBleedMip = 4.0instead of 0. -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_common.slangh— fork-owned change.cloudDomeDirToUvwraps u withfrac()so dome sampling no longer depends on sampler address mode (the mip-gen uses a CLAMP sampler). -
src/dxvk/rtx_render/rtx_options.h—cloudSkyBleedStrengthdefault 0.0 -> 0.15 (re-enabled).
Workstream — Cloud direct energy conservation + multiscatter sunset gather fix (fork — 2026-06-19)
Two physically-motivated lighting corrections, both addressing "lit things out-bright/mis-color the sky they composite against." WIP: shipped + deployed for in-game look validation, NOT yet eyeballed — values/approach may change after the look-check.
(1) Cloud direct dual-lobe energy conservation. The Nubis direct term summed two full-amplitude phase lobes (L_direct = T_primary*HG1 + M*HG2); HG1 and HG2 each integrate to 1 over the sphere, so the in-scatter phase integrated to up to (1+M) ≈ 2 — the cloud scattered up to ~2× the energy one scattering event can redistribute, worst at the sunlit edge where both lobes fire at once, so lit clouds out-brightened the physical sky LUT. Reformulated as an energy-conserving convex blend (phase integral 1), lerped from the legacy additive sum by a strength knob for in-game A/B. Brings the sun path onto the bounded-energy footing the moon path already had.
(2) Multiscatter sunset gather fix. The MS LUT gather in computeMultiscattering swept zenith 0..π at a SINGLE azimuth (rayDir = vec3(sinZenith, cosZenith, 0), x-y plane), but the sun lies in the y-z plane (x=0), so every gather ray sat 90° from the sun and the integral never sampled the sun-ward sky. At sunset it gathered only the blue sky orthogonal to the sun and missed the warm reddened horizon → pale-blue MS fill (sky too blue, warm band couldn't climb, clouds inherited the cold ambient via the sky-view LUT). Fixed to a proper 2D sphere quadrature (8 zenith × 8 azimuth, same 64 samples); the sinZenith Jacobian + 2π/N normalization are kept so a uniform integrand changes <1% — corrects the integral's direction/color without shifting the tuned brightness. Knob-free. (A first attempt — a multiScatterReddening tint knob — was reverted as a hack once the gather bug was found.)
-
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_common.slangh— fork-owned change. InevalNubisCubedSample, the direct termT_primary*HG1 + M*HG2becomesA*(1 - s*w) + B*(1 - s*(1-w))withA = T_primary*HG1,B = M*HG2,s = cloudEnergyConserve,w = cloudMsLobeWeight. s=0 reproduces the legacy additive sum byte-for-byte. -
src/dxvk/shaders/rtx/pass/atmosphere/multiscattering_lut.comp.slang— fork-owned change.computeMultiscatteringgather loop: single-azimuth 64× zenith sweep → nested 8 zenith × 8 azimuth full-sphere quadrature;rayDirgains an azimuth term. Jacobian + 2π/N normalization unchanged (uniform-integrand magnitude preserved to <1%). -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h— fork-owned change.pad_artistic1/pad_artistic2(trailing slots of thecloudShadowFactorStrength16-byte row) →cloudEnergyConserve/cloudMsLobeWeight. No CB layout change. -
src/dxvk/rtx_render/rtx_options.h/rtx_atmosphere.cpp— fork-owned changes. NewRTX_OPTION(float, cloudEnergyConserve, 1.0f, …)andRTX_OPTION(float, cloudMsLobeWeight, 0.5f, …)aftercloudPhaseG2;getAtmosphereArgsfills bothargs.*. -
RtxOptions.md— PENDING:cloudEnergyConserve/cloudMsLobeWeightnot yet added; regenerate in-app for canonical form.
Workstream — Sunset "neon cloud" fixes: cloud sun-transmittance Mie + dayFactor rolloff (fork — 2026-06-20)
Final two corrections that fixed the right-before-sunset "neon orange clouds" (flat, too bright, too saturated; worst at sun elevation below sun.y ~0.2, recovering once the sun is well below the horizon). Both validated in-game.
(1) Mie/aerosol extinction added to the cloud + moon sun-direction transmittance. getAtmosphericTransmittanceForDir (the analytical Kasten-Young model that lights clouds + moons; the sky body uses the transmittance LUT) was composing extinction as Rayleigh + ozone only — the Mie term was MISSING, while the LUT (transmittance_lut.comp.slang) correctly includes Rayleigh + Mie + ozone. At a low sun the omitted grey aerosol extinction left the cloud beam under-extinguished, so cloud direct light stayed a bright, fully-saturated red while the LUT-lit sky looked correct. Added the Mie slant optical depth (mieScaleHeight * airMass), mirroring the LUT's extinction composition — physically the aerosol extinction that dims a hazy setting sun, and it realigns the cloud/moon sun color with the sky.
(2) Cloud daytime-lighting rolloff widened through the sunset approach. buildCloudShadeContext's dayFactor = smoothstep(-0.05, 0.02, sun.y) held the cloud's sun + sky-ambient lighting at FULL brightness until the geometric horizon, then crashed to night — so through the whole low-sun band (where airTrans + the sky-view LUT are most saturated) the clouds were lit at full strength = full-bright, full-saturated, flat orange. Widened the upper bound to 0.20 (~11.5°) so the daytime cloud lighting dims PROGRESSIVELY as the sun descends (physically: the scene darkens through sunset); the reddest light is no longer the brightest. Above 0.20 daytime is unchanged (golden hour untouched); horizon dayFactor ~0.2, reaching 0 at -0.05 where the moon/night terms take over.
-
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_common.slangh— fork-owned change.getAtmosphericTransmittanceForDir: add- args.mieScattering * (args.mieScaleHeight * airMass)to the transmittance exponent (Rayleigh + Mie + ozone, matching the LUT bake). Stale "mirrors lines 308-328" comment corrected. -
src/dxvk/shaders/rtx/pass/atmosphere/cloud_march_common.slangh— fork-owned change.buildCloudShadeContext:dayFactorsmoothstep upper bound 0.02 → 0.20 for a progressive sunset-approach dim of all sun-derived cloud lighting.
Workstream — Layer-2 "echo deck" rework (fork — 2026-06-21)
Replaced the old layer-2 path (a fully-independent second marchCloudSlab pass that inherited layer 1's ~32-step floor and tapped the layer-1-only D_sun grid for a decorrelated cloudscape, so every deck sample read stale residual) with a lean echo deck: the same per-column cloud-slab density model at a higher, gapped altitude, marched cheaply. Perf comes from a low, user-adjustable step budget (the dominant lever — deck cost is ~linear in step count) and an analytic sun-shadow proxy in place of the stale D_sun grid tap. Variety ("varied echo") comes from the deck's own coverage/type means + cloudLayer2NoiseSeed, which decorrelates its control field from layer 1 while sharing the same field machinery. The column-placement model is deliberately KEPT — it provides the per-sample early-out that refunds the placement tap, contrary to the earlier (now-corrected) analysis. The deck DOES take moonlight (same Beer-Lambert shadow + Wrenninge phase as layer 1, deck-aware self-shadow tap) so the two decks read consistently at night. 3 cloudLayer2* options were added (2 step-budget + 1 color) and 1 removed (cloudLayer2CoverageSpread); the rest are unchanged. Validated in-game 2026-06-21 (works after the CB-alignment fix below); same-day follow-ups: density/thickness/type decoupled from the main layer, the deck's coverage "tiling" fixed (coverage spread forced uniform + option removed), and an independent deck color.
Same-day follow-ups (2026-06-21):
-
Density/thickness/type decoupling. The deck ran on a copy of the main
args, so main-layer sliders (most visiblycloudDensity) bled into the deck.marchCloudLayersnow overrides onargs2:cloudDensity→ fixedkEchoDeckExtinction = 1.8(= cloudDensity's default, so the deck decouples from the live slider with no look change at default),cloudThickness→cloudLayer2Thickness,cloudTypeMean→cloudLayer2TypeMean(the shared lighting —sampleCloudSdf,sampleDimProfile, underside down-integral — reads these directly rather than via the deck slab params).marchEchoDeckalso rebuildsctx.moonBaseSigmafrom the decoupled density. Coverage was already deck-specific. Audit conclusion: all OTHER look knobs (phase, multi-scatter, detail, bottom-darkening, edge, anvil, etc.) stay shared by "same slab" design — only color was split out (below). -
Coverage "tiling" fix. The single-octave Worley coverage field, amplified by the spread slider, read as a fine tiled/stippled texture at the deck's distance (~3 km cells subtend a tiny angle). A de-tile attempt (domain-warp + extra octave) made it worse (small/smudgy), so it was reverted; instead
marchCloudLayersforces the deck'scloudCoverageSpreadto 0 — coverage collapses to a uniformcloudLayer2CoverageMeansheet, no field, no tiling.cloudLayer2CoverageSpreadwas removed as an option (its arg slot kept aspad_cloudLayer2CoverageSpreadto preserve CB layout). Type spread is still available. -
Independent deck color. New
cloudLayer2Color(Vector3, defaults to cloudColor's near-white).marchCloudLayersoverridesargs2.cloudColor. The one look knob split from layer 1, per user request. -
GPU-hang backstop.
marchEchoDeckhard-caps the step count atkEchoStepHardCap = 256so a future CB misread can't escalate to a device hang. -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_common.slangh— fork-owned change.evalNubisCubedSamplegains a trailingfloat dSunOverrideparam:< 0samples theD_sunvoxel grid (production path, byte-identical to before);>= 0is used verbatim and the grid tap is skipped. Only the echo-deck march passes>= 0. -
src/dxvk/shaders/rtx/pass/atmosphere/cloud_march_common.slangh— fork-owned change. NewmarchEchoDeck(lean second-deck march:cloudLayer2StepFloor/cloudLayer2StepMaxstep budget — default 8/16 — honoringcloudViewStepKm; analytic ; otherwise identical density model + control fields + per-sample moon block + Beer-Lambert/aerial asmarchCloudSlab). The moon shadow uses a new slab-parametricsampleCloudSunOpticalDepth_localSlabso the deck self-shadows against ITS altitude band (the args-default helper keys on layer 1's slab → zero density at the deck = unshadowed).marchCloudLayers' layer-2 branch callsmarchEchoDeckinstead of a secondmarchCloudSlab(the old moon-coefficient zeroing is gone — the deck wants moonlight).marchCloudSlab'sevalNubisCubedSamplecall passesdSunOverride = -1.0(grid path). Both consumers (view RT + secondary dome LUT) inherit viamarchCloudLayers. -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h— fork-owned change. Adds a 16-byte (vec4) tail block:cloudLayer2StepFloor,cloudLayer2StepMax, +pad_cloudLayer2Step0/1. No free pad slots remained, so the CB grows — but it MUST grow by a whole vec4 orsizeof(AtmosphereArgs)stops being 16-byte aligned and the whole-structupdateBuffercorrupts the cbuffer (the new tail fields read garbage → marchEchoDeck's step count blows up → GPU hang → solid black when layer 2 is on). A first draft appended bare scalars (8 bytes) and hit exactly that; the two pad words fix it. Future additions should consume the pads first. -
src/dxvk/rtx_render/rtx_options.h— fork-owned change. AddedcloudLayer2StepFloor(uint32, 8) andcloudLayer2StepMax(uint32, 16). RewrotecloudLayer2Enable/cloudLayer2Altitude/cloudLayer2Thickness/cloudLayer2DensityScalehelp strings to describe the echo deck + inter-deck gap; corrected three stale default-value mentions (7.5 km / 0.5 km / 0.30 vs the actual 5.5 / 2.0 / 0.65). No option removed or renamed. -
src/dxvk/rtx_render/rtx_atmosphere.cpp— fork-owned change. Packsargs.cloudLayer2StepFloor/cloudLayer2StepMaxfrom the new RtxOptions alongside the existingcloudLayer2*packing. -
src/dxvk/rtx_render/rtx_fork_atmosphere.cpp— fork-owned change. Adds "Layer 2 Step Floor" / "Layer 2 Max Steps"DragIntwidgets to the Layer 2 imgui node; refreshed the Layer 2 Density tooltip off the old cirrus framing. -
RtxOptions.md— regenerated; reflects the 3 new options (cloudLayer2StepFloor,cloudLayer2StepMax,cloudLayer2Color) and the removedcloudLayer2CoverageSpread.(The
atmosphere_args.h/rtx_options.h/rtx_atmosphere.cpp/rtx_fork_atmosphere.cppbullets above also cover the follow-upcloudLayer2Colorfield/option/packing/picker; the args color field is a second vec4-aligned tail block —vec3 cloudLayer2Color+pad_cloudLayer2Color0. The de-tile attempt was reverted: the deck's coverage tiling is instead handled by forcingcloudCoverageSpreadto 0 and removing thecloudLayer2CoverageSpreadoption, see below.)
Workstream — Dead-code removal + stale-doc cleanup (fork — 2026-06-21)
Audit-driven cleanup of the sky/cloud system; no behavior change. Removed two unused shader helpers — evalSunDisk (atmosphere_sky.slangh) and uvToSkyViewLutParams (atmosphere_common.slangh); removed the orphaned rtx.atmosphere.sunDisc option (never consumed — the sun disc renders via the sun-as-distant-light / NEE path) along with its RemixSkyAPI.md row and a RemixApiChangelog.md "Removed" entry. Demoted the three dead cloudVoxelGrid* dirty/offset args fields to pad_* reserves (CPU zero-writes dropped) — kept as pads so the CB layout is unchanged. Corrected ~30 stale comments + 3 user-facing RTX_OPTION descriptions that referenced the long-removed analytical evalClouds path / "no consumer in this commit" voxel grids as if current; each description was re-verified against its consumer before rewording. RtxOptions.md regenerated.
src/dxvk/rtx_render/rtx_options.h— fork-owned change. Removed thesunDiscRTX_OPTION; rewordedcloudRenderRTEnable/cloudSecondaryLutEnable/cloudHeightLutEnabledescriptions to drop the removed-evalClouds references (off = cloudless; secondary rays get clouds from the dome LUT). The "original 17 atmosphere options" historical list earlier in this file still namessunDisc— left as history.src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_sky.slangh,atmosphere_common.slangh— fork-owned change. DeletedevalSunDisk/uvToSkyViewLutParams; comment fixes for removed-evalClouds references.src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h— fork-owned change.cloudVoxelGridFrameOffset/…SunDirty/…AmbientDirty→pad_*; comment fixes.- Comment-only doc-rot fixes in
cloud_render.comp.slang,cloud_secondary_lut.comp.slang,cloud_march_common.slangh,common_bindings.slangh,common_binding_indices.h,debug_view.comp.slang,rtx_atmosphere.cpp/.h,rtx_debug_view.cpp. No functional change. RtxOptions.md/docs/RemixSkyAPI.md/docs/RemixApiChangelog.md— regenerated /sunDiscrow removed /[0.4.3] Removedentry added.
Workstream — Cloud drift overhaul: field evolution (fork — 2026-06-21)
Replaces the artificial-looking cloud "drift." The old motion had two pieces: rigid wind advection (fine) and a separate global weather-parameter drift (rtx_fork_weather.cpp) that modulated 9 cloud scalars with a sum-of-sines whose fast layer had base period exactly 30 s — its dominant inner sine was a clean 30 s beat, and all 9 fields shared one phase clock, so the whole sky visibly "breathed" in lockstep every ~30 s. The fix moves shape change into the field (differential advection, the Nubis/Decima trick) and demotes the global scalar drift to genuinely slow weather-scale change. Two new view-path levers, both reverting to legacy rigid behavior at speed 0: morph (a slow 3D scroll of the base noise sample position, Y-dominant so it samples a continuously different, tile-wrapping slice → formations form/dissolve in place, also breaking the 12 km wind tile-repeat) and edge boil (an independent faster scroll on the edge-detail tap → billows churn at the silhouette). No constant-buffer growth — four reserve pad slots are reused.
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h— fork-owned change. Reuses the three contiguouspad_cloudVoxelGrid{FrameOffset,SunDirty,AmbientDirty}slots (from the 2026-06-21 dead-code pass) ascloudEvolutionOffsetX/Y/Z(float; the two formeruintslots retype to float), and thepad_cloudLayer2CoverageSpreadslot ascloudBoilPhase. CB layout byte-identical.src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_common.slangh— fork-owned change. InsampleCloudDensityTextured(view path only): buildsnoiseSamplePos = perturbed + cloudEvolutionOffsetand a parallelnoiseTexcoord, used for the base 3D tap (incl. hex variants) and the step-3b footprint slice ONLY — placement map, hex lattice, column model, and height fraction stay on the realperturbed/texcoordso cluster location and altitude are unaffected. Step 4b detail tap addscloudBoilPhase * kBoilDir(fixed off-axis unit dir) to its sample position. The shadow sampler (sampleCloudDensityForShadow) is intentionally NOT evolved in v1 (baked grids keep describing the bulk field).src/dxvk/rtx_render/rtx_atmosphere.cpp— fork-owned change. IngetAtmosphereArgs(): accumulates the morph offset fromcloudEvolutionSpeed/cloudEvolutionVerticalBias(Y-dominant + diagonal XZ remainder) and the boil phase fromcloudBoilSpeed, both ×timeSeconds(mirrors the existingcloudWindOffsetpattern). Zeros the four new per-frame fields innormalizeForSkyLutCacheso the sky-LUT memcmp gate doesn't fire every frame.src/dxvk/rtx_render/rtx_options.h— fork-owned change. AddscloudEvolutionSpeed(0.0015),cloudBoilSpeed(0.004),cloudEvolutionVerticalBias(0.8) to thertx.atmospherecluster.src/dxvk/rtx_render/rtx_fork_atmosphere.cpp— fork-owned change. Adds an "Evolution" subtree (Morph Speed / Morph Vertical Bias / Edge Boil Speed) to Atmosphere → Clouds, next to "Wind". Named distinctly from the weather "Cloud Drift" subtree to avoid confusion.src/dxvk/rtx_render/rtx_fork_weather.cpp— fork-owned change. De-pulse:driftOffsetForFielddrops the fast 30 s layer (slow-only now); removedkDriftFastPeriodSec.kDriftTabletrimmed 9 → 3 (keptcloudCoverageMean,cloudWindSpeed,cloudWindDirection— the weather-scale fields field evolution does not reproduce; dropped the shape-ish density/thickness/type/anvil/coverage-spread entries now owned by field evolution).static_assertupdated to 3.RtxOptions.md— REGEN PENDING (run Remix withDXVK_DOCUMENTATION_WRITE_RTX_OPTIONS_MD=1): will add the 3 new options.
Workstream — Cloud motion unification + advection integrator fix (fork — 2026-06-21)
Folds the three cloud-motion sources (wind advection, field-evolution morph/boil, and the slow weather-parameter drift) into ONE per-frame integrator, fixing a latent bug. The wind/evolution/boil offsets were computed stateless as instantaneousSpeed * timeSeconds inside the const getAtmosphereArgs (called ~12×/frame, hence stateless). That is only correct while wind is constant — but the weather blender writes drift-modulated cloudWindSpeed/cloudWindDirection into the Derived config layer, so each frame the entire accumulated offset re-scaled (speed change) or rotated about the origin (direction change), snapping the whole field. Replaced with persistent accumulators advanced once per frame by offset += velocity * dt, so a varying wind eases the field instead of jumping. Rates stay independent (no cross-coupling). No CB/shader change — only how the CPU fills the existing cloudWindOffset / cloudEvolutionOffset* / cloudBoilPhase fields. Also: merged the "Wind" + "Evolution" dev-menu subtrees into one "Cloud Motion" tree, and bumped cloudLayer2StepMax 16 → 32.
src/dxvk/rtx_render/rtx_atmosphere.h— fork-owned change. Addsvoid advanceCloudMotion(float dt)(public) and three persistent accumulator membersm_cloudAdvectOffset(Vector2) /m_cloudEvolutionOffset(Vector3) /m_cloudBoilPhase(float), next to the other per-frame-pushed members.src/dxvk/rtx_render/rtx_atmosphere.cpp— fork-owned change. ImplementsadvanceCloudMotion(integrates wind from drift-modulatedcloudWindSpeed/cloudWindDirection, plus morph + boil,+= vel*dt, with adt<=0pause guard).getAtmosphereArgsnow reads the three members instead of computingspeed*timeSeconds.normalizeForSkyLutCachealready zeros those per-frame fields (unchanged).src/dxvk/rtx_render/rtx_fork_atmosphere.cpp— fork-owned change. Callsctx.m_atmosphere->advanceCloudMotion(GlobalTime::get().deltaTime())once per frame inupdateAtmosphereConstants(Numos block, alongside the camera-basis push); adds#include "../util/util_global_time.h". Merges the Wind + Evolution UI subtrees into one "Cloud Motion" tree with a pointer to the Weather → Cloud Drift slow modulator.src/dxvk/rtx_render/rtx_options.h— fork-owned change.cloudLayer2StepMaxdefault 16 → 32 (per request — layer-2 echo-deck step budget to match layer 1'scloudViewSamples = 32).RtxOptions.md— REGEN PENDING: reflects thecloudLayer2StepMaxdefault change.
Workstream — Rename weather "Cloud Drift" → "Weather Variation" (fork — 2026-06-21, UI clarity)
Pure dev-menu/label rename to stop the word collision with the new Atmosphere → Clouds → "Cloud Motion" tree. The weather-parameter wander (slow, preset-driven modulation of coverage + wind) is a different layer from the per-frame field motion; calling both "drift"/"motion" in adjacent panels was confusing. The __weather.drift_* GameStateStore API keys and the internal m_drift* members are UNCHANGED — only user-facing strings moved.
src/dxvk/rtx_render/rtx_fork_weather.cpp— fork-owned change. Subtree "Cloud Drift" → "Weather Variation"; slider/button labels "Drift …" → "Variation …"; tooltips note the unchanged__weather.drift_*keys; added a one-line pointer to Cloud Motion.src/dxvk/rtx_render/rtx_fork_atmosphere.cpp— fork-owned change. Cloud Motion tree's cross-reference text updated to point at "Weather → Weather Variation".docs/CloudSystem.md,docs/integrators/weather-presets.md— updated the dev-menu sub-tree name references (keys unchanged).
Workstream — Remove the bespoke atmosphere sun/moon NEE (fork — 2026-06-21)
Graduates the "sun/moon as real distant lights" work from an experiment-with-fallback to the sole sun/moon path in Numos, deleting the now-redundant bespoke atmosphere NEE that was previously only runtime-gated off. Supersedes the earlier evalAtmosphere*NEE* touchpoint entries above. Runtime-neutral at default settings (the bespoke path was already gated off whenever useDirectionalLights was on, the default). Cloud-on-terrain shadows are unaffected — they fold per-pixel onto the real sun distant light in the integrators (gated on atmosphereCloudShadowed + cloudVoxelShadowsEnable); the volumetric fold via sampleAtmosphereSunLightVolume is unchanged.
src/dxvk/shaders/rtx/algorithm/integrator_direct.slangh— fork-owned change. DeletedevalAtmosphereSunNEE+evalAtmosphereMoonNEEand their gated call block; the direct path now lights the atmosphere sun/moon purely via the standard NEE on the injected distant lights.src/dxvk/shaders/rtx/algorithm/integrator_indirect.slangh— fork-owned change. DeletedevalAtmosphereSunNEESecondary+evalAtmosphereMoonNEESecondaryand their gated secondary call block.src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_common.slangh— fork-owned change. Deleted the now-orphaned surface samplerssampleAtmosphereSunLight/sampleAtmosphereMoonLightand their privatepickMoonhelper (only the bespoke NEE called them). The volumetric samplers (sampleAtmosphereSunLightVolume, …) are retained — still used by the RTXDI volume integrator.src/dxvk/rtx_render/rtx_options.h— fork-owned change. Removed theuseDirectionalLightsRTX_OPTION (injection is now unconditional in Numos) and the deaddebugEnableAtmosphereNeediagnostic; rewordeddirectionalLightRadianceScale(no longer references the toggle).src/dxvk/rtx_render/rtx_atmosphere.cpp— fork-owned change.debugSkyBisectFlagsnow packs only bit 1 (debugEnableSkyMissShading); bit 0 (atmosphere NEE) and bit 2 (directional-lights skip) retired with the bespoke NEE.src/dxvk/rtx_render/rtx_fork_atmosphere.cpp— fork-owned change.fhSyncAtmosphereDistantLightsnow gates onskyMode == Numosonly (dropped theuseDirectionalLightscondition); comments de-experimentalised.docs/CloudSystem.md— updated the cloud-on-terrain shadow section to the real-distant-light fold; correctedcloudShadowStrengthdefault (0 → 0.5).RtxOptions.md— REGEN PENDING (dropsuseDirectionalLights+debugEnableAtmosphereNee).
Workstream — Diffuse-indirect sky radiance scale (fork — 2026-06-28)
Adds the rtx.atmosphere.skyIndirectRadianceScale knob (default 1.0 = physical). Since the sun/moon graduated to real distant lights, the distant-light sun out-radiates the sky and indirect lighting reads dull; raising this multiplies sky radiance gathered by diffuse indirect bounces only. The scale is applied per-ray inside evalSkyRadiance after the sky-view LUT sample, so it never feeds any LUT bake. Eligibility is classified at the lobe-sample site (where the material type is known): only an opaque diffuse reflection/transmission gather opts in — sky seen via specular reflection, refraction, alpha-cutout (opacity transmission), translucent/glass lobes, PSR, or the primary view stays at physical brightness, so reflections keep matching the visible sky.
src/dxvk/rtx_render/rtx_options.h— fork-owned addition. AddsRTX_OPTION_ARGS(rtx.atmosphere, float, skyIndirectRadianceScale, 1.0f, …, args.minValue = 0.0f)aftersunsetSaturation.src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h— fork-owned change. Repurposes the deadpad_cloudAnisotropyslot asskyIndirectRadianceScale; CB layout /sizeof(AtmosphereArgs)unchanged.src/dxvk/rtx_render/rtx_atmosphere.cpp— fork-owned change.getAtmosphereArgspopulates the field (clamped >= 0).normalizeForSkyLutCachezeroes it in the sky-LUT memcmp key — it is applied post-LUT, so dragging the slider must not trigger a rebake.src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_sky.slangh— fork-owned change. Adds the trailingapplySkyIndirectRadianceScaleparameter (default false) toevalSkyRadianceand multiplies the final radiance byargs.skyIndirectRadianceScaleonly when it is set.src/dxvk/shaders/rtx/algorithm/path_state.slangh— fork-owned change. Adds_skyGatherEligible(uint8) + askyGatherEligiblebool property so the indirect integrator knows whether the in-flight ray came from a diffuse sky gather (_flagsis bit-saturated, hence its own byte).src/dxvk/shaders/rtx/algorithm/integrator_indirect.slangh— fork-owned change. InitializesskyGatherEligible(false inpathStateCreateEmpty;!firstSampledLobeIsSpecularat first bounce); reclassifies it after each continuation sample usingmaterialType+ the opaque lobe enum; passes it asapplySkyIndirectRadianceScaleat the physical-atmosphere sky miss.src/dxvk/rtx_render/rtx_fork_atmosphere.cpp— fork-owned change. Adds the "Sky Indirect Scale" ImGui slider (0–20) + tooltip in the atmosphere UI.RtxOptions.md— REGEN PENDING (addsrtx.atmosphere.skyIndirectRadianceScale).
Workstream — Cloud detail-shading pass (fork — 2026-07-14)
Attacks the "blobby clouds" read at its root: the edge-detail field previously only wobbled the coverage threshold (silhouette), while every lighting input (D_sun grid, dim profile, SDF proxy) is km-scale smooth — so threshold-grown billows were invisible inside the cloud body. The same detail signal now also SHADES: billow micro-AO (grown knuckles brighten, carved crevices darken, gated by SDF surface proximity so cores stay clean), a Schneider powder term (thin sun-facing wisps darken; faded toward the sun so silver linings survive), a wispy-base/billowy-top character flip over the bottom quarter of each column, and a height-fading horizontal shear on the detail tap. View path only; the cheap shadow sampler and the validated self-shadow bakes are untouched; every knob at 0 is bit-identical legacy.
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_common.slangh— fork-owned change.sampleCloudDensityTexturedgrows anout float detailSigned(zero-mean character-shaped detail signal; convenience overload forwards a dummy) and step 4b gains the character flip + base shear; the tap now also runs when only micro-AO wants it.evalNubisCubedSamplegains thedetailSignedparam plus the micro-AO (ambient + MS body lobe only, clamp [0.15, 1.25]) and powder blocks.src/dxvk/shaders/rtx/pass/atmosphere/cloud_march_common.slangh— fork-owned change. Both march loops (layer 1 + echo deck) threaddetailSignedfrom the density sampler into the lighting evaluator.src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h— fork-owned change. Reclaims thepad_cloudShadowTint(vec3) +pad_cloudShadowTintStrengthrow ascloudMicroAoStrength/cloudPowderStrength/cloudDetailHeightCharacter/cloudDetailBaseShearKm; CB layout unchanged.src/dxvk/rtx_render/rtx_options.h— fork-owned additions. 4 RTX_OPTIONs in thertx.atmospherecluster aftercloudDetailScale:cloudMicroAoStrength(0.6),cloudPowderStrength(0.5),cloudDetailHeightCharacter(0.7),cloudDetailBaseShearKm(0.2).src/dxvk/rtx_render/rtx_atmosphere.cpp— fork-owned change.getAtmosphereArgspopulates the four fields.src/dxvk/rtx_render/rtx_fork_atmosphere.cpp— fork-owned change. 4 sliders (Detail Shading / Powder Darkening / Base Wispiness / Base Wisp Shear) in the Detail & Edges tree.RtxOptions.md— REGEN PENDING (4 new options).
Workstream — Cloud dramatic shading (fork — 2026-07-14)
Adds the contrast axis the Nubis ambient lacked (reference: CoD4 iw3xo daynight cumulus). The sky-ambient fill (topAmbient) was the one light term with no response to D_sun, so it reflooded sun-shadowed bulk with bright daytime sky light and put a high flat floor under the shading — clouds read soft/flat however the direct lobes were tuned. evalNubisCubedSample now attenuates the top-down ambient by exp(-0.6 * D_sun) (internal diffusion-flavored sigma, well below the beam sigma), lerped in by the O(1) cloudAmbientShadowStrength knob: sunlit faces / silver linings (D_sun ~ 0) keep full ambient, shaded cores plunge dark. The sky-dome underside fill (cloudSkyAmbientFill) is deliberately exempt — it models open-sky light arriving from below/around (not through) the cloud, and stays the tunable underside floor. Echo deck inherits via its analytic dSunProxy; secondary-ray LUT re-bakes on slider change (field not zeroed in the cache-key normalizers). 0 = bit-identical legacy. Also fixes the INVERTED cloudMsScale doc/tooltip (higher = darker shadowed bulk, not brighter).
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_common.slangh— fork-owned change.evalNubisCubedSample: ambientShadow block ahead of the ambient composite; multipliestopAmbientonly.src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h— fork-owned change. Reclaimspad_cloudMultiScatterStrengthascloudAmbientShadowStrength; CB layout unchanged.src/dxvk/rtx_render/rtx_options.h— fork-owned addition.cloudAmbientShadowStrength(default 0.6) aftercloudSkyAmbientFill;cloudMsScaledoc direction fixed.src/dxvk/rtx_render/rtx_atmosphere.cpp— fork-owned change.getAtmosphereArgspopulates the field (next tocloudMsScale).src/dxvk/rtx_render/rtx_fork_atmosphere.cpp— fork-owned change. "Ambient Shadowing" slider in the Lighting tree (between Bottom Darkening and Sky Fill); Multi-Scatter tooltip direction fixed.RtxOptions.md— REGEN PENDING (1 new option +cloudMsScaledoc fix).
Workstream — Lightning (fork — 2026-07-14)
In-cloud lightning flashes plus a synchronized transient scene light, driven by a CPU strike scheduler. Strikes fire via a per-frame Bernoulli draw at lightningStrikesPerMinute (memoryless Poisson — adapts instantly to the weather blender's continuous rate ramp), place themselves in a 1 km–lightningRangeKm annulus around the camera at cloud-base height, and run a ~70 ms-decay envelope with 0–2 restrike pulses. The in-cloud emissive scales with local density via the Beer-Lambert accumulation, so a strike where the column model placed no cloud lights nothing. The flash is compile-gated (CLOUD_MARCH_LIGHTNING) to the screen cloud pass only — the persistent secondary-ray cloud LUT must never bake a transient flash. lightningEnable (default TRUE) is a master mute; the rate (default 0) is the real switch, raised by storm weather presets (thunderstorm 12/min, rainstorm 4/min) via the new 53rd preset field lightningStrikesPerMinute.
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h— fork-owned addition. Two new vec4 rows at the struct end: (lightningStrikePosKm,lightningFlashIntensity) and (lightningColor,lightningEnvelope— the raw envelope, so the scene light calibrates independently).src/dxvk/shaders/rtx/pass/atmosphere/cloud_march_common.slangh— fork-owned addition.CLOUD_MARCH_LIGHTNINGgate (default 0) +evalLightningFlash(inverse-square, 0.5 km soft core, 0.35/km extinction reach) added to both march loops' in-scatter source.src/dxvk/shaders/rtx/pass/atmosphere/cloud_render.comp.slang— fork-owned addition. DefinesCLOUD_MARCH_LIGHTNING 1before including the march header (sole flash consumer).src/dxvk/rtx_render/rtx_atmosphere.h/rtx_atmosphere.cpp— fork-owned additions.advanceLightning(dt)scheduler (xorshift32, envelope decay, restrikes, annulus placement) +requestLightningStrike()static latch for the Test Strike button;getAtmosphereArgspublishes the lightning CB fields;normalizeForSkyLutCachezeroes them (per-frame animated, never feeds a LUT bake).src/dxvk/rtx_render/rtx_fork_atmosphere.cpp— fork-owned additions.advanceLightningtick after the camera-position push; transientRtSphereLight(150 m emitter, persistent handle, zero-radiance between strikes, NOT cloudShadowed) infhSyncAtmosphereDistantLights; "Lightning" ImGui tree (enable + Test Strike + rate/intensities/range/color).src/dxvk/rtx_render/rtx_options.h— fork-owned additions. 6 RTX_OPTIONs:lightningEnable(true),lightningStrikesPerMinute(0),lightningFlashIntensity(50),lightningSceneLightIntensity(2000),lightningRangeKm(10),lightningColor(blue-white).src/dxvk/rtx_render/rtx_fork_weather.h/rtx_fork_weather.cpp— fork-owned additions.lightningStrikesPerMinuteas weather-preset field 53 (WK_Scalar, Clouds → Lightning): FIELD_LIST row + all 12 preset value macros + the four hand-listed sites (tooltip map,snapshotRenderer,WVARIES, gatedwriteBlendedToDerivedLayer).RtxOptions.md— REGEN PENDING (6 lightning options + 12 preset fields).
Workstream — Weather preset retune (fork — 2026-07-14)
Look-check driven ("thunderstorm looks like a normal day — the sky is blue, there's a lot of light"). Root cause: every preset inherited the clear-day Rayleigh spectrum and only dimmed the sun. The moody presets now retune three axes — sky COLOR (rayleighScattering flattened toward grey; sandstorm inverts the spectrum for an orange-brown sky), MURK (aerosolDensity up), and LIGHT (sunIlluminance cut hard, skyIndirectRadianceScale reduced). cloudShadowStrength is 1.0 in ALL presets (per request). Thunderstorm/rainstorm also tighten cloudAerialFadePerKm so horizon blue can't bleed through the deck. Clear is deliberately untouched. Because rayleighScattering and skyIndirectRadianceScale now vary across presets, their weatherVaries gates fire and both get written during a blend (comment updated at the write site).
src/dxvk/rtx_render/rtx_fork_weather.h— fork-owned change. Per-preset values only (no structural change): shadow 1.0 ×12; graded sky/murk/light retunes for overcast, drizzle, rainstorm, thunderstorm, snow, blizzard, foggy; sandstorm orange sky + warm night sky; smoggy brown-grey; hazy milky; partlyCloudy coverage 0.35 / type 0.65.src/dxvk/rtx_render/rtx_fork_weather.cpp— fork-owned change. Stale "neutral in every preset" gate comment updated inwriteBlendedToDerivedLayer.
Workstream — Lightning ghost suppression (fork — 2026-07-14)
A lightning flash embedded into the cloud temporal smoother's ~1 s EMA (fixed 0.92 history weight in evalSkyRadiance) outlived itself 3–10x; on camera move the reprojected history dragged a sharp-edged "old frame" imprint of the flash-lit deck across the sky. The strike scheduler now tracks a ghost-suppression signal — 1 while a flash is live, decaying over ~0.25 s after — and the temporal blend collapses its history weight by x(1 - 0.8 * fade) (0.92 → ~0.18 at full fade, ~2-frame convergence), so the flash never embeds. The jitter the EMA normally hides is masked by the flash itself; inert at fade 0. Validated in-game.
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h— fork-owned change. Reclaims thepad_cloudSunsetWarmthslot aslightningHistoryFade; CB layout unchanged.src/dxvk/rtx_render/rtx_atmosphere.h/rtx_atmosphere.cpp— fork-owned change.m_lightningHistoryFadetracked at the end ofadvanceLightning(max of envelope and a tau-0.25 s decay); published bygetAtmosphereArgs; zeroed innormalizeForSkyLutCache.src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_sky.slangh— fork-owned change. Temporal-smoothing block scaleskHistoryWeightby the fade before the history lerp.
Workstream - Nubis3 conversion Phase A: cloud NVDF SDF bake (fork - 2026-07-16)
Foundation for converting the Numos cloud MODELING to the Nubis3 (SIGGRAPH
2023) voxel/SDF architecture. The procedural cloud BODY (placement map +
column model - our "Frankencloudscape") is voxelized into a tile-periodic
256x64x256 occupancy grid and distance-transformed by a wrap-aware jump-
flooding (JFA) chain into a REAL signed distance field (R16F, raw km,
negative inside; texture y = VERTICAL - see cloud_nvdf.h). Double-buffered
publish-swap; full synchronous chain at init, amortized 2-JFA-passes-per-
frame state machine for runtime re-bakes (dirty keys: cell/tile size, column
shape knobs, quantized thickness, quantized nominal coverage). Detail noise
never enters the SDF (sample-time erosion per Nubis3). Phase A ships the bake
infra + a debug slice view only - no visual change; the density-model swap
(profile-from-SDF behind rtx.atmosphere.nubis3ModelEnable) is Phase B and
SDF sphere-trace stepping is Phase C.
src/dxvk/shaders/rtx/pass/atmosphere/cloud_nvdf.h- NEW fork-owned file. Shared CPU/GPU dims (256x64x256), pass-local binding maps for the three NVDF passes,CloudNvdfJfaArgspush-constant struct.src/dxvk/shaders/rtx/pass/atmosphere/cloud_nvdf_common.slangh- NEW fork-owned file. Seed pack/unpack (R32_UINT, 0xFFFFFFFF invalid), wrap-aware anisotropic voxel-center distance in km, voxel -> tile-UV / height-fraction mapping.src/dxvk/shaders/rtx/pass/atmosphere/cloud_nvdf_occupancy.comp.slang- NEW fork-owned file. VoxelizescomputeCloudColumnat the baked NOMINAL coverage (hexOn=false, no noise taps) into R8 occupancy.src/dxvk/shaders/rtx/pass/atmosphere/cloud_nvdf_jfa.comp.slang- NEW fork-owned file. Push-constant mode 0 = boundary seed init (occupied voxel with an empty 6-neighbor; Y out-of-slab counts empty), mode 1 = jump pass at jumpSizeVoxels comparing wrapped physical-km distances.src/dxvk/shaders/rtx/pass/atmosphere/cloud_nvdf_resolve.comp.slang- NEW fork-owned file. Distance to closest boundary seed, sign from occupancy, +100 km when no seeds exist; writes the BACK SDF buffer.src/dxvk/rtx_render/rtx_atmosphere.h/rtx_atmosphere.cpp- fork-owned change. Resources (occupancy, JFA ping-pong x2, SDF front/back x2, ~54 MB), jump schedule {128..1,1}, dirty-key struct + amortized state machine (stepCloudNvdfBakeincomputeLutsafter the placement-rebake block; placement rebake clears the NVDF key), full init chain ininitialize(), three dispatch fns + 4 ManagedShader classes,getCloudNvdfSdf()front-buffer getter,args.nvdfNominalCoveragefill (auto = live coverage quantized to 0.25 steps, option pins).src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h- fork-owned change. Batched pad consumption (CB layout unchanged, one edit for the whole phase):pad3->nubis3SharpenStrength,padMilkyWay0->nvdfStepScale,pad_sunShadowMaxSamples->nubis3ModelEnable,pad_moonShadowMaxSamples->nvdfNominalCoverage,pad_cloudLayer2Step0/1->nvdfProfileDepthKm/nvdfCoverageOffsetKm,pad_cloudLayer2Color0->nubis3ErosionStrength. OnlynvdfNominalCoverageis consumed in Phase A; the rest are zero-filled until their phase.src/dxvk/rtx_render/rtx_options.h- fork-touchpoint inline tweak. Addsrtx.atmosphere.nvdfNominalCoverage(0 = auto-track quantized weather coverage).src/dxvk/rtx_render/rtx_context.h- fork-touchpoint inline tweak.fork_hooks::getCloudNvdfSdfforward declaration + friend line (mirrors getCloudDSun).src/dxvk/rtx_render/rtx_fork_atmosphere.cpp- fork-owned addition.getCloudNvdfSdflazy-init accessor.src/dxvk/shaders/rtx/utility/debug_view_indices.h- index-only, fork. AddsDEBUG_VIEW_CLOUD_NVDF_SDF = 879.src/dxvk/shaders/rtx/pass/debug_view/debug_view_binding_indices.h- index-only, fork. AddsDEBUG_VIEW_BINDING_CLOUD_NVDF_SDF_INPUT = 43(extends the fork cloud-debug block to 39-43).src/dxvk/shaders/rtx/pass/debug_view/debug_view.comp.slang- fork-owned addition.Texture3D<float> DebugViewCloudNvdfSdf+ case 879: horizontal slice at height fraction 0.25, warm-inside / cool-outside ramps, green zero-crossing band.src/dxvk/rtx_render/rtx_debug_view.cpp- fork-owned addition.TEXTURE3D(43)parameter entry, bind viafork_hooks::getCloudNvdfSdf, selector entry with validation gates (smooth blobby cells, clean iso-line, seamless tile wrap, live re-bake on cell-size drag).RtxOptions.md- REGEN PENDING (nvdfNominalCoverage).
Workstream - Nubis3 conversion Phase B: SDF density model + evaluator move (fork - 2026-07-16)
The density-model swap, behind rtx.atmosphere.nubis3ModelEnable (default
OFF - legacy path bit-identical for in-game A/B). Cloud shape becomes the
Nubis3 recipe: dimensional profile = sample-time remap of the (Phase A)
body SDF with live coverage as a level-set offset (zero-rebake coverage
dynamics), eroded by a new wispy/billowy detail volume via the Schneider
ValueErosion remap + page-123 pow sharpen. ONE sampler serves the view march
AND the shadow/OD bakes (iso parity by construction; also erases the legacy
view-0.15 vs shadow-0.25 gate-softness parity gap). The Nubis Cubed lighting
evaluator MOVED out of atmosphere_common.slangh into cloud_march_common.slangh
(its only callers) and split into a parameterized core (real profile / live
SDF meters / D_ambient-derived downTau) + a bit-identical legacy wrapper -
lighting edits no longer recompile the path tracer. Echo deck stays on the
legacy sampler (deferred to Phase E).
src/dxvk/shaders/rtx/pass/atmosphere/cloud_detail_noise_baker.comp.slang- NEW fork-owned file. RGBA8 volume: R/G = low/high-freq wispy (Perlin FBM), B/A = low/high-freq billowy (inverted Worley), integer cycles-per-volume periods; baked once at init (fixed pattern).src/dxvk/shaders/rtx/pass/atmosphere/cloud_nubis3_common.slangh- NEW fork-owned file.valueErosion,sampleCloudDensityNubis3(hex-de-tiled SDF tap -> coverage offset -> profile gate/early-out -> animated wispy/billowy erosion -> sharpen; outs profile/detailSigned/live-sdf-km), plus the MOVED D_sun/D_ambient bake integrals with a model-branched integrand.src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_common.slangh- fork-owned change (the phase's one batched edit). REMOVES the moved blocks: the two OD bake integrals and the whole Nubis lighting evaluator block (sampleDimProfile / sampleCloudSdf / hgPhaseNubis / NubisCubedLighting / evalNubisCubedSample); short pointers left in place. sampleDSun/sampleDAmbient + ground-shadow helpers stay (integrators need them).src/dxvk/shaders/rtx/pass/atmosphere/cloud_march_common.slangh- fork-owned change. Receives the evaluator asevalNubisCubedSampleCore(dim_profile, cloud_sdf_m, downTau, ...)+ legacy wrapper; includes cloud_nubis3_common; marchCloudSlab branches density (Nubis3 sampler vs legacy) and lighting (core with real SDF into the sigma_ms remap +sampleDAmbient x cloudDensityas downTau vs wrapper); layer-1 moon OD tap branches likewise. marchEchoDeck untouched (legacy).src/dxvk/shaders/rtx/pass/atmosphere/cloud_render.comp.slang/cloud_secondary_lut.comp.slang- fork-owned change. Pass-local bindings 13 =AtmosphereCloudNvdfSdf(front buffer), 14 =AtmosphereCloudDetailNoise3D; sampled with the existing linear/REPEAT cloud sampler (slot 2).src/dxvk/shaders/rtx/pass/atmosphere/cloud_sun_density_grid.comp.slang/cloud_ambient_density_grid.comp.slang- fork-owned change. Pass-local bindings 5 = NVDF SDF, 6 = detail volume; call the moved integrals with the extended signature. Terrain cloud shadows track the model switch with zero integrator changes.src/dxvk/rtx_render/rtx_atmosphere.h/rtx_atmosphere.cpp- fork-owned change.m_cloudDetailNoise3D( RGBA8, ~8 MB) + one-shot init bake +CloudDetailNoiseBakerShader; grid/render/secondary shader classes grow TEXTURE3D slots (5/6 and 13/14) with binds + read-tracking at all four dispatch sites (front SDF + detail); getAtmosphereArgs fills the five Nubis3 CB fields (pads consumed in Phase A).src/dxvk/rtx_render/rtx_options.h- fork-touchpoint inline tweak. Addsrtx.atmosphere.nubis3ModelEnable(false),nvdfProfileDepthKm(1.0),nvdfCoverageOffsetKm(1.5),nubis3ErosionStrength(1.0),nubis3SharpenStrength(1.0).src/dxvk/rtx_render/rtx_fork_atmosphere.cpp- fork-owned addition. ImGui Clouds -> "Nubis3 Model (SDF)" block: enable toggle + profile depth / coverage reach / erosion / sharpen / bake-nominal sliders.RtxOptions.md- REGEN PENDING (5 new options + Phase A's nvdfNominalCoverage).
Workstream - Nubis3 anti-blobby pass + Phase C SDF stepping (fork - 2026-07-16)
Phase B validated ("more natural") but read as convex blobs. Root causes and
fixes, each on its own runtime lever for in-game bisection: (1) the BODIES
were convex by construction (placement blob x column band) - a tile-periodic
3D FBM now shifts the placement waterline per voxel in the occupancy bake, so
columns bake in overhangs/notches/lumps (nvdfBodyErosionStrength, an NVDF
re-bake dirty key; zero-mean so nominal-coverage semantics stay centered);
(2) the detail volume's erosion/wobble content was far too coarse vs Nubis's
own (~sub-100 m; ours was 700/350 m) - baker channels move to 6/16
cycles-per-volume with 4/3 octaves, and the silhouette wobble keys on a fixed
half-mix of the high-freq channels instead of the profile-blend (which
collapsed to pure low-freq exactly at the silhouette); (3) ported the Nubis
p.125 near-camera HF detail (twice-folded high-freq channels replace a slice
of the erosion composite near the camera; range scaled 0.2->2 km for our
coarser cycles-per-km; nubis3HFDetailStrength, 1 = the paper's 10% max mix).
Phase C: the sampler additionally returns a CONSERVATIVE step bound (MIN of
the hex taps - a blended distance is not metric - minus coverage offset and
max outward wobble), and marchCloudSlab jumps whole loop indices through
provably-empty air (nvdfStepScale safety factor, 0 = off; index-quantized so
samples stay on the jittered stratified lattice - no temporal/banding change).
src/dxvk/shaders/rtx/pass/atmosphere/cloud_nvdf_occupancy.comp.slang- fork-owned change. Body-erosion carve: tile-periodicfbmNoise3DPeriodic(6 XZ cycles, thickness-proportional Y cycles, 4 octaves) shiftsplacement.rbeforecomputeCloudColumn.src/dxvk/shaders/rtx/pass/atmosphere/cloud_detail_noise_baker.comp.slang- fork-owned change. Channel frequencies 4/8 -> 6/16 cycles-per-volume, octaves 3 -> 4/3 (finest octaves pinned at the 128-texel Nyquist).src/dxvk/shaders/rtx/pass/atmosphere/cloud_nubis3_common.slangh- fork-owned change. Full-overload signature growscameraDistKmin +sdfStepKmOutout; min-of-taps tracking; p.125 HF detail block; wobble signal rev 2 (fixed high-freq half-mix). Convenience overload (bakes/OD taps) unchanged externally - passes 1e6 dist (HF detail must not make bakes camera-dependent).src/dxvk/shaders/rtx/pass/atmosphere/cloud_march_common.slangh- fork-owned change. marchCloudSlab passes raytas camera distance and, on empty samples, advances the loop index byfloor(sdfStep x nvdfStepScale / stepLen)(4096 hard cap).src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h- fork-touchpoint inline tweak.padMilkyWay1->nvdfBodyErosionStrength,padMilkyWay2->nubis3HFDetailStrength; CB layout unchanged. (All former reserve pads are now consumed - Phase D growth needs a new 16-byte block.)src/dxvk/rtx_render/rtx_atmosphere.h/rtx_atmosphere.cpp- fork-owned change.CloudNvdfBakeKey.bodyErosiondirty key + cache; getAtmosphereArgs fills the two new pads +nvdfStepScale(CB field existed since Phase A, zero-filled until now).src/dxvk/rtx_render/rtx_options.h- fork-touchpoint inline tweak. Addsrtx.atmosphere.nvdfBodyErosionStrength(0.6),nubis3HFDetailStrength(1.0),nvdfStepScale(0.8).src/dxvk/rtx_render/rtx_fork_atmosphere.cpp- fork-owned addition. "Nubis3 Model (SDF)" block grows Body Erosion / HF Detail (Near) / SDF Step Scale sliders.RtxOptions.md- REGEN PENDING (3 more options; 9 total outstanding for the Nubis3 workstreams).
Workstream - Cloud crispness: temporal-smoother weight knob (fork - 2026-07-16)
Follow-up to the anti-blobby pass: with the SHAPE fixed, the remaining "bad" read was smear - the cloud RT runs at 0.5x the DLSS-internal resolution and the temporal smoother blended with a HARDCODED 0.92 EMA weight (~0.5 s settle), so edges arrived pre-blurred and doubly upscaled. Render scale was already a live option; this makes the EMA weight one too. Consumes the first scalar of a NEW 16-byte CB block appended at the AtmosphereArgs tail (all former reserve pads were spent by the Nubis3 workstreams; 3 reserve pads remain for Phase D dynamics).
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h- fork-touchpoint inline tweak. NEW tail block:cloudHistoryWeight+padReserve0/1/2(16-byte aligned growth per the CB discipline).src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_sky.slangh- fork-owned change. evalSkyRadiance temporal blend readsargs.cloudHistoryWeight(saturated) instead of the hardcodedkHistoryWeight = 0.92; lightning ghost-suppression modulation unchanged on top.src/dxvk/rtx_render/rtx_atmosphere.cpp- fork-owned change. getAtmosphereArgs fills the new block (weight clamped [0..0.98], reserve pads zeroed); normalizeForSkyLutCache zeroes the weight (composite-only - slider drags must not invalidate the sky-LUT cache key).src/dxvk/rtx_render/rtx_options.h- fork-touchpoint inline tweak. Addsrtx.atmosphere.cloudHistoryWeight(0.92 = previous hardcoded behavior; 0 = raw jittered march).src/dxvk/rtx_render/rtx_fork_atmosphere.cpp- fork-owned addition. "Temporal Smoothing" slider next to Cloud Render Scale.RtxOptions.md- REGEN PENDING (10 options now outstanding across the Nubis3 + crispness workstreams).
Workstream - Nubis3 interior texture + alligator noise (fork - 2026-07-16)
User verdict on the anti-blobby knobs: "barking up the wrong tree - the
implementation is missing a core feature." Re-reading both references
(Nubis Cubed pp.89-99; iw3xo ps_3_0_iw3xo_daynight.hlsl) confirmed it:
(1) OUR DENSITY SATURATED TO A CONSTANT 1.0 inside the body (ValueErosion at
profile 1 is 1 regardless of noise), so every face beyond the thin silhouette
skirt rendered as a flat white mass. Both references keep density
proportional to the noise EVERYWHERE inside: iw3xo multiplies its razor gate
by the raw FBM (dens *= smoothstep(cov, cov+0.05, dens)), Nubis3 multiplies
by its authored per-voxel Density Scale NVDF (uprezzed_density *= powered_density_scale). New sampler step 8 modulates the post-sharpen
density by the type-blended raw detail channels (nubis3InteriorTexture,
default 0.7, rides padReserve0; shared sampler, so the D_sun/D_ambient bakes
track automatically). (2) The detail volume used the EXACT noises the talk
rejects (p.98: inverted Worley = "packed spheres", Perlin "not wispy
enough") - the baker now approximates their replacements: alligator3DPeriodic
(sparse-bump, strongest-minus-runner-up = amplitude-layered lumps with
creases) for billows, curl-warped inverted alligator ("Curly-Alligator",
periodic curl of three Perlin FBM potentials) for wisps.
src/dxvk/shaders/rtx/pass/atmosphere/cloud_detail_noise_baker.comp.slang- fork-owned change. NEWalligator3DPeriodic/alligatorFbm3DPeriodic/curlNoise3DPeriodichelpers (local to the baker - one-shot init cost); channels rebuilt: R/G = curly-alligator wisps (pow-2 to recenter mean ~0.5 for the zero-mean wobble), B/A = alligator billows.src/dxvk/shaders/rtx/pass/atmosphere/cloud_nubis3_common.slangh- fork-owned change. Sampler step 8: interior density modulation, range [0.25, 1.75] around a roughly density-neutral mean, applied post-sharpen pre-saturate.src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h- fork-touchpoint inline tweak.padReserve0->nubis3InteriorTexture; CB layout unchanged (2 reserve pads left).src/dxvk/rtx_render/rtx_atmosphere.cpp- fork-owned change. getAtmosphereArgs fills it (clamped [0..1]).src/dxvk/rtx_render/rtx_options.h- fork-touchpoint inline tweak. Addsrtx.atmosphere.nubis3InteriorTexture(0.7).src/dxvk/rtx_render/rtx_fork_atmosphere.cpp- fork-owned addition. "Interior Texture" slider at the top of the Nubis3 block.RtxOptions.md- REGEN PENDING (11 options outstanding).
Workstream - Nubis3 edge wisp cut (fork - 2026-07-16)
User feedback after validating the interior-texture round ("helped a lot"):
"the erosion should cut out wisps and stuff." Root cause: the base erosion
signal tops out ~0.4 at liked settings (type-blended composite mean ~0.35 x
strength 0.6), so ValueErosion only nibbles the outer ~40% of the profile -
it can never cut THROUGH the shell and detach a wisp shape. Fix: (1) sampler
step 7b adds erosion shaped by the WISPY channel alone, weighted
(1-profile)^2 - strand-shaped cuts reach full depth at the silhouette and
fade by mid-shell, so billowy cores keep rounded edges at any type
(nubis3EdgeErosion, default 1.0, rides padReserve1 - ONE reserve pad
left); (2) the baker's wispy channels are now anisotropic (2x vertical cycle
count = features twice as wide as tall; integer per-axis periods keep exact
tiling), so the cuts read as sheared trailing streaks instead of round webs.
src/dxvk/shaders/rtx/pass/atmosphere/cloud_nubis3_common.slangh- fork-owned change. Step 7b edge-wisp erosion added into the ValueErosion input.src/dxvk/shaders/rtx/pass/atmosphere/cloud_detail_noise_baker.comp.slang- fork-owned change. kWispSqueeze (1,2,1) anisotropy on the wispy channels' sample coords, curl field, and periods.src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h- fork-touchpoint inline tweak.padReserve1->nubis3EdgeErosion; CB layout unchanged.src/dxvk/rtx_render/rtx_atmosphere.cpp- fork-owned change. getAtmosphereArgs fills it (clamped [0..3]).src/dxvk/rtx_render/rtx_options.h- fork-touchpoint inline tweak. Addsrtx.atmosphere.nubis3EdgeErosion(1.0).src/dxvk/rtx_render/rtx_fork_atmosphere.cpp- fork-owned addition. "Edge Wisp Cut" slider after Erosion Strength.RtxOptions.md- REGEN PENDING (12 options outstanding).
Same-day follow-up (user look checks: "alligator noise is really intense / looks fake", "especially looking straight up"):
cloud_detail_noise_baker.comp.slang- alligator tempering: kernel exponent 2.0 -> 1.6 (softer shoulders), amplitude range widened 0.35..1 -> 0.25..1 (lump-size variety), contrast gain 1.8 -> 1.25 (the hard gain saturated lumps into plateaus with razor creases).cloud_nubis3_common.slangh- (1) TWO-SCALE DE-TILE on the detail tap: the volume repeat (~2.8 km) was NOT hex-de-tiled and reads as a grid when a face is seen flat-on (worst at the zenith); a second tap at 0.531x scale blended 40/60 makes the pattern effectively non-repeating, variance re-expanded 1.35x around channel means. (2) Nubis p.125 NEAR-CAMERA DENSITY SOFTEN ported (pow 0.5->1.0, gain 0.666->1.0 over the HF range) — the HF fold without its companion soften read harsh in fly-through. (3) D_SUN SUNSET BANDING FIX in sampleCloudSunOpticalDepthAtWorld (user report: "squares"/"bands" cast into clouds at sunset, both models — the grid predates Nubis3): the fixed 8 taps spread over an uncapped near-horizontal 20-50 km sun path landed kilometers apart, so neighbor voxels integrated random cloud subsets (squares = grid texels) and tap-distance quantization made onion shells perpendicular to the sun (arcs). Fix: path capped at 12 km (integral saturated beyond), adaptive 8..24 taps (~0.6 km step), deterministic world-anchored per-voxel tap-phase jitter (residual error becomes bilinear-absorbed noise, stable across frames).
Workstream - GT7 cross-audit bug fixes (fork - 2026-07-16)
A Fable-subagent audit of our sky/cloud shaders against the GDC23 GT7 talk
(report: Fable_5_testing/gt7-applicability-report.md) surfaced three
verified BUGS, landed here. (Its LUT verdict: our sky-view LUT does NOT have
GT7's misaligned-horizon arc failure — per-0.1-degree re-bake + v^2 horizon
pin — so the in-cloud sunset artifacts trace to the D_sun grid instead.)
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_sky.slangh- fork-owned change. PREMULTIPLY DOUBLE-APPLY: both cloud sources store premultiplied rgb, but the temporal-smoother input didrgb * aagain and the no-smoothing path composited withmix(radiance, rgb, a)(another x a). Opaque cores hid it; THIN cloud radiance scaled quadratically with alpha — wisps and edge skirts everywhere rendered darker than intended. Fixed to the plain premultiplied over in both paths.src/dxvk/rtx_render/rtx_atmosphere.h+cloud_sun_density_grid.comp.slang/cloud_ambient_density_grid.comp.slang- fork-owned change. D_SUN/D_AMBIENT GRID AXIS SWAP: the UVW mapping puts VERTICAL on uvw.y and world Z on uvw.z, but allocation was Y=256/Z=32 — world-Z shadow texels were 375 m wide (the blocky "squares" cast into the deck) while vertical wasted 256 texels on ~12 m. Now X=256, Y(vertical)=32, Z=256; dispatch math derives from the constants, samplers use normalized uvw — constants-only change.src/dxvk/rtx_render/rtx_atmosphere.cpp- fork-owned change. NIGHT RE-BAKE STORM: normalizeForSkyViewLutKey's comment claimed the star / Milky Way fields were zeroed out of the cache key; no zeroing existed anywhere, so the game-driven per-frame starRotation re-baked the transmittance -> multiscatter -> sky-view cascade AND the voxel grids every frame at night. Zeroing added to normalizeForSkyLutCache (base) so all derived keys inherit.
REV 2 (same day, after the user's sunset test — three regressions/misses):
cloud_nubis3_common.slangh- (a) HF fold + soften range 0.2-2 km -> 0.1-0.6 km: the deck base sits ~1.3 km overhead, so the 2 km range repainted the whole zenith view with folded HF noise + flattened contrast ("noise directly above is bad", part of the shadow-wash read). (b) EDGE WISP CUT rev 2: rev 1 never produced cutouts — its (1-profile)^2 band decayed before the erosion could EXCEED the profile (the requirement for ValueErosion to return a hard 0 that pow-sharpen cannot lift back); now pow(wispy,2) x linear band x 1.5 gain — strand cores cut through at profile ~0.4 where the optical edge lives. (c) D_sun rev 2: the 12 km cap had REMOVED real sunset occlusion ("internal shadow casting gone") — added a 4-tap coarse jittered far tail over [12..40 km]; and the intersect-fail path + a ~1.5-deg grazing ramp now return/blend to fully-occluded OD 60 instead of flipping the whole grid to 0 at sun elevation 0 (the "flashes bright orange then vanishes" discontinuity). Tap jitter hash scale 37.1 -> 173.3 (hash31 int-truncates; neighbor voxels now always decorrelate).
Workstream - Nubis3 detail round: sqrt-adaptive march + spectrum/mean fixes (fork - 2026-07-16)
"How do the papers get cloud detail" round — three pillars from the two
reference decks, in priority order. (1) SQRT-ADAPTIVE HYBRID MARCH (Nubis
p.172/174): the fixed 100 m step length was the detail CEILING — the detail
volume carries sub-200 m content the lattice could never resolve.
marchCloudSlab's per-sample body is extracted into integrateCloudSample
(the fixed-step loop is preserved bit-identical for legacy / knob-off), and
a new variable-step loop marches step = max(SDF x nvdfStepScale,
clamp(cloudViewStepKm x sqrt(t / 12 km), floor, 4 x cloudViewStepKm)) — the
existing knob keeps its meaning at the tile distance (100 m at 12 km),
~30 m near the deck base, floor (default 25 m) for fly-through. The SDF
fold subsumes the Phase C index-skip in this mode. Nubis jitter policy:
animated hash < 250 m, static per-pixel hash (FAST-noise frame slice 0)
beyond — no distant shimmer; the cursor stays unjittered (p.174).
Iterations hard-capped by cloudViewSamplesMax. (2) ZERO-MEAN BAKER
NORMALIZATION (GT7 p.228): the detail channels' raw means were MEASURED
(numpy Monte-Carlo port of the alligator kernel; curl warp skipped =
measure-preserving): wispy 0.7224/0.7267, billowy 0.1555/0.1542 — NOT the
~0.5 / 0.3 the sampler consumers assumed. The baker now recenters every
channel to mean 0.5 (pure bias + saturate), making the "zero-mean" wobble
actually zero-mean (was pushing wispy silhouettes OUT ~+0.22 x amplitude,
billowy IN ~-0.10) and the interior-texture factor exactly density-neutral
(was hiding up to ~52% density cut on billowy cores). kChannelMean in the
two-scale de-tile collapses to vec4(0.5). (3) MID-OCTAVE SILHOUETTE LOBES
(GT7 pp.229-230): the silhouette spectrum was 87-875 m against km-scale
smooth bodies — the empty 1-2.5 km octave is GT7's "reads synthetic" gap.
A third tap of the same volume at 0.193x base frequency (low channels only,
~2.4 km lobes) folds into the wobble at 60/40; the combined signal stays in
[-0.5, 0.5] so the step-5 early-out and conservative step bound hold.
(4) ALTITUDE OCTAVE WEIGHTS (GT7 pp.236-237 approx): typeShaped shifts
the wispy<->billowy blend by +-0.25 across the slab height band
(smoothstep 0.1..0.8) — wispy bases, billowy tops — and feeds every
character consumer (erosion composite, HF fold, wobble + mid, interior).
src/dxvk/shaders/rtx/pass/atmosphere/cloud_march_common.slangh- fork-owned change. Per-sample shading extracted tointegrateCloudSample(verbatim; returns sampled?/sdfStepKm); marchCloudSlab + marchCloudLayers growpixelJitterStatic; new adaptive loop gated onnubis3AdaptiveStepKm > 0(FP stall guard at stepKm <= 1e-5).src/dxvk/shaders/rtx/pass/atmosphere/cloud_render.comp.slang- fork-owned change. PassesfastJitter(pixelCoord, 0)as the static far-field hash.src/dxvk/shaders/rtx/pass/atmosphere/cloud_secondary_lut.comp.slang- fork-owned change. Passes its frame-constant jitter for both hashes.src/dxvk/shaders/rtx/pass/atmosphere/cloud_detail_noise_baker.comp.slang- fork-owned change.kRawChannelMean = vec4(0.7224, 0.7267, 0.1555, 0.1542)bias-recenter to 0.5 (measured via scratchpad measure_channel_means.py — RE-MEASURE if kernel gain/exponent/amp/octaves/transforms change).src/dxvk/shaders/rtx/pass/atmosphere/cloud_nubis3_common.slangh- fork-owned change. kChannelMean -> vec4(0.5); step 6d mid-octave wobble tap (0.193x, 60/40);typeShapedaltitude keying replacessaturate(typeLocal)at all four character consumers.src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h- fork-touchpoint inline tweak.padReserve2->nubis3AdaptiveStepKm. THE LAST reserve pad is now consumed — any further CB growth needs a new full 16-byte row.src/dxvk/rtx_render/rtx_atmosphere.cpp- fork-owned change. getAtmosphereArgs fillsnubis3AdaptiveStepKm(clamped [0..0.2]; stays in the LUT cache keys — same class as nvdfStepScale).src/dxvk/rtx_render/rtx_options.h- fork-touchpoint inline tweak. Addsrtx.atmosphere.nubis3AdaptiveStepKm(0.025; 0 = legacy fixed-length stepping).src/dxvk/rtx_render/rtx_fork_atmosphere.cpp- fork-owned addition. "Adaptive Step Floor" slider next to SDF Step Scale.RtxOptions.md- REGEN PENDING (11 options now outstanding across the Nubis3 workstreams).
Workstream - Legacy cloud model retirement (fork - 2026-07-16)
With the Nubis3 detail round user-validated, the pre-Nubis3 density model is DELETED end to end - the A/B toggle, the legacy noise-threshold view sampler and its whole helper web, the Perlin/Worley noise volume + baker, the 64x128 cloud height LUT + baker, four legacy-only look knobs, and their ImGui sliders ("they're making it very hard to tune"). The ECHO DECK (layer 2) was the last legacy consumer - it is CONVERTED to the Nubis3 sampler: the deck now samples the same NVDF/detail model as layer 1 with the density-FIELD position offset by a seed-keyed XZ shift (a decorrelated-but-related copy of the layer-1 bodies at the deck altitude - the original Phase E design), lit by the evaluator core with the analytic dSunProxy and downTau=0. The COLUMN MODEL and placement map SURVIVE - they author the NVDF occupancy bodies - but their view-pass bindings are gone (the placement map is now purely a bake input). Weather-preset ABI: unaffected (audited - no preset field mapped to a deleted option).
DELETED shader files: rtx_cloud_noise_baker.comp.slang,
cloud_height_lut_baker.comp.slang.
DELETED functions (atmosphere_common.slangh): sampleCloudDensityTextured x3,
sampleCloudDensityForShadow x2, sampleCloudPlacement, sampleCloudNoiseHexBlend,
sampleTricubicBSpline, cloudHeightProfileFull x2, sampleCloudHeightLUT,
cloudHeightProfile, cloudHeightProfileDownIntegral x2, cloudTypeProfile,
cloudTypeProfileIntegralFromTop; (cloud_march_common.slangh): sampleDimProfile,
sampleCloudSdf, evalNubisCubedSample legacy wrapper.
DELETED options: cloudAnvilBias, cloudVerticalStretch, cloudEdgeSoftness,
cloudDetailHeightCharacter, cloudWorleyCarveStrength, cloudWorleyFrequency,
cloudWorleyOctaves, cloudNoiseBaseFreqScale, cloudHeightLutEnable,
nubis3ModelEnable (their CB fields became padRetired0-9 - zero-filled
reserve, free for Phase D).
RETIRED binding indices: 203 (cloud noise 3D), 216 (view-pass placement map)
-
annotated do-not-reuse in common_binding_indices.h; BINDING_ATMOSPHERE_MAX repointed to 215.
-
src/dxvk/shaders/rtx/pass/atmosphere/cloud_march_common.slangh- fork-owned change. marchEchoDeck + sampleCloudSunOpticalDepth_localSlab converted to the Nubis3 sampler (fieldOffsetKm seed shift; SDF index-skip added to the deck loop); integrateCloudSample + sampleCloudSunOpticalDepth_local unbranched; adaptive/skip gates drop the model term. -
src/dxvk/shaders/rtx/pass/atmosphere/cloud_nubis3_common.slangh- fork-owned change. sampleCloudBakeDensity unbranched; bake-integral signatures drop the legacy noise/placement params (grid bakers updated). -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_common.slangh- fork-owned change (deletions above). -
src/dxvk/shaders/rtx/pass/atmosphere/cloud_render.comp.slang/cloud_secondary_lut.comp.slang- fork-owned change. Bindings 1/10/11/12 (noise, height LUT + sampler, placement) removed; ATMOSPHERE_CLOUD_HEIGHT_LUT_AVAILABLE gone. -
src/dxvk/shaders/rtx/pass/atmosphere/cloud_sun_density_grid.comp.slang/cloud_ambient_density_grid.comp.slang- fork-owned change. Bindings 2/4 removed. -
src/dxvk/shaders/rtx/pass/common_bindings.slangh/common_binding_indices.h/atmosphere/atmosphere_bindings.slangh- fork-touchpoint inline tweak. Noise/placement decls + layout-macro rows removed; retired indices annotated. -
src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h- fork-touchpoint inline tweak. 10 fields -> padRetired0-9 (layout unchanged). -
src/dxvk/rtx_render/rtx_atmosphere.h/rtx_atmosphere.cpp- fork-owned change. m_cloudNoise3D + m_cloudHeightLut resources, bakers, dispatch fns, re-bake gate, cached inputs, binds/tracks: all removed; retired pads zero-filled in getAtmosphereArgs. -
src/dxvk/rtx_render/rtx_options.h- fork-touchpoint inline tweak (10 options removed). -
src/dxvk/rtx_render/rtx_fork_atmosphere.cpp- fork-owned change. Nubis3 enable checkbox + Anvil Spread / Noise Frequency / Base Wispiness / Edge Softness sliders removed; common-bind of noise/placement removed. -
RtxOptions.md- REGEN PENDING (10 options removed + prior additions outstanding).
Workstream - Fine-frequency detail band (fork - 2026-07-16, detail round follow-up)
External feedback on the GT7 low/mid comparison slides: "you might need another higher-frequency noise texture." Spectrum audit agreed - after the detail round the sqrt-adaptive march resolves 25-30 m near the camera but the detail volume's finest content is ~87 m, so the TEXTURE became the detail ceiling within a few km. GT7-style fix (reuse, zero new memory): a THIRD incommensurate tap of the same volume at 2.63x (content ~177..33 m), folded into the erosion composite and the interior-texture signal via mean-matched lerps (all channels are mean-0.5 by the bake normalization, so coverage is unchanged). Distance-gated full-inside-2km -> gone-by-8km: sub-pixel at range (would only feed the temporal EMA noise) and keeps the OD bakes camera-independent for free (bake taps pass cameraDistKm = 1e6). No wobble term - the step-5 early-out and conservative step bound are untouched. Placed after the step-7 profile gate so gated-out samples never pay the tap.
src/dxvk/shaders/rtx/pass/atmosphere/cloud_nubis3_common.slangh- fork-owned change. Sampler step 7a (fine tap + erosion fold) + step-8 interior fold.src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h- fork-touchpoint inline tweak. padRetired1 -> nubis3FineDetailStrength (first reuse of the retired legacy pads).src/dxvk/rtx_render/rtx_atmosphere.cpp- fork-owned change (args fill, clamp [0..2]).src/dxvk/rtx_render/rtx_options.h- fork-touchpoint inline tweak. Addsrtx.atmosphere.nubis3FineDetailStrength(1.0; 0 = two-band spectrum).src/dxvk/rtx_render/rtx_fork_atmosphere.cpp- fork-owned addition. "Fine Detail" slider after HF Detail (Near).RtxOptions.md- REGEN PENDING.
Workstream - Mid-band shape variety (fork - 2026-07-17)
User reframing of the GT7 low-vs-mid comparison: the mid band belongs in the
SHAPE system, not edges/shading - "actually change the SHAPE of all of the
clouds ... more varied cloud shapes instead of nice round singular blobs."
The existing 2.4 km mid tap gets a DEDICATED level-set displacement on top of
the wobble mix: +-0.5 x nubis3ShapeVarietyKm of iso-surface push/pull, deep
enough to lobe and fully split km-scale bodies. Zero-mean signal ->
coverage-neutral on average; step-5 early-out + conservative step bound
budget the extra excursion. Live, no rebake. (Entry backfilled - shipped in
c3ba707d.)
src/dxvk/shaders/rtx/pass/atmosphere/cloud_nubis3_common.slangh- fork-owned change. Sampler step 6e (level-set displacement) + widened step-5 excursion bound.src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h- fork-touchpoint inline tweak. padRetired2 -> nubis3ShapeVarietyKm (second reuse of the retired pads).src/dxvk/rtx_render/rtx_atmosphere.cpp- fork-owned change (args fill, clamp [0..1.5]).src/dxvk/rtx_render/rtx_options.h- fork-touchpoint inline tweak. Addsrtx.atmosphere.nubis3ShapeVarietyKm.src/dxvk/rtx_render/rtx_fork_atmosphere.cpp- fork-owned addition. "Shape Variety" slider above Body Erosion.RtxOptions.md- REGEN PENDING.
Workstream - Near-field live sun occlusion + mid interior fold (fork - 2026-07-17)
Shape-variety look check: mid-band lobes still read as silhouette recutting ("cloud is a giant blob that's just a slightly different shape now") - no toward/away-camera depth. Root cause: both mid mechanisms move only the SURFACE, and every lighting input is smooth at lobe scale - the D_sun grid bake integrates with ~0.6 km jittered taps + bilinear filtering (lobe-scale self-shadowing is low-passed away) and micro-AO is a nondirectional local-value emboss. Fix = the Nubis Cubed p.129 split ("first light samples live, grid for the far field"), the top open item on the porting-gap list: per lit march sample (density >= 0.05, sun up), 2 live density taps of the displaced field over nubis3SunNearFieldKm + a D_sun grid tap at the range-end offset for the far field (no double count), plumbed through the existing dSunOverride evaluator param - every D_sun consumer (T_primary, body lobe, ambient shadow, warm/cool blend) sharpens together, so each lobe gets its own sunlit face and shadowed crevice. Companion: the step-6d mid tap now also folds into the interior-texture factor (0.35 x saturate(shapeVarietyKm x 2), mean-matched = density-neutral) so lobes differ in optical thickness too - 2.4 km structure is well above the ~0.56 km mean free path, so unlike the fine band it survives to transmittance. Echo deck untouched (its analytic dSunOverride short-circuits the new block).
src/dxvk/shaders/rtx/pass/atmosphere/cloud_march_common.slangh- fork-owned change. integrateCloudSample: near-field live-tap block ahead of the evaluator call.src/dxvk/shaders/rtx/pass/atmosphere/cloud_nubis3_common.slangh- fork-owned change. Step-8 mid-band interior fold.src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h- fork-touchpoint inline tweak. padRetired3 -> nubis3SunNearFieldKm (third reuse of the retired pads).src/dxvk/rtx_render/rtx_atmosphere.cpp- fork-owned change (args fill, clamp [0..3]; padRetired3 zero-fill dropped).src/dxvk/rtx_render/rtx_options.h- fork-touchpoint inline tweak. Addsrtx.atmosphere.nubis3SunNearFieldKm(1.2 km; 0 = grid only).src/dxvk/rtx_render/rtx_fork_atmosphere.cpp- fork-owned addition. "Sun Shadow (Near)" slider after Shape Variety.RtxOptions.md- REGEN PENDING.
Workstream - Nubis3 shipping defaults from the FNV reference tuning (fork - 2026-07-17)
The maintainer's in-game-validated FNV look becomes the shipping defaults:
27 rtx.atmosphere option defaults re-pinned to the reference rtx.conf.
Highlights of the new look: the shaped mechanisms carry the detail (shape
variety 1.11 km, edge wisp cut 2.28, body erosion 1.5, near-field sun
occlusion 3 km) while the uniform ones are off (erosion 0.42, interior
texture 0, fine detail 0, silhouette wobble 0); denser water (cloudDensity
4), broken coverage (mean 0.29, spread 0), full-strength ambient shadow +
cloud ground shadow, full-res cloud RT (resolution scale 1), echo deck off
by default, nvdfNominalCoverage pinned at 0.65 (bodies bake fat, the live
level-set offset at 0.2 km/unit separates them - replaces auto-track as
the default). sunElevation deliberately NOT pinned (live scene state
driven by game wrappers).
src/dxvk/rtx_render/rtx_options.h- fork-touchpoint inline tweak (default values only, no signature/description changes).RtxOptions.md- REGEN PENDING (defaults column).
Workstream — FSR 3.1 upscaler + frame generation (fork — 2026-07-21)
Adds AMD FSR 3.1 as a new upscaler backend (UpscalerType::FSR), a new frame
generation backend (FrameGenerationType::FSR, alongside the existing DLSS-G
path), and a standalone RCAS sharpening pass shared by the upscalers that have
no sharpener of their own. Links against the prebuilt signed
amd_fidelityfx_vk.dll via the submodules/FidelityFX-SDK pin (tag v1.1.4;
import lib + ffx-api headers only, no source build).
The FidelityFX DLL is delay-loaded, not statically imported. Every FFX entry
point sits behind fork_hooks::fsrRuntimeAvailable(), a latched
LoadLibrary("amd_fidelityfx_vk.dll") probe. Without this, an install that
ships d3d9.dll but not the 9.3 MB FidelityFX DLL would fail to load d3d9.dll
at all and the game would not start; with it, FSR simply reports unsupported.
The same probe gates the two Vulkan extensions and the two extra device queues
the FFX backend needs, so an install without the DLL gets upstream's instance,
device and queue set byte-for-byte.
FSR Frame Generation needs its own swapchain presenter (DxvkFSRFGPresenter,
fork-owned, alongside DxvkDLFGPresenter), which required exposing
surface-transfer plumbing on the base Vulkan presenter so that switching
presenter types at runtime does not destroy and re-create the native window
surface.
Fork-owned files (not upstream touches)
src/dxvk/rtx_render/rtx_fork_fsr.cpp/.h—DxvkFSRupscaler backend, plus thefsrRuntimeAvailable/isFsrUpscalerActive/setFsrDownscaleExtent/dispatchFsrUpscale/fsrUpscalingMipBias/fsrJitterSequenceLengthhooks.src/dxvk/rtx_render/rtx_fork_fsr_framegen.cpp/.h—DxvkFSRFrameGenbackend andDxvkFSRFGPresenter, plus theisFsrFrameGenEnabled/dispatchFsrFrameGenerationhooks.src/dxvk/rtx_render/rtx_fork_rcas.cpp/.h— standalone RCAS sharpening pass and thedispatchRcasSharpeninghook. Owns the shared sharpening option atrtx.sharpening.sharpness(deliberately not underrtx.fsr: it drives the RCAS pass for DLSS / DLSS-RR / XeSS / TAA-U as well as FSR's internal sharpener, and an FSR-namespaced key silently owning four non-FSR paths would be a trap for anyone later reworking FSR).src/dxvk/rtx_render/rtx_fork_upscaler_ui.cpp— the FSR upscaler panel, the shared sharpness slider, and the frame-generation backend selector. Kept out ofdxvk_imgui.cppso thatImGUI::showDLFGOptionsstays byte-identical to upstream: the selector decides whether the upstream DLSS-G panel is drawn rather than that panel being rewritten to host a second backend.src/dxvk/shaders/rtx/pass/post_fx/fork_rcas.comp.slang/fork_rcas.h— the RCAS compute pass.fork_-prefixed becausepost_fx/is an upstream directory; an unprefixedrcas.*there would collide if NVIDIA ever ships its own RCAS pass (same convention astonemap/fork_tonemap_operators.slangh).
Upstream files touched
-
.gitmodules— inline tweak. Adds thesubmodules/FidelityFX-SDKsubmodule (GPUOpen-LibrariesAndSDKs/FidelityFX-SDK, pinned to tagv1.1.4). Note this adds ~411 MB to agit clone --recursive. -
meson.build(root) — inline tweak. Addsfsr3_dll_pathand aninstall_datarule deploying the prebuiltamd_fidelityfx_vk.dllnext to the other upscaler DLLs (NIS/XeSS/DLSS). -
src/dxvk/meson.build— inline tweak. Registers thertx_fork_fsr*/rtx_fork_rcas*/rtx_fork_upscaler_ui.cppsources inside the existing contiguousrtx_fork_*block; declaresfsr3_dep(import library +ffx-apiincludes) andfsr3_delay_load_args.fsr3_depis appended todxvk_depson its owndxvk_deps += [ fsr3_dep ]line rather than inlined into thedxvk_depsarray literal — upstream rewrites that literal (it is currently movingxess_depunder a Windows-on-ARM guard), and an inlined entry turns every such edit into a merge conflict. -
src/d3d9/meson.build— inline tweak. Adds acopy_fsr3_dllcustom target placingamd_fidelityfx_vk.dllnext tod3d9.dll, and addsfsr3_delay_load_argsto the d3d9 link args so the DLL is delay-loaded. -
src/dxvk/dxvk_adapter.cpp/dxvk_adapter.h— inline tweak. AddsimageAcquireandfsrPresentqueue-family/queue-info fields alongside the existing DLFGpresentqueue. The family-selection logic infindQueuesand theVK_KHR_GET_MEMORY_REQUIREMENTS_2request are both gated onfork_hooks::fsrRuntimeAvailable(), so a build/install without the FidelityFX DLL allocates exactly upstream's queue set and asks for exactly upstream's extensions. -
src/dxvk/dxvk_instance.cpp— inline tweak. RequestsVK_EXT_DEBUG_UTILS(used by the FFX Vulkan backend for object naming), gated onfork_hooks::fsrRuntimeAvailable(). -
src/dxvk/dxvk_context.cpp/dxvk_context.h— hook.DxvkContext::isFSRFGEnabled()— the DXVK-side mirror ofisDLFGEnabled(), consumed byd3d9_swapchain.cppto decide which presenter to create — is a one-line delegation tofork_hooks::isFsrFrameGenEnabled(m_device.ptr()). The predicate folds in device support, so callers never need a separate "supported" check. -
src/dxvk/dxvk_device.cpp/dxvk_device.h— inline tweak. Adds theimageAcquire/fsrPresentdevice queues mirroring the adapter-level fields; constructs them_fsr,m_fsrFrameGen,m_rcasActive<>members alongside the existing upscaler members and tears downm_fsrFrameGeninonDestroy. -
src/dxvk/dxvk_objects.h— inline tweak. AddsDxvkFSR/DxvkFSRFrameGen/DxvkRCASforward declarations andmetaFSR()/metaFSRFrameGen()/metaRCAS()accessors + backingActive<>members, following the existingmetaXeSS()pattern. Forward declarations only — no FFX headers are pulled in here, so the macro-heavyffx_apichain does not reach every translation unit that includesdxvk_objects.h. -
src/dxvk/imgui/dxvk_imgui.cpp— hook (+19 / -3 LOC). AnFSRentry in the two upscaler-type combos and in theRtxFramePassStagename map (one line each); the V-Sync guard'sDxvkDLFG::enable()test replaced byfork_hooks::anyFrameGenerationEnabled()so it covers both backends; the upscaler switch gains anFSRcase that callsfork_hooks::showFsrUpscalerSettings(ctx);fork_hooks::showSharedSharpnessSlider()after the switch; and the whole frame-generation section becomes onefork_hooks::showFrameGenerationOptions(ctx, dlfgSupported)call.dxvk_imgui.hhas zero fork delta. Note:ImGUI::showDLFGOptionsis left byte-identical to upstream but is no longer called from either menu. Its body is built around a per-backend "Enable DLSS Frame Generation" checkbox, and this fork's frame-generation UI has no such checkbox — the technology dropdown is itself the enable control. Leaving the function untouched-and-unused keeps it at zero conflict surface forever; the tradeoff is that DLFG UI upstream adds to it will not appear here, so it is worth a glance on each NVIDIA rebase. -
src/dxvk/imgui/rtx_user_menu.cpp— hook (+11 / -2 LOC). Same shape as the dev menu: anUpscalerType::FSRcase delegating tofork_hooks::showFsrUpscalerSettings,fork_hooks::showSharedSharpnessSlider(), and a "Frame Generation Settings" section gated onfork_hooks::anyFrameGenerationSupported(ctx, dlfgSupported)— so it appears on non-DLSS GPUs that can still do FSR FG — whose body is the same singlefork_hooks::showFrameGenerationOptionscall. -
src/dxvk/rtx_render/rtx_camera.cpp— hook.calcPixelJitteralso jitters when FSR is active, and takes its sequence length fromfork_hooks::fsrJitterSequenceLength()(which returns 0 — "keep your own" — unless FSR is the active upscaler). The formula itself,8 * upscaleFactor²matchingffxFsr3UpscalerGetJitterPhaseCount, lives in the fork file. -
src/dxvk/rtx_render/rtx_context.cpp/rtx_context.h— hook (+11 LOC in the .cpp). AddsInternalUpscaler::FSRand four one-line dispatches:fork_hooks::setFsrDownscaleExtent,isFsrUpscalerActive,dispatchFsrUpscale,dispatchRcasSharpening(called unconditionally after the main upscale; no-ops unless sharpening is non-zero and the active upscaler has no internal sharpener) anddispatchFsrFrameGeneration(called from the same site asdispatchDLFG). The header adds the four matchingfrienddeclarations to the existing fork-hook friend block. No FSR type or FFX header reachesrtx_context.cpp. -
src/dxvk/rtx_render/rtx_options.h— inline tweak. AddsUpscalerType::FSR, theFrameGenerationTypeenum (None/DLSS/FSR) + itsRTX_OPTION(rtx.frameGenerationType, envDXVK_FRAMEGEN_TYPE), andRtxOptions::isFSREnabled()alongside the existingisXeSSEnabled()-style helpers. Also forward-declaresfork_hooks::applyFrameGenerationType(rather than includingrtx_fork_hooks.h, which would be circular) and wires it as the option'sonChangeCallback:rtx.frameGenerationTypeis the single enable control for frame generation, and that handler drivesrtx.dlfg.enable/rtx.fsrfg.enablefrom it. Putting the invariant on the option rather than in the menu means it holds for config files, the environment variable andSetConfigVariable, not only while the settings window is open. -
src/dxvk/rtx_render/rtx_resources.cpp— hook. The sampler mip-bias helper addsfork_hooks::fsrUpscalingMipBias(m_device), which returns 0 unless FSR is the active upscaler. -
src/dxvk/rtx_render/rtx_scene_manager.cpp— hook.getTotalMipBias()/getCalculatedUpscalingMipBias()gain an FSR branch delegating to the samefork_hooks::fsrUpscalingMipBiashelper. -
src/dxvk/rtx_render/rtx_types.h— inline tweak. AddsRtxFramePassStage::FSRto the frame-pass-stage enum. -
src/vulkan/vulkan_presenter.cpp/vulkan_presenter.h— inline tweak. Adds a protectedPresenter(..., VkSurfaceKHR existingSurface)constructor overload plus areleaseSurface()accessor, soDxvkFSRFGPresentercan be constructed from an already-live surface — avoidingVK_ERROR_NATIVE_WINDOW_IN_USE_KHRwhen the swapchain switches presenter type at runtime. Deliberately minimal: this is the deepest layer the fork touches (src/vulkan/is base DXVK, not NVIDIA-remix code), and there is no way to express a protected-constructor overload as a one-line hook, so every member added here is permanent hand-merge surface.takeSurfaceFrom(),getSurface()andgetSwapChain()were part of the original cherry-pick and are not kept: nothing called them. -
src/d3d9/d3d9_swapchain.cpp/d3d9_swapchain.h— inline tweak.m_fsrfgPresenter(a third, mutually-exclusive presenter slot alongsidem_presenter/m_dlfgPresenter) is created/torn down inCreatePresenter()based onisFSRFGEnabled();NeedRecreatePresenter()andGetPresenter()consider all three slots. Surface ownership transfers to the new presenter viareleaseSurface()plus the protectedPresenterconstructor (see thevulkan_presenter.*entry); the single destroy-the-old-surface path sits above the presenter if/else rather than being duplicated in two of its three branches. AddsGetActivePresenterOrNull(), a null-tolerant sibling ofGetPresenter(), becauseCreatePresenter()legitimately runs before any presenter exists andGetPresenter()asserts on null — calling it there tripped that assert on startup indebuganddebugoptimizedbuilds.
Workstream - sRGB texture linearization fix (fork - 2026-07-21)
FO4 albedo/emissive were double-linearized: the game's diffuse/glow maps arrive in sRGB formats (the sampler decodes them on read) AND the opaque surface shader applied its own gammaToLinear (pow 2.2), so the value was gamma-corrected twice -> washed-out albedo. Fix is a per-material "source texture is sRGB" flag: the shader skips its software gamma correction for a channel whose source texture uses an sRGB VkFormat (the sampler already linearized it), while constants and UNORM textures still get the software conversion. Detection reads the resolved albedo/emissive image-view format in createSurfaceMaterial and self-heals across async texture loads because the material cache key already folds isImageEmpty(). Gated by rtx.linearizeSrgbTextures (default on), folded into preCreationHash so toggling the menu checkbox rebuilds cached materials live. Separately, gammaToLinear now uses the exact sRGB piecewise EOTF instead of x^2.2, so software-linearized inputs (notably albedo the FO4 plugin submits as UNORM) match the hardware sampler's sRGB decode; linearToGamma (output encode) is deliberately left as x^(1/2.2).
src/dxvk/shaders/rtx/utility/shared_constants.h- fork-touchpoint inline tweak. Adds OPAQUE_SURFACE_MATERIAL_FLAG_ALBEDO/EMISSIVE_TEXTURE_IS_SRGB at TYPE_OFFSET(6/7). (Originally 5/6; renumbered on the 2026-07-29 upstream sync — upstream took TYPE_OFFSET(5) forOPAQUE_SURFACE_MATERIAL_FLAG_IS_HAIR_CARD. These offsets are a shared, silently-colliding namespace: check for a free slot on every sync.)src/dxvk/rtx_render/rtx_texture.h- fork-touchpoint inline tweak. Adds TextureUtils::isSRGB(VkFormat).src/dxvk/shaders/rtx/concept/surface_material/opaque_surface_material_interaction.slangh- fork-owned change. Skips gammaToLinear for albedo/emissive when the loaded source texture is sRGB.src/dxvk/rtx_render/rtx_materials.h- fork-owned change. RtOpaqueSurfaceMaterial carries albedoTextureIsSrgb/emissiveTextureIsSrgb (ctor/members/hash/writeGPUData flags); hash static_assert 120 -> 128.src/dxvk/rtx_render/rtx_scene_manager.cpp- fork-owned change. createSurfaceMaterial detects the sRGB image-view format and folds rtx.linearizeSrgbTextures into preCreationHash for live A/B.src/dxvk/rtx_render/rtx_options.h- fork-touchpoint inline tweak. Adds rtx.linearizeSrgbTextures (default true).src/dxvk/imgui/dxvk_imgui.cpp- fork-owned addition. "Linearize sRGB Textures" checkbox under Material Filtering.src/dxvk/shaders/rtx/utility/color.slangh- fork-owned change. gammaToLinear -> exact sRGB piecewise EOTF (linearToGamma unchanged).RtxOptions.md- REGEN PENDING (new rtx.linearizeSrgbTextures option).
Workstream - weather precipitation particle system (fork - 2026-07-25)
Rain / snow / blowing sand as a property of a Numos weather preset. Ported
from the rain effect in xoxor4d's NFS Carbon RTX mod
(https://github.com/xoxor4d/nfsc-rtx, src/comp/modules/rain.cpp), which
builds its rain GAME-side: CreateMaterial + CreateMesh for a quad
"spawner", then one DrawInstance per frame carrying an
InstanceInfoParticleSystemEXT anchored to the camera. This workstream does
the same thing from INSIDE the runtime against the same
RtxParticleSystemManager, so no game integration has to re-implement it and
the values blend with the rest of the weather.
Ten new preset fields (group "Precipitation": intensity, fall speed, wind
response, turbulence, drag, streak, drop width/length, opacity, colour) ride
the existing WEATHER_PRESET_FIELD_LIST machinery, so per-preset options,
blending, the preset editor UI, conf export and tooltips are all generated.
They write the live rtx.weather.precipitation.* options that
PrecipitationSystem reads. The 12 preset archetypes are authored: dry for
clear/partlyCloudy/overcast/hazy/smoggy, mist for foggy, escalating rain for
drizzle/rainstorm/thunderstorm, tumbling flakes for snow/blizzard (no streak,
high drag + turbulence), and tinted near-horizontal grit for sandstorm.
Implementation notes worth keeping:
-
No shipped assets. The drop sprite is generated on the CPU at init (64x64 RGBA8 soft radial falloff + box-filtered mip chain, RGB pinned white so the UNORM-vs-sRGB question is moot). With motion trails the runtime stretches only the sprite's centre and preserves its edges, so the same round drop is a capped rain streak with trails on and a snowflake with them off.
-
Emitter winding is load-bearing.
particle_system_spawn.comp.slangderives the emission direction ascross(normalize(p1-p0), normalize(p2-p0))and uses it UNNORMALIZED to scale initial velocity, so both quad triangles must be wound off perpendicular unit-length edges (indices{0,1,2}and{3,2,1}) or the two halves emit at different speeds. -
Drag/gravity are coupled deliberately. The evolve shader integrates
v = (v + up*gravity*dt) * (1 - drag*dt), so terminal velocity isgravity/drag. Settinggravity = -fallSpeed * dragmakes the spawn velocity also the terminal velocity: turbulence kicks decay back to the fall speed instead of the particles stalling or accelerating. That is what separates snow (drag + turbulence: flutters, then resumes falling) from rain (no drag: dead straight). -
Descriptor stability is mandatory, not an optimisation.
RtxParticleSystemManager::fetchParticleSystemkeys systems onmaterialHash ^ desc.calcHash()and allocates fullmaxNumParticles-sized buffers per system. A descriptor recomputed from continuously-blending weather values every frame would therefore allocate a whole particle system per frame, with orphans living untilmaxTimeToLive. The descriptor is adopted on a dwell timer (descUpdateIntervalMs, default 750 ms) instead, which bounds a transition to a handful of systems; the orphans stop spawning, their particles finish their lifetime, and the manager evicts them - so the step is a crossfade rather than a pop. The material is kept constant (white albedo, colour/opacity ride on the per-particle colour thatsubmitDrawStatemodulates in via VertexColor0) so it never forks the system identity. -
Nothing to reset on scene clear.
SceneManager::clear()does not touch the asset replacer's external mesh/material tables, and the material holds itsTextureRef(and therefore theRc<DxvkImageView>) directly rather than resolving through the texture table, so it does not hit the API-uploaded texture failure mode documented inrtx_fork_api_entry.cpp. -
src/dxvk/rtx_render/rtx_fork_precipitation.h- fork-owned addition.PrecipitationSystem: the 10 blend-target options + the global budget/spawn-volume/collision options. -
src/dxvk/rtx_render/rtx_fork_precipitation.cpp- fork-owned addition. Procedural drop texture, emitter quad registration, parameter resolution, dwell-gated descriptor, per-frame external draw, global ImGui panel, and the threefork_hooksbodies. -
src/dxvk/rtx_render/rtx_fork_hooks.h- fork-owned addition. DeclaressubmitPrecipitationandshowPrecipitationUI. -
src/dxvk/rtx_render/rtx_types.h- fork-touchpoint inline tweak - 2 blocks. Forward-declaresfork_hooks::precipitationEmitterDrawCallnext to the existingexternalDrawTextureCategoriesdecl, and friends it insideDrawCallState. Required becauseDrawCallState::transformData/::materialDataare private and the emitter builds its draw call from scratch - the same accessRemixAPIPrivateAccessor::toRtDrawStatehas for the API's external draws. -
src/dxvk/rtx_render/rtx_context.cpp- fork-touchpoint inline tweak - 4 LOC ininjectRTX. Callsfork_hooks::submitPrecipitation(*this)immediately beforegetSceneManager().prepareSceneData(...). Ordering is required:prepareSceneDatais whereRtxParticleSystemManager::simulateconsumes the frame's spawn contexts. -
src/dxvk/rtx_render/rtx_fork_weather.h- fork-owned change. 10 rows appended toWEATHER_PRESET_FIELD_LIST+ per-preset values in all 12WEATHER_PRESET_VALUES_*macros (53 -> 63 fields, 636 -> 756 options). -
src/dxvk/rtx_render/rtx_fork_weather.cpp- fork-owned change.snapshotRendererreads,writeBlendedToDerivedLayerwrites (gated as a block onweatherVaries_precipitationIntensity), tooltips mirrored from the live options, and the global panel hosted under the weather tree. -
src/dxvk/meson.build- fork-touchpoint inline tweak. Adds the two new source entries. -
RtxOptions.md- REGEN PENDING (newrtx.weather.precipitation.*options + 120 new per-preset options).
Fix - precipitation appeared to follow the camera (fork - 2026-07-25)
User report against the initial precipitation drop: "when I turn the camera from left to right, the rain changes the direction it's falling... from left to right. Same with any other camera angle." Three separate camera couplings stacked; the first is the one that made it read as camera-locked at every heading rather than varying with the wind direction.
- Billboard type.
FaceCamera_Sphericalputs the billboard plane in the CAMERA plane (basisRight/basisUp= camera right/up inparticle_system_generate_geometry.comp.slang), and the motion trail stretches along the world velocity projected into that plane. Any horizontal component of the fall therefore gets re-projected as the camera yaws, sweeping the apparent fall direction. Streaked precipitation now usesFaceCamera_UpAxisLocked, which pinsbasisUpto world up and only yaws to face the viewer, so a falling streak stays locked to the world vertical. Un-streaked snow keeps spherical - a round sprite has no orientation to betray and spherical silhouettes better when looking up/down. - Emitter placement read camera orientation. The spawn plane was biased
along the camera's flattened forward vector to push the volume into view.
That made panning swing the whole volume around the player, and because the
spawn shader smears new particles between the emitter's previous and current
transform (
lerp(worldPosition, prevWorldPosition, randSeed)), a turn dragged freshly spawned drops along the swing. The bias is GONE and the option with it: only the camera's position may move the emitter. A 20 m radius disc centred on the viewer already covers the view in every direction. - Wind coupling was ~3x too strong.
windResponseis a fraction ofcloudWindSpeed, which defaults to 0.02 km/s = 20 m/s - an ALTITUDE wind. At 0.15-0.30 that produced ~21 deg of slant on rain. The slant is correctly fixed in world space, which is exactly why it re-projects on screen as the camera turns; big enough to notice reads as "the rain changes direction". Retuned so rain drifts 6-9 deg and only blizzard (42 deg) / sandstorm (63 deg) slant hard.
Also added a "Live values" tree to the global panel exposing the per-preset fields directly, so a look can be A/B'd in-game without the blender running.
src/dxvk/rtx_render/rtx_fork_precipitation.cpp- fork-owned change. Billboard selection, orientation-free emitter transform, live-value ImGui tree.src/dxvk/rtx_render/rtx_fork_precipitation.h- fork-owned change. DroppedforwardOffsetMeters(with a note on why no view-bias option may exist);windResponsedefault 0.15 -> 0.04 and reworded.src/dxvk/rtx_render/rtx_fork_weather.h- fork-owned change. Per-presetprecipitationWindResponseretuned across all 12 presets.docs/integrators/weather-presets.md,weather-presets-reference.md- fork-owned change. Orientation-independence + wind-tuning guidance; stale "the plugin owns all particles" / field-count claims corrected.
Fix - precipitation review pass (fork - 2026-07-25, adversarial review)
Verification pass over the precipitation drop against the upstream particle
manager/shaders. Confirmed sound: staging-buffer lifetime in ensureTexture
(DxvkContext::copyBufferToImage calls trackResource<Read> on the source, so
the command list owns the staging buffer until the GPU is done), emitter quad
winding (both triangles yield exactly unit local +Z off unit-length edges, and
the transform's third column is the unit fall direction), the sceneScale unit
conventions, orphan crossfade/eviction (prepareForNextFrame erases a system
maxTimeToLive after its last spawn; simulate keeps stepping spawn-less
systems), and survival of the external mesh/material tables across
SceneManager::clear(). Five real defects found and fixed:
- fp16
timeToLivestalls at high refresh rates (upstream bug, exposed by slow snow).GpuParticle::timeToLivewas a half; fp16 spacing in [16,32) is 1/64 s, so the evolve shader'stimeToLive -= dtrounds back to the same value whenever dt < 1/128 s. At >128 fps every particle with >=16 s of remaining life is IMMORTAL: the conservative counter never decrements, the capacity gate blocks all further spawns, and after the initial population falls out of view precipitation stops entirely. The snow preset's 1.1 m/s fall over a 22 m volume needs ~20 s lifetimes, exactly in the broken range. Promoted to a float by absorbing the two pad halves (struct stays 48 bytes); sentinel + whole-buffer clear word are now float +inf. - Constant-population mode trap.
spawnParticlesflips into "re-init every particle every frame" whenspawnRatePerSecond >= maxNumParticles; the intensity-drivenbudget/ttlrate crosses that whenever ttl < 1 s (fast fall- small volume) and would freeze precipitation into a static sheet at the spawn plane. Rate now capped at 0.9x budget.
- Orphan pile-up during blends with long lifetimes. Orphaned systems live
for their remaining particle lifetime, so a flat 750 ms dwell blending the
snow preset (~20 s ttl, ~7.5 MB buffers/system at default budget) stacks
~27 systems (~200 MB transient + 27 simulate groups/frame). Effective dwell
now floored at
maxTimeToLive/6(~7 systems worst case);descUpdateIntervalMs = 0still bypasses everything for debugging. roughness/metallicwere write-only after first init. The material registered once; later edits silently did nothing. refreshDesc now re-registers the material (destroy + register under the same handle) when they change, paced by the same dwell — submitExternalDraw stamps the external material hash onto the emitter draw call viasetHashOverride, and that is the hash the particle manager keys systems on, so a material edit forks the system identity exactly like a desc edit (crossfade; dwell prevents per-frame forks while a slider drags).- Weather write gate missed constant-nonzero authoring. Gate was "intensity varies across presets"; a game authoring the SAME nonzero intensity into all 12 presets would never get its live options written and would never rain. Gate is now varies-OR-nonzero (all-zero remains the only dormant case, preserving hand-authored configs).
Also hardened: the singleton now tracks the DxvkDevice* its resources were
built on and drops/rebuilds everything if the device changes (previously the
registered-flags claimed "done" while a recreated device's asset replacer had
never seen the handles -> per-frame "External mesh has no submeshes" forever);
stale "cleared by onSceneClear" comment (no such hook exists) replaced with the
real lifetime story. Deliberately NOT changed: frame-time-dependent motion-trail
length (speed * multiplier * dt is upstream's motion-blur semantic — per-frame
streaks tile temporally into continuous lines; compensating would gap them), and
no interior suppression runtime-side (no renderer signal for "indoors" exists;
documented as the integration's job in weather-presets.md).
src/dxvk/shaders/rtx/pass/particles/particle_system_binding_indices.h- fork-touchpoint inline tweak. GpuParticle.timeToLive half -> float (absorbs pads, 48 bytes preserved); +inf sentinel/clear word.src/dxvk/rtx_render/rtx_fork_precipitation.cpp- fork-owned change. Spawn-rate cap, lifetime-scaled dwell floor, dwell-paced material re-registration, device-change reset, staging-lifetime note.src/dxvk/rtx_render/rtx_fork_precipitation.h- fork-owned change. refreshDesc signature (+ctx), m_resourceDevice / applied-material members, corrected lifetime comments + tooltips.src/dxvk/rtx_render/rtx_fork_weather.cpp- fork-owned change. Precipitation write gate: varies-OR-nonzero.docs/integrators/weather-presets.md- fork-owned change. Interiors-are-integration-responsibility section, fast-movement note, global-knob table additions.docs/integrators/weather-presets-reference.md- fork-owned change. Write-gate semantics note.
Workstream - ray-traced spawn occlusion for particles (fork - 2026-07-25)
Precipitation now stops under roofs, bridges and overhangs by asking the actual
scene geometry, not the screen. Supersedes the previous "interiors are the
integration's responsibility" position in docs/integrators/weather-presets.md.
Motivation: upstream's only particle collision (enableCollisionDetection ->
collideParticleWithScene in particle_system_evolve.comp.slang) reprojects the
particle into the PREVIOUS FRAME'S G-BUFFER and early-outs if it lands outside
[0,1] UV. That is a screen-space test, so it cannot see anything off-screen,
behind the camera, or outside the frustum - i.e. it cannot see the roof over the
player's head, which is exactly the geometry that decides whether it should be
raining on them. This is a ray tracer; the TLAS is right there.
Everything needed was already wired and simply unused:
- all three particle passes already declare
COMMON_RAYTRACING_BINDINGS(which includesBINDING_ACCELERATION_STRUCTURE), andparticle_system_bindings.slanghalready includescommon_bindings.slangh, sotopLevelASwas already visible to the shaders; khrRayQueryFeatures.rayQueryis already an enabled device feature andRayQuery<>is used elsewhere (visibility.slangh);- the descriptor was never populated for these dispatches only because
bindCommonRayTracingResourcesruns later in the frame with the raytracing work.
Frame ordering makes this cheap rather than awkward: particle simulation runs
from SceneManager::prepareSceneData, BEFORE AccelManager::prepareSceneData
builds and swaps this frame's TLAS - so getTLAS(Opaque).accelStructure at that
point is still last frame's fully-built structure. One frame of staleness is
irrelevant for "is there a roof above this raindrop". It is null for the first
frames of a scene, hence the sceneTlasValid gate.
Design (reworked 2026-07-25 after the first in-game test): TWO rays per SPAWNED particle (hundreds per frame at default settings, not thousands).
- Shelter probe - upward, against the fall direction, 1 km of authored
distance (
100000 cm * sceneScale). A hit means opaque cover hangs somewhere overhead, so the drop would have been stopped up there and must not exist: it is killed at birth (timeToLive = 0-> born sleeping, never rendered, retired through the conservative counter exactly like upstream's Kill collision mode). - Landing trace - along the initial velocity for the particle's whole
ballistic path, shortening
timeToLiveso it dies at the surface. Skipped when the shelter probe already killed the drop.
For rain (no drag, no turbulence) the path IS a straight line so the landing
trace is exact; heavy turbulence/drag curves it and makes it an approximation.
Opaque-only (OBJECT_MASK_OPAQUE + RAY_FLAG_CULL_NON_OPAQUE), so glass and
foliage cards do not stop precipitation and traversal needs no any-hit - a
verified-safe combination here because fully opaque draws get
VK_GEOMETRY_OPAQUE_BIT_KHR + OBJECT_MASK_OPAQUE
(rtx_instance_manager.cpp), meaning solid world geometry is never culled by
the flag and a single Proceed() completes traversal. Strictly opt-in: the
new descriptor bit defaults to 0, so the remixapi, USD and global-preset particle
paths are byte-identical to before.
POST-MORTEM of the first version (single downward ray), which shipped and
failed in-game ("rain still falls under roofs"): every link was verified
correct from source afterwards - the C++/Slang bitfield layouts are
byte-identical (proven by SPIR-V disassembly: the desc bit reads byte 95 bit 1
on both sides), the compiled shader contains the full ray-query sequence with
flags 0x84 and mask 0x8, the TLAS binding/ordering/lifetime all hold, and FO4
world geometry really is opaque-flagged with the OPAQUE mask. The actual
defect was geometric: the ray only looked DOWN from the spawn plane, so any
cover ABOVE the spawn plane could never occlude - and in FO4 that is nearly
all cover, because the integration runs rtx.sceneScale = 0.1
(meterToWorldUnitScale = 10 units/m) while the game world is ~70 units per
real meter, which shrinks the authored 14 m spawn-plane height to ~2 REAL
meters above the camera - beneath canopies, overpasses and ceilings. The
upward shelter probe closes the hole at any scale (it is also the correct
model with an accurate scale: a bridge 30 m up should shelter the street even
though the spawn plane hangs at 14 m). Diagnostics were added at the same
time so the next failure is a measurement, not archaeology.
Diagnostics (read-only, dev menu -> Weather Presets -> Precipitation (global),
visible while occludeUnderCover is on): scene-TLAS availability, per-frame
spawn-ray / shelter-kill / landing-hit counters (GPU atomics in the spawn
kernel, 10-frame readback ring mirroring ConservativeCounter), and the
spawn-plane height in WORLD UNITS next to the sceneScale that produced it -
the line that would have exposed this bug on day one. Interpretation: rays = 0
means the trace is not running; rays > 0 with zero kills/hits under a roof
means the roof is not reachable in the TLAS under OBJECT_MASK_OPAQUE;
shelter kills > 0 under cover means the feature is working.
src/dxvk/shaders/rtx/pass/particles/particle_system_common.h- fork-touchpoint inline tweak. AddsGpuParticleSystemDesc::traceSpawnOcclusion : 1(default 0 in the ctor) and repurposes the trailingParticleSystemConstants::pad1assceneTlasValid.src/dxvk/shaders/rtx/pass/particles/particle_system_spawn.comp.slang- fork-owned change. Shelter probe + landing trace at spawn; includesinstance_definitions.hforOBJECT_MASK_OPAQUE;SpawnTraceStatsdiagnostic atomics.src/dxvk/shaders/rtx/pass/particles/particle_system_binding_indices.h- fork-touchpoint inline tweak. AddsPARTICLE_SYSTEM_BINDING_SPAWN_TRACE_STATS_OUTPUT(63).src/dxvk/rtx_render/rtx_particle_system.h/.cpp- fork-touchpoint inline tweaks. BindsBINDING_ACCELERATION_STRUCTURE(last frame's Opaque TLAS) insimulate, setsconstants.sceneTlasValidinsetupConstants, and owns the spawn-trace stats buffer + 10-frame readback ring +getSpawnTraceDiagnostics().src/dxvk/rtx_render/rtx_fork_precipitation.h/.cpp- fork-owned change.rtx.weather.precipitation.occludeUnderCover(default on) driving the new bit, the dev-menu toggle, and the read-only occlusion diagnostics block.docs/integrators/weather-presets.md- fork-owned change. "Interiors and cover" replaces "Interiors are the integration's responsibility".RtxOptions.md- REGEN PENDING.
Fix - sheltered spawns relocate instead of dying (fork - 2026-07-25)
User report against the spawn-occlusion drop above: "when I go into a sheltered area, the rain entirely stops" - including the rain that should stay visible outside, through the doorway. Everything else about occlusion was confirmed working (rain no longer falls through roofs).
Root cause is the interaction of two facts, each fine on its own:
- The spawn volume is a camera-centred disc. With the integration's
understated scene scale (
rtx.sceneScale = 0.1=> 10 units per authored meter, in a world that is really ~70 units/m) the authored 20 m radius / 14 m height is a ~3 REAL meter bubble around the player's head. - The shelter probe killed sheltered drops at birth.
Step under any porch, canopy or awning bigger than the bubble and EVERY spawn
candidate is (correctly) judged sheltered, every drop dies at birth, and there
is no rain left ANYWHERE - the rain "visible outside" only ever existed inside
that same bubble. A pure scale correction (worldUnitsPerMeter = 70) was
tried earlier, did not resolve the user-visible symptom, and was reverted at
user request; this fix removes the starvation mechanism itself and works at
any scene scale.
The fix, in particle_system_spawn.comp.slang: a sheltered spawn candidate
REROLLS instead of dying. Spawn-point sampling was factored into
sampleSpawnPoint() (triangle pick + barycentric + emitter-motion smear +
cone direction, i.e. everything random about a candidate), and the shelter
probe loops: draw a candidate, probe upward, and on a hit draw a completely
fresh candidate - up to kShelterRelocationAttempts (8) in all - keeping the
first one with open sky. Only when every attempt is covered (a genuinely
enclosed interior) is the drop killed at birth as before. The landing trace
runs on the accepted candidate. Per-attempt RNG seed bases stride by
maxNumParticles so a retry can never replay a neighbouring particle's seed
window (which would spawn coincident drops).
Consequences, deliberate:
- The particle budget migrates to wherever the sky is visible: standing under a roof, rain continues outside the doorway; a 95%-covered volume still lands ~1/3 of its budget in the open 5% (1 - 0.).
- Density conservation is traded away: the surviving spawn rate concentrates in the uncovered fraction, so rain framed in a doorway can read up to ~8x denser than open-field rain. That errs on the visible side, which is the point; killing proportionally is exactly the starvation this replaces.
- Cost: worst case 9 rays per spawned particle (8 shelter probes + landing), and only in enclosed spaces; the common outdoor case is unchanged at 2.
Diagnostics: a fourth counter, "relocated" (SpawnTraceStats[3]), counts
spawns rescued by the reroll. Interpretation shift: under PARTIAL cover
(doorway, porch) expect relocated > 0 with shelter kills near ZERO now -
shelter kills climbing there means the retries all land under the same cover
(volume too small relative to the roof); shelter kills > 0 with relocated == 0
is the signature of a genuinely enclosed space, where killing is correct.
src/dxvk/shaders/rtx/pass/particles/particle_system_spawn.comp.slang- fork-owned change.SpawnSample/sampleSpawnPoint()factoring, shelter relocation loop,kShelterRelocationAttempts, stats[3].src/dxvk/rtx_render/rtx_particle_system.h/.cpp- fork-touchpoint inline tweaks.kSpawnTraceStatsCount3 -> 4,s_spawnTraceRelocations, extendedSpawnTraceDiagnostics.src/dxvk/rtx_render/rtx_fork_precipitation.h/.cpp- fork-owned change.occludeUnderCoverdescription now states the relocation semantics; the dev-panel diagnostics show "relocated" and the interpretation comment covers the new counter.
USER-VERIFIED 2026-07-26 ("you fixed it").
Workstream - precipitation appearance overrides (fork - 2026-07-26)
User request: adjustable rain appearance - "not just the roughness or whatever, but the color, length, etc."
The per-preset look fields (color, opacity, drop width/length, streak) have
existed since the first drop, but they are OWNED by the WeatherBlender
whenever a preset is active: writeBlendedToDerivedLayer rewrites them every
frame, so dragging them in the dev menu never sticks. The only look knobs
that held were the material constants (roughness / metallic), which ride the
registered material rather than the blended options - exactly the user's
observation.
Fix: a global appearance-override layer, rtx.weather.precipitation. appearance.*, applied multiplicatively ON TOP of the blended per-preset
values inside buildDesc. The blender never touches these, so they stick
under any weather, persist across presets and transitions, and save like any
other rtx option. All defaults are identity => untouched configs produce
bit-identical descriptors (and therefore identical particle-system hashes).
Options: tintColor (multiplies preset color), opacityScale, widthScale,
lengthScale, streakScale (0 suppresses trails entirely, also dropping
velocity alignment like snow), plus two variance knobs: sizeVariance and
opacityVariance (0..0.9). Variance costs nothing: the animation-data
texture already carries a min row and a max row per property and the GPU
samples between them with each particle's stable randSeed
(computeDataRow, randomizeAcrossTwoRows) - upstream plumbing that the
precipitation descriptor had been collapsing by writing identical min/max
curves. Spreading the curves to [1-v, 1+v] gives every drop its own size /
opacity, fixed for its lifetime. Per-drop variation is what breaks up the
"sheet of identical sprites" look, especially for snow.
Edits go through the existing descriptor dwell (~750 ms + lifetime floor), so slider drags fork a bounded number of crossfading systems - same contract as weather blends. UI: a new "Appearance overrides (stack on presets)" tree in the Precipitation (global) panel, between Live values and the budget knobs; the Live values note now points at it.
src/dxvk/rtx_render/rtx_fork_precipitation.h- fork-owned change. Sevenrtx.weather.precipitation.appearance.*options with rationale block.src/dxvk/rtx_render/rtx_fork_precipitation.cpp- fork-owned change. buildDesc applies the overrides (streak, size + variance, tint + opacity + variance); Appearance overrides ImGui tree; Live values note.RtxOptions.md- REGEN PENDING.
Fix - rain always renders dark (fork - 2026-07-26)
User report (with screenshot, FNV overcast): rain streaks read as dark silhouettes against the bright cloud deck, "no matter what tint or color or opacity settings I choose", and the user asked why - "aren't these part of the remix particle system? I thought they'd be able to react to light."
They DO react to light - that is precisely the problem. The drops are fully path-traced lit geometry, and the renderer shades them correctly for what they are: opaque diffuse sprites. Under an overcast preset that kills the sun, a diffuse card receives very little irradiance and legitimately renders darker than the bright sky behind it; albedo, tint and opacity are all MULTIPLIERS on received light, so no setting can lift a surface above the light falling on it. A real raindrop is transparent water that transmits and refracts the bright sky behind and around it (strong forward scattering) - that transmission is why real rain reads as bright streaks against clouds, and it is exactly the term a flat sprite cannot have. Every production rain system substitutes a small self-lit component for it.
Fix: two more appearance options, rtx.weather.precipitation.appearance.glow
(emissive intensity, default 2.0, 0 restores the previous scene-lit-only
behavior) and .glowColor (default the same cool white-blue as the default
drop color). Wired as material emission (setEnableEmission /
setEmissiveIntensity / setEmissiveColorConstant - the same mechanism the
instance manager uses for emissive-blend particles), so scene lighting still
applies ON TOP: sunlit rain still catches real highlights, the glow only
stands in for the missing sky transmission. Rides the existing dwell-paced
material re-registration (change detection extended from roughness/metallic
to glow/glowColor), so live drags fork crossfading systems at the bounded
rate. The emissive term is uniform across the sprite; the drop texture's
alpha falloff still shapes it at composite time because blending weights the
whole contribution by opacity.
Note: emissive intensity is scene-referred radiance, so the right value depends on exposure/time-of-day - it is a taste dial, surfaced in the Appearance overrides tree (Glow Intensity / Glow Color).
REVISED SAME DAY - the constant glow failed its first night test ("rain just glows during the night", user) and was replaced by a sun-elevation-gated version, which the user also rejected in favor of researching the real mechanism. BOTH glow variants are now REMOVED - superseded by the sky-lit particle workstream below, which fixes the actual missing term.
Workstream - sky-lit particles: real skylight for precipitation (fork - 2026-07-26)
The definitive dark-rain fix, replacing the glow/emissive stand-ins after an in-depth trace of how the renderer actually shades the drops.
MECHANISM (all source-verified):
- The particle manager stamps
InstanceCategories::Particleon its generated geometry ->surface.isParticle. - The primary resolve DIVERTS isParticle surfaces away from normal lit
shading into the "opacity lighting approximation"
(
RESOLVE_OPACITY_LIGHTING_APPROXIMATION, resolve.slangh):emission += albedo x evalVolumetricNEE(froxel radiance cache)- the same treatment as upstream's dust motes. - The froxel radiance cache is filled ONLY by NEE over the analytic light
pool.
volume_integrate.slanghhas NO sky/ambient/environment term: skylight exists in Remix only via path-traced miss rays, which never feed froxels. - Under a Numos overcast preset the sun distant light is heavily dimmed, so froxels in open air are nearly black -> rain = albedo x ~0 = dark silhouettes against a bright sky. No albedo-side knob (tint / opacity / roughness / emissive hacks) can fix a lighting-side hole.
FIX: add the missing skylight term at the same place the froxel term lives.
evaluateOpaqueApproximations takes a new defaulted skyLitParticle flag
(passed at all three call sites from a new opaque-material flag bit); when
set - and only in Numos mode with a nonzero scale - the resolver samples the
atmosphere's own sky-view LUT via sampleSkyAmbientForVolume (which folds in
the cloud-transmittance LUT: real storm decks dim it, sunsets tint it, night
zeroes it) and adds albedo x skyAmbient x cb.particleSkyAmbientScale. Two
LUT taps per rain pixel: the view direction lifted above the horizon (forward
transmission - the sky behind the drop) and the zenith (ambient), averaged.
The froxel term stays, so local lights still light rain at night.
WHY A MATERIAL FLAG AND NOT isParticle: generic game particles (indoor smoke)
must not glow sky-blue - sky visibility is unknowable for them. It IS known
for precipitation: the spawn-time shelter probe with relocation guarantees
every living drop saw open sky at birth. The flag rides the opaque material
(sky_lit_particle), set only by the precipitation drop material.
Surface-flag bits (data2.w) are full; opaque material flags had bit
TYPE_OFFSET(7) free.
COMPILATION SCOPING: the sky term needs the atmosphere helpers + LUT
bindings, which the geometry-resolver TUs include before resolve.slangh but
other resolve consumers (integrators, visibility) may not. So
geometry_resolver.slangh defines RESOLVE_SKY_LIT_PARTICLES right before
including resolve.slangh, and the term compiles out everywhere else. This
covers the primary view (including ray-query mode, whose variant directive
defines ATMOSPHERE_AVAILABLE); indirect bounces keep froxel-only particle
lighting, which is fine - the effect matters where the rain is looked at.
The glow options (glow, glowColor) and skyGlowFactor() are REMOVED,
replaced by one dial: rtx.weather.precipitation.appearance.skyLightScale
(default 1 = physical, 0 = old froxel-only look; a per-frame constant, so it
applies immediately with no descriptor/material rebuild). Stale glow keys in
saved configs are harmless.
src/dxvk/shaders/rtx/utility/shared_constants.h- fork-touchpoint inline tweak.OPAQUE_SURFACE_MATERIAL_FLAG_SKY_LIT_PARTICLE(TYPE_OFFSET(8)). (Originally 7; renumbered on the 2026-07-29 upstream sync, which pushed the sRGB pair up to 6/7.)src/dxvk/rtx_render/rtx_material_data.h- fork-touchpoint inline tweak.X(SkyLitParticle, sky_lit_particle, bool, ...)in LIST_OPAQUE_MATERIAL_CONSTANTS.src/dxvk/rtx_render/rtx_materials.h- fork-touchpoint inline tweaks.RtOpaqueSurfaceMaterial: defaulted ctor param, member, flags pack, hash-struct entry.src/dxvk/rtx_render/rtx_scene_manager.cpp- fork-touchpoint inline tweak. PassesgetSkyLitParticle()into the GPU material.src/dxvk/shaders/rtx/algorithm/geometry_resolver.slangh- fork-touchpoint inline tweak. DefinesRESOLVE_SKY_LIT_PARTICLESbefore including resolve.slangh.src/dxvk/shaders/rtx/algorithm/resolve.slangh- fork-touchpoint inline tweaks.skyLitParticleparam + sky-ambient term in the opacity lighting approximation; flag passed at all three call sites.src/dxvk/shaders/rtx/pass/raytrace_args.h- fork-touchpoint inline tweak.particleSkyAmbientScaleappended at the END of RaytraceArgs (no existing offsets move).src/dxvk/rtx_render/rtx_fork_atmosphere.cpp- fork-owned change. Fillsconstants.particleSkyAmbientScalein updateAtmosphereConstants.src/dxvk/rtx_render/rtx_fork_precipitation.h/.cpp- fork-owned change. Glow machinery removed;setSkyLitParticle(true)on the drop material.RtxOptions.md- REGEN PENDING.
ALIGNMENT PASS (same day, user request - "in line with the other Numos
features"): the sky-light dial was folded into the weather preset table as an
11TH precipitation field, precipitationSkyLight (group Precipitation ->
Look, 0..10, default 1 in all 12 archetypes), driving a per-preset live
option rtx.weather.precipitation.skyLight exactly like the other ten - so
a blizzard can author different skylight than a drizzle, and the preset
editor generates its slider from the same table as everything else. The
short-lived global appearance.skyLightScale option was removed. The "Live
values (per-preset fields)" tree in Precipitation (global) was also RETIRED
in favor of the Weather Preset Editor (a pointer note remains) - preset
fields now surface in exactly one place, matching the house pattern. The
Appearance overrides tree (tint/scales/variance) stays: it is the one
deliberate pattern deviation, kept because blender-owned fields cannot be
live-edited mid-storm.
-
src/dxvk/rtx_render/rtx_fork_weather.h- fork-owned change.precipitationSkyLightfield row + 12 preset archetype rows; field counts 10 -> 11. -
src/dxvk/rtx_render/rtx_fork_weather.cpp- fork-owned change. Description mirror, snapshot read, derived-layer write for the new field. -
src/dxvk/rtx_render/rtx_fork_precipitation.h/.cpp- fork-owned change.skyLightper-preset option replacesappearance.skyLightScale; Live values tree retired. -
src/dxvk/rtx_render/rtx_fork_atmosphere.cpp- fork-owned change. cb fill readsskyLight(). -
src/dxvk/rtx_render/rtx_fork_precipitation.h- fork-owned change.glow/glowColoroptions;m_materialGlow/m_materialGlowColorregistered-state members. -
src/dxvk/rtx_render/rtx_fork_precipitation.cpp- fork-owned change. ensureMaterial sets emission; refreshDesc change detection extended; Glow widgets in the Appearance overrides tree. -
RtxOptions.md- REGEN PENDING.
Workstream - cloud march hot-path hoists (fork - 2026-07-30, perf)
Pure perf pass over the Nubis Cubed cloud raymarch, prompted by an external review
noting the inner loops likely repeat work that belongs outside the hot path. No look
change is intended: every edit is either an exact hoist of a loop-invariant
subexpression or a provably-conservative skip of dead work. Full audit + the ranked
backlog of what is NOT yet done lives outside the repo at
Fable_5_testing/cloud-perf-optimization-plan.md.
Four changes:
- Ray/frame-constant lighting hoisted out of the per-sample evaluator.
CloudShadeContextnow has two halves - the original frame-constant fields, plus a ray-constant half filled once per ray by the newcloudShadeContextBeginRay. Moved out ofevalNubisCubedSampleCoreand the two per-sample moon blocks: the sun dot product, both Henyey-Greenstein lobe evaluations, the sigma_ms sun-dot term, the powder view fade, the underside darkening amount, the sunset ambient ramp, both energy-conserving lobe weights, the micro-AO gain, the ambient-shadow amount, and - the largest - the Wrenninge moon phase-octave loop, a 3-iteration loop overhgPhase()that produced an identical value at every one of the march's 64-96 samples. Two deadexp()s are now also branch-skipped: the sunset cool-blend reach curve abovecloudSunsetAmbientRampHighSun(every midday frame) and the undersideskyDownwhen darkening is zero. - Density-sampler tap reorder. The step-6d mid-frequency tap moved ahead of the
two-scale base taps so a conservative surface gate can run on it alone. The base
pair (two volume fetches plus the erosion composite) used to be paid by every
sample in the
maxOutwardKmskirt band before the step-7 gate discarded the result - and the SDF sphere-trace parks samples in exactly that band. The gate bounds the unknown base-tap wobble by its construction range [-0.5, 0.5], so it is exact at the shipping defaultcloudDetailStrength = 0and strictly conservative above it; returned density is unchanged either way. cloudPlanetRadiushoisted. It evaluates anexp()and is frame-constant, butcomputeCloudHeightFractioncalled it up to five times per march sample (the density tap, two near-field sun taps, two moon taps). NewcomputeCloudHeightFractionRtakes the radius as a parameter; the radius lives inCloudShadeContext.cloudRadiusKm.- D_ambient tap skipped when it cannot matter.
downTau's only consumer isverticalLight, which is exactly 1 wheneverdarkenStrengthis 0 - the whole night / low-sun band, and any config withcloudBottomDarkening = 0. Saves one 3D texture tap plus anexp()per dense sample there.
src/dxvk/shaders/rtx/pass/atmosphere/cloud_march_common.slangh- fork-owned change.CloudShadeContextmoved above the evaluator and split frame-constant / ray-constant; newcloudShadeContextBeginRaycalled once per ray frommarchCloudLayers;evalNubisCubedSampleCoresignature takes the context in place of the six view/sun/sky vectors it used to receive; both moon blocks drop their per-sample octave loop;sampleCloudSunOpticalDepth_local/_localSlabtake the cloud radius; D_ambient tap gated onctx.darkenStrength.src/dxvk/shaders/rtx/pass/atmosphere/cloud_nubis3_common.slangh- fork-owned change. Step-6d mid tap +wobbleMidmoved ahead of the two-scale base taps, with the conservative surface gate between them.src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_common.slangh- fork-owned addition.computeCloudHeightFractionRradius-parameterized core; both existing overloads now delegate to it, unchanged.
No RTX_OPTION, CB, or binding changes - RtxOptions.md regen not required.
Workstream - clouds-off gate on the cloud voxel-grid bakes (fork - 2026-07-30, perf + correctness)
The D_sun / D_ambient voxel grids (256x256x32 voxels, 8 and 6 density taps per
voxel respectively) were dispatched every frame regardless of cloudEnabled -
measured at ~0.5 ms in the 2026-06-11 sky-perf bisect, which flagged the missing
gate as a follow-on that was never done. With clouds off nothing consumes them, so
the cost was pure waste, and it silently inflated every "what does the sky alone
cost" measurement.
Gating the bake alone would have been a bug: the terrain cloud-shadow consumer was
gated only on cloudVoxelShadowsEnable, so a closed gate would have left it
sampling whatever the last bake left in the grid. That path turns out to have had a
pre-existing correctness bug of the same shape - with cloudEnabled off and
cloudVoxelShadowsEnable on, terrain was already being shadowed by clouds that
render nothing, while the analytical twin evalCloudGroundShadow had always
early-outed on cloudEnabled. Both shadow paths now agree.
The consumer gate lives in the shared _impl rather than at the call sites: there
are several consumers (surface NEE, volumetric NEE, the SSS fold, the debug view)
and the enum-875 debug view exists specifically to compare the production and debug
forms, so they must not diverge.
src/dxvk/rtx_render/rtx_atmosphere.cpp- fork-owned change.computeLutstakes acloudsEnabledlocal; the D_sun / D_ambient dispatch condition gains it. While the gate is closed the cached voxel-grid key is zeroed each frame so the frame clouds return forces a fresh bake instead of trusting a key that went stale behind the gate.src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_common.slangh- fork-owned change.sampleCloudGroundShadow_OptionB_implearly-returns 1.0 whenargs.cloudEnabled < 0.5, mirroringevalCloudGroundShadow's long-standing gate.
No RTX_OPTION, CB, or binding changes - RtxOptions.md regen not required.
Workstream - contribution-weighted cloud lighting LOD (fork - 2026-07-30, perf)
Ablation measured the near-field live sun refinement (nubis3SunNearFieldKm, shipped
at its 3 km cap) at ~1 ms of the 3.3 ms cloud pass - dragging it to 0 recovers
the full millisecond but loses the lobe self-shadowing that makes clouds read as
volumes. It costs two full density-sampler calls per dense lit sample, roughly 9 of
the ~16 3D texture taps.
It was gated on density >= 0.05 alone - nothing about visibility. With
cloudDensity = 4 the view transmittance decays geometrically, so a large fraction
of dense samples sit deep inside the cloud paying full price while contributing
almost nothing to the pixel.
New cloudLightingLodThreshold gates both the near-field sun refinement and the
moon shadow march on the sample's ACTUAL contribution weight - view transmittance x
aerial haze x its own opacity, which is exactly the factor its color is multiplied
by in the composite. Folding aerial haze into the weight also retires
distance-dimmed samples automatically, with an error bound, instead of needing a
hand-tuned distance cutoff.
Both fallbacks are the ones the existing thin-sample density gates already use (the D_sun grid, and unshadowed moonlight), so this only ever coarsens lighting that was designed to degrade that way - it never removes cloud material. There is no held value or refresh quantum: the weight is a deterministic function of already-jittered quantities, so the switch point moves with the jitter like every other threshold in the march, unlike the caching patterns that caused the posterization family.
Enabling it required hoisting the Beer-Lambert and aerial-perspective factors above the lighting block - an exact reorder, since they depend only on density, segment length and camera distance, all final at that point.
src/dxvk/shaders/rtx/pass/atmosphere/cloud_march_common.slangh- fork-owned change.integrateCloudSamplecomputeseffectiveDensity/stepTransmittance/aerialT_haze/aerialT_fadebefore the lighting and derivessampleWeight+refineLightingfrom them; the near-field sun gate and the moon shadow gate both takerefineLighting. The accumulation at the end reuses the hoisted values.src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h- fork-owned change.cloudLightingLodThresholdrides the formerpadRetired6slot; CB layout unchanged.src/dxvk/rtx_render/rtx_options.h- fork-owned change. New RTX_OPTION, default 0.02.src/dxvk/rtx_render/rtx_atmosphere.cpp- fork-owned change. CB fill replaces the padRetired6 zero-fill.src/dxvk/rtx_render/rtx_fork_atmosphere.cpp- fork-owned change. "Lighting LOD" slider under Clouds > Shape, plus "Sun Shadow (Near)" restored to the panel (the curation pass had demoted it to conf-only as "pinned 3 km"; it is the primary live perf-ablation lever and those have to be ImGui widgets).
RtxOptions.md REGEN PENDING (new rtx.atmosphere.cloudLightingLodThreshold).
Workstream - de-jittered D_sun near section (fork - 2026-07-30, perf)
Attacks the largest measured cost in the cloud pass: the near-field LIVE sun
refinement is ~1 ms of the 3.3 ms (nubis3SunNearFieldKm 3 -> 0 recovers
exactly that), costing two full density-sampler calls per dense lit sample.
The refinement's own comment justifies it as "the grid bake integrates with ~0.6 km jittered taps and bilinear filtering smooths what survives, so lobe-scale self-shadowing is low-passed away". That is arithmetically false:
- the bake already spends 5-8 taps inside the first 3 km (8 on a short sun path, 5 at 0.6 km steps on a long one) - DENSER along the sun ray than the live refinement's 2 taps at 0.75 / 2.25 km;
- grid texels are ~47 m horizontal (12 km / 256) and ~95 m vertical, far above Nyquist for the 1-2.5 km lobes in question; trilinear blur is ~1 texel.
The lobe signal is representable in the grid and is not being low-passed away. What
differs is estimator COHERENCE. The tap phase is hash31(worldPosYUpKm * 173.3) -
decorrelated per voxel, world-anchored, frozen. Sampling 87-875 m detail content at
~0.5 km strata leaves per-voxel OD sigma ~0.15-0.25, and through exp(-sigma * OD)
with sigma = 4 that is up to a ~2x STATIC shading swing at 1-2 texel (50-150 m)
granularity - worst on lit faces, where low OD makes the exponential most sensitive.
The lobe gradients are present but buried under frozen mottle. The live refinement
wins by being a DETERMINISTIC estimator anchored to the shade point: its (larger)
quadrature bias varies continuously and reads as shading structure, not noise.
Fix: the near section becomes a deterministic fixed-count quadrature (8 taps, phase
0.5, over min(tExit, 3 km)); the far tail keeps its jitter, where undersampling
genuinely needs decorrelating and the contribution sits deep enough in the
exponential that mottle is invisible. A FIXED count is required - the historical
"concentric arcs" artifact came from ceil(tExit / 0.6) jumping between neighbouring
voxels, not from determinism, so a fixed near count cannot reintroduce it.
The far section is RE-BASED to partition [nearEnd, tExit] rather than having taps
skipped out of the old [0, tExit] lattice. Skipping would leave a jittered partial
cell at the handoff whose width varies per voxel - reintroducing exactly the
per-voxel variance the near section removes, right at the boundary. Re-based, the
tap weights sum to the path length exactly for every voxel (verified for
tExit = 1.5 / 3 / 6 / 12 km). Bake tap count goes 8/10/20 -> 8/13/23.
Zero shade-path changes: nubis3SunNearFieldKm = 0 already routes the evaluator
to the pure grid tap, so the restored "Sun Shadow (Near)" slider IS the in-game A/B
between live and baked near-field. If the grid-only look holds, the knob ships at 0
and the ~1 ms is recovered.
Also fixes the same frozen mottle in terrain cloud shadows, which read the same grid.
src/dxvk/shaders/rtx/pass/atmosphere/cloud_nubis3_common.slangh- fork-owned change.sampleCloudSunOpticalDepthAtWorldsplits into a deterministic 8-tap near section and the original jittered scheme re-based onto the remaining path. The old singlesamples/stepLenlattice is gone. Bake integrand still uses the FULL sampler (coarseOd = false), so render/grid iso-parity is untouched.
OPEN, only if the knob ships at 0: with cloudVoxelShadowsEnable = false the grid
falls back to the granularity key, which zeroes the boil/evolution animation fields -
so the grid would go stale against animated detail. Either force per-frame bakes when
nubis3SunNearFieldKm == 0, or fold a quantized boil phase into the voxel key.
(Not a problem in the shipping config: cloudVoxelShadowsEnable defaults true, which
makes voxelGridsDirty unconditionally true and the grid rebake every frame.)
Note - cloud march micro-optimizations (fork - 2026-07-30, perf)
Two small changes shipped alongside the cloud perf work above, plus one negative result worth recording so it is not re-attempted:
[unroll]on the moon precompute loop (cloud_march_common.slangh). Its two sibling MAX_MOONS loops were already unrolled; this one was not. Predicted to eliminate the shader's only local-memory arrays (a 256 BMoonParams[4]plus threevec3[4]) by making every index constant. MEASURED: it did not. Re-parsing the rebuilt SPIR-V gave byte-identical accounting (1 function, 309 Function-storage vars, 2,048 B, still one 256 Barray[4]). A cross-shader correlation showed the array tracks readingmoons[]at all — it is absent from every atmosphere shader that never touches moons, including ones that also copyAtmosphereArgsby value, so it is caused neither by the dynamic index nor by the CB copy. Standing lesson: a Function-storage array in SPIR-V does NOT imply scratch in the final ISA — constant-indexed local arrays are routinely promoted by the driver's own SROA, so SPIR-V local accounting cannot settle occupancy questions. The[unroll]was kept (bit-identical, consistent with its siblings) but is not a win.frac()dropped on the SDF taps only (cloud_nubis3_common.slangh).tileUVis already fracced and the hex offsets are in [0,1), so those operands are bounded to [0,2) and the sampler's REPEAT mode wraps them identically. Deliberately KEPT on the detail taps:boilPoscarries wind/boil/evolution offsets that accumulate without bound over a session, soboilPos * detailFreqcan reach the hundreds, and the frac is what keeps the coordinate small before the texture unit converts it to limited-precision fixed point. A comment marks this at the site.
Workstream - retire the live near-field sun path (fork - 2026-07-30, perf)
Follows the de-jittered D_sun near section above. With the bake's near-field estimator made coherent, the grid-only look was compared against the live-refined look in an 11-person vote and PREFERRED - on top of recovering ~1 ms of the 3.3 ms cloud pass. The live path is therefore removed outright rather than defaulted off.
Verified at the IR level: cloud_render.spv static SampleLevel count drops 85 -> 65
(SDF 40 -> 30, detail volume 32 -> 24, D_sun 5 -> 3), exactly the two inlined
near-field sampler bodies (fixed-step + adaptive march) plus their two range-end grid
taps. Runtime DLL shrinks ~56 KB.
A latent bug had to be fixed first. normalizeForSkyLutCache zeroes
cloudBoilPhase / cloudEvolutionOffset* on the stated grounds that they "feed only
the view-path cloud taps, not any LUT bake". That holds for the sky LUTs but NOT for
the D_sun / D_ambient bakes, whose integrand is the shared density sampler and so
reads the animated detail field through boilPos. The voxel-grid cache key therefore
never noticed clouds evolving. This was harmless while the live near-field taps
re-sampled the animated field every frame; with them gone the grid is the sole source
of sun occlusion, and any config running cloudVoxelShadowsEnable = false (which is
the only path that consults the granularity key at all) would have lit animating
clouds with a frozen shadow field - shadows visibly lagging the clouds they belong
to. The animation fields are now restored into the key, quantized on the same km
granularity as wind and camera, so staleness stays bounded by one step instead of
forcing an unconditional per-frame bake.
Kept deliberately: the coarse OD sampler path (both moon shadow marches still use it)
and cloudLightingLodThreshold (now gates only the moon march; still defaults to 0
and is still documented as tested-and-rejected).
src/dxvk/shaders/rtx/pass/atmosphere/cloud_march_common.slangh- fork-owned change. Near-field block deleted fromintegrateCloudSample;dSunOverridestays -1 so the evaluator takes the pure grid tap. A note at the site records why the path existed, why the bake fix supersedes it, and the warp-uniformity lesson - a contribution-weighted gate on this exact block measured a recovery of precisely zero, because the pass is tap-latency-bound and warp-synchronized, so per-lane conditional work does not convert to time.src/dxvk/rtx_render/rtx_atmosphere.cpp- fork-owned change.normalizeForVoxelGridKeycaptures and re-quantizescloudBoilPhase/cloudEvolutionOffset*after the base normalizer zeroes them. CB fill for the retired option replaced by a pad zero-fill.src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_args.h- fork-owned change.nubis3SunNearFieldKm->padRetired12; CB layout unchanged.src/dxvk/rtx_render/rtx_options.h- fork-owned change. RTX_OPTION removed.src/dxvk/rtx_render/rtx_fork_atmosphere.cpp- fork-owned change. "Sun Shadow (Near)" slider removed; stale conf-only comment corrected.src/dxvk/shaders/rtx/pass/atmosphere/atmosphere_common.slangh- fork-owned change. Second half of the voxel terrain-shadow gate:|| args.cloudCoverageMean < 0.01f. User-reported: snapping coverage 0.8 -> 0 left terrain shadows behind. Coverage is a live LEVEL-SET OFFSET, so 0 only shrinks bodies by ~130 m at defaults - the density field still contains clouds. What removes them from screen is the render pass's separate hard early-out ateffectiveCoverageMean < 0.01, so the D_sun bake kept integrating a field the renderer had stopped drawing. The analytical twinevalCloudGroundShadowalways had both halves of this condition; the earlier cloudEnabled fix ported only one. PlaincloudCoverageMean(not the render pass's layer-2 max) is correct here because the D_sun grid is baked over layer 1 only. Distinct from the animation-key bug above: this one fires in ANY config, whereas the staleness bug needscloudVoxelShadowsEnable = false.
Note: user rtx.conf files carrying rtx.atmosphere.nubis3SunNearFieldKm will log a
harmless unknown-option warning.