Skip to content

Commit 32fb1e3

Browse files
add caching to DrawShapedText
1 parent 8de547b commit 32fb1e3

File tree

3 files changed

+407
-3
lines changed

3 files changed

+407
-3
lines changed

binding/Binding.Shared/HashCode.cs

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,5 +145,186 @@ public int ToHashCode ()
145145
hash = MixFinal (hash);
146146
return (int)hash;
147147
}
148+
149+
public static int Combine<T1> (T1 value1)
150+
{
151+
// Provide a way of diffusing bits from something with a limited
152+
// input hash space. For example, many enums only have a few
153+
// possible hashes, only using the bottom few bits of the code. Some
154+
// collections are built on the assumption that hashes are spread
155+
// over a larger space, so diffusing the bits may help the
156+
// collection work more efficiently.
157+
158+
uint hc1 = (uint)(value1?.GetHashCode () ?? 0);
159+
160+
uint hash = MixEmptyState ();
161+
hash += 4;
162+
163+
hash = QueueRound (hash, hc1);
164+
165+
hash = MixFinal (hash);
166+
return (int)hash;
167+
}
168+
169+
public static int Combine<T1, T2> (T1 value1, T2 value2)
170+
{
171+
uint hc1 = (uint)(value1?.GetHashCode () ?? 0);
172+
uint hc2 = (uint)(value2?.GetHashCode () ?? 0);
173+
174+
uint hash = MixEmptyState ();
175+
hash += 8;
176+
177+
hash = QueueRound (hash, hc1);
178+
hash = QueueRound (hash, hc2);
179+
180+
hash = MixFinal (hash);
181+
return (int)hash;
182+
}
183+
184+
public static int Combine<T1, T2, T3> (T1 value1, T2 value2, T3 value3)
185+
{
186+
uint hc1 = (uint)(value1?.GetHashCode () ?? 0);
187+
uint hc2 = (uint)(value2?.GetHashCode () ?? 0);
188+
uint hc3 = (uint)(value3?.GetHashCode () ?? 0);
189+
190+
uint hash = MixEmptyState ();
191+
hash += 12;
192+
193+
hash = QueueRound (hash, hc1);
194+
hash = QueueRound (hash, hc2);
195+
hash = QueueRound (hash, hc3);
196+
197+
hash = MixFinal (hash);
198+
return (int)hash;
199+
}
200+
201+
public static int Combine<T1, T2, T3, T4> (T1 value1, T2 value2, T3 value3, T4 value4)
202+
{
203+
uint hc1 = (uint)(value1?.GetHashCode () ?? 0);
204+
uint hc2 = (uint)(value2?.GetHashCode () ?? 0);
205+
uint hc3 = (uint)(value3?.GetHashCode () ?? 0);
206+
uint hc4 = (uint)(value4?.GetHashCode () ?? 0);
207+
208+
Initialize (out uint v1, out uint v2, out uint v3, out uint v4);
209+
210+
v1 = Round (v1, hc1);
211+
v2 = Round (v2, hc2);
212+
v3 = Round (v3, hc3);
213+
v4 = Round (v4, hc4);
214+
215+
uint hash = MixState (v1, v2, v3, v4);
216+
hash += 16;
217+
218+
hash = MixFinal (hash);
219+
return (int)hash;
220+
}
221+
222+
public static int Combine<T1, T2, T3, T4, T5> (T1 value1, T2 value2, T3 value3, T4 value4, T5 value5)
223+
{
224+
uint hc1 = (uint)(value1?.GetHashCode () ?? 0);
225+
uint hc2 = (uint)(value2?.GetHashCode () ?? 0);
226+
uint hc3 = (uint)(value3?.GetHashCode () ?? 0);
227+
uint hc4 = (uint)(value4?.GetHashCode () ?? 0);
228+
uint hc5 = (uint)(value5?.GetHashCode () ?? 0);
229+
230+
Initialize (out uint v1, out uint v2, out uint v3, out uint v4);
231+
232+
v1 = Round (v1, hc1);
233+
v2 = Round (v2, hc2);
234+
v3 = Round (v3, hc3);
235+
v4 = Round (v4, hc4);
236+
237+
uint hash = MixState (v1, v2, v3, v4);
238+
hash += 20;
239+
240+
hash = QueueRound (hash, hc5);
241+
242+
hash = MixFinal (hash);
243+
return (int)hash;
244+
}
245+
246+
public static int Combine<T1, T2, T3, T4, T5, T6> (T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6)
247+
{
248+
uint hc1 = (uint)(value1?.GetHashCode () ?? 0);
249+
uint hc2 = (uint)(value2?.GetHashCode () ?? 0);
250+
uint hc3 = (uint)(value3?.GetHashCode () ?? 0);
251+
uint hc4 = (uint)(value4?.GetHashCode () ?? 0);
252+
uint hc5 = (uint)(value5?.GetHashCode () ?? 0);
253+
uint hc6 = (uint)(value6?.GetHashCode () ?? 0);
254+
255+
Initialize (out uint v1, out uint v2, out uint v3, out uint v4);
256+
257+
v1 = Round (v1, hc1);
258+
v2 = Round (v2, hc2);
259+
v3 = Round (v3, hc3);
260+
v4 = Round (v4, hc4);
261+
262+
uint hash = MixState (v1, v2, v3, v4);
263+
hash += 24;
264+
265+
hash = QueueRound (hash, hc5);
266+
hash = QueueRound (hash, hc6);
267+
268+
hash = MixFinal (hash);
269+
return (int)hash;
270+
}
271+
272+
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)
273+
{
274+
uint hc1 = (uint)(value1?.GetHashCode () ?? 0);
275+
uint hc2 = (uint)(value2?.GetHashCode () ?? 0);
276+
uint hc3 = (uint)(value3?.GetHashCode () ?? 0);
277+
uint hc4 = (uint)(value4?.GetHashCode () ?? 0);
278+
uint hc5 = (uint)(value5?.GetHashCode () ?? 0);
279+
uint hc6 = (uint)(value6?.GetHashCode () ?? 0);
280+
uint hc7 = (uint)(value7?.GetHashCode () ?? 0);
281+
282+
Initialize (out uint v1, out uint v2, out uint v3, out uint v4);
283+
284+
v1 = Round (v1, hc1);
285+
v2 = Round (v2, hc2);
286+
v3 = Round (v3, hc3);
287+
v4 = Round (v4, hc4);
288+
289+
uint hash = MixState (v1, v2, v3, v4);
290+
hash += 28;
291+
292+
hash = QueueRound (hash, hc5);
293+
hash = QueueRound (hash, hc6);
294+
hash = QueueRound (hash, hc7);
295+
296+
hash = MixFinal (hash);
297+
return (int)hash;
298+
}
299+
300+
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)
301+
{
302+
uint hc1 = (uint)(value1?.GetHashCode () ?? 0);
303+
uint hc2 = (uint)(value2?.GetHashCode () ?? 0);
304+
uint hc3 = (uint)(value3?.GetHashCode () ?? 0);
305+
uint hc4 = (uint)(value4?.GetHashCode () ?? 0);
306+
uint hc5 = (uint)(value5?.GetHashCode () ?? 0);
307+
uint hc6 = (uint)(value6?.GetHashCode () ?? 0);
308+
uint hc7 = (uint)(value7?.GetHashCode () ?? 0);
309+
uint hc8 = (uint)(value8?.GetHashCode () ?? 0);
310+
311+
Initialize (out uint v1, out uint v2, out uint v3, out uint v4);
312+
313+
v1 = Round (v1, hc1);
314+
v2 = Round (v2, hc2);
315+
v3 = Round (v3, hc3);
316+
v4 = Round (v4, hc4);
317+
318+
v1 = Round (v1, hc5);
319+
v2 = Round (v2, hc6);
320+
v3 = Round (v3, hc7);
321+
v4 = Round (v4, hc8);
322+
323+
uint hash = MixState (v1, v2, v3, v4);
324+
hash += 32;
325+
326+
hash = MixFinal (hash);
327+
return (int)hash;
328+
}
148329
}
149330
}

source/SkiaSharp.HarfBuzz/SkiaSharp.HarfBuzz/CanvasExtensions.cs

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using System;
2+
using System.Collections.Concurrent;
3+
using System.Threading;
24

35
namespace SkiaSharp.HarfBuzz
46
{
@@ -30,8 +32,10 @@ public static void DrawShapedText(this SKCanvas canvas, string text, float x, fl
3032
if (string.IsNullOrEmpty(text))
3133
return;
3234

33-
using var shaper = new SKShaper(font.Typeface);
35+
var shaper = GetShaper(font.Typeface);
3436
canvas.DrawShapedText(shaper, text, x, y, textAlign, font, paint);
37+
if (cacheDuration == 0)
38+
shaper.Dispose();
3539
}
3640

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

7276
// shape the text
73-
var result = shaper.Shape(text, x, y, font);
77+
var result = GetShapeResult(shaper, text, font);
7478

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

101105
// draw the text
102-
canvas.DrawText(textBlob, xOffset, 0, paint);
106+
canvas.DrawText(textBlob, x + xOffset, y, paint);
107+
108+
if (clearCacheTimer is null && cacheDuration > 0)
109+
clearCacheTimer = new Timer(_ => ClearCache(), null, 0, cacheDuration);
110+
}
111+
112+
private static uint cacheDuration = 0;
113+
114+
public static void SetShaperCacheDuration(this SKCanvas canvas, uint milliseconds) => SetShaperCacheDuration(milliseconds);
115+
public static void SetShaperCacheDuration(uint milliseconds)
116+
{
117+
cacheDuration = milliseconds;
118+
119+
clearCacheTimer?.Change(0, cacheDuration);
120+
}
121+
122+
private static readonly ConcurrentDictionary<int, (SKShaper shaper, DateTime cachedAt)> shaperCache = new();
123+
private static readonly ConcurrentDictionary<int, (SKShaper.Result shapeResult, DateTime cachedAt)> shapeResultCache = new();
124+
125+
private static SKShaper GetShaper(SKTypeface typeface)
126+
{
127+
var key = HashCode.Combine(typeface.FamilyName, typeface.IsBold, typeface.IsItalic);
128+
129+
var shaper = shaperCache.TryGetValue(key, out var value)
130+
? value.shaper
131+
: new SKShaper(typeface);
132+
133+
if (cacheDuration > 0)
134+
shaperCache[key] = (shaper, DateTime.Now); // update timestamp
135+
return shaper;
136+
}
137+
138+
private static SKShaper.Result GetShapeResult(SKShaper shaper, string text, SKFont font)
139+
{
140+
var key = HashCode.Combine(font.Typeface.FamilyName, font.Size, font.Typeface.IsBold, font.Typeface.IsItalic, text);
141+
142+
var result = shapeResultCache.TryGetValue(key, out var value)
143+
? value.shapeResult
144+
: shaper.Shape(text, 0, 0, font);
145+
146+
if (cacheDuration > 0)
147+
shapeResultCache[key] = (result, DateTime.Now); // update timestamp
148+
return result;
149+
}
150+
151+
private static Timer clearCacheTimer = null;
152+
153+
private static void ClearCache()
154+
{
155+
var outdated = DateTime.Now - TimeSpan.FromMilliseconds(cacheDuration);
156+
157+
foreach (var kv in shaperCache.ToArray())
158+
{
159+
if (kv.Value.cachedAt < outdated)
160+
{
161+
if (shaperCache.TryRemove(kv.Key, out var entry))
162+
entry.shaper.Dispose();
163+
}
164+
}
165+
166+
foreach (var kv in shapeResultCache.ToArray())
167+
{
168+
if (kv.Value.cachedAt < outdated)
169+
shapeResultCache.TryRemove(kv.Key, out var _);
170+
}
171+
172+
if ((shaperCache.IsEmpty && shapeResultCache.IsEmpty) || cacheDuration == 0)
173+
{
174+
clearCacheTimer?.Dispose();
175+
clearCacheTimer = null;
176+
}
103177
}
104178
}
105179
}

0 commit comments

Comments
 (0)