An Odin application using Sokol for windowing and rendering.
The starter opens a resizable native window and presents a CPU-backed 160×120 pixel canvas through sokol_framebuffer. Draw pink pixels with the left mouse button and erase with the right mouse button. Fast pointer movement is connected into continuous strokes.
Press Shift to preview a straight line from the previous stroke endpoint to the cursor. Click to commit that segment; while Shift remains held, a new preview immediately starts from the clicked endpoint, allowing chained lines. Releasing Shift removes the preview. Escape cancels an active preview; otherwise it quits.
The canvas opens centered at 100% inside the editor's canvas region instead of fitting the window. Scroll vertically or horizontally to pan; pinch, or use Control/Command + scroll, to move through pixel-perfect zoom levels from 6.25% to 6400%. View → Home or the SMGUI Home button restores 100% and recenters the canvas inside that region.
The editor shell is rendered by a vendored, backend-independent SMGUI Odin package. An SMGUI Custom Form reserves a transparent canvas region; BitSpryte keeps ownership of the Sokol application loop and draws the document canvas into that region before alpha-compositing SMGUI on top. This ordering allows future menus and popups to cover the canvas correctly.
The shell uses SMGUI's Aseprite sprite-sheet font at 2× scale and the generated Catppuccin Mocha Aseprite theme bundle. The theme's software cursor is disabled because BitSpryte uses one native cursor across both the UI shell and GPU-rendered canvas.
The vendored source and its pinned upstream revision are documented in vendor/smgui/VENDOR.md. BitSpryte intentionally does not vendor SMGUI's platform adapters or duplicate Sokol dependency.
- A recent Odin compiler
make, a C compiler,ar,curl, andtar- macOS (Metal, ARM64 or x86_64) or Linux (OpenGL, x86_64)
On Linux, install the X11, Xi, Xcursor, OpenGL, pthread, and dl development libraries required by sokol_app.
make # debug build
make run # debug build and run
make release # optimized build
make check # type-check
make test # run application logic testsThe first build downloads the Odin Sokol bindings and compiles the native Sokol libraries used by the application. Dependencies are kept in the ignored sokol/ directory; outputs go to build.nosync/.
app/actions and app/events are synchronous publish/subscribe buses. Actions describe requested work; events describe completed facts. Both support filtered and catch-all subscriptions, explicit unsubscription, source metadata, and nested publication.
Native menu selections publish an actions.Action with .Native_Menu source. For example, Edit → Delete publishes .Clear; the canvas subscriber performs the work and then publishes the .Canvas_Cleared event. Pointer edits publish .Canvas_Changed facts.
On macOS, platform/native_menu installs native File, Edit, Sprite, Layer, Frame, Select, View, Window, and Help menus with nested Export, Import, transform, layer, frame, selection, and view commands. Every custom item maps to the canonical actions.Kind enum. The menu uses Odin's native AppKit interop and an Odin-registered NSObject target with action tags; non-macOS builds use a no-op implementation.
render/cpu_framebuffer owns a CPU-side RGBA8 pixel array and its sokol_framebuffer handle. It deliberately separates update and presentation:
cpu_framebuffer.upload_if_dirty(&canvas) // outside a render pass
sg.begin_pass(pass)
cpu_framebuffer.render(&canvas) // inside the chosen pass/viewport
sg.end_pass()The package provides pixel access, dirty tracking, upload, and rendering while leaving pass ordering and viewport placement to the caller. It also exposes the resolved texture for custom composition.
The straight-line preview is transient editor state rather than part of the document canvas:
drawing/line_preview.odinowns the testable preview and chained-commit lifecycle.editor/overlayrasterizes previews into a transparent CPU framebuffer.render/compositoralpha-blends that framebuffer after the document canvas.- Preview and commit both use the same Bresenham rasterizer, so the committed line matches the preview.
The compositor shader source is render/compositor/textured_quad.glsl; its generated Odin binding is committed beside it.
editor/checkerboard fills a CPU framebuffer from a reusable checkerboard.Config. The default uses 16×16 cells with #808080 and #c0c0c0. The application keeps this config as mutable state so a future settings menu can change it and call checkerboard.fill again.
Run make help to list available targets.