fix(render) Campaign FW3.4a: rebind sections per DrawOrderedRange - the device-lost fix

The dense-Arwic re-measure crashed with VK_ERROR_DEVICE_LOST: between
ordered ranges the walk leaf draws (terrain, shells, sky, punch fans)
and RetailAlphaQueue flushes rebind the SAME set-0 storage slots to
their own sections, so the bind-once latch made the next range draw
against foreign buffers - out-of-bounds instance reads and a GPU
fault. Sections now re-bind on every DrawOrderedRange call, exactly
like the proven DrawPreparedAlphaBatchRhi; the once-per-frame ring
WRITES in PrepareOrderedStream (the actual measured cost) are
unchanged. The bind-once referee test flips to assert per-range
rebinds with unchanged draw coverage.

Suites: full Release build 0 warnings; hermetic 6,758/0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-30 17:07:29 +02:00
parent 212f5a12e5
commit 1da178d9ba
2 changed files with 28 additions and 20 deletions

View file

@ -403,8 +403,15 @@ public sealed class OrderPreservingSubmitterTests
/// (the old SubmitOrderedStream rebound everything on every call).
/// </summary>
[Fact]
public void DrawOrderedRange_SecondCallInTheSameFrame_BindsNoFurtherStorageSections()
public void DrawOrderedRange_EveryCallRebindsTheStorageSections()
{
// The corrected FW3.4a contract (the dense-Arwic device-lost fix):
// between ordered ranges the walk's leaf draws and RetailAlphaQueue
// flushes rebind the SAME set-0 slots to THEIR sections, so every
// DrawOrderedRange call must re-bind its own — a latched skip draws
// the next range against foreign buffers. Only the ring WRITES are
// once-per-frame (PrepareOrderedStream); binds repeat per range,
// exactly like DrawPreparedAlphaBatchRhi.
using var fx = new DispatcherFixture();
using DrawScope draw = fx.BeginDraw();
@ -420,9 +427,8 @@ public sealed class OrderPreservingSubmitterTests
fx.Dispatcher.DrawOrderedRange(draw.Pass, 1, 1);
int boundAfterSecond = fx.Device.Calls.OfType<GpuRecordedStorageBind>().Count();
Assert.Equal(boundAfterFirst, boundAfterSecond);
// Both commands still drew — the bind-once optimization changed
// nothing about draw coverage.
Assert.Equal(boundAfterFirst * 2, boundAfterSecond);
// Both commands drew; the rebinds changed nothing about coverage.
Assert.Equal([(0, 1), (1, 1)], DecodeDrawRanges(fx.Device));
}