This commit is contained in:
agra
2026-03-02 19:47:25 +02:00
parent 812bc6d6ec
commit e63c946116
33 changed files with 32185 additions and 202 deletions

View File

@@ -1,4 +1,5 @@
#import "modules/std.sx";
#import "modules/compiler.sx";
#import "modules/opengl.sx";
#import "modules/math";
#import "ui/types.sx";
@@ -14,17 +15,23 @@ UIRenderer :: struct {
vbo: u32;
shader: u32;
proj_loc: s32;
tex_loc: s32;
vertices: [*]f32;
vertex_count: s64;
screen_width: f32;
screen_height: f32;
white_texture: u32;
current_texture: u32;
init :: (self: *UIRenderer) {
// Create shader
self.shader = create_program(UI_VERT_SRC, UI_FRAG_SRC);
// Create shader (ES for WASM/WebGL2, Core for desktop)
inline if OS == .wasm {
self.shader = create_program(UI_VERT_SRC_ES, UI_FRAG_SRC_ES);
} else {
self.shader = create_program(UI_VERT_SRC_CORE, UI_FRAG_SRC_CORE);
}
self.proj_loc = glGetUniformLocation(self.shader, "uProj");
self.tex_loc = glGetUniformLocation(self.shader, "uTex");
// Allocate vertex buffer (CPU side)
buf_size := MAX_UI_VERTICES * UI_VERTEX_BYTES;
@@ -62,6 +69,14 @@ UIRenderer :: struct {
self.screen_width = width;
self.screen_height = height;
self.vertex_count = 0;
self.current_texture = self.white_texture;
}
bind_texture :: (self: *UIRenderer, tex: u32) {
if tex != self.current_texture {
self.flush();
self.current_texture = tex;
}
}
// Emit a quad (2 triangles = 6 vertices)
@@ -122,13 +137,14 @@ UIRenderer :: struct {
self.push_quad(node.frame, node.fill_color, node.corner_radius, node.stroke_width);
}
case .text: {
// TODO: text rendering via glyph atlas
// For now, render a placeholder rect
self.push_quad(node.frame, node.text_color, 0.0, 0.0);
if xx g_font != 0 and g_font.char_data != null {
self.render_text(node);
}
}
case .image: {
// TODO: textured quad
self.bind_texture(node.texture_id);
self.push_quad(node.frame, COLOR_WHITE, 0.0, 0.0);
self.bind_texture(self.white_texture);
}
case .clip_push: {
self.flush();
@@ -160,6 +176,11 @@ UIRenderer :: struct {
proj := Mat4.ortho(0.0, self.screen_width, self.screen_height, 0.0, -1.0, 1.0);
glUniformMatrix4fv(self.proj_loc, 1, 0, proj.data);
// Bind current texture
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, self.current_texture);
glUniform1i(self.tex_loc, 0);
glBindVertexArray(self.vao);
glBindBuffer(GL_ARRAY_BUFFER, self.vbo);
@@ -171,39 +192,66 @@ UIRenderer :: struct {
glBindVertexArray(0);
self.vertex_count = 0;
}
render_text :: (self: *UIRenderer, node: RenderNode) {
font := g_font;
scale := node.font_size / font.font_size;
self.bind_texture(font.texture_id);
r := node.text_color.rf();
g := node.text_color.gf();
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 = ---;
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);
// 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 self.vertex_count + 6 > MAX_UI_VERTICES {
self.flush();
}
// 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;
}
self.bind_texture(self.white_texture);
}
}
// Additional GL functions needed for UI renderer
GL_SCISSOR_TEST :u32: 0x0C11;
GL_DYNAMIC_DRAW :u32: 0x88E8;
GL_TEXTURE_2D :u32: 0x0DE1;
GL_TEXTURE_MIN_FILTER :u32: 0x2801;
GL_TEXTURE_MAG_FILTER :u32: 0x2800;
GL_NEAREST :u32: 0x2600;
GL_RGBA :u32: 0x1908;
GL_UNSIGNED_BYTE :u32: 0x1401;
GL_SRC_ALPHA :u32: 0x0302;
GL_ONE_MINUS_SRC_ALPHA :u32: 0x0303;
glScissor : (s32, s32, s32, s32) -> void = ---;
glBufferSubData : (u32, s64, s64, *void) -> void = ---;
glGenTextures : (s32, *u32) -> void = ---;
glBindTexture : (u32, u32) -> void = ---;
glTexImage2D : (u32, s32, s32, s32, s32, s32, u32, u32, *void) -> void = ---;
glTexParameteri : (u32, u32, s32) -> void = ---;
glBlendFunc : (u32, u32) -> void = ---;
glReadPixels : (s32, s32, s32, s32, u32, u32, *void) -> void = ---;
// Load additional GL functions (call after load_gl)
load_gl_ui :: (get_proc: ([:0]u8) -> *void) {
glScissor = xx get_proc("glScissor");
glBufferSubData = xx get_proc("glBufferSubData");
glGenTextures = xx get_proc("glGenTextures");
glBindTexture = xx get_proc("glBindTexture");
glTexImage2D = xx get_proc("glTexImage2D");
glTexParameteri = xx get_proc("glTexParameteri");
glBlendFunc = xx get_proc("glBlendFunc");
glReadPixels = xx get_proc("glReadPixels");
// 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 {
@@ -219,7 +267,9 @@ create_white_texture :: () -> u32 {
// --- UI Shaders ---
UI_VERT_SRC :: #string GLSL
// --- Desktop (Core Profile 3.3) shaders ---
UI_VERT_SRC_CORE :: #string GLSL
#version 330 core
layout(location = 0) in vec2 aPos;
layout(location = 1) in vec2 aUV;
@@ -240,11 +290,13 @@ void main() {
}
GLSL;
UI_FRAG_SRC :: #string GLSL
UI_FRAG_SRC_CORE :: #string GLSL
#version 330 core
in vec2 vUV;
in vec4 vColor;
in vec4 vParams; // corner_radius, border_width, rect_w, rect_h
in vec4 vParams;
uniform sampler2D uTex;
out vec4 FragColor;
@@ -258,7 +310,11 @@ void main() {
float border = vParams.y;
vec2 rectSize = vParams.zw;
if (radius > 0.0 || border > 0.0) {
if (radius < 0.0) {
float textAlpha = texture(uTex, vUV).r;
FragColor = vec4(vColor.rgb, vColor.a * textAlpha);
} else if (radius > 0.0 || border > 0.0) {
vec4 texColor = texture(uTex, vUV);
vec2 half_size = rectSize * 0.5;
vec2 center = (vUV - vec2(0.5)) * rectSize;
float dist = roundedBoxSDF(center, half_size, radius);
@@ -271,9 +327,80 @@ void main() {
alpha = alpha * max(border_alpha, 0.0);
}
FragColor = vec4(vColor.rgb, vColor.a * alpha);
FragColor = vec4(texColor.rgb * vColor.rgb, texColor.a * vColor.a * alpha);
} else {
FragColor = vColor;
vec4 texColor = texture(uTex, vUV);
FragColor = texColor * vColor;
}
}
GLSL;
// --- WASM (ES 3.0 / WebGL2) shaders ---
UI_VERT_SRC_ES :: #string GLSL
#version 300 es
precision mediump float;
layout(location = 0) in vec2 aPos;
layout(location = 1) in vec2 aUV;
layout(location = 2) in vec4 aColor;
layout(location = 3) in vec4 aParams;
uniform mat4 uProj;
out vec2 vUV;
out vec4 vColor;
out vec4 vParams;
void main() {
gl_Position = uProj * vec4(aPos, 0.0, 1.0);
vUV = aUV;
vColor = aColor;
vParams = aParams;
}
GLSL;
UI_FRAG_SRC_ES :: #string GLSL
#version 300 es
precision mediump float;
in vec2 vUV;
in vec4 vColor;
in vec4 vParams;
uniform sampler2D uTex;
out vec4 FragColor;
float roundedBoxSDF(vec2 center, vec2 half_size, float radius) {
vec2 q = abs(center) - half_size + vec2(radius);
return length(max(q, vec2(0.0))) + min(max(q.x, q.y), 0.0) - radius;
}
void main() {
float radius = vParams.x;
float border = vParams.y;
vec2 rectSize = vParams.zw;
if (radius < 0.0) {
float textAlpha = texture(uTex, vUV).r;
FragColor = vec4(vColor.rgb, vColor.a * textAlpha);
} else if (radius > 0.0 || border > 0.0) {
vec4 texColor = texture(uTex, vUV);
vec2 half_size = rectSize * 0.5;
vec2 center = (vUV - vec2(0.5)) * rectSize;
float dist = roundedBoxSDF(center, half_size, radius);
float aa = fwidth(dist);
float alpha = 1.0 - smoothstep(-aa, aa, dist);
if (border > 0.0) {
float inner = roundedBoxSDF(center, half_size - vec2(border), max(radius - border, 0.0));
float border_alpha = smoothstep(-aa, aa, inner);
alpha = alpha * max(border_alpha, 0.0);
}
FragColor = vec4(texColor.rgb * vColor.rgb, texColor.a * vColor.a * alpha);
} else {
vec4 texColor = texture(uTex, vUV);
FragColor = texColor * vColor;
}
}
GLSL;