test(render): add blank-world attribution apparatus and a post-world GL sample
Investigating the V4c connected blank-world failure needed two things the tree
did not have: a way to tell whether a blank run is caused by the binary under
test, and a way to see GL state at the end of the world phase rather than only
at the frame clear. Both are apparatus only - no production behaviour changes,
and the new probe emits nothing unless ACDREAM_PROBE_GLSTATE=1.
run-blank-world-ab-probe.ps1 interleaves two client builds over the repeat
gate's exact connected route and reports the blank rate per arm. This exists
because the blank rate is not stable across blocks: the same V4c binary
measured 3/10 in one block and 7/10 in another an hour later, so a block of A
followed by a block of B confounds the change with whatever else moved on the
machine in between. Strict alternation shares that drift between both arms.
Run against V4c and its parent it reported 4/5 versus 0/5 (Fisher exact
p~0.024), which is what established the defect follows the binary.
run-blank-world-surface-probe.ps1 grabs the composited window off the desktop
with CopyFromScreen at the same moment the client writes its own screenshot.
No instrument inside the GL context can separate "the renderer drew nothing"
from "the read did not return what the renderer drew", because both live on
the same side of the readback; an independent witness can. It is what showed
the two disagree - see below.
EmitPostWorldGlStateIfChanged is a second sample of the existing [gl-state]
snapshot, taken at the end of the normal-world phase. The existing tripwire
samples just after the clear phase's RestoreFrameDefaults, so it can only
observe state that survives from one frame into the next, and the draw
framebuffer is restored by no frame-global path. A binding established during
the world phase and put back before the next clear was therefore invisible to
it. Sampling at both ends brackets the phase.
What the apparatus established, recorded here rather than in the campaign doc
because no fix landed and the doc's re-land conditions are unchanged:
* The world draw path is not what is missing from the frame. On a blank run
the desktop grab shows the atmosphere clear over the whole viewport and the
complete retained UI - chat, radar, toolbar, vitals - in their normal
places, with every 3-D surface absent. Terrain and sky are still raw GL and
V4c does not touch them, so whatever V4c disturbs is shared, not per-
renderer.
* The CPU issues the same work either way. With ACDREAM_PROBE_FLAP=1 the
render signature is identical between blank and rendered runs: same
RetailPViewInside branch, same resolved root, terrain drawn, 3,331 outdoor
statics and 6 live dynamics dispatched.
* Both GL-state samples read fbo=0, full 1280x720 viewport, scissor off and
err=0x0, byte-identical between blank and rendered runs.
* The client's own capture disagrees with the screen. glReadPixels returns
uniformly RGBA(0,0,0,0) on a frame the desktop grab shows as fog plus UI.
The default framebuffer is 4x multisampled (SampleBuffers=1, Samples=4 in
the capability report) and glReadPixels against a multisampled read
framebuffer is undefined per the GL spec, so the gate's blank-versus-
rendered verdict rests on undefined behaviour in both directions.
Baseline App tests 3,864 passed / 3 skipped, unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
c090cd693d
commit
fed636b9c0
4 changed files with 352 additions and 0 deletions
|
|
@ -121,6 +121,9 @@ internal sealed class WorldRenderDiagnostics
|
|||
private string? _lastGlStateSignature;
|
||||
private long _glStateFrame;
|
||||
private long _glStateStableFrames;
|
||||
private string? _lastPostWorldGlStateSignature;
|
||||
private long _postWorldGlStateFrame;
|
||||
private long _postWorldGlStateStableFrames;
|
||||
private string? _lastScissorSignature;
|
||||
private long _scissorSequence;
|
||||
private string? _lastOutStageSignature;
|
||||
|
|
@ -180,6 +183,41 @@ internal sealed class WorldRenderDiagnostics
|
|||
_glStateStableFrames = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Second sample of the same snapshot, taken at the END of the normal-world
|
||||
/// phase instead of at the frame clear.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="EmitGlStateTripwireIfChanged"/> samples immediately after the
|
||||
/// clear phase has run <c>RestoreFrameDefaults</c>, so it can only observe
|
||||
/// state that survives from one frame into the next. State that a world pass
|
||||
/// establishes and something in private presentation puts back before the
|
||||
/// next clear is invisible to it — including the draw framebuffer, which no
|
||||
/// frame-global restore touches. Sampling here as well brackets the world
|
||||
/// phase, so a binding that the world's geometry drew into but the retained
|
||||
/// UI did not shows up as a difference between the two lines rather than as
|
||||
/// no line at all.
|
||||
/// </remarks>
|
||||
public void EmitPostWorldGlStateIfChanged(bool enabled)
|
||||
{
|
||||
if (!enabled)
|
||||
return;
|
||||
|
||||
_postWorldGlStateFrame++;
|
||||
string signature = FormatGlState(_gl.CaptureState());
|
||||
if (signature == _lastPostWorldGlStateSignature)
|
||||
{
|
||||
_postWorldGlStateStableFrames++;
|
||||
return;
|
||||
}
|
||||
|
||||
_log.WriteLine(
|
||||
$"[gl-state-postworld] frame={_postWorldGlStateFrame} "
|
||||
+ $"stable={_postWorldGlStateStableFrames} {signature}");
|
||||
_lastPostWorldGlStateSignature = signature;
|
||||
_postWorldGlStateStableFrames = 0;
|
||||
}
|
||||
|
||||
public void EmitClipRouteScissorProbe(
|
||||
bool enabled,
|
||||
bool applied,
|
||||
|
|
|
|||
|
|
@ -171,6 +171,11 @@ internal sealed class WorldSceneDiagnosticsController : IWorldSceneDiagnostics
|
|||
IReadOnlyList<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax)> bounds)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(bounds);
|
||||
// Brackets the normal-world phase against the clear-phase tripwire: any
|
||||
// GL state the world's geometry drew under but the next frame's clear
|
||||
// no longer sees shows up as a difference between the two lines.
|
||||
_diagnostics.EmitPostWorldGlStateIfChanged(
|
||||
AcDream.Core.Rendering.RenderingDiagnostics.ProbeGlStateEnabled);
|
||||
DrawCollisionWireframes(in camera);
|
||||
|
||||
int visible = 0;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue