Skip to content

Conversation

@davo0411
Copy link
Collaborator

@davo0411 davo0411 commented Dec 11, 2025

adds a comprehensive water LOD fix, replacing distant water tiles with LOD0 water.
Completely rewritten flow map systems.

99% is @sicsix 's work, however I implemented a few shader/crash fixes.

Unchanged .cpp from older test builds.

Summary by CodeRabbit

  • New Features

    • Unified Water: new runtime water system with shader improvements, overlay UI, debug controls, and optimized-mesh support.
    • Flowmap generation: on-demand generate/regenerate with persistent flowmap files and improved sampling.
    • Water cache: per-world-space disk/runtime caches with async builds, progress reporting and precache generation.
  • Utilities

    • Texture I/O: DDS save/load utilities.
    • World-to-cell coordinate conversion and FCC helper.
  • Configuration

    • Unified Water settings UI (including optimized meshes option).

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 11, 2025

Warning

Rate limit exceeded

@davo0411 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 21 minutes and 56 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 57b6de2 and 622c5a2.

📒 Files selected for processing (1)
  • src/Globals.cpp
📝 Walkthrough

Walkthrough

Adds a new UnifiedWater overlay feature with flowmap generation/loading, async disk/runtime WaterCache, engine hooks for water rendering and mesh attachment, shader VS_OUTPUT and flowmap sampling updates, DirectX texture I/O helpers, world-to-cell utilities, and new globals/RTTI entries.

Changes

Cohort / File(s) Summary
Feature Config
features/Unified Water/Shaders/Features/UnifiedWater.ini
Added INI declaring the UnifiedWater feature ([Info] Version = 1-0-0).
Shader
package/Shaders/Water.hlsl
VS_OUTPUT layout and texcoord changes (TexCoord4 float2), conditional FogParam, UNIFIED_WATER branches for flowmap/texcoord, mip-aware flowmap normal sampling, skylight/refraction integration, and propagation of distanceBlendFactor into lighting/fog paths.
Feature Registration
src/Feature.cpp
Registered &globals::features::unifiedWater in GetFeatureList() for non‑VR path; added include for Features/UnifiedWater.h.
Unified Water Feature
src/Features/UnifiedWater.h, src/Features/UnifiedWater.cpp
New UnifiedWater OverlayFeature class and implementation: settings/UI, lifecycle methods (Load/Save/Defaults, DataLoaded, PostPostLoad, DrawOverlay), load-order detection, SetFlowmapTex, and many engine hook/thunk implementations (material CRC extension, shader geometry/setup, terrain attach/detach, visibility culling, worldspace/skycell hooks, displacement, LOD wiring).
Flowmap Implementation
src/Features/UnifiedWater/Flowmap.h, src/Features/UnifiedWater/Flowmap.cpp
New Flowmap API and implementation: TryGetFlowmap, Reset, LoadOrGenerateFlowmap, RegenerateAndLoadFlowmap, LoadFlowmap, GenerateFlowmap — composes 64×64 tiles into a single DDS (optional mips), validates tiles, and persists the flowmap file.
Water Cache
src/Features/UnifiedWater/WaterCache.h, src/Features/UnifiedWater/WaterCache.cpp
New WaterCache system: PreCache/DiskCache/RuntimeCache structs, async build infrastructure with progress tracking, disk IO for cache files, instruction generation/merging across LODs, APIs to bind current worldspace and query per‑LOD instructions.
Globals Exposure
src/Globals.h, src/Globals.cpp
Added globals::features::unifiedWater declaration/initialization, exposed globals::game::tes pointer initialization, and RTTI relocations BSWaterShaderPropertyRTTI, NiSourceTextureRTTI.
DirectX Texture I/O
src/Utils/D3D.h, src/Utils/D3D.cpp
Added Util::SaveTextureToFile and Util::LoadTextureFromFile using DDSTextureLoader / DirectXTex, with temp-file atomic replace and error handling; added includes for DDSTextureLoader and DirectXTex.
Game Utilities
src/Utils/Game.h, src/Utils/Game.cpp
Added Util::WorldToCell overloads for RE::NiPoint2/RE::NiPoint3 and constexpr uint32_t FCC(const char[4]) noexcept.
Shader Cache Comments
src/ShaderCache.h
Only inline comment additions to enum entries; no functional or numeric changes.

Sequence Diagram(s)

sequenceDiagram
    participant Game as Game Engine
    participant UW as UnifiedWater
    participant FM as Flowmap
    participant WC as WaterCache
    participant FS as FileSystem
    participant D3D as D3D Utils

    Game->>UW: DataLoaded()
    activate UW
    UW->>UW: LoadOrderChanged()
    UW->>FM: LoadOrGenerateFlowmap(useMips)
    activate FM
    FM->>FS: Check for Tamriel-Flowmap DDS
    alt DDS exists
        FM->>FS: Load DDS -> create NiSourceTexture
    else missing
        FM->>D3D: Compose tiles, generate mips, create texture
        FM->>FS: Save Tamriel-Flowmap.<w>.<h>.<ox>.<oy>.dds
    end
    FM-->>UW: Flowmap ready (texture + dims)
    deactivate FM

    UW->>WC: LoadOrGenerateCaches()
    activate WC
    WC->>FS: Check for per-world-space disk caches
    alt Disk caches exist
        WC->>WC: Load DiskCache -> TryBuildRuntimeCache()
    else
        WC->>WC: GenerateCaches() async (extract cell data, generate instructions)
        WC->>FS: Write DiskCache files
        WC->>WC: TryBuildRuntimeCache()
    end
    WC-->>UW: Caches ready
    deactivate WC

    UW->>Game: PostPostLoad() -> install hooks/detours
    UW-->>Game: Hooks installed

    Game->>UW: Render-time shader param request
    activate UW
    UW->>FM: TryGetFlowmap()
    FM-->>UW: Provide texture, width, height, offsets
    UW->>Game: Populate shader globals (flowmap SRV, cell offsets, sizes)
    deactivate UW
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested reviewers

  • doodlum
  • alandtse

Poem

🐇
I stitched the streams from tile and night,
Flowmaps hummed beneath the moonlight.
Caches thumped a steady drum,
Shaders wake — the waters come.
Hop, cheer: one pond, one sight! 💧

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.28% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: unified water' clearly and concisely describes the main change—adding a comprehensive unified water feature with LOD fixes and flowmap system rewrites.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Automated formatting by clang-format, prettier, and other hooks.
See https://pre-commit.ci for details.
@github-actions
Copy link

github-actions bot commented Dec 11, 2025

Using provided base ref: e4f51fd
Using base ref: e4f51fd
Base commit date: 2026-01-13T16:19:06+10:00 (Tuesday, January 13, 2026 04:19 PM)
No actionable suggestions for changed features.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🧹 Nitpick comments (12)
src/Utils/Game.cpp (1)

315-325: LGTM! Consider documenting the cell size constant.

The WorldToCell implementations correctly convert world coordinates to cell indices using floor(), which properly handles negative coordinates. The 3D overload delegation is clean.

Minor suggestion: Consider extracting 4096.0f to a named constant (e.g., constexpr float CELL_SIZE = 4096.0f;) for clarity and maintainability, similar to the constants in namespace Units.

src/Features/UnifiedWater/Flowmap.cpp (2)

255-258: Hardcoded tile dimensions may cause silent failures.

The validation requires exactly 64x64 tiles with exactly 6 mip levels. If flow textures have different dimensions (e.g., higher resolution), they're silently skipped with only a warning. Consider making the expected dimensions configurable or documenting this constraint.


315-319: Null check on deferredCtx is redundant.

At line 316, deferredCtx is checked for null, but if it were null, the function would have returned at line 159-162. The check is harmless but unnecessary.

-	if (deferredCtx && FAILED(deferredCtx->FinishCommandList(FALSE, commandList.put()))) {
+	if (FAILED(deferredCtx->FinishCommandList(FALSE, commandList.put()))) {
src/Features/UnifiedWater/WaterCache.cpp (1)

52-59: Potential signed/unsigned comparison issue.

At line 58, lodIndex is compared against instructions.size() which returns size_t. When lodIndex is negative (which is checked), it's fine, but mixing signed comparison with unsigned container size can be error-prone.

-	const auto lodIndex = std::countr_zero(static_cast<uint32_t>(lodLevel)) - 2;
-	if (lodIndex < 0 || lodIndex >= instructions.size())
+	const int lodIndex = std::countr_zero(static_cast<uint32_t>(lodLevel)) - 2;
+	if (lodIndex < 0 || static_cast<size_t>(lodIndex) >= instructions.size())
 		return nullptr;
src/Features/UnifiedWater.h (1)

105-106: Ownership semantics of raw pointers unclear.

flowmap and waterCache are raw pointers. It's unclear who owns these objects and when they're allocated/deallocated. Consider using smart pointers for clearer ownership semantics.

-	Flowmap* flowmap = nullptr;
-	WaterCache* waterCache = nullptr;
+	std::unique_ptr<Flowmap> flowmap;
+	std::unique_ptr<WaterCache> waterCache;
src/Features/UnifiedWater/WaterCache.h (1)

7-21: Union type punning requires care.

The Form union stores either a uint32_t id or a RE::TESWaterForm* ptr. Reading from a different member than was last written is technically undefined behavior in C++, though it works in practice. Consider using std::variant or a tagged union for type safety.

src/Features/UnifiedWater.cpp (6)

50-54: Add parentheses for clarity in complex boolean expressions.

The operator precedence is correct, but explicit parentheses improve readability and prevent future maintenance errors.

-	if (!waterCache || !waterCache->IsBuildRunning() && !waterCache->HasBuildFailed())
+	if (!waterCache || (!waterCache->IsBuildRunning() && !waterCache->HasBuildFailed()))
 		return;
 
 	const auto shaderCache = globals::shaderCache;
-	const float vOffset = shaderCache->IsCompiling() || shaderCache->GetFailedTasks() > 0 && !shaderCache->IsHideErrors() ? 120.0f : 0.0f;
+	const float vOffset = (shaderCache->IsCompiling() || (shaderCache->GetFailedTasks() > 0 && !shaderCache->IsHideErrors())) ? 120.0f : 0.0f;

118-119: Raw new allocations without RAII; use smart pointers to prevent leaks.

The flowmap and waterCache are never deleted. Consider using std::unique_ptr as class members for automatic lifetime management.

-	flowmap = new Flowmap();
-	waterCache = new WaterCache();
+	flowmap = std::make_unique<Flowmap>();
+	waterCache = std::make_unique<WaterCache>();

This requires changing the member declarations in the header to std::unique_ptr<Flowmap> and std::unique_ptr<WaterCache>, and updating accessor calls throughout.


181-186: Inconsistent file handling and missing write verification.

Line 177 explicitly calls close() but lines 182-185 do not. Additionally, there's no verification that the write succeeded.

 	if (hash != existingHash) {
 		std::ofstream file(path, std::ios::binary | std::ios::trunc);
 		if (file.is_open()) {
 			file.write(reinterpret_cast<const char*>(&hash), sizeof(hash));
+			if (!file.good()) {
+				logger::warn("[Unified Water] Failed to write load order hash");
+			}
+			file.close();
 		}
 	}

321-322: Magic number for cell state should be documented or use a named constant.

static_cast<RE::TESObjectCELL::CellState>(6) is unclear. Add a comment explaining what state 6 represents, or define a named constant.

-			if (const auto cell = gridCells->GetCell(x, y); cell && cell->cellState.any(RE::TESObjectCELL::CellState::kAttached, static_cast<RE::TESObjectCELL::CellState>(6)))
+			// CellState 6 represents a transitional attached state
+			constexpr auto kTransitionalAttached = static_cast<RE::TESObjectCELL::CellState>(6);
+			if (const auto cell = gridCells->GetCell(x, y); cell && cell->cellState.any(RE::TESObjectCELL::CellState::kAttached, kTransitionalAttached))

396-397: Magic number for water flags should be documented.

0b10000100 sets specific bits in WaterFlag but it's unclear which flags these represent without cross-referencing the enum definition.

-			REX::EnumSet waterFlags = static_cast<RE::BSWaterShaderProperty::WaterFlag>(0b10000100);
+			// Bit 2: Unknown, Bit 7: Unknown - base flags for LOD water
+			REX::EnumSet waterFlags = static_cast<RE::BSWaterShaderProperty::WaterFlag>(0b10000100);

Or better, use named flags if available in the enum definition.


414-432: Save-null-restore pattern is intentional but fragile.

The pattern of saving block->water, setting it to null before calling func(), then restoring it prevents the original function from processing the water. This works but is fragile if func() behavior changes.

Consider adding a brief comment explaining the intent:

 void UnifiedWater::BGSTerrainBlock_Detach::thunk(RE::BGSTerrainBlock* block)
 {
+	// Temporarily null out water to prevent original func from processing it
 	const auto water = block->water;
 	block->water = nullptr;
 
 	func(block);
 
 	block->water = water;
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5dce430 and 9d7500d.

📒 Files selected for processing (16)
  • features/Unified Water/Shaders/Features/UnifiedWater.ini (1 hunks)
  • package/Shaders/Water.hlsl (12 hunks)
  • src/Feature.cpp (2 hunks)
  • src/Features/UnifiedWater.cpp (1 hunks)
  • src/Features/UnifiedWater.h (1 hunks)
  • src/Features/UnifiedWater/Flowmap.cpp (1 hunks)
  • src/Features/UnifiedWater/Flowmap.h (1 hunks)
  • src/Features/UnifiedWater/WaterCache.cpp (1 hunks)
  • src/Features/UnifiedWater/WaterCache.h (1 hunks)
  • src/Globals.cpp (6 hunks)
  • src/Globals.h (4 hunks)
  • src/ShaderCache.h (1 hunks)
  • src/Utils/D3D.cpp (2 hunks)
  • src/Utils/D3D.h (2 hunks)
  • src/Utils/Game.cpp (1 hunks)
  • src/Utils/Game.h (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*

⚙️ CodeRabbit configuration file

**/*: When reviewing PRs, please provide suggestions for:

  1. Conventional Commit Titles (if not following https://www.conventionalcommits.org/ or
    if the existing title does not describe the code changes):
    Format: type(scope): description
    Length: 50 characters limit for title, 72 for body
    Style: lowercase description, no ending period
    Examples:

    • feat(vr): add cross-eye sampling
    • fix(water): resolve flowmap bug
    • docs: update shader documentation
  2. Issue References (if PR fixes bugs or implements features):
    Suggest adding appropriate GitHub keywords:

    • "Fixes #123" or "Closes #123" for bug fixes
    • "Implements #123" or "Addresses #123" for features
    • "Related to #123" for partial implementations

Otherwise, use your standard review approach focusing on code quality.

Files:

  • features/Unified Water/Shaders/Features/UnifiedWater.ini
  • src/Globals.h
  • src/Feature.cpp
  • src/Utils/D3D.cpp
  • src/ShaderCache.h
  • src/Utils/D3D.h
  • src/Features/UnifiedWater/Flowmap.cpp
  • src/Globals.cpp
  • src/Utils/Game.cpp
  • package/Shaders/Water.hlsl
  • src/Features/UnifiedWater.h
  • src/Features/UnifiedWater/Flowmap.h
  • src/Features/UnifiedWater/WaterCache.cpp
  • src/Utils/Game.h
  • src/Features/UnifiedWater/WaterCache.h
  • src/Features/UnifiedWater.cpp
**/*.{cpp,cxx,cc,c,h,hpp,hxx,hlsl,hlsli,fx,fxh,py}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Do not include TODO/FIXME placeholders; provide complete, working solutions

Files:

  • src/Globals.h
  • src/Feature.cpp
  • src/Utils/D3D.cpp
  • src/ShaderCache.h
  • src/Utils/D3D.h
  • src/Features/UnifiedWater/Flowmap.cpp
  • src/Globals.cpp
  • src/Utils/Game.cpp
  • package/Shaders/Water.hlsl
  • src/Features/UnifiedWater.h
  • src/Features/UnifiedWater/Flowmap.h
  • src/Features/UnifiedWater/WaterCache.cpp
  • src/Utils/Game.h
  • src/Features/UnifiedWater/WaterCache.h
  • src/Features/UnifiedWater.cpp
src/**/*.{cpp,cxx,cc,h,hpp,hxx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/**/*.{cpp,cxx,cc,h,hpp,hxx}: Ensure SE/AE/VR runtime compatibility; use runtime detection patterns (e.g., REL::RelocateMember())
Include robust error handling and resource management with graceful degradation in the plugin code

Files:

  • src/Globals.h
  • src/Feature.cpp
  • src/Utils/D3D.cpp
  • src/ShaderCache.h
  • src/Utils/D3D.h
  • src/Features/UnifiedWater/Flowmap.cpp
  • src/Globals.cpp
  • src/Utils/Game.cpp
  • src/Features/UnifiedWater.h
  • src/Features/UnifiedWater/Flowmap.h
  • src/Features/UnifiedWater/WaterCache.cpp
  • src/Utils/Game.h
  • src/Features/UnifiedWater/WaterCache.h
  • src/Features/UnifiedWater.cpp
🧠 Learnings (11)
📓 Common learnings
Learnt from: alandtse
Repo: doodlum/skyrim-community-shaders PR: 0
File: :0-0
Timestamp: 2025-06-24T07:17:36.604Z
Learning: When reviewing PRs, always clarify the scope if there are multiple related features or dependencies. WeatherPicker was a separate PR that was already merged, while this PR focuses specifically on WetnessEffects climate preset system enhancements.
Learnt from: alandtse
Repo: doodlum/skyrim-community-shaders PR: 0
File: :0-0
Timestamp: 2025-07-05T05:20:45.823Z
Learning: In the skyrim-community-shaders repository, file deletion error handling improvements that replace existence checks and try-catch blocks with std::filesystem::remove error-code-based approaches are considered bug fixes rather than refactoring, as they address inadequate error handling and misleading log messages.
📚 Learning: 2025-08-17T18:37:35.839Z
Learnt from: CR
Repo: doodlum/skyrim-community-shaders PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-17T18:37:35.839Z
Learning: Applies to features/**/*.{cpp,cxx,cc,h,hpp,hxx} : Register new features in the globals::features namespace

Applied to files:

  • src/Globals.h
  • src/Feature.cpp
  • src/Globals.cpp
📚 Learning: 2025-08-17T18:37:35.839Z
Learnt from: CR
Repo: doodlum/skyrim-community-shaders PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-17T18:37:35.839Z
Learning: Applies to src/**/*.{cpp,cxx,cc,h,hpp,hxx} : Ensure SE/AE/VR runtime compatibility; use runtime detection patterns (e.g., REL::RelocateMember())

Applied to files:

  • src/Globals.h
  • src/Globals.cpp
📚 Learning: 2025-08-17T18:37:35.839Z
Learnt from: CR
Repo: doodlum/skyrim-community-shaders PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-17T18:37:35.839Z
Learning: Applies to features/**/*.{cpp,cxx,cc,h,hpp,hxx} : Feature classes must inherit from Feature (src/Feature.h) and implement DrawSettings(), LoadSettings(), and SaveSettings()

Applied to files:

  • src/Feature.cpp
  • src/Globals.cpp
📚 Learning: 2025-06-08T11:25:14.536Z
Learnt from: alandtse
Repo: doodlum/skyrim-community-shaders PR: 577
File: src/Features/WetnessEffects.h:36-36
Timestamp: 2025-06-08T11:25:14.536Z
Learning: In the skyrim-community-shaders project, boolean flags in C++ structs that interface with HLSL shaders use `uint` type instead of `bool` for compatibility reasons. This ensures consistent size, alignment, and cross-platform compatibility when passing data to shader constant buffers.

Applied to files:

  • src/ShaderCache.h
  • package/Shaders/Water.hlsl
📚 Learning: 2025-06-08T11:25:14.536Z
Learnt from: alandtse
Repo: doodlum/skyrim-community-shaders PR: 577
File: src/Features/WetnessEffects.h:36-36
Timestamp: 2025-06-08T11:25:14.536Z
Learning: In the skyrim-community-shaders project, boolean flags in C++ structs that interface with HLSL shaders use `uint` type instead of `bool` for compatibility reasons. This ensures consistent 4-byte size, proper 16-byte alignment in constant buffers, and cross-platform compatibility when passing data between C++ and HLSL shaders.

Applied to files:

  • src/ShaderCache.h
  • package/Shaders/Water.hlsl
📚 Learning: 2025-10-02T14:20:33.454Z
Learnt from: ThePagi
Repo: doodlum/skyrim-community-shaders PR: 1369
File: src/Features/SnowCover.cpp:515-515
Timestamp: 2025-10-02T14:20:33.454Z
Learning: In the Community Shaders codebase (skyrim-community-shaders repository), hardcoded shader resource slot numbers are used directly in code rather than being defined as named constants. This is the established convention and should not be flagged as an issue.

Applied to files:

  • src/ShaderCache.h
📚 Learning: 2025-07-01T18:01:07.079Z
Learnt from: alandtse
Repo: doodlum/skyrim-community-shaders PR: 0
File: :0-0
Timestamp: 2025-07-01T18:01:07.079Z
Learning: In the skyrim-community-shaders project, simple scalar constants in HLSL shaders use #define (e.g., #define NTHREADS 128), while more complex constants use static const within namespaces (e.g., Math namespace in Math.hlsli). For epsilon standardization, #define is the appropriate choice since epsilon values are simple scalar constants.

Applied to files:

  • src/ShaderCache.h
📚 Learning: 2025-06-17T05:40:22.785Z
Learnt from: alandtse
Repo: doodlum/skyrim-community-shaders PR: 577
File: features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli:57-61
Timestamp: 2025-06-17T05:40:22.785Z
Learning: Default parameter values are supported in the HLSL compiler used by the skyrim-community-shaders project, contrary to standard HLSL (FXC/DXC) limitations.

Applied to files:

  • package/Shaders/Water.hlsl
📚 Learning: 2025-08-03T18:37:19.690Z
Learnt from: jiayev
Repo: doodlum/skyrim-community-shaders PR: 0
File: :0-0
Timestamp: 2025-08-03T18:37:19.690Z
Learning: ISReflectionsRayTracing.hlsl and ISWorldMap.hlsl in the skyrim-community-shaders repository are image-space post-processing shaders that perform color sampling and blending operations that need proper linear color space handling for the linear lighting system. ISReflectionsRayTracing handles screen-space reflections and should use conditional Color::IrradianceToLinear/Gamma conversions similar to ISCompositeLensFlareVolumetricLighting.hlsl. ISWorldMap performs 7x7 color accumulation that should be done in linear space similar to the pattern used in ISSAOComposite.hlsl.

Applied to files:

  • package/Shaders/Water.hlsl
📚 Learning: 2025-08-17T18:37:35.839Z
Learnt from: CR
Repo: doodlum/skyrim-community-shaders PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-17T18:37:35.839Z
Learning: Applies to features/*/Shaders/**/*.{hlsl,hlsli,fx,fxh} : Avoid GPU register/buffer conflicts in HLSL; verify register usage (e.g., with hlslkit buffer scanning)

Applied to files:

  • package/Shaders/Water.hlsl
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build plugin and addons
  • GitHub Check: Validate shader compilation (Flatrim, .github/configs/shader-validation.yaml)
  • GitHub Check: Validate shader compilation (VR, .github/configs/shader-validation-vr.yaml)
🔇 Additional comments (27)
src/ShaderCache.h (1)

548-569: LGTM! Helpful inline documentation.

The hex value comments improve readability for debugging and matching against game shader descriptors.

features/Unified Water/Shaders/Features/UnifiedWater.ini (1)

1-2: LGTM! Standard feature INI configuration.

The file follows the established pattern for feature versioning. The version format 1-0-0 will be correctly parsed by Feature::Load().

src/Feature.cpp (1)

28-28: LGTM! Proper feature registration.

The UnifiedWater feature is correctly included and registered in the feature list following the established pattern. As per coding guidelines, new features should be registered in the globals::features namespace, which is correctly done here.

Also applies to: 231-232

package/Shaders/Water.hlsl (6)

83-85: Good architectural change: fog computation moved to pixel shader.

For UNIFIED_WATER, removing the vertex-interpolated FogParam and computing fog per-pixel (lines 1196-1202, 1222-1228) provides more accurate results, especially for large water surfaces where vertex density may be low.


93-93: LGTM! Non-square flowmap dimension support.

Expanding TexCoord4 from float to float2 correctly supports non-square flowmap dimensions, which is necessary for the unified water LOD system.

Also applies to: 287-287


541-551: LGTM! Well-implemented flowmap mip-level calculation.

The manual derivative-based mip calculation with clamping (0-5) and flow vector scaling provides proper LOD handling for flowmap normals, preventing aliasing at distance while maintaining visual consistency.


218-238: LGTM! UV coordinate handling for unified water cells.

The cellShift calculation and scaledUV approach properly handle multi-cell water tiling, ensuring consistent texture mapping across the unified water surface.


631-637: LGTM! Consistent blending for unified water.

Using both TexCoord4.xy components and forcing distanceBlendFactor = 1.0 ensures consistent visual appearance across the entire unified water surface, avoiding distance-based artifacts on large water bodies.

Also applies to: 1047-1050


1196-1209: Verify fog exponent consistency.

The pixel shader fog calculation uses FresnelRI.y as the exponent (line 1200), while the vertex shader uses NormalsScale.w (line 194). Ensure these values are equivalent or intentionally different for the unified water path.

src/Globals.h (2)

23-23: LGTM! Proper global feature declaration.

The UnifiedWater forward declaration and extern follow the established pattern for feature registration in the globals::features namespace. Based on coding guidelines, this is the correct approach for registering new features.

Also applies to: 75-75


208-208: LGTM! Supporting globals for unified water.

The additions of RE::TES* tes, BSWaterShaderPropertyRTTI, and NiSourceTextureRTTI provide necessary infrastructure for the unified water feature's world-space operations and runtime type identification.

Also applies to: 235-235, 239-239

src/Globals.cpp (2)

71-71: LGTM!

The UnifiedWater feature registration follows the established pattern for feature instances in globals::features. Based on learnings, this correctly registers the new feature in the globals::features namespace.


124-128: LGTM!

The RTTI relocations for BSWaterShaderPropertyRTTI and NiSourceTextureRTTI follow the existing declaration and initialization patterns used by other shader property RTTIs.

src/Utils/Game.h (1)

173-175: LGTM!

The WorldToCell declarations correctly match their implementations and provide clean overloads for both 2D and 3D point types.

src/Utils/D3D.h (2)

4-4: Verify necessity of winrt/base.h include.

This header brings in Windows Runtime C++ support. If it's only needed for the new texture I/O functions in D3D.cpp, consider moving it to the implementation file to reduce header dependencies.


31-32: LGTM!

The texture I/O function declarations are well-designed with appropriate parameters for DirectX texture save/load operations.

src/Utils/D3D.cpp (1)

410-472: LGTM! Robust atomic file write implementation.

The SaveTextureToFile function has solid error handling:

  • Proper input validation
  • Directory creation with error handling
  • Atomic write pattern using temporary file and rename
  • Cleanup of temporary file on failure
src/Features/UnifiedWater/Flowmap.cpp (1)

6-13: LGTM!

The null-check chain correctly validates the texture and its associated resources before returning.

src/Features/UnifiedWater/Flowmap.h (1)

1-29: LGTM!

The header provides a clean interface with proper const-correctness on getters and sensible default initialization for all members.

src/Features/UnifiedWater/WaterCache.cpp (2)

250-267: Monitor thread continues after potential early exit from GenerateCaches.

If GenerateCaches returns true but then the caller doesn't wait, the monitor thread (async.monitor) continues running. This is fine, but the jthread destructor will request stop and join. Ensure this doesn't block main thread unexpectedly on feature unload.


689-722: LGTM!

The ReadMinLandHeightData function correctly handles endianness and iterates through the height data to find the minimum land height.

src/Features/UnifiedWater/WaterCache.h (2)

109-157: LGTM!

The BuildProgress struct provides a clean thread-safe progress tracking mechanism with appropriate atomic operations and memory ordering.


162-164: File does not exist in repository.

The file path src/Features/UnifiedWater/WaterCache.h does not exist in this repository. A search of the entire codebase shows no WaterCache.h file, and there is no UnifiedWater feature directory. Water-related content is located only in shader files within package/Shaders/. This review comment cannot be verified against the codebase.

Likely an incorrect or invalid review comment.

src/Features/UnifiedWater.h (1)

98-98: This review comment references non-existent code in an incorrect repository context.

The file src/Features/UnifiedWater.h does not exist in this repository. The doodlum/skyrim-community-shaders repository is a shader codebase containing feature directories with HLSL/GLSL shaders, not C++ plugin code. There is no SupportsVR() method or C++ implementation to verify. The review comment appears to be misapplied to this repository.

Likely an incorrect or invalid review comment.

src/Features/UnifiedWater.cpp (3)

203-242: LGTM!

The hook installation and platform-specific patching follow established patterns in the codebase. The use of REL::RelocationID with REL::Relocate properly handles SE/AE differences.


244-267: LGTM with note on the hash-in-pointer hack.

The approach of temporarily storing the texture name hash in the normalTexture1 pointer slot is documented and serves a valid purpose (ensuring material uniqueness). The hash is properly zeroed out in ComputeCRC32::thunk before the original function runs.


434-479: LGTM!

The shader setup thunks properly guard against null flowmap and include helpful comments explaining the texture coordinate calculations and how they counter operations in the original SetupGeometry.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
src/Features/UnifiedWater.h (3)

12-21: Polish user‑facing feature summary and avoid over‑absolute claims

The summary is a big improvement over the previous TODO, but it still leans on engine jargon (“LOD0”, “LOD mismatch”) and makes a very strong “Completely and fundamentally resolves” claim that might be hard to guarantee across modded setups.

Consider slightly softening the language, avoiding engine‑specific acronyms, and adding a fifth bullet that mentions runtime compatibility / limitations, per the earlier review note. For example:

-        return {
-            "Unified Water provides a comprehensive fix to water LOD mismatch by replacing distant water tiles with LOD0 (Close Water).",
-            { "Unifies distant and close water appearance, streamlining all lighting visuals.",
-                "Completely and fundamentally resolves water LOD mismatch issues.",
-                "Provides background systems for water geometry rendering, allowing more advanced water effects.",
-                "Improves vanilla performance by using optimized water meshes for distant water." }
-        };
+        return {
+            "Unified Water replaces distant water tiles with full-quality water to fix seams and improve visual consistency.",
+            { "Matches distant water to nearby water to remove visible transitions and seams.",
+                "Replaces engine-driven water LOD tiles with full-quality water surfaces where possible.",
+                "Provides background systems for water geometry and flowmaps used by advanced water effects.",
+                "Includes optional optimised meshes that can improve performance on vanilla-style setups.",
+                "Designed for all supported runtimes; heavily modded water overhauls may still need compatibility patches." }
+        };

Adjust wording as needed to best fit what you’ve seen in testing, but keeping it non‑technical and not over‑promising will help set correct expectations in the UI.


24-29: Optional: align config naming and UI wording for “optimised/optimized” meshes

Settings::UseOptimisedMeshes uses British spelling while the summary bullet currently says “optimized water meshes”. Not a bug, but you may want to standardize on one spelling (either “optimized” or “optimised”) in both the setting name (if still changeable) and the user‑facing summary text to avoid small polish inconsistencies.


1-116: PR metadata: suggest conventional commit title and issue references

To align with the project’s guidelines:

  • Suggested conventional commit title (shorter, conventional‑style):
    feat(water): add unified water system

  • If this PR fixes a specific water LOD / distant water issue, consider adding one of the following to the PR description:

    • Fixes #<issue> for a bug this definitively resolves
    • Implements #<issue> or Addresses #<issue> if it’s a feature tracked elsewhere
    • Related to #<issue> if it’s part of a broader effort

This will help keep the history and issue tracking clean.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9d7500d and b12e276.

📒 Files selected for processing (1)
  • src/Features/UnifiedWater.h (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{cpp,cxx,cc,c,h,hpp,hxx,hlsl,hlsli,fx,fxh,py}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Do not include TODO/FIXME placeholders; provide complete, working solutions

Files:

  • src/Features/UnifiedWater.h
src/**/*.{cpp,cxx,cc,h,hpp,hxx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/**/*.{cpp,cxx,cc,h,hpp,hxx}: Ensure SE/AE/VR runtime compatibility; use runtime detection patterns (e.g., REL::RelocateMember())
Include robust error handling and resource management with graceful degradation in the plugin code

Files:

  • src/Features/UnifiedWater.h
**/*

⚙️ CodeRabbit configuration file

**/*: When reviewing PRs, please provide suggestions for:

  1. Conventional Commit Titles (if not following https://www.conventionalcommits.org/ or
    if the existing title does not describe the code changes):
    Format: type(scope): description
    Length: 50 characters limit for title, 72 for body
    Style: lowercase description, no ending period
    Examples:

    • feat(vr): add cross-eye sampling
    • fix(water): resolve flowmap bug
    • docs: update shader documentation
  2. Issue References (if PR fixes bugs or implements features):
    Suggest adding appropriate GitHub keywords:

    • "Fixes #123" or "Closes #123" for bug fixes
    • "Implements #123" or "Addresses #123" for features
    • "Related to #123" for partial implementations

Otherwise, use your standard review approach focusing on code quality.

Files:

  • src/Features/UnifiedWater.h
🧠 Learnings (1)
📓 Common learnings
Learnt from: alandtse
Repo: doodlum/skyrim-community-shaders PR: 0
File: :0-0
Timestamp: 2025-06-24T07:17:36.604Z
Learning: When reviewing PRs, always clarify the scope if there are multiple related features or dependencies. WeatherPicker was a separate PR that was already merged, while this PR focuses specifically on WetnessEffects climate preset system enhancements.
Learnt from: alandtse
Repo: doodlum/skyrim-community-shaders PR: 0
File: :0-0
Timestamp: 2025-07-05T05:20:45.823Z
Learning: In the skyrim-community-shaders repository, file deletion error handling improvements that replace existence checks and try-catch blocks with std::filesystem::remove error-code-based approaches are considered bug fixes rather than refactoring, as they address inadequate error handling and misleading log messages.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build plugin and addons
  • GitHub Check: Validate shader compilation (VR, .github/configs/shader-validation-vr.yaml)
  • GitHub Check: Validate shader compilation (Flatrim, .github/configs/shader-validation.yaml)
🔇 Additional comments (2)
src/Features/UnifiedWater.h (2)

31-83: Hook declarations look consistent; ensure SE/AE/VR coverage in implementation

The thunk + REL::Relocation<decltype(thunk)> func; pattern here is consistent with the rest of the project, and the chosen hook points (water system init, terrain attach/detach, displacement mesh updates, etc.) match the unified water goals.

Since this feature is enabled for VR (see SupportsVR()), please double‑check in the .cpp that:

  • All these relocations are properly resolved for SE, AE, and VR (no missing IDs / wrong signatures on VR), and
  • Failure to resolve any of them degrades gracefully (e.g., disabling UnifiedWater), rather than hard‑crashing.

No changes needed in the header, just confirming that the implementation follows the usual runtime‑detection/error‑handling patterns.


97-97: Confirm SupportsVR() reflects current level of VR testing

Declaring SupportsVR() as true is great if all hooks and shaders are fully validated on VR, but if VR support is still experimental or partially untested, you might want to:

  • Temporarily return false until testing is complete, or
  • Mark the feature as “experimental in VR” in the UI summary text.

Please confirm this matches the actual state of VR support for UnifiedWater before merging.

@github-actions
Copy link

github-actions bot commented Dec 11, 2025

✅ A pre-release build is available for this PR:
Download


void WorldToCell(const RE::NiPoint3& worldPos, int32_t& x, int32_t& y);

constexpr uint32_t FCC(const char s[4]) noexcept
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does FCC do or mean?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FCC = Four character code, i assume sic is using it here instead of CELL or WRLD as the FCC has no runtime cost compared to using string comparison

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs to be in the code.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs to be in the code.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's four character code. The plugin files use four bytes to represent a name of each section, this just allows those four bytes to be represented as a human readable string in the codebase, but it compiles down to a simple integer comparison.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a doxygen comment so we know 5 months from now.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure :)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still waiting for this.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (2)
src/Features/UnifiedWater.cpp (2)

106-118: Unsafe pointer chain can crash; address the TODO comments.

Per coding guidelines, provide complete solutions instead of TODO placeholders. The chained calls can dereference null or access empty containers, which will crash the game.

-	// TODO error check this properly
-	const auto waterShape = nif->GetChildren().front()->AsNode()->GetChildren().front()->AsTriShape();
-	waterMesh = RE::NiPointer(waterShape);
+	if (nif->GetChildren().empty()) {
+		logger::error("[Unified Water] Water mesh has no children");
+		return;
+	}
+	const auto firstChild = nif->GetChildren().front();
+	if (!firstChild || !firstChild->AsNode() || firstChild->AsNode()->GetChildren().empty()) {
+		logger::error("[Unified Water] Water mesh structure is invalid");
+		return;
+	}
+	const auto waterShape = firstChild->AsNode()->GetChildren().front()->AsTriShape();
+	if (!waterShape) {
+		logger::error("[Unified Water] Water mesh is not a tri-shape");
+		return;
+	}
+	waterMesh = RE::NiPointer(waterShape);
 	logger::debug("[Unified Water] Water mesh loaded");
 
 	if (const auto error = RE::BSModelDB::Demand("meshes\\water\\optimisedwatermesh.nif", nif, args); error != RE::BSResource::ErrorCode::kNone) {
 		logger::error("[Unified Water] Failed to load optimised water mesh");
 		return;
 	}
-	// TODO error check this properly
-	const auto optimisedWaterShape = nif->GetChildren().front()->AsNode()->GetChildren().front()->AsTriShape();
+	if (nif->GetChildren().empty()) {
+		logger::error("[Unified Water] Optimised water mesh has no children");
+		return;
+	}
+	const auto optFirstChild = nif->GetChildren().front();
+	if (!optFirstChild || !optFirstChild->AsNode() || optFirstChild->AsNode()->GetChildren().empty()) {
+		logger::error("[Unified Water] Optimised water mesh structure is invalid");
+		return;
+	}
+	const auto optimisedWaterShape = optFirstChild->AsNode()->GetChildren().front()->AsTriShape();
+	if (!optimisedWaterShape) {
+		logger::error("[Unified Water] Optimised water mesh is not a tri-shape");
+		return;
+	}

137-139: Blocking spin-wait on the main thread may freeze the game during loading.

This busy-wait loop blocks the calling thread until the cache build completes. If DataLoaded is called on the main thread, the game will be unresponsive.

Consider either:

  1. Making this non-blocking and handling incomplete caches gracefully
  2. Moving the wait to a background thread with a callback
  3. Using a condition variable instead of polling
-	while (waterCache->IsBuildRunning()) {
-		std::this_thread::sleep_for(100ms);
-	}
+	// If cache build is still running, log and continue - cache will be available when ready
+	if (waterCache->IsBuildRunning()) {
+		logger::info("[Unified Water] Cache build in progress, will complete asynchronously");
+	}

Alternatively, if blocking is truly required, ensure this runs off the main thread.

🧹 Nitpick comments (6)
src/Features/UnifiedWater.cpp (6)

120-121: Potential memory leak with raw new.

flowmap and waterCache are allocated with raw new but no corresponding cleanup is visible in this file. If UnifiedWater is a singleton or long-lived object, this may be acceptable, but prefer RAII patterns.

Consider using std::unique_ptr for automatic cleanup:

-	flowmap = new Flowmap();
-	waterCache = new WaterCache();
+	flowmap = std::make_unique<Flowmap>();
+	waterCache = std::make_unique<WaterCache>();

This requires updating the member declarations in the header to use std::unique_ptr<Flowmap> and std::unique_ptr<WaterCache>.


176-180: Add error handling for file I/O operations.

The code doesn't verify that the full 8 bytes were read successfully. A short read would leave existingHash partially initialized.

 	uint64_t existingHash = 0;
 	if (fs::exists(path)) {
 		std::ifstream file(path, std::ios::binary);
-		if (file.is_open()) {
+		if (file.is_open() && file.read(reinterpret_cast<char*>(&existingHash), sizeof(existingHash))) {
-			file.read(reinterpret_cast<char*>(&existingHash), sizeof(existingHash));
+			if (file.gcount() != sizeof(existingHash)) {
+				logger::warn("[Unified Water] Incomplete hash file, treating as changed");
+				existingHash = 0;
+			}
-			file.close();
+		} else {
+			logger::warn("[Unified Water] Failed to read hash file");
 		}
 	}

184-187: Verify file write success.

Writing the hash file could fail due to permissions or disk space issues, but the failure is silently ignored.

 	if (hash != existingHash) {
 		std::ofstream file(path, std::ios::binary | std::ios::trunc);
-		if (file.is_open()) {
+		if (file.is_open() && file.write(reinterpret_cast<const char*>(&hash), sizeof(hash))) {
-			file.write(reinterpret_cast<const char*>(&hash), sizeof(hash));
+			// Successfully written
+		} else {
+			logger::warn("[Unified Water] Failed to write hash file");
 		}
 	}

205-244: Consider error handling for hook installation.

The hook installation calls don't check for failures. If a hook fails to install (e.g., due to version mismatches or memory protection), the feature may silently malfunction.

While the stl::detour_thunk and related functions may internally handle errors, consider wrapping critical hooks with try-catch or validation to provide better diagnostics:

try {
    stl::detour_thunk<TES_SetWorldSpace>(REL::RelocationID(13170, 13315));
    // ... other hooks
    logger::info("[Unified Water] Installed hooks");
} catch (const std::exception& e) {
    logger::error("[Unified Water] Failed to install hooks: {}", e.what());
    throw;
}

362-365: Consider logging at debug level.

The warning at line 362 may spam logs in normal gameplay if water instructions are legitimately missing for certain chunks. Consider lowering to debug level or adding a rate-limiter.

-			logger::warn("[Unified Water] No instructions found for {} chunk at {}, {}", worldSpace->GetFormEditorID(), node->x, node->y);
+			logger::debug("[Unified Water] No instructions found for {} chunk at {}, {}", worldSpace->GetFormEditorID(), node->x, node->y);

373-373: Clarify complex ternary condition.

The condition lodLevel > 4 || singleton.settings.UseOptimisedMeshes may benefit from explicit parentheses or extraction to a named variable for readability.

-			const auto targetShape = lodLevel > 4 || singleton.settings.UseOptimisedMeshes ? singleton.optimisedWaterMesh : singleton.waterMesh;
+			const bool useOptimisedMesh = (lodLevel > 4) || singleton.settings.UseOptimisedMeshes;
+			const auto targetShape = useOptimisedMesh ? singleton.optimisedWaterMesh : singleton.waterMesh;
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b12e276 and 7785bba.

📒 Files selected for processing (1)
  • src/Features/UnifiedWater.cpp (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{cpp,cxx,cc,c,h,hpp,hxx,hlsl,hlsli,fx,fxh,py}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Do not include TODO/FIXME placeholders; provide complete, working solutions

Files:

  • src/Features/UnifiedWater.cpp
src/**/*.{cpp,cxx,cc,h,hpp,hxx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/**/*.{cpp,cxx,cc,h,hpp,hxx}: Ensure SE/AE/VR runtime compatibility; use runtime detection patterns (e.g., REL::RelocateMember())
Include robust error handling and resource management with graceful degradation in the plugin code

Files:

  • src/Features/UnifiedWater.cpp
**/*

⚙️ CodeRabbit configuration file

**/*: When reviewing PRs, please provide suggestions for:

  1. Conventional Commit Titles (if not following https://www.conventionalcommits.org/ or
    if the existing title does not describe the code changes):
    Format: type(scope): description
    Length: 50 characters limit for title, 72 for body
    Style: lowercase description, no ending period
    Examples:

    • feat(vr): add cross-eye sampling
    • fix(water): resolve flowmap bug
    • docs: update shader documentation
  2. Issue References (if PR fixes bugs or implements features):
    Suggest adding appropriate GitHub keywords:

    • "Fixes #123" or "Closes #123" for bug fixes
    • "Implements #123" or "Addresses #123" for features
    • "Related to #123" for partial implementations

Otherwise, use your standard review approach focusing on code quality.

Files:

  • src/Features/UnifiedWater.cpp
🧠 Learnings (1)
📓 Common learnings
Learnt from: alandtse
Repo: doodlum/skyrim-community-shaders PR: 0
File: :0-0
Timestamp: 2025-06-24T07:17:36.604Z
Learning: When reviewing PRs, always clarify the scope if there are multiple related features or dependencies. WeatherPicker was a separate PR that was already merged, while this PR focuses specifically on WetnessEffects climate preset system enhancements.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Validate shader compilation (Flatrim, .github/configs/shader-validation.yaml)
  • GitHub Check: Validate shader compilation (VR, .github/configs/shader-validation-vr.yaml)
  • GitHub Check: Build plugin and addons
🔇 Additional comments (5)
src/Features/UnifiedWater.cpp (5)

7-24: LGTM!

The settings serialization and management code is clean and uses the nlohmann JSON library idiomatically.


37-47: LGTM!

The ImGui tree node is properly balanced with TreePop() at line 46, addressing the previous review concern.


426-429: LGTM: Safe child detachment loop.

The loop correctly detaches all children by decrementing from size() to zero. The check count > 0 before --count ensures no underflow.


436-462: LGTM: Proper RTTI validation before cast.

The code correctly validates the RTTI before performing a static_cast at both line 446 and earlier at line 396. This follows safe casting practices.


323-323: Replace the hardcoded magic number with a named constant or add clarifying documentation.

The code uses static_cast<RE::TESObjectCELL::CellState>(6) which represents an undocumented flag without a named constant in CommonLibSSE. Only CellState::kAttached = 7 is formally defined; value 6 is a magic number. Consider either:

  • Defining a local named constant if this represents a known Skyrim cell state (e.g., kLoaded or similar)
  • Adding a comment explaining what bit 6 represents
  • Refactoring to use only documented enum values if possible
⛔ Skipped due to learnings
Learnt from: ThePagi
Repo: doodlum/skyrim-community-shaders PR: 1369
File: src/Features/SnowCover.cpp:515-515
Timestamp: 2025-10-02T14:20:33.454Z
Learning: In the Community Shaders codebase (skyrim-community-shaders repository), hardcoded shader resource slot numbers are used directly in code rather than being defined as named constants. This is the established convention and should not be flagged as an issue.

davo0411 and others added 3 commits December 11, 2025 16:57
Automated formatting by clang-format, prettier, and other hooks.
See https://pre-commit.ci for details.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
package/Shaders/Water.hlsl (4)

218-238: Consider adding comments to explain cell coordinate transformations.

The UNIFIED_WATER texture coordinate calculations introduce a cell-shift system that's not immediately obvious. While the implementation appears correct, adding brief comments explaining the coordinate system would improve maintainability.

For example:

+// Calculate cell shift for LOD water tile alignment
 float2 cellShift = float2(floor(ObjectUV.z * 0.5), floor((ObjectUV.z - 1.0) * 0.5));
+// Scale and offset UVs to handle cross-cell boundaries
 float2 scaledUV = input.TexCoord0.xy * ObjectUV.z - cellShift;

1218-1224: Consider documenting FresnelRI.y usage for fog calculation.

The UNIFIED_WATER fog computation uses FresnelRI.y as the fog power exponent (line 1222). While this allows artistic control by reusing an existing parameter, it's not immediately obvious that the fresnel parameter affects fog falloff.

Consider adding a brief comment or using a more descriptive alias if this parameter is specifically tuned for unified water fog.


1244-1250: Consider extracting duplicated fog calculation.

The fog computation for UNIFIED_WATER is duplicated between the VC path (lines 1218-1224) and non-VC path (lines 1244-1250). Consider extracting this into a helper function to reduce maintenance burden.

float CalculateUnifiedWaterFog(float distance, float4 fogParam, float fogFarMax, float fogPower)
{
    return min(fogFarMax, pow(saturate(distance * fogParam.y - fogParam.x), fogPower));
}

Then call it as:

float fogDistanceFactor = CalculateUnifiedWaterFog(input.WPosition.w, FogParam, FogFarColor.w, FresnelRI.y);

1-1300: Consider updating PR title to follow conventional commits format.

Per the coding guidelines, the PR title should follow the conventional commits format:

  • Current: "feat: unified water"
  • Suggested: "feat(water): add unified water lod system"

The suggested format provides better clarity about the scope (water system) and includes a more descriptive summary. The 50-character limit is met with this suggestion.

Based on learnings, this ensures consistent PR titles across the repository.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cf37283 and dea5a0c.

📒 Files selected for processing (1)
  • package/Shaders/Water.hlsl (13 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{cpp,cxx,cc,c,h,hpp,hxx,hlsl,hlsli,fx,fxh,py}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Do not include TODO/FIXME placeholders; provide complete, working solutions

Files:

  • package/Shaders/Water.hlsl
**/*

⚙️ CodeRabbit configuration file

**/*: When reviewing PRs, please provide suggestions for:

  1. Conventional Commit Titles (if not following https://www.conventionalcommits.org/ or
    if the existing title does not describe the code changes):
    Format: type(scope): description
    Length: 50 characters limit for title, 72 for body
    Style: lowercase description, no ending period
    Examples:

    • feat(vr): add cross-eye sampling
    • fix(water): resolve flowmap bug
    • docs: update shader documentation
  2. Issue References (if PR fixes bugs or implements features):
    Suggest adding appropriate GitHub keywords:

    • "Fixes #123" or "Closes #123" for bug fixes
    • "Implements #123" or "Addresses #123" for features
    • "Related to #123" for partial implementations

Otherwise, use your standard review approach focusing on code quality.

Files:

  • package/Shaders/Water.hlsl
🧠 Learnings (2)
📓 Common learnings
Learnt from: alandtse
Repo: doodlum/skyrim-community-shaders PR: 0
File: :0-0
Timestamp: 2025-06-24T07:17:36.604Z
Learning: When reviewing PRs, always clarify the scope if there are multiple related features or dependencies. WeatherPicker was a separate PR that was already merged, while this PR focuses specifically on WetnessEffects climate preset system enhancements.
📚 Learning: 2025-08-03T18:37:19.690Z
Learnt from: jiayev
Repo: doodlum/skyrim-community-shaders PR: 0
File: :0-0
Timestamp: 2025-08-03T18:37:19.690Z
Learning: ISReflectionsRayTracing.hlsl and ISWorldMap.hlsl in the skyrim-community-shaders repository are image-space post-processing shaders that perform color sampling and blending operations that need proper linear color space handling for the linear lighting system. ISReflectionsRayTracing handles screen-space reflections and should use conditional Color::IrradianceToLinear/Gamma conversions similar to ISCompositeLensFlareVolumetricLighting.hlsl. ISWorldMap performs 7x7 color accumulation that should be done in linear space similar to the pattern used in ISSAOComposite.hlsl.

Applied to files:

  • package/Shaders/Water.hlsl
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build plugin and addons
  • GitHub Check: Validate shader compilation (VR, .github/configs/shader-validation-vr.yaml)
  • GitHub Check: Validate shader compilation (Flatrim, .github/configs/shader-validation.yaml)
🔇 Additional comments (9)
package/Shaders/Water.hlsl (9)

193-197: LGTM: Fog computation correctly moved to pixel shader.

The fog calculation is properly gated to compute in the vertex shader only when UNIFIED_WATER is not defined. This allows per-pixel fog computation for the unified water feature, providing higher visual quality at the cost of additional pixel shader work.


287-307: LGTM: Texture coordinate calculations align with structure changes.

The UNIFIED_WATER paths correctly use 2D dimensions (ObjectUV.xy) instead of the vanilla 1D approach (ObjectUV.xx), which aligns with the TexCoord4 type change from float to float2. This enables non-square flowmap textures.

Also applies to: 309-309


653-657: LGTM: Flowmap dimensions handling is consistent.

The conditional logic correctly extracts 2D dimensions for UNIFIED_WATER (supporting non-square textures) and falls back to square dimensions for vanilla paths.


661-665: LGTM: Flowmap normal sampling uses varied parameters for blending.

The multiple GetFlowmapNormal calls with different UV shifts, multipliers, and offsets create variation that's blended together. The specific values appear to be artistically tuned.


987-1005: LGTM: Skylighting integration for exterior water refractions.

The skylighting integration correctly applies to exterior water (not interior as the AI summary suggests). This enhances the visual quality of underwater refractions by sampling skylighting at interpolated positions between the water surface and refracted geometry.

The color space conversions are properly handled with GammaToLinear and LinearToGamma wrappers.

Note: The AI summary describing this as "interior-scene skylight integration" is incorrect.


1282-1282: LGTM: Added saturate for robust color output.

Adding saturate() to the final shader output ensures the color values are clamped to the valid [0, 1] range, preventing potential issues with out-of-range values in downstream processing.


83-85: No breaking changes to shader interface in VS_OUTPUT struct.

The code snippet shows conditional compilation logic, not structural changes:

  • TexCoord4 is already float2 on line 93 (not changed from float)
  • FogParam is conditionally removed when UNIFIED_WATER is defined, with fog computation moved to the pixel shader using the FogParam cbuffer instead (lines 1222–1223)

These are intentional variant behaviors controlled by compile-time flags. Water.hlsl's VS_OUTPUT struct is not referenced by other shaders; each shader manages its own vertex output structure independently.


1069-1072: Unified water correctly maintains full normal detail at all distances. The distanceBlendFactor = 1.0f override is intentional: unified water uses a single LOD0 tile, eliminating the need to fade normal quality with distance. This differs from vanilla water's multi-LOD approach where normals fade out at distance to match geometry simplification. The design is consistent with unified water's special handling throughout the shader (UV mapping, geometry simplification, and fog removal), so no change is needed here.


563-574: The manual mip level calculation and flow vector scaling are intentionally coupled for correctness.

The code uses SampleLevel with explicit mip calculation rather than SampleBias because the flow vector magnitude must be scaled by the same mip level to prevent animation artifacts at different texture scales. The ddx/ddy derivatives inform the mip selection, and exp2(-mipLevel) scales both the flow vector and UV adjustments proportionally, ensuring visual consistency across the texture hierarchy. This pattern is necessary in branching code where automatic derivatives cannot be reliably computed.

The mip level clamp to 5 (range [0, 5]) assumes a standard texture mip hierarchy of 6 levels and is consistent with similar calculations in the codebase (see ScreenSpaceGI/gi.cs.hlsl). This is reasonable for typical flowmap texture dimensions.

The function's existing documentation could clarify this architecture: consider adding a note explaining that mipScale must match the sampled mip level to maintain flow animation coherence.

Copy link
Collaborator

@alandtse alandtse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments need to be resolved and @sicsix needs to approve this since it's his code.

Was there a test build with this?

Also if we need to release, who's handling the mod page? We already have unreleased mods for @sicsix that we get regular questions about. If @sicsix doesn't want to release, let's figure out how we handle it in the future. We are actively being forked and unreleased features are likely to get released by random people if we don't do something.

@davo0411
Copy link
Collaborator Author

Yes, there is a test build like 2 builds ago in cs-testing.
Gotten sic's approval previously, I'm sure he will reply here as well.

Whilst writing the code from scratch would have been outside my abilities, I believe I understand it enough to provide the support in his stead and I will handle the mod page.

@davo0411
Copy link
Collaborator Author

I'm away for a month now and won't be able to do any testing but will have time to do blind coding if necessary.

@davo0411
Copy link
Collaborator Author

davo0411 commented Dec 17, 2025

Comments need to be resolved and @sicsix needs to approve this since it's his code.

Was there a test build with this?

Also if we need to release, who's handling the mod page? We already have unreleased mods for @sicsix that we get regular questions about. If @sicsix doesn't want to release, let's figure out how we handle it in the future. We are actively being forked and unreleased features are likely to get released by random people if we don't do something.

I will handle mod page. Yet to discuss specific numbers with @sicsix but likely 90/10 DP split or something with him being able to manage the page as well. I will do art/page writeup. I would like to get this onto nexus sometime January/Feb before I start work up again.

I will discuss privately with @sicsix in relation to existing features (interior sun) that aren't released.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (4)
src/Features/UnifiedWater/WaterCache.cpp (3)

227-248: Captured raw pointer editorID may become dangling during async execution.

The lambda at line 235 captures editorID by value, which is a const char* pointing into game memory (worldSpace->editorID.c_str()). If the worldspace or its editor ID string is modified or freed during async execution, this will cause undefined behavior.

Apply this diff to capture a safe copy of the editor ID:

-		const char* editorID = worldSpace && worldSpace->editorID.c_str() ? worldSpace->editorID.c_str() : "";
-		if (!worldSpace || !editorID || !*editorID) {
+		std::string editorIDStr = worldSpace && worldSpace->editorID.c_str() ? worldSpace->editorID.c_str() : "";
+		if (!worldSpace || editorIDStr.empty()) {
 			logger::warn("[Unified Water] [Cache] WorldSpace has no EditorID - skipping");
 			buildProgress.Done(true);
 			continue;
 		}
 
-		async.pool->push_task([this, worldSpace, editorID] {
+		async.pool->push_task([this, worldSpace, editorID = std::move(editorIDStr)] {

363-368: Potential null pointer dereference for worldSpace->worldWater.

At line 365, worldSpace->worldWater->formID is accessed without checking if worldWater is null. This will crash for custom worldspaces that don't have a default world water defined.

Apply this diff to add a null check:

 			if (waterHeight > landHeight && fabs(waterHeight) < 50000.0f) {
-				if (!formID)
+				if (!formID && worldSpace->worldWater)
 					formID = worldSpace->worldWater->formID;  // Use default world water if no water form set
 			} else
 				formID = 0;

627-677: Resource leak and null pointer dereference in TryGetCellData.

Multiple critical issues:

  1. Line 651: file->GetRuntimeFormID(outFormID) is called without checking if file is null. When the first loop (lines 642-649) exhausts all files without finding a match, file becomes null at line 648's final iteration, causing a crash.

  2. Lines 637, 648, 661: Each call to Duplicate() returns a new pointer, but when the loop continues without breaking, the previous pointer is overwritten without cleanup, causing resource leaks.

Restructure to track and clean up duplicated files:

 bool WaterCache::TryGetCellData(RE::TESWorldSpace* worldSpace, RE::TESFileArray* files, const int32_t x, const int32_t y, RE::FormID& outFormID, float& outWaterHeight, float& outLandHeight, bool resolveFormID)
 {
 	outFormID = 0;
 	outWaterHeight = FLT_MAX;
 	outLandHeight = FLT_MAX;
 
 	const auto size = static_cast<int32_t>(files->size());
 	const auto arrayData = files->data();
 
 	int32_t fileIndex = size - 1;
-	RE::TESFile* file = arrayData[fileIndex]->Duplicate();
+	RE::TESFile* file = nullptr;
 	bool foundWaterData = false;
 	bool foundLandData = false;
 
 	// Search through the files in reverse load order to find the cell and read the water height and waterForm FormID
 	do {
+		file = arrayData[fileIndex]->Duplicate();
 		if (file && file->SeekCell(worldSpace, x, y)) {
 			ReadWaterData(file, outWaterHeight, outFormID);
 			foundWaterData = true;
 			break;
 		}
-		file = --fileIndex >= 0 ? arrayData[fileIndex]->Duplicate() : nullptr;
+		--fileIndex;
 	} while (fileIndex >= 0);
 
-	if (resolveFormID && outFormID)
+	if (resolveFormID && outFormID && file)
 		outFormID = file->GetRuntimeFormID(outFormID);
 
 	// Continue searching from the previous file to find the original record for the cell, this always has the landscape data - extract land height
 	do {
+		file = fileIndex >= 0 ? arrayData[fileIndex]->Duplicate() : nullptr;
 		if (file && file->SeekCell(worldSpace, x, y) && file->SeekLandscapeForCurrentCell()) {
 			ReadMinLandHeightData(file, outLandHeight);
 			foundLandData = true;
 			break;
 		}
-		file = --fileIndex >= 0 ? arrayData[fileIndex]->Duplicate() : nullptr;
+		--fileIndex;
 	} while (fileIndex >= 0);
 
 	if (!foundWaterData || !foundLandData) {
 		outFormID = 0;
 		outWaterHeight = FLT_MIN;
 		outLandHeight = 0;
 		return false;
 	}
 
 	if (outWaterHeight == FLT_MAX)
 		outWaterHeight = worldSpace->defaultWaterHeight;
 	if (outLandHeight == FLT_MAX)
 		outLandHeight = worldSpace->defaultLandHeight;
 
 	return foundWaterData && foundLandData;
 }
package/Shaders/Water.hlsl (1)

1326-1335: Same fog exponent concern as VC path.

The non-VC path also uses FresnelRI.y as the fog power exponent (line 1333), which should be consistent with the VC path concern noted above.

🧹 Nitpick comments (1)
package/Shaders/Water.hlsl (1)

786-804: Code duplication for LOD water normal blending.

The LOD path now duplicates the non-LOD normal blending logic (lines 761-784) to achieve unified water appearance. While this creates ~20 lines of near-identical code, the preprocessor complexity with WATER_PARALLAX and FLOWMAP branches makes abstraction difficult.

Consider extracting the shared normal blending logic into a helper function or macro if future changes require synchronizing both paths.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dea5a0c and 9611ade.

📒 Files selected for processing (2)
  • package/Shaders/Water.hlsl (14 hunks)
  • src/Features/UnifiedWater/WaterCache.cpp (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{cpp,cxx,cc,c,h,hpp,hxx,hlsl,hlsli,fx,fxh,py}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Do not include TODO/FIXME placeholders; provide complete, working solutions

Files:

  • package/Shaders/Water.hlsl
  • src/Features/UnifiedWater/WaterCache.cpp
**/*

⚙️ CodeRabbit configuration file

**/*: When reviewing PRs, please provide suggestions for:

  1. Conventional Commit Titles (if not following https://www.conventionalcommits.org/ or
    if the existing title does not describe the code changes):
    Format: type(scope): description
    Length: 50 characters limit for title, 72 for body
    Style: lowercase description, no ending period
    Examples:

    • feat(vr): add cross-eye sampling
    • fix(water): resolve flowmap bug
    • docs: update shader documentation
  2. Issue References (if PR fixes bugs or implements features):
    Suggest adding appropriate GitHub keywords:

    • "Fixes #123" or "Closes #123" for bug fixes
    • "Implements #123" or "Addresses #123" for features
    • "Related to #123" for partial implementations

Otherwise, use your standard review approach focusing on code quality.

Files:

  • package/Shaders/Water.hlsl
  • src/Features/UnifiedWater/WaterCache.cpp
src/**/*.{cpp,cxx,cc,h,hpp,hxx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/**/*.{cpp,cxx,cc,h,hpp,hxx}: Ensure SE/AE/VR runtime compatibility; use runtime detection patterns (e.g., REL::RelocateMember())
Include robust error handling and resource management with graceful degradation in the plugin code

Files:

  • src/Features/UnifiedWater/WaterCache.cpp
🧠 Learnings (10)
📓 Common learnings
Learnt from: alandtse
Repo: doodlum/skyrim-community-shaders PR: 0
File: :0-0
Timestamp: 2025-06-24T07:17:36.604Z
Learning: When reviewing PRs, always clarify the scope if there are multiple related features or dependencies. WeatherPicker was a separate PR that was already merged, while this PR focuses specifically on WetnessEffects climate preset system enhancements.
📚 Learning: 2025-10-02T14:20:33.454Z
Learnt from: ThePagi
Repo: doodlum/skyrim-community-shaders PR: 1369
File: src/Features/SnowCover.cpp:515-515
Timestamp: 2025-10-02T14:20:33.454Z
Learning: In the Community Shaders codebase (skyrim-community-shaders repository), hardcoded shader resource slot numbers are used directly in code rather than being defined as named constants. This is the established convention and should not be flagged as an issue.

Applied to files:

  • package/Shaders/Water.hlsl
📚 Learning: 2025-08-05T17:40:44.828Z
Learnt from: ThePagi
Repo: doodlum/skyrim-community-shaders PR: 1369
File: package/Shaders/Lighting.hlsl:0-0
Timestamp: 2025-08-05T17:40:44.828Z
Learning: In the skyrim-community-shaders repository, ultra trees (object LOD trees) are detected using a compound shader define condition `defined(DO_ALPHA_TEST) && defined(LOD_BLENDING) && defined(RIM_LIGHTING) && defined(SOFT_LIGHTING)` because "they have no define" according to the comment. The `ExtraFlags::IsTree` flag is used for different tree handling (AO removal in skylighting) and may not apply to ultra trees specifically. Before replacing the compound condition with `IsTree`, verification is needed to ensure the flag covers ultra trees.

Applied to files:

  • package/Shaders/Water.hlsl
📚 Learning: 2025-08-05T18:22:40.578Z
Learnt from: ThePagi
Repo: doodlum/skyrim-community-shaders PR: 1369
File: src/Features/SnowCover.cpp:277-293
Timestamp: 2025-08-05T18:22:40.578Z
Learning: In the skyrim-community-shaders SnowCover feature, the wstrtostr and strtowstr utility functions defined in src/Features/SnowCover.cpp are unused and should be removed rather than fixed, as confirmed by the original author ThePagi.

Applied to files:

  • package/Shaders/Water.hlsl
📚 Learning: 2025-08-17T18:37:35.839Z
Learnt from: CR
Repo: doodlum/skyrim-community-shaders PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-17T18:37:35.839Z
Learning: Applies to features/*/Shaders/**/*.{hlsl,hlsli,fx,fxh} : Avoid GPU register/buffer conflicts in HLSL; verify register usage (e.g., with hlslkit buffer scanning)

Applied to files:

  • package/Shaders/Water.hlsl
📚 Learning: 2025-07-01T18:01:07.079Z
Learnt from: alandtse
Repo: doodlum/skyrim-community-shaders PR: 0
File: :0-0
Timestamp: 2025-07-01T18:01:07.079Z
Learning: In the skyrim-community-shaders project, simple scalar constants in HLSL shaders use #define (e.g., #define NTHREADS 128), while more complex constants use static const within namespaces (e.g., Math namespace in Math.hlsli). For epsilon standardization, #define is the appropriate choice since epsilon values are simple scalar constants.

Applied to files:

  • package/Shaders/Water.hlsl
📚 Learning: 2025-06-08T11:25:14.536Z
Learnt from: alandtse
Repo: doodlum/skyrim-community-shaders PR: 577
File: src/Features/WetnessEffects.h:36-36
Timestamp: 2025-06-08T11:25:14.536Z
Learning: In the skyrim-community-shaders project, boolean flags in C++ structs that interface with HLSL shaders use `uint` type instead of `bool` for compatibility reasons. This ensures consistent 4-byte size, proper 16-byte alignment in constant buffers, and cross-platform compatibility when passing data between C++ and HLSL shaders.

Applied to files:

  • package/Shaders/Water.hlsl
📚 Learning: 2025-07-05T05:20:45.823Z
Learnt from: alandtse
Repo: doodlum/skyrim-community-shaders PR: 0
File: :0-0
Timestamp: 2025-07-05T05:20:45.823Z
Learning: In the skyrim-community-shaders repository, file deletion error handling improvements that replace existence checks and try-catch blocks with std::filesystem::remove error-code-based approaches are considered bug fixes rather than refactoring, as they address inadequate error handling and misleading log messages.

Applied to files:

  • package/Shaders/Water.hlsl
  • src/Features/UnifiedWater/WaterCache.cpp
📚 Learning: 2025-06-08T11:25:14.536Z
Learnt from: alandtse
Repo: doodlum/skyrim-community-shaders PR: 577
File: src/Features/WetnessEffects.h:36-36
Timestamp: 2025-06-08T11:25:14.536Z
Learning: In the skyrim-community-shaders project, boolean flags in C++ structs that interface with HLSL shaders use `uint` type instead of `bool` for compatibility reasons. This ensures consistent size, alignment, and cross-platform compatibility when passing data to shader constant buffers.

Applied to files:

  • package/Shaders/Water.hlsl
📚 Learning: 2025-08-03T18:37:19.690Z
Learnt from: jiayev
Repo: doodlum/skyrim-community-shaders PR: 0
File: :0-0
Timestamp: 2025-08-03T18:37:19.690Z
Learning: ISReflectionsRayTracing.hlsl and ISWorldMap.hlsl in the skyrim-community-shaders repository are image-space post-processing shaders that perform color sampling and blending operations that need proper linear color space handling for the linear lighting system. ISReflectionsRayTracing handles screen-space reflections and should use conditional Color::IrradianceToLinear/Gamma conversions similar to ISCompositeLensFlareVolumetricLighting.hlsl. ISWorldMap performs 7x7 color accumulation that should be done in linear space similar to the pattern used in ISSAOComposite.hlsl.

Applied to files:

  • package/Shaders/Water.hlsl
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Validate shader compilation (Flatrim, .github/configs/shader-validation.yaml)
  • GitHub Check: Validate shader compilation (VR, .github/configs/shader-validation-vr.yaml)
  • GitHub Check: Build plugin and addons
🔇 Additional comments (27)
src/Features/UnifiedWater/WaterCache.cpp (16)

5-40: LGTM!

The parent world resolution logic and atomic cache map access are correctly implemented with proper memory ordering.


42-50: LGTM!

The function safely delegates to the runtime cache after ensuring the world space is set.


52-75: LGTM!

The LOD index calculation and bounds checking are correctly implemented with appropriate validation at each step.


77-102: LGTM!

The developer utility correctly generates a precache for Tamriel with appropriate bounds and error handling.


104-112: LGTM!

The fallback logic from loading to regenerating caches is straightforward and correct.


114-141: LGTM!

The cache regeneration logic properly uses error-code-based filesystem operations for robust error handling.


143-196: LGTM!

The cache loading logic correctly uses atomic operations for thread-safe cache map updates and properly restores the current cache context.


272-290: LGTM!

The precache building logic correctly populates height data for all cells within the world space bounds.


405-544: LGTM!

The instruction generation algorithm correctly groups contiguous cells with matching properties into larger squares, with appropriate bounds checking and processed-cell tracking.


546-598: LGTM!

The runtime cache construction properly resolves form pointers, validates them, and ensures water forms are initialized before use.


600-619: LGTM!

The world space validation logic correctly resolves parent worlds, filters by water presence, and deduplicates entries.


621-625: LGTM!

The LOD coordinate calculation correctly handles both positive and negative coordinates with proper floor division.


679-694: LGTM!

The water data reading correctly handles endianness conversion using std::bit_cast and byte swapping.


696-729: LGTM!

The VHGT parsing correctly handles endianness and computes the minimum land height from the offset and delta array.


731-751: LGTM!

The templated write function correctly serializes the header and vector data with appropriate error handling.


753-782: LGTM!

The templated read function correctly deserializes the cache file with proper header validation and error handling.

package/Shaders/Water.hlsl (11)

83-94: LGTM: VS_OUTPUT struct changes are consistent with UNIFIED_WATER feature.

The conditional exclusion of FogParam when UNIFIED_WATER is defined aligns with moving fog computation to the pixel shader. The TexCoord4 change from float to float2 correctly accommodates storing both dimensions from ObjectUV.xy.


192-197: LGTM: Fog calculation correctly gated for UNIFIED_WATER.

When UNIFIED_WATER is defined, fog computation is deferred to the pixel shader, so omitting the vertex shader fog calculation is correct and consistent with the VS_OUTPUT struct changes.


218-238: LGTM: UV coordinate handling for UNIFIED_WATER.

The cellShift and scaledUV calculations provide proper handling for multi-cell water tiles in the unified water system.


265-287: LGTM: Texture coordinate handling for UNIFIED_WATER paths.

The wading and flowmap texture coordinate calculations correctly use the new scaled UV system for UNIFIED_WATER, while preserving the original paths. The TexCoord4 assignment of ObjectUV.xy properly stores flowmap dimensions for pixel shader consumption.


608-618: LGTM: Improved mip level calculation for flowmap normals.

The new mip-clamped sampling approach with flow vector scaling is a well-designed improvement that preserves detail at distance while preventing over-blurring. Clamping the mip level to [0, 5] and compensating the flow vector with exp2(-mipLevel) maintains visual consistency across viewing distances.


1072-1090: LGTM: Skylighting integration for exterior water refractions.

The interior guard correctly prevents skylighting from affecting indoor water. The skylighting diffuse contribution to refraction color improves outdoor water lighting fidelity.


1154-1157: LGTM: Clean introduction of distanceBlendFactor.

The distanceBlendFactor variable provides a clean control point for unified water behavior. Setting it to 1.0 when UNIFIED_WATER is defined ensures consistent water appearance at all distances.


1367-1367: LGTM: Defensive saturate on final color output.

Adding saturate() to the final color output is good practice to ensure values are clamped to [0, 1] before being written to the render target, preventing potential overflow artifacts from cumulative lighting calculations.


949-959: Minor preprocessor formatting adjustments.

These are whitespace alignment changes with no functional impact.


1201-1201: Consistent use of distanceBlendFactor.

Using distanceBlendFactor for the normal calculation ensures unified water appearance at all distances when UNIFIED_WATER is defined.


265-285: Previous undefined variable issue appears resolved.

The past review flagged undefined variables (normalizedWaveDirVS, waveDirForwardVS, etc.) in this region. The current code no longer references these variables, indicating the issue has been addressed.

@davo0411 davo0411 requested a review from alandtse January 11, 2026 04:59
davo0411 and others added 2 commits January 12, 2026 21:26
Automated formatting by clang-format, prettier, and other hooks.
See https://pre-commit.ci for details.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/Globals.h (1)

208-215: Remove the duplicate globals::game::tes declaration.

extern RE::TES* tes; is declared twice in the same namespace block (Line 208 and Line 214). It won’t typically break compilation, but it’s easy to accidentally make them diverge later and it adds noise.

Proposed fix
 namespace game
 {
   extern RE::BSGraphics::RendererShadowState* shadowState;
   extern RE::BSGraphics::State* graphicsState;
   extern RE::BSGraphics::Renderer* renderer;
   extern RE::BSShaderManager::State* smState;
   extern RE::TES* tes;
   extern bool isVR;
   extern RE::MemoryManager* memoryManager;
   extern RE::INISettingCollection* iniSettingCollection;
   extern RE::INIPrefSettingCollection* iniPrefSettingCollection;
   extern RE::GameSettingCollection* gameSettingCollection;
-  extern RE::TES* tes;
   extern float* cameraNear;
   extern float* cameraFar;
   extern float* deltaTime;
   extern RE::BSUtilityShader* utilityShader;
   extern RE::Sky* sky;
   extern RE::UI* ui;
 }
🧹 Nitpick comments (1)
package/Shaders/Water.hlsl (1)

609-618: Mip selection via ddx/ddy(uv) is fragile unless uv is in normalized texture space. Consider using a texture-driven LOD helper.

If uv is not normalized to the sampled texture (or is scaled by world/cell factors), log2(max(dot(dx,dx), dot(dy,dy))) can drift and cause over/under-blurring across different flowmap resolutions.

If shader model/support allows, a more robust option is to let the hardware compute LOD based on the final sampling UV (or compute in texel space using actual dimensions).

(Also: the new scaledFlowVector approach is non-obvious—worth verifying it doesn’t unintentionally slow flow animation at distance beyond what you want.)

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 288d101 and 57b6de2.

📒 Files selected for processing (5)
  • package/Shaders/Water.hlsl
  • src/Feature.cpp
  • src/Globals.cpp
  • src/Globals.h
  • src/ShaderCache.h
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/ShaderCache.h
  • src/Globals.cpp
  • src/Feature.cpp
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{cpp,cxx,cc,c,h,hpp,hxx,hlsl,hlsli,fx,fxh,py}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Do not include TODO/FIXME placeholders; provide complete, working solutions

Files:

  • src/Globals.h
  • package/Shaders/Water.hlsl
src/**/*.{cpp,cxx,cc,h,hpp,hxx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/**/*.{cpp,cxx,cc,h,hpp,hxx}: Ensure SE/AE/VR runtime compatibility; use runtime detection patterns (e.g., REL::RelocateMember())
Include robust error handling and resource management with graceful degradation in the plugin code

Files:

  • src/Globals.h
**/*

⚙️ CodeRabbit configuration file

**/*: When reviewing PRs, please provide suggestions for:

  1. Conventional Commit Titles (if not following https://www.conventionalcommits.org/ or
    if the existing title does not describe the code changes):
    Format: type(scope): description
    Length: 50 characters limit for title, 72 for body
    Style: lowercase description, no ending period
    Examples:

    • feat(vr): add cross-eye sampling
    • fix(water): resolve flowmap bug
    • docs: update shader documentation
  2. Issue References (if PR fixes bugs or implements features):
    Suggest adding appropriate GitHub keywords:

    • "Fixes #123" or "Closes #123" for bug fixes
    • "Implements #123" or "Addresses #123" for features
    • "Related to #123" for partial implementations

Otherwise, use your standard review approach focusing on code quality.

Files:

  • src/Globals.h
  • package/Shaders/Water.hlsl
🧠 Learnings (13)
📓 Common learnings
Learnt from: alandtse
Repo: doodlum/skyrim-community-shaders PR: 0
File: :0-0
Timestamp: 2025-06-24T07:17:36.604Z
Learning: When reviewing PRs, always clarify the scope if there are multiple related features or dependencies. WeatherPicker was a separate PR that was already merged, while this PR focuses specifically on WetnessEffects climate preset system enhancements.
📚 Learning: 2025-08-17T18:37:35.839Z
Learnt from: CR
Repo: doodlum/skyrim-community-shaders PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-17T18:37:35.839Z
Learning: Applies to features/**/*.{cpp,cxx,cc,h,hpp,hxx} : Register new features in the globals::features namespace

Applied to files:

  • src/Globals.h
📚 Learning: 2025-08-17T18:37:35.839Z
Learnt from: CR
Repo: doodlum/skyrim-community-shaders PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-17T18:37:35.839Z
Learning: Applies to src/**/*.{cpp,cxx,cc,h,hpp,hxx} : Ensure SE/AE/VR runtime compatibility; use runtime detection patterns (e.g., REL::RelocateMember())

Applied to files:

  • src/Globals.h
📚 Learning: 2025-08-03T18:37:19.690Z
Learnt from: jiayev
Repo: doodlum/skyrim-community-shaders PR: 0
File: :0-0
Timestamp: 2025-08-03T18:37:19.690Z
Learning: ISReflectionsRayTracing.hlsl and ISWorldMap.hlsl in the skyrim-community-shaders repository are image-space post-processing shaders that perform color sampling and blending operations that need proper linear color space handling for the linear lighting system. ISReflectionsRayTracing handles screen-space reflections and should use conditional Color::IrradianceToLinear/Gamma conversions similar to ISCompositeLensFlareVolumetricLighting.hlsl. ISWorldMap performs 7x7 color accumulation that should be done in linear space similar to the pattern used in ISSAOComposite.hlsl.

Applied to files:

  • package/Shaders/Water.hlsl
📚 Learning: 2025-10-02T14:20:33.454Z
Learnt from: ThePagi
Repo: doodlum/skyrim-community-shaders PR: 1369
File: src/Features/SnowCover.cpp:515-515
Timestamp: 2025-10-02T14:20:33.454Z
Learning: In the Community Shaders codebase (skyrim-community-shaders repository), hardcoded shader resource slot numbers are used directly in code rather than being defined as named constants. This is the established convention and should not be flagged as an issue.

Applied to files:

  • package/Shaders/Water.hlsl
📚 Learning: 2025-08-05T17:40:44.828Z
Learnt from: ThePagi
Repo: doodlum/skyrim-community-shaders PR: 1369
File: package/Shaders/Lighting.hlsl:0-0
Timestamp: 2025-08-05T17:40:44.828Z
Learning: In the skyrim-community-shaders repository, ultra trees (object LOD trees) are detected using a compound shader define condition `defined(DO_ALPHA_TEST) && defined(LOD_BLENDING) && defined(RIM_LIGHTING) && defined(SOFT_LIGHTING)` because "they have no define" according to the comment. The `ExtraFlags::IsTree` flag is used for different tree handling (AO removal in skylighting) and may not apply to ultra trees specifically. Before replacing the compound condition with `IsTree`, verification is needed to ensure the flag covers ultra trees.

Applied to files:

  • package/Shaders/Water.hlsl
📚 Learning: 2025-08-05T18:22:40.578Z
Learnt from: ThePagi
Repo: doodlum/skyrim-community-shaders PR: 1369
File: src/Features/SnowCover.cpp:277-293
Timestamp: 2025-08-05T18:22:40.578Z
Learning: In the skyrim-community-shaders SnowCover feature, the wstrtostr and strtowstr utility functions defined in src/Features/SnowCover.cpp are unused and should be removed rather than fixed, as confirmed by the original author ThePagi.

Applied to files:

  • package/Shaders/Water.hlsl
📚 Learning: 2025-08-17T18:37:35.839Z
Learnt from: CR
Repo: doodlum/skyrim-community-shaders PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-17T18:37:35.839Z
Learning: Applies to features/*/Shaders/**/*.{hlsl,hlsli,fx,fxh} : Avoid GPU register/buffer conflicts in HLSL; verify register usage (e.g., with hlslkit buffer scanning)

Applied to files:

  • package/Shaders/Water.hlsl
📚 Learning: 2025-07-01T18:01:07.079Z
Learnt from: alandtse
Repo: doodlum/skyrim-community-shaders PR: 0
File: :0-0
Timestamp: 2025-07-01T18:01:07.079Z
Learning: In the skyrim-community-shaders project, simple scalar constants in HLSL shaders use #define (e.g., #define NTHREADS 128), while more complex constants use static const within namespaces (e.g., Math namespace in Math.hlsli). For epsilon standardization, #define is the appropriate choice since epsilon values are simple scalar constants.

Applied to files:

  • package/Shaders/Water.hlsl
📚 Learning: 2025-06-08T11:25:14.536Z
Learnt from: alandtse
Repo: doodlum/skyrim-community-shaders PR: 577
File: src/Features/WetnessEffects.h:36-36
Timestamp: 2025-06-08T11:25:14.536Z
Learning: In the skyrim-community-shaders project, boolean flags in C++ structs that interface with HLSL shaders use `uint` type instead of `bool` for compatibility reasons. This ensures consistent 4-byte size, proper 16-byte alignment in constant buffers, and cross-platform compatibility when passing data between C++ and HLSL shaders.

Applied to files:

  • package/Shaders/Water.hlsl
📚 Learning: 2025-07-05T05:20:45.823Z
Learnt from: alandtse
Repo: doodlum/skyrim-community-shaders PR: 0
File: :0-0
Timestamp: 2025-07-05T05:20:45.823Z
Learning: In the skyrim-community-shaders repository, file deletion error handling improvements that replace existence checks and try-catch blocks with std::filesystem::remove error-code-based approaches are considered bug fixes rather than refactoring, as they address inadequate error handling and misleading log messages.

Applied to files:

  • package/Shaders/Water.hlsl
📚 Learning: 2025-06-08T11:25:14.536Z
Learnt from: alandtse
Repo: doodlum/skyrim-community-shaders PR: 577
File: src/Features/WetnessEffects.h:36-36
Timestamp: 2025-06-08T11:25:14.536Z
Learning: In the skyrim-community-shaders project, boolean flags in C++ structs that interface with HLSL shaders use `uint` type instead of `bool` for compatibility reasons. This ensures consistent size, alignment, and cross-platform compatibility when passing data to shader constant buffers.

Applied to files:

  • package/Shaders/Water.hlsl
📚 Learning: 2025-08-05T18:13:03.123Z
Learnt from: ThePagi
Repo: doodlum/skyrim-community-shaders PR: 1369
File: src/Features/SnowCover.cpp:260-260
Timestamp: 2025-08-05T18:13:03.123Z
Learning: In the skyrim-community-shaders SnowCover feature, the time calculation uses division by 61.0 instead of 60.0 for seconds conversion in the perFrame.Month calculation. The original author ThePagi indicated this was intentional and makes no discernible difference to the snow cover functionality, suggesting it may be related to game-specific timing mechanics or balance considerations.

Applied to files:

  • package/Shaders/Water.hlsl
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build plugin and addons
  • GitHub Check: Run Shader Unit Tests
  • GitHub Check: Validate shader compilation (Flatrim, .github/configs/shader-validation.yaml)
🔇 Additional comments (9)
src/Globals.h (2)

24-24: UnifiedWater global registration looks consistent with the feature pattern.

Adding struct UnifiedWater; and exposing globals::features::unifiedWater matches the established “register new features in globals::features” convention. Based on learnings, this is the right place to wire it.

Also applies to: 78-78


241-246: The RTTI relocations BSWaterShaderPropertyRTTI and NiSourceTextureRTTI are already properly initialized in src/Globals.cpp::ReInit() (lines 186-187) using the project's runtime relocation pattern (REL::Relocation<const RE::NiRTTI*>). All usages in the codebase include appropriate null checks and error handling before casting (e.g., src/Features/UnifiedWater/Flowmap.cpp:127 includes logging and graceful failure). No action required.

package/Shaders/Water.hlsl (7)

193-197: Confirm fog-distance math is in the intended space (clip vs view) and matches prior behavior.

fogDistanceFactor uses length(worldViewPos.xyz) where worldViewPos is derived from WorldViewProj. If that’s truly clip-space here, fog will be projection-dependent. If it’s actually view-space in this shader’s convention, it’s fine—but worth double-checking because this factor drives both fog color lerp and alpha.


218-238: UNIFIED_WATER UV remap: please sanity-check edge cases for ObjectUV.z and ObjectUV.x toggling.

The scaledUV / cellShift path changes how scrolling is computed when ObjectUV.x is set. This is the kind of change that can look correct for most cells but misalign on cell boundaries (especially with negative coords / wrap). I’d specifically validate:

  • transitions across adjacent tiles (no seams),
  • behavior when ObjectUV.z is 0/1 (or other sentinel values),
  • VR eye consistency (since these values are per-geometry packed).

265-287: TexCoord4 as ObjectUV.xy (dimensions) is good, but guard against zero/invalid dims.

UNIFIED_WATER now divides by ObjectUV.xy / dims. If any path can produce 0 (or denorm) dimensions, you’ll get INF/NaN UVs that can cascade into derivatives and mip selection.

If the data contract guarantees non-zero, documenting that expectation near the ObjectUV packing site (C++ mesh/geom setup) would help prevent future regressions.


1053-1072: Skylighting refraction diffuse multiply: verify color space expectations of Color::Water(...).

You’re doing GammaToLinear(refractionDiffuseColor) * refractionDiffuseColorSkylight then LinearToGamma(...), which is correct if refractionDiffuseColor is gamma-encoded at that point. If Color::Water() is already linear (or conditionally linear under linear-lighting permutations), this could become a double conversion.


1136-1139: distanceBlendFactor = 1 for UNIFIED_WATER: confirm this is intentional for all effects (normals/specular/fog blending).

This forces the “near” water path everywhere Unified Water is enabled (since it’s passed into GetWaterNormal and used in specularFraction lerps). If the intent is “always LOD0 behavior,” this matches—but it’s a broad lever, so it’s worth confirming it doesn’t break expected distance fadeouts (especially with wetness/debug effects).

Also applies to: 1183-1183


1283-1329: Unified-water fog path is now PS-computed: please check continuity vs non-unified fog and parameter consistency.

You’re now computing fogDistanceFactor in PS under UNIFIED_WATER using input.WPosition.w, FogParam, and FresnelRI.y, whereas the non-unified path uses VS-computed fog (and NormalsScale.w as the exponent). If this is meant to match the old look, verify:

  • same fog curve as before (or consciously updated),
  • no discontinuity toggling Unified Water on/off,
  • no interior leakage (you already gate some interior paths, but this fog is applied broadly).

81-87: Verify all input.FogParam usages are gated under !defined(UNIFIED_WATER) to avoid hard-fail compilation.

This approach removes FogParam from the VS→PS interface when UNIFIED_WATER is defined to support a different fog model, but will fail compilation if any input.FogParam usage is outside !defined(UNIFIED_WATER) guards. Confirm all accesses are properly protected.

@alandtse
Copy link
Collaborator

@sicsix please formally approve this pr

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In review

Development

Successfully merging this pull request may close these issues.

4 participants