AxlGfx — Graphics

Basic graphics output over the UEFI GOP (Graphics Output Protocol). Fill rectangles, blit pixel buffers, draw lines and outlines, render text from a pluggable bitmap font, composite through a clip stack, and double-buffer via off-screen pixel buffers. Falls back gracefully on headless systems where GOP is not available.

Headers:

  • <axl/axl-gfx.h> — framebuffer ops, off-screen buffers, clipping, drawing primitives, paths, rounded rects, text rendering

  • <axl/axl-font.h> — bitmap font primitives (AxlFont, AxlGlyph) consumed by axl-gfx and authored by tools / generators

  • <axl/axl-truetype.h> — vector text via stb_truetype. See the AxlTtf module <truetype.html>_ page for the full surface.

  • <axl/axl-pixmap.h> — PNG / JPEG / GIF / BMP decoders via stb_image; returns blittable AxlGfxBuffer. See the AxlPixmap module <pixmap.html>_ page.

This module is the graphics substrate for higher-level toolkits. Per docs/AGT-Design.md §”Substrate discipline rules”, axl-gfx is pure C and paradigm-agnostic — it draws pixels, not widgets. Hierarchy, layout, theming, and event dispatch live in the toolkit on top (AGT, or a future GTK-shape peer).

Overview

Not all UEFI systems have a display. Serial-only servers and headless BMCs typically lack GOP. Always check axl_gfx_available() before drawing:

#include <axl.h>

if (!axl_gfx_available()) {
    axl_printf("No display — running headless\n");
    return;
}

AxlGfxInfo info;
axl_gfx_get_info(&info);
axl_printf("Display: %ux%u\n", info.width, info.height);

axl_gfx_fill_rect(0, 0, info.width, info.height, AXL_GFX_BLACK);

const AxlFont *font = axl_gfx_default_font();
axl_gfx_draw_text(font, 20, 20, "Hello from AXL!", AXL_GFX_WHITE, 2);

Pixel Format

Pixels use BGRA layout (AxlGfxPixel): blue, green, red, alpha. The BGR ordering matches the native GOP pixel format on most hardware, avoiding conversion overhead. The 4th byte is alpha — 0xFF means fully opaque (default for solid colors), 0 means fully transparent. Alpha-blending is honored on buffer targets; screen-target draws treat all pixels as opaque (GOP has no blending hardware — render to a back-buffer first if you want semi-transparent overlays).

For ergonomic RGB-style construction without paying any per-pixel byte-swap cost, use the convenience macros:

AxlGfxPixel tomato      = AXL_GFX_RGB(0xFF, 0x63, 0x47);     // CSS #FF6347
AxlGfxPixel translucent = AXL_GFX_RGBA(0xFF, 0x00, 0x00, 0x80);  // 50% red
axl_gfx_fill_rect(10, 10, 100, 100, AXL_GFX_RED);             // named palette

These expand to BGRA compound literals at compile time — zero runtime cost. Named colors available: AXL_GFX_BLACK, AXL_GFX_WHITE, AXL_GFX_RED, AXL_GFX_GREEN, AXL_GFX_BLUE, AXL_GFX_YELLOW, AXL_GFX_CYAN, AXL_GFX_MAGENTA, AXL_GFX_GRAY, AXL_GFX_TRANSPARENT.

Alpha compositing

axl_gfx_blend(dst, src) returns the source-over composite of two pixels using 8-bit integer math:

out.rgb = (src.rgb * a + dst.rgb * (255 - a) + 127) / 255    where a = src.alpha
out.alpha = 0xFF                                              (destination treated as opaque)

The drawing primitives (fill_rect, draw_line, draw_text, etc.) apply this internally when both the target is a buffer and the source alpha is non-zero and non-opaque. Screen targets degrade to opaque draws — render to a back-buffer first for translucent overlays.

The constant-color source-over fill (a translucent fill_rect onto a buffer) is SIMD-accelerated via axl_cpu_simd_tier() — an AVX2, SSE2 (the x86-64 baseline, so every x86 build benefits), or NEON row kernel, bit-identical to the scalar formula above (the /255 uses the exact (Y + (Y>>8) + 1) >> 8 identity, valid because the numerator is ≤ 255²+127). The separable PDF blend modes below stay on the scalar path. See the AxlCpu <cpu.html>_ module for the dispatch machinery.

Gamma-correct (linear-light) compositing

By default compositing happens in gamma-encoded sRGB (free, matches Cairo/Blend2D). axl_gfx_set_gamma_correct(true) switches buffer-target alpha/coverage compositing to linear light — decode src+dst sRGB→linear, blend, re-encode — which is the physically correct way to mix light. It removes the “dark fringe” on anti-aliased text and path edges (thin text keeps its weight) and corrects the brightness of translucent overlays and fades. It’s a module-global flag (save/restore via axl_gfx_get_gamma_correct, off by default), costs two small lazily -built LUTs, and applies uniformly under every compositing primitive. The sRGB transfer functions are exposed as axl_gfx_srgb_to_linear / axl_gfx_linear_to_srgb for consumers doing their own color math. Gradient color ramps also honor the flag (interpolated in linear light for even, dark-dip-free transitions). The remaining sRGB-only paths are the separable PDF blend functions (multiply/screen/…).

Blend modes

axl_gfx_set_blend_mode(mode) selects how subsequent draws composite — the standard separable blend functions on top of source-over:

Mode

Effect

AXL_GFX_BLEND_OVER

normal source-over (default)

AXL_GFX_BLEND_MULTIPLY

darken: Cb·Cs/255

AXL_GFX_BLEND_SCREEN

lighten: 255−(255−Cb)(255−Cs)/255

AXL_GFX_BLEND_OVERLAY

contrast (multiply or screen per backdrop)

AXL_GFX_BLEND_DARKEN / _LIGHTEN

per-channel min / max

AXL_GFX_BLEND_ADD

additive, clamped to 255

It is module-global graphics state (like the draw target and clip stack); every compositing primitive on a buffer target honors it — fills, gradients, lines, text (bitmap + vector), and axl_gfx_blit_transformincluding opaque draws (an opaque source under MULTIPLY still multiplies). Save/restore with axl_gfx_get_blend_mode and reset to AXL_GFX_BLEND_OVER for normal drawing. The raw-copy axl_gfx_blit and screen (GOP) targets are exempt (no compositing). axl_gfx_blend_ex(dst, src, mode) exposes the pure per-pixel math.

axl_gfx_set_blend_mode(AXL_GFX_BLEND_MULTIPLY);
axl_gfx_fill_rect(0, 0, w, h, shadow);   // multiplies onto the backdrop
axl_gfx_set_blend_mode(AXL_GFX_BLEND_OVER);

Drawing Primitives

Primitive

Function

Notes

Fill

axl_gfx_fill_rect

Solid color, screen or buffer (uint32_t coords)

Fill (signed)

axl_gfx_fill_rect_i

Same, but int32_t coords — partly off-screen widgets without manual clamping

Rounded fill

axl_gfx_fill_rounded_rect

SDF-based corners with anti-aliasing; the headline widget primitive

Outline

axl_gfx_draw_rect

1-pixel-wide rectangle border

Line

axl_gfx_draw_line

Bresenham, signed origins, inclusive endpoints

Polyline

axl_gfx_draw_polyline

Connected segments through AxlGfxPoint[]

Path fill

axl_gfx_fill_path

4x4-supersampled even-odd rasterizer; see “Paths” below

Path stroke

axl_gfx_stroke_path / _ex

width-w anti-aliased stroke; caps butt/round/square, joins miter/round/bevel, dashes (AxlGfxStrokeStyle); offset geometry filled non-zero — see axl-gfx-stroke.c

Blit

axl_gfx_blit

Copy a pixel buffer onto the target

Blit sub-rect

axl_gfx_blit_rect

Copy a sub-rectangle of a wider source (src_stride, src_x/y) — e.g. one sprite-sheet cell — onto the target without copying it out first

Pattern fill

axl_gfx_fill_pattern

Tile an AxlGfxBuffer over a rect; AxlGfxRepeat BOTH/X/Y/NONE (CSS background-repeat), anchored at the origin, honors clip + blend + per-texel alpha

Color parse

axl_gfx_color_parse

CSS hex string → AxlGfxPixel (#RGB, #RGBA, #RRGGBB, #RRGGBBAA)

Capture

axl_gfx_capture

Read screen region into a buffer

Text

axl_gfx_draw_text

UTF-8, bitmap-font-driven, integer-scaled

Text (vector)

axl_ttf_draw

UTF-8, TTF/OTF, fractional px_size; see AxlTtf <truetype.html>_

Text (boxed)

axl_ttf_draw_box / axl_ttf_measure_box

word-wrapped multi-line text in a rect; \n hard breaks, AXL_TTF_ALIGN_* alignment, box clip; measure_box for layout-before-draw

Display list

axl_gfx_display_list_new / axl_gfx_dl_* / axl_gfx_display_list_replay

record draw ops into a retained, replayable command buffer; introspect via _count / _op_at; see AxlGfxDisplayList <display-list.html>_

Lines, outlines, signed-coord fills, and polylines accept signed coordinates so partly off-screen geometry is expressible directly without caller clamping. The clip stack (below) handles the actual pixel rejection.

Paths and Rounded Rects

AxlGfxPath is a retained-mode path object — sequence of subpaths built from move_to / line_to / curve_to / arc / close. Curves and arcs are flattened to line segments at insertion time so fill / stroke walk a uniform list. Fill uses 4x4 supersampling and the even-odd rule (subpath intersections invert — outer ring + inner ring makes a donut, matching SVG fill-rule:evenodd).

AxlGfxPath *p = axl_gfx_path_new();
axl_gfx_path_move_to(p, 10.0f, 10.0f);
axl_gfx_path_line_to(p, 50.0f, 10.0f);
axl_gfx_path_line_to(p, 30.0f, 40.0f);
axl_gfx_path_close(p);
axl_gfx_fill_path(p, AXL_GFX_RED);
axl_gfx_path_free(p);

axl_gfx_fill_rounded_rect is an immediate-mode helper — it rasterizes the 4 corners + 3 plain-fill bands directly via signed-distance coverage, bypassing the path API. Faster than building a per-call path for the case that dominates widget rendering (button + panel backgrounds).

axl_gfx_fill_rounded_rect(x, y, w, h, /* radius */ 6.0f, AXL_GFX_BLUE);

Stroke is fixed at 1 px wide in the current implementation — wider strokes await a future batch. Math primitives (sqrt, sin, cos, floor, ceil, fabs) come from the AxlMath <math.html>_ module so the path rasterizer stays libm-free.

Gradients

AxlGfxGradient is an opaque, reusable gradient object — linear (color axis from one point to another) or radial (offset by distance from a center). Add up to AXL_GFX_GRADIENT_MAX_STOPS color stops in any order; the offset t is normalized to [0, 1] and clamped, so the end stops extend flat beyond the axis / radius. Colors are interpolated per channel (including alpha) between adjacent stops.

AxlGfxGradient *g = axl_gfx_gradient_linear_new(0, 0, 0, 100);
axl_gfx_gradient_add_stop(g, 0.0f, AXL_GFX_RGB(0x4a, 0x90, 0xd9));
axl_gfx_gradient_add_stop(g, 1.0f, AXL_GFX_RGB(0x1c, 0x3f, 0x6b));
axl_gfx_fill_rect_gradient(10, 10, 200, 100, g);   /* vertical fade */
axl_gfx_gradient_free(g);

Geometry is in the active draw target’s coordinate space, and fills honor the clip stack + target + alpha blending like every other primitive. Sampling is per-pixel at the pixel center; interpolation is linear in stored (sRGB) bytes — gamma-correct interpolation is a future refinement. Path and rounded-rect gradient fills are planned follow-ups; the current entry point is axl_gfx_fill_rect_gradient.

Clipping

axl-gfx maintains a 16-deep clip stack. Pushing a clip rect intersects it with the current top of stack — child clips never expand outside their parent. All draw operations (fill, blit, line, text) respect the active clip; pixels outside it are not written.

axl_gfx_push_clip((AxlGfxClip){ .x = 100, .y = 50, .w = 400, .h = 300 });
axl_gfx_draw_text(font, 90, 60, "clipped at x=100", AXL_GFX_WHITE, 1);
axl_gfx_pop_clip();

Clip rectangles are interpreted in the active draw target’s coordinate system: screen pixels by default, buffer-local pixels after axl_gfx_target_buffer(buf). Push clips after setting the target you want them to apply to.

axl_gfx_reset_clip() empties the stack (useful for error recovery or app teardown); pair push/pop for normal widget-tree operation.

Non-axis-aligned (quad) clip

axl_gfx_push_clip_quad(const AxlGfxPointF q[4]) pushes a convex quadrilateral clip — the rotated/sheared counterpart to axl_gfx_push_clip. The four corners are in device/target space (any winding); the new region is intersected with the current top of stack, and axl_gfx_pop_clip pops it like any other. Every primitive honors it (fills, paths, text, blits). axl_gfx_get_clip still reports the axis-aligned bounding box. Use it to clip content inside a rotated panel.

Arbitrary-shape (path) clip

axl_gfx_push_clip_path(const AxlGfxPath *path) clips to the filled interior of any path — concave, self-intersecting, or multi-contour (holed) — the foundation for CSS clip-path. Unlike the convex quad clip, it rasterizes the path (even-odd) to an 8-bit coverage mask over its device bounding box; subsequent draws are confined to the shape ∩ the previous clip. Hard-edged (a pixel is kept at ≥ 50% coverage). The path is taken in its current device coordinates (CTM not re-applied, as with push_clip_quad). Pop frees the mask. Every primitive honors it; on a screen target it falls to a per-pixel path (compose into a back-buffer for speed).

Transform-Aware Rendering

For toolkits that own their own coordinate model (Qt/GTK-style), the AxlTransform (AxlMath, 3×3 double, row-major, cairo layout — x' = m[0]·x + m[1]·y + m[2]) is the currency for rotation / shear / non-uniform scale. Build one with the value helpers — no matrix code or trig at the call site:

AxlTransform m = axl_transform_multiply(
    axl_transform_rotate(angle),       // rotate about the origin first…
    axl_transform_translate(cx, cy));  // …then place at (cx, cy)

(axl_transform_identity / _translate / _scale / _rotate / _shear / _multiply / _invert; multiply(a, b) applies a first, then b — cairo cairo_matrix_multiply order. Apply one with axl_transform_map_point / _map_vector — combine _invert + _map_point to map a device point back to local space for hit-testing.)

Two primitives consume it; both compose on top of the active graphics transform (axl_gfx_translate et al.), so the effective map is CTM × m (just m under the default identity CTM):

What

Function

Notes

Vector text

axl_ttf_draw_transform(font, utf8, px_size, &m, color)

glyph outlines filled through m; anti-aliased at any angle. m = translate(x, y) reproduces axl_ttf_draw. Keep axl_ttf_draw for upright text (cached, faster).

Image

axl_gfx_blit_transform(src, &m)

m maps source pixels into the target; bilinear-sampled (smooth under rotation/scale), source alpha honored.

Clip region

axl_gfx_push_clip_rect_transformed(rect, &m)

clips to a rect mapped through m — the convex-quad clip without packing corners by hand.

Both drawing primitives are perspective-correct for a projective m (e.g. one from axl_transform_quad_to_quad): the blit inverse-maps each pixel through the full homography, and vector text flattens its glyph curves in local space before projecting (since a projective map sends lines to lines). An affine m takes an exact, cheaper fast path. Geometry is assumed to stay in front of the transform’s horizon (the same precondition as axl_transform_map_rect).

Both honor the clip stack (including push_clip_quad), draw target, and alpha — they are paradigm-agnostic: AXL never holds the matrix, the caller hands it in per call.

Double-Buffering

Off-screen AxlGfxBuffer objects let widgets composite into a back-buffer and then atomically present the result, eliminating tearing and partial-redraw flicker.

AxlGfxBuffer *buf = axl_gfx_buffer_new(800, 600);
axl_gfx_buffer_clear(buf, AXL_GFX_BLACK);

axl_gfx_target_buffer(buf);                      // draws now go to buf
    axl_gfx_fill_rect(10, 10, 100, 100, AXL_GFX_RED);
    axl_gfx_draw_text(font, 20, 20, "Hello", AXL_GFX_WHITE, 1);
axl_gfx_target_buffer(NULL);                     // back to screen

axl_gfx_buffer_present(buf, 0, 0);               // single blit to screen
axl_gfx_buffer_free(buf);

axl_gfx_buffer_present bypasses the clip stack and current target — it’s the unconditional “swap” step. While a buffer target is active, the clip stack and alpha compositing apply against the buffer’s pixel array using buffer-local coordinates.

Present pipeline (direct framebuffer + dirty rectangles)

axl_gfx_buffer_present writes the back-buffer straight to the GOP linear framebuffer (FrameBufferBase) when one is available, honoring the panel’s PixelFormat and scan-line stride. Because AxlGfxPixel is stored BGRA — matching the common GOP “BGR” format — each row is a single memcpy; an “RGB” panel gets a per-pixel red/blue swap. The present path is write-only: it never reads VRAM (uncached MMIO reads are catastrophically slow), so all compositing must stay in the RAM buffer. Panels with no linear framebuffer (PixelBitMask, PixelBltOnly, or a zero FrameBufferBase) transparently fall back to GOP Blt. The pure conversion is exposed as axl_gfx_pack_pixel(px, order) for callers writing their own framebuffer code.

For incremental redraws — a moving cursor, one updated widget — present only what changed instead of the whole buffer:

// Push just a sub-region.
axl_gfx_buffer_present_rect(buf, dst_x, dst_y, src_x, src_y, w, h);

// Or accumulate damage as you draw, then flush the union in one call.
axl_gfx_buffer_clear_damage(buf);
axl_gfx_target_buffer(buf);
    axl_gfx_fill_rect(40, 40, 16, 16, AXL_GFX_RED);
axl_gfx_target_buffer(NULL);
axl_gfx_buffer_add_damage(buf, (AxlGfxClip){40, 40, 16, 16});
axl_gfx_buffer_present_damage(buf, screen_x, screen_y);   // flushes + clears

axl_gfx_buffer_add_damage unions each dirty rect (clamped to the buffer) into a per-buffer bounding box; axl_gfx_buffer_present_damage flushes exactly that box to the screen and clears it for the next frame. axl_gfx_buffer_get_damage reports the current box (empty when clean). Presenting only the dirty region cuts present bandwidth by 10–100× for typical UI updates.

Cursor Overlay

GOP exposes no hardware-cursor API (the GPU usually has a cursor plane, but UEFI surfaces only a framebuffer + Blt), so a sprite that tracks the pointer must be composited in software. <axl/axl-cursor.h> (AxlCursor) owns the one cursor on the screen and its position. It binds to the back-buffer “scene” being scanned out and touches only the cursor region.

The cursor is composited into the scene as its top layer, never presented as a separate GOP operation — the same approach real software-cursor compositors use (wlroots’ wlr_output_render_software_cursors composites the cursor as the last layer before one commit; Qt’s direct-framebuffer QFbCursor composites over the repainted scene and flushes the region once). A move folds the sprite in, presents the old∪new region atomically (one present, so a small move never shows a cursor-less gap), then unfolds to keep the scene byte-clean. This avoids the flicker you get when the cursor is erased then redrawn as two separate GOP writes — at low present rates (e.g. a throttled 10 Hz pointer poll) that gap is visible.

AxlGfxBuffer *scene = axl_gfx_buffer_new(w, h);   // your back-buffer
AxlCursor *cur = axl_cursor_new(scene);           // built-in arrow, hidden
axl_cursor_attach(cur, loop, on_input, &app);     // tracks the pointer
// when you re-present a new frame, bracket it so the cursor folds INTO the
// flush — scene+cursor reach the screen in one present, atomically:
axl_cursor_lift(cur);                             // fold sprite into scene
axl_gfx_buffer_present(scene, 0, 0);              // one present carries both
axl_cursor_drop(cur);                             // unfold (scene clean again)

axl_cursor_set_image swaps the sprite (a NULL sprite restores the built-in arrow); axl_cursor_move / axl_cursor_move_rel reposition the hotspot — drive a relative pointer (EFI_SIMPLE_POINTER) with _move_rel so the cursor clamps at a screen edge yet recovers the instant motion reverses. A NULL scene selects direct-to-screen “save-under” mode for consumers without a back-buffer. The cursor never routes input — hit-testing stays with the consumer, or with the compositor’s seat below.

Compositor — Surfaces, Seat, and Cursor

<axl/axl-compositor.h> (AxlCompositor) is a local, in-process compositor: per-surface back-buffers, a z-ordered surface tree, multi-surface present on the single framebuffer, and a seat (pointer routing, grabs, per-surface keyboard focus, the cursor overlay). It lets a toolkit stop faking window stacking with “reparent a child so it paints last” and gives a reusable windowing primitive for any app that wants stacked surfaces + input without a full widget set. The full design — why it is deliberately Wayland-shaped — is in the AXL-Compositor-Design doc.

A surface is a CPU-backed rectangle the client draws into, with a position, visibility, opacity, an optional input region, and a place in a scene-graph tree: each surface has an optional parent, a child’s position is relative to its parent, and stacking is tree pre-order (a node, then its children on top). Raising / moving / hiding / destroying a surface acts on its whole subtree.

AxlCompositor *c = axl_compositor_new(1280, 800);
AxlSurface *win = axl_surface_create(axl_compositor_root(c), 400, 300);
axl_surface_move(win, 100, 80);
axl_gfx_target_buffer(axl_surface_buffer(win));    // draw into the surface
axl_gfx_fill_rect(0, 0, 400, 300, AXL_GFX_RGB(0x20, 0x40, 0x80));
axl_gfx_target_buffer(NULL);
axl_surface_damage(win, (AxlGfxClip){0, 0, 400, 300});
axl_compositor_present(c);                          // composite + flush damage

axl_compositor_present composites every visible surface into one output buffer and flushes only the damaged region to the screen — it is the single atomicity barrier (one synchronous client, so there is no per-surface double-buffering to manage), and it keeps the cursor on top.

The seat routes raw axl-input events. A surface installs an AxlSurfaceListener — the Wayland-wl_pointer_listener-shaped struct of surface-local callbacks (enter / leave / motion / button / axis / key / focus_in / focus_out) — via axl_surface_set_listener; this is the one seam a C++ toolkit bridges to virtual methods. axl_compositor_pointer_event hit-tests topmost-first (honoring the optional per-surface input region) and delivers to the surface under the pointer. axl_compositor_pointer_grab confines the pointer to a surface subtree — the menu / popup mechanism, dismissed by a press outside it — and axl_compositor_set_keyboard_focus + axl_compositor_key_event route keys to the focused surface. The compositor drives an AxlCursor as the topmost overlay, with per-surface cursor shapes via axl_compositor_set_cursor_image (a surface requests its shape from its enter callback). Wire real devices with axl_compositor_attach_pointer / axl_compositor_attach_keyboard on a caller-owned event loop.

Effects: Blur

axl_gfx_buffer_blur(buf, radius) softens an off-screen buffer in place with a separable triangular-kernel blur (the standard single-pass Gaussian approximation), clamp-to-edge so borders don’t darken. All four channels are blurred, including alpha, so it works on an alpha/shadow mask as well as on color content. radius 0 is a no-op; the kernel is normalized (total intensity is preserved).

The per-axis convolution is SIMD-accelerated via runtime dispatch on axl_cpu_simd_tier() — an AVX2 (256-bit, x86), SSE4.1 (x86), or NEON (AArch64) kernel, falling back to scalar on older CPUs. Every vectorized path is bit-identical to the scalar reference (only the per-tap multiply-accumulate is vectorized; the edge clamp and the final rounding divide stay scalar), so output never depends on which CPU ran it. On x86 the AVX2 path needs YMM state, which the dispatcher enables via axl_cpu_enable_avx() — see the AxlCpu <cpu.html>_ module for the detection/enable details.

AxlGfxBuffer *b = axl_gfx_buffer_new(w, h);
axl_gfx_target_buffer(b);
    /* ... render a shape ... */
axl_gfx_target_buffer(NULL);
axl_gfx_buffer_blur(b, 8);            /* CSS filter: blur(8px) */
axl_gfx_buffer_present(b, 0, 0);
axl_gfx_buffer_free(b);

axl_gfx_draw_shadow(src, x, y, color, radius) builds a soft drop shadow on top of the blur: the shape comes from src’s alpha channel (so it works for boxes, rounded rects, or anti-aliased text), tinted with color, blurred by radius, and composited into the active target at (x, y) — pass x + offset_x, y + offset_y to offset the shadow. Draw the real content on top afterward.

// `card` is a buffer with the widget rendered into it (alpha = shape)
axl_gfx_draw_shadow(card, card_x + 2, card_y + 4,
                    AXL_GFX_RGBA(0, 0, 0, 0x60), 8);  // soft offset shadow
axl_gfx_buffer_present(card, card_x, card_y);          // content on top

See the AxlGfx effects <gfx.html>_ API reference.

Text Rendering

Text APIs take a const AxlFont * and UTF-8 input. Invalid UTF-8 sequences become U+FFFD REPLACEMENT CHARACTER; codepoints missing from the font render the font’s fallback glyph (when one is defined), or skip while still advancing the pen.

const AxlFont *font = axl_gfx_default_font();
uint32_t w = axl_gfx_measure_text(font, "Hello, 世界!", 1);
axl_gfx_draw_text(font, 20, 20, "Hello, 世界!", AXL_GFX_WHITE, 1);

axl_gfx_measure_text does not require GOP — useful for layout calculations on headless systems.

Built-in fonts

axl-gfx ships two bitmap fonts. Both are codepoint-sorted with fallback_codepoint = '?' so missing glyphs render visibly.

Font

Coverage

License

Notes

axl_font_edk2_laffstd

ASCII (95 glyphs)

BSD-2-Clause-Patent

EDK2 8x16 narrow font. Returned by axl_gfx_default_font().

axl_font_unifont_16

391-glyph BMP subset

SIL OFL 1.1

GNU Unifont 16.0.04. 8-wide ASCII + 16-wide CJK / box-drawing / symbols.

Linker garbage collection drops the fonts that aren’t referenced, so consumers pay only for the fonts they actually draw with.

For vector text, axl_ttf_default() returns a shared, built-in DejaVu Sans subset (ASCII + Latin-1 + common typographic punctuation) so consumers don’t have to bundle a TTF asset — see the AxlTtf <truetype.html>_ page. It is likewise gc-dropped when unreferenced.

AxlTtf *f = axl_ttf_default();             /* shared; do not free */
axl_ttf_draw(f, 20, 40, "Café — 21°C", 16.0f, AXL_GFX_WHITE);

axl_ttf_draw rasterizes glyphs with horizontal subpixel positioning (quarter-pixel) and caches the results per font, keyed on (codepoint, px_size, subpixel offset) with LRU eviction. The cache is entirely internal — no API change — so repeated text redraws (the common UI case) skip the rasterizer after the first frame. Glyphs at whole-pixel pen positions render identically to the un-cached path.

For paragraphs, axl_ttf_draw_box wraps UTF-8 into a rectangle: greedy whitespace word-wrap, hard \n breaks, baselines advancing by the axl_ttf_metrics line-height, and horizontal alignment via AXL_TTF_ALIGN_{LEFT,CENTER,RIGHT}. A word wider than the box takes its own line and overflows (clipped to the box, which is pushed onto the clip stack for the draw). axl_ttf_measure_box runs the same layout without drawing — reporting line count plus the block width and height — so a widget can size itself before painting.

axl_ttf_draw_box(f, 20, 40, 200, 120, paragraph, 16.0f,
                 AXL_GFX_WHITE, AXL_TTF_ALIGN_LEFT);

Adding a font

Bitmap fonts are generated from BDF (Glyph Bitmap Distribution Format) sources via scripts/gen-bdf-font.py. The script reads a BDF file (plus optional codepoint subset list) and emits a C source file defining a single AxlFont with its AxlGlyph[] table — drop the output into src/gfx/fonts/ and add a declaration to a header your consumer includes.

AxlFont itself is plain .rodata: codepoint-sorted glyph array, font-wide metrics (cell width/height, ascent, descent, line height), and a flags bitmask (AXL_FONT_MONOSPACE / AXL_FONT_VARIABLE). Lookups use binary search; see axl-font.h’s design note for why binary search beats hash/radix at the sizes typical for bitmap fonts.

Toolkits typically wrap fonts in a named-lookup / fallback-chain layer of their own. Substrate discipline keeps that out of axl-gfx — the substrate provides glyph rasterization and metrics, not text layout.

API Reference

The <axl/axl-gfx.h> umbrella pulls in all of the sub-headers below; include it for the whole 2D library, or include a sub-header directly for just that slice.

Types — <axl/axl-gfx-types.h>

AxlGfx shared vocabulary: framebuffer info, the BGRA pixel type, source-over blend, and the RGB(A) literal macros + named color palette. Every other AxlGfx sub-header builds on these types. Pulled in transitively via the <axl/axl-gfx.h> umbrella.

Defines

AXL_GFX_RGB(r, g, b)
AXL_GFX_RGBA(r, g, b, a)
AXL_GFX_BLACK
AXL_GFX_WHITE
AXL_GFX_RED
AXL_GFX_GREEN
AXL_GFX_BLUE
AXL_GFX_YELLOW
AXL_GFX_CYAN
AXL_GFX_MAGENTA
AXL_GFX_GRAY
AXL_GFX_TRANSPARENT

Enums

enum AxlGfxBlendMode

Separable blend modes (Porter-Duff “over” plus the common PDF / Canvas / SVG separable blend functions). Select one with axl_gfx_set_blend_mode; it affects subsequent drawing on buffer targets (GOP screen targets cannot composite, same caveat as translucent alpha). The blend function B(Cb, Cs) is applied per channel to backdrop Cb and source Cs, then composited over the (opaque) backdrop by the source alpha: out = ((255 - a)*Cb + a*B(Cb, Cs) + 127) / 255.

Values:

enumerator AXL_GFX_BLEND_OVER

normal source-over (default); B = Cs

enumerator AXL_GFX_BLEND_MULTIPLY

B = Cb*Cs/255 (darkens)

enumerator AXL_GFX_BLEND_SCREEN

B = 255 - (255-Cb)(255-Cs)/255 (lightens)

enumerator AXL_GFX_BLEND_OVERLAY

multiply or screen per backdrop (contrast)

enumerator AXL_GFX_BLEND_DARKEN

B = min(Cb, Cs)

enumerator AXL_GFX_BLEND_LIGHTEN

B = max(Cb, Cs)

enumerator AXL_GFX_BLEND_ADD

B = min(255, Cb + Cs) (additive / “plus”)

enum AxlGfxPixelOrder

Framebuffer pixel byte order for the two direct-write GOP formats.

A backend-neutral spelling of the two 8-bit-per-channel GOP pixel formats so the present path can pack AxlGfxPixels for a direct framebuffer write without leaking UEFI’s EFI_GRAPHICS_PIXEL_FORMAT into the public API. The bitmask (PixelBitMask) and Blt-only (PixelBltOnly) GOP modes have no fixed byte order and are not representable here — the present path falls back to GOP Blt for those.

Values:

enumerator AXL_GFX_PIXEL_BGRA

blue, green, red, reserved (GOP “BGR”)

enumerator AXL_GFX_PIXEL_RGBA

red, green, blue, reserved (GOP “RGB”)

enum AxlGfxPixelFormat

The display’s actual GOP framebuffer pixel format, including the two formats AxlGfxPixelOrder can’t represent.

Reported by axl_gfx_get_pixel_format. AxlGfx normalizes everything to BGRA AxlGfxPixels internally, so consumers don’t need this to draw — it is for code that reasons about the raw framebuffer layout (screenshot export, direct-framebuffer writers, fixture capture).

Values:

enumerator AXL_GFX_PIXEL_FORMAT_RGBX8

8-bit R,G,B + reserved (GOP PixelRedGreenBlueReserved8BitPerColor)

enumerator AXL_GFX_PIXEL_FORMAT_BGRX8

8-bit B,G,R + reserved (GOP PixelBlueGreenRedReserved8BitPerColor)

enumerator AXL_GFX_PIXEL_FORMAT_BITMASK

arbitrary channel masks — read them via axl_gfx_get_pixel_bitmask

enumerator AXL_GFX_PIXEL_FORMAT_BLT_ONLY

no CPU-addressable framebuffer (GOP-Blt-only display)

Functions

AxlGfxPixel axl_gfx_blend(AxlGfxPixel dst, AxlGfxPixel src)

Source-over alpha composite: blend a source pixel over a destination. Result alpha is always 0xFF (destination is treated as opaque). Math (8-bit integer): out.rgb = (src.rgb * a + dst.rgb * (255 - a) + 127) / 255 where a = src.alpha.

Parameters:
  • dst – destination pixel (existing)

  • src – source pixel (with alpha)

AxlGfxPixel axl_gfx_blend_ex(AxlGfxPixel dst, AxlGfxPixel src, AxlGfxBlendMode mode)

Composite src over dst using an explicit blend mode.

The mode-parameterized form of axl_gfx_blend (which is exactly axl_gfx_blend_ex(dst, src, AXL_GFX_BLEND_OVER)). Pure: depends only on its arguments, not the active blend-mode state. Result alpha is always 0xFF (the backdrop is treated as opaque).

Parameters:
  • dst – destination / backdrop pixel

  • src – source pixel (its alpha drives compositing)

  • mode – blend function to apply

AxlGfxPixel axl_gfx_composite(AxlGfxPixel dst, AxlGfxPixel src)

Source-over composite src over dst, honoring the active axl_gfx_set_gamma_correct setting.

Unlike axl_gfx_blend (a pure sRGB helper), this runs the over- composite in linear light when gamma-correct mode is on — the same path the drawing primitives use, so off-screen compositing (e.g. a compositor blending surfaces, or a translucent overlay) matches what the gamma-aware fills/blits produce. Always source-over; result alpha is 0xFF (the backdrop is treated as opaque).

Parameters:
  • dst – destination / backdrop pixel

  • src – source pixel (its alpha drives compositing)

int axl_gfx_color_parse(const char *str, AxlGfxPixel *out)

Parse a CSS-style hex color string into an AxlGfxPixel.

The inverse of the #RRGGBBAA form emitted by axl_gfx_display_list_dump, and the parser a consumer needs to load colors from a JSON5 theme. Accepts the four CSS hex forms, each preceded by a required #:

  • #RGB — 3 nibbles; each is doubled (#f80#ff8800), alpha defaults to opaque (0xFF).

  • #RGBA — 4 nibbles, each doubled, including alpha.

  • #RRGGBB — 6 hex digits, alpha defaults to opaque.

  • #RRGGBBAA — 8 hex digits, alpha explicit.

Hex digits are case-insensitive (#FF6347 == #ff6347). No surrounding whitespace is tolerated — pass the bare token (a JSON5 string value already arrives trimmed). Named colors (e.g. “tomato”) are intentionally NOT recognized; use the AXL_GFX_* palette macros for those.

On failure out is left unmodified.

Parameters:
  • str – [in] e.g. “#RRGGBB”, “#RGB”, “#RRGGBBAA”

  • out – [out] parsed color (untouched on error)

Returns:

AXL_OK on success. AXL_ERR if str or out is NULL, str lacks a leading #, its length is not 3/4/6/8 nibbles, or it contains a non-hex digit.

float axl_gfx_srgb_to_linear(uint8_t srgb)

Decode one 8-bit sRGB channel value to linear light in [0, 1].

The standard sRGB EOTF: c/12.92 below the 0.04045 knee, else ((c+0.055)/1.055)^2.4. Light mixing (alpha blending, anti-alias coverage, filtering) is physically linear, but pixels are stored gamma-encoded — converting to linear first is what axl_gfx_set_gamma_correct does internally. Exposed so consumers doing their own color math (or gamma-correct gradient ramps) can use the exact same transfer function.

Parameters:
  • srgb – 8-bit sRGB-encoded channel value

Returns:

linear-light value in [0.0, 1.0].

uint8_t axl_gfx_linear_to_srgb(float linear)

Encode a linear-light value in [0, 1] back to an 8-bit sRGB channel.

Inverse of axl_gfx_srgb_to_linear (the sRGB OETF: l*12.92 below the 0.0031308 knee, else 1.055*l^(1/2.4) - 0.055), rounded to the nearest 8-bit code. Inputs are clamped to [0, 1].

Parameters:
  • linear – linear-light value (clamped to [0, 1])

Returns:

8-bit sRGB-encoded channel value [0, 255].

uint32_t axl_gfx_pack_pixel(AxlGfxPixel px, AxlGfxPixelOrder order)

Pack an AxlGfxPixel into a 32-bit framebuffer word for order.

The pure pixel-format conversion at the heart of the direct framebuffer present path (Phase G17). AxlGfxPixel is stored BGRA (blue in the low byte), matching the GOP “BGR” format exactly, so:

  • AXL_GFX_PIXEL_BGRA is the identity (the bytes are already in framebuffer order — present is a straight row copy).

  • AXL_GFX_PIXEL_RGBA swaps the red and blue bytes.

The returned word is in the host’s native byte order: byte 0 (the low 8 bits on a little-endian framebuffer) holds the channel that order names first. The alpha/reserved byte is carried through unchanged in the high byte; firmware ignores it.

Pure: depends only on its arguments. Exposed so consumers writing their own framebuffer code can reuse the exact conversion the library uses.

Parameters:
  • px – [in] source pixel (BGRA storage)

  • order – [in] target framebuffer byte order

Returns:

the packed 32-bit framebuffer word.

struct AxlGfxInfo
#include <axl-gfx-types.h>

Framebuffer information (standard C types, no UEFI types).

Public Members

uint32_t width

horizontal resolution in pixels

uint32_t height

vertical resolution in pixels

uint32_t stride

pixels per scan line (>= width)

uint64_t framebuffer

physical address (0 if BltOnly mode)

struct AxlGfxMode
#include <axl-gfx-types.h>

One enumerable GOP display mode (see axl_gfx_query_mode). Geometry only — the pixel format is normalized by the present path, not the caller’s concern when picking a resolution.

Public Members

uint32_t index

mode number — pass to axl_gfx_set_mode

uint32_t width

horizontal resolution in pixels

uint32_t height

vertical resolution in pixels

uint32_t stride

pixels per scan line (>= width)

struct AxlGfxPointF
#include <axl-gfx-types.h>

2D point at floating-point (sub-pixel) precision.

The vocabulary type for geometry the caller supplies pre-transformed into device/target space — e.g. the four corners handed to axl_gfx_push_clip_quad. Distinct from the integer AxlGfxPoint (polyline vertices); use this where sub-pixel placement matters.

Public Members

float x
float y
struct AxlGfxPixel
#include <axl-gfx-types.h>

Pixel color in BGRA layout (matches GOP native BGRX pixel format for the BGR bytes; the 4th byte is alpha for blending operations).

alpha = 0xFF means fully opaque; alpha = 0 means fully transparent. When passed to drawing primitives:

  • alpha == 0xFF: fast opaque draw (existing behavior; for screen targets uses GOP Blt directly).

  • alpha == 0: no-op (fully transparent).

  • 0 < alpha < 0xFF: source-over blend against the destination’s existing pixels. Only supported on buffer targets (alpha-aware drawing on screen targets falls back to opaque, since GOP has no blending hardware — render to a back-buffer first if you want semi-transparent overlays on screen).

Public Members

uint8_t blue
uint8_t green
uint8_t red
uint8_t alpha

0xFF = opaque, 0 = transparent

struct AxlGfxPixelBitmask
#include <axl-gfx-types.h>

Per-channel bit masks for an AXL_GFX_PIXEL_FORMAT_BITMASK display (the GOP PixelBitMask case). Each mask selects that channel’s bits within the 32-bit pixel.

Public Members

uint32_t red_mask

bits carrying the red channel

uint32_t green_mask

bits carrying the green channel

uint32_t blue_mask

bits carrying the blue channel

uint32_t reserved_mask

reserved / unused bits

struct AxlGfxOutput
#include <axl-gfx-types.h>

One physical display, as enumerated by axl_gfx_output_count / axl_gfx_output_get. Where the single-display accessors (axl_gfx_get_info, axl_gfx_get_pixel_format, axl_gfx_get_edid) report the active GOP, this describes each GOP individually — what a multi-monitor compositor needs to lay out and identify outputs.

Public Members

uint32_t width

horizontal resolution in pixels

uint32_t height

vertical resolution in pixels

uint32_t stride

pixels per scan line (>= width)

uint64_t framebuffer

framebuffer physical address (0 if Blt-only)

uint64_t framebuffer_size

framebuffer byte size (GOP Mode->FrameBufferSize; firmware-reported, may exceed stride*height*bpp; 0 if Blt-only)

AxlGfxPixelFormat pixel_format

this output’s raw GOP pixel format

uint32_t mode_count

number of modes this output enumerates

uint32_t current_mode

this output’s active mode index

const uint8_t *edid

borrowed EDID bytes, or NULL if none published

size_t edid_len

EDID byte count (0 when edid is NULL)

struct AxlGfxOutputMode
#include <axl-gfx-types.h>

One mode of a specific display output, as enumerated by axl_gfx_output_query_mode. The inventory-side peer of AxlGfxMode: where AxlGfxMode is geometry-only (the pixel format is the present path’s concern when you are picking a resolution to draw at), this carries the mode’s actual GOP pixel format — what a GOP-inventory reader reports per mode. Read from the output’s own GOP, so it is faithful even for an output that is not the active one.

Public Members

uint32_t index

mode number (matches the queried index)

uint32_t width

horizontal resolution in pixels

uint32_t height

vertical resolution in pixels

uint32_t stride

pixels per scan line (>= width)

AxlGfxPixelFormat pixel_format

this mode’s raw GOP pixel format

Surface — <axl/axl-gfx-surface.h>

AxlGfx drawing surface + module-global state: GOP availability, off-screen buffers (double-buffering), the clip stack, and the affine transform stack (Phase G4). Pulled in via the <axl/axl-gfx.h> umbrella.

Defines

AXL_GFX_CLIP_STACK_MAX

Maximum nested clip-stack depth. Deep enough for any realistic widget tree; small enough to live in static module state.

AXL_GFX_TRANSFORM_STACK_MAX

Maximum nested transform-stack depth (parallel to the clip stack).

Typedefs

typedef struct AxlGfxBuffer AxlGfxBuffer

Opaque off-screen pixel buffer. Holds a w*h array of AxlGfxPixel in row-major order plus dimensions. Used as a render target for flicker-free double-buffered rendering: widgets composite into the buffer, then axl_gfx_buffer_present blits the full buffer to the screen in one operation.

typedef struct AxlGfxPath AxlGfxPath

fwd-decl (full def in axl-gfx-path.h)

Enums

enum AxlGfxRepeat

How an axl_gfx_fill_pattern source tiles across the destination rectangle. Mirrors CSS background-repeat.

Values:

enumerator AXL_GFX_REPEAT_BOTH

tile on both axes (CSS repeat)

enumerator AXL_GFX_REPEAT_X

tile horizontally only (CSS repeat-x)

enumerator AXL_GFX_REPEAT_Y

tile vertically only (CSS repeat-y)

enumerator AXL_GFX_REPEAT_NONE

a single copy at the origin (CSS no-repeat)

Functions

bool axl_gfx_available(void)

Check whether a graphics display is available.

Returns:

true if GOP was found, false on headless/serial systems.

int axl_gfx_get_info(AxlGfxInfo *info)

Get framebuffer information.

Parameters:
  • info – [out] receives display info

Returns:

AXL_OK on success, AXL_ERR if GOP not available.

int axl_gfx_get_pixel_format(AxlGfxPixelFormat *out)

Get the display’s actual GOP framebuffer pixel format.

AxlGfx normalizes pixels to BGRA internally; this exposes the raw format for consumers that reason about the framebuffer layout (screenshot export, direct writers, fixture capture). For AXL_GFX_PIXEL_FORMAT_BITMASK the channel masks come from axl_gfx_get_pixel_bitmask.

Parameters:
  • out – [out] receives the pixel format

Returns:

AXL_OK on success, AXL_ERR if out is NULL or GOP is not available.

int axl_gfx_get_pixel_bitmask(AxlGfxPixelBitmask *out)

Get the per-channel bit masks for a bitmask-format display.

Only meaningful when axl_gfx_get_pixel_format reports AXL_GFX_PIXEL_FORMAT_BITMASK; any other format returns AXL_ERR (the masks are implied by RGBX8/BGRX8 and undefined for Blt-only).

Parameters:
  • out – [out] receives the channel masks

Returns:

AXL_OK on success, AXL_ERR if out is NULL, GOP is not available, or the format is not bitmask.

int axl_gfx_get_edid(const uint8_t **bytes, size_t *len)

Get the active display’s raw EDID bytes, if the firmware published an EFI_EDID_DISCOVERED_PROTOCOL.

Returns a borrowed pointer into firmware-owned memory valid for the life of the boot — the caller must NOT free it, and should copy if it needs to retain the bytes past driver teardown. Decode with axl_edid_parse (<axl/axl-edid.h>). Many displays / virtual GPUs (QEMU’s std VGA) never publish EDID, so AXL_ERR is common and not an error condition.

Parameters:
  • bytes – [out] borrowed pointer to EDID bytes

  • len – [out] number of EDID bytes

Returns:

AXL_OK with bytes / len set, or AXL_ERR if either out parameter is NULL or no EDID is available.

size_t axl_gfx_output_count(void)

Number of physical display outputs (GOP handles) on the system.

The single-display accessors (axl_gfx_get_info etc.) report one active GOP; this counts every GOP the firmware published — what a multi-monitor consumer enumerates.

Returns:

the output count, or 0 if there is no GOP at all.

int axl_gfx_output_get(size_t index, AxlGfxOutput *out)

Describe display output index into out.

Fills geometry, pixel format, framebuffer base and size, mode count / current mode, and (if the panel published one) a borrowed pointer to its EDID bytes — the same firmware-owned, do-not-free, decode-with- axl_edid_parse contract as axl_gfx_get_edid. Outputs are indexed [0, axl_gfx_output_count()) in firmware handle order, stable within a boot.

Parameters:
Returns:

AXL_OK with out populated, or AXL_ERR if out is NULL, index is out of range, the output’s GOP could not be read, or its pixel format is unrecognized (out is untouched on error).

int axl_gfx_output_query_mode(size_t output_index, uint32_t mode_index, AxlGfxOutputMode *out)

Query the geometry and pixel format of mode mode_index of output output_index, without switching to it.

The per-output inventory peer of axl_gfx_query_mode, which can only read the active GOP. Reads the named output’s own GOP, so a multi-monitor consumer can enumerate the mode list of an output that is not the active one (a laptop panel and an external monitor enumerate different mode sets), and each mode carries its own pixel_format rather than the output’s.

A conformant GOP only ever reports one of the four mapped pixel formats, so the “unrecognized format” failure is malformed-firmware only; an inventory walk over [0, mode_count) may treat an AXL_ERR on an in-range mode as “skip this mode and continue.”

Parameters:
  • output_index – output index in [0, axl_gfx_output_count())

  • mode_index – mode number in [0, that output’s mode_count)

  • out – [out] receives the mode description

Returns:

AXL_OK with out populated, or AXL_ERR if out is NULL, output_index is out of range, mode_index is >= that output’s mode_count, the GOP could not be read, QueryMode failed, or the mode’s pixel format is unrecognized.

int axl_gfx_output_get_pixel_bitmask(size_t output_index, AxlGfxPixelBitmask *out)

Get the per-channel bit masks for output output_index, when that output’s active mode is a PixelBitMask display.

The per-output peer of axl_gfx_get_pixel_bitmask (which reads only the active GOP). Only meaningful when the output’s pixel_format is AXL_GFX_PIXEL_FORMAT_BITMASK; any other format returns AXL_ERR (the masks are implied by RGBX8/BGRX8 and undefined for Blt-only).

Parameters:
Returns:

AXL_OK with out populated, or AXL_ERR if out is NULL, output_index is out of range, the GOP could not be read, or the output’s format is not bitmask.

uint32_t axl_gfx_mode_count(void)

Number of display modes the GOP enumerates.

Returns:

mode count, or 0 if headless / no GOP.

int axl_gfx_query_mode(uint32_t index, AxlGfxMode *out)

Query the geometry of mode index without switching to it.

Parameters:
Returns:

AXL_OK on success, AXL_ERR if no GOP, index out of range (>= axl_gfx_mode_count()), out is NULL, or QueryMode failed.

int axl_gfx_current_mode(uint32_t *out_index)

The currently-active mode index (its geometry matches axl_gfx_get_info()).

Parameters:
  • out_index – [out] receives the active mode number

Returns:

AXL_OK + *out_index set, or AXL_ERR if no GOP / out_index NULL.

int axl_gfx_find_mode(uint32_t width, uint32_t height, uint32_t *out_index)

Find the first enumerated mode matching width x height.

Parameters:
  • width – desired horizontal resolution

  • height – desired vertical resolution

  • out_index – [out] receives the matching mode number

Returns:

AXL_OK + *out_index set, or AXL_ERR if no GOP, no matching mode, or out_index is NULL.

int axl_gfx_max_mode(AxlGfxMode *out)

Find the largest enumerated mode — greatest pixel area, ties broken by the wider mode. The “use the full screen” pick (its index feeds axl_gfx_set_mode).

Parameters:
  • out – [out] receives the largest mode

Returns:

AXL_OK + *out, or AXL_ERR if no GOP, no modes, or out NULL.

int axl_gfx_set_mode(uint32_t index)

Switch the display to mode index.

The framebuffer is reallocated — axl_gfx_get_info() reflects the new geometry afterward and any cached FrameBufferBase is stale — and the screen is cleared, so the caller MUST repaint. Boot-services only (the GOP is gone after ExitBootServices).

Parameters:
Returns:

AXL_OK on success, AXL_ERR if no GOP, index out of range, or SetMode failed.

int axl_gfx_set_native_mode(void)

Switch the display to the panel’s native resolution.

Reads the display’s EDID (axl_gfx_get_edid), decodes its preferred timing (Detailed Timing Descriptor #1 — the panel’s native mode), finds the matching enumerated GOP mode, and switches to it. This is the correct “use the real panel resolution” pick — unlike axl_gfx_max_mode, which just takes the largest enumerated mode and can land on a scaled/letterboxed non-native resolution.

On success the framebuffer is reallocated and the screen cleared, so the caller MUST repaint (same contract as axl_gfx_set_mode). Boot-services only. Fails cleanly without switching when EDID is absent, so it is safe to attempt and fall back to axl_gfx_max_mode.

Returns:

AXL_OK if the display was switched to its native mode; AXL_ERR if there is no GOP, no EDID, the EDID carries no native timing, no enumerated mode matches it, or SetMode failed (the current mode is unchanged in every failure case — the checks precede the switch).

int axl_gfx_get_dpi(uint32_t *dpi_x, uint32_t *dpi_y)

Get the display’s physical DPI from its EDID.

Reads EDID (axl_gfx_get_edid), decodes it, and derives dots-per-inch per axis from the native resolution and physical image size (axl_edid_dpi). Either out parameter may be NULL.

Parameters:
  • dpi_x – [out, optional] horizontal DPI

  • dpi_y – [out, optional] vertical DPI

Returns:

AXL_OK with the requested axes set; AXL_ERR if there is no GOP, no EDID, or the EDID lacks a usable resolution / image size (the out parameters are untouched on error).

int axl_gfx_scale_for_dpi(uint32_t dpi)

Map a DPI to a recommended integer UI scale factor.

A pure threshold function: < 144 → 1 (standard), 144..239 → 2 (HiDPI), >= 240 → 3 (ultra-HiDPI). 144 is 1.5x the ~96 dpi baseline, the conventional point where integer 2x scaling wins.

Parameters:
  • dpi – dots per inch

Returns:

the scale factor 1, 2, or 3.

Recommend an integer UI scale factor for the active display.

Combines axl_gfx_get_dpi (taking the smaller axis, to avoid over-scaling) with axl_gfx_scale_for_dpi. When DPI can’t be determined (no EDID — common in VMs) it returns 1: a sensible “no scaling” default rather than an error, so callers can use the result unconditionally.

Returns:

the recommended scale factor (>= 1); 1 when DPI is unknown.

AxlGfxBuffer *axl_gfx_buffer_new(uint32_t w, uint32_t h)

Allocate an off-screen buffer. Pixels are uninitialized — call axl_gfx_buffer_clear before first use if you want a known background.

Parameters:
  • w – width in pixels (must be > 0)

  • h – height in pixels (must be > 0)

Returns:

new buffer (caller frees with axl_gfx_buffer_free), or NULL on allocation failure or invalid dimensions (w == 0 or h == 0).

void axl_gfx_buffer_free(AxlGfxBuffer *buf)

Free an off-screen buffer. Safe to call with NULL.

Parameters:
  • buf – buffer to free, or NULL

int axl_gfx_buffer_get_info(const AxlGfxBuffer *buf, uint32_t *out_w, uint32_t *out_h)

Get buffer dimensions.

Parameters:
  • buf – [in] buffer

  • out_w – [out] width (NULL OK to skip)

  • out_h – [out] height (NULL OK to skip)

Returns:

AXL_OK on success, AXL_ERR if buf is NULL.

int axl_gfx_buffer_clear(AxlGfxBuffer *buf, AxlGfxPixel color)

Fill the entire buffer with color.

Parameters:
  • buf – buffer to clear

  • color – fill color

Returns:

AXL_OK on success, AXL_ERR if buf is NULL.

AxlGfxPixel *axl_gfx_buffer_pixels(AxlGfxBuffer *buf)

Direct access to the buffer’s pixel array (row-major, no padding). Pointer is stable for the buffer’s lifetime. Length is w*h pixels.

Returns:

pointer to pixel array, or NULL if buf is NULL.

void axl_gfx_target_buffer(AxlGfxBuffer *buf)

Make subsequent drawing operations target buf instead of the screen. Pass NULL to switch back to screen rendering (the default).

While a buffer target is active, fill_rect / blit / draw_text write into the buffer’s pixel array using buffer-local coordinates. Clipping (push_clip / pop_clip) is honored against the same buffer-local coordinate system.

State persists until changed. Pair every set with an eventual reset to NULL to avoid leaking the target across unrelated frames.

Parameters:
  • buf – buffer to target, or NULL for screen

AxlGfxBuffer *axl_gfx_get_current_target(void)

Query the buffer currently set as the draw target.

Paradigm-agnostic primitive intended for callers that need to nest target changes — capture the current target, switch to their own buffer, then restore the captured value on exit. Without this query, a caller swapping in NULL on exit would stomp on an outer caller that expected its buffer to stay active.

Returns:

the currently-targeted buffer, or NULL if rendering targets the screen (the default).

int axl_gfx_buffer_present(const AxlGfxBuffer *buf, uint32_t dst_x, uint32_t dst_y)

Present (blit) a buffer to the screen at (dst_x, dst_y). Bypasses the clip stack and the current draw target — always blits directly via GOP. This is the “swap” step of double-buffering.

Parameters:
  • buf – source buffer

  • dst_x – screen x position

  • dst_y – screen y position

Returns:

AXL_OK on success, AXL_ERR if GOP is not available or buf is NULL.

int axl_gfx_buffer_present_rect(const AxlGfxBuffer *buf, uint32_t dst_x, uint32_t dst_y, uint32_t src_x, uint32_t src_y, uint32_t w, uint32_t h)

Present a sub-rectangle of buf to the screen (Phase G18).

The dirty-rectangle counterpart to axl_gfx_buffer_present: copies only the @a w × @a h region whose top-left lies at (src_x, src_y) in the buffer to screen position (dst_x, dst_y). Presenting just the changed region cuts present bandwidth by 10–100× for incremental redraws (a moving cursor, one updated widget). axl_gfx_buffer_present(buf, x, y) is exactly this over the whole buffer.

The source region is clamped to the buffer extent: a region that starts at or past the buffer edge presents nothing (still AXL_OK). Like axl_gfx_buffer_present, this bypasses the clip stack and the active draw target — it always writes directly to the screen.

Parameters:
  • buf – source buffer

  • dst_x – screen x of the region’s top-left

  • dst_y – screen y of the region’s top-left

  • src_x – buffer x of the region’s top-left

  • src_y – buffer y of the region’s top-left

  • w – region width in pixels

  • h – region height in pixels

Returns:

AXL_OK on success (including the fully-clamped no-op). AXL_ERR if GOP is not available or buf is NULL.

int axl_gfx_blit_transform(const AxlGfxBuffer *src, const AxlTransform *m)

Blit a source buffer into the active draw target through a transform.

The transform-aware counterpart to axl_gfx_blit — for rotated, sheared, or non-uniformly scaled images. The transform m maps source pixel coordinates (the rectangle [0, src->w] × [0, src->h]) into the draw target: e.g. axl_transform_translate(x, y) places the image upright at (x, y), and axl_transform_multiply(axl_transform_rotate(a), axl_transform_translate(x,y)) rotates it about its top-left corner (rotate first, then translate — cairo order).

Each covered destination pixel is inverse-mapped back into the source and bilinearly sampled (smooth under rotation / scale). Destination pixels whose sample falls outside the source rectangle are left untouched. The source alpha is honored: fully transparent source texels contribute nothing; partial alpha blends source-over on buffer targets (matching axl_gfx_fill_rect).

The active graphics transform (axl_gfx_translate et al.) composes on top: the effective mapping is CTM × m. Honors the active clip stack (including axl_gfx_push_clip_quad) and draw target. A singular (zero-area) m draws nothing.

Parameters:
  • src – [in] source image

  • m – [in] source-pixel → target transform

Returns:

AXL_OK on success (including the nothing-drawn cases). AXL_ERR if src or m is NULL, or the active target is the screen and GOP is unavailable.

int axl_gfx_fill_pattern(int32_t x, int32_t y, int32_t w, int32_t h, const AxlGfxBuffer *pattern, AxlGfxRepeat repeat)

Fill the rectangle (x, y, w, h) by tiling pattern.

The substrate for textured backgrounds, CSS repeating-linear- gradient (pre-render one period into a buffer, then repeat it), and nine-slice button art. The pattern is anchored at the rect’s top-left (x, y) — texel (0,0) lands there — and repeats per repeat: BOTH tiles in x and y; X/Y tile one axis and draw a single band on the other (rows/columns past the pattern extent are left untouched); NONE draws one copy at the origin.

Signed origins (like axl_gfx_fill_rect_i) so a pattern can start off the top-left edge. Honors the active clip stack and draw target, and composites each texel with the active blend mode and the texel’s alpha — fully-transparent texels (alpha == 0) leave the destination untouched, so patterns with holes show through. A zero-size fill (w <= 0 or h <= 0) is a documented no-op.

Parameters:
  • x – destination left (signed)

  • y – destination top (signed)

  • w – destination width (<= 0 = no-op)

  • h – destination height (<= 0 = no-op)

  • pattern – [in] tile source (anchored at x,y)

  • repeat – tiling mode

Returns:

AXL_OK on success (including the no-op). AXL_ERR if pattern is NULL or has zero dimensions, or the active target is the screen and GOP is unavailable.

void axl_gfx_set_blend_mode(AxlGfxBlendMode mode)

Set the active blend mode for subsequent drawing.

Graphics-driver state (like the draw target and clip stack), default AXL_GFX_BLEND_OVER (normal source-over). Every primitive that composites onto a buffer target — fills (rect / rounded / path / gradient), lines, text (bitmap + vector), and axl_gfx_blit_transform — honors it, including otherwise-opaque draws (an opaque source under AXL_GFX_BLEND_MULTIPLY still multiplies). The raw-copy axl_gfx_blit is exempt (it overwrites, it does not composite). Screen (GOP) targets cannot composite and ignore non-default modes, the same limitation as translucent alpha.

Save/restore via axl_gfx_get_blend_mode when nesting; reset to AXL_GFX_BLEND_OVER for normal drawing.

Parameters:
  • mode – blend function for subsequent draws

AxlGfxBlendMode axl_gfx_get_blend_mode(void)

Get the active blend mode (for save/restore around a nested draw).

void axl_gfx_reset_blend_mode(void)

Reset the blend mode to AXL_GFX_BLEND_OVER (the default). The recovery counterpart to axl_gfx_reset_clip / axl_gfx_reset_ transform, for a teardown / error path that must restore normal compositing without tracking the prior mode.

void axl_gfx_set_gamma_correct(bool enable)

Enable or disable gamma-correct (linear-light) compositing.

Module-global compositing state, like the blend mode — off by default (plain sRGB compositing, matching Cairo/Blend2D defaults and costing nothing). When enabled, every alpha/coverage composite onto a buffer target decodes the source and destination colors from sRGB to linear light, blends in linear, and re-encodes — the physically correct way to mix light.

What it fixes: the “dark fringe” on anti-aliased text and path edges (partial-coverage pixels are no longer composited too dark, so thin text keeps its weight and edges stay clean), and the brightness of translucent overlays / fades / shadows. Costs two small lookup tables (built lazily on first use) plus a decode/encode per blended channel, so it is opt-in for consumers that want the quality.

Scope: the source-over alpha composite is gamma-correct for all modes, and gradient color ramps interpolate in linear light too (see axl_gfx_gradient_sample). Still sRGB-only: the separable PDF blend functions (multiply/screen/…). Opaque draws are unaffected (no blending happens). Screen (GOP) targets composite in sRGB regardless, the same limitation as translucent alpha.

Save/restore via axl_gfx_get_gamma_correct when nesting.

Parameters:
  • enable – true = linear-light compositing; false = sRGB (default)

bool axl_gfx_get_gamma_correct(void)

Get the active gamma-correct compositing flag (for save/restore).

void axl_gfx_reset_gamma_correct(void)

Reset gamma-correct compositing to off (the default) — the recovery counterpart to axl_gfx_reset_blend_mode.

int axl_gfx_push_clip(AxlGfxClip rect)

Push a clipping rectangle onto the clip stack.

The new active clip is the intersection of rect with the previous top of stack (or rect itself if the stack was empty). Subsequent draw operations are clamped to the active clip; pixels outside it are not written. Empty intersections are valid (all draws no-op until pop or reset).

Pair every push with a matching pop. Stacking enables widget trees where each level adds its own clip on top of its parent’s.

Coordinate space: clip rectangles are interpreted in the active draw target’s coordinate system. With no target set (screen rendering), coordinates are screen pixels. After axl_gfx_target_buffer(buf), coordinates are buffer-local. Push clips AFTER setting the target you want them to apply to.

Parameters:
  • rect – clip rectangle to intersect onto the stack

Returns:

AXL_OK on success, AXL_ERR if the stack is full (depth > AXL_GFX_CLIP_STACK_MAX).

int axl_gfx_push_clip_quad(const AxlGfxPointF q[4])

Push a convex-quadrilateral clip region onto the clip stack.

The lean counterpart to axl_gfx_push_clip for callers whose clip is not axis-aligned — e.g. a widget toolkit that has rotated/sheared its content and needs the clip rotated to match. The four corners q are in the active draw target’s coordinate space (device / buffer-local pixels, the same space as AxlGfxClip and the rest of the clip stack) — pass the four already-transformed corners; the active transform (axl_gfx_translate et al.) is not re-applied, matching the device-space convention of axl_gfx_push_clip.

The new active clip is the intersection of the quad with the previous top of stack (same nesting semantics as axl_gfx_push_clip). Subsequent draws are clipped to the quad: a pixel is kept when its center lies inside all four edges. Edges are hard (no anti-aliasing on the clip boundary), matching the existing rectangular clip. Pair every push with a matching axl_gfx_pop_clip.

The corners may be given in either winding order (clockwise or counter-clockwise) and may be partly or fully outside the target. The region MUST be convex — the four transformed corners of a rectangle always are. A degenerate (zero-area) quad clips everything (empty region). axl_gfx_get_clip continues to report the axis-aligned bounding box of the active clip (a conservative rect), since its output type is a rectangle.

Parameters:
  • q – [in] four device-space corners (any winding)

Returns:

AXL_OK on success, AXL_ERR if q is NULL or the stack is full (depth > AXL_GFX_CLIP_STACK_MAX).

int axl_gfx_push_clip_rect_transformed(AxlRect r, const AxlTransform *m)

Push a clip = axis-aligned rect r mapped through transform m (and the active CTM), as a convex quad.

The convenience form of axl_gfx_push_clip_quad for the common case of clipping to a transformed rectangle: it maps the four corners of r through CTM × m (the same effective mapping the transform-aware drawing primitives use) and pushes the resulting device-space quad. An affine m yields a parallelogram, a projective m a general convex quad — both valid convex clip regions (a rect whose projective image crosses the horizon is out of contract, as for axl_transform_map_rect).

Parameters:
  • r – [in] rect in pre-transform (local) space

  • m – [in] local → target transform

Returns:

AXL_OK on success; AXL_ERR if m is NULL or the clip stack is full.

int axl_gfx_push_clip_path(const AxlGfxPath *path)

Push a clip = the filled interior of path, intersected with the current clip — arbitrary (concave, self-intersecting, multi-contour / holed) masking, the foundation for CSS clip-path.

Unlike axl_gfx_push_clip_quad (convex only), this rasterizes path to a coverage mask, so any shape works. The path is taken in its current device-space coordinates (built under whatever transform was active; the CTM is not re-applied here, matching push_clip_quad), and filled with the even-odd rule (same as axl_gfx_fill_path). The clip is hard-edged: a pixel is kept when its center is >= 50% covered (no anti-aliased clip boundary, matching the quad clip).

Subsequent draws are confined to the path ∩ the previous clip. Pair every push with axl_gfx_pop_clip. axl_gfx_get_clip reports the mask’s axis-aligned bounding box (a conservative rect).

Parameters:
  • path – [in] shape to clip to (device-space)

Returns:

AXL_OK on success; AXL_ERR if path is NULL or has fewer than 3 vertices (can’t enclose area), the clip stack is full, or the mask allocation fails.

int axl_gfx_pop_clip(void)

Pop the top clip off the stack.

Restores the previous clip (or “no clipping” if the stack is now empty). Pops both rectangular (axl_gfx_push_clip) and quad (axl_gfx_push_clip_quad) pushes — the stack is uniform.

Returns:

AXL_OK on success, AXL_ERR if the stack was already empty.

int axl_gfx_get_clip(AxlGfxClip *out)

Get the current effective clip rectangle.

Parameters:
  • out – [out] receives the active clip rect

Returns:

AXL_OK with out filled if a clip is active; AXL_ERR if no clip is on the stack (drawing is full-screen) OR if out is NULL.

void axl_gfx_reset_clip(void)

Reset the clip stack to empty (no clipping).

Useful as a recovery primitive when an error path made stack depth unrecoverable, or at app teardown. Widget toolkits should pair push/pop rather than reset for normal operation.

int axl_gfx_buffer_add_damage(AxlGfxBuffer *buf, AxlGfxClip rect)

Union rect into buf’s damage bounding box.

Accumulates the dirty region for the next axl_gfx_buffer_present_ damage. The stored damage is the axis-aligned bbox of every rect added since the last clear, clamped to the buffer extent. Portions of rect outside the buffer (including negative origins) are clipped off; a fully-out-of-bounds or empty (w == 0 || h == 0) rect contributes nothing.

Parameters:
  • buf – buffer to mark dirty

  • rect – dirty region in buffer-local coords

Returns:

AXL_OK on success, AXL_ERR if buf is NULL.

int axl_gfx_buffer_get_damage(const AxlGfxBuffer *buf, AxlGfxClip *out)

Get buf’s current damage bounding box.

Parameters:
  • buf – [in] buffer

  • out – [out] damage bbox (buffer-local)

Returns:

AXL_OK with out filled if the buffer has accumulated damage; AXL_ERR if buf or out is NULL, or the buffer is currently un-damaged (nothing to present).

int axl_gfx_buffer_clear_damage(AxlGfxBuffer *buf)

Clear buf’s accumulated damage (mark it fully clean).

Parameters:
  • buf – buffer to reset

Returns:

AXL_OK on success, AXL_ERR if buf is NULL.

int axl_gfx_buffer_present_damage(AxlGfxBuffer *buf, uint32_t dst_x, uint32_t dst_y)

Present buf’s accumulated damage region to the screen, then clear it (the “swap” step for incremental redraw).

Flushes exactly the damage bbox via axl_gfx_buffer_present_rect, mapping the buffer-local damage origin to screen position (dst_x + damage.x, dst_y + damage.y), then resets the damage so the next frame starts clean. With no damage accumulated this is a no-op (still AXL_OK) — nothing changed, nothing to present.

Parameters:
  • buf – source buffer (its damage is flushed + cleared)

  • dst_x – screen x for the buffer’s local origin

  • dst_y – screen y for the buffer’s local origin

Returns:

AXL_OK on success (including the no-damage no-op). AXL_ERR if GOP is not available or buf is NULL.

void axl_gfx_translate(double tx, double ty)

Append a translation to the current transform.

Composition matches HTML canvas: after axl_gfx_translate(10, 20) the local origin draws at world (10, 20). Subsequent translates add: translate(10, 0); translate(5, 0) is equivalent to translate(15, 0).

Affects only the drawing primitives that honor the transform — see “Transform-aware primitives” below.

void axl_gfx_scale(double sx, double sy)

Append a non-uniform scale to the current transform.

axl_gfx_scale(2, 2) doubles everything drawn after the call. axl_gfx_scale(1, -1) flips the y-axis.

void axl_gfx_rotate(double radians)

Append a rotation (in radians) to the current transform.

Positive radians rotate counter-clockwise in math y-up coords; clockwise in screen y-down (the axl-gfx default). See axl_transform_rotate for the underlying matrix.

void axl_gfx_skew(double sx, double sy)

Append a 2D shear to the current transform.

sx shears x as a function of y; sy shears y as a function of x. See axl_transform_shear for the convention.

int axl_gfx_push_transform(void)

Save the current transform onto the stack.

Pair every push with a matching axl_gfx_pop_transform. Up to AXL_GFX_TRANSFORM_STACK_MAX levels of nesting.

Returns:

AXL_OK on success, AXL_ERR if the stack is full.

int axl_gfx_pop_transform(void)

Restore the previously-saved transform.

Returns:

AXL_OK on success, AXL_ERR if the stack was empty.

AxlTransform axl_gfx_get_transform(void)

Get the current active transform as a 3×3 matrix.

Useful for converting hit-test coordinates from screen-space back to local-space via axl_transform_map_point against the inverse (when AxlMath ships matrix inverse — see the M-phase roadmap).

void axl_gfx_reset_transform(void)

Reset the transform to identity and clear the save stack.

Recovery primitive — pair push/pop for normal operation.

struct AxlGfxClip
#include <axl-gfx-surface.h>

Clipping rectangle. Signed origins so off-screen / negative positions are expressible (useful when widgets scroll partly out of view). Width/height are unsigned — an empty clip is encoded as w == 0 or h == 0.

Public Members

int32_t x

left edge in pixels

int32_t y

top edge in pixels

uint32_t w

width in pixels (0 = empty)

uint32_t h

height in pixels (0 = empty)

Drawing — <axl/axl-gfx-draw.h>

AxlGfx immediate-mode drawing primitives (fill/line/rect/blit/ capture, AxlGfxPoint polylines) and bitmap-font text rendering. Pulled in via the <axl/axl-gfx.h> umbrella.

Functions

int axl_gfx_fill_rect(uint32_t x, uint32_t y, uint32_t w, uint32_t h, AxlGfxPixel color)

Fill a rectangle with a solid color.

Parameters:
  • x – left edge

  • y – top edge

  • w – width in pixels

  • h – height in pixels

  • color – fill color

Returns:

AXL_OK on success, AXL_ERR if GOP not available.

int axl_gfx_fill_rect_i(int32_t x, int32_t y, int32_t w, int32_t h, AxlGfxPixel color)

Fill a rectangle with a solid color, signed-coord variant.

Identical semantics to axl_gfx_fill_rect except (x, y) may be negative — useful for widgets that partly scroll off the top or left edge. The visible portion is the intersection of the requested rect with (a) the active draw target’s bounds and (b) the active clip stack. Width / height are signed too so w < 0 or h < 0 is a no-op (returns AXL_OK) — matches the zero-dim lenience of the unsigned variant.

Equivalent to: clamp negatives in caller code, then call the unsigned axl_gfx_fill_rect — but with the boundary arithmetic inside the library, callers don’t have to repeat the four-line dance every time a widget might scroll partly off-screen.

Parameters:
  • x – left edge (may be negative)

  • y – top edge (may be negative)

  • w – width in pixels (<=0 = no-op)

  • h – height in pixels (<=0 = no-op)

  • color – fill color

Returns:

AXL_OK on success (including the no-op cases), AXL_ERR if GOP not available on a screen target.

int axl_gfx_blit(const AxlGfxPixel *buffer, uint32_t x, uint32_t y, uint32_t w, uint32_t h)

Blit a pixel buffer to the screen.

buffer must contain at least w * h pixels in row-major order with AxlGfxPixel (BGRX) layout.

Parameters:
  • buffer – [in] source pixel buffer

  • x – destination left edge

  • y – destination top edge

  • w – width in pixels

  • h – height in pixels

Returns:

AXL_OK on success, AXL_ERR if GOP not available.

int axl_gfx_blit_rect(const AxlGfxPixel *buffer, uint32_t src_stride, uint32_t src_x, uint32_t src_y, uint32_t dst_x, uint32_t dst_y, uint32_t w, uint32_t h)

Blit a w×h sub-rectangle of a larger source image to the active target.

Unlike axl_gfx_blit (which treats the whole buffer as one tight w×h image), this addresses a region of a wider source: src_stride is the source’s full row width in pixels (e.g. a sprite sheet’s width), and the sub-rect’s top-left within the source is (src_x, src_y). This lets a consumer blit one cell of a sprite sheet each frame without first CPU-copying the cell out.

axl_gfx_blit(buf, x, y, w, h) is exactly axl_gfx_blit_rect(buf, w, 0, 0, x, y, w, h).

Raw target coordinates — NOT transform-aware (see the header note above); same pixel (BGRX) and source-alpha semantics as axl_gfx_blit. The destination is clipped to the target bounds and the active clip stack. It is the caller’s responsibility that the source sub-rect lies within the source buffer — i.e. src_x + w <= src_stride and the buffer holds at least (src_y + h) * src_stride pixels; out-of-range values read past the source (matching the low-level contract).

Parameters:
  • buffer – [in] source pixel buffer (full image)

  • src_stride – source row stride in pixels

  • src_x – sub-rect left in the source

  • src_y – sub-rect top in the source

  • dst_x – destination left edge

  • dst_y – destination top edge

  • w – sub-rect / blit width

  • h – sub-rect / blit height

Returns:

AXL_OK on success; AXL_ERR if buffer is NULL, w or h is zero, or the GOP is unavailable on a screen target.

int axl_gfx_draw_line(int32_t x0, int32_t y0, int32_t x1, int32_t y1, AxlGfxPixel color)

Draw a 1-pixel-wide line from (x0, y0) to (x1, y1).

Uses Bresenham’s line algorithm. Signed origins so partly off-screen lines are expressible. Honors the active clip and (for buffer targets) source alpha. Endpoints are inclusive — both (x0,y0) and (x1,y1) get written.

Parameters:
  • x0 – start x (signed; off-screen origins OK)

  • y0 – start y

  • x1 – end x (inclusive)

  • y1 – end y (inclusive)

  • color – line color (alpha honored on buffer targets)

Returns:

AXL_OK on success, AXL_ERR if GOP not available (screen target only).

int axl_gfx_draw_rect(uint32_t x, uint32_t y, uint32_t w, uint32_t h, AxlGfxPixel color)

Draw a 1-pixel-wide rectangle outline.

Equivalent to four 1-pixel-thick axl_gfx_fill_rect calls — top, bottom, left, right edges. Interior pixels are not touched. For w==1 or h==1, the four edges degenerate sensibly (still draws the 1-wide column or row). w==0 or h==0 is a documented no-op (matches axl_gfx_fill_rect’s lenience).

Parameters:
  • x – left edge

  • y – top edge

  • w – width (0 = no-op)

  • h – height (0 = no-op)

  • color – outline color

Returns:

AXL_OK on success (including the zero-dim no-op), AXL_ERR if GOP not available on a screen target.

int axl_gfx_draw_polyline(const AxlGfxPoint *points, size_t count, AxlGfxPixel color)

Draw connected 1-pixel-wide line segments through count points.

Equivalent to (count - 1) axl_gfx_draw_line calls between consecutive points. Points may have signed (off-screen) coordinates.

Parameters:
  • points – [in] array of point vertices

  • count – number of points (>= 2)

  • color – line color

Returns:

AXL_OK on success, AXL_ERR if points is NULL or count < 2.

int axl_gfx_capture(AxlGfxPixel *buffer, uint32_t x, uint32_t y, uint32_t w, uint32_t h)

Capture a screen region into a pixel buffer.

buffer must have space for at least w * h pixels.

Parameters:
  • buffer – [out] destination pixel buffer

  • x – source left edge

  • y – source top edge

  • w – width in pixels

  • h – height in pixels

Returns:

AXL_OK on success, AXL_ERR if GOP not available.

const AxlFont *axl_gfx_default_font(void)

Get the default built-in font.

Returns a pointer to the font axl-gfx ships with — currently the EDK2 LaffStd 8x16 narrow font. Stable across calls (returns the same pointer; never NULL). Suitable as the font argument to the text APIs when the caller has no preference.

Returns:

pointer to the default font (static, never NULL).

uint32_t axl_gfx_measure_text(const AxlFont *font, const char *text, uint32_t scale)

Compute the rendered pixel width of a text string.

text is UTF-8. Each codepoint contributes its per-glyph advance (or the font’s cell_width for monospace / missing glyphs). Invalid UTF-8 bytes are treated as U+FFFD REPLACEMENT CHARACTER. Does not require GOP.

Parameters:
  • font – [in] font atlas to measure with

  • text – [in] UTF-8 text to measure

  • scale – scale factor (1 = native, 2 = doubled, etc.)

Returns:

rendered width in pixels, or 0 if any argument is invalid (font NULL, text NULL, or scale 0).

int axl_gfx_draw_text(const AxlFont *font, uint32_t x, uint32_t y, const char *text, AxlGfxPixel color, uint32_t scale)

Draw a UTF-8 text string at the given position.

Decodes text as UTF-8 and renders each codepoint’s glyph from font. Codepoints absent from the font render the font’s fallback glyph (if any) or skip while still advancing the pen. Invalid UTF-8 sequences become U+FFFD REPLACEMENT CHARACTER. Output is clamped to screen bounds.

Parameters:
  • font – [in] font atlas

  • x – left edge (pixels)

  • y – top edge (pixels)

  • text – UTF-8 text to render

  • color – text foreground color

  • scale – scale factor (1 = native, 2 = doubled, etc.)

Returns:

AXL_OK on success, AXL_ERR if GOP not available, font or text is NULL, or scale is 0.

struct AxlGfxPoint
#include <axl-gfx-draw.h>

2D integer point for polyline / shape APIs.

Public Members

int32_t x
int32_t y

Paths — <axl/axl-gfx-path.h>

AxlGfx retained-mode paths (Phase G3): build with move_to/line_to/ curve_to/arc/close, then fill (even-odd, anti-aliased), stroke, or fill_rounded_rect. Pulled in via the <axl/axl-gfx.h> umbrella.

Typedefs

typedef struct AxlGfxPath AxlGfxPath

Opaque retained path — sequence of line segments and subpaths.

Built incrementally with move_to / line_to / curve_to / arc / close. Curves and arcs are flattened to line segments at insertion time so fill / stroke operations walk a uniform segment list. Filled or stroked with axl_gfx_fill_path / axl_gfx_stroke_path; the path object is reusable across many fills (caller frees with axl_gfx_path_free).

Retained shape (not immediate-mode) is deliberate per the AGT recording-fixture design — capturing a path pointer keeps trace entries cheap, where capturing a points array would require deep-copy + UAF risk.

Enums

enum AxlGfxLineCap

Line-cap style for the open ends of a stroked subpath.

Values:

enumerator AXL_GFX_CAP_BUTT

flush square end exactly at the endpoint (default)

enumerator AXL_GFX_CAP_ROUND

semicircular end, radius = width/2

enumerator AXL_GFX_CAP_SQUARE

square end projecting width/2 past the endpoint

enum AxlGfxLineJoin

Line-join style for the corners between stroked segments.

Values:

enumerator AXL_GFX_JOIN_MITER

sharp projected corner, clamped by miter_limit (default)

enumerator AXL_GFX_JOIN_ROUND

rounded corner, radius = width/2

enumerator AXL_GFX_JOIN_BEVEL

flat chamfered corner

Functions

AxlGfxPath *axl_gfx_path_new(void)

Allocate an empty path.

Returns:

new path (caller frees with axl_gfx_path_free), or NULL on allocation failure.

void axl_gfx_path_free(AxlGfxPath *p)

Free a path allocated with axl_gfx_path_new.

Safe to call with NULL.

Parameters:
  • p – path to free, or NULL

void axl_gfx_path_reset(AxlGfxPath *p)

Clear all segments from p, retaining the allocation for reuse.

After reset the path is empty (no subpaths, no current pen position). Subsequent axl_gfx_path_move_to starts a new subpath as if p were freshly allocated. Cheaper than free + new for paths that are rebuilt every frame.

Parameters:
  • p – path to reset

void axl_gfx_path_move_to(AxlGfxPath *p, float x, float y)

Start a new subpath at (x, y).

Moves the current pen position without emitting a line segment. Multiple subpaths within one path are filled together using the even-odd fill rule (subpath intersections invert).

Parameters:
  • p – path

  • x – pen x

  • y – pen y

void axl_gfx_path_line_to(AxlGfxPath *p, float x, float y)

Add a line segment from the current pen position to (x, y).

If no subpath is open (no prior move_to since last reset / new), the line implicitly starts a subpath at (x, y) (i.e., the initial point becomes both start and current — no segment yet).

Parameters:
  • p – path

  • x – end x

  • y – end y

void axl_gfx_path_curve_to(AxlGfxPath *p, float c1x, float c1y, float c2x, float c2y, float x, float y)

Add a cubic Bezier curve from the current pen to (x, y).

Control points (c1x, c1y) and (c2x, c2y) shape the curve. The implementation flattens to line segments using recursive de Casteljau subdivision; segment density scales with the curve’s deviation from straight. After insertion the pen is at (x, y).

Parameters:
  • p – path

  • c1x – first control point x

  • c1y – first control point y

  • c2x – second control point x

  • c2y – second control point y

  • x – end x

  • y – end y

void axl_gfx_path_arc(AxlGfxPath *p, float cx, float cy, float r, float start_rad, float end_rad)

Add a circular arc to the path.

Center (cx, cy), radius r, sweeping counterclockwise from start_rad to end_rad. If there’s already an open subpath, the implementation emits a line_to to the arc’s starting point (start_rad on the circle) before tracing the arc. After insertion the pen is at the arc’s end point.

Arc segment density is chosen so the chord-to-arc deviation is sub-pixel at typical UI scales.

Parameters:
  • p – path

  • cx – center x

  • cy – center y

  • r – radius

  • start_rad – start angle (radians)

  • end_rad – end angle (radians, > start_rad)

void axl_gfx_path_close(AxlGfxPath *p)

Close the current subpath by adding a line from the current pen to the subpath’s starting point.

No-op if no subpath is open. Calling axl_gfx_path_move_to after close starts a new subpath.

Parameters:
  • p – path

int axl_gfx_fill_path(const AxlGfxPath *p, AxlGfxPixel color)

Fill the area enclosed by p with color.

Uses even-odd fill rule for nested subpaths. Edges are anti- aliased: pixels straddling the path boundary are blended into the active draw target via their exact fractional coverage (computed by an analytic scanline rasterizer). Honors the active clip stack and draw target; alpha-blends on buffer targets where supported. Open subpaths are implicitly closed for filling.

Parameters:
  • p – [in] path to fill

  • color – fill color

Returns:

AXL_OK on success. AXL_ERR if p is NULL, the path is empty, or the active target is the screen and GOP is unavailable.

int axl_gfx_fill_path_gradient(const AxlGfxPath *p, const AxlGfxGradient *g)

Fill p with a gradient instead of a solid color.

Identical rasterization to axl_gfx_fill_path (even-odd, anti-aliased, honors clip + draw target), except each covered pixel takes its color from g sampled at that pixel (axl_gfx_gradient_sample) modulated by edge coverage. A gradient with no stops paints nothing.

Parameters:
  • p – [in] path to fill

  • g – [in] gradient to sample

Returns:

AXL_OK on success. AXL_ERR if p is NULL / empty, g is NULL, or the active target is the screen and GOP is unavailable.

int axl_gfx_stroke_path_ex(const AxlGfxPath *p, AxlGfxPixel color, const AxlGfxStrokeStyle *style)

Stroke the outline of p with color and explicit style.

The stroke is anti-aliased (shares the path fill rasterizer) and honors width, caps (butt / round / square), and joins (miter with miter_limit / round / bevel). Miters that exceed the limit fall back to a bevel. Closed subpaths are joined all round (no caps); open subpaths are capped at both ends.

When style->dashes is non-NULL with n_dashes > 0, each subpath is split into on/off intervals by the dash pattern (each “on” interval is stroked as its own capped open piece, honoring the cap style at its ends). dash_offset shifts the pattern start. An empty / all-zero / negative pattern (or one with no element long enough to advance) strokes solid. On a closed subpath the dashes at the start/end seam are not merged (each is capped) — a minor fidelity gap vs SVG; the FreeType backend handles it.

Honors the active clip stack and draw target. style->width <= 0 is a no-op success.

Parameters:
  • p – [in] path to stroke

  • color – stroke color

  • style – [in] cap / join / width / miter

Returns:

AXL_OK on success (including the no-op width <= 0 case). AXL_ERR if p or style is NULL, on allocation failure, or the active target is the screen and GOP is unavailable.

int axl_gfx_stroke_path(const AxlGfxPath *p, AxlGfxPixel color, float w)

Stroke p with color and width w — convenience wrapper over axl_gfx_stroke_path_ex with the default style (butt caps, miter joins, miter limit 10).

Parameters:
  • p – [in] path to stroke

  • color – stroke color

  • w – stroke width in pixels

Returns:

AXL_OK on success (including the no-op w <= 0 case). AXL_ERR if p is NULL, on allocation failure, or the active target is the screen and GOP is unavailable.

int axl_gfx_fill_rounded_rect(int32_t x, int32_t y, int32_t w, int32_t h, float radius, AxlGfxPixel color)

Fill a rectangle with rounded corners — immediate-mode helper.

Internally builds a transient path, fills it, and frees. Saves callers from path-lifecycle management for the case that dominates widget rendering (button + panel backgrounds).

radius is clamped to min(w, h) / 2. A radius of 0 produces a plain rectangle (equivalent to axl_gfx_fill_rect_i). Honors clip + draw target.

Parameters:
  • x – left edge (may be negative)

  • y – top edge (may be negative)

  • w – width in pixels (<= 0: no-op)

  • h – height in pixels (<= 0: no-op)

  • radius – corner radius (clamped to min(w,h)/2)

  • color – fill color

Returns:

AXL_OK on success. AXL_ERR if w / h are non-positive (no-op success path also returns AXL_OK matching the unsigned-rect lenience) or the active target is the screen and GOP is unavailable.

int axl_gfx_fill_rounded_rect_gradient(int32_t x, int32_t y, int32_t w, int32_t h, float radius, const AxlGfxGradient *g)

Fill a rounded rectangle with a gradient instead of a solid color.

Same geometry/clamping/AA as axl_gfx_fill_rounded_rect; each pixel (straight bands and anti-aliased corners alike) takes its color from g sampled at that pixel. A gradient with no stops paints nothing.

Parameters:
  • x – left edge (may be negative)

  • y – top edge (may be negative)

  • w – width in pixels (<= 0: no-op)

  • h – height in pixels (<= 0: no-op)

  • radius – corner radius (clamped to min(w,h)/2)

  • g – [in] gradient to sample

Returns:

AXL_OK on success (including the w/ <= 0 no-op). AXL_ERR if g is NULL or the active target is the screen and GOP is unavailable.

struct AxlGfxStrokeStyle
#include <axl-gfx-path.h>

Stroke styling for axl_gfx_stroke_path_ex. A zero-initialized value is a valid CSS-style default (butt caps, miter joins) once width is set.

Public Members

float width

stroke width in pixels (<= 0: no-op)

AxlGfxLineCap cap

end-cap style (open subpaths + dash ends)

AxlGfxLineJoin join

corner style between segments

float miter_limit

max miterLength/strokeWidth = 1/sin(θ/2) (SVG/Canvas); <= 0 → default 10

const float *dashes

on/off dash lengths in pixels, alternating on,off,…; NULL = solid

size_t n_dashes

number of entries in dashes (0 = solid; odd repeats to even, SVG-style)

float dash_offset

phase: distance into the pattern at each subpath start

Gradients — <axl/axl-gfx-gradient.h>

AxlGfx gradients (Phase G5) — linear and radial color gradients for filling rectangles, paths, and rounded rects. Table stakes for modern UI: button backgrounds, hero panels, CSS linear-gradient / radial-gradient. Pulled in via the <axl/axl-gfx.h> umbrella.

AxlGfxGradient *g = axl_gfx_gradient_linear_new(0, 0, 0, 100);
axl_gfx_gradient_add_stop(g, 0.0f, AXL_GFX_RGB(0x4a, 0x90, 0xd9));
axl_gfx_gradient_add_stop(g, 1.0f, AXL_GFX_RGB(0x1c, 0x3f, 0x6b));
axl_gfx_fill_rect_gradient(10, 10, 200, 100, g);
axl_gfx_gradient_free(g);

Defines

AXL_GFX_GRADIENT_MAX_STOPS

Maximum color stops per gradient. Deep enough for any realistic UI gradient; small enough to live inline in the gradient object with no per-stop allocation.

Typedefs

typedef struct AxlGfxGradient AxlGfxGradient

Opaque gradient object. Holds the gradient geometry (the axis for linear, the center + radius for radial) plus a color-stop list. Built with axl_gfx_gradient_linear_new / _radial_new + axl_gfx_gradient_add_stop; consumed by the _gradient fill variants; freed with axl_gfx_gradient_free. Reusable across many fills.

Geometry coordinates are in the active draw target’s coordinate system — the same space the fill rectangle / path uses.

Functions

AxlGfxGradient *axl_gfx_gradient_linear_new(float x0, float y0, float x1, float y1)

Create a linear gradient whose color axis runs from (x0, y0) to (x1, y1).

A filled pixel’s color is chosen by projecting the pixel onto this axis: the offset is 0 at (x0, y0), 1 at (x1, y1), and is clamped to [0, 1] beyond the endpoints (so the end stops extend flat). A zero-length axis (both endpoints equal) paints the whole region with the first stop’s color.

Coordinates are floats in the active draw target’s space.

Parameters:
  • x0 – axis start x

  • y0 – axis start y

  • x1 – axis end x

  • y1 – axis end y

Returns:

new gradient (caller frees with axl_gfx_gradient_free), or NULL on allocation failure.

AxlGfxGradient *axl_gfx_gradient_radial_new(float cx, float cy, float radius)

Create a radial gradient centered at (cx, cy) with radius radius.

A filled pixel’s color is chosen by its distance from the center: offset 0 at the center, 1 at radius, clamped to [0, 1] beyond (the last stop extends flat outside the circle). A non-positive radius paints the whole region with the last stop’s color.

Parameters:
  • cx – center x

  • cy – center y

  • radius – radius in pixels (offset 1 at this distance)

Returns:

new gradient (caller frees with axl_gfx_gradient_free), or NULL on allocation failure.

int axl_gfx_gradient_add_stop(AxlGfxGradient *g, float t, AxlGfxPixel color)

Add a color stop at normalized offset t (clamped to [0, 1]).

Stops may be added in any order; the gradient keeps them sorted by offset internally. Between two adjacent stops the color is linearly interpolated per channel (including alpha). Before the first stop the first stop’s color applies; after the last stop the last stop’s color applies.

Parameters:
  • g – gradient to add to

  • t – offset in [0, 1] (clamped)

  • color – stop color (alpha interpolated too)

Returns:

AXL_OK on success. AXL_ERR if g is NULL or the stop list is already full (AXL_GFX_GRADIENT_MAX_STOPS).

void axl_gfx_gradient_free(AxlGfxGradient *g)

Free a gradient created with axl_gfx_gradient_*_new.

Safe to call with NULL.

Parameters:
  • g – gradient to free, or NULL

AxlGfxPixel axl_gfx_gradient_sample(const AxlGfxGradient *g, int32_t x, int32_t y)

Sample g at the center of pixel (x, y).

Returns the interpolated color the fill helpers would paint at that pixel (before coverage/AA is applied). The offset is computed the same way as the fill variants — axis projection for linear, distance/radius for radial, clamped to [0, 1]. Useful for custom painting and as the per-pixel source for path / rounded-rect fills.

Honors axl_gfx_set_gamma_correct: when on, the color ramp between stops is interpolated in linear light (perceptually even, no dark dip between colors); alpha always interpolates plainly (it’s coverage, not a light value).

Parameters:
  • g – [in] gradient to sample

  • x – pixel x

  • y – pixel y

Returns:

the sampled AxlGfxPixel; fully-transparent (alpha 0) if g is NULL or has no stops.

int axl_gfx_fill_rect_gradient(int32_t x, int32_t y, int32_t w, int32_t h, const AxlGfxGradient *g)

Fill a rectangle with g, signed-coord variant.

Geometry mirrors axl_gfx_fill_rect_i: (x, y) may be negative, w <= 0 or h <= 0 is a no-op success, and the visible region is the intersection of the rect with the active draw target and clip stack. Each filled pixel samples g at its position; stop colors with alpha < 0xFF source-over blend on buffer targets.

A gradient with no stops fills nothing (AXL_OK, no-op).

Parameters:
  • x – left edge (may be negative)

  • y – top edge (may be negative)

  • w – width in pixels (<= 0: no-op)

  • h – height in pixels (<= 0: no-op)

  • g – [in] gradient to sample

Returns:

AXL_OK on success (including the no-op cases). AXL_ERR if g is NULL, or the active target is the screen and GOP is unavailable.

Effects — <axl/axl-gfx-effects.h>

AxlGfx raster effects (Phase G6+): blur and (later) drop shadows, blend modes. Operate on off-screen AxlGfxBuffers. Pulled in via the <axl/axl-gfx.h> umbrella.

AxlGfxBuffer *b = axl_gfx_buffer_new(w, h);
// ... render into b ...
axl_gfx_buffer_blur(b, 8);          // soften it
axl_gfx_buffer_present(b, 0, 0);    // blit to screen

Functions

int axl_gfx_buffer_blur(AxlGfxBuffer *buf, uint32_t radius)

Blur an off-screen buffer in place (stack blur — a fast Gaussian approximation that is O(1) per pixel regardless of radius, run as a separable horizontal + vertical pass).

All four channels are blurred, including alpha, so this works directly on a shadow/alpha mask as well as on color content. Edges use clamp-to-edge sampling (no darkening at the border).

radius is the blur reach in pixels each way; the effective kernel width is 2*radius + 1. A radius of 0 is a no-op (returns AXL_OK). Radii larger than the buffer are clamped to fit.

Operates purely on the buffer’s pixel array — does NOT require GOP and ignores the active draw target / clip stack.

Parameters:
  • buf – buffer to blur in place

  • radius – blur radius in pixels (0 = no-op)

Returns:

AXL_OK on success (including the radius-0 no-op). AXL_ERR if buf is NULL or the temporary scratch allocation fails.

int axl_gfx_draw_shadow(const AxlGfxBuffer *src, int32_t x, int32_t y, AxlGfxPixel color, uint32_t radius)

Draw a soft drop shadow of src into the active draw target.

The shadow’s SHAPE comes from src’s alpha channel (so it works for any rendered content — a box, rounded rect, or anti-aliased text). It is tinted with color (use a translucent color for a subtle shadow), blurred by radius, and composited at (x, y) — the position src’s top-left would occupy, so to offset the shadow pass x + offset_x, y + offset_y. The caller then draws the real content on top.

The shadow softly extends up to radius pixels beyond src’s bounds. Honors the active draw target, clip stack, and alpha blending (each shadow pixel composites source-over). radius 0 gives a hard (un-blurred) tinted silhouette.

Parameters:
  • src – [in] shape whose alpha is the shadow mask

  • x – target x for src’s top-left (bake offset in)

  • y – target y for src’s top-left

  • color – shadow tint (alpha scales the shadow)

  • radius – blur radius in pixels

Returns:

AXL_OK on success. AXL_ERR if src is NULL, a temporary buffer allocation fails, or the active target is the screen and GOP is unavailable.

The retained display list lives on its own page — see AxlGfxDisplayList — Retained Display List.

AxlFont — <axl/axl-font.h>

Bitmap font primitives used by axl-gfx and consumers (e.g. AGT).

Defines the data structures that describe a bitmap font: per-glyph metrics + scanline data (AxlGlyph) and a font atlas (AxlFont) that bundles glyphs with font-wide metrics. Each font is a separate compilation unit (see src/gfx/fonts/) so consumers can pick which fonts to link.

A small number of built-in fonts are shipped with axl-gfx; consumers can also author their own AxlFont objects (typically generated from BDF or PSF sources via scripts/gen-bdf-font.py).

const AxlFont *font = axl_gfx_default_font();
axl_gfx_draw_text(font, 20, 20, "Hello", white, 1);

Substrate discipline (per docs/AGT-Design.md): pure C, paradigm- agnostic — no widget, theme, or registry concepts. Named-font lookup, fallback chains, and theme integration live in AGT.

Defines

AXL_GLYPH_MAX_BYTES

Maximum packed-bitmap bytes per glyph. Sized to fit 16x16 glyphs (16 rows * 2 bytes/row). Glyphs larger than this are out of scope for v0.1 and will be addressed when a consumer requires them.

AXL_FONT_MONOSPACE

Font type flags — mirrors PEG’s PegFont uType bits where applicable.

every glyph’s advance == cell_width

AXL_FONT_VARIABLE

proportional advance widths (per-glyph)

Functions

const AxlGlyph *axl_font_glyph(const AxlFont *font, uint32_t codepoint)

Look up a glyph by codepoint.

Performs binary search over the codepoint-sorted glyph array. If the requested codepoint is absent, returns the fallback glyph (when font has a fallback_codepoint defined) or NULL.

Parameters:
  • font – [in] font atlas

  • codepoint – Unicode codepoint to look up

Returns:

pointer to the glyph (owned by font), or NULL if the codepoint is missing and there is no fallback.

uint16_t axl_font_advance(const AxlFont *font, uint32_t codepoint)

Compute the advance width of a single codepoint in this font.

For monospace fonts this returns cell_width regardless of whether the glyph is present. For variable-width fonts, returns the glyph’s advance (or the fallback glyph’s, or cell_width if neither exists).

Parameters:
  • font – [in] font atlas

  • codepoint – Unicode codepoint

Returns:

advance width in pixels.

struct AxlGlyph
#include <axl-font.h>

A single glyph: codepoint + bitmap + positioning metrics.

Bitmap layout: packed scanlines, row-major, MSB-first within each byte. Stride (bytes per row) = (width + 7) / 8. Total bytes used = height * stride, with remaining bitmap[] bytes undefined.

Default placement is top-of-cell aligned, pen-anchored. The x_offset / y_offset fields shift the bitmap from this default: positive x_offset moves right (away from pen), positive y_offset moves down (away from cell top). Both are zero for cell-based bitmap fonts where each glyph fills its cell exactly; BDF-derived proportional fonts use non-zero offsets to position glyph bitmaps within their cell.

Public Members

uint32_t codepoint

Unicode codepoint.

uint8_t width

bitmap width in pixels

uint8_t height

bitmap height in pixels

int8_t x_offset

pixel shift from pen (positive = right)

int8_t y_offset

pixel shift from top of cell (positive = down)

uint8_t advance

pen advance after glyph (== cell_width for monospace)

uint8_t _pad[3]

padding to align bitmap[]

uint8_t bitmap[32]

packed scanlines, stride = (width+7)/8

struct AxlFont
#include <axl-font.h>

A font atlas: array of glyphs sorted by codepoint + font-wide metrics.

Glyphs MUST be sorted by ascending codepoint to enable binary-search lookup via axl_font_glyph. Sparse codepoint coverage is the norm for Unicode subset fonts; contiguous-range fonts simply happen to be densely packed.

Public Members

const char *name

short identifier (“edk2-laffstd”)

const char *description

human-readable description

uint32_t flags

AXL_FONT_* bitmask.

uint16_t cell_width

monospace cell width (max-advance for variable)

uint16_t cell_height

glyph cell height (== ascent + descent)

uint16_t ascent

pixels above baseline

uint16_t descent

pixels below baseline

uint16_t line_height

recommended line-to-line advance

uint32_t n_glyphs

number of entries in glyphs[]

const AxlGlyph *glyphs

codepoint-sorted array

uint32_t fallback_codepoint

glyph used when a codepoint is missing (0 = render blank, no fallback)

AxlCursor — <axl/axl-cursor.h>

Software mouse-cursor compositor for axl-gfx. GOP exposes no hardware-cursor API (the GPU usually has a cursor plane, but UEFI surfaces only a framebuffer + Blt — no portable way to drive it), so a sprite that tracks the pointer must be composited in software — and since exactly one cursor owns the screen, that belongs in one shared place rather than re-invented per consumer.

The cursor is bound to the back-buffer “scene” being scanned out (the source of truth for the pixels under it). Moving it touches only the cursor region: the old position is erased by re-presenting the clean scene there, and the sprite is composited over the scene at the new position — no full-frame redraw (validated; see docs/AXL-Pointer-Cursor-Design.md, Option C).

AxlCursor owns the sprite on the screen and the current position. It never routes input — hit-testing and widget behavior stay with the consumer. (Under a future surface compositor the cursor becomes the topmost overlay and input routing moves to a seat; this same sprite + compositing logic carries over.)

AxlGfxBuffer *scene = axl_gfx_buffer_new(w, h);   // your back-buffer
// ... draw your frame into scene, present it ...
AxlCursor *cur = axl_cursor_new(scene);           // built-in arrow
axl_cursor_attach(cur, loop, on_input, &app);     // tracks the pointer
// when you re-present a new frame:
axl_cursor_lift(cur);
axl_gfx_buffer_present(scene, 0, 0);
axl_cursor_drop(cur);

Typedefs

typedef struct AxlCursor AxlCursor

Opaque cursor compositor.

Functions

AxlCursor *axl_cursor_new(AxlGfxBuffer *scene)

Create a cursor bound to a back-buffer scene.

scene is the buffer being scanned out — the source of truth for the pixels under the cursor (today a consumer’s back-buffer; under a future compositor, its composited output). It must outlive the cursor. The cursor starts with the built-in arrow, hidden, at (0, 0).

Pass a NULL scene for direct-to-screen “save-under” mode (Option B): the screen itself is the scene, so the cursor captures the screen pixels under it (axl_gfx_capture) and restores them on move. This needs a live framebuffer — it returns NULL if graphics output is unavailable. The bracket discipline still applies: when the consumer presents a new frame while the cursor is up, the saved pixels go stale, so wrap the present in axl_cursor_lift / axl_cursor_drop. Most consumers have a back-buffer; prefer the (non-NULL scene) Option-C path when you do.

Parameters:
  • scene – back-buffer being scanned out, or NULL for save-under mode; if non-NULL must outlive the cursor

Returns:

the cursor, or NULL on allocation failure (or, for save-under mode, if graphics output is unavailable).

void axl_cursor_free(AxlCursor *c)

Destroy a cursor. NULL-safe. Does not free the scene.

If the cursor is visible, hide it (restore the scene under it) first.

Parameters:
  • c – cursor (NULL-safe)

int axl_cursor_set_image(AxlCursor *c, const AxlGfxBuffer *sprite, int32_t hot_x, int32_t hot_y)

Set the cursor sprite and hotspot.

sprite is an RGBA buffer composited source-over the scene; its pixels are copied, so the buffer need not outlive the call. hot_x / hot_y are the hotspot offset within the sprite (the pixel that sits exactly on the pointer position). Pass a NULL sprite to restore the built-in arrow.

Parameters:
  • c – cursor

  • sprite – RGBA sprite (copied); NULL = built-in arrow

  • hot_x – hotspot x within the sprite

  • hot_y – hotspot y within the sprite

Returns:

AXL_OK on success, AXL_ERR on bad arguments / allocation failure.

void axl_cursor_show(AxlCursor *c)

Show the cursor (composite it at the current position).

Parameters:
  • c – cursor

void axl_cursor_hide(AxlCursor *c)

Hide the cursor (restore the scene under it).

Parameters:
  • c – cursor

bool axl_cursor_visible(const AxlCursor *c)

Whether the cursor is currently shown.

Parameters:
  • c – cursor (NULL-safe; NULL → false)

Returns:

true if visible.

void axl_cursor_move(AxlCursor *c, int32_t x, int32_t y)

Move the hotspot to (x, y) in screen pixels.

Erases the cursor at its old position (re-presents the clean scene there) and composites it at the new one — touching only the cursor region(s), not the whole frame. The position is clamped to the framebuffer. No-op visually while hidden (position is still tracked).

Parameters:
  • c – cursor

  • x – hotspot x in screen pixels

  • y – hotspot y in screen pixels

void axl_cursor_move_rel(AxlCursor *c, int32_t dx, int32_t dy)

Move the hotspot by a relative delta from its current position.

Applies (dx, dy) to the current hotspot and clamps the result to the framebuffer — so the cursor stops at an edge but moves the instant the delta reverses. This is the right way to drive the cursor from a relative pointer (EFI_SIMPLE_POINTER): feeding the device’s raw accumulated position to axl_cursor_move instead would let the cursor get stuck off-screen (the accumulator can drift far past an edge, and the clamped cursor then can’t recover until the accumulator climbs all the way back). axl_cursor_attach tracks the pointer through this.

Parameters:
  • c – cursor

  • dx – delta x in screen pixels

  • dy – delta y in screen pixels

void axl_cursor_position(const AxlCursor *c, int32_t *x, int32_t *y)

Get the current hotspot position.

Parameters:
  • c – cursor (NULL-safe)

  • x – [out] hotspot x (may be NULL)

  • y – [out] hotspot y (may be NULL)

void axl_cursor_lift(AxlCursor *c)

Bracket-open: fold the cursor into the scene before the consumer flushes it, so one present carries the cursor atomically.

For a bound scene (Option C), this composites the sprite INTO the scene as its top layer (saving the pixels it overwrites), so the consumer’s axl_gfx_buffer_present(scene, …) — or the compositor’s damage flush — presents scene+cursor in a SINGLE operation. This is the flicker fix: there is no separate erase/redraw to the GOP, so there is never an intermediate frame showing the scene without the cursor (the failure mode at low present rates). It mirrors how software-cursor compositors composite the cursor as the last layer before one commit (wlroots, Qt’s QFbCursor).

For save-under (NULL scene) there is no buffer to fold into, so this falls back to restoring the screen pixels under the cursor; axl_cursor_drop then redraws it after the present.

Pair with axl_cursor_drop after the present. A lift/drop with no present in between is harmless. No-op if already lifted/hidden.

Parameters:
  • c – cursor

void axl_cursor_drop(AxlCursor *c)

Bracket-close: counterpart to axl_cursor_lift.

For a bound scene, unfolds the cursor — restores the saved scene pixels so the scene is byte-clean for the next partial-damage repaint. For save-under, re-composites the cursor over the freshly-presented screen. No-op if hidden.

Parameters:
  • c – cursor

AxlSourceId axl_cursor_attach(AxlCursor *c, AxlLoop *loop, AxlInputCallback cb, void *data)

Track the physical pointer on a loop — both relative and absolute.

Attaches the relative mouse (axl_input_attach_mouse) AND the absolute pointer (axl_input_attach_touch), moving the cursor on motion from either and showing it, then forwarding every event to cb so the consumer still does hit-testing / widget logic. The absolute source is authoritative for position once seen, so a remote-console / VNC / touch pointer tracks correctly (see AxlCursorConfig for the exact rule). Binds with the current process-global absolute-read settings — use axl_cursor_attach_ex to choose sources and set those settings in one call.

Note

Behavior change: this now ALSO binds the absolute pointer, claiming the single process-wide absolute-pointer slot. Two consequences for existing callers: (1) a single saved source ID no longer tears the cursor down — use axl_cursor_detach (it removes both sources); (2) do not combine with axl_compositor_attach_touch or a direct axl_input_attach_touch in the same process — whichever binds the absolute slot second gets 0. If another component owns the absolute pointer, attach via axl_cursor_attach_ex with cfg.skip_touch = true.

Parameters:
  • c – cursor

  • loop – event loop (caller-owned)

  • cb – consumer callback for forwarded events (may be NULL)

  • data – opaque data for cb

Returns:

a non-zero source ID on success (a source for axl_loop_remove_source — but prefer axl_cursor_detach, which tears down BOTH sources), or 0 if neither source could be bound.

AxlSourceId axl_cursor_attach_ex(AxlCursor *c, AxlLoop *loop, AxlInputCallback cb, void *data, const AxlCursorConfig *cfg)

Track the physical pointer on a loop, choosing which source(s) to bind and how to read the absolute one.

Like axl_cursor_attach, but cfg selects the relative and/or absolute source and configures the absolute read path (method / ConsoleIn-only / poll interval / drain). A NULL cfg behaves exactly like axl_cursor_attach (bind both, current global touch settings, none applied). With a non-NULL cfg the touch_* fields are applied PROCESS-GLOBALLY before the absolute source is bound — including fields left at default — so a config built only to select sources still resets the global touch tunables to its (default) touch_* values; set them deliberately. The same single-absolute-slot caveat as axl_cursor_attach applies (use skip_touch when another component owns the absolute pointer).

Parameters:
  • c – cursor

  • loop – event loop (caller-owned)

  • cb – consumer callback for forwarded events (may be NULL)

  • data – opaque data for cb

  • cfg – sources + absolute-read config (NULL = both, current settings)

Returns:

a non-zero source ID on success (prefer axl_cursor_detach to tear down), or 0 if no requested source could be bound.

void axl_cursor_detach(AxlCursor *c, AxlLoop *loop)

Stop tracking the pointer (counterpart to axl_cursor_attach / axl_cursor_attach_ex).

Detaches whichever of the relative and absolute sources this cursor bound.

Parameters:
  • c – cursor

  • loop – the loop passed to axl_cursor_attach

struct AxlCursorConfig
#include <axl-cursor.h>

Which pointer sources axl_cursor_attach_ex binds, and how it reads the absolute one.

Zero-initialize (AxlCursorConfig cfg = {0};) for the common case — bind BOTH pointers with library-default absolute-read settings — then override only what you need (e.g. cfg.touch_drain = 8;). The source flags are skip flags, and touch_all_handles is likewise an opt-OUT, precisely so the all-zero config matches the library defaults: bind both pointers, EVENT_AND_POLL, ConsoleIn-only, 30 ms poll, no coalesce.

The cursor tracks both pointer kinds when both are bound: the absolute pointer (EFI_ABSOLUTE_POINTER — a touchscreen, a digitizer, or a remote-console / BMC virtual mouse, and QEMU’s usb-tablet over VNC) reports a position directly. Once ANY absolute event arrives it is latched AUTHORITATIVE for cursor position for the lifetime of the attach: from then on relative-mouse motion no longer moves the cursor (it still reaches cb for button / wheel handling). Before the first absolute event — and forever on bare metal with no absolute device — the relative mouse drives position. Absolute coordinates are mapped onto the cursor’s scene extent (the bound back-buffer’s size, or the live GOP resolution in save-under / NULL-scene mode) — the same extent axl_cursor_move clamps to.

The touch_* fields configure the absolute source exactly as axl_input_set_touch_config / axl_input_set_touch_drain do. Passing this config to axl_cursor_attach_ex applies them PROCESS-GLOBALLY (overwriting any prior axl_input_set_touch_* state, even for fields you left at their default) before binding the touch source. They are the levers for the remote-console “catch-up lag”: some firmware queues absolute states FIFO and a one-read-per-poll drain trails a fast move, so raise touch_drain to coalesce the backlog to the latest position. See <axl/axl-input.h>.

Public Members

bool skip_mouse

do NOT bind the relative pointer (default 0: bind it)

bool skip_touch

do NOT bind the absolute pointer (default 0: bind it)

AxlInputTouchMethod touch_method

absolute read mechanism (0 = EVENT_AND_POLL)

bool touch_all_handles

bind ALL absolute handles, not just gST->ConsoleInHandle (default 0 = ConsoleIn-only; opt out only where the pointer is on a separate physical handle)

uint32_t touch_poll_ms

absolute poll-fallback interval in ms (0 = 30 ms default)

uint32_t touch_drain

coalesce up to N queued absolute states per read (0/1 = no coalesce)

AxlCompositor — <axl/axl-compositor.h>

Local, in-process compositor for axl-gfx (Phase C1 — skeleton). See docs/AXL-Compositor-Design.md.

A compositor owns a full-screen output buffer and a tree of surfaces (each a CPU-backed rectangle the client draws into). It composites the visible surfaces bottom-to-top — tree pre-order, a node then its children — into the output, and presents the changed region to the GOP. One synchronous in-process client, so a present is the atomicity barrier: the client mutates surfaces, then calls present.

Surfaces form a tree (the scene graph). A surface’s position is relative to its parent; top-level surfaces are children of the compositor’s root (axl_compositor_root). The tree is an AxlNTree under the hood, so stacking is its pre-order and hit-test (a later phase) its reverse.

C1 scope: opaque surfaces, create/move/show/hide/damage, composite + damaged present. Stacking is creation order among siblings. Later phases add raise/lower/reparent, opacity/blending, the input seat, and the cursor overlay.

AxlCompositor *c = axl_compositor_new(1280, 800);
AxlSurface *win = axl_surface_create(axl_compositor_root(c), 400, 300);
axl_surface_move(win, 100, 80);
axl_gfx_target_buffer(axl_surface_buffer(win));   // draw into it
axl_gfx_fill_rect(0, 0, 400, 300, AXL_GFX_RGB(0x20, 0x40, 0x80));
axl_gfx_target_buffer(NULL);
axl_surface_damage(win, (AxlGfxClip){0, 0, 400, 300});
axl_compositor_present(c);                          // composite + flush

Defines

AXL_COMPOSITOR_GRAB_MAX

Maximum nesting depth of the pointer-grab stack (a deep popup chain).

Typedefs

typedef struct AxlCompositor AxlCompositor

Opaque compositor (owns the output and the surface tree).

typedef struct AxlSurface AxlSurface

Opaque surface (a node in the compositor’s scene graph).

typedef struct AxlCursor AxlCursor

Software cursor overlay (see <axl/axl-cursor.h>); the compositor owns one and drives it from the seat pointer (C6).

typedef void (*AxlGrabDismissFunc)(void *user)

Called when a pointer grab is dismissed by a button press outside the grabbing surface’s subtree. The grab has already been popped when this runs, so the callback may safely re-grab / close popups.

typedef void (*AxlFrameCallback)(void *user, uint64_t time_ms)

One-shot frame callback (Wayland wl_surface.frame).

time_ms is the monotonic frame timestamp (the axl_time_get_ms epoch) — use the delta between successive callbacks to advance animation.

Functions

AxlCompositor *axl_compositor_new(uint32_t w, uint32_t h)

Create a compositor with a w × h output.

Parameters:
  • w – output width in pixels (> 0)

  • h – output height in pixels (> 0)

Returns:

the compositor, or NULL on allocation failure / zero dimensions.

void axl_compositor_free(AxlCompositor *c)

Destroy a compositor and every surface in its tree. NULL-safe.

Parameters:
  • c – compositor (NULL-safe)

AxlSurface *axl_compositor_root(AxlCompositor *c)

The root surface — the tree anchor at (0, 0). Create top-level surfaces as its children. It has no buffer and is never drawn.

Parameters:
  • c – compositor

Returns:

the root, or NULL if c is NULL.

AxlGfxBuffer *axl_compositor_output(AxlCompositor *c)

The output buffer — the composited output. Read its pixels to inspect what would be presented. Owned by the compositor.

Parameters:
  • c – compositor

Returns:

the output buffer, or NULL if c is NULL.

void axl_compositor_composite(AxlCompositor *c)

Recomposite every visible surface into the output (in tree pre-order). Does not touch the screen — use this to inspect the composited result, or before reading axl_compositor_output.

Parameters:
  • c – compositor

int axl_compositor_present(AxlCompositor *c)

Composite and present the damaged region to the GOP.

Recomposites the output and flushes the accumulated damage REGION — each disjoint changed rectangle since the last present, not their spanning bounding box — then clears the damage. Sparse changes far apart on screen (a caret blink and a clock, say) flush as small separate rects instead of one large box. See axl_compositor_get_damage_region.

“Present” here is the Vulkan / DXGI / X-Present sense — composite the frame and put it on screen — NOT Wayland’s wl_surface.commit (which only applies pending state; the compositor repaints separately). With one synchronous in-process client there is no pending/current double-buffering, so this single call is both Wayland’s commit (the atomicity barrier — the client mutates surfaces, then presents one coherent frame) and the compositor’s repaint.

The damage is consumed (cleared) and the output updated even when AXL_ERR is returned because the GOP is unavailable (a headless run): the output is current regardless; the return reports only whether the flush reached the screen.

Parameters:
  • c – compositor

Returns:

AXL_OK on success (including a no-op when nothing is damaged), AXL_ERR if c is NULL or the GOP is unavailable.

int axl_compositor_get_damage(const AxlCompositor *c, AxlGfxClip *out)

Get the pending damage bbox (the union of surface changes since the last present), in output coordinates.

Parameters:
  • c – compositor

  • out – [out] damage bbox (clamped to the output)

Returns:

AXL_OK and fills out if damage is pending; AXL_ERR (and leaves out untouched) if nothing is damaged or c / out is NULL.

const AxlGfxRegion *axl_compositor_get_damage_region(const AxlCompositor *c)

Get the pending damage as a precise REGION (the exact set of changed rectangles since the last present), in output coordinates.

Unlike axl_compositor_get_damage (which returns the bounding box), this is the exact disjoint-rectangle set that present flushes. Borrowed — owned by the compositor, valid until the next surface change or present; do not free or retain it.

Parameters:
  • c – compositor

Returns:

the damage region (empty when nothing is pending), or NULL if c is NULL.

uint32_t axl_compositor_composited_count(const AxlCompositor *c)

Number of surfaces actually composited (blitted) in the most recent composite/present — i.e. after occlusion culling.

For perf introspection and verifying opaque hints take effect.

Parameters:
  • c – compositor

Returns:

the count, or 0 if c is NULL / nothing has been composited.

uint32_t axl_compositor_occlusion_passes(const AxlCompositor *c)

Number of occlusion passes (draw-order + visible-region builds) performed in the most recent composite/present.

Occlusion is computed ONCE per present and reused for every damage rect (the visible regions are a global property, independent of which sub-rect is repainted), so a present that flushes N disjoint damage rects still reports 1 — not N. A full axl_compositor_composite is likewise 1. An allocation failure that forces the full-paint fallback still counts as the 1 attempted pass. For perf introspection (the once-per-present hoist, §10).

Parameters:
  • c – compositor

Returns:

the count, or 0 if c is NULL / nothing has been composited.

AxlSurface *axl_surface_create(AxlSurface *parent, uint32_t w, uint32_t h)

Create a w × h surface as the last child of parent.

Top-level surfaces pass axl_compositor_root as parent. The surface starts at (0, 0) relative to the parent, visible, with its own cleared back-buffer (draw into it via axl_surface_buffer). Re-hit-tests under the pointer — a surface created over a stationary pointer gets a synthetic enter.

Parameters:
  • parent – parent surface (compositor root for top-level)

  • w – width in pixels (> 0)

  • h – height in pixels (> 0)

Returns:

the surface, or NULL on allocation failure / NULL parent / zero dimensions.

void axl_surface_destroy(AxlSurface *s)

Destroy a surface and its entire subtree (children die with it). NULL-safe. Marks the vacated screen region damaged.

Must not be the compositor root. Drops the subtree from the seat’s pointer focus, keyboard focus, and grab stack, then re-hit-tests under the pointer — a surface beneath the destroyed one receives a synthetic enter.

Parameters:
  • s – surface (NULL-safe)

AxlGfxBuffer *axl_surface_buffer(AxlSurface *s)

The surface’s back-buffer — its draw target. Owned by the surface.

Parameters:
  • s – surface

Returns:

the buffer, or NULL if s is NULL / the root (which has none).

void axl_surface_move(AxlSurface *s, int32_t x, int32_t y)

Move the surface to (x, y) relative to its parent. Marks both the old and new screen regions damaged.

Re-hit-tests under the pointer: if the surface under a stationary pointer changed, the seat emits synthetic leave / enter (no motion).

Parameters:
  • s – surface

  • x – x relative to parent origin

  • y – y relative to parent origin

void axl_surface_resize(AxlSurface *s, uint32_t w, uint32_t h)

Resize the surface’s back-buffer to w × h (content-driven popups/dialogs, a top-level on a GOP mode change).

Pixels in the overlapping top-left region are preserved; any newly exposed area is cleared (transparent). Damages the union of the old and new screen bounds and re-hit-tests under the pointer. No-op on the root, on zero dimensions, on an unchanged size, or if the new buffer can’t be allocated (the surface keeps its old buffer). The draw target obtained from axl_surface_buffer is invalidated — re-fetch it after a resize.

Parameters:
  • s – surface (not the root)

  • w – new width in pixels (> 0)

  • h – new height in pixels (> 0)

void axl_surface_raise(AxlSurface *s)

Raise the surface to the top of its siblings.

It (and its whole subtree) paints last among its siblings, so it sits on top of them — Wayland place_above against the topmost sibling. No-op for the root or an only child. Marks the surface’s screen region damaged, and re-hit-tests under the pointer (synthetic leave / enter if the surface under a stationary pointer changed).

Parameters:
  • s – surface

void axl_surface_lower(AxlSurface *s)

Lower the surface to the bottom of its siblings (the inverse of axl_surface_raise).

Re-hit-tests under the pointer (synthetic leave / enter if the surface under a stationary pointer changed).

Parameters:
  • s – surface

void axl_surface_set_parent(AxlSurface *s, AxlSurface *new_parent)

Reparent the surface (and its subtree) under new_parent, as its top child.

The surface’s (x, y) numbers are unchanged but now relative to new_parent, so its absolute position moves; both the old and new screen regions are damaged. No-op if new_parent belongs to a different compositor, if s is the root, or if the move would create a cycle (new_parent is s or one of its descendants). Re-hit-tests under the pointer (synthetic leave / enter if the surface under a stationary pointer changed).

Parameters:
  • s – surface to reparent

  • new_parent – new parent (same compositor)

void axl_surface_set_visible(AxlSurface *s, bool visible)

Show or hide the surface (and, visually, its subtree). Marks the affected screen region damaged.

Re-hit-tests under the pointer: hiding a surface beneath the pointer (or revealing one) emits synthetic leave / enter (no motion).

Parameters:
  • s – surface

  • visible – true to show, false to hide

void axl_surface_set_opacity(AxlSurface *s, uint8_t opacity)

Set the surface’s constant opacity (255 = opaque, 0 = fully transparent). Default 255.

At 255 the surface is composited as an opaque rectangle (a fast copy). Below 255 it is alpha-blended over whatever is beneath it — the translucent veil / dim case — gamma-correct per the global axl_gfx_set_gamma_correct setting; the effective per-pixel alpha is the buffer’s alpha scaled by this opacity. Marks the surface region damaged.

Parameters:
  • s – surface

  • opacity – 0 (transparent) .. 255 (opaque)

void axl_surface_set_opaque(AxlSurface *s, bool opaque)

Mark the surface opaque (or not) — an occlusion hint.

A surface flagged opaque that fully covers the output lets the compositor skip compositing everything beneath it (see §7). Default is not-opaque (no culling). This is purely an optimization hint in the current phase — it does not change the composited pixels.

Parameters:
  • s – surface

  • opaque – true if the surface fully covers its rect opaquely

void axl_surface_set_per_pixel_alpha(AxlSurface *s, bool on)

Honor the buffer’s OWN per-pixel alpha at full (255) opacity.

Normally a surface at opacity 255 is composited as a straight copy (per-pixel buffer alpha ignored — the fast opaque path), and only opacity < 255 alpha-blends. Setting this makes a 255-opacity surface instead source-over its buffer’s own alpha channel: an OPAQUE body (alpha 255 pixels), soft ANTI-ALIASED / shadow edges (partial alpha), and fully transparent gaps (alpha 0 — what’s beneath shows through), all in one surface, with NO whole-surface dimming. This is the popup / dropdown / tooltip case (an opaque panel plus a soft drop shadow) and the dialog veil. Default off.

Such a surface never occludes what is behind it (its gaps are see-through), so it is excluded from occlusion culling regardless of the opaque hint. Combine with opacity < 255 to additionally fade the whole surface (the per-pixel alpha is then scaled by the opacity). Marks the surface damaged.

Parameters:
  • s – surface

  • on – true to blend the buffer’s own alpha at full opacity

void axl_surface_set_backdrop_blur(AxlSurface *s, uint32_t radius)

Blur the already-composited backdrop under this surface at composite time (the dialog veil / frosted panel). radius 0 = off (default).

When this surface is composited, the region of the output it covers — which, because compositing is back-to-front, already holds everything beneath it — is blurred in place by radius pixels (the axl-gfx stack blur) BEFORE this surface is blitted on top. Combine with opacity < 255 or axl_surface_set_per_pixel_alpha so the frosted backdrop shows through the translucent tint: a dialog veil with NO back-buffer readback in the consumer. A fully-opaque straight-copy surface (opacity 255 + no per-pixel alpha) would overwrite the frosted backdrop entirely, so backdrop blur is only meaningful on a translucent surface. A backdrop-blur surface is excluded from occlusion culling (it is a translucent overlay). Marks the surface’s subtree damaged.

A present recomposites such a surface over its FULL rect (not a damage sub-rect), so the blur always clamps at the surface’s own edges — for a near-fullscreen veil that is the screen edge, so it is invisible. On a scratch-buffer allocation failure the blur is skipped (the surface is simply un-frosted) — never fatal.

Parameters:
  • s – surface

  • radius – backdrop blur radius in pixels (0 = off)

bool axl_surface_visible(const AxlSurface *s)

Whether the surface is shown.

Parameters:
  • s – surface (NULL-safe)

Returns:

true if visible (NULL-safe; NULL → false).

void axl_surface_damage(AxlSurface *s, AxlGfxClip rect)

Mark a surface-local rectangle dirty, so the next present recomposites and flushes it.

Parameters:
  • s – surface

  • rect – dirty rect in surface-local coordinates

void axl_surface_get_geometry(const AxlSurface *s, int32_t *x, int32_t *y, uint32_t *w, uint32_t *h)

Get the surface geometry: position relative to its parent and size. Any out-param may be NULL. NULL-safe.

Parameters:
  • s – surface (NULL-safe)

  • x – [out] x relative to parent (may be NULL)

  • y – [out] y relative to parent (may be NULL)

  • w – [out] width (may be NULL)

  • h – [out] height (may be NULL)

void axl_surface_get_absolute(const AxlSurface *s, int32_t *x, int32_t *y)

Get the surface origin in output (absolute) coordinates — the sum of the (x, y) offsets up the parent chain.

Saves a toolkit walking the parent chain itself when placing a submenu / tooltip or doing hit math. The root is at (0, 0). Out-params optional; NULL-safe.

Parameters:
  • s – surface (NULL-safe)

  • x – [out] absolute x (may be NULL)

  • y – [out] absolute y (may be NULL)

void axl_surface_to_output(const AxlSurface *s, int32_t lx, int32_t ly, int32_t *ox, int32_t *oy)

Map a surface-local point (lx, ly) to output coordinates. Out-params optional; NULL-safe.

Parameters:
  • s – surface (NULL-safe)

  • lx – surface-local x

  • ly – surface-local y

  • ox – [out] output x (may be NULL)

  • oy – [out] output y (may be NULL)

void axl_surface_from_output(const AxlSurface *s, int32_t ox, int32_t oy, int32_t *lx, int32_t *ly)

Map an output point (ox, oy) to this surface’s local coordinates (the inverse of axl_surface_to_output). Out-params optional; NULL-safe.

Parameters:
  • s – surface (NULL-safe)

  • ox – output x

  • oy – output y

  • lx – [out] surface-local x (may be NULL)

  • ly – [out] surface-local y (may be NULL)

void axl_surface_set_listener(AxlSurface *s, const AxlSurfaceListener *listener, void *user)

Install the surface’s input listener (copied by value) and its user pointer. Pass listener == NULL to clear it.

The struct contents are copied, so a transient (stack) listener is fine; user is borrowed and must outlive the surface’s event delivery.

Parameters:
  • s – surface

  • listener – listener (copied; NULL clears)

  • user – borrowed context passed to callbacks

void axl_surface_set_input_region(AxlSurface *s, const AxlGfxClip *clips, size_t n)

Set the surface’s input region — the part that catches pointer events; the rest is input-transparent (events pass through to whatever is beneath).

Wayland set_input_region semantics:

  • clips == NULL → the full surface rect catches input (the default).

  • clips != NULL, n == 0 → the surface is fully input-transparent (every event passes through — the dim-veil pass-through case).

  • otherwise → the union of the n surface-local rects is input-opaque.

The rects are copied (the caller’s array need not persist). The input region is independent of opacity and the opaque hint — a fully transparent (opacity 0) surface still catches input unless its region says otherwise.

Parameters:
  • s – surface

  • clips – surface-local input rects (NULL = full rect)

  • n – number of rects (0 with non-NULL = transparent)

void axl_compositor_pointer_event(AxlCompositor *c, const AxlInputEvent *ev)

Feed one raw pointer event into the seat, which hit-tests the surface tree topmost-first and dispatches to the surface under the pointer (surface-local) via its listener.

ev->x / ev->y are interpreted as output (absolute) coordinates and clamped to the output. Handles AXL_INPUT_MOUSE_MOVE / _BUTTON_DOWN / _BUTTON_UP / _WHEEL; other event types are ignored (key routing is C5). Crossing a surface boundary emits leave on the old surface and enter on the new one. This is the testable routing core; axl_compositor_attach_pointer drives it from a real device.

Parameters:
  • c – compositor

  • ev – pointer event (output coordinates)

AxlSourceId axl_compositor_attach_pointer(AxlCompositor *c, AxlLoop *loop)

Attach a mouse device on loop as the seat’s pointer source.

Registers EFI_SIMPLE_POINTER_PROTOCOL via axl_input_attach_mouse; each device event is converted from accumulated relative motion to a clamped output position (seeded at the output centre) and fed to axl_compositor_pointer_event. The loop is owned by the caller (the seat only adds a source).

Only one mouse source exists per process, so do not combine this with axl_cursor_attach in the same process (C6 unifies the cursor overlay with the seat). NULL-safe.

Parameters:
  • c – compositor

  • loop – caller-owned event loop

Returns:

the axl-loop source ID (for axl_loop_remove_source), or 0 on failure (NULL args, no pointer protocol, or a mouse already attached).

void axl_compositor_detach_pointer(AxlCompositor *c, AxlLoop *loop)

Detach the seat’s pointer source from loop (inverse of axl_compositor_attach_pointer). NULL-safe and idempotent.

Parameters:
  • c – compositor

  • loop – the loop the pointer was attached to

AxlSourceId axl_compositor_attach_touch(AxlCompositor *c, AxlLoop *loop)

Drive the seat from an ABSOLUTE pointer (touch / digitizer / BMC remote-console virtual mouse) via axl_input_attach_touch.

The absolute source reports a normalized position (no relative deltas); this scales it onto the output and routes it as pointer motion / buttons, so a remote-console mouse or touchscreen drives the same cursor + hit-testing as a physical mouse. Complementary to axl_compositor_attach_pointer — attach both; whichever the firmware feeds drives the cursor. NULL-safe.

Parameters:
  • c – compositor

  • loop – caller-owned event loop

Returns:

the axl-loop source ID, or 0 on failure (NULL args, no absolute pointer protocol, or a touch source already attached).

void axl_compositor_detach_touch(AxlCompositor *c, AxlLoop *loop)

Detach the seat’s touch source from loop (inverse of axl_compositor_attach_touch). NULL-safe and idempotent.

Parameters:
  • c – compositor

  • loop – the loop the touch source was attached to

void axl_compositor_pointer_grab(AxlCompositor *c, AxlSurface *s, AxlGrabDismissFunc on_dismiss, void *user)

Push an EXCLUSIVE pointer grab for s (the top of the LIFO grab stack) — the modal case.

While s's grab is active, pointer focus is confined strictly to s's subtree; everything below it on the stack is suspended (a modal dialog over a menu blocks the menu). A button press outside s's subtree pops this grab and calls on_dismiss. No-op if s is NULL / the root / a different compositor, or if the grab stack is full (AXL_COMPOSITOR_GRAB_MAX deep).

For a popup CHAIN (a menu and its submenus, where the whole open chain stays interactive) use axl_compositor_pointer_grab_chain instead.

Parameters:
  • c – compositor

  • s – surface to grab the pointer

  • on_dismiss – dismissed-by-outside-click callback (may be NULL)

  • user – borrowed context for on_dismiss

void axl_compositor_pointer_grab_chain(AxlCompositor *c, AxlSurface *s, AxlGrabDismissFunc on_dismiss, void *user)

Push a CHAIN pointer grab for s — a popup chain (menu bar → menu → submenu, each a sibling surface).

Like axl_compositor_pointer_grab, but the confinement EXTENDS the grab below it instead of replacing it: the active region is the union of s's subtree and the contiguous chain grabs beneath it on the stack, stopping at (and including) the first EXCLUSIVE grab. So hover and clicks route freely across the whole open chain — no manual cross-surface routing in the toolkit — while a press outside ALL of it pops the top grab and calls on_dismiss (the toolkit then closes the chain). A later exclusive grab (a modal opened over the chain) still blocks it. A lone chain grab confines to its own subtree, exactly like an exclusive one.

Same no-op conditions as axl_compositor_pointer_grab.

Parameters:
  • c – compositor

  • s – surface to grab the pointer (chain member)

  • on_dismiss – dismissed-by-outside-click callback (may be NULL)

  • user – borrowed context for on_dismiss

void axl_compositor_pointer_ungrab(AxlCompositor *c)

Pop the top pointer grab (inverse of axl_compositor_pointer_grab). No-op if no grab is active. Re-hit-tests under the pointer.

Parameters:
  • c – compositor

void axl_compositor_set_keyboard_focus(AxlCompositor *c, AxlSurface *s)

Set the surface that receives keyboard events (NULL = none).

Emits focus_out on the previously focused surface and focus_in on s via their listeners. No-op if s belongs to a different compositor.

Parameters:
  • c – compositor

  • s – surface to focus (NULL clears focus)

AxlSurface *axl_compositor_keyboard_focus(const AxlCompositor *c)

The surface with keyboard focus, or NULL if none.

Parameters:
  • c – compositor (NULL-safe)

Returns:

the focused surface (NULL-safe; NULL → NULL).

void axl_compositor_key_event(AxlCompositor *c, const AxlInputEvent *ev)

Route a key event to the keyboard-focus surface’s key listener.

Handles AXL_INPUT_KEY_DOWN / _KEY_UP; other event types are ignored. The raw ev is forwarded unchanged (the firmware owns keymap and repeat). No-op if no surface has focus. This is the testable routing core; axl_compositor_attach_keyboard drives it from a real keyboard.

Parameters:
  • c – compositor

  • ev – key event

AxlSourceId axl_compositor_attach_keyboard(AxlCompositor *c, AxlLoop *loop)

Attach a keyboard device on loop as the seat’s key source.

Registers the keyboard via axl_input_attach_key; each event is routed to the keyboard-focus surface through axl_compositor_key_event. The loop is owned by the caller. Only one keyboard source exists per process. NULL-safe.

Parameters:
  • c – compositor

  • loop – caller-owned event loop

Returns:

the axl-loop source ID (for axl_loop_remove_source), or 0 on failure (NULL args or a keyboard already attached).

void axl_compositor_detach_keyboard(AxlCompositor *c, AxlLoop *loop)

Detach the seat’s keyboard source from loop (inverse of axl_compositor_attach_keyboard). NULL-safe and idempotent.

Parameters:
  • c – compositor

  • loop – the loop the keyboard was attached to

AxlCursor *axl_compositor_cursor(AxlCompositor *c)

The compositor’s cursor overlay (bound to the output, driven from the seat pointer). Owned by the compositor.

Exposed for direct show/hide/position queries; most consumers only set the shape via axl_compositor_set_cursor_image and let the seat drive position.

Parameters:
  • c – compositor (NULL-safe)

Returns:

the cursor, or NULL (NULL c, or no cursor was created).

int axl_compositor_set_cursor_image(AxlCompositor *c, const AxlGfxBuffer *sprite, int32_t hot_x, int32_t hot_y)

Set the pointer’s cursor image and hotspot — the per-surface shape (§2.4). Forwards to axl_cursor_set_image on the compositor’s cursor.

The seat resets to the built-in arrow on every pointer-focus change, so a surface chooses its shape by calling this from its listener enter callback (an I-beam over a text field, a resize arrow over a border). Pass a NULL sprite for the built-in arrow.

Parameters:
  • c – compositor

  • sprite – RGBA sprite (copied); NULL = built-in arrow

  • hot_x – hotspot x within the sprite

  • hot_y – hotspot y within the sprite

Returns:

AXL_OK on success, AXL_ERR on bad arguments / no cursor / allocation failure.

void axl_surface_request_frame(AxlSurface *s, AxlFrameCallback cb, void *user)

Request a one-shot frame callback for s (Wayland wl_surface.frame).

Fired once at the next frame tick, then cleared; re-request inside the callback to keep animating (that is the throttle — one redraw per tick). A second request before the tick replaces the pending callback (latest wins); passing cb == NULL cancels a pending request. Allowed on any surface including the compositor root (a screen-wide transition holder). Destroying the surface cancels its pending callback (it is never fired with a stale user).

The callback is delivered by axl_compositor_dispatch_frame — either the attached frame clock (axl_compositor_attach_frame_clock) or a direct call. Requesting with no frame clock attached simply leaves the callback pending until something dispatches.

Parameters:
  • s – surface (NULL-safe)

  • cb – callback (NULL cancels a pending request)

  • user – borrowed context passed to cb

bool axl_compositor_dispatch_frame(AxlCompositor *c, uint64_t time_ms)

Fire every frame callback pending at entry with time_ms, clearing them — the testable routing core (drive it from any clock).

Snapshots the callbacks pending at entry, clears them, then fires each, so a re-request made inside a callback queues for the NEXT dispatch, not this one (matching Wayland). An allocation failure skips the frame (callbacks stay pending for the next dispatch) rather than dropping them.

Precondition: a callback may mutate surface content, mark damage, present, and re-request frames, but must not create or destroy surfaces — defer teardown past the present, exactly as a Wayland client never frees a surface inside its own frame handler.

Parameters:
  • c – compositor

  • time_ms – monotonic frame time passed to each callback

Returns:

true if any callback is pending after this dispatch (i.e. a callback re-requested, or an allocation failure left some unfired) — the frame clock uses this to decide whether to keep ticking. NULL-safe (→ false).

bool axl_compositor_has_pending_frames(const AxlCompositor *c)

Whether any surface has a pending frame callback.

Parameters:
  • c – compositor (NULL-safe)

Returns:

true if a frame is pending (NULL-safe; NULL → false).

AxlSourceId axl_compositor_attach_frame_clock(AxlCompositor *c, AxlLoop *loop, uint32_t interval_ms)

Attach the compositor’s frame clock to loop — the present throttle.

Adds a self-cancelling repeating timer on loop: each tick calls axl_compositor_dispatch_frame with the current axl_time_get_ms, and the timer removes itself once no callbacks remain pending (idle = no wakeups). A later axl_surface_request_frame re-arms it. interval_ms is the target frame period (0 → a ~60 fps default). The loop is caller-owned (the clock only adds a source).

The returned value is a non-zero loop source id on success, but the clock manages its own timer lifecycle (lazy re-arm, self-cancel), so stop it via axl_compositor_detach_frame_clock — do not pass the id to axl_loop_remove_source yourself. Detach (or free the compositor) before the loop, so no tick fires into freed state.

Parameters:
  • c – compositor

  • loop – caller-owned event loop

  • interval_ms – target frame period in ms (0 = ~60 fps)

Returns:

a non-zero source id on success, or 0 on failure (NULL args / the timer could not be added).

void axl_compositor_detach_frame_clock(AxlCompositor *c, AxlLoop *loop)

Detach the frame clock from loop (inverse of axl_compositor_attach_frame_clock). NULL-safe and idempotent.

Parameters:
  • c – compositor

  • loop – the loop the frame clock was attached to

struct AxlSurfaceListener
#include <axl-compositor.h>

Per-surface input listener (the C→C++-virtual seam, §2.3).

Every callback is optional (a NULL field is simply not called). All coordinates are surface-local (relative to the surface origin).

C4 delivers the pointer callbacks: enter, leave, motion, button, axis. The key / focus_in / focus_out callbacks are the keyboard-focus seam wired in C5 — declared now so the listener (the expensive-to-change contract a toolkit bridges once) is stable.

Public Members

void (*enter)(void *user, int32_t x, int32_t y)

Pointer entered the surface, now at surface-local (x, y).

void (*leave)(void *user)

Pointer left the surface.

void (*motion)(void *user, int32_t x, int32_t y, uint32_t modifiers)

Pointer moved to surface-local (x, y). modifiers is the live AXL_INPUT_MOD_* state from the originating event.

void (*button)(void *user, uint32_t button, bool pressed, int32_t x, int32_t y, uint32_t modifiers, uint32_t click_count, bool dragging)

A button changed: button is the AXL_INPUT_BUTTON_* bit that transitioned, pressed its new state, at surface-local (x, y). modifiers is the live AXL_INPUT_MOD_* state; click_count is the gesture recognizer’s 1/2/3 single/double/triple-click count (0 if not a click); dragging is true once the held-button press has crossed the drag threshold (for drag-select). If several buttons transition in one event the callback fires once per changed bit, each carrying the same per-event click_count / dragging.

void (*axis)(void *user, int32_t dx, int32_t dy, uint32_t modifiers)

Scroll/axis motion by (dx, dy) notch ticks. modifiers is the live AXL_INPUT_MOD_* state (e.g. Shift+wheel for horizontal scroll).

void (*key)(void *user, const AxlInputEvent *ev)

Key event routed to the keyboard-focus surface (C5).

void (*focus_in)(void *user)

Surface gained keyboard focus (C5).

void (*focus_out)(void *user)

Surface lost keyboard focus (C5).

AxlGfxRegion — <axl/axl-gfx-region.h>

Typedefs

typedef struct AxlGfxRegion AxlGfxRegion

axl-gfx-region.h:

AxlGfxRegion — an EXACT set of non-overlapping rectangles with set algebra (union / subtract / intersect), the role pixman plays in a real Wayland stack. The substrate for damage tracking (a frame’s changed area as disjoint rects, not one spanning bounding box), opaque-region occlusion culling, and multi-rect input regions.

Representation: rectangles are kept in a canonical form — non-overlapping, organised into y-sorted horizontal bands (the X11 miregion model). The three set operations are a band sweep: cut the y-axis at every band edge of both operands, combine the two x-span sets in each strip, then coalesce vertically-adjacent identical bands. (A future optimization can replace the sweep with the O(n) band merge-walk if rect counts ever warrant it; results are identical either way.) The canonical form is unique and deterministic for a given covered pixel set: any two regions covering the same pixels yield the same rectangle sequence (which is what makes axl_gfx_region_equal a direct compare and axl_gfx_region_get_rect’s order well-defined). Backing storage is an AxlArray that persists across operations (axl_gfx_region_clear resets the count but keeps the allocation), so a region reused each frame does no steady-state allocation.

Coordinates: a rect (x, y, w, h) covers the HALF-OPEN pixel range [x, x+w) × [y, y+h) — the right and bottom edges are exclusive. So a point at (x+w, y) is NOT contained, and two rects that share only an edge do not overlap (but DO coalesce in a union, being abutting).

OOM contract: a region can always be left in a valid state. If a set operation cannot allocate the rectangles its exact result needs, the region degrades to its bounding box — a conservative SUPERSET of the true result — sets the lossy flag, and the operation returns AXL_ERR. The region stays usable: a damage consumer simply over-paints; an occlusion consumer MUST check axl_gfx_region_is_lossy and skip culling with a lossy operand (a superset opaque area would over-cull). This is the only source of imprecision — there is no routine rectangle cap.

A mutator returns AXL_ERR for exactly two reasons, distinguished by the lossy flag: an OOM degrade (the region is valid — is_lossy is true — just proceed conservatively) versus a NULL/invalid argument (a caller bug, NO state change — treat it as a precondition violation, not a runtime branch). Because a NULL arg makes no state change, it never sets lossy; so on AXL_ERR, is_lossy true means “degraded but valid” and false means “bad argument” — UNLESS the region was already lossy from an earlier op. Callers that care about that distinction should reject NULL arguments up front rather than rely on the flag across calls.

Not thread-safe; a region is a single-owner value (UEFI is single threaded). Rectangles use AxlGfxClip (x, y, w, h); an empty rect (w == 0 || h == 0) contributes nothing. An exact, non-overlapping set of rectangles. Opaque — created with axl_gfx_region_new, released with axl_gfx_region_free.

Functions

AxlGfxRegion *axl_gfx_region_new(void)

Create a new, empty region.

Returns:

new region, or NULL on allocation failure. Free with axl_gfx_region_free().

void axl_gfx_region_free(AxlGfxRegion *r)

Free a region and its backing storage. NULL-safe.

Parameters:
  • r – region (NULL-safe)

void axl_gfx_region_clear(AxlGfxRegion *r)

Reset a region to empty, KEEPING its backing allocation.

The high-water-mark capacity is retained so a region reused each frame does no further allocation. Also clears the lossy flag.

Parameters:
  • r – region to empty

int axl_gfx_region_copy(AxlGfxRegion *dst, const AxlGfxRegion *src)

Replace dst's contents with a copy of src.

Parameters:
  • dst – destination (overwritten)

  • src – source

Returns:

AXL_OK on success; AXL_ERR on a NULL argument, or on allocation failure (in which case dst degrades to src's bounding box and is flagged lossy — see the OOM contract).

int axl_gfx_region_union(AxlGfxRegion *r, const AxlGfxRegion *other)

Union other into r (r := rother).

Parameters:
  • r – region, modified in place

  • other – region to add

Returns:

AXL_OK, or AXL_ERR (see the set-algebra note above).

int axl_gfx_region_subtract(AxlGfxRegion *r, const AxlGfxRegion *other)

Subtract other from r (r := rother).

Parameters:
  • r – region, modified in place

  • other – region to remove

Returns:

AXL_OK, or AXL_ERR (see the set-algebra note above).

int axl_gfx_region_intersect(AxlGfxRegion *r, const AxlGfxRegion *other)

Intersect r with other (r := rother).

Parameters:
  • r – region, modified in place

  • other – region to intersect with

Returns:

AXL_OK, or AXL_ERR (see the set-algebra note above).

int axl_gfx_region_union_rect(AxlGfxRegion *r, AxlGfxClip rect)

Union a single rectangle into r (the damage-accumulate path).

Convenience over axl_gfx_region_union with a one-rect operand. An empty rect (w == 0 || h == 0) is a no-op.

Parameters:
  • r – region, modified in place

  • rect – rectangle to add

Returns:

AXL_OK, or AXL_ERR (see the set-algebra note above).

int axl_gfx_region_subtract_rect(AxlGfxRegion *r, AxlGfxClip rect)

Subtract a single rectangle from r (the occlusion path).

Convenience over axl_gfx_region_subtract with a one-rect operand.

Parameters:
  • r – region, modified in place

  • rect – rectangle to remove

Returns:

AXL_OK, or AXL_ERR (see the set-algebra note above).

int axl_gfx_region_intersect_rect(AxlGfxRegion *r, AxlGfxClip rect)

Clip r to a single rectangle (r := rrect).

Convenience over axl_gfx_region_intersect with a one-rect operand — e.g. clamping accumulated damage to the output bounds.

Parameters:
  • r – region, modified in place

  • rect – rectangle to clip to

Returns:

AXL_OK, or AXL_ERR (see the set-algebra note above).

void axl_gfx_region_translate(AxlGfxRegion *r, int32_t dx, int32_t dy)

Translate every rectangle in r by (dx, dy).

Cannot fail or allocate (offsets in place); preserves the lossy flag. No overflow check — keep offsets within int32 pixel range (the caller’s responsibility; never an issue for real output coordinates).

Parameters:
  • r – region, modified in place

  • dx – horizontal offset in pixels

  • dy – vertical offset in pixels

bool axl_gfx_region_is_empty(const AxlGfxRegion *r)

Is the region empty (covers no pixels)?

Parameters:
  • r – region

Returns:

true if empty or r is NULL.

bool axl_gfx_region_is_lossy(const AxlGfxRegion *r)

Did a prior operation degrade this region to its bounding box?

Set by an OOM degrade (see the OOM contract) and stays set until axl_gfx_region_clear. A lossy region is a conservative SUPERSET of the exact result — safe to PAINT from, NOT safe to SUBTRACT as an opaque operand (an occlusion consumer must skip culling when this is true).

Parameters:
  • r – region

Returns:

true if the region is a lossy bounding-box superset.

AxlGfxClip axl_gfx_region_bounds(const AxlGfxRegion *r)

Bounding box of the whole region.

The zero rect {0,0,0,0} is the canonical empty rectangle (w == 0), so an empty or NULL region and a (degenerate) region at the origin are not distinguishable from bounds alone — use axl_gfx_region_is_empty to test emptiness.

Parameters:
  • r – region

Returns:

the enclosing rectangle, or {0,0,0,0} if empty / NULL.

bool axl_gfx_region_contains_point(const AxlGfxRegion *r, int32_t x, int32_t y)

Is the point (x, y) inside the region?

Parameters:
  • r – region

  • x – x in pixels

  • y – y in pixels

Returns:

true if covered by some rectangle in r.

bool axl_gfx_region_intersects_rect(const AxlGfxRegion *r, AxlGfxClip rect)

Does rect overlap any part of the region?

Parameters:
  • r – region

  • rect – rectangle to test

Returns:

true if rect intersects r (false for an empty rect).

bool axl_gfx_region_equal(const AxlGfxRegion *a, const AxlGfxRegion *b)

Do two regions cover exactly the same pixels?

Exact set equality (the unique canonical form makes this a direct rect compare). Useful to detect “damage unchanged since last frame”. All regions covering no pixels compare equal — an empty region, another empty region, and NULL are all equal to each other.

Parameters:
  • a – first region

  • b – second region

Returns:

true if a and b cover the same pixels.

size_t axl_gfx_region_num_rects(const AxlGfxRegion *r)

Number of canonical rectangles in the region.

Parameters:
  • r – region

Returns:

rect count (0 if empty / NULL).

AxlGfxClip axl_gfx_region_get_rect(const AxlGfxRegion *r, size_t i)

The i-th canonical rectangle (0-based, in band order).

Parameters:
  • r – region

  • i – index in [0, num_rects)

Returns:

the rectangle, or {0,0,0,0} if i is out of range.