Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
1b2e067
remove .ToArray() calls in Canvas class
MiniMe453 Sep 7, 2025
bc9ffeb
removed .NET Standard 2.1 support to allow for CollectionMarshalling
MiniMe453 Sep 7, 2025
d12b2e3
converted indices to array to remove .ToArray() call
MiniMe453 Sep 7, 2025
4390c87
use array for vertices
MiniMe453 Sep 7, 2025
44439b9
reimplemented pooling for meshes, edges, vertices, and faces
MiniMe453 Sep 7, 2025
8e473b0
pool subpath objects
MiniMe453 Sep 8, 2025
83477db
optimized canvas demo
MiniMe453 Sep 8, 2025
29c08ca
pool active regions
MiniMe453 Sep 8, 2025
d91f4ee
removed debug line from list pool
MiniMe453 Sep 8, 2025
eedb887
pool dictionary nodes to reduce GC pressure
MiniMe453 Sep 8, 2025
68792c0
testing array pooling in tess
MiniMe453 Sep 8, 2025
bc62103
return readonlyspan<> from polyline mesher
MiniMe453 Sep 8, 2025
0e4caff
use array pooling in Tesselation functions
MiniMe453 Sep 8, 2025
60bd383
properly free edges, vertices, and faces
MiniMe453 Sep 8, 2025
6b7a647
Optimized PriorityQueue.cs with array pooling and a single instance
MiniMe453 Sep 8, 2025
918b6cd
use more array pooling in tesselation function
MiniMe453 Sep 8, 2025
dda5587
optimize priority heap in the same way as priority queue
MiniMe453 Sep 8, 2025
2c6037f
use list pooling on stroke pattern list
MiniMe453 Sep 8, 2025
20adb9e
use a single instance for Dict<ActiveRegion>
MiniMe453 Sep 8, 2025
54c3b33
use ListPool<> for Vector2s
MiniMe453 Sep 8, 2025
e9c67b6
fixed double translations when building meshes
MiniMe453 Sep 13, 2025
1d424ec
fixed bug with incorrect dashing on lines
MiniMe453 Sep 13, 2025
645587e
Cleaned up code in Canvas Class
MiniMe453 Sep 13, 2025
dc2b086
Added support for .NET Standard 2.1 through compiler flags
MiniMe453 Sep 13, 2025
48e089a
fixed issues in the silknet renderer example
MiniMe453 Sep 13, 2025
fc4d4e7
use defined arrays in Tess.cs, not array pool
MiniMe453 Sep 13, 2025
56405fe
removed some unneeded comments in ListPool
MiniMe453 Sep 13, 2025
5386105
reduced .NET if statements to bare minimum
MiniMe453 Sep 13, 2025
b3109e2
Merge remote-tracking branch 'origin/main' into feature/mem-optimizat…
MiniMe453 Sep 13, 2025
b043647
begin debugging null reference error
MiniMe453 Sep 16, 2025
6728a32
fixed double transformation bug
MiniMe453 Sep 16, 2025
c6ef011
cleaned up pooling object code
MiniMe453 Sep 16, 2025
a33438a
fixed null reference error with mesh creation
MiniMe453 Sep 16, 2025
9e2c204
free all objects in mesh correctly
MiniMe453 Sep 16, 2025
2b8fbe9
use a memory arena instead of object pooling
MiniMe453 Sep 16, 2025
6b7c7c6
use readonlyspan in polylinemesher
MiniMe453 Sep 16, 2025
8129290
pool dictionary nodes
MiniMe453 Sep 16, 2025
9b151d8
use array pooling in tess, use one tess for canvas
MiniMe453 Sep 16, 2025
21a759e
fixed rendering code with correct vertex length
MiniMe453 Sep 16, 2025
36b7d96
pool active regions
MiniMe453 Sep 16, 2025
76cd8a2
optimized priority queue
MiniMe453 Sep 16, 2025
ccbe364
don't create new lists for dash patterns
MiniMe453 Sep 16, 2025
e134bd0
pool subpath objects
MiniMe453 Sep 16, 2025
ed30982
use the memory arena for dictionary nodes
MiniMe453 Sep 26, 2025
a7a7c2d
optimized use of lists in polyline mesher
MiniMe453 Sep 26, 2025
2fe71c8
use memory arena for active region
MiniMe453 Sep 26, 2025
2a945d4
use a predefined list for RoundedRectFilled
MiniMe453 Sep 26, 2025
2b0c705
optimized priority heap to use object pooling
MiniMe453 Sep 26, 2025
44abdcb
use array pooling in the SVGPathElement object
MiniMe453 Sep 26, 2025
04c1c22
fixed pooling for polylinemesher points list
MiniMe453 Sep 27, 2025
f640c9a
use a defined list for SVG elements to reduce allocation
MiniMe453 Sep 27, 2025
5616d24
used array pools in path element parser
MiniMe453 Sep 27, 2025
a959b39
use defined strings over new ones each frame
MiniMe453 Sep 27, 2025
3b0759c
Merge branch 'feature/mem-optimization-v3' into feature/mem-optimizat…
MiniMe453 Sep 27, 2025
2258b50
Added curly braces for namespaces
MiniMe453 Sep 27, 2025
ea50133
added support for .NET 2.1
MiniMe453 Sep 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
269 changes: 165 additions & 104 deletions Quill/Canvas.cs

Large diffs are not rendered by default.

18 changes: 16 additions & 2 deletions Quill/External/LibTessDotNet/Dict.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,19 @@ namespace LibTessDotNet
{
internal class Dict<TValue> where TValue : class
{
public class Node
public class Node : Poolable
{
internal TValue _key;
internal Node _prev, _next;

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);
Expand All @@ -62,6 +67,14 @@ public Dict(LessOrEqual leq)
_head._next = _head;
}

public void Reset()
{
var newNode = MemoryArena.Get<Node>();
_head = newNode;
_head._prev = _head;
_head._next = _head;
}

public Node Insert(TValue key)
{
return InsertBefore(_head, key);
Expand All @@ -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<Node>();
newNode._key = key;
newNode._next = node._next;
node._next._prev = newNode;
newNode._prev = node;
Expand Down
59 changes: 33 additions & 26 deletions Quill/External/LibTessDotNet/Mesh.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,21 @@ namespace Prowl.Quill.External

namespace LibTessDotNet
{
internal class Mesh : MeshUtils.Pooled<Mesh>
internal class Mesh : Poolable
{
internal MeshUtils.Vertex _vHead;
internal MeshUtils.Face _fHead;
internal MeshUtils.Edge _eHead, _eHeadSym;

public Mesh()
{
var v = _vHead = MeshUtils.Vertex.Create();
var f = _fHead = MeshUtils.Face.Create();
Reset();
}

public override void Reset()
{
var v = _vHead = MemoryArena.Get<MeshUtils.Vertex>();
var f = _fHead = MemoryArena.Get<MeshUtils.Face>();

var pair = MeshUtils.EdgePair.Create();
var e = _eHead = pair._e;
Expand Down Expand Up @@ -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();
}

/// <summary>
Expand Down Expand Up @@ -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)
Expand Down
84 changes: 44 additions & 40 deletions Quill/External/LibTessDotNet/MeshUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,39 +108,49 @@ public override string ToString()
}
}

internal static class MeshUtils
public abstract class Pooled<T> where T : Pooled<T>, new()
{
public const int Undef = ~0;

public abstract class Pooled<T> where T : Pooled<T>, new()
private static Stack<T> _stack = new Stack<T>();
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<T> _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<T>();
}
_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<Vertex>
public class Vertex : Poolable
{
internal Vertex _prev, _next;
internal Edge _anEdge;
Expand All @@ -164,7 +174,7 @@ public override void Reset()
}
}

public class Face : Pooled<Face>
public class Face : Poolable
{
internal Face _prev, _next;
internal Edge _anEdge;
Expand Down Expand Up @@ -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<Edge>();
pair._e._pair = pair;
pair._eSym = MeshUtils.Edge.Create();
pair._eSym = MemoryArena.Get<Edge>();
pair._eSym._pair = pair;
return pair;
}
Expand All @@ -218,7 +228,7 @@ public void Reset()
}
}

public class Edge : Pooled<Edge>
public class Edge : Poolable
{
internal EdgePair _pair;
internal Edge _next, _Sym, _Onext, _Lnext;
Expand Down Expand Up @@ -326,7 +336,7 @@ public static void Splice(Edge a, Edge b)
/// </summary>
public static void MakeVertex(Edge eOrig, Vertex vNext)
{
var vNew = MeshUtils.Vertex.Create();
var vNew = MemoryArena.Get<Vertex>();

// insert in circular doubly-linked list before vNext
var vPrev = vNext._prev;
Expand Down Expand Up @@ -355,7 +365,7 @@ public static void MakeVertex(Edge eOrig, Vertex vNext)
/// </summary>
public static void MakeFace(Edge eOrig, Face fNext)
{
var fNew = MeshUtils.Face.Create();
var fNew = MemoryArena.Get<Face>();

// insert in circular doubly-linked list before fNext
var fPrev = fNext._prev;
Expand Down Expand Up @@ -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();
}

/// <summary>
Expand All @@ -418,8 +426,6 @@ public static void KillVertex(Vertex vDel, Vertex newOrg)
var vNext = vDel._next;
vNext._prev = vPrev;
vPrev._next = vNext;

vDel.Free();
}

/// <summary>
Expand All @@ -442,8 +448,6 @@ public static void KillFace(Face fDel, Face newLFace)
var fNext = fDel._next;
fNext._prev = fPrev;
fPrev._next = fNext;

fDel.Free();
}

/// <summary>
Expand Down
60 changes: 57 additions & 3 deletions Quill/External/LibTessDotNet/PriorityHeap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
*/

using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;

namespace Prowl.Quill.External
Expand All @@ -53,6 +55,29 @@ protected class HandleElem
{
internal TValue _key;
internal int _node;
private static Stack<HandleElem> _pool = new Stack<HandleElem>();

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;
Expand All @@ -68,16 +93,45 @@ public PriorityHeap(int initialSize, LessOrEqual leq)
{
_leq = leq;

_nodes = new int[initialSize + 1];
_handles = new HandleElem[initialSize + 1];
_nodes = ArrayPool<int>.Shared.Rent(initialSize + 1);
_handles = ArrayPool<HandleElem>.Shared.Rent(initialSize + 1);

_size = 0;
_max = initialSize;
_freeList = 0;
_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<int>.Shared.Return(_nodes);
_nodes = ArrayPool<int>.Shared.Rent(initialSize + 1);

if(_handles != null)
{
foreach (HandleElem element in _handles)
{
if (element == null) continue;

HandleElem.Return(element);
}

ArrayPool<HandleElem>.Shared.Return(_handles);
}
_handles = ArrayPool<HandleElem>.Shared.Rent(initialSize + 1);

_size = 0;
_max = initialSize;
_freeList = 0;
_initialized = false;

_nodes[1] = 1;
_handles[1] = HandleElem.Get();
}

private void FloatDown(int curr)
Expand Down
Loading