GLW Rendering Pipeline¶
Overview¶
The GLW rendering pipeline is a multi-stage process that transforms widget hierarchies into OpenGL draw calls. It handles geometry generation, effects processing, and efficient batching to deliver smooth 60fps performance.
Pipeline Architecture¶
graph TD
A[Frame Start] --> B[Property Updates]
B --> C[Layout Phase]
C --> D[Render Phase]
D --> E[Geometry Processing]
E --> F[Effects Pipeline]
F --> G[Job Submission]
G --> H[Sorting & Batching]
H --> I[OpenGL Rendering]
I --> J[Frame Present]
subgraph "Layout Phase"
C1[Widget Traversal]
C2[Size Calculation]
C3[Position Update]
C4[Matrix Generation]
C5[Visibility Culling]
end
subgraph "Effects Pipeline"
F1[Clipping Planes]
F2[Fade Effects]
F3[Blur Processing]
F4[Stencil Masking]
end
subgraph "Backend Rendering"
I1[Vertex Buffer Upload]
I2[Texture Binding]
I3[Shader Programs]
I4[Draw Calls]
end
Frame Lifecycle¶
Frame Preparation (glw_prepare_frame)¶
Timing Management:
void glw_prepare_frame(glw_root_t *gr, int flags) {
// Update frame timing
gr->gr_frame_start = arch_get_ts();
gr->gr_time_usec = gr->gr_frame_start - gr->gr_ui_start;
gr->gr_time_sec = gr->gr_time_usec / 1000000.0f;
// Calculate framerate
if(gr->gr_frames > 16) {
int64_t d = gr->gr_frame_start - gr->gr_framerate_avg[gr->gr_frames & 0xf];
double hz = 16000000.0 / d;
gr->gr_framerate = hz;
}
}
Property Processing: - Dispatch pending property events - Update widget properties from data bindings - Process dynamic view evaluations - Handle focus and navigation changes
Resource Management: - Update screen dimensions and aspect ratio - Process texture loading requests - Manage font glyph caching - Clean up destroyed widgets
Layout Phase¶
The layout phase calculates widget positions and sizes through recursive traversal:
Layout Context Propagation:
void glw_layout0(glw_t *w, const glw_rctx_t *rc) {
// Update active widget list
LIST_REMOVE(w, glw_active_link);
LIST_INSERT_HEAD(&gr->gr_active_list, w, glw_active_link);
// Mark as active if visible
if(!rc->rc_invisible) {
w->glw_flags |= GLW_ACTIVE;
glw_signal0(w, GLW_SIGNAL_ACTIVE, NULL);
}
// Process dynamic evaluations
if(w->glw_dynamic_eval & GLW_VIEW_EVAL_LAYOUT)
glw_view_eval_layout(w, rc, GLW_VIEW_EVAL_LAYOUT);
// Apply margins if present
if(w->glw_flags & GLW_HAVE_MARGINS) {
glw_rctx_t rc0 = *rc;
glw_reposition(&rc0, w->glw_margin[0], rc->rc_height - w->glw_margin[1],
rc->rc_width - w->glw_margin[2], w->glw_margin[3]);
w->glw_class->gc_layout(w, &rc0);
} else {
w->glw_class->gc_layout(w, rc);
}
}
Size and Position Calculation: - Container widgets calculate child positions - Constraint propagation (min/max sizes, aspect ratios) - Alignment and spacing application - Transformation matrix generation
Visibility Culling: - Check if widget intersects viewport - Apply alpha and clipping tests - Mark invisible widgets to skip rendering - Update active widget lists
Render Phase¶
The render phase generates geometry and submits render jobs:
Render Context Processing:
void glw_render0(glw_t *w, const glw_rctx_t *rc) {
// Handle Z-offset for layering
if(w->glw_zoffset) {
glw_rctx_t rc0 = *rc;
rc0.rc_zindex = w->glw_zoffset;
w->glw_class->gc_render(w, &rc0);
} else {
w->glw_class->gc_render(w, rc);
}
// Debug wireframe rendering
if(w->glw_flags2 & GLW2_DEBUG)
glw_wirebox(w->glw_root, rc);
}
Matrix Storage:
void glw_store_matrix(glw_t *w, const glw_rctx_t *rc) {
if(rc->rc_inhibit_matrix_store)
return;
if(w->glw_matrix == NULL)
w->glw_matrix = malloc(sizeof(Mtx));
*w->glw_matrix = rc->rc_mtx;
}
Geometry Processing¶
Vertex Format¶
GLW uses a standardized vertex format for all geometry:
#define VERTEX_SIZE 12 // floats per vertex
// Vertex layout (48 bytes per vertex):
// Offset 0-2: Position (x, y, z)
// Offset 3: Reserved/padding
// Offset 4-7: Color (r, g, b, a)
// Offset 8-9: Primary texture coordinates (s, t)
// Offset 10-11: Secondary texture coordinates (s2, t2)
Renderer System¶
Renderer Initialization:
void glw_renderer_init_quad(glw_renderer_t *gr) {
glw_renderer_init(gr, 4, 2, quadvertices);
// Initialize vertex colors to white
for(int i = 0; i < 4; i++) {
gr->gr_vertices[i * VERTEX_SIZE + 4] = 1.0f; // r
gr->gr_vertices[i * VERTEX_SIZE + 5] = 1.0f; // g
gr->gr_vertices[i * VERTEX_SIZE + 6] = 1.0f; // b
gr->gr_vertices[i * VERTEX_SIZE + 7] = 1.0f; // a
}
}
Vertex Manipulation:
void glw_renderer_vtx_pos(glw_renderer_t *gr, int vertex, float x, float y, float z) {
gr->gr_vertices[vertex * VERTEX_SIZE + 0] = x;
gr->gr_vertices[vertex * VERTEX_SIZE + 1] = y;
gr->gr_vertices[vertex * VERTEX_SIZE + 2] = z;
gr->gr_dirty = 1;
}
void glw_renderer_vtx_st(glw_renderer_t *gr, int vertex, float s, float t) {
gr->gr_vertices[vertex * VERTEX_SIZE + 8] = s;
gr->gr_vertices[vertex * VERTEX_SIZE + 9] = t;
gr->gr_dirty = 1;
}
Effects Pipeline¶
Software Clipping¶
GLW implements sophisticated software clipping for complex shapes:
Clipping Plane Setup:
int glw_clip_enable(glw_root_t *gr, const glw_rctx_t *rc,
glw_clip_boundary_t how, float distance,
float alpha_out, float sharpness_out) {
// Find available clipper slot
for(int i = 0; i < NUM_CLIPPLANES; i++)
if(!(gr->gr_active_clippers & (1 << i)))
break;
// Transform clipping plane to eye space
Vec4 v4 = {clip_planes[how][0], clip_planes[how][1],
clip_planes[how][2], 1 - (distance * 2)};
Mtx inv;
if(!glw_mtx_invert(&inv, &rc->rc_mtx))
return -1;
glw_mtx_trans_mul_vec4(gr->gr_clip[i], &inv, v4);
gr->gr_active_clippers |= (1 << i);
return i;
}
Triangle Clipping Algorithm:
static void clipper(glw_root_t *gr, glw_renderer_cache_t *grc,
const Vec4 V1, const Vec4 V2, const Vec4 V3,
const Vec4 C1, const Vec4 C2, const Vec4 C3,
const Vec4 T1, const Vec4 T2, const Vec4 T3,
int plane) {
// Test triangle vertices against clipping plane
const float D1 = glw_vec34_dot(V1, grc->grc_clip[plane]);
const float D2 = glw_vec34_dot(V2, grc->grc_clip[plane]);
const float D3 = glw_vec34_dot(V3, grc->grc_clip[plane]);
// Sutherland-Hodgman clipping algorithm
// Generates new triangles for partially clipped geometry
// Handles all 8 possible clipping cases
}
Fade Effects¶
Distance-based fading for depth and focus effects:
static void fader(glw_root_t *gr, glw_renderer_cache_t *grc,
const Vec4 V1, const Vec4 V2, const Vec4 V3,
const Vec4 C1, const Vec4 C2, const Vec4 C3,
const Vec4 T1, const Vec4 T2, const Vec4 T3,
int plane) {
for(int i = 0; i < NUM_FADERS; i++) {
if(!(grc->grc_active_faders & (1 << i)))
continue;
// Calculate distance from fade plane
const float D1 = glw_vec34_dot(V1, grc->grc_fader[i]);
const float D2 = glw_vec34_dot(V2, grc->grc_fader[i]);
const float D3 = glw_vec34_dot(V3, grc->grc_fader[i]);
// Apply alpha and blur based on distance
float ar = grc->grc_fader_alpha[i];
float br = grc->grc_fader_blur[i];
if(ar > 0) {
glw_vec4_mul_c3(c1, 1 + D1 / ar);
glw_vec4_mul_c3(c2, 1 + D2 / ar);
glw_vec4_mul_c3(c3, 1 + D3 / ar);
}
}
}
Tessellation and Caching¶
Geometry Tessellation:
static void glw_renderer_tesselate(glw_renderer_t *gr, glw_root_t *root,
const glw_rctx_t *rc, glw_renderer_cache_t *grc) {
root->gr_vtmp_cur = 0;
// Copy transformation and effect state
grc->grc_mtx = rc->rc_mtx;
grc->grc_active_clippers = root->gr_active_clippers;
// Process each triangle through effects pipeline
for(int i = 0; i < gr->gr_num_triangles; i++) {
int v1 = gr->gr_indices[i * 3 + 0];
int v2 = gr->gr_indices[i * 3 + 1];
int v3 = gr->gr_indices[i * 3 + 2];
// Transform vertices to eye space
Vec4 V1, V2, V3;
glw_pmtx_mul_vec4_i(V1, &pmtx, glw_vec4_get(a + v1*VERTEX_SIZE));
glw_pmtx_mul_vec4_i(V2, &pmtx, glw_vec4_get(a + v2*VERTEX_SIZE));
glw_pmtx_mul_vec4_i(V3, &pmtx, glw_vec4_get(a + v3*VERTEX_SIZE));
// Process through effects pipeline
clipper(root, grc, V1, V2, V3, C1, C2, C3, T1, T2, T3, 0);
}
}
Cache Management:
static glw_renderer_cache_t *glw_renderer_get_cache(glw_root_t *root, glw_renderer_t *gr) {
// Rotate cache slots each frame
if((root->gr_frames & 0xff) != gr->gr_framecmp) {
gr->gr_cacheptr = 0;
gr->gr_framecmp = root->gr_frames & 0xff;
} else {
gr->gr_cacheptr = (gr->gr_cacheptr + 1) & (GLW_RENDERER_CACHES - 1);
}
int idx = gr->gr_cacheptr;
if(gr->gr_cache[idx] == NULL)
gr->gr_cache[idx] = calloc(1, sizeof(glw_renderer_cache_t));
return gr->gr_cache[idx];
}
Render Job System¶
Job Submission¶
Render Job Structure:
typedef struct glw_render_job {
// Transformation
Mtx m; // Model matrix
int eyespace; // Pre-transformed flag
// Geometry
int vertex_offset; // Vertex buffer offset
int num_vertices; // Vertex count
int index_offset; // Index buffer offset
int num_indices; // Index count
// Textures and materials
const glw_backend_texture_t *t0, *t1;
glw_rgb_t rgb_mul, rgb_off; // Color modulation
float alpha, blur; // Effects
// Rendering state
int blendmode; // Blend function
int frontface; // Face culling
int flags; // Render flags
int16_t primitive_type; // GL_TRIANGLES, etc.
} glw_render_job_t;
Job Creation:
static void add_job(glw_root_t *gr, const Mtx *m,
const glw_backend_texture_t *t0, const glw_backend_texture_t *t1,
const glw_rgb_t *rgb_mul, const glw_rgb_t *rgb_off,
float alpha, float blur,
const float *vertices, int num_vertices,
const uint16_t *indices, int num_indices,
int flags, glw_program_args_t *gpa,
const glw_rctx_t *rc, int16_t primitive_type, int zoffset) {
// Expand job arrays if needed
if(gr->gr_num_render_jobs >= gr->gr_render_jobs_capacity) {
gr->gr_render_jobs_capacity = 100 + gr->gr_render_jobs_capacity * 2;
gr->gr_render_jobs = realloc(gr->gr_render_jobs,
sizeof(glw_render_job_t) * gr->gr_render_jobs_capacity);
}
// Copy vertex and index data to buffers
// Set up render job parameters
// Add to render queue
}
Sorting and Batching¶
Z-Order Sorting:
static int render_order_cmp(const void *A, const void *B) {
const glw_render_order_t *a = A;
const glw_render_order_t *b = B;
// Primary sort: Z-index (front to back)
if(a->zindex != b->zindex)
return a->zindex - b->zindex;
// Secondary sort: Texture (minimize state changes)
const glw_render_job_t *aj = a->job;
const glw_render_job_t *bj = b->job;
if(aj->t0 < bj->t0) return -1;
if(aj->t0 > bj->t0) return 1;
return 0;
}
void glw_renderer_render(glw_root_t *gr) {
// Sort render jobs for optimal rendering
qsort(gr->gr_render_order, gr->gr_num_render_jobs,
sizeof(glw_render_order_t), render_order_cmp);
// Execute backend rendering
gr->gr_be_render_unlocked(gr);
}
Performance Optimizations¶
Dirty Tracking¶
- Geometry Dirty Flag - Only re-tessellate when geometry changes
- Matrix Comparison - Cache tessellated geometry per transformation
- Effect State Tracking - Invalidate cache when clipping/fading changes
Memory Management¶
- Vertex Buffer Pooling - Reuse large vertex buffers across frames
- Cache Rotation - Multiple cache slots to handle multi-frame effects
- Temporary Buffer Growth - Exponential growth to minimize allocations
Culling and Optimization¶
- Viewport Culling - Skip widgets outside screen bounds
- Alpha Culling - Skip fully transparent widgets
- Z-Order Optimization - Front-to-back rendering for early Z rejection
- Texture Batching - Group draws by texture to minimize state changes
Backend Integration¶
OpenGL Backend¶
The rendering pipeline abstracts OpenGL operations through a backend interface:
// Backend function pointers in glw_root_t
void (*gr_be_render_unlocked)(glw_root_t *gr);
pixmap_t *(*gr_br_read_pixels)(glw_root_t *gr);
Vertex Buffer Management: - Dynamic vertex/index buffer allocation - Efficient upload strategies for different GL versions - Vertex array object (VAO) management
Shader Program Management: - Program selection based on render job flags - Uniform parameter binding - Texture unit management
State Management: - Blend mode switching - Depth testing configuration - Face culling setup
This rendering pipeline provides the foundation for GLW's smooth, efficient UI rendering while maintaining flexibility for complex effects and animations.