fix(render): #105 white indoor walls — restore WB's per-frame staged-texture flush dropped in the N.4/O-T4 extraction
Root cause: TextureAtlasManager.AddTexture only STAGES texture content (PBO write + ManagedGLTextureArray._pendingUpdates); the actual TexSubImage3D copies + mipmap regeneration happen in ProcessDirtyUpdates, which WB drives once per frame via ObjectMeshManager.GenerateMipmaps() from its render loop (WB GameScene.cs:975, just before the opaque pass). GameScene is the file we replaced with GameWindow, so the call site was silently dropped — staged updates only reached the GPU as a side effect of PBO growth (UpdateLayerInternal flushes pending updates before orphaning the PBO). Every layer staged after an array's LAST growth kept undefined TexStorage3D content behind a valid, resident bindless sampler handle: white/garbage walls, zh==0, dat tripwires silent — exactly the #105 signature. Only ObjectRenderBatch.BindlessTextureHandle consumers are affected (EnvCellRenderer cell shells = indoor walls); entities resolve via TextureCache (immediate TexImage2D) and terrain via TerrainAtlas (immediate GenerateMipmap), which is why only indoor walls ever struck. Fix: WbMeshAdapter.Tick() now calls _meshManager.GenerateMipmaps() after the staged-upload drain — Tick runs before all draw passes (GameWindow OnRender), the exact WB-equivalent position. Evidence (ACDREAM_PROBE_TEXFLUSH=1 apparatus, kept env-gated): - pre-fix (texflush-prefix.log): pending updates climb 0->48->...->142 and park at 126 across 34/34 atlas arrays at standstill, forever (19 heartbeats); brief dips only at PBO-growth crossings — the broken contract live. - post-fix (texflush-postfix.log): every line after=0 — staged updates drain the same frame, all 34 arrays clean. Intermittency explained: background decode-completion order shuffles which textures land in the never-flushed tail; whether a visible wall samples one is per-run luck. Also explains the #110 correlation: znear=0.1 makes close-up geometry newly visible -> more prepare/upload pressure indoors -> bigger tail -> higher strike probability. The near plane is mechanism-innocent (re-land follows as its own commit). Baseline maintained: App 223 / UI 420 / Net 294 / Core 1377 green + 4 pre-existing #99-era failures + 1 skip; CornerFloodReplayTests (5) and CameraCornerSealReplayTests (2) gates green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
5d63038b61
commit
c78720127a
4 changed files with 92 additions and 0 deletions
|
|
@ -41,6 +41,16 @@ namespace AcDream.App.Rendering.Wb {
|
|||
public ulong BindlessClampHandle { get; private set; }
|
||||
public long TotalSizeInBytes => CalculateTotalSize();
|
||||
|
||||
/// <summary>
|
||||
/// #105 diagnostic: staged layer updates (PBO writes + pending list) not yet
|
||||
/// applied to the GL texture by <see cref="ProcessDirtyUpdates"/>. Layers with
|
||||
/// a pending update sample UNDEFINED content (TexStorage3D contents) until the
|
||||
/// flush runs — a stuck non-zero count at standstill is the white-walls mechanism.
|
||||
/// </summary>
|
||||
public int PendingUpdateCount {
|
||||
get { lock (_mipmapLock) { return _pendingUpdates.Count; } }
|
||||
}
|
||||
|
||||
public ManagedGLTextureArray(OpenGLGraphicsDevice graphicsDevice, TextureFormat format, int width, int height,
|
||||
int size, ILogger logger, TextureParameters? texParams = null) {
|
||||
var p = texParams ?? TextureParameters.Default;
|
||||
|
|
|
|||
|
|
@ -301,6 +301,23 @@ namespace AcDream.App.Rendering.Wb {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// #105 diagnostic: counts staged-but-unflushed texture layer updates across all
|
||||
/// shared atlases (see <see cref="ManagedGLTextureArray.PendingUpdateCount"/>).
|
||||
/// Render thread only — <c>_globalAtlases</c> is render-thread-owned.
|
||||
/// </summary>
|
||||
public (int PendingUpdates, int ArraysWithPending, int TotalArrays) GetPendingTextureUpdateStats() {
|
||||
int pending = 0, arraysWith = 0, total = 0;
|
||||
foreach (var atlasList in _globalAtlases.Values) {
|
||||
foreach (var atlas in atlasList) {
|
||||
total++;
|
||||
int p = atlas.TextureArray.PendingUpdateCount;
|
||||
if (p > 0) { arraysWith++; pending += p; }
|
||||
}
|
||||
}
|
||||
return (pending, arraysWith, total);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrement reference count and unload GPU resources if no longer needed.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -212,6 +212,56 @@ public sealed class WbMeshAdapter : IDisposable, IWbMeshAdapter
|
|||
{
|
||||
_meshManager.UploadMeshData(meshData);
|
||||
}
|
||||
|
||||
bool texProbe = AcDream.Core.Rendering.RenderingDiagnostics.ProbeTexFlushEnabled;
|
||||
var pendingBefore = texProbe
|
||||
? _meshManager.GetPendingTextureUpdateStats()
|
||||
: default;
|
||||
|
||||
// #105 root cause (2026-06-10): TextureAtlasManager.AddTexture only STAGES
|
||||
// texture content (PBO write + ManagedGLTextureArray._pendingUpdates) — the
|
||||
// actual TexSubImage3D copies + mipmap regeneration happen in
|
||||
// ProcessDirtyUpdates, which WB drives ONCE PER FRAME from its render loop
|
||||
// (WB GameScene.cs:975 `_meshManager?.GenerateMipmaps()`, just before the
|
||||
// opaque pass). That call site lived in the GameScene file the N.4/O-T4
|
||||
// extraction replaced with GameWindow, so the driver was silently dropped:
|
||||
// staged updates only ever reached the GPU as a side effect of PBO growth,
|
||||
// and every layer staged after an array's LAST growth kept undefined
|
||||
// TexStorage3D content behind a valid resident bindless handle — the
|
||||
// intermittent white indoor walls (#105). Pre-fix evidence: 126 updates
|
||||
// stuck across 34/34 arrays at standstill (texflush-prefix.log). Tick()
|
||||
// runs before all draw passes (GameWindow OnRender), so this is the exact
|
||||
// WB-equivalent position.
|
||||
_meshManager.GenerateMipmaps();
|
||||
|
||||
if (texProbe)
|
||||
EmitTexFlushProbe(pendingBefore);
|
||||
}
|
||||
|
||||
// #105 apparatus state — see RenderingDiagnostics.ProbeTexFlushEnabled.
|
||||
private int _lastTexFlushBefore = -1;
|
||||
private int _texFlushHeartbeat;
|
||||
|
||||
/// <summary>
|
||||
/// #105 apparatus: one <c>[tex-flush]</c> line on change of the staged-texture
|
||||
/// pending picture (plus a ~10 s heartbeat while anything is stuck). A healthy
|
||||
/// frame ends with <c>after=0</c>; <c>before==after>0</c> persisting at
|
||||
/// standstill is the white-walls mechanism live (staged uploads never applied).
|
||||
/// </summary>
|
||||
private void EmitTexFlushProbe((int PendingUpdates, int ArraysWithPending, int TotalArrays) before)
|
||||
{
|
||||
var after = _meshManager!.GetPendingTextureUpdateStats();
|
||||
bool changed = before.PendingUpdates != _lastTexFlushBefore;
|
||||
bool flushed = after.PendingUpdates != before.PendingUpdates;
|
||||
bool heartbeat = after.PendingUpdates > 0 && ++_texFlushHeartbeat >= 600;
|
||||
if (!changed && !flushed && !heartbeat) return;
|
||||
|
||||
_texFlushHeartbeat = 0;
|
||||
_lastTexFlushBefore = before.PendingUpdates;
|
||||
Console.WriteLine(
|
||||
$"[tex-flush] before={before.PendingUpdates} after={after.PendingUpdates}" +
|
||||
$" arrays={after.ArraysWithPending}/{after.TotalArrays}" +
|
||||
$" (arraysBefore={before.ArraysWithPending})");
|
||||
}
|
||||
|
||||
private void PopulateMetadata(ulong id)
|
||||
|
|
|
|||
|
|
@ -172,6 +172,21 @@ public static class RenderingDiagnostics
|
|||
public static bool ProbeClipRouteEnabled { get; set; } =
|
||||
Environment.GetEnvironmentVariable("ACDREAM_PROBE_CLIPROUTE") == "1";
|
||||
|
||||
/// <summary>
|
||||
/// #105 white-indoor-textures apparatus (2026-06-10). When true, <c>WbMeshAdapter.Tick</c>
|
||||
/// emits one <c>[tex-flush]</c> line whenever the staged-texture-update picture changes:
|
||||
/// pending layer updates across all shared atlases BEFORE and AFTER the per-frame
|
||||
/// <c>ObjectMeshManager.GenerateMipmaps()</c> flush, plus arrays-with-pending / total-array
|
||||
/// counts. The broken contract this pins: <c>TextureAtlasManager.AddTexture</c> only STAGES
|
||||
/// pixel data (PBO + pending list); without the per-frame flush (WB GameScene.cs:975) the
|
||||
/// data never reaches the GL texture and the batch samples undefined content behind a valid
|
||||
/// bindless handle — the classic white walls. A healthy run shows <c>after=0</c> on every
|
||||
/// line; a stuck <c>before==after>0</c> at standstill is the #105 mechanism live.
|
||||
/// Initial state from <c>ACDREAM_PROBE_TEXFLUSH=1</c>.
|
||||
/// </summary>
|
||||
public static bool ProbeTexFlushEnabled { get; set; } =
|
||||
Environment.GetEnvironmentVariable("ACDREAM_PROBE_TEXFLUSH") == "1";
|
||||
|
||||
/// <summary>
|
||||
/// Bounded-propagation port apparatus (2026-06-08). When true, PortalVisibilityBuilder.Build emits
|
||||
/// one [portal-churn] summary line per call: per-cell pop count (re-pops = churn), total re-enqueues,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue