#125: gpu_us query ring reads never-begun query objects - root cause of the WB_DIAG GL error cascade; fixed + live-verified
Root cause (by read, verified live): a glGenQueries name does not become a QUERY OBJECT until its first glBeginQuery - GetQueryObject on a never-begun name is GL_INVALID_OPERATION. The N.6 gpu_us ring assumed ONE dispatcher Draw per frame with both passes always non-empty; the pview pipeline issues MANY small Draws per frame (landscape slices, per-cell static buckets, dynamics), where zero-draw passes routinely skip BeginQuery. Under ACDREAM_WB_DIAG=1 the slot read queued an InvalidOperation EVERY frame - silently, until WB's diligent texture-path glGetError checks ate the stale errors and treated their own successful uploads as failures ([wb-error] + the sticky drop) and ProcessDirtyUpdates' check threw (process death, tower-wbdiag3.log). The GL-error-attribution trap, textbook form. Fix: begun-flags per ring slot per target; the read path only queries slots that were actually begun (a skipped pass contributes 0 ns). Live verification (tower-wbdiag4.log, in-tower spawn): zero [wb-error] (was 7), no crash, gpu_us reads real values (9-11 us) for the first time under the pview pipeline, meshMissing=0 / entSeen==entDrawn. Consequences: (1) the #119 missing-stairs mechanism theory via sticky GL upload failures is RETIRED for normal runs (WB_DIAG off = no query calls = no errors; clean runs confirmed zero wb-error) - and the in-tower screenshot on the current build shows the spiral staircase RENDERING, so the stairs were most plausibly a #120 flood-corruption casualty (the tower threshold cells portal back to 0x0107 exactly in the ping-pong window); user verdict pending. (2) The sticky-drop defect (upload failure never retried) stays filed under #125 as defense-in-depth debt - the trigger is gone but the design flaw isn't. Suites: App 236, Core 1419+2skip, UI 420, Net 294. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
63d14c3d6b
commit
fcade06c46
1 changed files with 52 additions and 9 deletions
|
|
@ -257,6 +257,19 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
|||
private const int GpuQueryRingDepth = 3;
|
||||
private readonly uint[] _gpuQueryOpaque = new uint[GpuQueryRingDepth];
|
||||
private readonly uint[] _gpuQueryTransparent = new uint[GpuQueryRingDepth];
|
||||
// #125: a glGenQueries name does not become a QUERY OBJECT until its first
|
||||
// glBeginQuery — GetQueryObject on a never-begun name is GL_INVALID_OPERATION.
|
||||
// The N.6 ring assumed ONE Draw per frame with both passes always non-empty;
|
||||
// the pview pipeline issues MANY small Draws per frame (landscape slices,
|
||||
// per-cell buckets, dynamics), where zero-draw passes routinely skip
|
||||
// BeginQuery. Under ACDREAM_WB_DIAG=1 the slot read then queued an
|
||||
// InvalidOperation EVERY frame — silently, until WB's diligent texture-path
|
||||
// glGetError checks ate the stale errors and treated their own successful
|
||||
// uploads as failures ([wb-error] + sticky drop) and ProcessDirtyUpdates'
|
||||
// check threw (process death; tower-wbdiag3.log). Track which slots were
|
||||
// actually begun and only read those.
|
||||
private readonly bool[] _gpuQueryOpaqueBegun = new bool[GpuQueryRingDepth];
|
||||
private readonly bool[] _gpuQueryTransparentBegun = new bool[GpuQueryRingDepth];
|
||||
private int _gpuQueryFrameIndex;
|
||||
private readonly long[] _gpuSamples = new long[256]; // microseconds
|
||||
private int _gpuSampleCursor;
|
||||
|
|
@ -1303,18 +1316,40 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
|||
// re-reading the same slot, producing duplicate stale samples.
|
||||
if (diag && _gpuQueriesInitialized && _gpuQueryFrameIndex >= GpuQueryRingDepth)
|
||||
{
|
||||
_gl.GetQueryObject(_gpuQueryOpaque[gpuQuerySlot], QueryObjectParameterName.ResultAvailable, out int avail);
|
||||
if (avail != 0)
|
||||
// #125: only read slots whose query objects were actually BEGUN (a
|
||||
// zero-draw pass skips BeginQuery; reading a never-begun name is
|
||||
// GL_INVALID_OPERATION). A pass that never ran contributes 0 ns.
|
||||
ulong opaqueNs = 0, transNs = 0;
|
||||
bool anyRead = false, allAvailable = true;
|
||||
if (_gpuQueryOpaqueBegun[gpuQuerySlot])
|
||||
{
|
||||
_gl.GetQueryObject(_gpuQueryOpaque[gpuQuerySlot], QueryObjectParameterName.ResultAvailable, out int availO);
|
||||
if (availO != 0)
|
||||
{
|
||||
_gl.GetQueryObject(_gpuQueryOpaque[gpuQuerySlot], QueryObjectParameterName.Result, out opaqueNs);
|
||||
anyRead = true;
|
||||
}
|
||||
else allAvailable = false;
|
||||
}
|
||||
if (_gpuQueryTransparentBegun[gpuQuerySlot])
|
||||
{
|
||||
_gl.GetQueryObject(_gpuQueryTransparent[gpuQuerySlot], QueryObjectParameterName.ResultAvailable, out int availT);
|
||||
if (availT != 0)
|
||||
{
|
||||
_gl.GetQueryObject(_gpuQueryTransparent[gpuQuerySlot], QueryObjectParameterName.Result, out transNs);
|
||||
anyRead = true;
|
||||
}
|
||||
else allAvailable = false;
|
||||
}
|
||||
// If a begun query isn't available yet the sample is dropped
|
||||
// silently. MedianMicros computes over the non-zero subset, so
|
||||
// dropped samples don't poison the median.
|
||||
if (anyRead && allAvailable)
|
||||
{
|
||||
_gl.GetQueryObject(_gpuQueryOpaque[gpuQuerySlot], QueryObjectParameterName.Result, out ulong opaqueNs);
|
||||
_gl.GetQueryObject(_gpuQueryTransparent[gpuQuerySlot], QueryObjectParameterName.Result, out ulong transNs);
|
||||
long gpuUs = (long)((opaqueNs + transNs) / 1000UL);
|
||||
_gpuSamples[_gpuSampleCursor] = gpuUs;
|
||||
_gpuSampleCursor = (_gpuSampleCursor + 1) % _gpuSamples.Length;
|
||||
}
|
||||
// If avail==0 the sample is dropped silently. MedianMicros
|
||||
// computes over the non-zero subset, so dropped samples don't
|
||||
// poison the median.
|
||||
}
|
||||
|
||||
// ── Phase 7: opaque pass ─────────────────────────────────────────────
|
||||
|
|
@ -1334,7 +1369,11 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
|||
// mesh_modern.vert for why this is needed.
|
||||
_shader.SetInt("uDrawIDOffset", 0);
|
||||
_gl.BindBuffer(BufferTargetARB.DrawIndirectBuffer, _indirectBuffer);
|
||||
if (diag && _gpuQueriesInitialized) _gl.BeginQuery(QueryTarget.TimeElapsed, _gpuQueryOpaque[gpuQuerySlot]);
|
||||
if (diag && _gpuQueriesInitialized)
|
||||
{
|
||||
_gl.BeginQuery(QueryTarget.TimeElapsed, _gpuQueryOpaque[gpuQuerySlot]);
|
||||
_gpuQueryOpaqueBegun[gpuQuerySlot] = true; // #125
|
||||
}
|
||||
DrawIndirectRange(0, _opaqueDrawCount);
|
||||
if (diag && _gpuQueriesInitialized) _gl.EndQuery(QueryTarget.TimeElapsed);
|
||||
if (AlphaToCoverage) _gl.Disable(EnableCap.SampleAlphaToCoverage);
|
||||
|
|
@ -1358,7 +1397,11 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
|||
// section. BuildIndirectArrays preserves CullMode in _drawCullModes.
|
||||
_gl.FrontFace(FrontFaceDirection.CW);
|
||||
_shader.SetInt("uRenderPass", 1);
|
||||
if (diag && _gpuQueriesInitialized) _gl.BeginQuery(QueryTarget.TimeElapsed, _gpuQueryTransparent[gpuQuerySlot]);
|
||||
if (diag && _gpuQueriesInitialized)
|
||||
{
|
||||
_gl.BeginQuery(QueryTarget.TimeElapsed, _gpuQueryTransparent[gpuQuerySlot]);
|
||||
_gpuQueryTransparentBegun[gpuQuerySlot] = true; // #125
|
||||
}
|
||||
DrawIndirectRange(_opaqueDrawCount, _transparentDrawCount);
|
||||
if (diag && _gpuQueriesInitialized) _gl.EndQuery(QueryTarget.TimeElapsed);
|
||||
_gl.DepthMask(true);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue