[HN Gopher] Texture-Less Text Rendering
       ___________________________________________________________________
        
       Texture-Less Text Rendering
        
       Author : PaulHoule
       Score  : 167 points
       Date   : 2024-11-09 07:27 UTC (15 hours ago)
        
 (HTM) web link (poniesandlight.co.uk)
 (TXT) w3m dump (poniesandlight.co.uk)
        
       | sklivvz1971 wrote:
       | I'm pretty sure we've done this before... lol!
        
       | esperent wrote:
       | This is delightfully clever and hacky (so basically like every 3d
       | rendering technique ever) but the end result isn't exactly
       | beautiful unless you're trying to recreate an old school
       | electronic billboard. You could improve it by adding more bits,
       | but long before it starts to look good you'd be searching for an
       | easier way to handle setting all the bits... And there's almost
       | certainly no more efficient solution than using black and white
       | pixels in a drawing program then saving the results in a texture.
       | So, full circle.
       | 
       | 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.
        
         | 082349872349872 wrote:
         | > _So, full circle_
         | 
         | 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 samsara.
        
         | noduerme wrote:
         | It is pretty clever for debug text if, for instance, textures
         | aren't uploading properly. But uh... while it's cute that the
         | OP compares sprite sheets to 16th century manual typesetting,
         | the reality is that it took a printer's assistant an hour to
         | layout a broadsheet of tiny metal slugs on a press, and it
         | takes oh < 10ms to upload a spritesheet to a GPU, which is then
         | infinitely configurable.
         | 
         | Not saying it's not a neat trick, it is.
        
       | the-smug-one wrote:
       | Fun, I really wish I had the ability to reason around shaders and
       | draw calls to do things like this :-).
        
       | IshKebab wrote:
       | Pretty confusing to say you're not going to store a bitmap in the
       | shader... and then explain exactly how you stored a bitmap in the
       | shader!
       | 
       | (TL;DR, he embeds a bitmap font in the shader.)
        
         | naavis wrote:
         | No, they say they are not going to store a bitmap in a texture,
         | which is not the same thing as embedding directly in the shader
         | code.
         | 
         | 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.
        
           | Galanwe wrote:
           | While not technically misleading, I also find it a bit
           | misleading.
           | 
           | 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.
        
           | itronitron wrote:
           | I suppose you could call this approach "texture as code"
        
           | ginko wrote:
           | The effect of that is that you're circumventing using
           | hardware specialized for efficient pixel lookup in favor of
           | using general data lookup inside the shader binary. You're
           | saving yourself some memory due to using 1 bit per pixel
           | rather than at least 8 (none of the major APIs expose a 1-bit
           | texture format AFAIK so R8 would be the next best thing), but
           | you're bound to use some extra cycles for the lookup and
           | decoding of your embedded font.
        
             | Const-me wrote:
             | > none of the major APIs expose a 1-bit texture format
             | AFAIK so R8 would be the next best thing
             | 
             | 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...
        
           | dahart wrote:
           | The bitmap absolutely _is_ a texture in the broad sense of
           | the word. It's not a Vulkan texture in the sense that it
           | doesn't use the Vulkan texture API, but it is a texture
           | nonetheless.
           | 
           | 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.
        
           | glimshe wrote:
           | Memory is memory, irrespective of whether it's "code memory"
           | or "data memory".
           | 
           | 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)
        
           | IshKebab wrote:
           | He doesn't:
           | 
           | > 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.
        
       | bob1029 wrote:
       | There's also the option of rendering text as meshes.
       | 
       | TextMeshPro goes one step further and uses signed distance fields
       | to handle arbitrary scale.
       | 
       | https://docs.unity3d.com/Packages/com.unity.textmeshpro@4.0/...
        
         | jsheard wrote:
         | Going one step further still there's the option of evaluating
         | font curves directly on the GPU, which can be high quality
         | regardless of scale or perspective. That turns out to be very
         | difficult to do efficiently but it can be done.
         | 
         | e.g. https://sluglibrary.com
         | 
         | 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.
        
           | ath92 wrote:
           | Another example of this by Evan Wallace (founder of Figma):
           | https://medium.com/@evanwallace/easy-scalable-text-
           | rendering... (code: https://github.com/evanw/theta)
        
       | exDM69 wrote:
       | I have a similar technique before, embedding the entire font data
       | in the fragment shader source code. Then you can use `snprintf`
       | to directly print into a GPU buffer mapped to the CPU (this is a
       | footgun, I know). Instead of drawing individual characters with a
       | vertex shader, I've just drawn one full screen triangle and use
       | `gl_FragCoord` instead of UV coordinates. Not the most efficient
       | way of doing things but it's a debug feature and it's fast enough
       | to be practical.
       | 
       | 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...
        
         | LoganDark wrote:
         | > You can find the "NES font" and other 8x8 pixel fonts around
         | the web.
         | 
         | This is my favorite pixel font pack:
         | 
         | https://int10h.org/oldschool-pc-fonts/
        
           | shikaan wrote:
           | Thanks so much! I am doing something similar to OP in my game
           | engine, but I hand-rolled a font, which is readable but ugly
           | af.
           | 
           | This is going to be such a source of inspiration! Do people
           | have favorites from the list?
        
             | spookie wrote:
             | Mine is IBM XGA-AI 12x20 for sure
        
         | RiverCrochet wrote:
         | Incoming trivia:
         | 
         | 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.
        
           | LocalH wrote:
           | The font has slightly changed over the years. Notably, the
           | "E" glyph originally had a longer lower arm, and the "!" and
           | "?" glyphs are often different between variants of the font.
           | Super Mario Bros also notably modifies the 8 to have a
           | straight crossbar instead of intersecting spines.
        
       | bazzargh wrote:
       | Previously https://news.ycombinator.com/item?id=41993084
       | 
       | (tho mine was the only comment last time)
        
       | styczen wrote:
       | Please make similar in SDL
        
       | unwind wrote:
       | Very cool! It would be fun with some kind of performance
       | comparison against the "traditional" textured method.
       | 
       | 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". :/
        
         | HappMacDonald wrote:
         | The answer I look for in "how does it perform?" is "VSCode
         | stops eating hundreds to thousands of MB of my VRAM".
        
       | kardianos wrote:
       | I think SLUG does this, but professionally:
       | 
       | https://sluglibrary.com/
        
       | janci wrote:
       | Honest question as I don't know almost anything about modern
       | computer graphics: Is it so much performance penalty to upload a
       | small texture to GPU so you can't render the whole string to the
       | texture in 2D and just display the texture onto two triangles?
        
         | superjan wrote:
         | It depends on the application. It's the easiest way especially
         | if you might encounter right to left script, CJK or emoji. It
         | is worthwhile to cache the textures, most text does not change
         | every frame. It is good enough for us.
        
         | jamesu wrote:
         | Generally you want to avoid wasting too much memory on a GPU,
         | even today. That large text box texture also has to go over the
         | PCI bus which can cause stalls depending on when its uploaded
         | and if the GPU ends up having to evict resources. If you end up
         | having a lot of independent texture text boxes being rendered
         | by the comparatively slower CPU, that could add up quickly and
         | cut into your budget.
         | 
         | 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.
        
         | Sesse__ wrote:
         | You can render the entire string before upload, but then you
         | are essentially using a CPU render, which will be slower than
         | having the GPU do the same thing.
         | 
         | 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.
        
         | spookie wrote:
         | It's huge. Passing data from CPU to GPU is most likely, 90% of
         | time, the biggest bottleneck.
        
         | ferbivore wrote:
         | Drawing one quad to cover N characters and picking out a glyph
         | in the shader is going to be faster than drawing individual
         | quads for each character (for monospace fonts, anyway). But
         | there are only so many characters you can fill the screen with,
         | so it's probably not a huge difference in practice.
         | 
         | 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.
        
         | nox101 wrote:
         | > Is it so much performance penalty to upload a small texture
         | to GPU so you can't render the whole string to the texture in
         | 2D and just display the texture onto two triangles?
         | 
         | 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
        
       | somat wrote:
       | I find it interesting how much effort was put into getting high
       | quality scalable vector fonts and how useless these techniques
       | were once we got our accelerated vector graphics co-processors.
       | 
       | 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?
        
         | immibis wrote:
         | IIRC every possible quadratic bezier curve (the kind used in
         | truetype) can be rendered as one triangle and the equation in
         | barycentric coordinates is identical for all possible curves,
         | so you can just evaluate it in the shader with no extra vertex
         | data.
        
       | stonethrowaway wrote:
       | As an aside I've yet to come across anything more technically
       | complete than ClearType. Bitmaps/Textures done via some janky
       | early-2000 NeHe tutorial inspired thing aren't even on the table.
       | Yeah people will hate on Microsoft and Windows and bicker and
       | whatever, I don't care because of all the shit I've dealt with
       | trying to use freetype and additional libraries, ClearType has
       | never let me down. I've used D2D with D3D in conjunction with
       | shared surfaces and other hacks to join the two, and it's
       | pleasant enough that the final product is well worth the
       | programming agony.
        
       | dahart wrote:
       | If anyone wants to try this, work through the artithmetic, it's
       | incredibly easy (and a fun Saturday morning exercise if you're
       | into this kind of thing) to code up on ShaderToy. From scratch is
       | fun, but if you need a hint to get started I just made one
       | https://www.shadertoy.com/view/Mc3cW2 and there are a bunch of
       | super clever text hacks other people have done like this Matrix
       | in less than 300 characters https://www.shadertoy.com/view/llXSzj
       | or green CRT display effect
       | https://www.shadertoy.com/view/XtfSD8. Loads of other examples
       | abound if you look around.
        
         | 0x1ceb00da wrote:
         | I've never been able to make text look good at small sizes
         | whenever I've tried immediate mode text rendering. Even in the
         | first shadertoy, in vec2(30, -30), if you change 30 to 300,
         | you'll see some artifacts. Is there a trick to getting that
         | right? For me, multisampling the texture inside fragment shader
         | appears to work the best, although it still isn't as good as
         | the state of the art.
        
           | dahart wrote:
           | Yeah for that you want to do something better than the
           | nearest neighbor sampling I did there. Multisampling can help
           | but there are definitely some other alternatives. This is
           | where the texture/atlas method the author avoided comes in
           | very handy, because it typically comes with mip-mapping which
           | will help small text look good. There are also analytic
           | stroke drawing methods, though even that isn't perfect (it
           | always depends on your choice of filter and what actual
           | display you use, and what your goals are, etc.)
           | 
           | 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
        
         | hnisoss wrote:
         | offtopic but interesting, matrix effect in HTML/CSS/JS, 1024
         | bytes,
         | 
         | ``` <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=docu
         | ment;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.createEl
         | ement("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.len
         | gth-50)>0){if((e.style.opacity=1-v/32)==0){for(f in c)if(c[f].s
         | tyle)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;fo
         | r(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()> ```
         | 
         | https://codegolf.stackexchange.com/a/17414
        
       | variadix wrote:
       | Sebastian Lague has a good video covering many different font
       | rendering techniques.
       | 
       | https://youtu.be/SO83KQuuZvg
        
       | Retr0id wrote:
       | I've thought about doing something like this before, but my
       | understanding was that GPUs are especially efficient at rendering
       | from textures, while being relatively slow at bit twiddling. So
       | although you're saving a little bit of memory here, is it
       | _actually_ faster than having an atlas?
       | 
       | Maybe you could get the best of both worlds by bitpacking into a
       | regular texture, with a fragment shader doing the decoding.
        
       | z3t4 wrote:
       | It would be interesting to compare this technique with native
       | text rendering. How many FPS can you get when rendering a full
       | screen of text?
        
       ___________________________________________________________________
       (page generated 2024-11-09 23:00 UTC)