Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[skrifa] Implement an optimized fill_glyph operation #754

Merged
merged 9 commits into from
Jan 23, 2024

Conversation

drott
Copy link
Contributor

@drott drott commented Jan 12, 2024

In COLRv0/v1 drawing clients may gain a performance benefit from filling
a shape (with a solid or gradient fill) instead of performing a clip +
unbounded fill in sequence.

Allow clients to do that using a fill_glyph() operation that combines
the two.

Implement this using a temporary painter that speculatively traverses
the glyph tree down from a PaintGlyph operation collecting potential
transforms along the way.

New behavior shows clearly in new baselines for tests added in #758 that
nest PaintGlyph tables and cover mixed situations in
which the optimization succeeds for a leaf but fails for the
PaintGlyph at a layer above, and demonstrate recording transforms
along the way.

Fixes #746.

skrifa/src/color/traversal.rs Outdated Show resolved Hide resolved
@drott drott force-pushed the skrifaOptimizeFill branch 2 times, most recently from c8cfff3 to 46621c9 Compare January 22, 2024 13:11
@drott drott changed the title WIP: Skrifa optimize fill [skrifa] Implement an optimized fill_glyph operation Jan 22, 2024
@drott drott changed the title [skrifa] Implement an optimized fill_glyph operation [skrifa] Implement an optimized fill_glyph operation Jan 22, 2024
@drott drott force-pushed the skrifaOptimizeFill branch 2 times, most recently from cd22c92 to 1cc4339 Compare January 22, 2024 13:15
@drott drott requested a review from dfrg January 22, 2024 13:16
In COLRv0/v1 drawing clients may gain a performance benefit from filling
a shape (with a solid or gradient fill) instead of performing a clip +
unbounded fill in sequence.

Allow clients to do that using a fill_glyph() operation that combines
the two.

Implement this using a temporary painter that speculatively traverses
the glyph tree down from a `PaintGlyph` operation collecting potential
transforms along the way.

New behavior shows clearly in new baselines for tests added in googlefonts#758 that
nest `PaintGlyph` tables and cover mixed situations in
which the optimization succeeds for a leaf but fails for the
`PaintGlyph` at a layer above, and demonstrate recording transforms
along the way.

Fixes googlefonts#746.
@drott
Copy link
Contributor Author

drott commented Jan 22, 2024

I would like to send this for review now. This is still a nested painter-based implementation. I have opted for now not to change signatures of the draw functions to return a Result and thus potentially be able to cancel drawing, as I believe this realizes the performance benefit and we would have yet to discover pathological situations in which there are deeply nested trees behind a PaintGlyph. The benefit the nested painter approach I see in avoiding implementing separate imperative traversal logic - instead - the existing traverse_with_callbacks can be used.

Please let me know if this works for you or if there are strong opinions about a different approach. I am open to revisiting this, but for now, this definitely meets the mark in terms of benchmark results as shared in the chat. An API change to Result<>-returning, cancellable methods would require a roundtrip of deprecation steps and careful chaining it into Skia and then Chrome with a phase of parallel ColorPainter functions along the way. I prefer to save that time atm and focus on implementing bitmap fonts support next.

Copy link
Member

@dfrg dfrg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this!

I’m happy to land with the nested traversal since it works and seems to hit our performance targets. Upon further thought, I’m not too excited about complicating the API with cancellation. If we do encounter deeply nested graphs under a clip glyph, I’d prefer to implement the procedural matching instead which wouldn’t require changing the API.

I noted one request for a change and a couple nits. Otherwise lgtm!

skrifa/src/color/mod.rs Show resolved Hide resolved
fn fill_glyph(
&mut self,
_glyph_id: GlyphId,
_transform: Transform,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be called brush_transform just to further signify that it applies to the brush and not the clip.

Also, optional: make the transform optional :) I suspect this will be identity most of the time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a) Renamed to brush_transform and updated arg name in documentation.
b) Transform turned into Option<Transform> - I wasn't sure if you prefer to have that be expressed in the test results as an empty string - for now I left testing to dump an identity matrix in that case, instead of special casing the None situation, but agree this might be helpful for clients. Let me know if you want changes on the testing side. On the Skia side I am also making this a default matrix in the callbacks to C++.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your approach looks good to me. Thanks for making the change.

@@ -25,6 +27,23 @@ pub struct Transform {
pub dy: f32,
}

impl MulAssign for Transform {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realize we don’t strictly need it here but this will be exposed publicly and having an implementation of MulAssign without Mul feels a bit weird.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a Mul implementation, moved the logic into that and made the MulAssign implementation based on the former - hope that's what's usually done. Not sure I need the clone() there, but I guess I need some sort of temporary value at least?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also derive Copy for Transform which would remove the need for the clone. I’m good either way.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add that.

skrifa/src/color/mod.rs Outdated Show resolved Hide resolved
skrifa/src/color/mod.rs Outdated Show resolved Hide resolved
@drott drott merged commit c31397f into googlefonts:main Jan 23, 2024
6 of 7 checks passed
aarongable pushed a commit to chromium/chromium that referenced this pull request Jan 24, 2024
Contains [0] for an optimized combined glyph fill operation.
Include patch updates for read-fonts and font-types along the way.

Followed instructions in [1], but passed "skrifa" argument to gnrt
update step to restrict the change to the Skrifa roll.

[0] googlefonts/fontations#754
[1] https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/rust.md#updating-existing-third_party-crates

Bug: 1516634
Change-Id: I5ec7d163196476ed790cc9467b5893583d69795c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5224635
Commit-Queue: Dominik Röttsches <drott@chromium.org>
Auto-Submit: Dominik Röttsches <drott@chromium.org>
Reviewed-by: danakj <danakj@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1251673}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Skrifa][COLRv1] Add a clip&fill optimized method to paint callbacks
3 participants