perf(capture): overlap GPU readbacks with monotonic publish gate (slice 17)

Measure take ty-1824 on the slice-16 build: the downscale fix worked (conv
~47ms, ring allocs 0) but desktop band was still 88% frozen at 6.8 updates/s.
Telemetry isolated the real wall — CreateCopyFromSurfaceAsync readback ~45ms
of each conversion, serialized one-in-flight => ~17/s capture cap. Docs fact:
pool-sized surfaces CLIP, not scale (Microsoft Learn), so readback stays
native; the lever is concurrency.

- MaxConcurrentConversions=3 with pool 2->5 buffers (in-flight frames fit)
- new MonotonicGate (Interlocked compare-exchange): stale OLDER completions
  are dropped, never overwrite a newer LatestFrame (mirror of 1742 tear)
- FrameRingBuffer.Rent/ConsumeAllocations now lock; downscale row scratch is
  per-conversion locals
- Good Dog test PublishGate_TryPublish_OnlyStrictlyNewerWins; 296/296 green,
  0 warnings; docs cited Microsoft screen-capture page + libyuv fixed-point.

Local only, no push.
This commit is contained in:
2026-09-14 18:40:20 -07:00
parent 71932b9756
commit b37b8a30f9
7 changed files with 238 additions and 114 deletions
@@ -14,6 +14,22 @@ namespace ytLive.Tests;
/// </summary>
public class ScreenCaptureFrameSourceTests
{
[Fact]
public void PublishGate_TryPublish_OnlyStrictlyNewerWins()
{
// Slice 17: overlapping conversions can finish out of order; the monotonic
// gate must keep a slow OLDER completion from overwriting a newer LatestFrame.
var gate = new MonotonicGate();
Assert.True(gate.TryPublish(1));
Assert.True(gate.TryPublish(2));
Assert.False(gate.TryPublish(1)); // stale replay (older than 2)
Assert.True(gate.TryPublish(3));
Assert.False(gate.TryPublish(3)); // duplicate — never accepted twice
Assert.False(gate.TryPublish(2)); // late older completion
Assert.False(gate.TryPublish(long.MinValue));
Assert.True(gate.TryPublish(long.MaxValue));
}
[Fact]
public void Ring_NoLap_ReusesOnlyAfterRedLineRents()
{