Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
1555ba9
removed allocations in the DrawText function
MiniMe453 Sep 13, 2025
934a61e
pooling for text layouts
MiniMe453 Sep 13, 2025
232517d
use predefined arrays in image drawing
MiniMe453 Sep 13, 2025
6ea5c76
use collection marshalling to reduce .ToArray() calls
MiniMe453 Sep 13, 2025
f7d6efc
use a static string builder over a new one each frame
MiniMe453 Sep 13, 2025
15c734a
use static lists for markdown engine
MiniMe453 Sep 13, 2025
addd165
moved font selector to textlayout, not markdown layout engine
MiniMe453 Sep 13, 2025
147797d
move get cols to a function, not delegate
MiniMe453 Sep 13, 2025
45129ce
remove delegate creation inside of the layout text function
MiniMe453 Sep 13, 2025
aa4876c
cleaned up text layout
MiniMe453 Sep 13, 2025
2e5e21b
implemented pooling for lines, textlayout, and glyphinstances
MiniMe453 Sep 14, 2025
b5115f8
first object pooling for markdown draw commands
MiniMe453 Sep 14, 2025
bc67a96
changed drawcommands to classes to remove allocations
MiniMe453 Sep 14, 2025
cf4b618
use array pool when assigning link hitboxes
MiniMe453 Sep 14, 2025
e996ae0
use array pool for bitmap rasterization
MiniMe453 Sep 14, 2025
6ecbfbf
use object pooling for TextLayoutSettings
MiniMe453 Sep 14, 2025
d48e80f
implemented a document cache for markdown parsing
MiniMe453 Sep 14, 2025
642b65f
removed some unneeded lines
MiniMe453 Sep 14, 2025
91a7fb1
using pooling for inline lists in markdown layout
MiniMe453 Sep 14, 2025
c47e622
small cleanup for markdown parser
MiniMe453 Sep 14, 2025
2125b26
removed commented line
MiniMe453 Sep 14, 2025
d542af9
use object pooling for MarkdownDisplayList
MiniMe453 Sep 14, 2025
70de99b
use object pooling for active edges
MiniMe453 Sep 14, 2025
087375c
fix: made text layout settings a list to remove memory leak
MiniMe453 Sep 26, 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
34 changes: 26 additions & 8 deletions Scribe/FontSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -389,14 +389,14 @@ public TextLayout CreateLayout(string text, TextLayoutSettings settings)
{
if (string.IsNullOrEmpty(text))
{
var empty = new TextLayout();
var empty = TextLayout.Get();
empty.UpdateLayout(text, settings, this);
return empty;
}

if (!CacheLayouts)
{
var direct = new TextLayout();
var direct = TextLayout.Get();
direct.UpdateLayout(text, settings, this);
return direct;
}
Expand All @@ -406,7 +406,7 @@ public TextLayout CreateLayout(string text, TextLayoutSettings settings)
if (layoutCache.TryGetValue(key, out var cached))
return cached;

var layout = new TextLayout();
var layout = TextLayout.Get();
layout.UpdateLayout(text, settings, this);

layoutCache.Add(key, layout);
Expand All @@ -423,12 +423,13 @@ LayoutCacheKey GenerateLayoutCacheKey(string text, TextLayoutSettings s)

public Vector2 MeasureText(string text, float pixelSize, FontFile font, float letterSpacing = 0)
{
var settings = TextLayoutSettings.Default;
var settings = TextLayoutSettings.Get();
settings.PixelSize = pixelSize;
settings.Font = font;
settings.LetterSpacing = letterSpacing;

var layout = CreateLayout(text, settings);
TextLayoutSettings.Return(settings);
return layout.Size;
}

Expand All @@ -440,12 +441,14 @@ public Vector2 MeasureText(string text, TextLayoutSettings settings)

public void DrawText(string text, Vector2 position, FontColor color, float pixelSize, FontFile font, float letterSpacing = 0)
{
var settings = TextLayoutSettings.Default;
var settings = TextLayoutSettings.Get();
settings.PixelSize = pixelSize;
settings.Font = font;
settings.LetterSpacing = letterSpacing;

DrawText(text, position, color, settings);

TextLayoutSettings.Return(settings);
}


Expand All @@ -457,12 +460,16 @@ public void DrawText(string text, Vector2 position, FontColor color, TextLayoutS
DrawLayout(layout, position, color);
}

List<IFontRenderer.Vertex> _vertices = new List<IFontRenderer.Vertex>();
List<int> _indices = new List<int>();
public void DrawLayout(TextLayout layout, Vector2 position, FontColor color)
{
if (layout.Lines.Count == 0) return;

var vertices = new List<IFontRenderer.Vertex>();
var indices = new List<int>();
_vertices.Clear();
var vertices = _vertices;
_indices.Clear();
var indices = _indices;
int vertexCount = 0;

foreach (var line in layout.Lines)
Expand All @@ -487,15 +494,26 @@ public void DrawLayout(TextLayout layout, Vector2 position, FontColor color)
vertices.Add(new IFontRenderer.Vertex(new Vector3(glyphX + glyphW, glyphY + glyphH, 0), color, new Vector2(glyph.U1, glyph.V1)));

// Create quad indices
indices.AddRange(new[] { vertexCount, vertexCount + 1, vertexCount + 2, vertexCount + 1, vertexCount + 3, vertexCount + 2 });
indices.Add(vertexCount);
indices.Add(vertexCount + 1);
indices.Add(vertexCount + 2);
indices.Add(vertexCount + 1);
indices.Add(vertexCount + 3);
indices.Add(vertexCount + 2);
vertexCount += 4;
}
}

if (vertices.Count > 0)
{
#if NET5_0_OR_GREATER
renderer.DrawQuads(atlasTexture, CollectionsMarshal.AsSpan(vertices), CollectionsMarshal.AsSpan(indices));
#else
renderer.DrawQuads(atlasTexture, vertices.ToArray(), indices.ToArray());
#endif
}

TextLayout.Return(layout);
}

#endregion
Expand Down
75 changes: 68 additions & 7 deletions Scribe/Internal/Bitmap.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using System;
using System.Buffers;
using System.Collections;
using System.Collections.Generic;
using System.Numerics;
using static Prowl.Scribe.Internal.Common;

Expand All @@ -11,7 +14,7 @@ internal class Bitmap
public int h; // Height in pixels
public int stride; // Row stride in bytes
public FakePtr<byte> pixels; // Pixel buffer (8-bit coverage)

private List<ActiveEdge> _createdActiveEdges = new List<ActiveEdge>();
/// <summary>Flatten curves → rasterize.</summary>
public void Rasterize(
float flatnessInPixels,
Expand Down Expand Up @@ -213,15 +216,16 @@ private static void FillActiveEdges(float[] scanline, int scanlineFill, int len,
e = e.next;
}
}

/// <summary>Main scanline rasterization over sorted edges. Uses a sentinel head for the active edge list.</summary>
private void RasterizeSortedEdges(FakePtr<Edge> edges, int count, int vsubsample, int offX, int offY)
{
// Active list sentinel (dummy head)
var head = new ActiveEdge { next = null };
var head = ActiveEdge.Get();
_createdActiveEdges.Add(head);

// Scratch scanlines: coverage + running sums
float[] scanline = w > 64 ? new float[w * 2 + 1] : new float[129];
float[] scanline = w > 64 ? ArrayPool<float>.Shared.Rent(w * 2 + 1) : ArrayPool<float>.Shared.Rent(129);
int scanlineFill = w; // second half holds column sums

int y = offY; // absolute y in destination bitmap
Expand Down Expand Up @@ -299,11 +303,20 @@ private void RasterizeSortedEdges(FakePtr<Edge> edges, int count, int vsubsample
++y;
++row;
}

foreach (ActiveEdge edge in _createdActiveEdges)
{
ActiveEdge.Return(edge);
}

_createdActiveEdges.Clear();
ArrayPool<float>.Shared.Return(scanline);
}

private ActiveEdge NewActive(Edge e, int offX, float startY)
{
var z = new ActiveEdge();
var z = ActiveEdge.Get();
_createdActiveEdges.Add(z);
float dxdy = (e.x1 - e.x0) / (e.y1 - e.y0); // safe: edges generated with y0 != y1
z.fdx = dxdy;
z.fdy = dxdy != 0.0f ? 1.0f / dxdy : 0.0f;
Expand All @@ -327,8 +340,9 @@ private void RasterizeContours(Vector2[] pts, int[] wcount, int windings, float
for (int i = 0; i < windings; ++i) totalEdges += wcount[i];

// +1 sentinel edge
var edges = new Edge[totalEdges + 1];
for (int i = 0; i < edges.Length; ++i) edges[i] = new Edge();
int edgesLength = totalEdges + 1;
var edges = ArrayPool<Edge>.Shared.Rent(edgesLength);
for (int i = 0; i < edgesLength; ++i) edges[i] = Edge.Get();

int n = 0; // number of produced edges
int m = 0; // running index into pts per winding
Expand Down Expand Up @@ -368,6 +382,13 @@ private void RasterizeContours(Vector2[] pts, int[] wcount, int windings, float
SortEdgesInsSort(edgePtr, n);

RasterizeSortedEdges(edgePtr, n, vsubsample, offX, offY);

for (int i = 0; i < edgesLength; i++)
{
Edge.Return(edges[i]);
}
ArrayPool<Edge>.Shared.Return(edges);
edgePtr.Clear(edgesLength);
}

private Vector2[] FlattenCurves(GlyphVertex[] vertices, int numVerts, float objspaceFlatness, out int[] contourLengths, out int numContours)
Expand Down Expand Up @@ -612,13 +633,53 @@ private class ActiveEdge
public float fx;
public ActiveEdge next;
public float sy;

private static Stack<ActiveEdge> _pool = new Stack<ActiveEdge>();

public static ActiveEdge Get()
{
if (_pool.TryPop(out ActiveEdge edge))
{
edge.next = null;
return edge;
}

return new ActiveEdge();
}

public static void Return(ActiveEdge edge)
{
if (edge == null)
throw new InvalidOperationException("Objects cannot be null when going into the stack!");
_pool.Push(edge);
}
}

private class Edge
{
public int invert;
public float x0, x1;
public float y0, y1;

private static Stack<Edge> _pool = new Stack<Edge>();

public static Edge Get()
{
if (_pool.TryPop(out Edge edge))
{
return edge;
}

return new Edge();
}

public static void Return(Edge edge)
{
if (edge == null)
throw new InvalidOperationException("Objects cannot be null when going into the stack!");

_pool.Push(edge);
}
}
}
}
Loading