perf(render): gate EnvCellRenderer visibility-snapshot rebuilds on real input changes (2026-07-24 audit review)
PrepareRenderBatches rebuilt the full EnvCell visibility snapshot every frame any indoor/building root was resolved: a Parallel.ForEach dispatch over every GpuReady EnvCell landblock (with per-landblock locks), a fresh outer dictionary plus one fresh inner dictionary per visible cell, a new snapshot object, and a complete transparency rescan - all while standing perfectly still. The NeedsPrepare flag existed since Phase A8 as the intended rebuild gate but was never read by production code. This wires the gate on the snapshot's actual inputs: - landblock commits/removals (the existing NeedsPrepare flag), - the visible-cell filter (content-compared; the caller reuses one scratch HashSet across frames), - the trim window (center/radius), - mesh render-data availability - new ObjectMeshManager.RenderDataAvailabilityVersion, bumped at publish, pending-release hide, release completion, and teardown, because the snapshot bakes per-cell transparency from TryGetRenderData and a late-arriving transparent shell must reclassify its cell, - the camera: eye position under a 1 mm ABSOLUTE epsilon (swallows the documented ~36 um rest jitter, dirties on any real movement; the VP translation row scales with AC's ~5e4 world coordinates where a relative tolerance would mask sub-meter motion) plus rows 1-3 of view*projection (position-independent rotation x projection) under relative 1e-5. Skipping is pool-safe: RenderCore re-anchors _poolIndex to the active snapshot's PostPreparePoolIndex on every call, so consecutive Renders without an intervening Prepare reuse scratch lists past the snapshot's owned region exactly as within-frame passes already do. The empty-filter branch is now also idempotent instead of allocating a fresh empty snapshot per frame. Render still receives the current frame's view-projection every frame (the U.4 stale-matrix rule) - only the snapshot rebuild is gated. Pixels must be identical; needs the standard user visual pass (dungeon + town-near-buildings) before the change is considered accepted. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
38e14c6a71
commit
c2cb83d11f
3 changed files with 208 additions and 0 deletions
|
|
@ -122,6 +122,12 @@ namespace AcDream.App.Rendering.Wb
|
|||
private bool _workersQuiesced;
|
||||
private bool _workSignalDisposed;
|
||||
private readonly ConcurrentDictionary<ulong, ObjectRenderData> _renderData = new();
|
||||
// Bumped whenever TryGetRenderData's answer can change for any id
|
||||
// (publish, pending-release hide, release completion, teardown).
|
||||
// EnvCellRenderer's prepare gate keys its visibility snapshot on this:
|
||||
// the snapshot bakes per-cell transparency from TryGetRenderData, so a
|
||||
// skipped rebuild must be provably input-identical.
|
||||
private long _renderDataAvailabilityVersion;
|
||||
// A render-data entry remains published until every one of its physical
|
||||
// resources has either released or reported a committed exceptional
|
||||
// outcome. Accessors hide entries in this map because a partially
|
||||
|
|
@ -454,6 +460,17 @@ namespace AcDream.App.Rendering.Wb
|
|||
: null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Monotonic counter that changes whenever <see cref="TryGetRenderData"/>
|
||||
/// can answer differently for any id. Consumers that bake availability
|
||||
/// into a cached product (EnvCellRenderer's visibility snapshot) rebuild
|
||||
/// when this moves.
|
||||
/// </summary>
|
||||
public long RenderDataAvailabilityVersion => Volatile.Read(ref _renderDataAvailabilityVersion);
|
||||
|
||||
private void MarkRenderDataAvailabilityChanged() =>
|
||||
Interlocked.Increment(ref _renderDataAvailabilityVersion);
|
||||
|
||||
/// <summary>
|
||||
/// Increment reference count for an object (e.g. when a landblock starts using it).
|
||||
/// </summary>
|
||||
|
|
@ -1324,6 +1341,7 @@ namespace AcDream.App.Rendering.Wb
|
|||
if (!_renderData.TryAdd(meshData.ObjectId, data))
|
||||
throw new InvalidOperationException(
|
||||
$"Setup 0x{meshData.ObjectId:X10} was published concurrently.");
|
||||
MarkRenderDataAvailabilityChanged();
|
||||
_currentNonArenaGpuMemory = checked(
|
||||
_currentNonArenaGpuMemory + data.NonArenaGpuBytes);
|
||||
}
|
||||
|
|
@ -1372,6 +1390,7 @@ namespace AcDream.App.Rendering.Wb
|
|||
renderData.DIDDegrade = meshData.DIDDegrade;
|
||||
renderData.SelectionSphere = meshData.SelectionSphere;
|
||||
_renderData.TryAdd(meshData.ObjectId, renderData);
|
||||
MarkRenderDataAvailabilityChanged();
|
||||
_currentNonArenaGpuMemory = checked(
|
||||
_currentNonArenaGpuMemory + renderData.NonArenaGpuBytes);
|
||||
UpdateLruAfterUpload(meshData.ObjectId);
|
||||
|
|
@ -2315,6 +2334,7 @@ namespace AcDream.App.Rendering.Wb
|
|||
GetReclaimableBytes(data),
|
||||
new RetryableResourceReleaseLedger(releases));
|
||||
_objectReleases.Add(key, ticket);
|
||||
MarkRenderDataAvailabilityChanged();
|
||||
return ticket;
|
||||
}
|
||||
|
||||
|
|
@ -2351,6 +2371,7 @@ namespace AcDream.App.Rendering.Wb
|
|||
_renderData.TryRemove(key, out _);
|
||||
}
|
||||
_objectReleases.Remove(key);
|
||||
MarkRenderDataAvailabilityChanged();
|
||||
if (!_ownership.IsOwned(key))
|
||||
_ownership.Remove(key);
|
||||
lock (_lruList)
|
||||
|
|
@ -2635,6 +2656,7 @@ namespace AcDream.App.Rendering.Wb
|
|||
|
||||
_renderData.Clear();
|
||||
_objectReleases.Clear();
|
||||
MarkRenderDataAvailabilityChanged();
|
||||
_objectReleaseQueue.Clear();
|
||||
_uploadRollbacks.Clear();
|
||||
_uploadRollbackQueue.Clear();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue