Files
LlamaCasty/ytLive.Tests/ScreenCaptureFrameSourceTests.cs
T
gramps 71932b9756 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.
2026-09-14 18:18:34 -07:00

56 lines
2.3 KiB
C#

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());
}
}