| Crates.io | wasm96-sdk |
| lib.rs | wasm96-sdk |
| version | 0.1.2 |
| created_at | 2025-12-20 16:58:47.127094+00 |
| updated_at | 2025-12-26 20:21:16.698079+00 |
| description | SDK for building WASM apps that run under the wasm96 libretro core. |
| homepage | https://github.com/isaiahpettingill/wasm96 |
| repository | https://github.com/isaiahpettingill/wasm96 |
| max_upload_size | |
| id | 1996725 |
| size | 46,543 |
A Rust SDK for building WebAssembly applications that run under the wasm96 libretro core.
wasm96-sdk provides safe, ergonomic bindings to the wasm96 guest ABI, allowing you to write games and applications in Rust that compile to WebAssembly and run in libretro frontends like RetroArch.
Key features:
.wasm) issues drawing/audio/input calls.setup() (required)update() (optional)draw() (optional)Add this to your Cargo.toml:
[package]
name = "my-wasm96-app"
version = "0.1.0"
edition = "2024"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm96-sdk = "0.1.0"
In your src/lib.rs:
use wasm96_sdk::prelude::*;
// Required: Called once on startup
#[no_mangle]
pub extern "C" fn setup() {
graphics::set_size(640, 480);
// Register assets, initialize state, etc.
}
// Optional: Called once per frame to update logic
#[no_mangle]
pub extern "C" fn update() {
// Handle input, update game state
}
// Optional: Called once per frame to draw
#[no_mangle]
pub extern "C" fn draw() {
graphics::background(0, 0, 0); // Black background
graphics::set_color(255, 255, 255, 255); // White
graphics::rect(100, 100, 100, 100); // Draw a rectangle
}
Build for WebAssembly:
rustup target add wasm32-unknown-unknown
cargo build --target wasm32-unknown-unknown --release
The output .wasm file can be loaded into the wasm96 core in RetroArch.
Text rendering is one of the easiest places to get surprised by ABI and lifecycle details. This section is intentionally long.
Fonts in wasm96 are keyed resources:
In Rust, the SDK exposes string-key APIs (like "ui" or "debug"), and the SDK hashes those strings to the underlying u64 key.
The host (wasm96-core) implements a fallback if you try to measure/draw text with a key that has not been registered:
graphics::text_key(...) falls back to built-in Spleen at size 16graphics::text_measure_key(...) uses the exact same fallbackThis means:
Best practice: register fonts in setup() and always measure/draw using those same keys.
You have three practical options:
graphics::font_register_spleen(key, size)graphics::font_register_bdf(key, bdf_bytes)graphics::font_register_ttf(key, font_bytes)A key is an arbitrary string you choose, such as:
"ui""debug""title_32"You should treat keys as part of your app’s “asset namespace”. They should be:
setup():graphics::text_measure_key(font_key, text)graphics::text_key(x, y, font_key, text)Example (built-in Spleen):
use wasm96_sdk::graphics;
#[no_mangle]
pub extern "C" fn setup() {
graphics::set_size(320, 240);
// Register a predictable font key for UI.
graphics::font_register_spleen("ui", 16);
}
#[no_mangle]
pub extern "C" fn draw() {
graphics::background(0, 0, 0);
graphics::set_color(255, 255, 255, 255);
let msg = "Hello wasm96";
let size = graphics::text_measure_key("ui", msg);
let x = (320i32 - size.width as i32) / 2;
let y = 20;
graphics::text_key(x, y, "ui", msg);
}
Example (custom TTF/OTF):
use wasm96_sdk::graphics;
static UI_TTF: &[u8] = include_bytes!("../assets/YourFont.ttf");
#[no_mangle]
pub extern "C" fn setup() {
graphics::set_size(640, 480);
// Choose a stable key and register once.
let ok = graphics::font_register_ttf("ui", UI_TTF);
if !ok {
// If you want, log/fallback in your app.
// The host will still be able to render via fallback Spleen 16 for unknown keys.
}
}
#[no_mangle]
pub extern "C" fn draw() {
graphics::background(16, 16, 16);
graphics::set_color(240, 240, 240, 255);
graphics::text_key(20, 20, "ui", "TTF text");
}
Because the host fallback is defined, if you accidentally measure with "ui" but draw with "UI" (different key), you can end up measuring one font and drawing another.
Best practice:
All text/font registration APIs that take byte pointers behave like “read immediately”:
font_register_*, the host reads the font bytes during the call and parses/copies them host-side.text_key / text_measure_key, the host reads the UTF-8 text bytes during the call.So:
&str and &[u8] that live only for the duration of the callIf you need to reclaim host-side resources (rare for typical games), you can unregister:
graphics::font_unregister(key)After unregistering:
If text is not drawing as expected:
graphics::set_color(r,g,b,a) before drawing text.font_register_spleen, verify you are using a supported size.font_register_ttf, verify the font bytes are valid and included correctly.std (default): Enables standard library features for convenience.wee_alloc: Optional global allocator for wasm32-unknown-unknown targets.See the wasm96 repository for complete examples:
rust-guest/: Basic hello-worldrust-guest-showcase/: Comprehensive feature demoGenerate and view docs locally:
cargo doc --open
This SDK targets the wasm96 ABI as defined in the WIT interface. Ensure your wasm96-core version matches the SDK version for compatibility.
MIT License - see LICENSE for details.
Contributions are welcome! Please see the main repository for development guidelines.