Skip to content

LVGL Benchmark — Zig

Source: apps/zig/heap/lvgl_benchmark/src/main.zig (and apps/zig/zeroheap/lvgl_benchmark/).

The Zig port uses ove.lvgl for widget construction. Scene tables are comptime arrays of function pointers; metrics are stored in std.atomic.Value(u32).

Build

make host.posix.lvgl_benchmark_zig
make qemu.freertos.lvgl_benchmark_zig
make && make run

Scene builders

fn rectangleRoundedCreate() void {
    const scr = lvgl.Object.activeScreen();
    var obj = lvgl.Object.new(scr);
    obj.setSize(200, 200);
    obj.center();
    obj.style().backgroundColor(lvgl.Color.hex(0xff_00_00)).radius(20);
}

var obj = ... because the fluent style chain mutates obj.style() results. Zig's value semantics keep the wrapper a thin handle (a pointer plus a phantom for borrow checking).

Scene table

const Scene = struct {
    name: []const u8,
    create: *const fn () void,
    fps_avg: std.atomic.Value(u32),
};

var scenes = [_]Scene{
    .{ .name = "Empty screen",
       .create = emptyCreate,
       .fps_avg = std.atomic.Value(u32).init(0) },
    .{ .name = "Rectangle",
       .create = rectangleCreate,
       .fps_avg = std.atomic.Value(u32).init(0) },
    .{ .name = "Rectangle, rounded",
       .create = rectangleRoundedCreate,
       .fps_avg = std.atomic.Value(u32).init(0) },
    // …
};

comptime-known length so the array goes to .data directly; no allocator involved.

Locking

fn buildCard() void {
    var guard = ove.lvgl.lock();
    defer guard.deinit();

    var card = ove.lvgl.Object.new(parent);
    card.setSize(120, 80);
}

defer is Zig's RAII equivalent — guaranteed to run on scope exit, even when an error returns early.

Comptime feature detection

The port uses @hasDecl to gate code on backend selection, e.g. for image-scene fallbacks:

if (@hasDecl(ove.ffi, "CONFIG_OVE_LVGL_IMG_PNG_DECODER")) {
    // Use the PNG decoder
} else {
    // Fall back to pre-decoded RGB565 assets
}

This collapses at compile time — only one branch ships.

Where to next