Description

Summary

FSceneCulling MarkCellBoundsDirty(CellIndex, bStaticChanged, bDynamicChanged) (Engine/Source/Runtime/Renderer/Private/SceneCulling/SceneCulling.cpp, ~line 1907) assigns DirtyCellBoundsMaskStatic / DirtyCellBoundsMaskDynamic instead of accumulating them. When a cell is marked twice in the same frame (static first, then dynamic-only), the static flag is overwritten with false and the static chunk explicit bounds are never rebuilt. The GPU then culls against stale chunk bounds.

Observed symptom

If you have multiple runtime PCG volumes in your scene, which use the HLSL point processor and GPU static mesh spawners, when you walk around the map, the runtime cells get culled in and out, depending on the camera angle.

Root cause

  • MarkCellBoundsDirty adds the cell to DirtyCellBoundsIndices only on the first mark, but then does:

DirtyCellBoundsMaskStatic[CellIndex] = bStaticChanged; DirtyCellBoundsMaskDynamic[CellIndex] = bDynamicChanged;

  • A later dynamic-only mark (e.g. UpdateProcessDynamicInstances ~2020, or the cell finalize call ~1591 with bAnyChange[Static] == false) overwrites an earlier static mark (e.g. SingleCell retained-state update ~2103/2116) with false.
  • The rebuild loop (~2551) reads bStaticDirty = DirtyCellBoundsMaskStatic[CellIndex], which is now false, so it does StartIndex += CellHeader.NumStaticChunks and skips the static chunks. Their bounds are never re-uploaded.

Proposed fix

Accumulate the flags (OR) in MarkCellBoundsDirty rather than assign. Both masks are reset every frame (Init(false) ~2401-2402 / Empty ~2580-2581), so nothing leaks across frames.

if (bStaticChanged)  { DirtyCellBoundsMaskStatic[CellIndex]  = true; }
if (bDynamicChanged) { DirtyCellBoundsMaskDynamic[CellIndex] = true; }

Minor note

A call with both flags false on an unmarked cell adds the index to DirtyCellBoundsIndices without setting either mask, so a later real mark adds the index a second time (duplicate chunk entries; harmless, pre-existing).

Steps to Reproduce

In one frame, have a static instance update in a cell (e.g. an ISM/grass instance update) followed by a dynamic mark in the same cell (any dynamic primitive instance update retained in that cell, or a cell finalize with only dynamic changes). Static chunk explicit bounds for that cell remain stale.

Have Comments or More Details?

There's no existing public thread on this issue, so head over to Questions & Answers just mention UE-403021 in the post.

0
Login to Vote

Unresolved
ComponentUE - Rendering - Architecture
Affects Versions5.8
Target Fix5.9
CreatedOct 14, 2026
UpdatedOct 19, 2026
View Jira Issue