#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 const int GpuQueryRingDepth = 3;
|
||||||
private readonly uint[] _gpuQueryOpaque = new uint[GpuQueryRingDepth];
|
private readonly uint[] _gpuQueryOpaque = new uint[GpuQueryRingDepth];
|
||||||
private readonly uint[] _gpuQueryTransparent = 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 int _gpuQueryFrameIndex;
|
||||||
private readonly long[] _gpuSamples = new long[256]; // microseconds
|
private readonly long[] _gpuSamples = new long[256]; // microseconds
|
||||||
private int _gpuSampleCursor;
|
private int _gpuSampleCursor;
|
||||||
|
|
@ -1303,18 +1316,40 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
||||||
// re-reading the same slot, producing duplicate stale samples.
|
// re-reading the same slot, producing duplicate stale samples.
|
||||||
if (diag && _gpuQueriesInitialized && _gpuQueryFrameIndex >= GpuQueryRingDepth)
|
if (diag && _gpuQueriesInitialized && _gpuQueryFrameIndex >= GpuQueryRingDepth)
|
||||||
{
|
{
|
||||||
_gl.GetQueryObject(_gpuQueryOpaque[gpuQuerySlot], QueryObjectParameterName.ResultAvailable, out int avail);
|
// #125: only read slots whose query objects were actually BEGUN (a
|
||||||
if (avail != 0)
|
// 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);
|
long gpuUs = (long)((opaqueNs + transNs) / 1000UL);
|
||||||
_gpuSamples[_gpuSampleCursor] = gpuUs;
|
_gpuSamples[_gpuSampleCursor] = gpuUs;
|
||||||
_gpuSampleCursor = (_gpuSampleCursor + 1) % _gpuSamples.Length;
|
_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 ─────────────────────────────────────────────
|
// ── Phase 7: opaque pass ─────────────────────────────────────────────
|
||||||
|
|
@ -1334,7 +1369,11 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
||||||
// mesh_modern.vert for why this is needed.
|
// mesh_modern.vert for why this is needed.
|
||||||
_shader.SetInt("uDrawIDOffset", 0);
|
_shader.SetInt("uDrawIDOffset", 0);
|
||||||
_gl.BindBuffer(BufferTargetARB.DrawIndirectBuffer, _indirectBuffer);
|
_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);
|
DrawIndirectRange(0, _opaqueDrawCount);
|
||||||
if (diag && _gpuQueriesInitialized) _gl.EndQuery(QueryTarget.TimeElapsed);
|
if (diag && _gpuQueriesInitialized) _gl.EndQuery(QueryTarget.TimeElapsed);
|
||||||
if (AlphaToCoverage) _gl.Disable(EnableCap.SampleAlphaToCoverage);
|
if (AlphaToCoverage) _gl.Disable(EnableCap.SampleAlphaToCoverage);
|
||||||
|
|
@ -1358,7 +1397,11 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
||||||
// section. BuildIndirectArrays preserves CullMode in _drawCullModes.
|
// section. BuildIndirectArrays preserves CullMode in _drawCullModes.
|
||||||
_gl.FrontFace(FrontFaceDirection.CW);
|
_gl.FrontFace(FrontFaceDirection.CW);
|
||||||
_shader.SetInt("uRenderPass", 1);
|
_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);
|
DrawIndirectRange(_opaqueDrawCount, _transparentDrawCount);
|
||||||
if (diag && _gpuQueriesInitialized) _gl.EndQuery(QueryTarget.TimeElapsed);
|
if (diag && _gpuQueriesInitialized) _gl.EndQuery(QueryTarget.TimeElapsed);
|
||||||
_gl.DepthMask(true);
|
_gl.DepthMask(true);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue