Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 2 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ trim_trailing_whitespace = true

# Strong style — fail the build, don't lint forever
dotnet_diagnostic.IDE0005.severity = error # remove unused usings
dotnet_diagnostic.IDE0011.severity = error # always use braces
dotnet_diagnostic.CA1825.severity = error # avoid zero-length array allocations
dotnet_diagnostic.CA1859.severity = error # concrete types where possible
dotnet_diagnostic.CA1869.severity = error # cache JsonSerializerOptions
Expand All @@ -22,6 +23,7 @@ dotnet_diagnostic.IDE0073.severity = none
csharp_style_namespace_declarations = file_scoped:error
csharp_style_prefer_primary_constructors = true:suggestion
csharp_prefer_static_anonymous_function = true:warning
csharp_prefer_braces = true:error # always use braces (drives IDE0011)

[*.{xml,csproj,props,targets}]
indent_size = 2
Expand Down
7 changes: 7 additions & 0 deletions bench/Starling.Bench/AnimationBench.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,16 @@ public int SampleOnly()
engine.Tick(_clock);
var samples = 0;
foreach (var el in engine.ActiveElements)
{
foreach (var prop in engine.ActiveProperties(el))
{
if (engine.GetEffective(el, prop) is not null)
{
samples++;
}
}
}

return samples;
}
}
17 changes: 15 additions & 2 deletions bench/Starling.Bench/AnimationTraceProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,11 @@ public static int Run(string[] args)
Sample = static (ref ActivityCreationOptions<ActivityContext> _) => ActivitySamplingResult.AllDataAndRecorded,
ActivityStopped = a =>
{
if (a.OperationName != "paint.raster.command_record") return;
if (a.OperationName != "paint.raster.command_record")
{
return;
}

frameData.LastReused = TagInt(a, "raster.text.shaped_reused");
frameData.LastRebuilt = TagInt(a, "raster.text.shaped_rebuilt");
frameData.LastChars = TagInt(a, "raster.text.chars");
Expand Down Expand Up @@ -109,7 +113,11 @@ public static int Run(string[] args)
frameData.Reset();
using (backend.Render(list, viewport, scale)) { }

if (f < warmup) continue;
if (f < warmup)
{
continue;
}

sumReused += frameData.LastReused;
sumRebuilt += frameData.LastRebuilt;
sumChars += frameData.LastChars;
Expand Down Expand Up @@ -139,9 +147,14 @@ public static int Run(string[] args)
Console.WriteLine($"raster.time.draw_text_ms mean/frame: {sumDrawMs / n:F3} font_create_ms mean/frame: {sumFontMs / n:F3}");
Console.WriteLine();
if (meanRebuilt > meanReused)
{
Console.WriteLine("VERDICT: shaped_rebuilt dominates — text IS re-shaped at paint time every frame (heavy path confirmed).");
}
else
{
Console.WriteLine("VERDICT: shaped_reused dominates — paint-time shaping is cached; the per-frame cost lives in relayout/raster, not reshape.");
}

return 0;
}

Expand Down
41 changes: 37 additions & 4 deletions bench/Starling.Bench/Fixtures.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ public static bool GitHubSnapshotExists
public static void RequireGitHubSnapshot()
{
if (!GitHubSnapshotExists)
{
throw new InvalidOperationException(
"GitHub local snapshot missing. Run tools/snapshot-vendor/vendor-github-home.sh first.");
}
}

private static string LocateRepoRoot()
Expand All @@ -27,9 +29,15 @@ private static string LocateRepoRoot()
while (dir is not null
&& !File.Exists(Path.Combine(dir.FullName, "Starling.slnx"))
&& !File.Exists(Path.Combine(dir.FullName, "Starling.sln")))
{
dir = dir.Parent;
}

if (dir is null)
{
throw new InvalidOperationException("Could not locate the Starling solution walking up from the bench binary.");
}

return dir.FullName;
}

Expand Down Expand Up @@ -118,8 +126,11 @@ public static string TextHeavyParagraphs(int paragraphs)
var sb = new System.Text.StringBuilder(paragraphs * 120 + 64);
sb.Append("<!doctype html><html><body><main>");
for (var i = 0; i < paragraphs; i++)
{
sb.Append("<p>Paragraph ").Append(i)
.Append(" has several words of body text that the engine must shape and wrap across the available width of the line box.</p>");
}

sb.Append("</main></body></html>");
return sb.ToString();
}
Expand All @@ -129,9 +140,17 @@ public static string NestedFlex(int depth)
{
var sb = new System.Text.StringBuilder(depth * 48 + 96);
sb.Append("<!doctype html><html><body>");
for (var i = 0; i < depth; i++) sb.Append("<div class=\"f\">");
for (var i = 0; i < depth; i++)
{
sb.Append("<div class=\"f\">");
}

sb.Append("<span>leaf</span>");
for (var i = 0; i < depth; i++) sb.Append("</div>");
for (var i = 0; i < depth; i++)
{
sb.Append("</div>");
}

sb.Append("</body></html>");
return sb.ToString();
}
Expand All @@ -143,7 +162,11 @@ public static string ManyBorders(int boxes)
{
var sb = new System.Text.StringBuilder(boxes * 28 + 96);
sb.Append("<!doctype html><html><body>");
for (var i = 0; i < boxes; i++) sb.Append("<div class=\"b\"></div>");
for (var i = 0; i < boxes; i++)
{
sb.Append("<div class=\"b\"></div>");
}

sb.Append("</body></html>");
return sb.ToString();
}
Expand All @@ -168,7 +191,10 @@ public static string AnimatedBoxesHtml(int boxes)
var sb = new System.Text.StringBuilder(boxes * 44 + 96);
sb.Append("<!doctype html><html><body><main>");
for (var i = 0; i < boxes; i++)
{
sb.Append("<div class=\"anim\" id=\"box-").Append(i).Append("\">Box ").Append(i).Append("</div>");
}

sb.Append("</main></body></html>");
return sb.ToString();
}
Expand Down Expand Up @@ -204,7 +230,11 @@ public static string SolidBackgrounds(int boxes)
{
var sb = new System.Text.StringBuilder(boxes * 24 + 96);
sb.Append("<!doctype html><html><body>");
for (var i = 0; i < boxes; i++) sb.Append("<div class=\"s\"></div>");
for (var i = 0; i < boxes; i++)
{
sb.Append("<div class=\"s\"></div>");
}

sb.Append("</body></html>");
return sb.ToString();
}
Expand All @@ -229,9 +259,12 @@ public static string PromotedCards(int cards)
var sb = new System.Text.StringBuilder(cards * 96 + 96);
sb.Append("<!doctype html><html><body><main>");
for (var i = 0; i < cards; i++)
{
sb.Append("<div class=\"card\" id=\"card-").Append(i).Append("\"><h3>Card ").Append(i)
.Append("</h3><p>Card ").Append(i)
.Append(" body text that the layer must shape and fill when its cache is cold.</p></div>");
}

sb.Append("</main></body></html>");
return sb.ToString();
}
Expand Down
41 changes: 41 additions & 0 deletions bench/Starling.Bench/GitHubStyleBench.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,21 @@ public void Setup()
LoadInlineSheets(_doc);

if (_cssTexts.Count == 0)
{
throw new InvalidOperationException(
"GitHub local snapshot has no CSS. Re-run tools/snapshot-vendor/vendor-github-home.sh.");
}
}

[Benchmark]
public int ParseCss_GitHubHome()
{
var rules = 0;
foreach (var css in _cssTexts)
{
rules += CssParser.ParseStyleSheet(css).Rules.Count;
}

return rules;
}

Expand Down Expand Up @@ -109,15 +114,21 @@ private void LoadExternalSheets(Document doc)
foreach (var link in Elements(doc))
{
if (!IsStylesheetLink(link))
{
continue;
}

var href = link.GetAttribute("href");
if (string.IsNullOrWhiteSpace(href))
{
continue;
}

var path = SnapshotPathFromHref(href);
if (path is null || !File.Exists(path) || _externalSheets.ContainsKey(href))
{
continue;
}

var css = File.ReadAllText(path);
_cssTexts.Add(css);
Expand All @@ -130,11 +141,15 @@ private void LoadInlineSheets(Document doc)
foreach (var element in Elements(doc))
{
if (!string.Equals(element.LocalName, "style", StringComparison.Ordinal))
{
continue;
}

var source = element.TextContent;
if (string.IsNullOrWhiteSpace(source))
{
continue;
}

_cssTexts.Add(source);
_inlineSheets[element] = CssParser.ParseStyleSheet(source);
Expand Down Expand Up @@ -162,34 +177,50 @@ private void AddAuthorSheets(Document doc, StyleEngine style, bool useCachedInli
{
var source = element.TextContent;
if (string.IsNullOrWhiteSpace(source))
{
continue;
}

if (useCachedInlineSheets && _inlineSheets.TryGetValue(element, out var cached))
{
style.AddStyleSheet(cached);
}
else
{
style.AddStyleSheet(CssParser.ParseStyleSheet(source));
}
}
else if (IsStylesheetLink(element))
{
var href = element.GetAttribute("href");
if (href is not null && _externalSheets.TryGetValue(href, out var sheet))
{
style.AddStyleSheet(sheet);
}
}
}
}

private static bool IsStylesheetLink(Element element)
{
if (!string.Equals(element.LocalName, "link", StringComparison.Ordinal))
{
return false;
}

var rel = element.GetAttribute("rel");
if (rel is null)
{
return false;
}

foreach (var token in rel.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries))
{
if (string.Equals(token, "stylesheet", StringComparison.OrdinalIgnoreCase))
{
return true;
}
}

return false;
}
Expand All @@ -198,22 +229,32 @@ private static bool IsStylesheetLink(Element element)
{
var end = href.IndexOfAny(['?', '#']);
if (end >= 0)
{
href = href[..end];
}

href = href.TrimStart('/');
if (!href.StartsWith("assets/", StringComparison.Ordinal))
{
return null;
}

return Path.Combine(Fixtures.GitHubSnapshotRoot, href.Replace('/', Path.DirectorySeparatorChar));
}

private static IEnumerable<Element> Elements(Node root)
{
if (root is Element element)
{
yield return element;
}

foreach (var child in root.ChildNodes)
{
foreach (var nested in Elements(child))
{
yield return nested;
}
}
}
}
13 changes: 11 additions & 2 deletions bench/Starling.Bench/GitHubStyleSmoke.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ public MetricRecorder()
_listener.InstrumentPublished = (inst, lst) =>
{
if (inst.Meter.Name == StarlingTelemetry.SourceName)
{
lst.EnableMeasurementEvents(inst);
}
};
_listener.SetMeasurementEventCallback<double>((inst, m, _, _) => Add(inst.Name, m));
_listener.SetMeasurementEventCallback<long>((inst, m, _, _) => Add(inst.Name, (double)m));
Expand All @@ -73,7 +75,9 @@ public void Print(string prefix)
foreach (var pair in _counters
.Where(pair => pair.Key.StartsWith(prefix, StringComparison.Ordinal))
.OrderBy(pair => pair.Key))
{
Console.WriteLine($" counter {pair.Key}: {pair.Value:N0}");
}
}

public void Dispose() => _listener.Dispose();
Expand All @@ -94,13 +98,16 @@ public SpanRecorder()
{
ShouldListenTo = src => src.Name == StarlingTelemetry.SourceName,
Sample = static (ref ActivityCreationOptions<ActivityContext> _) => ActivitySamplingResult.AllDataAndRecorded,
ActivityStarted = a => { lock (_active) _active[a.Id ?? a.OperationName] = Stopwatch.StartNew(); },
ActivityStarted = a => { lock (_active) { _active[a.Id ?? a.OperationName] = Stopwatch.StartNew(); } },
ActivityStopped = a =>
{
Stopwatch? sw;
lock (_active)
{
if (!_active.Remove(a.Id ?? a.OperationName, out sw)) return;
if (!_active.Remove(a.Id ?? a.OperationName, out sw))
{
return;
}
}
sw.Stop();
lock (_spans)
Expand All @@ -116,7 +123,9 @@ public SpanRecorder()
public void Print()
{
foreach (var pair in _spans.OrderByDescending(pair => pair.Value))
{
Console.WriteLine($" span {pair.Key}: {pair.Value} ms");
}
}

public void Dispose() => _listener.Dispose();
Expand Down
Loading
Loading