[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)