googlefonts / oxidize

Notes on moving tools and libraries to Rust.
Apache License 2.0
173 stars 7 forks source link

fontmake glyph loop in Rust #33

Closed rsheeter closed 1 year ago

rsheeter commented 2 years ago

Rendered view.

Holding in PR for a few days to facilitate collection of feedback.

simoncozens commented 2 years ago

OK, some thoughts:

anthrotype commented 2 years ago

I left some comments in line. Overall I'm strongly in favour of this, and of doing this incrementally and in such a way that benefits existing consumer of fontmake.

There isn't such a thing as "the glyph loop" per se

Yes, we have several for loops that involve glyphs and we'd like to break up/parallelize. One that isn't mentioned here directly are all the ufo2ft's "filters" that pre-process UFO glyphs (e.g. decompose components, convert cubic to quadratic curves, in addition to custom user-defined filters) before they get translated to truetype or CFF glyphs. Filters are also run serially at the moment: not just one filter at a time, the result of the previous filter being the input to the next, but also, within each filter, one glyph at a time.

It's true the current code is at the same time tightly coupled, while also spanning different projects (ufo2ft for master UFOs=>glyf compilation, fonttools varLib for master glyfs=>gvar), which makes integrating this proposed "oxidized glyph loop" more complicated than it appears.

One way to tackle this at first could be to keep the current serial python pipeline to produce only a scaffolding of the VF, with all glyphs blanked out (a ufo2ft filter could even literally nuke all the contours/components before proceeding as usual). Then (or even at the same time) also have this new Rust tool or method take a designspace with a bunch of UFO masters and produce whole glyf and gvar tables (internally parallelizing per glyph) and finally use fonttools to stick those in the VF at the end.

Or alternatively, which I think is what Simon suggested ealier, we could modify ufo2ft's compileInterpolatableTTFsFromDS method to call out into Rust to build the master TTFs' glyf tables, while keeping the rest of the code which depends on them (e.g. for the bounding boxes, sidebearings); then when ufo2ft goes on to call fontTools.varLib.build method, pass exclude=["gvar"] parameter and subsequently call some other Rust API to build the latter and insert in the final VF.

simoncozens commented 2 years ago

I implemented what I'm calling "step one" of this (replace Python/C glyph table generation with Rust, not gvar yet) using this PyO3 module and this patch to ufo2ft.

I found a medium-sized speedup, around 10-15%. Which is not bad! Making code 10-15% faster is actually a pretty good optimization... it's just not what we were expecting.

Some observations:

But I think the biggest observation is that we still don't really have a sense of how long each sub-task within font compilation takes. We're prodding at bits we think are going to be bottlenecks and optimizing them, but I don't feel like we know where the bottlenecks really are. I know @madig did a lot of profiling a year or so ago, but rather than function-level profiling, I think it is more useful to mark the start and end of discrete operations ("load in the UFOs", "convert the curves", etc.) and see what proportion of the build time they take.

rsheeter commented 2 years ago

10-15% total correct? Can you readily compare the specific operation that was ported to Rust vs it's Python equivalent?