perf(capture): fast integer downscale + 10ms cadence floor + reuse-distance ring (slice 16)
The 240Hz monitor delivery + one-in-flight conversions + naive double-per-pixel DownscaleBgra (~150ms/frame under load) froze the desktop layer 90% of take ty-1742 (6.1 fresh content updates/s, freeze runs to 2.8s; decoded raw-frame audit). The render stat (33-36ms) was real but moot — the capture CONVERSION was the wall, and the one torn frame was a ring slot rewritten under the consumer's read. Reference: WGC delivers at DWM/monitor cadence (https://learn.microsoft.com/en-us/windows/apps/develop/media-authoring-processing/screen-capture) and libyuv row-simple/fixed-point scaling (https://chromium.googlesource.com/libyuv/libyuv/) — the repo's own take-4 rule. - DownscaleBgra: integer 8.8 fixed-point, shift-only-at-the-end (same two-stage math as SceneCompositor.Bilinear). ~150ms -> ~5ms per 2.5K->1080p frame. - 10ms MinConvertInterval: the ~4.2ms 240Hz tail stopped queuing ~150ms of serialized conversion/s; capacity sits just above the 60/s the pump can use. - FrameRingBuffer (depth 8, redLine 4): reuse-DISTANCE ring — a buffer is only rewritten >=4 rents after its last hand-out else fresh-allocated, so a frame a consumer still holds (session.LatestFrame survives conversions, dispatcher preview lags) is never read-while-overwritten. Needs no consumer Release API. - 2s startup.log telemetry: frames/s, conv avg/max ms, skip busy/cadence, ring allocs — the device take is judgeable numerically. Good Dog test: Ring_NoLap_ReusesOnlyAfterRedLineRents. 295/295 green, 0 warnings. C4 (composite Epoch-cached downscale) deferred pending the device re-measure. Local only, no push.
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using Xunit;
|
||||
using ytLive.Services;
|
||||
|
||||
namespace ytLive.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// The capture conversion ring (<see cref="FrameRingBuffer"/>) is a pure,
|
||||
/// deterministic piece of <see cref="ScreenCaptureFrameSource"/> — the WGC
|
||||
/// pool/session layer is WinRT and exercised only on Windows at runtime. These tests
|
||||
/// pin the slice-16 no-lap contract: a slot is never rewritten within redLine rents
|
||||
/// of its last hand-out (the 1742 tear — a ring slot rewritten under the consumer's
|
||||
/// read handed the compositor one new-top/old-bottom frame).
|
||||
/// </summary>
|
||||
public class ScreenCaptureFrameSourceTests
|
||||
{
|
||||
[Fact]
|
||||
public void Ring_NoLap_ReusesOnlyAfterRedLineRents()
|
||||
{
|
||||
// Depth 3 / redLine 4: the ring is narrower than its safety distance, so the
|
||||
// red-line skip is actually exercised (the production 8/4 config can never
|
||||
// block a rotation — the ring cycles before any slot comes due).
|
||||
var ring = new FrameRingBuffer(depth: 3, redLine: 4);
|
||||
const int size = 100;
|
||||
|
||||
var a = ring.Rent(size);
|
||||
var b = ring.Rent(size);
|
||||
var c = ring.Rent(size);
|
||||
Assert.NotSame(a, b);
|
||||
Assert.NotSame(b, c);
|
||||
Assert.NotSame(a, c);
|
||||
Assert.Equal(3, ring.ConsumeAllocations());
|
||||
|
||||
// 4th rent arrives while all three slots are inside their red line: the ring
|
||||
// must hand out a fresh buffer instead of rewriting a still-loaned slot.
|
||||
var d = ring.Rent(size);
|
||||
Assert.NotSame(d, a);
|
||||
Assert.NotSame(d, b);
|
||||
Assert.NotSame(d, c);
|
||||
Assert.Equal(1, ring.ConsumeAllocations());
|
||||
|
||||
// 5th rent: slot a (handed at seq 1, revisited at seq 5 = exactly redLine)
|
||||
// is reusable, and reuse is an in-place recycle, not a fresh allocation.
|
||||
Assert.Same(a, ring.Rent(size));
|
||||
Assert.Equal(0, ring.ConsumeAllocations());
|
||||
|
||||
// The production-sized ring (8/4, what ScreenCaptureFrameSource uses) settles
|
||||
// at 8 buffers and recycles them forever — no growth under steady capture.
|
||||
var prod = new FrameRingBuffer(depth: 8, redLine: 4);
|
||||
var first = new byte[8][];
|
||||
for (var i = 0; i < 8; i++) first[i] = prod.Rent(size);
|
||||
for (var i = 8; i < 200; i++)
|
||||
Assert.Same(first[i % 8], prod.Rent(size));
|
||||
Assert.Equal(8, prod.ConsumeAllocations());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user