opentypejs / opentype.js

Read and write OpenType fonts using JavaScript.
https://opentype.js.org/
MIT License
4.49k stars 479 forks source link

Performance Issues Rendering Lots of Text #307

Closed darkfrog26 closed 7 years ago

darkfrog26 commented 7 years ago

I'm not sure if this is just a limitation of the shear complexity of rendering text on the screen, but I'm getting fairly slow rendering of lots of text on the screen:

screenshot from 2017-10-09 12-30-01

It's taking around 250ms to render each time. I've tried converting it all to paths but it doesn't seem to help. Is this an expected performance limitation or is there something that can be done?

axkibe commented 7 years ago

I've been rendering way more in way less time. ( see for example http://www.ideoloom.net/ )

However I'm caching glyphs in mini canvases and then paste them (a given glyph with a given size and color doesn't need to rendered twice)

fpirsch commented 7 years ago

Hi @darkfrog26, rendering text is very complex indeed. Opentype has not been designed as a fast rendering library (what for, anyway ?) but as a glyph and font manipulation tool. Things could be improved, though.

darkfrog26 commented 7 years ago

I'm building a framework that, I'm hoping, will be a viable alternative to using HTML, but everything is drawn in Canvas. The performance issues create serious problems with that being possible.

fdb commented 7 years ago

I suggest profiling to see which parts are slow (and then proposing a PR that fixes it ;-)).

As always, the solution will be caching. If you think about "normal" font rendering, the cache the glyphs of a specific font at a specific size in a texture atlas. If you want the same performance I think you will end up with a solution very similar to that.

Rendering a texture atlas from OpenType.js is out of scope, but I imagine you could build a library on top of OTjs...

darkfrog26 commented 7 years ago

The problem is, in the test I'm running I'm playing with a scenario that sets the fill of the text path to a video, so it needs to re-render with every video frame. Obviously this is a pretty crazy scenario, but I figured if I could get the performance good enough to do that then HTML can effectively be considered antiquated. :-p

Unfortunately, this means that caching isn't really an option apart from caching the paths. I have been considering using Path2D on browsers that support it, but I'm not sure if that will really offer much of a performance benefit over drawing the paths on-demand.

darkfrog26 commented 7 years ago

FYI, two changes in the way I deal with rendering paths increased performance from 250ms per render to 50ms:

1.) I removed all open and close path calls between letters so the entire block of text is represented by a single path. 2.) Utilize Path2D (https://developer.mozilla.org/en-US/docs/Web/API/Path2D) instead of raw instructions to canvas context (made a significant improvement per render).

Any other suggestions are appreciated, but 20 renders per second is pretty decent for the level of complexity I'm drawing (especially since I'm up-scaling rendering for retina displays, so it's rendering at twice the displayed size).

axkibe commented 7 years ago

In case you want a video in the fill, how about just making a white/transparent cookie cutter of the whole text and apply it as "and-put" on each frame.

Heck you can even overlay two canvases, or a canvas above a <video> tag.

axkibe commented 7 years ago

PS: the native type rendering libraries of your operating system do glyph and even word caching too. Also on the very first render of a new glyph aren't that fast either, native and canvas.drawText().

@fpirsch for me performance is important too, but as I said, opentype.js is very acceptable, if added some caching. Maybe some things can be improved, but IMO opentype.js is in the upper field of doable already anyway.

PPS: The reason why I switched to opentype.js from canvas.drawText() are unrepairable issue with textzooming there. First there is an upper limit in text size which makes unlimited zoom not possible. Secondly native canvas.drawText() adds fonthinting rounding errors to text propagation, while opentype.js can be changed for each letter to be rounded, but not add rounding decissions to following letters. (that is "oooooooooooooooo" and "oooooooooooooooo" in a slightly different font size can have suddendly vastly different total lengths)

darkfrog26 commented 7 years ago

@axkibe I'm not sure I understand the "cookie cutter" approach you're talking about...can you elaborate on how that would work?

axkibe commented 7 years ago

Just make a transparent canvas above a video element. Or make two canvases atop each other one in which your text is transparent and the lower one behind with the animated content (however you want to animate it).

The web is full of examples that do this, it's simple CSS.

darkfrog26 commented 7 years ago

Ah, I see what you mean. Thanks. :)

fpirsch commented 7 years ago

:+1: for Path2D. Except that it only works in the browser, and it is not editable (remember OT.js is initialy a glyph edition library). Path2D could be used alongside the internal representation, just for rendering. But then memory could become an issue.

darkfrog26 commented 7 years ago

I understand, and I wasn't suggesting adding it to OpenType.js, but rather just as some feedback for anyone else looking to optimize their rendering. It's incredibly unclear based on online reading if Path2D offers much benefit, and I'm here to say that it definitely does!

axkibe commented 7 years ago

Just wondering, what you want to do can also be done with conventional canvas.strokeText -- using the stroke for globalCompositeOperation = 'destination-out' to cut out transparency. For this there is no need for opentype.js