refactor(render): cull terrain by walk in-view cells

This commit is contained in:
Erik 2026-08-31 04:54:03 +02:00
parent 98d2d43fdc
commit 837a33baff
3 changed files with 54 additions and 14 deletions

View file

@ -10,6 +10,11 @@ namespace AcDream.App.Rendering;
/// methods — the cutover changes ORDER only.</summary> /// methods — the cutover changes ORDER only.</summary>
internal sealed partial class RetailPViewPassExecutor internal sealed partial class RetailPViewPassExecutor
{ {
private IReadOnlySet<uint>? _walkTerrainInViewLandcells;
internal void SetWalkTerrainInViewLandcells(IReadOnlySet<uint>? cells) =>
_walkTerrainInViewLandcells = cells;
/// <summary>The walk's single sky turn: retail draws GameSky once /// <summary>The walk's single sky turn: retail draws GameSky once
/// inside <c>LScape::draw</c>, clipped by the active views; looping /// inside <c>LScape::draw</c>, clipped by the active views; looping
/// today's per-slice scissor + terrain-clip block reproduces today's /// today's per-slice scissor + terrain-clip block reproduces today's
@ -105,7 +110,8 @@ internal sealed partial class RetailPViewPassExecutor
frame.Frustum, frame.Frustum,
neverCullLandblockId: frame.PlayerLandblockId, neverCullLandblockId: frame.PlayerLandblockId,
clipPlanes: default, clipPlanes: default,
ndcClipAabb: new Vector4(-1f, -1f, 1f, 1f)); ndcClipAabb: new Vector4(-1f, -1f, 1f, 1f),
inViewLandcells: _walkTerrainInViewLandcells);
_terrainDiagnostics.Complete(); _terrainDiagnostics.Complete();
return; return;
} }
@ -120,7 +126,8 @@ internal sealed partial class RetailPViewPassExecutor
frame.Frustum, frame.Frustum,
neverCullLandblockId: frame.PlayerLandblockId, neverCullLandblockId: frame.PlayerLandblockId,
clipPlanes: slice.Planes, clipPlanes: slice.Planes,
ndcClipAabb: slice.NdcAabb); ndcClipAabb: slice.NdcAabb,
inViewLandcells: _walkTerrainInViewLandcells);
_terrainDiagnostics.Complete(); _terrainDiagnostics.Complete();
DisableClipDistances(); DisableClipDistances();
if (scissor) if (scissor)

View file

@ -1349,7 +1349,15 @@ public sealed class RetailPViewRenderer
Walk.WalkFrameDriver driver) Walk.WalkFrameDriver driver)
{ {
var (frame, encoder) = passes.RequireWalkSubmission(); var (frame, encoder) = passes.RequireWalkSubmission();
passes.SetWalkTerrainInViewLandcells(driver.VisitedLandscapeCellIds);
try
{
driver.Replay(frame, encoder); driver.Replay(frame, encoder);
}
finally
{
passes.SetWalkTerrainInViewLandcells(null);
}
// Landscape-stage static-owner particles (candles, the cathedral // Landscape-stage static-owner particles (candles, the cathedral
// falls) submit AT THEIR OWN WALK TURNS inside Replay // falls) submit AT THEIR OWN WALK TURNS inside Replay

View file

@ -68,6 +68,7 @@ public sealed partial class TerrainModernRenderer : IDisposable
// Reusable per-frame buffers. // Reusable per-frame buffers.
private readonly List<int> _visibleSlots = new(); private readonly List<int> _visibleSlots = new();
private readonly HashSet<uint> _visibleCellIds = new(); private readonly HashSet<uint> _visibleCellIds = new();
private readonly HashSet<uint> _walkVisibleLandblocks = new();
private DrawElementsIndirectCommand[] _deicScratch = Array.Empty<DrawElementsIndirectCommand>(); private DrawElementsIndirectCommand[] _deicScratch = Array.Empty<DrawElementsIndirectCommand>();
// Diag. // Diag.
@ -204,24 +205,47 @@ public sealed partial class TerrainModernRenderer : IDisposable
FrustumPlanes? frustum = null, FrustumPlanes? frustum = null,
uint? neverCullLandblockId = null, uint? neverCullLandblockId = null,
ReadOnlySpan<Vector4> clipPlanes = default, ReadOnlySpan<Vector4> clipPlanes = default,
Vector4? ndcClipAabb = null) Vector4? ndcClipAabb = null,
IReadOnlySet<uint>? inViewLandcells = null)
{ {
if (_alloc.LoadedCount == 0) return; if (_alloc.LoadedCount == 0) return;
Matrix4x4 viewProjection = camera.View * camera.Projection; Matrix4x4 viewProjection = camera.View * camera.Projection;
// Campaign FW4: RetailFrameWalk is the sole visibility authority.
// Terrain remains one full-landblock MDI draw, but only landblocks
// containing a walk-admitted landcell participate, and the published
// landcell set is the walk's exact in_view set rather than a second
// frustum-derived approximation.
_walkVisibleLandblocks.Clear();
if (inViewLandcells is not null)
{
foreach (uint cellId in inViewLandcells)
{
_walkVisibleLandblocks.Add(cellId & 0xFFFF0000u);
_visibleCellIds.Add(cellId);
}
}
// Build visible slot list with per-slot frustum cull. // Build visible slot list with per-slot frustum cull.
_visibleSlots.Clear(); _visibleSlots.Clear();
for (int slot = 0; slot < _slots.Length; slot++) for (int slot = 0; slot < _slots.Length; slot++)
{ {
var data = _slots[slot]; var data = _slots[slot];
if (data is null) continue; if (data is null) continue;
if (inViewLandcells is not null
&& !_walkVisibleLandblocks.Contains(data.LandblockId & 0xFFFF0000u))
{
continue;
}
if (frustum is not null && data.LandblockId != neverCullLandblockId) if (frustum is not null && data.LandblockId != neverCullLandblockId)
{ {
if (!FrustumCuller.IsAabbVisible(frustum.Value, data.AabbMin, data.AabbMax)) if (!FrustumCuller.IsAabbVisible(frustum.Value, data.AabbMin, data.AabbMax))
continue; continue;
} }
_visibleSlots.Add(slot); _visibleSlots.Add(slot);
if (inViewLandcells is null)
{
CollectVisibleCells( CollectVisibleCells(
_visibleCellIds, _visibleCellIds,
data.LandblockId, data.LandblockId,
@ -233,6 +257,7 @@ public sealed partial class TerrainModernRenderer : IDisposable
clipPlanes, clipPlanes,
ndcClipAabb); ndcClipAabb);
} }
}
if (_visibleSlots.Count == 0) return; if (_visibleSlots.Count == 0) return;
BuildIndirectCommands(); BuildIndirectCommands();