TortoiseUI: batch same-color, same-width committed strokes (#37) - #38
Merged
Conversation
The environment default (EnvironmentValues.tortoiseViewport) is .autoFit, matching the DocC article, but the enum's doc comment still marked scaleToFit as the default.
CanvasRenderer.drawElements built one Path and issued one ctx.stroke per committed segment, so a committed-layer redraw cost ~0.37us per element no matter how much of it was on screen — 4.17ms for a 10,000-stroke drawing, half of a 120Hz frame budget, paid again on every command commit. Consecutive strokes sharing color and width now merge into a single multi-subpath Path drawn with one ctx.stroke; round caps apply per subpath, so the drawing is unchanged. 10,000 strokes: 4.17ms -> 0.64ms (6.5x), 4,000: 1.68ms -> 0.36ms. Programs where no run is batchable (a new color per stroke) show no regression from the merge loop. Translucent strokes (color.alpha < 1) are excluded from the merge: overlapping segments have to blend once per stroke to match the SVG renderer's one <line> per stroke, and batching would blend the overlap only once. The new translucentOverlaps scenario guards this — its golden was recorded with the pre-batching renderer and still passes. Canvas goldens re-recorded: batching rasterizes stroke overlaps once instead of blending twice, so antialiased edges shift. Verified per file against the previous goldens — under 1% of pixels differ, at most 183 by more than delta 32 and none by more than 128, all confined to stroke edges and visually indistinguishable. SVG goldens are byte-identical.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #38 +/- ##
==========================================
+ Coverage 91.70% 91.89% +0.19%
==========================================
Files 15 15
Lines 892 913 +21
==========================================
+ Hits 818 839 +21
Misses 74 74 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
CanvasRenderer.drawElementsが確定要素 1 つにつきPathを 1 つ作りctx.strokeを 1 回呼んでいたため、CommittedLayerの再描画コストが「画面にどれだけ映っているか」と無関係に 要素あたり約 0.37µs かかっていた。10,000 ストロークで 4.17ms — 120Hz のフレーム予算の約半分を、コマンドがコミットされるたびに消費していた。同じ色・同じ太さの連続するストロークを 1 本の複数サブパス
Pathに集約し、ctx.strokeを 1 回だけ呼ぶようにした。丸キャップはサブパスごとに付くため描画結果は変わらない。10,000 ストロークで 0.64ms(受け入れ条件の 1ms 未満を達成)。バッチが一切効かないプログラムでも、集約ループのオーバーヘッドによる劣化はない。計測は issue と同じハーネス(
ImageRendererの 1 パス =CommittedLayer1 回の再描画、best-of-5, release, scale 2, 400×400, Apple M4)。issue の仕様案からの変更点: 半透明ストロークは除外
color.alpha < 1のストロークはバッチしない。半透明の線分が重なる箇所(丸キャップの継ぎ目すべてと自己交差すべて)では、集約すると合成が 2 回から 1 回に変わり、AA のズレではなく明確に見た目が変わる。SVG レンダラは 1 ストローク = 1 個の<line>を出力するため、バッチすると両レンダラの出力も乖離する。これを守るため
translucentOverlapsシナリオを追加した。ゴールデンは バッチ実装前のレンダラで記録 し、バッチ実装を入れた状態でも通ることを確認しているので、このゴールデンは構成上レンダラ非依存になっている。ガードを外すと 7,764 ピクセルが delta 32 超で変化してテストが落ちることも確認済み(AA のみのケースは 12〜29 ピクセル)。.fill/.arcStroke/.dotは issue の方針どおり現状維持。要素順・z-order(およびfillInsertionIndex)は不変。ゴールデンの再記録
CLAUDE.md の手順どおり両方録り直した。
precision: 0.995を超えていたのはclearDuringFillとteleportAndHomeの 2 件のみ。受け入れ条件
CommittedLayer再描画が 1ms 未満 → 0.64mspenStyles/backgroundAndColorsゴールデン)translucentOverlapsシナリオを追加)seek(to:)/step()/clear()後の表示が正しい(レンダラはmodel.elementsの純粋関数であり、既存のCanvasModelテストとclearAndRedraw/clearDuringFillゴールデンでカバー)未対応(別途ご判断ください)
issue の「参考」にあった「
ViewportModeは性能の要因ではない」という知見の README/DocC への記載は本 PR では見送った。CHANGELOG には計測の要点を記載済み。Related issue
Fixes #37
Checklist
swift testpasses locally(114 tests 全通過)xcrun swift-format lint --recursive --strict Sources TestspassestranslucentOverlapsシナリオを追加)CHANGELOG.md