You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The text rendering library didn't age well and while trying to use it for the upcoming UI library (mosra/magnum-extras#2, not there yet) I got really angry at the shameful state of it. My previous job revolved around text a lot and, while this library was the prototype that I built upon, the actual real-world text rendering needed a lot more flexibility than what's provided here. Putting this braindump here before it fades away completely.
If somebody wants to tackle one of these things, I will be glad to help you and suggest a direction.
Things that are shitty and need to be fixed ASAP
Introduce lower-level reusable & composable APIs that work on singular glyph runs and pre-allocated memory -- 568a420
Re-implement the existing Renderer interfaces on top of these -- 568a420
Rework (or ditch / completely replace?) the Renderer interfaces to be more efficient -- 8480912, 3c626b0, 69f2da2, f3d6ab4
Make it use some global index buffer. It's crazy to have it repeated in each and every instance -- it's just 0 1 2 0 2 3 4 5 6 4 6 7 ... over and over again. -- partially done in 3c626b0, can provide a custom allocator that reuses existing memory but it's still filled again by each renderer
Make it usable outside the "one GL buffer and mesh for each text block" use case -- 3c626b0
Rename it with a GL suffix to make room for Vulkan, WebGPU, etc. implementations -- 69f2da2
Reduce the amount of allocations and braindead use of std::vector every time we need some array with size known in advance -- 568a420, ab8dc07, 2692ff2...
Reduce the amount of virtual calls and batch things more. Currently each glyph on screen equals one virtual call. Crazy. It also unnecessarily complicates things further down, compared to just getting an array of glyph positions and then iterating over it to put them into final positions. -- ab8dc07
Things that need to be improved/fixed before going further
Properly handled initialization/finalization of plugins with dependencies. Current way with static initialize() function is broken by design: initializing HarfBuzz plugin after FreeType will call FreeTypeFont::initialize() second time, finalizing HarfBuzz plugin will call FreeTypeFont::finalize() even though FreeType plugin is still used and thus breaks it. -- mosra/magnum-plugins@217832f
Write a test for TextureTools::distanceField(), ensure that it doesn't have artifacts on older platforms, ensure that it works even on GLES and WebGL -- done in 209cdbc, 2e4beb3 and 8ba2cac
Write a test for Text::GlyphCache and Text::DistanceFieldGlyphCache -- 82691fc
Image::slice() that operate with PixelStorage parameters so we're able to upload image subdata Part of a larger chunk of work, here it's been worked around
Ability to have multiple fonts in the same glyph cache -- 6707534
Ability to use the text renderer with more than one font -- 8480912
Selecting the proper one based on glyph availability or some user-provided predicate function. This would require larger redesign, but very rewarding when done. Possible use cases are combining Latin and CJK text because there is no single font that contains both nice-looking Latin and CJK characters or for example having different parts of the same text block in different font (think bold, italic).
Decide how to handle the glyph availability. Some systems are doing it per character combination (better looking but harder), some per-codepoint (easier but the combinations may not match).
Switching fonts based on special Unicode control characters (or, if there are none, using the private plane for it) wouldn't be able to use the shaping context that way, has to be a separate stream
Ability to use Texture2DArray for the glyph cache instead of Texture2D so we can fit also big alphabets inside. And somehow make it optional or backwards compatible with GLES2 / WebGL 1 that doesn't have them. -- 8168a06
Rename the glyph caches with a GL suffix, to make room for Vulkan, WebGPU etc. implementations -- 42342cd
Ability to use cubemaps? Could help on ES2 platforms, it's not an array but it's 6x times as large as a 2D texture nobody cares about ES2 nowadays anymore
Specifying not just font size but also variable OpenType features like interpolating between regular and bold variant
Ability to create several "font variants" of different size (or OTF variable font features) inside a single AbstractFont instance, to not have to load the same font file several times
HB distinguishes between "face" and "font" for this
FT has setFontSize, how expensive is it to call it every time? any HB-like feature for this?
stb_truetype has nothing like that, can just use a different font size every time
have addVariant(size) / removeVariant(id) and then using that variant ID in createShaper()?
such variant then also has to be distinguished by the GlyphCache, i.e. a second argument to findFont() (and third to addFont())
have a feature flag that exposes this "variant caching", and a generic fallback maintained by the AbstractFont for fonts that don't need it
and a feature flag where the font has just one fixed size, for (shitty) bitmap fonts? or keep it like this?
also options to adjust line height, letter spacing etc
Fun! Ability to draw RGBA glyphs (emojis!) along with single-channel glyphs -- some sort of two-pass rendering (slow) or a more complex shader that switches between textures on per-character basis (slow) or ...?
Could be done with a combined RGBA+single-channel cache, the landfill algorithm would do a max() / fill more slices for the RGBA images, and just one slice for single-channel
Then a per-character mask, where it's either all channels 1, or just one channel 1, in which case a vec4(dot()) of the pixel value is used
Font view that allows the user to override font size, line advance etc. for particular rendering
Renderer view that allows rendering a part of the text differently (color, fake bold etc.)
Shear option for fake italics (easy!)
Allow better batching
Allowing the renderer to specify different alignment etc. for each new block that is being added -- 8480912
Two-dimensional line advance to allow rendering table cells etc. in one row. Can we abuse some special ASCII characters to advance both vertically and horizontally? Tab stops?
Renderer block that allows to share a single buffer/mesh but modify subparts of it -- as of 8480912 can be done with custom allocators, plus the UI library implements a very flexible interface
Aligning a text in a bounding box instead of just to cursor to avoid duplicating that functionality in user code, to be used by line breaking also
Make HarfBuzz the go-to solution on all platforms instead of trying to emulate it poorly using other libraries. Works on Linux, should "just work" on OSX, I was able to compile it for Windows using CMake. What about Android? iOS? Emscripten? No. Instead broaden the API to make it possible to fully control HarfBuzz from it while still making it possible to use stb_truetype or plain bitmap fonts where advanced shaping isn't needed
Ability to autodetect these in the shaper (http://unicode.org/reports/tr24/ -- I read through it and implemented it, but most of it faded away already. HarfBuzz somehow exposes this in the API so one could maybe take it as a starting point.) Just let HarfBuzz do that on its own.
Plugin that can decompress WOFF2 fonts and then delegate the TTF elsewhere, such as stb_truetype, which doesn't have WOFF2 support for obvious reasons: https://github.com/google/woff2 (does it support OTF also?)
Make a FontConverter plugin with this library also
Tools
magnum-fontconverter --info, like is already done for images and scenes
Do it instead in a way that makes it possible to interpret the data as a serialized SceneData + ImageData instance? (Zero-copy importer plugin APIs #240)
Having the glyph cache completely on the GPU, passing just a list of glyph IDs and positions and leaving the rest on the shader
Tried that, unfortunately it led to exhausting UBO limits with just about 2K glyphs, or 4K with some extremely complex packing
From a memory usage PoV would only make sense with instancing and instanced quads are likely not faster than just generating them CPU-side. Generated quads still need 4x position + 4x glyph ID (+ vertex ID) per glyph, which is only a marginal improvement over storing 4x position + 4x texcoord per glyph.
Subpixel rendering with the distance field
Improved distance field rendering with subsampling/mipmaps
Direction autodetection based on script (easy) HarfBuzz does this implicitly as of 0782cdf5e0b2f588bdbde6aa139e9fcb556c5bda
Proper BIDI algorithm implementation is probably not feasible to maintain in the context of Magnum, add a AbstractLayouter interface and expose it via plugins
It's just 2K lines that call into HB, FT or BIDI, and the direct dependency on a font makes it rather hard to expose in a composable way (i.e., couldn't layout with this and use stb_truetype for rendering for example).
Doesn't support line wrapping yet, which I'd have to implement myself anyway.
The text rendering library didn't age well and while trying to use it for the upcoming UI library (mosra/magnum-extras#2, not there yet) I got really angry at the shameful state of it. My previous job revolved around text a lot and, while this library was the prototype that I built upon, the actual real-world text rendering needed a lot more flexibility than what's provided here. Putting this braindump here before it fades away completely.
If somebody wants to tackle one of these things, I will be glad to help you and suggest a direction.
Things that are shitty and need to be fixed ASAP
8480912, 3c626b0, 69f2da2, f3d6ab4
0 1 2 0 2 3 4 5 6 4 6 7 ...over and over again. -- partially done in 3c626b0, can provide a custom allocator that reuses existing memory but it's still filled again by each rendererGLsuffix to make room for Vulkan, WebGPU, etc. implementations -- 69f2da2std::vectorevery time we need some array with size known in advance -- 568a420, ab8dc07, 2692ff2...Things that need to be improved/fixed before going further
initialize()function is broken by design: initializing HarfBuzz plugin after FreeType will callFreeTypeFont::initialize()second time, finalizing HarfBuzz plugin will callFreeTypeFont::finalize()even though FreeType plugin is still used and thus breaks it. -- mosra/magnum-plugins@217832fTextureTools::distanceField(), ensure that it doesn't have artifacts on older platforms, ensure that it works even on GLES and WebGL -- done in 209cdbc, 2e4beb3 and 8ba2cacText::GlyphCacheandText::DistanceFieldGlyphCache-- 82691fcMaking the renderer usable with real-world text and font features
Having automatic font fallback would make the engine better than 90% of competitors.
TextureTools::Atlasfunctionality -- 66bf0b2Part of a larger chunk of work, here it's been worked aroundImage::slice()that operate withPixelStorageparameters so we're able to upload image subdataTextureTools::distanceField()-- ada0645, abf8a75, a2554cbSwitching fonts based on special Unicode control characters (or, if there are none, using the private plane for it)wouldn't be able to use the shaping context that way, has to be a separate streamTexture2DArrayfor the glyph cache instead ofTexture2Dso we can fit also big alphabets inside. And somehow make it optional or backwards compatible with GLES2 / WebGL 1 that doesn't have them. -- 8168a06GLsuffix, to make room for Vulkan, WebGPU etc. implementations -- 42342cdAbility to use cubemaps? Could help on ES2 platforms, it's not an array but it's 6x times as large as a 2D texturenobody cares about ES2 nowadays anymoreaddVariant(size)/removeVariant(id)and then using that variant ID increateShaper()?findFont()(and third toaddFont())vec4(dot())of the pixel value is usedImproved text rendering capabilities
Unicode-aware text shaping
Make HarfBuzz the go-to solution on all platforms instead of trying to emulate it poorly using other libraries. Works on Linux, should "just work" on OSX, I was able to compile it for Windows using CMake. What about Android? iOS? Emscripten?No. Instead broaden the API to make it possible to fully control HarfBuzz from it while still making it possible to use stb_truetype or plain bitmap fonts where advanced shaping isn't neededAbility to autodetect these in the shaper (http://unicode.org/reports/tr24/ -- I read through it and implemented it, but most of it faded away already. HarfBuzz somehow exposes this in the API so one could maybe take it as a starting point.)Just let HarfBuzz do that on its own.Improved text editing capabilities
For the UI.
Plugins
stb_truetypeto make the usage easier on platforms that don't "just have" FreeType (stb_truetype font plugin magnum-plugins#12)MagnumFontFreeTypeFontStbTrueTypeFontStbTrueTypeFontto make it less blurry and differing in sizeAnyFontso one can just not care about which plugin is getting usedOpenTypeFontalias -- mosra/magnum-plugins@2c36ec4Tools
magnum-fontconverter --info, like is already done for images and scenesHarfBuzzFontConverterthat implements whathb-subsetdoes)Shaders and rendering
Having the glyph cache completely on the GPU, passing just a list of glyph IDs and positions and leaving the rest on the shaderhugenon-negligible impact with large textMaking the renderer usable with BIDI
Specs: http://unicode.org/reports/tr9/
Direction autodetection based on script (easy)HarfBuzz does this implicitly as of 0782cdf5e0b2f588bdbde6aa139e9fcb556c5bdaAbstractLayouterinterface and expose it via pluginsAbstractLayouter?