diff --git a/Quill/Canvas.cs b/Quill/Canvas.cs index 9d74280..98a2340 100644 --- a/Quill/Canvas.cs +++ b/Quill/Canvas.cs @@ -3,10 +3,12 @@ using Prowl.Scribe.Internal; using Prowl.Vector; using System; +using System.Buffers; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Runtime.InteropServices; +// using Tess = Prowl.Quill.External.LibTessDotNet.Tess; namespace Prowl.Quill { @@ -162,32 +164,37 @@ internal void Reset() public partial class Canvas { - internal class SubPath + internal class SubPath : Poolable { - internal List Points { get; } - internal bool IsClosed { get; } + internal List Points { get; set; } + internal bool IsClosed { get; set; } - public SubPath(List points, bool isClosed) + public override void Reset() { - Points = points; - IsClosed = isClosed; + if (Points == null) Points = new List(); + + Points.Clear(); + IsClosed = false; } } - public IReadOnlyList DrawCalls => _drawCalls.AsReadOnly(); - public IReadOnlyList Indices => _indices.AsReadOnly(); - public IReadOnlyList Vertices => _vertices.AsReadOnly(); + public IReadOnlyList DrawCalls => _drawCalls.Where(d => d.ElementCount != 0).ToList(); + public uint[] Indices => _indices; + public Vertex[] Vertices => _vertices; public Vector2 CurrentPoint => _currentSubPath != null && _currentSubPath.Points.Count > 0 ? CurrentPointInternal : Vector2.zero; internal Vector2 CurrentPointInternal => _currentSubPath.Points[_currentSubPath.Points.Count - 1]; internal ICanvasRenderer _renderer; - internal bool _isNewDrawCallRequested = false; internal List _drawCalls = new List(); internal Stack _textureStack = new Stack(); - internal List _indices = new List(); - internal List _vertices = new List(); + internal uint[] _indices = new uint[10000]; + internal int _indicesCount = 0; + public int IndicesCount => _indicesCount; + internal Vertex[] _vertices = new Vertex[10000]; + internal int _vertexCount = 0; + public int VertexCount => _vertexCount; private readonly List _subPaths = new List(); private SubPath? _currentSubPath = null; @@ -204,6 +211,12 @@ public SubPath(List points, bool isClosed) private double _pixelHalf = 0.5f; private IMarkdownImageProvider? _markdownImageProvider = null; + + private Tess _tess = new Tess(); + + private Type[] _meshTypes = {typeof(MeshUtils.Vertex), typeof(MeshUtils.Edge), typeof(MeshUtils.Face), typeof(Mesh), typeof(Dict.Node), typeof(Tess.ActiveRegion), typeof(SubPath)}; + + private List _roundedRectFilledPointList = new List(); public double DevicePixelRatio { @@ -227,6 +240,15 @@ public Canvas(ICanvasRenderer renderer, FontAtlasSettings fontAtlasSettings) _renderer = renderer; _scribeRenderer = new TextRenderer(this, fontAtlasSettings); + + MemoryArena.AddType(1024); + MemoryArena.AddType(2048); + MemoryArena.AddType(1024); + MemoryArena.AddType(8); + MemoryArena.AddType.Node>(1024); + MemoryArena.AddType(1024); + MemoryArena.AddType(512); + Clear(); } @@ -234,9 +256,10 @@ public void Clear() { _drawCalls.Clear(); _textureStack.Clear(); - - _indices.Clear(); - _vertices.Clear(); + AddDrawCmd(); + + _indicesCount = 0; + _vertexCount = 0; _savedStates.Clear(); _state = new ProwlCanvasState(); @@ -245,16 +268,31 @@ public void Clear() _subPaths.Clear(); _currentSubPath = null; _isPathOpen = true; - _globalAlpha = 1f; } + private void AddIndex(uint idx) + { + if (_indicesCount >= _indices.Length) + { + var newArray = new uint[_indices.Length * 2]; + Array.Copy(_indices, newArray, _indicesCount); + _indices = newArray; + } + + _indices[_indicesCount] = idx; + _indicesCount++; + } #region State public void SaveState() => _savedStates.Push(_state); public void RestoreState() => _state = _savedStates.Pop(); - public void ResetState() => _state.Reset(); + public void ResetState() + { + MemoryArena.FreeTypes(_meshTypes); + _state.Reset(); + } public void SetStrokeColor(Color color) => _state.strokeColor = color; public void SetStrokeJoint(JointStyle joint) => _state.strokeJoint = joint; @@ -460,16 +498,13 @@ public void ResetScissor() #region Draw Calls - /// - /// Ensure that future commands are not batched as part of any existing draw call. - /// - public void RequestNewDrawCall() - { - _isNewDrawCallRequested = true; - } + public void AddDrawCmd() => _drawCalls.Add(new DrawCall()); public void AddVertex(Vertex vertex) { + if (_drawCalls.Count == 0) + return; + if (_globalAlpha != 1.0f) vertex.a = (byte)(vertex.a * _globalAlpha); @@ -481,19 +516,29 @@ public void AddVertex(Vertex vertex) vertex.b = (byte)(vertex.b * (vertex.a / 255f)); } - + if (_vertexCount >= _vertices.Length) + { + var newVertexArray = new Vertex[_vertices.Length * 2]; + Array.Copy(_vertices, newVertexArray, _vertices.Length); + _vertices = newVertexArray; + } // Add the vertex to the list - _vertices.Add(vertex); + // _vertices.Add(vertex); + _vertices[_vertexCount] = vertex; + _vertexCount++; } - public void AddTriangle() => AddTriangle(_vertices.Count - 3, _vertices.Count - 2, _vertices.Count - 1); + public void AddTriangle() => AddTriangle(_vertexCount - 3, _vertexCount - 2, _vertexCount - 1); public void AddTriangle(int v1, int v2, int v3) => AddTriangle((uint)v1, (uint)v2, (uint)v3); public void AddTriangle(uint v1, uint v2, uint v3) { + if (_drawCalls.Count == 0) + return; + // Add the triangle indices to the list - _indices.Add(v1); - _indices.Add(v2); - _indices.Add(v3); + AddIndex(v1); + AddIndex(v2); + AddIndex(v3); AddTriangleCount(1); } @@ -501,9 +546,7 @@ public void AddTriangle(uint v1, uint v2, uint v3) private void AddTriangleCount(int count) { if (_drawCalls.Count == 0) - { - _drawCalls.Add(new DrawCall()); - } + return; DrawCall lastDrawCall = _drawCalls[_drawCalls.Count - 1]; @@ -512,19 +555,15 @@ private void AddTriangleCount(int count) lastDrawCall.scissor == _state.scissor && lastDrawCall.Brush.EqualsOther(_state.brush); - if (!isDrawStateSame || _isNewDrawCallRequested) + if (!isDrawStateSame) { - // If draw state has changed and the last draw call has already been used, add a new draw call - if (lastDrawCall.ElementCount != 0) - _drawCalls.Add(new DrawCall()); - + // If the texture or scissor state has changed, add a new draw call + AddDrawCmd(); lastDrawCall = _drawCalls[_drawCalls.Count - 1]; lastDrawCall.Texture = _state.texture; lastDrawCall.scissor = _state.scissor; lastDrawCall.scissorExtent = _state.scissorExtent; lastDrawCall.Brush = _state.brush; - - _isNewDrawCallRequested = false; } lastDrawCall.ElementCount += count * 3; @@ -568,7 +607,7 @@ public void MoveTo(double x, double y) if (!_isPathOpen) BeginPath(); - _currentSubPath = new SubPath(new List(), false); + _currentSubPath = MemoryArena.Get(); _currentSubPath.Points.Add(new Vector2(x, y)); _subPaths.Add(_currentSubPath); } @@ -1016,15 +1055,29 @@ public void FillComplex() if (_subPaths.Count == 0) return; - var tess = new Tess(); + var tess = _tess; + tess.Reset(); foreach (var path in _subPaths) { - var copy = path.Points.ToArray(); - for (int i = 0; i < copy.Length; i++) - copy[i] = TransformPoint(copy[i]) + new Vector2(0.5, 0.5); // And offset by half a pixel to properly align it with Stroke() - var points = copy.Select(v => new ContourVertex() { Position = new Vec3() { X = v.x, Y = v.y } }).ToArray(); + int length = path.Points.Count; + var copy = ArrayPool.Shared.Rent(length); + for (int i = 0; i < length; i++) + copy[i] = TransformPoint(path.Points[i]) + new Vector2(0.5, 0.5); // And offset by half a pixel to properly align it with Stroke() + + + //TODO this could be larger than the desired size, so we need to check that correctly. Maybe even updating the + // add contour function to account for this discrepancy + var points = ArrayPool.Shared.Rent(length); + + for (int i = 0; i < length; i++) + { + points[i] = new ContourVertex() { Position = new Vec3() { X = copy[i].x, Y = copy[i].y } }; + } + // List points = copy.Select(v => new ContourVertex() { Position = new Vec3() { X = v.x, Y = v.y } }).ToArray(); - tess.AddContour(points, ContourOrientation.Original); + tess.AddContour(points, length, ContourOrientation.Original); + ArrayPool.Shared.Return(points, true); + ArrayPool.Shared.Return(copy); } tess.Tessellate(_state.fillMode == WindingMode.OddEven ? WindingRule.EvenOdd : WindingRule.NonZero, ElementType.Polygons, 3); @@ -1032,15 +1085,15 @@ public void FillComplex() var vertices = tess.Vertices; // Create vertices and triangles - uint startVertexIndex = (uint)_vertices.Count; - for (int i = 0; i < vertices.Length; i++) + uint startVertexIndex = (uint)_vertexCount; + for (int i = 0; i < tess.VertexCount; i++) { var vertex = vertices[i]; Vector2 pos = new Vector2(vertex.Position.X, vertex.Position.Y); AddVertex(new Vertex(pos, new Vector2(0.5, 0.5), _state.fillColor)); } // Create triangles - for (int i = 0; i < indices.Length; i += 3) + for (int i = 0; i < tess.ElementCount; i += 3) { uint v1 = (uint)(startVertexIndex + indices[i]); uint v2 = (uint)(startVertexIndex + indices[i + 1]); @@ -1057,25 +1110,25 @@ private void FillSubPath(SubPath subPath) // Transform each point Vector2 center = Vector2.zero; - var copy = subPath.Points.ToArray(); - for (int i = 0; i < copy.Length; i++) + // var copy = CollectionsMarshal.AsSpan(subPath.Points); + int pointArrayLength = subPath.Points.Count; + var copy = ArrayPool.Shared.Rent(pointArrayLength); + for (int i = 0; i < pointArrayLength; i++) { - var point = copy[i]; + var point = subPath.Points[i]; point = TransformPoint(point) + new Vector2(0.5, 0.5); // And offset by half a pixel to properly center it with Stroke() center += point; copy[i] = point; } - center /= copy.Length; + center /= pointArrayLength; // Store the starting index to reference _vertices - uint startVertexIndex = (uint)_vertices.Count; + uint startVertexIndex = (uint)_vertexCount; // Add center vertex with UV at 0.5,0.5 (no AA, Since 0 or 1 in shader is considered edge of shape and get anti aliased) AddVertex(new Vertex(center, new Vector2(0.5f, 0.5f), _state.fillColor)); - - // Generate vertices around the path - int segments = copy.Length; - for (int i = 0; i < segments; i++) // Edge vertices have UV at 0,0 for anti-aliasing + + for (int i = 0; i < pointArrayLength; i++) // Edge vertices have UV at 0,0 for anti-aliasing { Vector2 dirToPoint = (copy[i] - center).normalized; AddVertex(new Vertex(copy[i] + (dirToPoint * _pixelWidth), new Vector2(0, 0), _state.fillColor)); @@ -1097,28 +1150,29 @@ private void FillSubPath(SubPath subPath) bool clockwise = cross <= 0; // Use the determined orientation for all triangles - for (int i = 0; i < segments; i++) + for (int i = 0; i < pointArrayLength; i++) { uint current = (uint)(startVertexIndex + 1 + i); - uint next = (uint)(startVertexIndex + 1 + ((i + 1) % segments)); + uint next = (uint)(startVertexIndex + 1 + ((i + 1) % pointArrayLength)); if (clockwise) { - _indices.Add(centerIdx); - _indices.Add(current); - _indices.Add(next); + AddIndex(centerIdx); + AddIndex(current); + AddIndex(next); } else { - _indices.Add(centerIdx); - _indices.Add(next); - _indices.Add(current); + AddIndex(centerIdx); + AddIndex(next); + AddIndex(current); } //AddTriangleCount(1); } - AddTriangleCount(segments); + AddTriangleCount(pointArrayLength); + ArrayPool.Shared.Return(copy); } public void Stroke() @@ -1131,33 +1185,38 @@ public void Stroke() StrokeSubPath(subPath); } + private List _scaledDashPattern = new List(); private void StrokeSubPath(SubPath subPath) { if (subPath.Points.Count < 2) return; - var copy = subPath.Points.ToArray(); + // var copy = CollectionsMarshal.AsSpan(subPath.Points); + int length = subPath.Points.Count; + var originalArray = ArrayPool.Shared.Rent(length); // Transform each point for (int i = 0; i < subPath.Points.Count; i++) + { + originalArray[i] = subPath.Points[i]; subPath.Points[i] = TransformPoint(subPath.Points[i]); + } bool isClosed = subPath.IsClosed; - - List dashPattern = null; + + _scaledDashPattern.Clear(); if (_state.strokeDashPattern != null) { - dashPattern = new List(_state.strokeDashPattern); - for (int i = 0; i < dashPattern.Count; i++) + for (int i = 0; i < _state.strokeDashPattern.Count; i++) { - dashPattern[i] *= _state.strokeScale; // Scale the dash pattern by stroke scale + _scaledDashPattern.Add(_state.strokeDashPattern[i] * _state.strokeScale); // Scale the dash pattern by stroke scale } } - var triangles = PolylineMesher.Create(subPath.Points, _state.strokeWidth * _state.strokeScale, _pixelWidth, _state.strokeColor, _state.strokeJoint, _state.miterLimit, false, _state.strokeStartCap, _state.strokeEndCap, dashPattern, _state.strokeDashOffset * _state.strokeScale); + var triangles = PolylineMesher.Create(subPath.Points, _state.strokeWidth * _state.strokeScale, _pixelWidth, _state.strokeColor, _state.strokeJoint, _state.miterLimit, false, _state.strokeStartCap, _state.strokeEndCap, _scaledDashPattern, _state.strokeDashOffset * _state.strokeScale); // Store the starting index to reference _vertices - uint startVertexIndex = (uint)_vertices.Count; + uint startVertexIndex = (uint)_vertexCount; foreach (var triangle in triangles) { var color = triangle.Color; @@ -1167,19 +1226,20 @@ private void StrokeSubPath(SubPath subPath) } // Add triangle _indices - for (uint i = 0; i < triangles.Count; i++) + for (uint i = 0; i < triangles.Length; i++) { - _indices.Add(startVertexIndex + (i * 3)); - _indices.Add(startVertexIndex + (i * 3) + 1); - _indices.Add(startVertexIndex + (i * 3) + 2); - //AddTriangleCount(1); + AddIndex(startVertexIndex + (i * 3)); + AddIndex(startVertexIndex + (i * 3) + 1); + AddIndex(startVertexIndex + (i * 3) + 2); } - AddTriangleCount(triangles.Count); + AddTriangleCount(triangles.Length); // Reset the points to their original values for (int i = 0; i < subPath.Points.Count; i++) - subPath.Points[i] = copy[i]; + subPath.Points[i] = originalArray[i]; + + ArrayPool.Shared.Return(originalArray); } public void FillAndStroke() @@ -1410,7 +1470,7 @@ public void RectFilled(double x, double y, double width, double height, System.D Vector2 bottomLeft = TransformPoint(new Vector2(x + width, y)); // Store the starting index to reference _vertices - uint startVertexIndex = (uint)_vertices.Count; + uint startVertexIndex = (uint)_vertexCount; // Add all vertices with the transformed coordinates AddVertex(new Vertex(topLeft, new Vector2(0, 0), color)); @@ -1419,13 +1479,13 @@ public void RectFilled(double x, double y, double width, double height, System.D AddVertex(new Vertex(bottomLeft, new Vector2(1, 0), color)); // Add indexes for fill - _indices.Add(startVertexIndex); - _indices.Add(startVertexIndex + 1); - _indices.Add(startVertexIndex + 2); + AddIndex(startVertexIndex); + AddIndex(startVertexIndex + 1); + AddIndex(startVertexIndex + 2); - _indices.Add(startVertexIndex); - _indices.Add(startVertexIndex + 2); - _indices.Add(startVertexIndex + 3); + AddIndex(startVertexIndex); + AddIndex(startVertexIndex + 2); + AddIndex(startVertexIndex + 3); AddTriangleCount(2); } @@ -1456,7 +1516,7 @@ public void RoundedRectFilled(double x, double y, double width, double height, { RoundedRectFilled(x, y, width, height, radius, radius, radius, radius, color); } - + /// /// Paints a Hardware-accelerated rounded rectangle on the canvas. /// This does not modify or use the current path. @@ -1498,7 +1558,7 @@ public void RoundedRectFilled(double x, double y, double width, double height, int blSegments = Math.Max(1, (int)Math.Ceiling(Math.PI * blRadii / 2 / _state.roundingMinDistance)); // Store the starting index to reference _vertices - uint startVertexIndex = (uint)_vertices.Count; + uint startVertexIndex = (uint)_vertexCount; // Calculate the center point of the rectangle Vector2 center = TransformPoint(new Vector2(x + width / 2, y + height / 2)); @@ -1506,7 +1566,8 @@ public void RoundedRectFilled(double x, double y, double width, double height, // Add center vertex with UV at 0.5,0.5 (no AA) AddVertex(new Vertex(center, new Vector2(0.5f, 0.5f), color)); - List points = new List(); + List points = _roundedRectFilledPointList; + points.Clear(); // Top-left corner if (tlRadii > 0) @@ -1589,9 +1650,9 @@ public void RoundedRectFilled(double x, double y, double width, double height, uint current = (uint)(startVertexIndex + 1 + i); uint next = (uint)(startVertexIndex + 1 + ((i + 1) % points.Count)); - _indices.Add((uint)startVertexIndex); // Center - _indices.Add(next); // Next edge vertex - _indices.Add(current); // Current edge vertex + AddIndex((uint)startVertexIndex); // Center + AddIndex(next); // Next edge vertex + AddIndex(current); // Current edge vertex //AddTriangleCount(1); } @@ -1624,7 +1685,7 @@ public void CircleFilled(double x, double y, double radius, System.Drawing.Color radius += _pixelHalf; // Store the starting index to reference _vertices - uint startVertexIndex = (uint)_vertices.Count; + uint startVertexIndex = (uint)_vertexCount; Vector2 transformedCenter = TransformPoint(new Vector2(x, y)); @@ -1651,9 +1712,9 @@ public void CircleFilled(double x, double y, double radius, System.Drawing.Color // Create triangles (fan from center to edges) for (int i = 0; i < segments; i++) { - _indices.Add((uint)startVertexIndex); // Center - _indices.Add((uint)(startVertexIndex + 1 + ((i + 1) % segments))); // Next edge vertex - _indices.Add((uint)(startVertexIndex + 1 + i)); // Current edge vertex + AddIndex((uint)startVertexIndex); // Center + AddIndex((uint)(startVertexIndex + 1 + ((i + 1) % segments))); // Next edge vertex + AddIndex((uint)(startVertexIndex + 1 + i)); // Current edge vertex //AddTriangleCount(1); } @@ -1702,7 +1763,7 @@ public void PieFilled(double x, double y, double radius, double startAngle, doub double centroidY = y + centroidDistance * Math.Sin(midAngle); // Store the starting index to reference _vertices - uint startVertexIndex = (uint)_vertices.Count; + uint startVertexIndex = (uint)_vertexCount; Vector2 transformedCenter = TransformPoint(new Vector2(x, y)); Vector2 transformedCentroid = TransformPoint(new Vector2(centroidX, centroidY)); @@ -1736,9 +1797,9 @@ public void PieFilled(double x, double y, double radius, double startAngle, doub // Create triangles (fan from centroid to each pair of edge points) for (int i = 0; i < segments + 2; i++) { - _indices.Add(startVertexIndex); // Centroid - _indices.Add((uint)(startVertexIndex + 1 + i + 1)); // Next edge vertex - _indices.Add((uint)(startVertexIndex + 1 + i)); // Current edge vertex + AddIndex(startVertexIndex); // Centroid + AddIndex((uint)(startVertexIndex + 1 + i + 1)); // Next edge vertex + AddIndex((uint)(startVertexIndex + 1 + i)); // Current edge vertex //AddTriangleCount(1); } diff --git a/Quill/External/LibTessDotNet/Dict.cs b/Quill/External/LibTessDotNet/Dict.cs index 66865f9..2a61875 100644 --- a/Quill/External/LibTessDotNet/Dict.cs +++ b/Quill/External/LibTessDotNet/Dict.cs @@ -38,7 +38,7 @@ namespace LibTessDotNet { internal class Dict where TValue : class { - public class Node + public class Node : Poolable { internal TValue _key; internal Node _prev, _next; @@ -46,6 +46,11 @@ public class Node public TValue Key { get { return _key; } } public Node Prev { get { return _prev; } } public Node Next { get { return _next; } } + public override void Reset() + { + // throw new System.NotImplementedException(); + _key = null; + } } public delegate bool LessOrEqual(TValue lhs, TValue rhs); @@ -62,6 +67,14 @@ public Dict(LessOrEqual leq) _head._next = _head; } + public void Reset() + { + var newNode = MemoryArena.Get(); + _head = newNode; + _head._prev = _head; + _head._next = _head; + } + public Node Insert(TValue key) { return InsertBefore(_head, key); @@ -73,7 +86,8 @@ public Node InsertBefore(Node node, TValue key) node = node._prev; } while (node._key != null && !_leq(node._key, key)); - var newNode = new Node { _key = key }; + var newNode = MemoryArena.Get(); + newNode._key = key; newNode._next = node._next; node._next._prev = newNode; newNode._prev = node; diff --git a/Quill/External/LibTessDotNet/Mesh.cs b/Quill/External/LibTessDotNet/Mesh.cs index 532b044..d0ad8aa 100644 --- a/Quill/External/LibTessDotNet/Mesh.cs +++ b/Quill/External/LibTessDotNet/Mesh.cs @@ -39,7 +39,7 @@ namespace Prowl.Quill.External namespace LibTessDotNet { - internal class Mesh : MeshUtils.Pooled + internal class Mesh : Poolable { internal MeshUtils.Vertex _vHead; internal MeshUtils.Face _fHead; @@ -47,8 +47,13 @@ internal class Mesh : MeshUtils.Pooled public Mesh() { - var v = _vHead = MeshUtils.Vertex.Create(); - var f = _fHead = MeshUtils.Face.Create(); + Reset(); + } + + public override void Reset() + { + var v = _vHead = MemoryArena.Get(); + var f = _fHead = MemoryArena.Get(); var pair = MeshUtils.EdgePair.Create(); var e = _eHead = pair._e; @@ -82,30 +87,32 @@ public Mesh() eSym._activeRegion = null; } - public override void Reset() - { - _vHead = null; - _fHead = null; - _eHead = _eHeadSym = null; - } - public override void OnFree() { - for (MeshUtils.Face f = _fHead._next, fNext = _fHead; f != _fHead; f = fNext) - { - fNext = f._next; - f.Free(); - } - for (MeshUtils.Vertex v = _vHead._next, vNext = _vHead; v != _vHead; v = vNext) - { - vNext = v._next; - v.Free(); - } - for (MeshUtils.Edge e = _eHead._next, eNext = _eHead; e != _eHead; e = eNext) - { - eNext = e._next; - e.Free(); - } + // for (MeshUtils.Face f = _fHead._next, fNext = _fHead; f != _fHead; f = fNext) + // { + // fNext = f._next; + // f.Free(); + // } + // _fHead.Free(); + // for (MeshUtils.Vertex v = _vHead._next, vNext = _vHead; v != _vHead; v = vNext) + // { + // vNext = v._next; + // v.Free(); + // } + // _vHead.Free(); + // for (MeshUtils.Edge e = _eHead._next, eNext = _eHead; e != _eHead; e = eNext) + // { + // eNext = e._next; + // e.Free(); + // } + // for (MeshUtils.Edge e = _eHeadSym._next, eNext = _eHeadSym; e != _eHeadSym; e = eNext) + // { + // eNext = e._next; + // e.Free(); + // } + // _eHeadSym.Free(); + // _eHead.Free(); } /// @@ -391,7 +398,7 @@ public void ZapFace(MeshUtils.Face fZap) fNext._prev = fPrev; fPrev._next = fNext; - fZap.Free(); + // fZap.Free(); } public void MergeConvexFaces(int maxVertsPerFace) diff --git a/Quill/External/LibTessDotNet/MeshUtils.cs b/Quill/External/LibTessDotNet/MeshUtils.cs index 61c9bc6..79f6c55 100644 --- a/Quill/External/LibTessDotNet/MeshUtils.cs +++ b/Quill/External/LibTessDotNet/MeshUtils.cs @@ -108,39 +108,49 @@ public override string ToString() } } - internal static class MeshUtils + public abstract class Pooled where T : Pooled, new() { - public const int Undef = ~0; - - public abstract class Pooled where T : Pooled, new() + private static Stack _stack = new Stack(); + protected static int _created = 0; + protected static int _returned = 0; + protected static int _count = 0; + public abstract void Reset(); + public virtual void OnFree() {} + + public static void ResetCounters() { - private static Stack _stack; - - public abstract void Reset(); - public virtual void OnFree() {} + _count = 0; + _created = 0; + _returned = 0; + } - public static T Create() - { - if (_stack != null && _stack.Count > 0) - { - return _stack.Pop(); - } - return new T(); - } + public static T Create() + { + // if (_stack.TryPop(out T obj)) + // { + // Debug.Assert(obj != null); + // return obj; + // } + + _created++; + return new T(); + } - public void Free() - { - OnFree(); - Reset(); - if (_stack == null) - { - _stack = new Stack(); - } - _stack.Push((T)this); - } + public void Free() + { + OnFree(); + Reset(); + _stack.Push((T)this); + _returned++; + _count = _stack.Count; } + } + + internal static class MeshUtils + { + public const int Undef = ~0; - public class Vertex : Pooled + public class Vertex : Poolable { internal Vertex _prev, _next; internal Edge _anEdge; @@ -164,7 +174,7 @@ public override void Reset() } } - public class Face : Pooled + public class Face : Poolable { internal Face _prev, _next; internal Edge _anEdge; @@ -204,10 +214,10 @@ public struct EdgePair public static EdgePair Create() { - var pair = new MeshUtils.EdgePair(); - pair._e = MeshUtils.Edge.Create(); + var pair = new EdgePair(); + pair._e = MemoryArena.Get(); pair._e._pair = pair; - pair._eSym = MeshUtils.Edge.Create(); + pair._eSym = MemoryArena.Get(); pair._eSym._pair = pair; return pair; } @@ -218,7 +228,7 @@ public void Reset() } } - public class Edge : Pooled + public class Edge : Poolable { internal EdgePair _pair; internal Edge _next, _Sym, _Onext, _Lnext; @@ -326,7 +336,7 @@ public static void Splice(Edge a, Edge b) /// public static void MakeVertex(Edge eOrig, Vertex vNext) { - var vNew = MeshUtils.Vertex.Create(); + var vNew = MemoryArena.Get(); // insert in circular doubly-linked list before vNext var vPrev = vNext._prev; @@ -355,7 +365,7 @@ public static void MakeVertex(Edge eOrig, Vertex vNext) /// public static void MakeFace(Edge eOrig, Face fNext) { - var fNew = MeshUtils.Face.Create(); + var fNew = MemoryArena.Get(); // insert in circular doubly-linked list before fNext var fPrev = fNext._prev; @@ -394,8 +404,6 @@ public static void KillEdge(Edge eDel) var ePrev = eDel._Sym._next; eNext._Sym._next = ePrev; ePrev._Sym._next = eNext; - - eDel.Free(); } /// @@ -418,8 +426,6 @@ public static void KillVertex(Vertex vDel, Vertex newOrg) var vNext = vDel._next; vNext._prev = vPrev; vPrev._next = vNext; - - vDel.Free(); } /// @@ -442,8 +448,6 @@ public static void KillFace(Face fDel, Face newLFace) var fNext = fDel._next; fNext._prev = fPrev; fPrev._next = fNext; - - fDel.Free(); } /// diff --git a/Quill/External/LibTessDotNet/PriorityHeap.cs b/Quill/External/LibTessDotNet/PriorityHeap.cs index 893fafd..993a327 100644 --- a/Quill/External/LibTessDotNet/PriorityHeap.cs +++ b/Quill/External/LibTessDotNet/PriorityHeap.cs @@ -32,6 +32,8 @@ */ using System; +using System.Buffers; +using System.Collections.Generic; using System.Diagnostics; namespace Prowl.Quill.External @@ -53,6 +55,29 @@ protected class HandleElem { internal TValue _key; internal int _node; + private static Stack _pool = new Stack(); + + private void Reset() + { + _key = null; + _node = 0; + } + + public static HandleElem Get() + { + if (!_pool.TryPop(out HandleElem element)) + { + element = new HandleElem(); + } + + return element; + } + + public static void Return(HandleElem element) + { + element.Reset(); + _pool.Push(element); + } } private LessOrEqual _leq; @@ -68,8 +93,8 @@ public PriorityHeap(int initialSize, LessOrEqual leq) { _leq = leq; - _nodes = new int[initialSize + 1]; - _handles = new HandleElem[initialSize + 1]; + _nodes = ArrayPool.Shared.Rent(initialSize + 1); + _handles = ArrayPool.Shared.Rent(initialSize + 1); _size = 0; _max = initialSize; @@ -77,7 +102,36 @@ public PriorityHeap(int initialSize, LessOrEqual leq) _initialized = false; _nodes[1] = 1; - _handles[1] = new HandleElem { _key = null }; + _handles[1] = HandleElem.Get(); + } + + public void Reset(int initialSize, LessOrEqual leq) + { + _leq = leq; + + if(_nodes != null) ArrayPool.Shared.Return(_nodes); + _nodes = ArrayPool.Shared.Rent(initialSize + 1); + + if(_handles != null) + { + foreach (HandleElem element in _handles) + { + if (element == null) continue; + + HandleElem.Return(element); + } + + ArrayPool.Shared.Return(_handles); + } + _handles = ArrayPool.Shared.Rent(initialSize + 1); + + _size = 0; + _max = initialSize; + _freeList = 0; + _initialized = false; + + _nodes[1] = 1; + _handles[1] = HandleElem.Get(); } private void FloatDown(int curr) diff --git a/Quill/External/LibTessDotNet/PriorityQueue.cs b/Quill/External/LibTessDotNet/PriorityQueue.cs index 1957a74..9862282 100644 --- a/Quill/External/LibTessDotNet/PriorityQueue.cs +++ b/Quill/External/LibTessDotNet/PriorityQueue.cs @@ -32,8 +32,10 @@ */ using System; +using System.Buffers; using System.Collections.Generic; using System.Diagnostics; +// using System.Reflection.Metadata.Ecma335; namespace Prowl.Quill.External { @@ -57,16 +59,51 @@ public PriorityQueue(int initialSize, PriorityHeap.LessOrEqual leq) _leq = leq; _heap = new PriorityHeap(initialSize, leq); - _keys = new TValue[initialSize]; + _keys = ArrayPool.Shared.Rent(initialSize); _size = 0; _max = initialSize; _initialized = false; + + MemoryArena.AddType(); } - class StackItem + public void Reset(int initialSize, PriorityHeap.LessOrEqual leq) + { + _leq = leq; + + //TODO reset the heap here so it works better + if(_heap != null) + _heap.Reset(initialSize, leq); + else + _heap = new PriorityHeap(initialSize, leq); + + // ArrayPool.Shared.Return(_keys, true); + if(_keys != null) ArrayPool.Shared.Return(_keys); + + _keys = ArrayPool.Shared.Rent(initialSize); + + _size = 0; + _max = initialSize; + _initialized = false; + + MemoryArena.Free(); + } + + class StackItem : Poolable { internal int p, r; + public override void Reset() + { + p = 0; + r = 0; + } + + public void SetData(int pVal, int rVal) + { + p = pVal; + r = rVal; + } }; static void Swap(ref int a, ref int b) @@ -75,22 +112,30 @@ static void Swap(ref int a, ref int b) a = b; b = tmp; } - + private Stack _stack = new Stack(); public void Init() { - var stack = new Stack(); + var stack = _stack; + stack.Clear(); int p, r, i, j, piv; uint seed = 2016473283; p = 0; r = _size - 1; - _order = new int[_size + 1]; + + if(_order != null) + ArrayPool.Shared.Return(_order); + _order = ArrayPool.Shared.Rent(_size + 1); + for (piv = 0, i = p; i <= r; ++piv, ++i) { _order[i] = piv; } - stack.Push(new StackItem { p = p, r = r }); + // stack.Push(new StackItem { p = p, r = r }); + var newItem = MemoryArena.Get(); + newItem.SetData(p, r); + stack.Push(newItem); while (stack.Count > 0) { var top = stack.Pop(); @@ -114,12 +159,16 @@ public void Init() Swap(ref _order[i], ref _order[j]); if (i - p < r - j) { - stack.Push(new StackItem { p = j + 1, r = r }); + newItem = MemoryArena.Get(); + newItem.SetData(j + 1, r); + stack.Push(newItem); r = i - 1; } else { - stack.Push(new StackItem { p = p, r = i - 1 }); + newItem = MemoryArena.Get(); + newItem.SetData(p, i - 1); + stack.Push(newItem); p = j + 1; } } diff --git a/Quill/External/LibTessDotNet/Sweep.cs b/Quill/External/LibTessDotNet/Sweep.cs index 2b6b4e6..bf3e48f 100644 --- a/Quill/External/LibTessDotNet/Sweep.cs +++ b/Quill/External/LibTessDotNet/Sweep.cs @@ -42,12 +42,21 @@ namespace LibTessDotNet { internal partial class Tess { - internal class ActiveRegion + internal class ActiveRegion : Poolable { internal MeshUtils.Edge _eUp; internal Dict.Node _nodeUp; internal int _windingNumber; internal bool _inside, _sentinel, _dirty, _fixUpperEdge; + public override void Reset() + { + _nodeUp = null; + _windingNumber = 0; + _inside = false; + _sentinel = false; + _dirty = false; + _fixUpperEdge = false; + } } private ActiveRegion RegionBelow(ActiveRegion reg) @@ -167,7 +176,7 @@ private ActiveRegion TopRightRegion(ActiveRegion reg) /// private ActiveRegion AddRegionBelow(ActiveRegion regAbove, MeshUtils.Edge eNewUp) { - var regNew = new ActiveRegion(); + var regNew = MemoryArena.Get(); regNew._eUp = eNewUp; regNew._nodeUp = _dict.InsertBefore(regAbove._nodeUp, regNew); @@ -557,7 +566,7 @@ private bool CheckForIntersect(ActiveRegion regUp) // At this point the edges intersect, at least marginally - var isect = MeshUtils.Vertex.Create(); + var isect = MemoryArena.Get(); Geom.EdgeIntersect(dstUp, orgUp, dstLo, orgLo, isect); // The following properties are guaranteed: Debug.Assert(Math.Min(orgUp._t, dstUp._t) <= isect._t); @@ -642,6 +651,8 @@ private bool CheckForIntersect(ActiveRegion regUp) eLo._Org._s = _event._s; eLo._Org._t = _event._t; } + + // isect.Free(); // leave the rest for ConnectRightVertex return false; } @@ -904,7 +915,7 @@ private void ConnectLeftDegenerate(ActiveRegion regUp, MeshUtils.Vertex vEvent) /// private void ConnectLeftVertex(MeshUtils.Vertex vEvent) { - var tmp = new ActiveRegion(); + var tmp = MemoryArena.Get(); // Get a pointer to the active region containing vEvent tmp._eUp = vEvent._anEdge._Sym; @@ -1023,7 +1034,7 @@ private void AddSentinel(Real smin, Real smax, Real t) e._Dst._t = t; _event = e._Dst; // initialize it - var reg = new ActiveRegion(); + var reg = MemoryArena.Get(); reg._eUp = e; reg._windingNumber = 0; reg._inside = false; @@ -1038,8 +1049,11 @@ private void AddSentinel(Real smin, Real smax, Real t) /// This order is maintained in a dynamic dictionary. /// private void InitEdgeDict() - { - _dict = new Dict(EdgeLeq); + { + if(_dict == null) + _dict = new Dict(EdgeLeq); + else + _dict.Reset(); AddSentinel(-SentinelCoord, SentinelCoord, -SentinelCoord); AddSentinel(-SentinelCoord, SentinelCoord, +SentinelCoord); @@ -1063,8 +1077,6 @@ private void DoneEdgeDict() Debug.Assert(reg._windingNumber == 0); DeleteRegion(reg); } - - _dict = null; } /// @@ -1125,7 +1137,10 @@ private void InitPriorityQ() // Make sure there is enough space for sentinels. vertexCount += 8; - _pq = new PriorityQueue(vertexCount, Geom.VertLeq); + if(_pq == null) + _pq = new PriorityQueue(vertexCount, Geom.VertLeq); + else + _pq.Reset(vertexCount, Geom.VertLeq); vHead = _mesh._vHead; for( v = vHead._next; v != vHead; v = v._next ) { @@ -1138,11 +1153,6 @@ private void InitPriorityQ() _pq.Init(); } - private void DonePriorityQ() - { - _pq = null; - } - /// /// Delete any degenerate faces with only two edges. WalkDirtyRegions() /// will catch almost all of these, but it won't catch degenerate faces @@ -1227,7 +1237,6 @@ protected void ComputeInterior() } DoneEdgeDict(); - DonePriorityQ(); RemoveDegenerateFaces(); _mesh.Check(); diff --git a/Quill/External/LibTessDotNet/Tess.cs b/Quill/External/LibTessDotNet/Tess.cs index de7237c..ef63371 100644 --- a/Quill/External/LibTessDotNet/Tess.cs +++ b/Quill/External/LibTessDotNet/Tess.cs @@ -32,6 +32,7 @@ */ using System; +using System.Buffers; using System.Diagnostics; namespace Prowl.Quill.External @@ -124,7 +125,17 @@ internal partial class Tess public int[] Elements { get { return _elements; } } public int ElementCount { get { return _elementCount; } } + Real[] _minVal = new Real[3]; + Real[] _maxVal = new Real[3]; + MeshUtils.Vertex[] _minVert = new MeshUtils.Vertex[3]; + MeshUtils.Vertex[] _maxVert = new MeshUtils.Vertex[3]; + public Tess() + { + Reset(); + } + + public void Reset() { _normal = Vec3.Zero; _bminX = _bminY = _bmaxX = _bmaxY = 0; @@ -132,8 +143,15 @@ public Tess() _windingRule = WindingRule.EvenOdd; _mesh = null; + if(_vertices != null) + ArrayPool.Shared.Return(_vertices); + _vertices = null; _vertexCount = 0; + + if(_elements != null) + ArrayPool.Shared.Return(_elements); + _elements = null; _elementCount = 0; } @@ -142,11 +160,27 @@ private void ComputeNormal(ref Vec3 norm) { var v = _mesh._vHead._next; - var minVal = new Real[3] { v._coords.X, v._coords.Y, v._coords.Z }; - var minVert = new MeshUtils.Vertex[3] { v, v, v }; - var maxVal = new Real[3] { v._coords.X, v._coords.Y, v._coords.Z }; - var maxVert = new MeshUtils.Vertex[3] { v, v, v }; - + var minVal = _minVal; + var maxVal = _maxVal; + var minVert = _minVert; + var maxVert = _maxVert; + + minVal[0] = v._coords.X; + minVal[1] = v._coords.Y; + minVal[2] = v._coords.Z; + + maxVal[0] = v._coords.X; + maxVal[1] = v._coords.Y; + maxVal[2] = v._coords.Z; + + minVert[0] = v; + minVert[1] = v; + minVert[2] = v; + + maxVert[0] = v; + maxVert[1] = v; + maxVert[2] = v; + for (; v != _mesh._vHead; v = v._next) { if (v._coords.X < minVal[0]) { minVal[0] = v._coords.X; minVert[0] = v; } @@ -502,10 +536,11 @@ private void OutputPolymesh(ElementType elementType, int polySize) _elementCount = maxFaceCount; if (elementType == ElementType.ConnectedPolygons) maxFaceCount *= 2; - _elements = new int[maxFaceCount * polySize]; - + + _elementCount = maxFaceCount * polySize; + _elements = ArrayPool.Shared.Rent(_elementCount); _vertexCount = maxVertexCount; - _vertices = new ContourVertex[_vertexCount]; + _vertices = ArrayPool.Shared.Rent(_vertexCount); // Output vertices. for (v = _mesh._vHead._next; v != _mesh._vHead; v = v._next) @@ -591,8 +626,9 @@ private void OutputContours() ++_elementCount; } - _elements = new int[_elementCount * 2]; - _vertices = new ContourVertex[_vertexCount]; + _elementCount *= 2; + _elements = ArrayPool.Shared.Rent(_elementCount); + _vertices = ArrayPool.Shared.Rent(_vertexCount); int vertIndex = 0; int elementIndex = 0; @@ -636,16 +672,16 @@ private Real SignedArea(ContourVertex[] vertices) return 0.5f * area; } - public void AddContour(ContourVertex[] vertices) + public void AddContour(ContourVertex[] vertices, int vertexCount) { - AddContour(vertices, ContourOrientation.Original); + AddContour(vertices, vertexCount, ContourOrientation.Original); } - public void AddContour(ContourVertex[] vertices, ContourOrientation forceOrientation) + public void AddContour(ContourVertex[] vertices, int vertexCount, ContourOrientation forceOrientation) { if (_mesh == null) { - _mesh = new Mesh(); + _mesh = MemoryArena.Get(); } bool reverse = false; @@ -656,7 +692,7 @@ public void AddContour(ContourVertex[] vertices, ContourOrientation forceOrienta } MeshUtils.Edge e = null; - for (int i = 0; i < vertices.Length; ++i) + for (int i = 0; i < vertexCount; ++i) { if (e == null) { @@ -738,10 +774,10 @@ public void Tessellate(WindingRule windingRule, ElementType elementType, int pol OutputPolymesh(elementType, polySize); } - if (UsePooling) - { - _mesh.Free(); - } + // if (UsePooling) + // { + // _mesh.Free(); + // } _mesh = null; } } diff --git a/Quill/ListPool.cs b/Quill/ListPool.cs new file mode 100644 index 0000000..3a7415d --- /dev/null +++ b/Quill/ListPool.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Collections.Concurrent; + +public static class ListPool +{ + private static readonly List> _pool = new List>(); + private static int _poolIndex = 0; + + public static List Rent() + { + if (_poolIndex >= _pool.Count) + { + _pool.Add(new List()); + } + + var list = _pool[_poolIndex]; + _poolIndex++; + return list; + } + + public static void Return(List list) + { + if (list == null) + throw new ArgumentNullException(nameof(list)); + + list.Clear(); + } + + public static void Free() + { + _poolIndex = 0; + } +} \ No newline at end of file diff --git a/Quill/MeshMemoryArena.cs b/Quill/MeshMemoryArena.cs new file mode 100644 index 0000000..9caecb8 --- /dev/null +++ b/Quill/MeshMemoryArena.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using Prowl.Quill.External.LibTessDotNet; + +namespace Prowl.Quill +{ + public abstract class Poolable + { + public abstract void Reset(); + + public virtual void OnFree() + { + } + } + + public static class MemoryArena + { + private static Dictionary> _arena = new Dictionary>(); + private static Dictionary _indices = new Dictionary(); + + public static void AddType(int size = 0) where T : Poolable + { + _arena.TryAdd(typeof(T), new List(size)); + _indices.TryAdd(typeof(T), 0); + } + + public static void AddTypes(Type[] typesToAdd) + { + foreach (Type type in typesToAdd) + { + if (!type.IsAssignableFrom(typeof(Poolable))) + throw new Exception($"{type} does not inherit from Poolabe and cannot be added!"); + + _arena.TryAdd(type, new List()); + _indices.TryAdd(type, 0); + } + } + + public static T Get() where T : Poolable, new() + { + if (!_arena.ContainsKey(typeof(T))) + throw new Exception( + $"Dictionary does not contain the type {typeof(T)}, but you are trying to get an object!"); + + List list = _arena[typeof(T)]; + int idx = _indices[typeof(T)]; + + if (_indices[typeof(T)] >= list.Count) + { + list.Add(new T()); + } + + var obj = list[idx]; + obj.Reset(); + + _indices[typeof(T)]++; + + return (T)obj; + } + + public static void Free() + { + if (!_arena.ContainsKey(typeof(T))) + throw new Exception( + $"Dictionary does not contain the type {typeof(T)}, but you are trying to get an object!"); + + _indices[typeof(T)] = 0; + } + + public static void FreeTypes(Type[] typesToFree) + { + foreach (Type type in typesToFree) + { + _indices[type] = 0; + } + } + + public static void FreeAll() + { + foreach (Type type in _arena.Keys) + { + _indices[type] = 0; + } + } + } +} \ No newline at end of file diff --git a/Quill/PolylineMesher.cs b/Quill/PolylineMesher.cs index 78fb1bb..47c95af 100644 --- a/Quill/PolylineMesher.cs +++ b/Quill/PolylineMesher.cs @@ -2,6 +2,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; +using Vector2 = Prowl.Vector.Vector2; namespace Prowl.Quill { @@ -14,6 +16,8 @@ internal static class PolylineMesher private static List _triangles; [ThreadStatic] private static List _polySegments; + [ThreadStatic] + private static List> _pointsListPool = new List>(); private static List TriangleCache => _triangles ??= new List(); private static List PolySegmentCache => _polySegments ??= new List(); @@ -48,12 +52,18 @@ public Triangle(Vector2 v1, Vector2 v2, Vector2 v3, Vector2 uv1, Vector2 uv2, Ve /// The miter limit (used when jointStyle is Miter) /// Whether to allow overlapping vertices for better results with close points /// A list of triangles describing the path - public static IReadOnlyList Create(List points, double thickness, double pixelWidth, System.Drawing.Color color, JointStyle jointStyle = JointStyle.Miter, double miterLimit = 4.0, bool allowOverlap = false, EndCapStyle startCap = EndCapStyle.Butt, EndCapStyle endCap = EndCapStyle.Butt, List dashPattern = null, double dashOffset = 0.0) + public static ReadOnlySpan Create(List points, double thickness, double pixelWidth, System.Drawing.Color color, JointStyle jointStyle = JointStyle.Miter, double miterLimit = 4.0, bool allowOverlap = false, EndCapStyle startCap = EndCapStyle.Butt, EndCapStyle endCap = EndCapStyle.Butt, List dashPattern = null, double dashOffset = 0.0) { TriangleCache.Clear(); if (points.Count < 2 || thickness <= 0 || color.A == 0) - return TriangleCache; + { +#if NET5_0_OR_GREATER + return CollectionsMarshal.AsSpan(TriangleCache); +#else + return TriangleCache.ToArray(); +#endif + } // Handle thin lines with alpha adjustment instead of thickness reduction if (thickness < 1.0) @@ -67,7 +77,18 @@ public static IReadOnlyList Create(List points, double thickn var dashSegments = GenerateDashSegments(points, dashPattern, dashOffset, HalfPixel); if (dashSegments.Count == 0) - return TriangleCache; + { + foreach (List list in dashSegments) + { + ReturnVector2PointList(list); + } + +#if NET5_0_OR_GREATER + return CollectionsMarshal.AsSpan(TriangleCache); +#else + return TriangleCache.ToArray(); +#endif + } foreach (var dashPoints in dashSegments) { @@ -84,7 +105,16 @@ public static IReadOnlyList Create(List points, double thickn color, isClosedPolyline); } - return TriangleCache; + foreach (List list in dashSegments) + { + ReturnVector2PointList(list); + } + +#if NET5_0_OR_GREATER + return CollectionsMarshal.AsSpan(TriangleCache); +#else + return TriangleCache.ToArray(); +#endif } private static void CreatePolySegments(List points, double halfThickness) @@ -217,33 +247,51 @@ private static void FinalizeLastSegment(PolySegment segment, double endU, ref Se data.EndUV2 = new Vector2(endU, 1); } - + private static List> _currentDashPoints = new List>(); + private static int _currentDashPointIdx = 0; private static List> GenerateDashSegments(List points, List dashPattern, double dashOffset, Vector2 halfPixelOffset) { - var allDashSegments = new List>(); + var allDashSegments = _currentDashPoints; + allDashSegments.Clear(); + _currentDashPointIdx = 0; if (points.Count < 2 || dashPattern == null || dashPattern.Count == 0 || dashPattern.Sum() <= Epsilon) { if (points.Count >= 2) { - var singleSegment = points.Select(p => p + halfPixelOffset).ToList(); - allDashSegments.Add(singleSegment); + for(int i = 0; i < points.Count; i++) + { + points[i] += halfPixelOffset; + } + allDashSegments.Add(points); } return allDashSegments; } var dashState = InitializeDashState(dashPattern, dashOffset); - var currentDashPoints = new List(); + + if (_currentDashPointIdx >= _currentDashPoints.Count) + { + _currentDashPoints.Add(GetVector2PointList()); + } + var currentDashPoints = _currentDashPoints[_currentDashPointIdx]; + _currentDashPointIdx++; + currentDashPoints.Clear(); ProcessLineSegments(points, halfPixelOffset, dashPattern, dashState, currentDashPoints, allDashSegments); // Add final dash segment if we're in dash state if (dashState.IsInDash && currentDashPoints.Count >= 2) { - allDashSegments.Add(new List(currentDashPoints)); + var list = GetVector2PointList(); + foreach (Vector2 vec in currentDashPoints) + { + list.Add(vec); + } + allDashSegments.Add(list); } - allDashSegments.RemoveAll(dash => dash.Count < 2); + // allDashSegments.RemoveAll(dash => dash.Count < 2); return allDashSegments; } @@ -293,7 +341,7 @@ private static DashState InitializeDashState(List dashPattern, double da return state; } - private static void ProcessLineSegments(List points, Vector2 halfPixelOffset, List dashPattern, DashState dashState, List currentDashPoints, List> allDashSegments) + private static bool ProcessLineSegments(List points, Vector2 halfPixelOffset, List dashPattern, DashState dashState, List currentDashPoints, List> allDashSegments) { for (int i = 0; i < points.Count - 1; i++) { @@ -313,6 +361,8 @@ private static void ProcessLineSegments(List points, Vector2 halfPixelO ProcessSingleSegment(p1, segmentDirection, segmentLength, halfPixelOffset, dashPattern, ref dashState, ref distanceTraversed, currentDashPoints, allDashSegments); } + + return points.Count >= 2; } private static void ProcessSingleSegment(Vector2 segmentStart, Vector2 segmentDirection, double segmentLength, Vector2 halfPixelOffset, List dashPattern, ref DashState dashState, ref double distanceTraversed, List currentDashPoints, List> allDashSegments) @@ -392,7 +442,12 @@ private static void AdvanceDashPattern(ref DashState dashState, List das // End of dash - save current segment if (currentDashPoints.Count >= 2) { - allDashSegments.Add(new List(currentDashPoints)); + var list = GetVector2PointList(); + foreach (Vector2 vec in currentDashPoints) + { + list.Add(vec); + } + allDashSegments.Add(list); } currentDashPoints.Clear(); } @@ -754,5 +809,30 @@ private static double CalculateAngle(Vector2 a, Vector2 b) double cosAngle = dot / Math.Sqrt(magASq * magBSq); return Math.Acos(Math.Max(-1.0, Math.Min(1.0, cosAngle))); } + + private static int _currIdx = 0; + private static List GetVector2PointList() + { + List list; + if (_currIdx == 0) + { + list = new List(); + _pointsListPool.Add(list); + } + else + { + list = _pointsListPool[_currIdx]; + _currIdx--; + } + + return list; + } + + private static void ReturnVector2PointList(List list) + { + if (_currIdx + 1 < _pointsListPool.Count) _currIdx++; + + list.Clear(); + } } } \ No newline at end of file diff --git a/Quill/SVGParser.cs b/Quill/SVGParser.cs index add95f4..adcb701 100644 --- a/Quill/SVGParser.cs +++ b/Quill/SVGParser.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; using System.Collections.Generic; using System.Xml; using System.IO; @@ -17,6 +18,7 @@ public class SvgElement public Dictionary Attributes { get; } public List Children { get; } public DrawCommand[] drawCommands; + public int drawCommandCount = 0; public Color stroke; public Color fill; @@ -225,6 +227,8 @@ public override void Parse() public class SvgPathElement : SvgElement { + private string _parametersString = ""; + private string _commandSegment = ""; public override void Parse() { base.Parse(); @@ -237,14 +241,15 @@ public override void Parse() throw new InvalidDataException(); var matches = Regex.Matches(pathData, @"([A-Za-z])([-0-9.,\s]*)"); - drawCommands = new DrawCommand[matches.Count]; + // drawCommands = new DrawCommand[matches.Count]; + drawCommands = ArrayPool.Shared.Rent(matches.Count); for (int i = 0; i < matches.Count; i++) { var match = matches[i]; var drawCommand = new DrawCommand(); - var commandSegment = match.Groups[1].Value + match.Groups[2].Value.Trim(); - var parametersString = commandSegment.Length > 1 ? commandSegment.Substring(1).Trim() : ""; - var command = commandSegment[0]; + _commandSegment = match.Groups[1].Value + match.Groups[2].Value.Trim(); + _parametersString = _commandSegment.Length > 1 ? _commandSegment.Substring(1).Trim() : ""; + var command = _commandSegment[0]; drawCommand.relative = char.IsLower(command); @@ -264,18 +269,25 @@ public override void Parse() //Console.WriteLine($"{command} {parametersString}"); - if (!string.IsNullOrEmpty(parametersString)) + if (!string.IsNullOrEmpty(_parametersString)) { - var param = new List(); - var matches2 = Regex.Matches(parametersString, @"[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?"); + var matches2 = Regex.Matches(_parametersString, @"[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?"); + int totalMatchArraySize = 0; + for (int j = 0; j < matches2.Count; j++) for (int k = 0; k < matches2[j].Groups.Count; k++) - param.Add(double.Parse(matches2[j].Groups[k].ToString(), CultureInfo.InvariantCulture)); + totalMatchArraySize++; + + var param = ArrayPool.Shared.Rent(totalMatchArraySize); + for (int j = 0; j < matches2.Count; j++) + for (int k = 0; k < matches2[j].Groups.Count; k++) + param[j + k] = (double.Parse(matches2[j].Groups[k].ToString(), CultureInfo.InvariantCulture)); - drawCommand.param = param.ToArray(); + drawCommand.param = param; } //Console.WriteLine(drawCommand.ToString()); drawCommands[i] = drawCommand; + drawCommandCount++; //if (!ValidateParameterCount(drawCommand)) //{ // Console.WriteLine(pathData); diff --git a/Quill/SVGRenderer.cs b/Quill/SVGRenderer.cs index b0b1d98..ff1232e 100644 --- a/Quill/SVGRenderer.cs +++ b/Quill/SVGRenderer.cs @@ -1,23 +1,28 @@ using Prowl.Vector; using System; +using System.Buffers; +using System.Collections.Generic; using System.Drawing; namespace Prowl.Quill { public static class SVGRenderer { + private static List _elementsToDraw = new List(); + public static Color currentColor = Color.White; - + //for debug public static bool debug; public static void DrawToCanvas(Canvas canvas, Vector2 position, SvgElement svgElement) { - var elements = svgElement.Flatten(); + _elementsToDraw.Clear(); + _elementsToDraw = svgElement.Flatten(); - for (var i = 0; i < elements.Count; i++) + for (var i = 0; i < _elementsToDraw.Count; i++) { - var element = elements[i]; + var element = _elementsToDraw[i]; SetState(canvas, element); @@ -86,7 +91,7 @@ static void DrawElement(Canvas canvas, SvgPathElement element, Vector2 position) canvas.BeginPath(); var lastControlPoint = Vector2.zero; - for (var i = 0; i < element.drawCommands.Length; i++) + for (var i = 0; i < element.drawCommandCount; i++) { var cmd = element.drawCommands[i]; var currentPoint = i == 0 ? position : canvas.CurrentPoint; @@ -133,7 +138,12 @@ static void DrawElement(Canvas canvas, SvgPathElement element, Vector2 position) canvas.ClosePath(); break; } + + if(cmd.param != null) + ArrayPool.Shared.Return(cmd.param); } + + ArrayPool.Shared.Return(element.drawCommands); } static Vector2 ReflectPoint(Vector2 mirrorPoint, Vector2 inputPoint) diff --git a/Quill/TextRenderer.cs b/Quill/TextRenderer.cs index 88825ca..78c8104 100644 --- a/Quill/TextRenderer.cs +++ b/Quill/TextRenderer.cs @@ -78,7 +78,7 @@ public void DrawQuads(object texture, ReadOnlySpan vertice var c = vertices[indices[i + 2]]; // Transform vertices through the current transform matrix - int index = _canvas.Vertices.Count; + int index = _canvas.VertexCount; var vertA = new Vertex(_canvas.TransformPoint(new Vector2(a.Position.X, a.Position.Y)), a.TextureCoordinate, ToColor(a.Color)); var vertB = new Vertex(_canvas.TransformPoint(new Vector2(b.Position.X, b.Position.Y)), b.TextureCoordinate, ToColor(b.Color)); var vertC = new Vertex(_canvas.TransformPoint(new Vector2(c.Position.X, c.Position.Y)), c.TextureCoordinate, ToColor(c.Color)); diff --git a/Samples/Common/CanvasDemo.cs b/Samples/Common/CanvasDemo.cs index 2c2963a..b935ddf 100644 --- a/Samples/Common/CanvasDemo.cs +++ b/Samples/Common/CanvasDemo.cs @@ -464,6 +464,9 @@ private void DrawShapesDemo(double x, double y, double width, double height) _canvas.RestoreState(); } + private List _dashPattern01 = new List() { 10, 5, 2, 2 }; + private List _dashPattern02 = new List() { 5, 5 }; + private double[] _lineWidths = {0.25f, 1.0f, 3.0f, 4.0f}; private void DrawLineStylesDemo(double x, double y, double width, double height) { // Draw group background and title @@ -475,10 +478,10 @@ private void DrawLineStylesDemo(double x, double y, double width, double height) _canvas.SetStrokeColor(Color.FromArgb(255, 255, 255, 255)); // Thin line - double[] widths = [0.25f, 1.0f, 3.0f, 4.0f]; + // double[] widths = [0.25f, 1.0f, 3.0f, 4.0f]; for (int i=0; i<4; i++) { - _canvas.SetStrokeWidth(widths[i]); + _canvas.SetStrokeWidth(_lineWidths[i]); _canvas.BeginPath(); _canvas.MoveTo(0, -45 + (i * 17)); _canvas.LineTo(160, -55 + (i * 17)); @@ -501,14 +504,14 @@ private void DrawLineStylesDemo(double x, double y, double width, double height) _canvas.MoveTo(0, 40 + 5); _canvas.LineTo(160, 40 - 5); _canvas.SetStrokeColor(Color.FromArgb(255, 255, 100, 100)); - _canvas.SetStrokeDash(new List() { 10, 5, 2, 2 }, 0); + _canvas.SetStrokeDash(_dashPattern01, 0); _canvas.Stroke(); _canvas.BeginPath(); _canvas.MoveTo(0, 60 + 5); _canvas.LineTo(160, 60 - 5); _canvas.SetStrokeColor(Color.FromArgb(255, 100, 100, 255)); - _canvas.SetStrokeDash(new List() { 5, 5 }, 0); + _canvas.SetStrokeDash(_dashPattern02, 0); _canvas.Stroke(); _canvas.RestoreState(); @@ -581,6 +584,7 @@ void DrawHeartbeat(JointStyle join, double yOffset, Color color) _canvas.SetStrokeJoint(join); _canvas.SetStrokeColor(color); _canvas.SetStrokeWidth(5); + _canvas.SetStrokeDash(new List() { 10, 5, 2, 2 }); // Base animation for spikes double baseAnim = Math.Sin(_time * 1) * 0.5f + 0.5f; // 0 to 1 range diff --git a/Samples/OpenTKExample/CanvasRenderer.cs b/Samples/OpenTKExample/CanvasRenderer.cs index fdcbb98..b40272e 100644 --- a/Samples/OpenTKExample/CanvasRenderer.cs +++ b/Samples/OpenTKExample/CanvasRenderer.cs @@ -295,7 +295,7 @@ public void RenderCalls(Canvas canvas, IReadOnlyList drawCalls) // Upload vertex data GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject); - GL.BufferData(BufferTarget.ArrayBuffer, canvas.Vertices.Count * Vertex.SizeInBytes, canvas.Vertices.ToArray(), BufferUsageHint.StreamDraw); + GL.BufferData(BufferTarget.ArrayBuffer, canvas.VertexCount * Vertex.SizeInBytes, canvas.Vertices, BufferUsageHint.StreamDraw); // Set up vertex attributes // Position attribute @@ -312,7 +312,7 @@ public void RenderCalls(Canvas canvas, IReadOnlyList drawCalls) // Upload index data GL.BindBuffer(BufferTarget.ElementArrayBuffer, _elementBufferObject); - GL.BufferData(BufferTarget.ElementArrayBuffer, canvas.Indices.Count * sizeof(uint), canvas.Indices.ToArray(), BufferUsageHint.StreamDraw); + GL.BufferData(BufferTarget.ElementArrayBuffer, canvas.IndicesCount * sizeof(uint), canvas.Indices, BufferUsageHint.StreamDraw); // Active texture unit for sampling GL.ActiveTexture(TextureUnit.Texture0); diff --git a/Samples/SilkNETExample/SilkNetRenderer.cs b/Samples/SilkNETExample/SilkNetRenderer.cs index 9e7861c..ca2a091 100644 --- a/Samples/SilkNETExample/SilkNetRenderer.cs +++ b/Samples/SilkNETExample/SilkNetRenderer.cs @@ -317,7 +317,7 @@ private unsafe void SetMatrix4Uniform(int location, Prowl.Vector.Matrix4x4 matri public unsafe void RenderCalls(Canvas canvas, IReadOnlyList drawCalls) { - if (drawCalls.Count == 0 || canvas.Vertices.Count == 0 || canvas.Indices.Count == 0) + if (drawCalls.Count == 0 || canvas.VertexCount == 0 || canvas.IndicesCount == 0) return; // Set up rendering state @@ -354,11 +354,11 @@ private unsafe void UploadGeometryData(Canvas canvas) { // Upload vertices _gl.BindBuffer(BufferTargetARB.ArrayBuffer, _vbo); - fixed (Vertex* vertexPtr = canvas.Vertices.ToArray()) + fixed (Vertex* vertexPtr = canvas.Vertices) { _gl.BufferData( BufferTargetARB.ArrayBuffer, - (nuint)(canvas.Vertices.Count * Vertex.SizeInBytes), + (nuint)(canvas.VertexCount * Vertex.SizeInBytes), vertexPtr, BufferUsageARB.StreamDraw ); @@ -366,11 +366,11 @@ private unsafe void UploadGeometryData(Canvas canvas) // Upload indices _gl.BindBuffer(BufferTargetARB.ElementArrayBuffer, _ebo); - fixed (uint* indexPtr = canvas.Indices.ToArray()) + fixed (uint* indexPtr = canvas.Indices) { _gl.BufferData( BufferTargetARB.ElementArrayBuffer, - (nuint)(canvas.Indices.Count * sizeof(uint)), + (nuint)(canvas.IndicesCount * sizeof(uint)), indexPtr, BufferUsageARB.StreamDraw );