Skip to content

Instantly share code, notes, and snippets.

@mmozeiko
Last active February 4, 2024 17:53
Show Gist options
  • Star 43 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save mmozeiko/c7cd68ba0733a0d9e4f0a97691a50d39 to your computer and use it in GitHub Desktop.
Save mmozeiko/c7cd68ba0733a0d9e4f0a97691a50d39 to your computer and use it in GitHub Desktop.
compute shader for rendering monospaced glyphs in grid
//
struct TerminalCell
{
// cell index into GlyphTexture, should be two 16-bit (x,y) values packed: "x | (y << 16)"
uint GlyphIndex;
// 0xAABBGGRR encoded colors, nonzero alpha for Foreground indicates to render colored-glyph
// which means use RGB values from GlyphTexture directly as output, not as ClearType blending weights
uint Foreground;
uint Background;
};
cbuffer ConstBuffer : register(b0)
{
uint2 CellSize;
uint2 TermSize;
};
// TermSize.x * TermSize.y amount of cells to render as output
StructuredBuffer<TerminalCell> Cells : register(t0);
// RGB blending weights for ClearType
// or alpha-premultipled RGB values for colored glyphs
Texture2D<float4> GlyphTexture : register(t1);
RWTexture2D<float4> Output : register(u0);
float3 GetColor(uint i)
{
int r = i & 0xff;
int g = (i >> 8) & 0xff;
int b = (i >> 16) & 0xff;
return float3(r, g, b) / 255.0;
}
uint2 GetGlyphPos(uint GlyphIndex)
{
return uint2(GlyphIndex & 0xffff, GlyphIndex >> 16) * CellSize;
}
// dispatch with (TermSize*CellSize+7)/8 groups for x,y and 1 for z
[numthreads(8, 8, 1)]
void shader(uint3 Id: SV_DispatchThreadID)
{
uint2 ScreenPos = Id.xy;
// index of cell on screen
uint2 CellIndex = ScreenPos / CellSize;
// pixel position in cell
uint2 CellPos = ScreenPos % CellSize;
TerminalCell Cell = Cells[CellIndex.y * TermSize.x + CellIndex.x];
// position where glyph starts in texture
uint2 GlyphPos = GetGlyphPos(Cell.GlyphIndex);
// absolute pixel location in texture to use for output
uint2 PixelPos = GlyphPos + CellPos;
float4 Tex = GlyphTexture[PixelPos];
float3 Background = GetColor(Cell.Background);
float3 Foreground = GetColor(Cell.Foreground);
bool ColoredGlyph = (Cell.Foreground >> 24) != 0;
float3 Color;
if (ColoredGlyph)
{
// colored glyphs are alpha premultiplied
Color = Background * (1.0 - Tex.a) + Tex.rgb;
}
else
{
// TODO: proper ClearType blending
Color = lerp(Background, Foreground, Tex.rgb);
}
Output[ScreenPos] = float4(Color, 1);
}
@CCDD564
Copy link

CCDD564 commented Nov 2, 2021

I have no experience with font rendering so the problem I'm having is probably very basic. One thing that confuses me is that all the cells are equally fixed size. But the font glyphs differ both in width and height and so does the spacing between them. So when we map the glyphs to their respective cells, but the cell is wider than the glyph, does this method introduce unwanted spacing between characters and causes problems for kerning? Or am I interpreting this the wrong way?

@mmozeiko
Copy link
Author

mmozeiko commented Nov 2, 2021

When you rasterize glyph with your font rasterizer you get whatever width & height it produces - you split the output into multiple cells and upload to font texture in gpu. Then this shader will render those cells next to each other without any spacing between them.

Casey convers texture generation in video here: https://www.youtube.com/watch?v=cGoQ3ceKX6g

Not sure what you mean by kerning. This is for monospace terminal rendering - there is no kerning for monospace characters.

@CCDD564
Copy link

CCDD564 commented Nov 3, 2021

Thank you for your answer and also my apologies, I should have looked into the terminology of what monospace meant. I was thinking that the fonts are "proportional". So this technique know makes sense given that monospaced glyphs are all equal in width and are fixed-pitch, and each cell can take any glyph (mostly Latin).
In a text editor the fonts are proportional and usually they support smooth scrolling, so this technique won't work. My guess is a text editor would just send an array of quads to the gpu each frame(depending how many characters you're rendering on the screen), and issue one draw call.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment