using System; using System.Linq; using Azimuth; using UnitTestSharp; using Blacklight.Core; using Blacklight.Core.Drawables; using Blacklight.GPUBridge; namespace Blacklight.GPUBridge.UnitTests { public class PrimitiveBatchTests : TestFixture { // A real batch with the device half stubbed out, so the packing is exercised without a GPU. public class TestBatch : PrimitiveBatch { public override void SubmitDrawCalls() { } } private static Sprite SpriteWithChannels( ColorChannel red, ColorChannel green, ColorChannel blue, ColorChannel alpha) { return new Sprite { Source = new SpriteSheet.Source { RedChannel = red, GreenChannel = green, BlueChannel = blue, AlphaChannel = alpha, }, }; } private static Sprite SpriteWithTiling(TilingBehavior x, TilingBehavior y) { return new Sprite { TilingBehaviorX = x, TilingBehaviorY = y, }; } private static Sprite SpriteWithTextures(Texture source, Texture tint) { return new Sprite { Source = new SpriteSheet.Source { SpriteSheet = new SpriteSheet { Texture = source } }, TintTexture = tint, }; } public class ReleaseResourcesTests : TestFixture { public void ClearsTextureSlots() { var batch = new TestBatch(); batch.TrySetTextures( new Rectangle { ShellFillTexture = new TextureSlotManagerTests.TestTexture() }, out _, out _); CheckEqual(1, batch.TextureCount); batch.ReleaseResources(); CheckEqual(0, batch.TextureCount); } public void ClearsVertexAndIndexCounts() { var batch = new TestBatch(); batch.QueuePrimitive(new Rectangle(), AffineMatrix.Identity); batch.ReleaseResources(); CheckEqual(0, batch.VertexCount); CheckEqual(0, batch.IndexCount); } } public class QueuePrimitiveTests : TestFixture { static readonly AffineMatrix Transform = new AffineMatrix(2, 3, 4, 5, 6, 7); static readonly Color Shell = new Color(1, 0, 0); static readonly Color Inside = new Color(0, 0, 1); const float PixelThickness = 3f; const float ModelThickness = 7f; public class TestSpriteSource : ISpriteSource { public Texture Texture { get; set; } public ColorChannel AlphaChannel { get; set; } public ColorChannel RedChannel { get; set; } public ColorChannel GreenChannel { get; set; } public ColorChannel BlueChannel { get; set; } public System.Drawing.Point Origin { get; set; } public System.Drawing.Size Size { get; set; } } // A reference-type IDrawPrimitive the generic QueuePrimitive doesn't recognize, to reach its final // "unsupported shape" throw. public class UnsupportedPrimitive : 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; } } // Rectangle and Ellipse share WriteQuadIndexAndVertexData verbatim; only the shape id differs, so both // verify every field the quad path packs through this one helper. [IgnoreTest] void CheckQuadPrimitivePacked(TestBatch batch, float shapeId) { CheckEqual(4, batch.VertexCount); CheckEqual(6, batch.IndexCount); CheckEqual(new ushort[] { 0, 1, 2, 0, 2, 3 }, batch.PackedIndices.Take(6).ToArray()); for (int i = 0; i < 4; ++i) { var vertex = batch.PackedVertices[i]; CheckEqual(Shell.ToArgb(), vertex.BorderColor); CheckEqual(Inside.ToArgb(), vertex.InsideColor); CheckEqual(PixelThickness, vertex.BorderStyle.X); CheckEqual(ModelThickness, vertex.BorderStyle.Y); CheckEqual(1f, vertex.BorderStyle.Z); CheckEqual(shapeId, vertex.BorderStyle.W); CheckEqual(-float.MaxValue, vertex.ClipWindowLS.X); CheckEqual(-float.MaxValue, vertex.ClipWindowLS.Y); CheckEqual(float.MaxValue, vertex.ClipWindowLS.Z); CheckEqual(float.MaxValue, vertex.ClipWindowLS.W); CheckEqual(2f, vertex.ScaleAndOrientation.X); CheckEqual(3f, vertex.ScaleAndOrientation.Y); CheckEqual(5f, vertex.ScaleAndOrientation.Z); CheckEqual(6f, vertex.ScaleAndOrientation.W); CheckEqual(4f, vertex.TranslationAndVertexPosition.X); CheckEqual(7f, vertex.TranslationAndVertexPosition.Y); CheckEqual(0f, vertex.TranslationAndVertexPosition.Z); CheckEqual(0f, vertex.TranslationAndVertexPosition.W); CheckEqual((float)i, vertex.UserData_1.X); CheckEqual(0f, vertex.UserData_1.Y); CheckEqual(0f, vertex.UserData_1.Z); CheckEqual(0f, vertex.UserData_1.W); CheckEqual(0f, vertex.SpriteDestinationRect.X); CheckEqual(0f, vertex.SpriteDestinationRect.Y); CheckEqual(0f, vertex.SpriteDestinationRect.Z); CheckEqual(0f, vertex.SpriteDestinationRect.W); CheckEqual(0, vertex.BorderTextureSlot); CheckEqual(1, vertex.InteriorTextureSlot); CheckEqual(TextureSlotManager.InvalidTextureSlot, vertex.SpriteTextureSlot); } } public void Rectangle_PacksEveryVertexFieldAsABox() { var batch = new TestBatch(); CheckTrue(batch.QueuePrimitive( new Rectangle { ShellColor = Shell, InsideColor = Inside, ShellPixelThickness = PixelThickness, ShellModelSpaceThickness = ModelThickness, SmoothBorder = true, ShellFillTexture = new TextureSlotManagerTests.TestTexture(), InsideFillTexture = new TextureSlotManagerTests.TestTexture(), }, Transform)); CheckQuadPrimitivePacked(batch, shapeId: 1f); } public void Ellipse_PacksEveryVertexFieldAsACircle() { var batch = new TestBatch(); CheckTrue(batch.QueuePrimitive( new Ellipse { ShellColor = Shell, InsideColor = Inside, ShellPixelThickness = PixelThickness, ShellModelSpaceThickness = ModelThickness, SmoothBorder = true, ShellFillTexture = new TextureSlotManagerTests.TestTexture(), InsideFillTexture = new TextureSlotManagerTests.TestTexture(), }, Transform)); CheckQuadPrimitivePacked(batch, shapeId: 2f); } public void QuadraticBezierTriangle_PacksThreeVerticesWithUVsAndTheInvertedFlag() { var triangle = new QuadraticBezierTriangle { ShellColor = Shell, InsideColor = Inside, ShellPixelThickness = PixelThickness, ShellModelSpaceThickness = ModelThickness, SmoothBorder = true, ShellFillTexture = new TextureSlotManagerTests.TestTexture(), InsideFillTexture = new TextureSlotManagerTests.TestTexture(), VertexA = new Vector(10, 20), VertexB = new Vector(30, 40), VertexC = new Vector(50, 60), Inverted = true, }; var batch = new TestBatch(); CheckTrue(batch.QueuePrimitive(triangle, Transform)); CheckEqual(3, batch.VertexCount); CheckEqual(3, batch.IndexCount); CheckEqual(new ushort[] { 0, 1, 2 }, batch.PackedIndices.Take(3).ToArray()); var vertices = triangle.Vertices; var uvs = PrimitiveBatch.QuadraticBezierCurveUVCoordinates; for (int i = 0; i < 3; ++i) { var vertex = batch.PackedVertices[i]; CheckEqual(Shell.ToArgb(), vertex.BorderColor); CheckEqual(Inside.ToArgb(), vertex.InsideColor); CheckEqual(PixelThickness, vertex.BorderStyle.X); CheckEqual(ModelThickness, vertex.BorderStyle.Y); CheckEqual(1f, vertex.BorderStyle.Z); CheckEqual(5f, vertex.BorderStyle.W); CheckEqual(-float.MaxValue, vertex.ClipWindowLS.X); CheckEqual(float.MaxValue, vertex.ClipWindowLS.Z); CheckEqual(2f, vertex.ScaleAndOrientation.X); CheckEqual(3f, vertex.ScaleAndOrientation.Y); CheckEqual(5f, vertex.ScaleAndOrientation.Z); CheckEqual(6f, vertex.ScaleAndOrientation.W); CheckEqual(4f, vertex.TranslationAndVertexPosition.X); CheckEqual(7f, vertex.TranslationAndVertexPosition.Y); CheckEqual((float)vertices[i].X, vertex.TranslationAndVertexPosition.Z); CheckEqual((float)vertices[i].Y, vertex.TranslationAndVertexPosition.W); CheckEqual((float)uvs[i].X, vertex.UserData_1.X); CheckEqual((float)uvs[i].Y, vertex.UserData_1.Y); CheckEqual(-1f, vertex.UserData_1.Z); CheckEqual(0f, vertex.UserData_1.W); CheckEqual(0f, vertex.SpriteDestinationRect.X); CheckEqual(0, vertex.BorderTextureSlot); CheckEqual(1, vertex.InteriorTextureSlot); CheckEqual(TextureSlotManager.InvalidTextureSlot, vertex.SpriteTextureSlot); } } public void Sprite_PacksEverySpriteFieldAcrossTheQuad() { var source = new TestSpriteSource { Texture = new TextureSlotManagerTests.TestTexture(), Origin = new System.Drawing.Point(11, 22), Size = new System.Drawing.Size(33, 44), RedChannel = ColorChannel.A, GreenChannel = ColorChannel.B, BlueChannel = ColorChannel.G, AlphaChannel = ColorChannel.R, }; var tint = new TintColor(0.1, 0.2, 0.3, 0.4); var clip = Annulus.Rect.FromTwoPoints(new Vector(10, 20), new Vector(40, 60)); var sprite = new Sprite { Source = source, TintTexture = new TextureSlotManagerTests.TestTexture(), TintColor = tint, ClipWindow = clip, TilingBehaviorX = TilingBehavior.Tile, TilingBehaviorY = TilingBehavior.StretchAndTile, Destination = ignored => new SpriteDestination { min = new Vector(1, 2), max = new Vector(3, 4) }, }; var batch = new TestBatch(); CheckTrue(batch.QueuePrimitive(sprite, Transform)); CheckEqual(4, batch.VertexCount); CheckEqual(6, batch.IndexCount); CheckEqual(new ushort[] { 0, 1, 2, 0, 2, 3 }, batch.PackedIndices.Take(6).ToArray()); var min = new Vector(1, 2); var max = new Vector(3, 4); for (int i = 0; i < 4; ++i) { var vertex = batch.PackedVertices[i]; CheckEqual((float)source.Origin.X, vertex.BorderStyle.X); CheckEqual((float)source.Origin.Y, vertex.BorderStyle.Y); CheckEqual((float)PrimitiveBatch.PackTilingBehavior(sprite), vertex.BorderStyle.Z); CheckEqual(3f, vertex.BorderStyle.W); CheckEqual((float)i, vertex.UserData_1.X); CheckEqual((float)PrimitiveBatch.PackColorChannel(sprite), vertex.UserData_1.Y); CheckEqual((float)source.Size.Width, vertex.UserData_1.Z); CheckEqual((float)source.Size.Height, vertex.UserData_1.W); CheckEqual(1f, vertex.SpriteDestinationRect.X); CheckEqual(2f, vertex.SpriteDestinationRect.Y); CheckEqual(3f, vertex.SpriteDestinationRect.Z); CheckEqual(4f, vertex.SpriteDestinationRect.W); CheckEqual((float)clip.Left, vertex.ClipWindowLS.X); CheckEqual((float)clip.Top, vertex.ClipWindowLS.Y); CheckEqual((float)clip.Right, vertex.ClipWindowLS.Z); CheckEqual((float)clip.Bottom, vertex.ClipWindowLS.W); CheckEqual(2f, vertex.ScaleAndOrientation.X); CheckEqual(3f, vertex.ScaleAndOrientation.Y); CheckEqual(5f, vertex.ScaleAndOrientation.Z); CheckEqual(6f, vertex.ScaleAndOrientation.W); CheckEqual(4f, vertex.TranslationAndVertexPosition.X); CheckEqual(7f, vertex.TranslationAndVertexPosition.Y); var corner = PrimitiveBatch.GetVertex(i, min, max); CheckEqual((float)corner.X, vertex.TranslationAndVertexPosition.Z); CheckEqual((float)corner.Y, vertex.TranslationAndVertexPosition.W); CheckEqual(((Color)tint).ToArgb(), vertex.InsideColor); CheckEqual(Colors.Transparent.ToArgb(), vertex.BorderColor); CheckEqual(TextureSlotManager.InvalidTextureSlot, vertex.BorderTextureSlot); CheckEqual(1, vertex.InteriorTextureSlot); CheckEqual(0, vertex.SpriteTextureSlot); } } public void BorderTriangle_IsNotYetImplemented() { var batch = new TestBatch(); CheckThrow(typeof(NotImplementedException)); batch.QueuePrimitive(new BorderTriangle(), Transform); } // Triangulation output isn't hand-predictable, so expectations are read back from the polygon's own // _geoTris and BorderLine: the test proves the packer copies that geometry faithfully, not that the // triangulator is correct. public void BorderPolygon_PacksEachTriangulatedVertexAsATriangle() { var line = new Annulus.Line { From = new Vector(1, 2), To = new Vector(3, 4) }; var polygon = new BorderPolygon( new Vector[] { new Vector(0, 0), new Vector(4, 0), new Vector(0, 3) }, line) { ShellColor = Shell, InsideColor = Inside, ShellPixelThickness = PixelThickness, ShellModelSpaceThickness = ModelThickness, SmoothBorder = true, ShellFillTexture = new TextureSlotManagerTests.TestTexture(), InsideFillTexture = new TextureSlotManagerTests.TestTexture(), }; var batch = new TestBatch(); CheckTrue(batch.QueuePrimitive(polygon, Transform)); var geoTris = polygon._geoTris; var verts = geoTris[0].vertices; var borderLine = polygon.BorderLine; CheckEqual(verts.Count, batch.VertexCount); CheckEqual(3 * geoTris.Count, batch.IndexCount); for (int tri = 0; tri < geoTris.Count; ++tri) { for (int corner = 0; corner < 3; ++corner) { CheckEqual((ushort)geoTris[tri].indices[corner], batch.PackedIndices[tri * 3 + corner]); } } for (int i = 0; i < verts.Count; ++i) { var vertex = batch.PackedVertices[i]; CheckEqual(Shell.ToArgb(), vertex.BorderColor); CheckEqual(Inside.ToArgb(), vertex.InsideColor); CheckEqual(PixelThickness, vertex.BorderStyle.X); CheckEqual(ModelThickness, vertex.BorderStyle.Y); CheckEqual(1f, vertex.BorderStyle.Z); CheckEqual(4f, vertex.BorderStyle.W); CheckEqual(2f, vertex.ScaleAndOrientation.X); CheckEqual(3f, vertex.ScaleAndOrientation.Y); CheckEqual(5f, vertex.ScaleAndOrientation.Z); CheckEqual(6f, vertex.ScaleAndOrientation.W); CheckEqual(4f, vertex.TranslationAndVertexPosition.X); CheckEqual(7f, vertex.TranslationAndVertexPosition.Y); CheckEqual((float)verts[i].X, vertex.TranslationAndVertexPosition.Z); CheckEqual((float)verts[i].Y, vertex.TranslationAndVertexPosition.W); CheckEqual((float)borderLine.From.X, vertex.UserData_1.X); CheckEqual((float)borderLine.From.Y, vertex.UserData_1.Y); CheckEqual((float)borderLine.To.X, vertex.UserData_1.Z); CheckEqual((float)borderLine.To.Y, vertex.UserData_1.W); CheckEqual(0f, vertex.SpriteDestinationRect.X); CheckEqual(0, vertex.BorderTextureSlot); CheckEqual(1, vertex.InteriorTextureSlot); CheckEqual(TextureSlotManager.InvalidTextureSlot, vertex.SpriteTextureSlot); } } // Straight-skeleton decomposition isn't hand-predictable, so assert the aggregate its BorderPolygons // produce plus that every vertex is tagged as a triangle in the polygon's colors. public void Polygon_QueuesEveryDecomposedBorderPolygon() { var polygon = new Polygon( new Vector[] { new Vector(0, 0), new Vector(5, 0), new Vector(1, 4) }) { ShellColor = Shell, InsideColor = Inside, ShellPixelThickness = PixelThickness, ShellModelSpaceThickness = ModelThickness, SmoothBorder = true, ShellFillTexture = new TextureSlotManagerTests.TestTexture(), InsideFillTexture = new TextureSlotManagerTests.TestTexture(), }; int expectedVertices = 0; int expectedIndices = 0; foreach (var border in polygon.BorderPolygons) { expectedVertices += border._geoTris[0].vertices.Count; expectedIndices += 3 * border._geoTris.Count; } var batch = new TestBatch(); CheckTrue(batch.QueuePrimitive(polygon, Transform)); CheckTrue(expectedVertices > 0); CheckEqual(expectedVertices, batch.VertexCount); CheckEqual(expectedIndices, batch.IndexCount); for (int i = 0; i < batch.VertexCount; ++i) { var vertex = batch.PackedVertices[i]; CheckEqual(4f, vertex.BorderStyle.W); CheckEqual(Shell.ToArgb(), vertex.BorderColor); CheckEqual(Inside.ToArgb(), vertex.InsideColor); CheckEqual(0, vertex.BorderTextureSlot); CheckEqual(1, vertex.InteriorTextureSlot); CheckEqual(TextureSlotManager.InvalidTextureSlot, vertex.SpriteTextureSlot); } } public void ClosedQuadraticBezierCurve_QueuesTheInteriorPolygonThenTheBezierTriangles() { var curve = new ClosedQuadraticBezierCurve(new Vector[] { new Vector(0, 0), new Vector(-5, 0), new Vector(-10, 10), new Vector(0, 7), new Vector(10, 10), new Vector(5, -5), }) { ShellColor = Shell, InsideColor = Inside, }; int interiorVertices = curve.InteriorPolygon._geoTris[0].vertices.Count; int interiorIndices = 3 * curve.InteriorPolygon._geoTris.Count; int bezierVertices = curve.BezierTriangles.Count; var batch = new TestBatch(); CheckTrue(batch.QueuePrimitive(curve, Transform)); CheckEqual(interiorVertices + bezierVertices, batch.VertexCount); CheckEqual(interiorIndices + bezierVertices, batch.IndexCount); for (int i = 0; i < interiorVertices; ++i) { CheckEqual(4f, batch.PackedVertices[i].BorderStyle.W); } for (int i = interiorVertices; i < batch.VertexCount; ++i) { CheckEqual(5f, batch.PackedVertices[i].BorderStyle.W); } } public void ValueTypePrimitive_ThrowsBecauseItNeedsAnExplicitOverride() { var batch = new TestBatch(); CheckThrow(typeof(InvalidOperationException)); batch.QueuePrimitive(new BatchPoolTests.PrimitiveMock(), Transform); } public void UnsupportedReferenceType_Throws() { var batch = new TestBatch(); CheckThrow(typeof(Exception)); batch.QueuePrimitive(new UnsupportedPrimitive(), Transform); } public void SecondQuad_AdvancesCountsAndOffsetsItsIndices() { var batch = new TestBatch(); batch.QueuePrimitive(new Rectangle(), AffineMatrix.Identity); batch.QueuePrimitive(new Rectangle(), AffineMatrix.Identity); CheckEqual(8, batch.VertexCount); CheckEqual(12, batch.IndexCount); CheckEqual(new ushort[] { 4, 5, 6, 4, 6, 7 }, batch.PackedIndices.Skip(6).Take(6).ToArray()); } public void WhenFull_RejectsAnotherQuad() { var batch = new TestBatch(); int accepted = 0; while (batch.QueuePrimitive(new Rectangle(), AffineMatrix.Identity)) { ++accepted; } CheckTrue(accepted > 0); CheckFalse(batch.QueuePrimitive(new Rectangle(), AffineMatrix.Identity)); CheckTrue( batch.VertexCount + PrimitiveBatch.VerticesPerQuad >= PrimitiveBatch.MaxVertices); } } public class PackColorChannelTests : TestFixture { public void PacksEachChannelIntoItsOwnThreeBitField() { var sprite = SpriteWithChannels( red: ColorChannel.A, green: ColorChannel.B, blue: ColorChannel.G, alpha: ColorChannel.R); CheckEqual(0b101_100_011_010, PrimitiveBatch.PackColorChannel(sprite)); } public void AllDefaultChannelsPackToZero() { var sprite = SpriteWithChannels( ColorChannel.Default, ColorChannel.Default, ColorChannel.Default, ColorChannel.Default); CheckEqual(0, PrimitiveBatch.PackColorChannel(sprite)); } public void MaxChannelValuesStayWithinTheirFields() { var sprite = SpriteWithChannels( ColorChannel.One, ColorChannel.One, ColorChannel.One, ColorChannel.One); CheckEqual(0b110_110_110_110, PrimitiveBatch.PackColorChannel(sprite)); } } public class PackTilingBehaviorTests : TestFixture { public void PacksYIntoTheHighByteAndXIntoTheLowByte() { var sprite = SpriteWithTiling(x: TilingBehavior.Tile, y: TilingBehavior.StretchAndTile); CheckEqual(0x0301, PrimitiveBatch.PackTilingBehavior(sprite)); } public void BothNonePackToZero() { var sprite = SpriteWithTiling(TilingBehavior.None, TilingBehavior.None); CheckEqual(0, PrimitiveBatch.PackTilingBehavior(sprite)); } public void AFullLowByteDoesNotBleedIntoTheHighByte() { var sprite = SpriteWithTiling(x: (TilingBehavior)0xFF, y: TilingBehavior.Tile); CheckEqual(0x01FF, PrimitiveBatch.PackTilingBehavior(sprite)); } } public class GetVertexTests : TestFixture { static readonly Vector Min = new Vector(1, 2); static readonly Vector Max = new Vector(3, 4); public void Index0ReturnsTheMinCorner() { CheckEqual(new Vector(1, 2), PrimitiveBatch.GetVertex(0, Min, Max)); } public void Index1ReturnsMaxXWithMinY() { CheckEqual(new Vector(3, 2), PrimitiveBatch.GetVertex(1, Min, Max)); } public void Index2ReturnsTheMaxCorner() { CheckEqual(new Vector(3, 4), PrimitiveBatch.GetVertex(2, Min, Max)); } public void Index3ReturnsMinXWithMaxY() { CheckEqual(new Vector(1, 4), PrimitiveBatch.GetVertex(3, Min, Max)); } public void NegativeIndexThrows() { CheckThrow(typeof(ArgumentOutOfRangeException)); PrimitiveBatch.GetVertex(-1, Min, Max); } public void IndexPastTheLastCornerThrows() { CheckThrow(typeof(ArgumentOutOfRangeException)); PrimitiveBatch.GetVertex(4, Min, Max); } } public class TrySetTexturesTests : TestFixture { public void Primitive_MapsShellAndInsideToConsecutiveFillSlots() { var shell = new TextureSlotManagerTests.TestTexture(); var inside = new TextureSlotManagerTests.TestTexture(); var batch = new TestBatch(); CheckTrue(batch.TrySetTextures( new Rectangle { ShellFillTexture = shell, InsideFillTexture = inside }, out byte shellSlot, out byte insideSlot)); CheckEqual(0, shellSlot); CheckEqual(1, insideSlot); CheckEqual(2, batch.TextureCount); CheckEqual(shell, batch.TextureSlots.GetSlot(0).texture); CheckEqual(inside, batch.TextureSlots.GetSlot(1).texture); CheckEqual(TextureSlotManager.SlotType.FillTexture, batch.TextureSlots.GetSlot(0).type); } public void Primitive_NullTexturesAreNotFailuresAndTakeNoSlots() { var batch = new TestBatch(); CheckTrue(batch.TrySetTextures(new Rectangle(), out byte shellSlot, out byte insideSlot)); CheckEqual(TextureSlotManager.InvalidTextureSlot, shellSlot); CheckEqual(TextureSlotManager.InvalidTextureSlot, insideSlot); CheckEqual(0, batch.TextureCount); } public void Primitive_DeduplicatesASharedTextureToOneSlot() { var texture = new TextureSlotManagerTests.TestTexture(); var batch = new TestBatch(); CheckTrue(batch.TrySetTextures( new Rectangle { ShellFillTexture = texture, InsideFillTexture = texture }, out byte shellSlot, out byte insideSlot)); CheckEqual(0, shellSlot); CheckEqual(0, insideSlot); CheckEqual(1, batch.TextureCount); } public void Primitive_ReturnsFalseWhenTheSlotsAreFull() { var batch = new TestBatch(); for (int i = 0; i < TextureSlotManager.MaxTextureCount / 2; ++i) { batch.TrySetTextures( new Rectangle { ShellFillTexture = new TextureSlotManagerTests.TestTexture(), InsideFillTexture = new TextureSlotManagerTests.TestTexture(), }, out _, out _); } CheckEqual(TextureSlotManager.MaxTextureCount, batch.TextureCount); CheckFalse(batch.TrySetTextures( new Rectangle { ShellFillTexture = new TextureSlotManagerTests.TestTexture() }, out _, out _)); CheckEqual(TextureSlotManager.MaxTextureCount, batch.TextureCount); } public void Sprite_AddsSourceAsSpriteSheetAndTintAsFillTexture() { var source = new TextureSlotManagerTests.TestTexture(); var tint = new TextureSlotManagerTests.TestTexture(); var batch = new TestBatch(); CheckTrue(batch.TrySetTextures( SpriteWithTextures(source, tint), out byte sourceSlot, out byte tintSlot)); CheckEqual(0, sourceSlot); CheckEqual(1, tintSlot); CheckEqual(2, batch.TextureCount); CheckEqual(source, batch.TextureSlots.GetSlot(0).texture); CheckEqual(tint, batch.TextureSlots.GetSlot(1).texture); CheckEqual(TextureSlotManager.SlotType.SpriteSheet, batch.TextureSlots.GetSlot(0).type); CheckEqual(TextureSlotManager.SlotType.FillTexture, batch.TextureSlots.GetSlot(1).type); } public void Sprite_NullTintTakesNoSlotAndStillSucceeds() { var source = new TextureSlotManagerTests.TestTexture(); var batch = new TestBatch(); CheckTrue(batch.TrySetTextures( SpriteWithTextures(source, tint: null), out byte sourceSlot, out byte tintSlot)); CheckEqual(0, sourceSlot); CheckEqual(TextureSlotManager.InvalidTextureSlot, tintSlot); CheckEqual(1, batch.TextureCount); } } public class FrameResetTests : TestFixture { public void ClearsCounts() { var batch = new TestBatch(); batch.QueuePrimitive(new Rectangle(), AffineMatrix.Identity); batch.FrameReset(viewport: null, camera: null); CheckEqual(0, batch.VertexCount); CheckEqual(0, batch.IndexCount); } } } }