perf(compositor): slice 5 — paste cache for non-opaque layers (take-7/8 data)
The stamped build settled what slices 3-4 could not: chat cache works (resolve ~0.0ms) but render stayed 26-27ms -> 124-135/300. The cost was the compositor re-rasterizing EVERY layer every tick: this Live scene re-samples chat (159k) + web widget (271k) + image (95k) + cam (156k) ~ 680k px @ ~38ns — for layers whose pixels do not change between chat/web/cam updates. OBS shape: cache the surface, paste per tick. BlitCachedLayer rasterizes a non-opaque layer ONCE into an element-space, transparent-based frame keyed by (source-array identity, src W/H, ceil'd dst rect, round, mirror), then pastes: integer position, row alpha-blend, opacity applied at paste. Producers hand out fresh immutable arrays -> array-identity keys cannot serve stale content; dict bounded (48, clears whole). Drag/opacity live in paste params, not keys, so editing stops triggering resamples too. Opaque backdrop keeps the memcpy path; the webcam keeps the direct path via its IsOpaque flag (revisit if take 9 is borderline). ONE integration test: PasteCache_RepeatRender_IsByteIdentical_And_ContentChange- Propagates (byte-exact raster-vs-paste incl. round-clip margins, new-array propagation); existing pixel suite guards sampler semantics. 85/85 across compositor/pump/chat/capture/session classes, clean build 0 warnings. Also: BuildStampTests.cs was written last commit but never staged — its own scope-check slip, added here (the run had used the on-disk file; tracked now). Docs same commit: ai.md slice 5 + stale 'general path only 130k' claim corrected, TASKS.md take-9 gate, MyMistakes recipe (prove the stage; a fix that doesn't move the stat wasn't the bottleneck), HANDOFF. take 9 expectation: 300/300, render <= ~8ms -> saga closes, Unit B starts.
This commit is contained in:
@@ -154,6 +154,63 @@ public class SceneCompositorTests
|
||||
Assert.DoesNotContain((byte)0xFD, output.BgraPixels); // full cover — nothing stale
|
||||
}
|
||||
|
||||
/// <summary>The ONE integration test for the paste cache (take-8): a second render of
|
||||
/// the SAME frames must produce byte-identical output (the hit path pastes a cached
|
||||
/// element-space raster — position/opacity/margins/round-clip must survive it), and a
|
||||
/// NEW source array must propagate (the cache keys on frame identity, so a stale
|
||||
/// raster can never outlive its content). Covers the margins the FullScene probe
|
||||
/// pixels don't: outside-the-circle alpha and the black pre-fill interaction.</summary>
|
||||
[Fact]
|
||||
public void PasteCache_RepeatRender_IsByteIdentical_And_ContentChangePropagates()
|
||||
{
|
||||
var red = Solid(1920, 1080, 255, 0, 0);
|
||||
var green = Solid(1280, 720, 0, 255, 0);
|
||||
var blue = Solid(1280, 720, 0, 0, 255);
|
||||
|
||||
var background = new Source { Type = SourceType.DisplayCapture, IsBackground = true, CaptureKey = "monitor:0" };
|
||||
var roundWebcam = new WebcamSceneConfig { X = 100, Y = 100, Width = 300, Height = 300, ClipShape = ClipShape.Round };
|
||||
var scene = new Scene { Name = "Live" };
|
||||
scene.Elements.Add(background);
|
||||
scene.Elements.Add(roundWebcam);
|
||||
|
||||
VideoFrame? FrameFor(SceneElement e) => e switch
|
||||
{
|
||||
WebcamSceneConfig => green,
|
||||
Source { IsBackground: true } => red,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
var options = new CompositorOptions
|
||||
{
|
||||
SourceRectX = 0, SourceRectY = 0, SourceRectWidth = 1920, SourceRectHeight = 1080,
|
||||
OutputWidth = 1920, OutputHeight = 1080,
|
||||
};
|
||||
|
||||
var compositor = new SceneCompositor();
|
||||
var first = compositor.Render(scene, FrameFor, null, options);
|
||||
var second = compositor.Render(scene, FrameFor, null, options); // paste-cache hit
|
||||
Assert.Equal(first.BgraPixels, second.BgraPixels);
|
||||
|
||||
// margins: inside the circle = webcam, outside = backdrop (both renders)
|
||||
foreach (var f in new[] { first, second })
|
||||
{
|
||||
AssertColor(f, 250, 250, 0, 255, 0);
|
||||
AssertColor(f, 101, 101, 255, 0, 0);
|
||||
Assert.Equal((byte)255, f.BgraPixels[(250 * 1920 + 250) * 4 + 3]); // alpha stays opaque
|
||||
}
|
||||
|
||||
// Content change (new array, same geometry) must show through the cache.
|
||||
VideoFrame? FrameForBlue(SceneElement e) => e switch
|
||||
{
|
||||
WebcamSceneConfig => blue,
|
||||
Source { IsBackground: true } => red,
|
||||
_ => null,
|
||||
};
|
||||
var third = compositor.Render(scene, FrameForBlue, null, options);
|
||||
AssertColor(third, 250, 250, 0, 0, 255);
|
||||
AssertColor(third, 101, 101, 255, 0, 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_VerticalTier_Outputs_1080x1920_From_The_Center_Crop()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user