Skip to content

feat(volumetric-shadows)!: replace VSM with PCF froxel grid#2400

Closed
doodlum wants to merge 1 commit into
devfrom
claude/volumetric-shadows-froxel-grid-twOof
Closed

feat(volumetric-shadows)!: replace VSM with PCF froxel grid#2400
doodlum wants to merge 1 commit into
devfrom
claude/volumetric-shadows-froxel-grid-twOof

Conversation

@doodlum
Copy link
Copy Markdown
Collaborator

@doodlum doodlum commented May 22, 2026

Summary

Rebuild the Volumetric Shadows feature around a view-space froxel grid of PCF-filtered directional shadow visibility, replacing the 2D downsampled VSM moments texture it previously produced.

Each frame, BuildShadowFroxelCS reconstructs camera-relative world positions for every voxel via screen UV + exponential view-Z slicing, picks and blends the cascade(s) for that view depth, then samples Skyrim's directional cascade array with a 5-tap cross PCF kernel using the hardware comparison sampler. The result is a Texture3D<float> of visibility values bound at t18 for consumers.

Consumers drop the cascade selection and shadow projection math entirely — Get3DFilteredShadow / GetLightingShadow in Common/ShadowSampling.hlsli now call new VolumetricShadows::GetShadow3D / GetShadow2D helpers that just do a trilinear 3D lookup. PCF removes the light-bleeding artefacts inherent to VSM and gives a smoother falloff than Chebyshev's inequality on quantised moments.

Inspired by the froxel grid in jiayev/skyrim-community-shaders volumetric-fog,` adapted to pre-filter only the directional shadow channel.

What changed

  • src/Features/VolumetricShadows.{h,cpp} — Texture3D + comparison sampler + single build CS. CopyShadowLightData renamed to BuildShadowFroxel (the old name was misleading after the refactor).
  • features/Volumetric Shadows/Shaders/VolumetricShadows/BuildShadowFroxelCS.hlsl (new) — the per-frame build pass.
  • features/Volumetric Shadows/Shaders/VolumetricShadows/VolumetricShadows.hlsli — rewritten to project a world position into the grid and SampleLevel it.
  • features/Volumetric Shadows/Shaders/VolumetricShadows/{DownsampleShadowCS,BlurShadowCS}.hlsl — deleted (no longer needed; PCF replaces the gaussian-blurred VSM pipeline).
  • package/Shaders/Common/ShadowSampling.hlsli — updated to call the new helper names.
  • features/Volumetric Shadows/Shaders/Features/VolumetricShadows.ini — version bumped 2-0-1 → 3-0-0 since the shared resource type changed.

Grid configuration

  • Resolution: 160 × 96 × 64 mono, 320 × 96 × 64 in VR (side-by-side per eye).
  • Format: R8_UNORM (~1 MB mono, ~2 MB VR). Trilinear filter smooths the 256-level quantisation.
  • Depth distribution: exponential view-Z in [16, EndSplitDistances.y] — concentrates resolution where shadows matter most.
  • View-Z ↔ NDC-Z conversion uses Skyrim's SharedData::CameraData formula; unprojection composes CameraViewInverse · CameraProjUnjitteredInverse to avoid jitter flicker.

BREAKING CHANGE

VolumetricShadows.hlsli now exposes Texture3D<float> SharedShadowMap : register(t18) (was Texture2D<float2>). The helper API is renamed GetVSMShadow3D/2DGetShadow3D/2D. Any out-of-tree shader that included the old .hlsli directly will need to update its calls.

Test plan

  • Build in ALL, SE, VR presets and verify BuildShadowFroxelCS.hlsl validates with hlslkit-compile.
  • In-game exterior: check that shadows on transparents / particles / effects look at least as good as the prior VSM (no light bleed, no acne).
  • Move between cascades — verify the smoothstep blend is seamless.
  • Interior cells: HasDirectionalShadows() returns false → froxel SRV is null and consumers fall back to lit (no crash, no black shadows).
  • VR: stereo eyes sample the correct half of the grid (no cross-eye contamination).
  • Rotate / move camera quickly — confirm no temporal flicker (we use the unjittered ViewProj inverse for the build).
  • RenderDoc: confirm VolumetricShadows::ShadowFroxel, VolumetricShadows::ShadowFroxel SRV/UAV, and sampler names are present.

Generated by Claude Code

Summary by CodeRabbit

  • Refactor
    • Volumetric shadows system architecture has been redesigned
    • Version updated to 3.0.0

Review Change Stack

Rebuild the Volumetric Shadows feature around a view-space froxel grid
of PCF-filtered directional shadow visibility instead of a 2D downsampled
VSM moments texture.

Each frame, BuildShadowFroxelCS reconstructs camera-relative world
positions for every voxel via screen UV + exponential view-Z slicing,
selects and blends the cascade(s) for that view depth, then samples the
directional shadow array with a 5-tap cross PCF kernel using the
hardware comparison sampler.

Consumers (effects, decals, transparents) drop the cascade selection
and shadow projection math entirely and just do a trilinear lookup into
the 3D texture at slot t18 — replacing GetVSMShadow3D/2D with the new
GetShadow3D/2D helpers in VolumetricShadows.hlsli. PCF removes the
light-bleeding artefacts that VSM is prone to and gives a smoother
falloff than Chebyshev's inequality on quantised moments.

Inspired by jiayev's volumetric-fog branch, adapted to pre-filter only
the directional shadow channel.

BREAKING CHANGE: VolumetricShadows.hlsli now exposes
Texture3D<float> SharedShadowMap at register t18 (was Texture2D<float2>),
and the helper API is renamed from GetVSMShadow3D/2D to GetShadow3D/2D.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 22, 2026

📝 Walkthrough

Walkthrough

This PR refactors volumetric shadowing from a Variance Shadow Maps (VSM) cascade-based approach to a pre-filtered 3D froxel-grid approach. The new pipeline builds a directional shadow froxel grid via a single compute shader, removes the prior downsample/blur pipeline, and updates all sampling APIs to use trilinear froxel lookups instead of variance moment filtering.

Changes

Volumetric Shadows Froxel Grid Migration

Layer / File(s) Summary
Header API contract and froxel resource layout
src/Features/VolumetricShadows.h, features/Volumetric Shadows/Shaders/Features/VolumetricShadows.ini
VolumetricShadows header now declares froxel grid dimension constants, 3D froxel texture (SRV/UAV), a BuildShadowFroxel public method, and a VolumetricShadowsCB constant buffer; feature summary updated to describe pre-filtering directional cascades into a view-space froxel grid. Version bumped from 2-0-1 to 3-0-0.
Froxel build compute shader implementation
features/Volumetric Shadows/Shaders/VolumetricShadows/BuildShadowFroxelCS.hlsl
New compute shader binds directional shadow cascades and froxel output UAV; helpers convert view depth to froxel slice, sample PCF across cascades with bounds/blending, and reconstruct voxel world positions; main entry bounds-checks, reconstructs each voxel's position, samples directional shadow visibility per cascade, and writes saturated results to the 3D froxel texture.
Froxel sampling API and helper functions
features/Volumetric Shadows/Shaders/VolumetricShadows/VolumetricShadows.hlsli
Replaces VSM variance/bleeding logic with froxel grid sampling: ViewZToSlice maps view depth to exponential slice, SampleShadowFroxel performs trilinear lookup with frustum/range checks, GetShadow3D ray-marches through froxels to compute volumetric shadow and surface shadow, GetShadow2D provides simple point-sample froxel lookup.
Shadow sampling integration points
package/Shaders/Common/ShadowSampling.hlsli
Get3DFilteredShadow and GetLightingShadow call sites switch from VSM functions (GetVSMShadow3D/GetVSMShadow2D) to new froxel functions (GetShadow3D/GetShadow2D), maintaining out-parameter signatures for surface shadow results.
C++ runtime setup and froxel dispatch
src/Features/VolumetricShadows.cpp, src/Features/VolumetricShadows.h, src/State.cpp
SetupResources adds comparison sampler and config constant buffer, shader compilation simplified to only BuildShadowFroxelCS (removing downsample/blur), CreateFroxelResources lazily allocates R8_UNORM 3D froxel texture with SRV/UAV, BuildShadowFroxel binds directional cascade data/constants/samplers/UAV, dispatches compute, publishes froxel SRV, DrawSettings UI updated to describe froxel grid; State.cpp call updated from CopyShadowLightData to BuildShadowFroxel.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • alandtse
  • davo0411

Poem

🐰 From moments blurred to froxels bright,
A 3D grid now filters light,
No downsample, no variance blur—
Just rays through voxels, clean and pure,
Cascades baked into the deep!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the main architectural change: replacing VSM (Variance Shadow Maps) with PCF (Percentage Closer Filtering) in a froxel grid structure.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/volumetric-shadows-froxel-grid-twOof

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 OpenGrep (1.21.0)

OpenGrep fatal error (exit code 2): [00.20][ERROR]: Error: exception Unix_error: No such file or directory stat src/Features/VolumetricShadows.cpp
Raised by primitive operation at UTmp.replace_named_pipe_by_regular_file_if_needed in file "libs/commons/UTmp.ml", line 145, characters 8-27
Called from Scan_CLI.replace_target_roots_by_regular_files_where_needed.(fun) in file "src/osemgrep/cli_scan/Scan_CLI.ml", lines 1086-1087, characters 19-65
Called from List_.fast_map in file "libs/commons/List_.ml", line 81, characters 17-20
Calle


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.

@github-actions
Copy link
Copy Markdown

No actionable suggestions for changed features.

Copy link
Copy Markdown
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 (5)
features/Volumetric Shadows/Shaders/VolumetricShadows/BuildShadowFroxelCS.hlsl (1)

17-39: 💤 Low value

DirectionalShadowLightData is duplicated from ShadowSampling.hlsli.

This struct definition is identical to the one in ShadowSampling.hlsli. If the layout ever changes, both must be updated in sync. Consider extracting to a shared header or adding a comment noting the dependency.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@features/Volumetric`
Shadows/Shaders/VolumetricShadows/BuildShadowFroxelCS.hlsl around lines 17 - 39,
The struct DirectionalShadowLightData in BuildShadowFroxelCS.hlsl is duplicated
from ShadowSampling.hlsli; remove the duplicate definition and instead include
or import the shared definition (or create a new shared header) so both shaders
reference the single canonical DirectionalShadowLightData; update
BuildShadowFroxelCS.hlsl to reference the shared header (or add a clear comment
linking to ShadowSampling.hlsli) to prevent drifting layouts between
DirectionalShadowLightData definitions.
src/Features/VolumetricShadows.cpp (2)

173-189: 💤 Low value

Constant buffer slots 5 and 12/13 are not cleared in cleanup.

Lines 173-183 clean up CB slot 1, SRV slots 0 and 98, samplers 0-1, and UAV 0, but constant buffer slots 5 (sharedDataBuf), 12 (perFrameCB), and 13 (vrPerFrameCB) are left bound. While this may be intentional if these buffers are expected to persist, it's inconsistent with the thorough cleanup of other bindings.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Features/VolumetricShadows.cpp` around lines 173 - 189, The cleanup
misses unbinding constant buffers at slots 5, 12 and 13 (sharedDataBuf,
perFrameCB, vrPerFrameCB); update the CS cleanup to also call
CSSetConstantBuffers for those slots using the existing nullCBs array (e.g. call
context->CSSetConstantBuffers(5,1,nullCBs);
context->CSSetConstantBuffers(12,1,nullCBs);
context->CSSetConstantBuffers(13,1,nullCBs);) ensuring the nulling occurs before
SetSharedShadowMapSRV/shadowView release and reusing the nullCBs variable
already defined in this scope.

196-210: 💤 Low value

DrawSettings message may be misleading for VR users.

Line 199 says "(per eye)" but kFroxelGridWidth is the mono eye width (160). In VR, froxelWidth is doubled to 320 total (side-by-side), so the actual per-eye resolution is still 160. The display at line 208 shows froxelWidth which is 320 in VR, potentially confusing users. Consider clarifying:

 	ImGui::TextWrapped(
-		"Builds a %ux%ux%u view-space froxel grid (per eye) of PCF-filtered directional shadow visibility.\n"
+		"Builds a %ux%ux%u view-space froxel grid of PCF-filtered directional shadow visibility.\n"
 		"Consumers sample the grid via the shared shadow map texture at slot t%u.",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Features/VolumetricShadows.cpp` around lines 196 - 210, The UI text is
misleading in VR because DrawSettings prints "(per eye)" using kFroxelGridWidth
but then shows froxelWidth which is the total (doubled) width in VR; update
DrawSettings to clearly state per-eye vs total dimensions by using
kFroxelGridWidth/kFroxelGridHeight/kFroxelGridDepth when describing the per-eye
froxel grid (the first ImGui::TextWrapped) and either display the resource line
using per-eye values (kFroxelGridWidth, kFroxelGridHeight, kFroxelGridDepth) or
show both per-eye and total (e.g., "per-eye: %ux%ux%u, total: %ux%ux%u") so
users aren’t confused—modify the code around VolumetricShadows::DrawSettings,
replacing the ambiguous use of froxelWidth/froxelHeight/froxelDepth with
explicit per-eye and/or total indicators.
features/Volumetric Shadows/Shaders/VolumetricShadows/VolumetricShadows.hlsli (1)

21-47: 💤 Low value

LinearSampler must be bound by consumers before calling SampleShadowFroxel.

Line 46 uses LinearSampler which is not declared in this file. Consumers must ensure it's bound. This appears to be an existing pattern in the codebase, but consider adding a comment noting this dependency for clarity.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@features/Volumetric`
Shadows/Shaders/VolumetricShadows/VolumetricShadows.hlsli around lines 21 - 47,
SampleShadowFroxel calls SharedShadowMap.SampleLevel with LinearSampler (used on
line where SampleShadowFroxel returns), but LinearSampler is not declared in
this file and must be bound by callers; add a brief comment immediately above
the SampleShadowFroxel function (referencing SampleShadowFroxel, LinearSampler
and SharedShadowMap) stating that LinearSampler must be bound by consumers
before calling this function and describing expected sampler state (e.g., linear
filtering, wrap/ clamp as required), so callers know the dependency and correct
binding.
src/Features/VolumetricShadows.h (1)

66-67: 💤 Low value

Stale transient pointer may be observed if BuildShadowFroxel is never called.

The shadowView member is described as "transient" but initialized to nullptr and only set/cleared inside BuildShadowFroxel. If another code path reads this member before the first dispatch, it will see stale data. Consider documenting the expected lifecycle or making it a local variable in the dispatch function since it's only used transiently.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Features/VolumetricShadows.h` around lines 66 - 67, The transient member
ID3D11ShaderResourceView* shadowView should not be a persistent class field
because it can be observed stale before BuildShadowFroxel runs; move shadowView
out of the class and make it a local variable inside the
dispatch/shadow-building function (e.g., inside BuildShadowFroxel or the
dispatch method that calls it) where it's allocated/used and cleared, or if it
must remain a member, explicitly initialize/clear it in the constructor and
every code path that may read it and add a clear lifecycle comment; ensure you
remove all reads/writes to the old member and update call sites to use the new
local variable so no other code can observe a stale pointer.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/Features/VolumetricShadows.cpp`:
- Around line 59-92: The created froxel resources (shadowFroxelTexture,
shadowFroxelSRV, shadowFroxelUAV) from CreateFroxelResources and the
samplers/configCB allocated in SetupResources are not released; add a cleanup
path by implementing either a VolumetricShadows::~VolumetricShadows() destructor
or a ClearResources() method that safely Releases each COM pointer
(shadowFroxelUAV, shadowFroxelSRV, shadowFroxelTexture, comparisonSampler,
linearSampler) and deletes/zeroes configCB, setting pointers to nullptr after
release so unloading the feature won’t leak resources. Ensure the
destructor/ClearResources is called when the feature is torn down.

---

Nitpick comments:
In `@features/Volumetric`
Shadows/Shaders/VolumetricShadows/BuildShadowFroxelCS.hlsl:
- Around line 17-39: The struct DirectionalShadowLightData in
BuildShadowFroxelCS.hlsl is duplicated from ShadowSampling.hlsli; remove the
duplicate definition and instead include or import the shared definition (or
create a new shared header) so both shaders reference the single canonical
DirectionalShadowLightData; update BuildShadowFroxelCS.hlsl to reference the
shared header (or add a clear comment linking to ShadowSampling.hlsli) to
prevent drifting layouts between DirectionalShadowLightData definitions.

In `@features/Volumetric`
Shadows/Shaders/VolumetricShadows/VolumetricShadows.hlsli:
- Around line 21-47: SampleShadowFroxel calls SharedShadowMap.SampleLevel with
LinearSampler (used on line where SampleShadowFroxel returns), but LinearSampler
is not declared in this file and must be bound by callers; add a brief comment
immediately above the SampleShadowFroxel function (referencing
SampleShadowFroxel, LinearSampler and SharedShadowMap) stating that
LinearSampler must be bound by consumers before calling this function and
describing expected sampler state (e.g., linear filtering, wrap/ clamp as
required), so callers know the dependency and correct binding.

In `@src/Features/VolumetricShadows.cpp`:
- Around line 173-189: The cleanup misses unbinding constant buffers at slots 5,
12 and 13 (sharedDataBuf, perFrameCB, vrPerFrameCB); update the CS cleanup to
also call CSSetConstantBuffers for those slots using the existing nullCBs array
(e.g. call context->CSSetConstantBuffers(5,1,nullCBs);
context->CSSetConstantBuffers(12,1,nullCBs);
context->CSSetConstantBuffers(13,1,nullCBs);) ensuring the nulling occurs before
SetSharedShadowMapSRV/shadowView release and reusing the nullCBs variable
already defined in this scope.
- Around line 196-210: The UI text is misleading in VR because DrawSettings
prints "(per eye)" using kFroxelGridWidth but then shows froxelWidth which is
the total (doubled) width in VR; update DrawSettings to clearly state per-eye vs
total dimensions by using kFroxelGridWidth/kFroxelGridHeight/kFroxelGridDepth
when describing the per-eye froxel grid (the first ImGui::TextWrapped) and
either display the resource line using per-eye values (kFroxelGridWidth,
kFroxelGridHeight, kFroxelGridDepth) or show both per-eye and total (e.g.,
"per-eye: %ux%ux%u, total: %ux%ux%u") so users aren’t confused—modify the code
around VolumetricShadows::DrawSettings, replacing the ambiguous use of
froxelWidth/froxelHeight/froxelDepth with explicit per-eye and/or total
indicators.

In `@src/Features/VolumetricShadows.h`:
- Around line 66-67: The transient member ID3D11ShaderResourceView* shadowView
should not be a persistent class field because it can be observed stale before
BuildShadowFroxel runs; move shadowView out of the class and make it a local
variable inside the dispatch/shadow-building function (e.g., inside
BuildShadowFroxel or the dispatch method that calls it) where it's
allocated/used and cleared, or if it must remain a member, explicitly
initialize/clear it in the constructor and every code path that may read it and
add a clear lifecycle comment; ensure you remove all reads/writes to the old
member and update call sites to use the new local variable so no other code can
observe a stale pointer.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b13e86c4-aa83-4446-8c67-342a146a0d05

📥 Commits

Reviewing files that changed from the base of the PR and between 377da5f and 83f17ef.

📒 Files selected for processing (9)
  • features/Volumetric Shadows/Shaders/Features/VolumetricShadows.ini
  • features/Volumetric Shadows/Shaders/VolumetricShadows/BlurShadowCS.hlsl
  • features/Volumetric Shadows/Shaders/VolumetricShadows/BuildShadowFroxelCS.hlsl
  • features/Volumetric Shadows/Shaders/VolumetricShadows/DownsampleShadowCS.hlsl
  • features/Volumetric Shadows/Shaders/VolumetricShadows/VolumetricShadows.hlsli
  • package/Shaders/Common/ShadowSampling.hlsli
  • src/Features/VolumetricShadows.cpp
  • src/Features/VolumetricShadows.h
  • src/State.cpp
💤 Files with no reviewable changes (2)
  • features/Volumetric Shadows/Shaders/VolumetricShadows/BlurShadowCS.hlsl
  • features/Volumetric Shadows/Shaders/VolumetricShadows/DownsampleShadowCS.hlsl

Comment on lines +59 to 92
void VolumetricShadows::CreateFroxelResources()
{
auto device = globals::d3d::device;

froxelWidth = kFroxelGridWidth * (REL::Module::IsVR() ? 2u : 1u);
froxelHeight = kFroxelGridHeight;
froxelDepth = kFroxelGridDepth;

D3D11_TEXTURE3D_DESC texDesc{};
texDesc.Width = froxelWidth;
texDesc.Height = froxelHeight;
texDesc.Depth = froxelDepth;
texDesc.MipLevels = 1;
texDesc.Format = DXGI_FORMAT_R8_UNORM;
texDesc.Usage = D3D11_USAGE_DEFAULT;
texDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS;

DX::ThrowIfFailed(device->CreateTexture3D(&texDesc, nullptr, &shadowFroxelTexture));
Util::SetResourceName(shadowFroxelTexture, "VolumetricShadows::ShadowFroxel");

D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc{};
srvDesc.Format = texDesc.Format;
srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE3D;
srvDesc.Texture3D.MipLevels = 1;
DX::ThrowIfFailed(device->CreateShaderResourceView(shadowFroxelTexture, &srvDesc, &shadowFroxelSRV));
Util::SetResourceName(shadowFroxelSRV, "VolumetricShadows::ShadowFroxel SRV");

D3D11_UNORDERED_ACCESS_VIEW_DESC uavDesc{};
uavDesc.Format = texDesc.Format;
uavDesc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE3D;
uavDesc.Texture3D.WSize = froxelDepth;
DX::ThrowIfFailed(device->CreateUnorderedAccessView(shadowFroxelTexture, &uavDesc, &shadowFroxelUAV));
Util::SetResourceName(shadowFroxelUAV, "VolumetricShadows::ShadowFroxel UAV");
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Missing resource cleanup for froxel texture, SRV, UAV, samplers, and constant buffer.

The CreateFroxelResources function allocates shadowFroxelTexture, shadowFroxelSRV, and shadowFroxelUAV, but there's no destructor or cleanup method to release these COM objects. Similarly, linearSampler, comparisonSampler, and configCB allocated in SetupResources are never released. This will cause resource leaks when the feature is unloaded or the game exits.

Consider adding a destructor or ClearResources method:

🛠️ Suggested cleanup pattern
// In header, add destructor:
~VolumetricShadows();

// In cpp:
VolumetricShadows::~VolumetricShadows()
{
    if (shadowFroxelUAV) { shadowFroxelUAV->Release(); shadowFroxelUAV = nullptr; }
    if (shadowFroxelSRV) { shadowFroxelSRV->Release(); shadowFroxelSRV = nullptr; }
    if (shadowFroxelTexture) { shadowFroxelTexture->Release(); shadowFroxelTexture = nullptr; }
    if (comparisonSampler) { comparisonSampler->Release(); comparisonSampler = nullptr; }
    if (linearSampler) { linearSampler->Release(); linearSampler = nullptr; }
    delete configCB; configCB = nullptr;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Features/VolumetricShadows.cpp` around lines 59 - 92, The created froxel
resources (shadowFroxelTexture, shadowFroxelSRV, shadowFroxelUAV) from
CreateFroxelResources and the samplers/configCB allocated in SetupResources are
not released; add a cleanup path by implementing either a
VolumetricShadows::~VolumetricShadows() destructor or a ClearResources() method
that safely Releases each COM pointer (shadowFroxelUAV, shadowFroxelSRV,
shadowFroxelTexture, comparisonSampler, linearSampler) and deletes/zeroes
configCB, setting pointers to nullptr after release so unloading the feature
won’t leak resources. Ensure the destructor/ClearResources is called when the
feature is torn down.

@SkrubbySkrubInAShrub
Copy link
Copy Markdown
Collaborator

what makes this PR breaking?

@github-actions
Copy link
Copy Markdown

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

@doodlum doodlum closed this May 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants