From 5f328c635086e68cc76105d65ed5e0941ddd3429 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Wed, 8 Apr 2026 02:35:49 -0700 Subject: [PATCH 1/4] fix(VR): interior water submersion masks --- .../Upscaling/UnderwaterMaskUpscalePS.hlsl | 14 ++++++++++++-- package/Shaders/Common/SharedData.hlsli | 2 ++ src/State.cpp | 18 ++++++++++++++++++ src/State.h | 2 ++ src/Utils/Game.cpp | 4 +++- 5 files changed, 37 insertions(+), 3 deletions(-) diff --git a/features/Upscaling/Shaders/Upscaling/UnderwaterMaskUpscalePS.hlsl b/features/Upscaling/Shaders/Upscaling/UnderwaterMaskUpscalePS.hlsl index 66dfd559be..213ccd6931 100644 --- a/features/Upscaling/Shaders/Upscaling/UnderwaterMaskUpscalePS.hlsl +++ b/features/Upscaling/Shaders/Upscaling/UnderwaterMaskUpscalePS.hlsl @@ -63,6 +63,15 @@ PS_OUTPUT main(PS_INPUT input) // itself, which always maps to the center tile (12). float waterHeight = SharedData::GetWaterData(float3(0, 0, 0), eyeIndex).w; + // Tile sentinel: try TESWaterSystem fallback. WaterSystemHeight is valid only when + // playerUnderwater == true (fully submerged); it is stored eye-0 camera-relative so + // the same per-eye correction as GetWaterData applies. + if (waterHeight <= WATER_HEIGHT_NO_TILE_SENTINEL) { + float sysHeight = SharedData::WaterSystemHeight; + if (sysHeight > WATER_HEIGHT_NO_TILE_SENTINEL) + waterHeight = sysHeight + FrameBuffer::CameraPosAdjust[0].z - FrameBuffer::CameraPosAdjust[eyeIndex].z; + } + // GetWaterData returns INT_MIN (~-2.147e9) when the tile is outside the 5x5 grid. if (waterHeight > WATER_HEIGHT_NO_TILE_SENTINEL) { // Unpack from side-by-side stereo layout to per-eye UV [0, 1] @@ -112,9 +121,10 @@ PS_OUTPUT main(PS_INPUT input) } return psout; } - // No water tile in range: fall through to the standard sampler path. + // No water tile or system height available: fall through to the standard sampler path. // The left-eye result from the vanilla mask is still accurate here; the right-eye - // will be approximate, but in the absence of nearby water the visual impact is nil. + // will be approximate, but both sources failing implies no nearby water so the + // visual impact is nil. # endif // Upscale using linear sampling with jitter-corrected coordinates diff --git a/package/Shaders/Common/SharedData.hlsli b/package/Shaders/Common/SharedData.hlsli index 767068f6df..60e9919b58 100644 --- a/package/Shaders/Common/SharedData.hlsli +++ b/package/Shaders/Common/SharedData.hlsli @@ -29,6 +29,8 @@ namespace SharedData float4 AmbientSHG; float4 AmbientSHB; float4 HDRData; + float WaterSystemHeight; // TES::GetWaterHeight at eye-0 in camera-relative Z; -FLT_MAX when no water body found + float3 pad_SharedData; }; struct GrassLightingSettings diff --git a/src/State.cpp b/src/State.cpp index 4892da9424..d3812a0ebf 100644 --- a/src/State.cpp +++ b/src/State.cpp @@ -856,6 +856,24 @@ void State::UpdateSharedData([[maybe_unused]] bool a_inWorld, [[maybe_unused]] b } } + // Fallback water height for the VR analytical mask when tile 12 returns the sentinel. + // Uses player->GetWaterHeight() (reads relevantWaterHeight from LOADED_REF_DATA) gated by + // underwaterCount > 0 so it is only set when the player is actually in a water body. + // Covers both interior water (where TES::GetWaterHeight returns -NI_INFINITY) and exterior + // partial submersion. Stored as eye-0 camera-relative Z to match WaterData[].w. + data.WaterSystemHeight = -RE::NI_INFINITY; + if (REL::Module::IsVR()) { + if (auto player = globals::game::player) { + if (player->loadedData && player->loadedData->underwaterCount > 0) { + float worldHeight = player->GetWaterHeight(); + if (worldHeight > -RE::NI_INFINITY) { + auto eye0Pos = Util::GetEyePosition(0); + data.WaterSystemHeight = worldHeight - eye0Pos.z; + } + } + } + } + data.InInterior = Util::IsInterior(); if (globals::game::sky) diff --git a/src/State.h b/src/State.h index 3d9ae141ec..9d7331958b 100644 --- a/src/State.h +++ b/src/State.h @@ -223,6 +223,8 @@ class State float4 AmbientSHG; float4 AmbientSHB; float4 HDRData; // xyz + menu scene encoding in w — see HDRDisplay::GetSharedDataHDR + float WaterSystemHeight; // TES::GetWaterHeight at eye-0 in camera-relative Z; -NI_INFINITY when no water body found (VR only) + float pad1[3]; }; STATIC_ASSERT_ALIGNAS_16(SharedDataCB); diff --git a/src/Utils/Game.cpp b/src/Utils/Game.cpp index 31900f417c..464bc863d0 100644 --- a/src/Utils/Game.cpp +++ b/src/Utils/Game.cpp @@ -84,7 +84,9 @@ namespace Util data.z *= color.blue; } - data.w = cell->GetExteriorWaterHeight() - position.z; + float cellWaterHeight; + if (cell->GetWaterHeight(position, cellWaterHeight)) + data.w = cellWaterHeight - position.z; return data; } From df73cbb1363cad596bf000e67e23ec1653a769fc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 02:24:26 +0000 Subject: [PATCH 2/4] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20pre-commit.?= =?UTF-8?q?ci=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automated formatting by clang-format, prettier, and other hooks. See https://pre-commit.ci for details. --- src/State.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/State.h b/src/State.h index 9d7331958b..d78979715f 100644 --- a/src/State.h +++ b/src/State.h @@ -222,7 +222,7 @@ class State float4 AmbientSHR; float4 AmbientSHG; float4 AmbientSHB; - float4 HDRData; // xyz + menu scene encoding in w — see HDRDisplay::GetSharedDataHDR + float4 HDRData; // xyz + menu scene encoding in w — see HDRDisplay::GetSharedDataHDR float WaterSystemHeight; // TES::GetWaterHeight at eye-0 in camera-relative Z; -NI_INFINITY when no water body found (VR only) float pad1[3]; }; From c3df235bb93041b9a3aaceb9470aeb0a0c9dc92c Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Thu, 9 Apr 2026 18:07:57 -0700 Subject: [PATCH 3/4] refactor: use game globals for isVR --- src/State.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/State.cpp b/src/State.cpp index d3812a0ebf..04db5dd2ae 100644 --- a/src/State.cpp +++ b/src/State.cpp @@ -862,7 +862,7 @@ void State::UpdateSharedData([[maybe_unused]] bool a_inWorld, [[maybe_unused]] b // Covers both interior water (where TES::GetWaterHeight returns -NI_INFINITY) and exterior // partial submersion. Stored as eye-0 camera-relative Z to match WaterData[].w. data.WaterSystemHeight = -RE::NI_INFINITY; - if (REL::Module::IsVR()) { + if (globals::game::isVR) { if (auto player = globals::game::player) { if (player->loadedData && player->loadedData->underwaterCount > 0) { float worldHeight = player->GetWaterHeight(); From 37686c277b3473c6efd36a616845d9d316040b70 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Thu, 9 Apr 2026 18:08:29 -0700 Subject: [PATCH 4/4] refactor: reuse pad --- package/Shaders/Common/SharedData.hlsli | 12 +++++------- src/State.h | 6 ++---- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/package/Shaders/Common/SharedData.hlsli b/package/Shaders/Common/SharedData.hlsli index 60e9919b58..ed40111591 100644 --- a/package/Shaders/Common/SharedData.hlsli +++ b/package/Shaders/Common/SharedData.hlsli @@ -20,17 +20,15 @@ namespace SharedData float Timer; uint FrameCount; uint FrameCountAlwaysActive; - bool InInterior; // If the area lacks a directional shadow light e.g. the sun or moon - bool InMapMenu; // If the world/local map is open (note that the renderer is still deferred here) - bool HideSky; // HideSky flag in WorldSpace, e.g. Blackreach - float MipBias; // Offset to mip level for TAA sharpness - float pad0; + bool InInterior; // If the area lacks a directional shadow light e.g. the sun or moon + bool InMapMenu; // If the world/local map is open (note that the renderer is still deferred here) + bool HideSky; // HideSky flag in WorldSpace, e.g. Blackreach + float MipBias; // Offset to mip level for TAA sharpness + float WaterSystemHeight; // TES::GetWaterHeight at eye-0 in camera-relative Z; -FLT_MAX when no water body found (VR only) float4 AmbientSHR; float4 AmbientSHG; float4 AmbientSHB; float4 HDRData; - float WaterSystemHeight; // TES::GetWaterHeight at eye-0 in camera-relative Z; -FLT_MAX when no water body found - float3 pad_SharedData; }; struct GrassLightingSettings diff --git a/src/State.h b/src/State.h index d78979715f..ad2658888b 100644 --- a/src/State.h +++ b/src/State.h @@ -218,13 +218,11 @@ class State uint InMapMenu; uint HideSky; float MipBias; - float pad0; // unused; must match SharedData.hlsli cbuffer (AmbientSHR 16-byte alignment) + float WaterSystemHeight; // TES::GetWaterHeight at eye-0 in camera-relative Z; -NI_INFINITY when no water body found (VR only) float4 AmbientSHR; float4 AmbientSHG; float4 AmbientSHB; - float4 HDRData; // xyz + menu scene encoding in w — see HDRDisplay::GetSharedDataHDR - float WaterSystemHeight; // TES::GetWaterHeight at eye-0 in camera-relative Z; -NI_INFINITY when no water body found (VR only) - float pad1[3]; + float4 HDRData; // xyz + menu scene encoding in w — see HDRDisplay::GetSharedDataHDR }; STATIC_ASSERT_ALIGNAS_16(SharedDataCB);