Skip to content

jeffersonmourak/arara-renderer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

42 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Arara Logo Arara Renderer

Screen Recording Feb 27 2026 from Convert to GIF project (2)

Arara is a library for building graphics natively built with Zig. It provides a set of primitives for building rendering pipelines and a set of utilities for working with OpenGL.

Note:

This is a work in progress. It is not yet ready for production.

Dependencies

Arara currently targets native macOS builds in build.zig by default (x86_64-macos), and requires:

  • zig (0.15.1 or compatible)
  • glfw
  • harfbuzz
  • Xcode Command Line Tools (xcrun, macOS SDK, OpenGL framework)

Install on macOS with Homebrew:

brew install zig glfw harfbuzz
xcode-select --install

Verify tools are available:

zig version
pkg-config --modversion harfbuzz
brew --prefix glfw
xcrun --sdk macosx --show-sdk-path

Examples

Use the examples index as the entrypoint:

  • examples/README.md

Per-example guides:

  • C smoke example: examples/c/README.md
  • Zig balls example: examples/zig/balls/README.md
  • Zig Clay Joe layout demo, stdout (unstable): examples/zig/clay_joe_definition/README.md
  • Zig Clay Joe render demo, OpenGL window (unstable): examples/zig/clay_joe_render_demo/README.md

How BufferedRender works

  • The render thread (the caller of renderLoop) owns the GL context and replays draw commands through Renderer.
  • setThreadUpdate spawns a worker thread. Each tick the worker writes into the next Frame slot of an internal TripleBuffer(Frame) and publishes it.
  • The render thread pulls the latest published frame on each renderLoop iteration. Stale frames are skipped — the consumer always sees the most recent Frame.
  • Input crosses the thread boundary through SharedInput(Snapshot) (mutex-protected). The render thread calls update; the worker reads via snapshot().
  • The render loop walks the RenderStage machine (BEFORE_CLEAR → CLEAR → BEFORE_DRAW → DRAW → BEFORE_END → END). Register side effects (e.g., glfwSwapBuffers, FPS overlays) with the interceptBeforeStages / interceptBeforeClearStage / interceptClearStage / interceptBeforeDrawStage / interceptDrawStage / interceptBeforeEndStage / interceptEndStage hooks.

Example: setup + draw + render loop

This example shows the resource setup, BufferedRender initialization, threaded update registration, world/screen drawing, and the render loop.

const std = @import("std");
const arara = @import("arara");

const BufferedRender = arara.BufferedRender;
const Color = arara.Color;
const FontBook = arara.FontBook;
const Frame = arara.BufferedRenderFrame;
const OpenGLUtils = arara.OpenGLUtils;

const font_data = @embedFile("src/fonts/monofonto_rg.otf");

const InputSnapshot = struct {};
const SharedInput = arara.SharedInput(InputSnapshot);
const State = struct {};

// Produce the initial state for the update thread
fn produceInitialState(_: f32, _: f32) State {
    return .{};
}

// Compute frame logic and queue draw commands
fn update(
    frame: *Frame,
    _: *State,
    _: InputSnapshot,
    font_book: *FontBook,
    _: f32,
    _: f32,
) void {
    frame.clearCommands();

    frame.setBackground(Color.fromHsl(263.0 / 360.0, 0.4, 0.48));
    frame.setCameraPos(0.0, 0.0);
    frame.setZoom(1.0);

    // World-space commands (affected by camera and zoom)
    frame.drawWorldRect(80.0, 80.0, 160.0, 90.0, Color.fromHsl(40.0 / 360.0, 0.8, 0.55));
    frame.drawWorldArc(200.0, 150.0, 32.0, 0.0, std.math.pi * 2.0, Color.fromHsl(200.0 / 360.0, 0.7, 0.55));

    // Screen-space command (UI overlay)
    const font_id = font_book.getFontId("Monofonto") catch return;
    frame.drawScreenText(16.0, 16.0, font_id, 24.0, Color.White(), "Hello from BufferedRender", .{});
}

// Main function
pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // Setup font book
    var font_book = FontBook.init(allocator);
    defer font_book.deinit();
    _ = font_book.addFontData(font_data, "Monofonto");

    // Setup window
    const width: f32 = 800;
    const height: f32 = 600;
    const window = try OpenGLUtils.Window.create(width, height);
    defer OpenGLUtils.Window.deinit(window);

    // Setup shared input
    var shared_input: SharedInput = .{};

    // Setup buffered render
    var buffered_render: BufferedRender(InputSnapshot, State) = .init(
        allocator,
        .{ .width = width, .height = height, .font_book = font_book },
    );
    defer buffered_render.deinit();

    // Register the update thread
    buffered_render.setThreadUpdate(produceInitialState, update, &shared_input);


    // Render loop
    while (OpenGLUtils.Window.requestNextFrame(window)) {
        // Update the shared input
        shared_input.update(.{});
        // Render the frame
        buffered_render.renderLoop();
    }
}

C Integration

Build C-consumable artifacts (both static and shared):

zig build c-install

Dependency paths are discovered automatically (Homebrew/Xcode on macOS).
To force custom locations, pass overrides such as:

zig build c-install -Dglfw_prefix=/custom/glfw/prefix -Dmacos_sdk_path=/custom/MacOSX.sdk

After building, artifacts are installed under zig-out/:

  • Header: zig-out/include/arara.h
  • Static library: zig-out/lib/libarara.a
  • Shared library (macOS): zig-out/lib/libarara.dylib

examples/c/smoke.c follows the threaded BufferedRender model:

  • C creates and owns the GLFW window/context and runs swap/poll.
  • C loads font bytes and passes them to Arara.
  • C explicitly controls the update thread with arara_buffered_render_start_thread / arara_buffered_render_stop_thread.
  • The C main loop pushes input and calls arara_buffered_render_render_loop.

For full standalone smoke build/run instructions, see:

  • examples/c/README.md

Testing And Benchmarks

  • Run tests: zig build test
  • Run native benchmarks: zig build bench

Benchmark output is JSON-lines so it can be archived and compared over time.

Engineering Docs

Architecture and migration docs are available under docs/:

  • docs/architecture.md
  • docs/dependency-boundaries.md
  • docs/benchmarking.md
  • docs/testing-strategy.md
  • docs/adr/ADR-0001-backend-interface.md

Disclaimer

  • World commands are affected by camera/zoom; screen commands are drawn in screen space.
  • Frame currently limits queued commands with:
    • MAX_DRAW_COMMANDS = 64 (per world buffer and per screen buffer)
    • MAX_TEXT_LEN = 128 (max text payload per text command)
  • When a buffer is full, OverflowPolicy decides what happens. .drop_newest (default) drops the incoming command and increments world_dropped_commands / screen_dropped_commands; .replace_last overwrites the most recent slot. Tune via Frame.setOverflowPolicy.

About

Simple renderer toy written with Zig.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors