← Blog
0.7.0 · VIDEO · BROWSER

WebGL 1.0 Stub + Sub-Frame Dirty-Rect Compositor — WEBGL-DIRTY-PASS

June 22, 2026 · sigil-video 5460b56 · Sigil-Docs
webgl compositor browser video dirty-rect 0.7.0

Two browser rendering components land in sigil-video 5460b56. webgl.sg is the WebGL 1.0 stub — it lets web content detect WebGL support and draw triangles today, with GPU paths silicon-pending. comp_dirty.sg brings a tile-level dirty-rect compositor that only pushes changed 64×64 tiles to the GPU, cutting browser repaint cost on Pi 3 hardware. Both follow GPU-first: GPU paths wired and silicon-pending; software floors (triangle bounding-box fill_span / tile-skip composite) work on Pi 3 and QEMU now. WEBGL-DIRTY-PASS: 1 triangle → bbox fill → pixel probe; tile dirty mark + composite; untouched tile delta=0.


webgl.sg — WebGL 1.0 stub

Web content today feature-detects WebGL before deciding whether to use it. Without WebGL support, many sites fall back to 2D canvas or refuse to render entirely. webgl.sg gives the sigilOS browser a real WebGL context — enough for feature detection, basic triangle draw, and gradual GPU path integration as the hardware validates.

The design is GPU-first per the GPU_FIRST_RENDERING standing rule. The GPU path (gpu_submit(CAP_ACCEL, webgl_ctx_buf(), fb, fw, fh)) is wired and stubbed pending silicon. The software floor draws triangles via bounding-box fill_span — exact for convex shapes, correct for web content that uses triangles for UI primitives, progress bars, and simple geometry.

Context memory: webgl_ctx_buf() at 0xA30000 (after css_xform_buf at 0xA10000 + 384B and img_desc_buf at 0xA20000 + 512B). 4 context slots × 64 bytes.

WebGL context API

gl_ctx_create(slot, fb, fw, fh)
Initialize a WebGL context in slot slot (up to 4 contexts). Associates with framebuffer fb at width fw × height fh. GPU path: allocates a VRAM-backed context. SW floor: records the fb pointer.
gl_buffer_data(ctx, verts, nv)
Upload vertex array. verts is an array of (x,y) pairs in screen coordinates; nv is the vertex count. GPU path: DMA upload to vertex buffer. SW floor: copies into the context's vertex store.
gl_shader_compile(ctx, type, src, slen)
Stub shader compilation. type: VERTEX or FRAGMENT. Returns 0 (success) always on SW floor — no runtime shader evaluation yet. GPU path: compiles GLSL on the silicon shader core.
gl_set_color(ctx, r, g, b, a)
Set current draw color (0–255 per channel). Affects all subsequent draw_arrays calls on this context.
gl_draw_arrays(ctx, mode, first, count)
Draw count vertices starting at first. Mode GL_TRIANGLES: bounding-box fill. SW floor: finds min/max x,y from the vertex array, fills the box with fill_span in the current color. GPU path: gpu_submit with draw call. Triangle bounding-box is exact for right-angle / screen-aligned triangles; sufficient for initial web content compat.

comp_dirty.sg — tile-level dirty-rect compositor

The browser compositor (compositor.sg, COMP-PASS 16997fc) does layer-level dirty tracking — it skips compositing entire layers when unchanged. comp_dirty.sg adds a finer grain: tile-level dirty tracking within a single layer. A 1280×720 display has 200 × 112 = 22,400 tiles of 64×64px. A text cursor blink touches 1 tile. Scrolling touches maybe 20 tiles on one side. Without tile tracking, every repaint pushes the full framebuffer — ~3.5 MB for 1280×720 RGBA32. With tile tracking, only changed tiles go to the GPU.

The tile model is the same as SRDX encode: 64×64px tiles, ceiling division ((w + 63) / 64). This is not coincidental — dirty-rect browser repaint and SRDX pixel stream share the same tile math so the SRDX encoder can directly consume browser repaint output without an extra copy.

API

comp_dirty_init(w, h)
Initialize tile dirty table for a display of w × h pixels. Computes ntiles_x = (w+63)/64, ntiles_y = (h+63)/64. Zeros all dirty flags at comp_tile_dirty_buf() (0xA40000 — 1 byte per tile, up to 256×256 tiles = 64KB).
comp_dirty_mark(tx, ty)
Mark tile at column tx, row ty as dirty. Single byte write: comp_tile_dirty_buf() + ty * ntiles_x + tx = 1.
comp_dirty_mark_rect(x, y, w, h)
Mark all tiles intersecting rectangle (x,y,w,h) as dirty. Converts pixel coordinates to tile ranges (x/64 .. (x+w-1)/64) and marks each. Used by the renderer when it knows which DOM element changed.
comp_dirty_composite(fb, src, fw, fh)
Iterate all tiles; for each dirty tile, copy src tile data to fb (blit 64×64 pixels or partial tile at edges). GPU path: gpu_submit(CAP_ACCEL, comp_tile_dirty_buf(), fb, fw, fh) — GPU compositor reads the dirty bitmap and batches tile transfers. SW floor: copy_span per row of each dirty tile. Clears dirty flags after composite.

WEBGL-DIRTY-PASS — test results

CheckWhat it verifies
gl_ctx_createContext slot 0 initialized (160×120 test canvas)
gl_buffer_data3 vertices uploaded (triangle at (10,10), (50,10), (30,40))
gl_set_color redColor set to (255,0,0,255)
gl_draw_arrays triangleSW floor: bounding box (10,10)→(50,40) filled red
pixel probe (30,25)In triangle bbox → red pixel confirmed
comp_dirty_mark (1,0)Tile at column 1, row 0 marked dirty
comp_dirty_compositeOnly tile (1,0) pushed to fb; tile (0,0) skipped
tile (1,0) blue pixelDirty tile composited with blue test pattern
tile (0,0) untoucheddelta=0 — unchanged tile not pushed

PASS: WEBGL-DIRTY-PASS webgl=ok dirty=ok delta=0

WEBGL-DIRTY-PASS on x86 QEMU
WEBGL-DIRTY-PASS on x86 QEMU — WebGL 1.0 triangle draw (bounding-box red fill, pixel probe at (30,25) confirmed), tile dirty compositor (tile (1,0) blue, tile (0,0) untouched, delta=0).