-
-
Save mmozeiko/c7cd68ba0733a0d9e4f0a97691a50d39 to your computer and use it in GitHub Desktop.
// | |
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); | |
} |
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.
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.
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?