diff --git a/src/AcDream.App/Rendering/Wb/ManagedGLTextureArray.cs b/src/AcDream.App/Rendering/Wb/ManagedGLTextureArray.cs index eb69e11f..0ca01a58 100644 --- a/src/AcDream.App/Rendering/Wb/ManagedGLTextureArray.cs +++ b/src/AcDream.App/Rendering/Wb/ManagedGLTextureArray.cs @@ -41,6 +41,16 @@ namespace AcDream.App.Rendering.Wb { public ulong BindlessClampHandle { get; private set; } public long TotalSizeInBytes => CalculateTotalSize(); + /// + /// #105 diagnostic: staged layer updates (PBO writes + pending list) not yet + /// applied to the GL texture by . 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. + /// + 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; diff --git a/src/AcDream.App/Rendering/Wb/ObjectMeshManager.cs b/src/AcDream.App/Rendering/Wb/ObjectMeshManager.cs index 03b7dd61..118e0134 100644 --- a/src/AcDream.App/Rendering/Wb/ObjectMeshManager.cs +++ b/src/AcDream.App/Rendering/Wb/ObjectMeshManager.cs @@ -301,6 +301,23 @@ namespace AcDream.App.Rendering.Wb { } } + /// + /// #105 diagnostic: counts staged-but-unflushed texture layer updates across all + /// shared atlases (see ). + /// Render thread only — _globalAtlases is render-thread-owned. + /// + 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); + } + /// /// Decrement reference count and unload GPU resources if no longer needed. /// diff --git a/src/AcDream.App/Rendering/Wb/WbMeshAdapter.cs b/src/AcDream.App/Rendering/Wb/WbMeshAdapter.cs index ecf25798..5f2e8e44 100644 --- a/src/AcDream.App/Rendering/Wb/WbMeshAdapter.cs +++ b/src/AcDream.App/Rendering/Wb/WbMeshAdapter.cs @@ -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; + + /// + /// #105 apparatus: one [tex-flush] line on change of the staged-texture + /// pending picture (plus a ~10 s heartbeat while anything is stuck). A healthy + /// frame ends with after=0; before==after>0 persisting at + /// standstill is the white-walls mechanism live (staged uploads never applied). + /// + 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) diff --git a/src/AcDream.Core/Rendering/RenderingDiagnostics.cs b/src/AcDream.Core/Rendering/RenderingDiagnostics.cs index 46eb3c64..f219fbb4 100644 --- a/src/AcDream.Core/Rendering/RenderingDiagnostics.cs +++ b/src/AcDream.Core/Rendering/RenderingDiagnostics.cs @@ -172,6 +172,21 @@ public static class RenderingDiagnostics public static bool ProbeClipRouteEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_CLIPROUTE") == "1"; + /// + /// #105 white-indoor-textures apparatus (2026-06-10). When true, WbMeshAdapter.Tick + /// emits one [tex-flush] line whenever the staged-texture-update picture changes: + /// pending layer updates across all shared atlases BEFORE and AFTER the per-frame + /// ObjectMeshManager.GenerateMipmaps() flush, plus arrays-with-pending / total-array + /// counts. The broken contract this pins: TextureAtlasManager.AddTexture 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 after=0 on every + /// line; a stuck before==after>0 at standstill is the #105 mechanism live. + /// Initial state from ACDREAM_PROBE_TEXFLUSH=1. + /// + public static bool ProbeTexFlushEnabled { get; set; } = + Environment.GetEnvironmentVariable("ACDREAM_PROBE_TEXFLUSH") == "1"; + /// /// 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,