Texture-Less Text Rendering
poniesandlight.co.uk
poniesandlight.co.uk
ShaderToy comes with a texture atlas built in. I have one or two examples of that, for example https://www.shadertoy.com/view/ltBfDD In addition to mipmap textures, there are other pseudo antialiasing methods people use on ShaderToy, for example when doing 2d stuff you can use the pixel derivative to make 1 pixel wide blending functions, and use it to antialias hard edges. Example https://www.shadertoy.com/view/MtyyRc
``` <head><style>{margin:0;padding:0;line-height:1;overflow:hidden;}div{width:1em;position:absolute;}</style><script> w=window;n=w.innerWidth;m=w.innerHeight;d=document;q="px";function z(a,b){return Math.floor(Math.random()(b-a)+a)}f=" 0123456789";for(i=0;i<45;i++)f+=String.fromCharCode(i+65393);function g(){for(i=0;i<90;i++){r=d.createElement("div");for(j=z(20,50);j;j--){x=d.createElement("pre");y=d.createTextNode(f[z(0,56)]);x.appendChild(y);x.style.opacity=0;r.appendChild(x)}r.id="r"+i;r.t=z(-99,0);with(r.style){left=z(0,n)+q;top=z(-m,0)+q;fontSize=z(10,25)+q}d.body.appendChild(r);setInterval("u("+i+")",z(60,120))}}function u(j){e=d.getElementById("r"+j);c=e.childNodes;t=e.t+1;if((v=t-c.length-50)>0){if((e.style.opacity=1-v/32)==0){for(f in c)if(c[f].style)c[f].style.opacity=0;with(e.style){left=z(0,n)+q;top=z(-m/2,m/2)+q;opacity=1}t=-50}}e.t=t;if(t<0||t>c.length+12)return;for(f=t;f&&f>t-12;f--){s=1-(t-f)/16;if(f<c.length&&c[f].style){c[f].style.opacity=s;}}} </script><body text=#0f0 bgcolor=#000 onload=g()> ```
Some years ago they bought the best font rendering tool that a guy had written and incorporated it natively. Of course after that all work on it pretty much stopped and it killed the competitive font rendering market.
The other day I wanted to try and make an app that looks as if its using a native console font and I had to fiddle for 2+ hours to just get it to about 90% of the way there.
If anyone is interested in the a more common way that modern 3d rendering engines draw text, look up SDF text (and related techniques like MSDF etc.). This uses a traditional texture atlas in a preprossing step to create an atlas of signed distance fields.
In case anyone hasn't yet seen the 1968 full circle paper, "On the Design of Display Processors": http://cva.stanford.edu/classes/cs99s/papers/myer-sutherland...
Hardware in their case, but we also have software saṃsāra.
Not saying it's not a neat trick, it is.
This is now at least a generation out of date. Pretty much everyone is now using approaches like https://sluglibrary.com, where one directly rasterises the font bezier curves in a shader.
I cooked up a (very basic) version of the concept a while back: https://www.shadertoy.com/view/sdXBDs
TextMeshPro goes one step further and uses signed distance fields to handle arbitrary scale.
https://docs.unity3d.com/Packages/com.unity.textmeshpro@4.0/...
Meshes and SDFs are much simpler on the GPU side but scaling them up too much can compromise accuracy, and scaling meshes down too much can introduce aliasing.
As usual with modern gpu stuff for (semi-, not knocking the OP) simple stuff like this I guess the answer to "how does it perform?" is "yes". :/
Despite what the filename says, this is using the font from the IBM PC ROM, not the NES. You can find the "NES font" and other 8x8 pixel fonts around the web.
https://github.com/rikusalminen/triangles/blob/nesfont/shade...
This is my favorite pixel font pack:
This is going to be such a source of inspiration! Do people have favorites from the list?
I finally found out recently that the "NES" font is from the 1976 arcade game Quiz Show. The font was used in other black-and-white Kee/Atari games. The font data is available in the quizshow MAME ROM set - split into nybbles for some reason.
This game was interesting - it stored question and answer data on a 8-track tape.
This actually seems conceptually similar to my favorite method by Will Dobbie (but much simpler). Both take raw font data and use it directly on a shader. The difference being, this method takes pixel data and stores it in arrays. Will takes svg path data and stores them as a "vector texture"
He made a cool demo, if anyone is curious: https://wdobbie.com/warandpeace/
Maybe you could get the best of both worlds by bitpacking into a regular texture, with a fragment shader doing the decoding.
That understanding is very out of date. For GPU's made in the last 15 or so years a texture lookup is roughly 100 times as slow as a bitwise operation.
Drawing using a glyph atlas is still a way better use of resources. Modern text rendering pipelines will also often use either SDF or encoded bezier curves to improve glyph legibility when scaling which is also a great way of saving more memory.
FWIW, this method is also a texture despite being called “texture-less”; the texture is just stored in a different format and a different place. True textureless font rendering evaluates the vector curves on the fly.
Regarding the upload part: at the end of the day, you have X bytes of glyphs and they need to get into GPU memory somehow. Whether you get them there as textures, as uniform data or as shader constants doesn't really matter performance-wise. If anything, doing it through shader constants as described in TFA is more expensive on the CPU side, since all those constant declarations need to be processed by the shader compiler.
What does matter on the GPU side is which memory hierarchy you hit when reading glyph data (texture fetches have a dedicated L1 cache on most GPUs, larger than the "normal" L1 cache I think) and what order the data is in (textures are usually stored in some version of Morton order to avoid cache misses when you're shading blocks of pixels). For a production atlas-based text renderer you probably want to use textures.
Edit: I misread the question; you were asking about drawing individual glyphs on the GPU vs. drawing an entire block of text on the CPU, right? This is a speed/space tradeoff, the answer is going to depend on how much memory you want to blow on text, whether your text changes, whether it needs to have per-character effects applied, and so on.
It's not. This technique is more about getting text on the screen in the easiest way possible for debugging. You just add some data to your shader and poof, you get text.
The converse is, you write code to generate a font atlas (so more work), or go find and existing one and need to load it (so need to write loading code so more work). And/Or draw a full message into a texture (more work) and you'll need to cache that result until the message changes (more work)
On top of all of that, you need to manage resources and bind them into the system where as here no resources are needed.
but again, it's a technique for getting "debugging text" on the screen. It is not a solution for text in general.
Note that drawing text to textures is how most browsers and OSes work. They draw fonts into texture atlases dynamically (since a atlas for all of Unicode per font per size would take too much time and memory). They then use the texture atlas glyphs to make more textures of portions of the app's window. In the browser you can turn on show texture edges to see all textures. Rendering->Layer borders will outline each texture in cyan
(TL;DR, he embeds a bitmap font in the shader.)
You could compare that to storing some data in a separate file which needs to be read during runtime versus embedding the data directly in the source code.
When told it's going to be a "texture less text rendering", I was thinking of procedural drawing of glyphs, not embedding bitmaps in a shader instead of a texture.
I think the next best thing is BC4. The compressed format stores 8 bits/pixels grayscale texture compressed into 8 bytes / 4x4 pixels i.e. 4 bits/pixel, twice smaller compared to R8.
https://learn.microsoft.com/en-us/windows/win32/direct3d10/d...
Moreover, parent’s point is double valid because of the example “Look Ma, No Font Atlas!!!” that uses a font atlas baked into shader code. I totally expected this article, based on the title, to talk about stroked font rendering, and instead it’s an article about “texture-less” textured rendering that uses a “no font atlas” font atlas.
Back in the bad old days you could just use precompiled textures which are basically a set of memory write CPU instructions using immediate mode operands (no texture/bitmap lookup of any kind)
> Obviously, we can’t store bitmaps inside our shaders, but we can store integer constants, which, if you squint hard enough, are nothing but maps of bits. Can we pretend that an integer is a bitmap?
He seems a bit confused about what a bitmap is. There's no squinting or pretending involved here.
(tho mine was the only comment last time)
The actual rendering part of fillText() isn’t that fast though, so you probably can beat it with your own bitmaps if you were to render a different full screen of text every frame, which would undermine the bitmap caching mechanism. I’m not sure, but it might help to vary the text style every frame too; I’m not sure if the engines build font atlases on the fly… they might, in which case defeating the cache would be using enough different styles to churn through several gigabytes of image cache.
Another thing to pay attention to is whether you’re rendering text to a buffer that then needs to be copied to the frame buffer, or whether you’re rendering directly to the frame buffer. Avoiding the round-trip through memory, if you can, will be quite a bit faster.
The technique in the article might be best for things like debug text overlays on top of a game. If you need to display a handful of values and watch them change, this shader technique can be very fast.
I mean, there are some very interesting projects to try and do font rendering on the graphics card, but by and large I find it funny how in general they are terrible at it.
What would a native gfx-card friendly scalable font format look like? would it just be a triangle mesh?