using System; using System.Collections.Generic; using Azimuth; using UnitTestSharp; using Blacklight.Core; using Blacklight.Core.Cameras; using Blacklight.Core.Drawables; using Blacklight.GPUBridge; namespace Blacklight.GPUBridge.UnitTests { public class BatchPoolTests : TestFixture { // A fake batch that reports "full" after a set number of primitives, letting the pool's spill/reuse logic be // driven deterministically without packing thousands of real vertices. It also records how the pool drives // it: submits, releases, and the viewport/camera it was frame-reset with. public class BatchMock : IPrimitiveBatch { private readonly int _capacity; public int Queued = 0; public int SubmitCount = 0; public int ReleaseCount = 0; public Blacklight.Core.Cameras.Viewport LastViewport = null; public Camera LastCamera = null; public bool Reset = false; public BatchMock(int capacity) { _capacity = capacity; } private bool TryQueue() { if (Queued >= _capacity) { return false; } Queued++; return true; } public void FrameReset(Viewport viewport, Camera camera) { Queued = 0; LastViewport = viewport; LastCamera = camera; Reset = true; } public bool QueuePrimitive(in Rectangle primitive, in AffineMatrix transform) => TryQueue(); public bool QueuePrimitive(in Ellipse primitive, in AffineMatrix transform) => TryQueue(); public bool QueuePrimitive(in BorderTriangle primitive, in AffineMatrix transform) => TryQueue(); public bool QueuePrimitive(in QuadraticBezierTriangle primitive, in AffineMatrix transform) => TryQueue(); public bool QueuePrimitive(Sprite sprite, AffineMatrix transform) => TryQueue(); public bool QueuePrimitive(T primitive, AffineMatrix transform) where T : IDrawPrimitive => TryQueue(); public void SubmitDrawCalls() { SubmitCount++; } public void ReleaseResources() { ReleaseCount++; } } public class BatchSourceMock : IBatchFactory { private readonly int _batchCapacity; public readonly List Created = new List(); public int BeginSubmitCount = 0; public int ResetCount = 0; public int ReleaseResourcesCount = 0; // batchCapacity is how many primitives each batch this backend hands out accepts before reporting full. public BatchSourceMock(int batchCapacity = int.MaxValue) { _batchCapacity = batchCapacity; } public IPrimitiveBatch CreateBatch() { var batch = new BatchMock(_batchCapacity); Created.Add(batch); return batch; } public void BeginSubmit() { BeginSubmitCount++; } public void Reset() { ResetCount++; } public void ReleaseResources() { ReleaseResourcesCount++; } } // A trivial IDrawPrimitive that matches none of the explicit QueuePrimitive overloads, so it routes through // the generic QueuePrimitive one. public struct PrimitiveMock : IDrawPrimitive { public Color ShellColor { get; set; } public Color InsideColor { get; set; } public Scalar ShellPixelThickness { get; set; } public Scalar ShellModelSpaceThickness { get; set; } public bool SmoothBorder { get; set; } public Texture ShellFillTexture { get; set; } public Texture InsideFillTexture { get; set; } } // The tryBatch predicate the FindWorkableBatch tests drive it with. private static bool AcceptOne(IPrimitiveBatch batch) { return batch.QueuePrimitive(new Rectangle(), AffineMatrix.Identity); } // Queues two primitives; with a batchCapacity-1 backend that fills the first batch and spills into a second. private static void SpillToTwoBatches(BatchPool pool) { pool.QueuePrimitive(new Rectangle(), AffineMatrix.Identity); pool.QueuePrimitive(new Rectangle(), AffineMatrix.Identity); } public class IsFullTests : TestFixture { public void IsAlwaysFalse() { CheckFalse(new BatchPool(new BatchSourceMock()).IsFull()); } } public class QueueToBatchTests : TestFixture { public void ReusesOneBatchWhileItKeepsAccepting() { var backend = new BatchSourceMock(); var pool = new BatchPool(backend); BatchPool.QueueToBatch(pool, AcceptOne); BatchPool.QueueToBatch(pool, AcceptOne); BatchPool.QueueToBatch(pool, AcceptOne); CheckEqual(1, backend.Created.Count); CheckEqual(3, backend.Created[0].Queued); } public void FetchesANewBatchWhenTheCurrentReportsFull() { var backend = new BatchSourceMock(batchCapacity: 1); var pool = new BatchPool(backend); BatchPool.QueueToBatch(pool, AcceptOne); BatchPool.QueueToBatch(pool, AcceptOne); CheckEqual(2, backend.Created.Count); CheckEqual(1, backend.Created[0].Queued); CheckEqual(1, backend.Created[1].Queued); } public void AfterFrameResetReusesTheSpareBatchInsteadOfAllocating() { var backend = new BatchSourceMock(batchCapacity: 1); var pool = new BatchPool(backend); BatchPool.QueueToBatch(pool, AcceptOne); BatchPool.QueueToBatch(pool, AcceptOne); pool.FrameReset(viewport: null, camera: null); BatchPool.QueueToBatch(pool, AcceptOne); BatchPool.QueueToBatch(pool, AcceptOne); CheckEqual(2, backend.Created.Count); } public void FrameResetsTheBatchItFetches() { var backend = new BatchSourceMock(); var pool = new BatchPool(backend); BatchPool.QueueToBatch(pool, AcceptOne); Check(backend.Created[0].Reset); } public void ThrowsWhenAFreshlyFetchedBatchStillReportsFull() { var backend = new BatchSourceMock(batchCapacity: 0); var pool = new BatchPool(backend); CheckThrow(typeof(Exception)); BatchPool.QueueToBatch(pool, AcceptOne); } } public class FrameResetTests : TestFixture { public void ResetsBatches() { var backend = new BatchSourceMock(batchCapacity: 1); var pool = new BatchPool(backend); SpillToTwoBatches(pool); pool.FrameReset(viewport: null, camera: null); CheckEqual(2, backend.Created.Count); Check(backend.Created[0].Reset); Check(backend.Created[1].Reset); } public void SetsCameraAndViewport() { var backend = new BatchSourceMock(batchCapacity: 1); var pool = new BatchPool(backend); SpillToTwoBatches(pool); var viewport = new Viewport(3, 4); var camera = new MicroscopeCamera(viewport); pool.FrameReset(viewport, camera); CheckEqual(2, backend.Created.Count); CheckEqual(viewport, backend.Created[0].LastViewport); CheckEqual(camera, backend.Created[0].LastCamera); CheckEqual(viewport, backend.Created[1].LastViewport); CheckEqual(camera, backend.Created[1].LastCamera); } public void ReusesAllocatedBatchesAndClearsTheirContents() { var backend = new BatchSourceMock(batchCapacity: 1); var pool = new BatchPool(backend); SpillToTwoBatches(pool); pool.FrameReset(null, null); pool.QueuePrimitive(new Rectangle(), AffineMatrix.Identity); CheckEqual(2, backend.Created.Count); CheckEqual(1, backend.Created[0].Queued); CheckEqual(0, backend.Created[1].Queued); } } public class QueuePrimitiveTests : TestFixture { public void Rectangle_QueuesToTheBatch() { var backend = new BatchSourceMock(); var pool = new BatchPool(backend); CheckTrue(pool.QueuePrimitive(new Rectangle(), AffineMatrix.Identity)); CheckEqual(1, backend.Created[0].Queued); } public void Ellipse_QueuesToTheBatch() { var backend = new BatchSourceMock(); var pool = new BatchPool(backend); CheckTrue(pool.QueuePrimitive(new Ellipse(), AffineMatrix.Identity)); CheckEqual(1, backend.Created[0].Queued); } public void BorderTriangle_QueuesToTheBatch() { var backend = new BatchSourceMock(); var pool = new BatchPool(backend); CheckTrue(pool.QueuePrimitive(new BorderTriangle(), AffineMatrix.Identity)); CheckEqual(1, backend.Created[0].Queued); } public void QuadraticBezierTriangle_QueuesToTheBatch() { var backend = new BatchSourceMock(); var pool = new BatchPool(backend); CheckTrue(pool.QueuePrimitive(new QuadraticBezierTriangle(), AffineMatrix.Identity)); CheckEqual(1, backend.Created[0].Queued); } public void Sprite_QueuesToTheBatch() { var backend = new BatchSourceMock(); var pool = new BatchPool(backend); CheckTrue(pool.QueuePrimitive(new Sprite(), AffineMatrix.Identity)); CheckEqual(1, backend.Created[0].Queued); } public void GenericPrimitive_QueuesToTheBatch() { var backend = new BatchSourceMock(); var pool = new BatchPool(backend); CheckTrue(pool.QueuePrimitive(new PrimitiveMock(), AffineMatrix.Identity)); CheckEqual(1, backend.Created[0].Queued); } } public class SubmitDrawCallsTests : TestFixture { public void NoBatchesSubmittedIfNoPrimitives() { var backend = new BatchSourceMock(batchCapacity: 1); var pool = new BatchPool(backend); SpillToTwoBatches(pool); pool.FrameReset(viewport: null, camera: null); pool.SubmitDrawCalls(); CheckEqual(0, backend.Created[0].SubmitCount); CheckEqual(0, backend.Created[1].SubmitCount); } public void SubmitsOnlyTheActiveBatches() { var backend = new BatchSourceMock(batchCapacity: 1); var pool = new BatchPool(backend); SpillToTwoBatches(pool); pool.FrameReset(viewport: null, camera: null); pool.QueuePrimitive(new Rectangle(), AffineMatrix.Identity); pool.SubmitDrawCalls(); CheckEqual(1, backend.Created[0].SubmitCount); CheckEqual(0, backend.Created[1].SubmitCount); } } public class ReleaseResourcesTests : TestFixture { public void ReleasesEveryAllocatedBatchEvenWhenInactive() { var backend = new BatchSourceMock(batchCapacity: 1); var pool = new BatchPool(backend); SpillToTwoBatches(pool); pool.FrameReset(viewport: null, camera: null); pool.ReleaseResources(); CheckEqual(1, backend.Created[0].ReleaseCount); CheckEqual(1, backend.Created[1].ReleaseCount); } public void EmptiesThePoolSoTheNextQueueAllocatesFresh() { var backend = new BatchSourceMock(batchCapacity: 1); var pool = new BatchPool(backend); SpillToTwoBatches(pool); pool.ReleaseResources(); pool.QueuePrimitive(new Rectangle(), AffineMatrix.Identity); CheckEqual(3, backend.Created.Count); } } } }