Skip to content

Commit

Permalink
add caching to DrawShapedText
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelRumpler committed Oct 8, 2024
1 parent 4d4e70d commit a12cfea
Show file tree
Hide file tree
Showing 3 changed files with 407 additions and 3 deletions.
181 changes: 181 additions & 0 deletions binding/Binding.Shared/HashCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,5 +145,186 @@ public int ToHashCode ()
hash = MixFinal (hash);
return (int)hash;
}

public static int Combine<T1> (T1 value1)
{
// Provide a way of diffusing bits from something with a limited
// input hash space. For example, many enums only have a few
// possible hashes, only using the bottom few bits of the code. Some
// collections are built on the assumption that hashes are spread
// over a larger space, so diffusing the bits may help the
// collection work more efficiently.

uint hc1 = (uint)(value1?.GetHashCode () ?? 0);

uint hash = MixEmptyState ();
hash += 4;

hash = QueueRound (hash, hc1);

hash = MixFinal (hash);
return (int)hash;
}

public static int Combine<T1, T2> (T1 value1, T2 value2)
{
uint hc1 = (uint)(value1?.GetHashCode () ?? 0);
uint hc2 = (uint)(value2?.GetHashCode () ?? 0);

uint hash = MixEmptyState ();
hash += 8;

hash = QueueRound (hash, hc1);
hash = QueueRound (hash, hc2);

hash = MixFinal (hash);
return (int)hash;
}

public static int Combine<T1, T2, T3> (T1 value1, T2 value2, T3 value3)
{
uint hc1 = (uint)(value1?.GetHashCode () ?? 0);
uint hc2 = (uint)(value2?.GetHashCode () ?? 0);
uint hc3 = (uint)(value3?.GetHashCode () ?? 0);

uint hash = MixEmptyState ();
hash += 12;

hash = QueueRound (hash, hc1);
hash = QueueRound (hash, hc2);
hash = QueueRound (hash, hc3);

hash = MixFinal (hash);
return (int)hash;
}

public static int Combine<T1, T2, T3, T4> (T1 value1, T2 value2, T3 value3, T4 value4)
{
uint hc1 = (uint)(value1?.GetHashCode () ?? 0);
uint hc2 = (uint)(value2?.GetHashCode () ?? 0);
uint hc3 = (uint)(value3?.GetHashCode () ?? 0);
uint hc4 = (uint)(value4?.GetHashCode () ?? 0);

Initialize (out uint v1, out uint v2, out uint v3, out uint v4);

v1 = Round (v1, hc1);
v2 = Round (v2, hc2);
v3 = Round (v3, hc3);
v4 = Round (v4, hc4);

uint hash = MixState (v1, v2, v3, v4);
hash += 16;

hash = MixFinal (hash);
return (int)hash;
}

public static int Combine<T1, T2, T3, T4, T5> (T1 value1, T2 value2, T3 value3, T4 value4, T5 value5)
{
uint hc1 = (uint)(value1?.GetHashCode () ?? 0);
uint hc2 = (uint)(value2?.GetHashCode () ?? 0);
uint hc3 = (uint)(value3?.GetHashCode () ?? 0);
uint hc4 = (uint)(value4?.GetHashCode () ?? 0);
uint hc5 = (uint)(value5?.GetHashCode () ?? 0);

Initialize (out uint v1, out uint v2, out uint v3, out uint v4);

v1 = Round (v1, hc1);
v2 = Round (v2, hc2);
v3 = Round (v3, hc3);
v4 = Round (v4, hc4);

uint hash = MixState (v1, v2, v3, v4);
hash += 20;

hash = QueueRound (hash, hc5);

hash = MixFinal (hash);
return (int)hash;
}

public static int Combine<T1, T2, T3, T4, T5, T6> (T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6)
{
uint hc1 = (uint)(value1?.GetHashCode () ?? 0);
uint hc2 = (uint)(value2?.GetHashCode () ?? 0);
uint hc3 = (uint)(value3?.GetHashCode () ?? 0);
uint hc4 = (uint)(value4?.GetHashCode () ?? 0);
uint hc5 = (uint)(value5?.GetHashCode () ?? 0);
uint hc6 = (uint)(value6?.GetHashCode () ?? 0);

Initialize (out uint v1, out uint v2, out uint v3, out uint v4);

v1 = Round (v1, hc1);
v2 = Round (v2, hc2);
v3 = Round (v3, hc3);
v4 = Round (v4, hc4);

uint hash = MixState (v1, v2, v3, v4);
hash += 24;

hash = QueueRound (hash, hc5);
hash = QueueRound (hash, hc6);

hash = MixFinal (hash);
return (int)hash;
}

public static int Combine<T1, T2, T3, T4, T5, T6, T7> (T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7)
{
uint hc1 = (uint)(value1?.GetHashCode () ?? 0);
uint hc2 = (uint)(value2?.GetHashCode () ?? 0);
uint hc3 = (uint)(value3?.GetHashCode () ?? 0);
uint hc4 = (uint)(value4?.GetHashCode () ?? 0);
uint hc5 = (uint)(value5?.GetHashCode () ?? 0);
uint hc6 = (uint)(value6?.GetHashCode () ?? 0);
uint hc7 = (uint)(value7?.GetHashCode () ?? 0);

Initialize (out uint v1, out uint v2, out uint v3, out uint v4);

v1 = Round (v1, hc1);
v2 = Round (v2, hc2);
v3 = Round (v3, hc3);
v4 = Round (v4, hc4);

uint hash = MixState (v1, v2, v3, v4);
hash += 28;

hash = QueueRound (hash, hc5);
hash = QueueRound (hash, hc6);
hash = QueueRound (hash, hc7);

hash = MixFinal (hash);
return (int)hash;
}

public static int Combine<T1, T2, T3, T4, T5, T6, T7, T8> (T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8)
{
uint hc1 = (uint)(value1?.GetHashCode () ?? 0);
uint hc2 = (uint)(value2?.GetHashCode () ?? 0);
uint hc3 = (uint)(value3?.GetHashCode () ?? 0);
uint hc4 = (uint)(value4?.GetHashCode () ?? 0);
uint hc5 = (uint)(value5?.GetHashCode () ?? 0);
uint hc6 = (uint)(value6?.GetHashCode () ?? 0);
uint hc7 = (uint)(value7?.GetHashCode () ?? 0);
uint hc8 = (uint)(value8?.GetHashCode () ?? 0);

Initialize (out uint v1, out uint v2, out uint v3, out uint v4);

v1 = Round (v1, hc1);
v2 = Round (v2, hc2);
v3 = Round (v3, hc3);
v4 = Round (v4, hc4);

v1 = Round (v1, hc5);
v2 = Round (v2, hc6);
v3 = Round (v3, hc7);
v4 = Round (v4, hc8);

uint hash = MixState (v1, v2, v3, v4);
hash += 32;

hash = MixFinal (hash);
return (int)hash;
}
}
}
80 changes: 77 additions & 3 deletions source/SkiaSharp.HarfBuzz/SkiaSharp.HarfBuzz/CanvasExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Collections.Concurrent;
using System.Threading;

namespace SkiaSharp.HarfBuzz
{
Expand Down Expand Up @@ -30,8 +32,10 @@ public static void DrawShapedText(this SKCanvas canvas, string text, float x, fl
if (string.IsNullOrEmpty(text))
return;

using var shaper = new SKShaper(font.Typeface);
var shaper = GetShaper(font.Typeface);
canvas.DrawShapedText(shaper, text, x, y, textAlign, font, paint);
if (cacheDuration == 0)
shaper.Dispose();
}

[Obsolete("Use DrawShapedText(SKShaper shaper, string text, SKPoint p, SKTextAlign textAlign, SKFont font, SKPaint paint) instead.")]
Expand Down Expand Up @@ -70,7 +74,7 @@ public static void DrawShapedText(this SKCanvas canvas, SKShaper shaper, string
font.Typeface = shaper.Typeface;

// shape the text
var result = shaper.Shape(text, x, y, font);
var result = GetShapeResult(shaper, text, font);

// create the text blob
using var builder = new SKTextBlobBuilder();
Expand Down Expand Up @@ -99,7 +103,77 @@ public static void DrawShapedText(this SKCanvas canvas, SKShaper shaper, string
}

// draw the text
canvas.DrawText(textBlob, xOffset, 0, paint);
canvas.DrawText(textBlob, x + xOffset, y, paint);

if (clearCacheTimer is null && cacheDuration > 0)
clearCacheTimer = new Timer(_ => ClearCache(), null, 0, cacheDuration);
}

private static uint cacheDuration = 0;

public static void SetShaperCacheDuration(this SKCanvas canvas, uint milliseconds) => SetShaperCacheDuration(milliseconds);
public static void SetShaperCacheDuration(uint milliseconds)
{
cacheDuration = milliseconds;

clearCacheTimer?.Change(0, cacheDuration);
}

private static readonly ConcurrentDictionary<int, (SKShaper shaper, DateTime cachedAt)> shaperCache = new();
private static readonly ConcurrentDictionary<int, (SKShaper.Result shapeResult, DateTime cachedAt)> shapeResultCache = new();

private static SKShaper GetShaper(SKTypeface typeface)
{
var key = HashCode.Combine(typeface.FamilyName, typeface.IsBold, typeface.IsItalic);

var shaper = shaperCache.TryGetValue(key, out var value)
? value.shaper
: new SKShaper(typeface);

if (cacheDuration > 0)
shaperCache[key] = (shaper, DateTime.Now); // update timestamp
return shaper;
}

private static SKShaper.Result GetShapeResult(SKShaper shaper, string text, SKFont font)
{
var key = HashCode.Combine(font.Typeface.FamilyName, font.Size, font.Typeface.IsBold, font.Typeface.IsItalic, text);

var result = shapeResultCache.TryGetValue(key, out var value)
? value.shapeResult
: shaper.Shape(text, 0, 0, font);

if (cacheDuration > 0)
shapeResultCache[key] = (result, DateTime.Now); // update timestamp
return result;
}

private static Timer clearCacheTimer = null;

private static void ClearCache()
{
var outdated = DateTime.Now - TimeSpan.FromMilliseconds(cacheDuration);

foreach (var kv in shaperCache.ToArray())
{
if (kv.Value.cachedAt < outdated)
{
if (shaperCache.TryRemove(kv.Key, out var entry))
entry.shaper.Dispose();
}
}

foreach (var kv in shapeResultCache.ToArray())
{
if (kv.Value.cachedAt < outdated)
shapeResultCache.TryRemove(kv.Key, out var _);
}

if ((shaperCache.IsEmpty && shapeResultCache.IsEmpty) || cacheDuration == 0)
{
clearCacheTimer?.Dispose();
clearCacheTimer = null;
}
}
}
}
Loading

0 comments on commit a12cfea

Please sign in to comment.