refactor(render): TextBackend owns its atlas, and staged glyphs carry a rotation basis

This commit is contained in:
Martino Ferrari committed 2026-09-28 09:57:28 +02:00
1 parent 90e17a0a11
commit e074f57ddf
23 files changed
+1073 -227

No files matched your search

+20 -10
View File
@@ -73,28 +73,38 @@ void main() {
)";
const char* const kTextVS = R"(#version 330 core
layout(location = 0) in vec2 a_pos; // per-instance glyph quad top-left, pixels
layout(location = 1) in vec2 a_uv; // per-instance glyph atlas texel origin
layout(location = 0) in vec2 a_pos; // per-instance glyph cell top-left, pixels
layout(location = 1) in vec2 a_uv; // per-instance glyph rect top-left, normalised
layout(location = 2) in vec2 a_axis; // per-instance cell +x axis, unit, pixels
uniform vec2 u_viewport; // render target size, pixels
uniform vec2 u_px_range; // pixels per glyph cell on screen
uniform vec2 u_px_range; // glyph cell on screen, pixels (width, height)
uniform vec2 u_glyph_uv; // glyph cell in the atlas, normalised (width, height)
out vec2 v_texel; // atlas texel coordinate of this fragment
const vec2 kGlyphTexels = vec2(8.0); // BitmapFont::kGlyphPx
out vec2 v_uv; // normalised atlas coordinate of this fragment
void main() {
// The cell's two axes: the run direction the instance carries and its
// perpendicular, so the cell follows the basis instead of the screen axes. The
// perpendicular is the cell's own +v axis -- with an unrotated basis
// (1.0, 0.0) it is (0.0, 1.0), i.e. down the screen, which is the direction v
// grows in the atlas -- and building it this way keeps the quad's orientation:
// a mirrored perpendicular would draw every glyph flipped about its diagonal.
vec2 ux = a_axis;
vec2 uy = vec2(-ux.y, ux.x);
vec2 corner = vec2(float(gl_VertexID & 1), float((gl_VertexID >> 1) & 1));
vec2 px = a_pos + corner * u_px_range;
vec2 px = a_pos + corner.x * u_px_range.x * ux + corner.y * u_px_range.y * uy;
// Rect is top-left origin, GL is bottom-left: the one and only y flip.
vec2 ndc = vec2(px.x / u_viewport.x * 2.0 - 1.0, 1.0 - px.y / u_viewport.y * 2.0);
gl_Position = vec4(ndc, 0.0, 1.0);
v_texel = a_uv + corner * kGlyphTexels;
// The same handedness as the cell, so the glyph's rect lands on it the right
// way round: u runs along the basis, v down the glyph.
v_uv = a_uv + corner * u_glyph_uv;
}
)";
const char* const kTextFS = R"(#version 330 core
in vec2 v_texel; // atlas texel coordinate of this fragment
in vec2 v_uv; // normalised atlas coordinate of this fragment
uniform sampler2D u_atlas; // GL_R8 glyph atlas, GL_NEAREST
uniform vec2 u_tex_size; // atlas size in texels
@@ -106,7 +116,7 @@ void main() {
// Sample the centre of the texel the fragment lands in. GL_NEAREST alone
// would tie at a texel boundary, where the driver may pick the neighbouring
// glyph cell; rounding to a centre removes the tie by construction.
vec2 texel = clamp(floor(v_texel), vec2(0.0), u_tex_size - vec2(1.0));
vec2 texel = clamp(floor(v_uv * u_tex_size), vec2(0.0), u_tex_size - vec2(1.0));
float coverage = texture(u_atlas, (texel + vec2(0.5)) / u_tex_size).r;
o_color = vec4(u_color.rgb, u_color.a * coverage);
}