This commit is contained in:
agra
2026-03-03 13:25:25 +02:00
parent 6ffe22acb0
commit 343ea4bf08
9 changed files with 736 additions and 169 deletions

View File

@@ -4,6 +4,8 @@
#import "modules/math";
#import "ui/types.sx";
#import "ui/render.sx";
#import "ui/glyph_cache.sx";
#import "ui/font.sx";
// Vertex: pos(2) + uv(2) + color(4) + params(4) = 12 floats
UI_VERTEX_FLOATS :s64: 12;
@@ -20,6 +22,7 @@ UIRenderer :: struct {
vertex_count: s64;
screen_width: f32;
screen_height: f32;
dpi_scale: f32;
white_texture: u32;
current_texture: u32;
@@ -61,6 +64,8 @@ UIRenderer :: struct {
glBindVertexArray(0);
self.dpi_scale = 1.0;
// 1x1 white texture for solid rects
self.white_texture = create_white_texture();
}
@@ -137,7 +142,7 @@ UIRenderer :: struct {
self.push_quad(node.frame, node.fill_color, node.corner_radius, node.stroke_width);
}
case .text: {
if xx g_font != 0 and g_font.char_data != null {
if xx g_font != 0 {
self.render_text(node);
}
}
@@ -149,11 +154,12 @@ UIRenderer :: struct {
case .clip_push: {
self.flush();
glEnable(GL_SCISSOR_TEST);
dpi := self.dpi_scale;
glScissor(
xx node.frame.origin.x,
xx (self.screen_height - node.frame.origin.y - node.frame.size.height),
xx node.frame.size.width,
xx node.frame.size.height
xx (node.frame.origin.x * dpi),
xx ((self.screen_height - node.frame.origin.y - node.frame.size.height) * dpi),
xx (node.frame.size.width * dpi),
xx (node.frame.size.height * dpi)
);
}
case .clip_pop: {
@@ -195,8 +201,13 @@ UIRenderer :: struct {
render_text :: (self: *UIRenderer, node: RenderNode) {
font := g_font;
scale := node.font_size / font.font_size;
if xx font == 0 { return; }
// Shape text into positioned glyphs
font.shape_text(node.text, node.font_size);
// Flush any new glyphs to the atlas texture before rendering
font.flush();
self.bind_texture(font.texture_id);
r := node.text_color.rf();
@@ -204,56 +215,51 @@ UIRenderer :: struct {
b := node.text_color.bf();
a := node.text_color.af();
// stbtt_GetBakedQuad works at baked size; we scale output positions
xpos : f32 = 0.0;
ypos : f32 = 0.0;
q : AlignedQuad = ---;
ascent := font.get_ascent(node.font_size);
raster_size := node.font_size * font.dpi_scale;
inv_dpi := font.inv_dpi;
i : s64 = 0;
while i < node.text.len {
ch : s32 = xx node.text[i];
if ch >= FIRST_CHAR and ch < FIRST_CHAR + NUM_CHARS {
stbtt_GetBakedQuad(xx font.char_data, ATLAS_W, ATLAS_H, ch - FIRST_CHAR, @xpos, @ypos, xx @q, 1);
while i < font.shaped_buf.len {
shaped := font.shaped_buf.items[i];
cached := font.get_or_rasterize(shaped.glyph_index, raster_size);
// Scale and offset to frame position
// ypos=0 means baseline is at y=0; glyphs go above (negative yoff)
// Add ascent so top of text aligns with frame top
gx0 := node.frame.origin.x + q.x0 * scale;
gy0 := node.frame.origin.y + font.ascent * scale + q.y0 * scale;
gx1 := node.frame.origin.x + q.x1 * scale;
gy1 := node.frame.origin.y + font.ascent * scale + q.y1 * scale;
if xx cached != 0 {
if cached.width > 0.0 {
// Scale physical pixel dimensions back to logical units
gx0 := node.frame.origin.x + shaped.x + cached.offset_x * inv_dpi;
gy0 := node.frame.origin.y + ascent + shaped.y + cached.offset_y * inv_dpi;
gx1 := gx0 + cached.width * inv_dpi;
gy1 := gy0 + cached.height * inv_dpi;
if self.vertex_count + 6 > MAX_UI_VERTICES {
self.flush();
u0 := cached.uv_x;
v0 := cached.uv_y;
u1 := cached.uv_x + cached.uv_w;
v1 := cached.uv_y + cached.uv_h;
if self.vertex_count + 6 > MAX_UI_VERTICES {
self.flush();
}
// corner_radius = -1.0 signals "text mode" to the fragment shader
neg1 : f32 = 0.0 - 1.0;
self.write_vertex(gx0, gy0, u0, v0, r, g, b, a, neg1, 0.0, 0.0, 0.0);
self.write_vertex(gx1, gy0, u1, v0, r, g, b, a, neg1, 0.0, 0.0, 0.0);
self.write_vertex(gx0, gy1, u0, v1, r, g, b, a, neg1, 0.0, 0.0, 0.0);
self.write_vertex(gx1, gy0, u1, v0, r, g, b, a, neg1, 0.0, 0.0, 0.0);
self.write_vertex(gx1, gy1, u1, v1, r, g, b, a, neg1, 0.0, 0.0, 0.0);
self.write_vertex(gx0, gy1, u0, v1, r, g, b, a, neg1, 0.0, 0.0, 0.0);
}
// corner_radius = -1.0 signals "text mode" to the fragment shader
self.write_vertex(gx0, gy0, q.s0, q.t0, r, g, b, a, 0.0 - 1.0, 0.0, 0.0, 0.0);
self.write_vertex(gx1, gy0, q.s1, q.t0, r, g, b, a, 0.0 - 1.0, 0.0, 0.0, 0.0);
self.write_vertex(gx0, gy1, q.s0, q.t1, r, g, b, a, 0.0 - 1.0, 0.0, 0.0, 0.0);
self.write_vertex(gx1, gy0, q.s1, q.t0, r, g, b, a, 0.0 - 1.0, 0.0, 0.0, 0.0);
self.write_vertex(gx1, gy1, q.s1, q.t1, r, g, b, a, 0.0 - 1.0, 0.0, 0.0, 0.0);
self.write_vertex(gx0, gy1, q.s0, q.t1, r, g, b, a, 0.0 - 1.0, 0.0, 0.0, 0.0);
}
i += 1;
}
// Flush any glyphs rasterized during this text draw
font.flush();
self.bind_texture(self.white_texture);
}
}
// Upload font atlas bitmap as GL texture (called after GL init)
upload_font_texture :: (font: *FontAtlas) {
if font.bitmap == null { return; }
glGenTextures(1, @font.texture_id);
glBindTexture(GL_TEXTURE_2D, font.texture_id);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexImage2D(GL_TEXTURE_2D, 0, xx GL_R8, ATLAS_W, ATLAS_H, 0, GL_RED, GL_UNSIGNED_BYTE, font.bitmap);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, xx GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, xx GL_LINEAR);
context.allocator.dealloc(font.bitmap);
font.bitmap = null;
}
create_white_texture :: () -> u32 {
tex : u32 = 0;
glGenTextures(1, @tex);
@@ -311,8 +317,10 @@ void main() {
vec2 rectSize = vParams.zw;
if (radius < 0.0) {
float textAlpha = texture(uTex, vUV).r;
FragColor = vec4(vColor.rgb, vColor.a * textAlpha);
float alpha = texture(uTex, vUV).r;
float ew = fwidth(alpha) * 0.7;
alpha = smoothstep(0.5 - ew, 0.5 + ew, alpha);
FragColor = vec4(vColor.rgb, vColor.a * pow(alpha, 0.9));
} else if (radius > 0.0 || border > 0.0) {
vec4 texColor = texture(uTex, vUV);
vec2 half_size = rectSize * 0.5;
@@ -381,8 +389,10 @@ void main() {
vec2 rectSize = vParams.zw;
if (radius < 0.0) {
float textAlpha = texture(uTex, vUV).r;
FragColor = vec4(vColor.rgb, vColor.a * textAlpha);
float alpha = texture(uTex, vUV).r;
float ew = fwidth(alpha) * 0.7;
alpha = smoothstep(0.5 - ew, 0.5 + ew, alpha);
FragColor = vec4(vColor.rgb, vColor.a * pow(alpha, 0.9));
} else if (radius > 0.0 || border > 0.0) {
vec4 texColor = texture(uTex, vUV);
vec2 half_size = rectSize * 0.5;