CUI

Guide

Rendering

CUI turns every shape into vertices of a single format and a few draw commands. A renderer only needs one shader.

Draw data

typedef struct cui_draw_data {
    const cui_vertex *vertices;  int vertex_count;
    const uint32_t *indices;     int index_count;     /* triangles */
    const cui_draw_cmd *commands; int command_count;
    int width_px, height_px;                         /* framebuffer size */
    cui_color clear_color;                           /* the theme background */
    cui_texture font_atlas;
} cui_draw_data;

typedef struct cui_draw_cmd {
    cui_rect clip;            /* scissor in pixels, top left origin */
    cui_texture texture;      /* 0 = font atlas */
    uint32_t index_offset, index_count;
} cui_draw_cmd;

Draw commands in order. Each command changes at most the scissor and the texture, so a typical frame is a handful of draw calls.

Vertex format (64 bytes)

FieldTypeMeaning
x, yfloatPosition in pixels
u, vfloatTexture coordinate
lx, lyfloatPosition relative to the shape's center, pixels
hw, hhfloatHalf width and half height of the shape
r_tl, r_tr, r_br, r_blfloatCorner radii
borderfloatBorder width (rounded rectangles) or blur sigma (shadows)
modefloatShading mode, see below
color4 × u8Fill color, straight alpha, interpolated
border_color4 × u8Border color, constant per shape

All fields except x, y, u, v, lx, ly, color are the same for every vertex of a shape and can be flat in shading languages.

ModeValueShading
CUI_SHADE_TEXT0color × atlas red channel (coverage)
CUI_SHADE_RRECT1Signed distance rounded rectangle with optional border
CUI_SHADE_SHADOW2Gaussian blurred rounded rectangle
CUI_SHADE_IMAGE3RGBA texture × color, masked by the rounded rectangle
CUI_SHADE_SOLID4color (anti-aliasing is in the geometry)

Reference shader (GLSL 330)

// vertex
in vec2 a_pos, a_uv, a_local, a_half; in vec4 a_radii; in vec2 a_params;   // params = (border, mode)
in vec4 a_color, a_bcolor;                                                  // normalized u8
uniform vec2 u_viewport;                                                    // framebuffer size in pixels
out vec2 v_uv, v_local; flat out vec2 v_half; flat out vec4 v_radii; flat out vec2 v_params;
out vec4 v_color; flat out vec4 v_bcolor;
void main() {
    v_uv = a_uv; v_local = a_local; v_half = a_half; v_radii = a_radii; v_params = a_params;
    v_color = a_color; v_bcolor = a_bcolor;
    gl_Position = vec4(a_pos.x / u_viewport.x * 2.0 - 1.0, 1.0 - a_pos.y / u_viewport.y * 2.0, 0.0, 1.0);
}

// fragment
uniform sampler2D u_tex;
float sd_rrect(vec2 p, vec2 b, vec4 r) {           // r = (tl, tr, br, bl), y down
    float rr = p.x < 0.0 ? (p.y < 0.0 ? r.x : r.w) : (p.y < 0.0 ? r.y : r.z);
    rr = min(rr, min(b.x, b.y));
    vec2 q = abs(p) - b + rr;
    return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - rr;
}
float erf_(float x) {                              // Abramowitz and Stegun 7.1.26
    float s = sign(x); x = abs(x);
    float t = 1.0 / (1.0 + 0.3275911 * x);
    return s * (1.0 - (((((1.061405429 * t - 1.453152027) * t) + 1.421413741) * t - 0.284496736) * t + 0.254829592) * t * exp(-x * x));
}
void main() {
    int mode = int(v_params.y + 0.5);
    vec4 c = v_color;
    if (mode == 4) { frag = vec4(c.rgb * c.a, c.a); return; }
    if (mode == 0) { float a = c.a * texture(u_tex, v_uv).r; frag = vec4(c.rgb * a, a); return; }
    float d = sd_rrect(v_local, v_half, v_radii);
    if (mode == 1) {
        float outer = clamp(0.5 - d, 0.0, 1.0);
        float inner = v_params.x > 0.0 ? clamp(0.5 - (d + v_params.x), 0.0, 1.0) : outer;
        float fa = c.a * inner, ba = v_bcolor.a * (outer - inner);
        frag = vec4(c.rgb * fa + v_bcolor.rgb * ba, fa + ba);
    } else if (mode == 2) {
        float a = c.a * 0.5 * (1.0 - erf_(d / (max(v_params.x, 0.01) * 1.41421356)));
        frag = vec4(c.rgb * a, a);
    } else {
        float k = c.a * clamp(0.5 - d, 0.0, 1.0);
        vec4 t = texture(u_tex, v_uv);
        frag = vec4(t.rgb * t.a * c.rgb * k, t.a * k);
    }
}

The output is premultiplied. Blend with ONE, ONE_MINUS_SRC_ALPHA. Sample textures with linear filtering and clamp to edge. The software renderer in src/render/cui_render_sw.c implements the same math in C and is a good second reference.