Perf: dedicated strip-parallel composite (4 threads, BelowNormal) — UNIT 5
A composite splits into four horizontal row strips across four DEDICATED background threads; the calling thread takes a strip too (so four workers never mean five-way oversubscription), and anything under 64 rows runs inline. BlitContentRaw, BlitBakedElement and BlitOverlay now fan out. Output is byte-identical — strips already partitioned by row. The demotion deliberately does NOT come from Parallel.For. The pool is shared, so Thread.CurrentThread.Priority = BelowNormal inside it leaks into every unrelated work item that thread later runs — a full test run after trying it broke AudioPipelineTests.Mix_HonorsProviderGains_AndGameMute_ KillsTheLoopback. Established guidance agrees: use your own producer/consumer queue with your own Threads and set their priority yourself (https://stackoverflow.com/questions/524483/net-backgroundworker-objects-thread-priority, https://stackoverflow.com/questions/1578599/c-sharp-lower-thread-priority-in-thread-pool). Helpers/StripPool.cs is that queue. Four threads is the measured sweet spot: fewer leaves render over budget, more steals cycles from the game and the encoder and made render worse. The scaled blit's column LUT moves from a shared static buffer to ArrayPool<float>.Shared with a finally — a single static buffer is a data race the moment strips run in parallel. Gate: SceneCompositorTests.StripParallel_RepeatedRenders_AreByteIdentical — 8 renders of a scaled round webcam + mirrored image + master-sized flash over a short backdrop must be byte-for-byte identical, and must differ from a backdrop-only render so it cannot pass vacuously. It caught a loop-variable capture (new Thread(() => Worker(i)) saw i == Workers) that crashed the test host on the first run.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
using Xunit;
|
||||
using Xunit;
|
||||
using ytLive.Models;
|
||||
using ytLive.Services;
|
||||
using ytLive.Services.Compositor;
|
||||
@@ -28,7 +28,7 @@ public class SceneCompositorTests
|
||||
}
|
||||
|
||||
/// <summary>Left half = leftColor, right half = rightColor.</summary>
|
||||
private static VideoFrame Split(int w, int h, byte lr, byte lg, byte lb, byte rr, byte rg, byte rb)
|
||||
public static VideoFrame Split(int w, int h, byte lr, byte lg, byte lb, byte rr, byte rg, byte rb)
|
||||
{
|
||||
var pixels = new byte[w * h * 4];
|
||||
var half = w / 2;
|
||||
@@ -509,6 +509,73 @@ public class SceneCompositorTests
|
||||
AssertColor(bottom, 0, 1039, 255, 0, 0); // background just above the bar
|
||||
AssertColor(bottom, 960, 540, 255, 0, 0); // bar region moved away from center
|
||||
}
|
||||
|
||||
/// <summary>Unit 5 gate — the compositor now fans a composite out across four dedicated
|
||||
/// BelowNormal strip workers (Helpers/StripPool), and the column LUT that the scaled blit
|
||||
/// samples is read by those workers while the call is still in flight. Any strip writing
|
||||
/// outside its own rows, or a LUT buffer shared between concurrent composites, corrupts a
|
||||
/// handful of pixels NON-deterministICALLY, so comparing one render to the next is the
|
||||
/// probe: the same scene must come out byte-for-byte identical every time. The canvas is
|
||||
/// 240 rows tall (past the fan-out threshold) and carries a scaled round mirrored webcam, a
|
||||
/// scaled image layer and a master-sized flash, so the three fanned-out paths
|
||||
/// (BlitContentRaw, BlitBakedElement, BlitOverlay) all run. The color probe keeps the
|
||||
/// test from passing vacuously on an all-black frame.</summary>
|
||||
[Fact]
|
||||
public void StripParallel_RepeatedRenders_AreByteIdentical()
|
||||
{
|
||||
const int W = 320, H = 240;
|
||||
var backdrop = SceneCompositorTests.Solid(W, 200, 12, 34, 56); // shorter than the canvas: prefill bands
|
||||
var webcamFrame = SceneCompositorTests.Split(64, 48, 255, 0, 0, 0, 255, 0);
|
||||
var imageFrame = SceneCompositorTests.Split(48, 48, 0, 0, 255, 255, 255, 0);
|
||||
var flash = new VideoFrame(W, H, new byte[W * H * 4]);
|
||||
for (var i = 0; i < flash.BgraPixels.Length; i += 4)
|
||||
{
|
||||
flash.BgraPixels[i] = 200;
|
||||
flash.BgraPixels[i + 1] = 200;
|
||||
flash.BgraPixels[i + 2] = 200;
|
||||
flash.BgraPixels[i + 3] = 32;
|
||||
}
|
||||
|
||||
var background = new Source { Type = SourceType.DisplayCapture, IsBackground = true, CaptureKey = "monitor:0" };
|
||||
var webcam = new WebcamSceneConfig { X = 20, Y = 30, Width = 200, Height = 120, ClipShape = ClipShape.Round, Opacity = 0.8 };
|
||||
var image = new Source { Type = SourceType.Image, X = 180, Y = 140, Width = 100, Height = 70, IsMirrored = true };
|
||||
var scene = new Scene { Name = "Live" };
|
||||
scene.Elements.Add(background);
|
||||
scene.Elements.Add(webcam);
|
||||
scene.Elements.Add(image);
|
||||
|
||||
VideoFrame? FrameFor(SceneElement e) => e switch
|
||||
{
|
||||
Source { IsBackground: true } => backdrop,
|
||||
WebcamSceneConfig => webcamFrame,
|
||||
Source { Type: SourceType.Image } => imageFrame,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
var options = new CompositorOptions
|
||||
{
|
||||
SourceRectX = 0, SourceRectY = 0, SourceRectWidth = W, SourceRectHeight = H,
|
||||
OutputWidth = W, OutputHeight = H,
|
||||
};
|
||||
|
||||
var compositor = new SceneCompositor();
|
||||
byte[]? reference = null;
|
||||
for (var run = 0; run < 8; run++)
|
||||
{
|
||||
var output = compositor.Render(scene, FrameFor, flash, options);
|
||||
if (reference == null) reference = output.BgraPixels.ToArray();
|
||||
else Assert.True(reference.AsSpan().SequenceEqual(output.BgraPixels),
|
||||
$"run {run} diverged from run 0 — strip writes or the shared column LUT raced");
|
||||
}
|
||||
|
||||
// Not vacuous: drop the two layers and the render must change, so the byte-identity
|
||||
// above is comparing a real composite and not an untouched backdrop.
|
||||
var backdropOnly = new Scene { Name = "Live" };
|
||||
backdropOnly.Elements.Add(background);
|
||||
var baseline = compositor.Render(backdropOnly, FrameFor, flash, options).BgraPixels;
|
||||
Assert.False(baseline.AsSpan().SequenceEqual(reference!),
|
||||
"scene rendered identical to the backdrop alone — the test would prove nothing");
|
||||
}
|
||||
}
|
||||
|
||||
public class StretchMathTests
|
||||
@@ -549,3 +616,4 @@ public class StretchMathTests
|
||||
Assert.Equal(255, scaled.BgraPixels[i + 1]); // solid green survives the scale
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user