Skip to main content

wasm96_sdk/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2
3//! wasm96-sdk (handwritten)
4//!
5//! This crate is used by **guest** WASM apps that run inside the `wasm96` libretro core.
6//!
7//! ABI model (Immediate Mode):
8//! - Host owns the framebuffer and handles rendering.
9//! - Guest issues drawing commands.
10//! - Guest exports `setup`, `update`, and `draw`.
11//!
12//! This file intentionally contains **no WIT** and **no codegen**.
13//!
14//! # Fonts & text: how it works
15//!
16//! wasm96 provides a **keyed font** model:
17//! - You register a font under a `u64` key (the SDK hashes `&str` keys for you).
18//! - Later you draw or measure text by referencing the same key.
19//!
20//! In the Rust SDK:
21//! - Registration lives in [`graphics::font_register_ttf`], [`graphics::font_register_bdf`],
22//!   and [`graphics::font_register_spleen`].
23//! - Drawing & measuring live in [`graphics::text_key`] and [`graphics::text_measure_key`].
24//!
25//! ## Fallback behavior (important)
26//!
27//! The host (wasm96-core) implements a **fallback** if you try to measure/draw text using a
28//! `font_key` that was never registered:
29//! - `graphics::text_key(...)` falls back to the built-in **Spleen** font at **size 16**.
30//! - `graphics::text_measure_key(...)` uses the same fallback (Spleen size 16).
31//!
32//! This means text “just works” out-of-the-box, but you should still register fonts when you
33//! care about style, metric stability, or performance.
34//!
35//! ## Which font format should you use?
36//!
37//! - **Spleen (built-in, BDF)** via [`graphics::font_register_spleen`]
38//!   - Great default for pixel UIs / debug text.
39//!   - Supported sizes: **8, 16, 24, 32, 64** (other sizes currently fail and return `false`).
40//! - **BDF (custom)** via [`graphics::font_register_bdf`]
41//!   - Best for pixel-perfect fonts you control.
42//!   - Host parses the BDF, builds a glyph map, and renders using the parsed bitmaps.
43//! - **TTF/OTF (custom)** via [`graphics::font_register_ttf`]
44//!   - Best for scalable fonts.
45//!   - Host uses font rasterization and draws glyphs as alpha-blended bitmaps.
46//!
47//! ## Recommended usage pattern
48//!
49//! 1. Pick a stable string key for each font you use (e.g. `"ui"`, `"title"`, `"debug"`).
50//! 2. Register fonts during `setup()` (once).
51//! 3. Use `text_measure_key` for layout (e.g. centering).
52//! 4. Use `text_key` to draw. Reuse keys; do not re-register each frame.
53//!
54//! Example (built-in Spleen):
55//!
56//! ```no_run
57//! use wasm96_sdk::graphics;
58//!
59//! #[no_mangle]
60//! pub extern "C" fn setup() {
61//!     graphics::set_size(320, 240);
62//!     // Register built-in Spleen under a key you control.
63//!     graphics::font_register_spleen("ui", 16);
64//! }
65//!
66//! #[no_mangle]
67//! pub extern "C" fn draw() {
68//!     graphics::background(0, 0, 0);
69//!     graphics::set_color(255, 255, 255, 255);
70//!
71//!     let size = graphics::text_measure_key("ui", "Hello wasm96");
72//!     let x = (320i32 - size.width as i32) / 2;
73//!     let y = 20;
74//!     graphics::text_key(x, y, "ui", "Hello wasm96");
75//! }
76//! ```
77//!
78//! ## Lifetimes and safety notes
79//!
80//! - `text_key` and `text_measure_key` pass pointers into guest memory to the host.
81//!   The host reads the bytes immediately during the call, so the `&str` only needs to remain
82//!   valid for the duration of the function call.
83//! - `font_register_*` similarly passes pointers to font data; the host copies/decodes immediately.
84//!
85//! ## Unregistering fonts
86//!
87//! If you need to reclaim host-side font resources (rare for most games), call
88//! [`graphics::font_unregister`]. The key will no longer map to a font. Subsequent text usage
89//! with that key will again hit the host fallback (Spleen size 16).
90
91#[cfg(not(feature = "std"))]
92extern crate alloc;
93
94use core::ffi::c_void;
95
96/// Joypad button ids.
97#[repr(u32)]
98#[derive(Copy, Clone, Debug, Eq, PartialEq)]
99pub enum Button {
100    B = 0,
101    Y = 1,
102    Select = 2,
103    Start = 3,
104    Up = 4,
105    Down = 5,
106    Left = 6,
107    Right = 7,
108    A = 8,
109    X = 9,
110    L1 = 10,
111    R1 = 11,
112    L2 = 12,
113    R2 = 13,
114    L3 = 14,
115    R3 = 15,
116}
117
118/// Text size dimensions.
119#[repr(C)]
120#[derive(Copy, Clone, Debug, Eq, PartialEq)]
121pub struct TextSize {
122    pub width: u32,
123    pub height: u32,
124}
125
126/// Low-level raw ABI imports.
127#[allow(non_camel_case_types)]
128pub mod sys {
129    unsafe extern "C" {
130        // Graphics
131        #[link_name = "wasm96_graphics_set_size"]
132        pub fn graphics_set_size(width: u32, height: u32);
133        #[link_name = "wasm96_graphics_set_color"]
134        pub fn graphics_set_color(r: u32, g: u32, b: u32, a: u32);
135        #[link_name = "wasm96_graphics_background"]
136        pub fn graphics_background(r: u32, g: u32, b: u32);
137        #[link_name = "wasm96_graphics_point"]
138        pub fn graphics_point(x: i32, y: i32);
139        #[link_name = "wasm96_graphics_line"]
140        pub fn graphics_line(x1: i32, y1: i32, x2: i32, y2: i32);
141        #[link_name = "wasm96_graphics_rect"]
142        pub fn graphics_rect(x: i32, y: i32, w: u32, h: u32);
143        #[link_name = "wasm96_graphics_rect_outline"]
144        pub fn graphics_rect_outline(x: i32, y: i32, w: u32, h: u32);
145        #[link_name = "wasm96_graphics_circle"]
146        pub fn graphics_circle(x: i32, y: i32, r: u32);
147        #[link_name = "wasm96_graphics_circle_outline"]
148        pub fn graphics_circle_outline(x: i32, y: i32, r: u32);
149        #[link_name = "wasm96_graphics_image"]
150        pub fn graphics_image(x: i32, y: i32, w: u32, h: u32, ptr: u32, len: u32);
151
152        // One-shot (decode + draw at natural size)
153        #[link_name = "wasm96_graphics_image_png"]
154        pub fn graphics_image_png(x: i32, y: i32, ptr: u32, len: u32);
155        #[link_name = "wasm96_graphics_image_jpeg"]
156        pub fn graphics_image_jpeg(x: i32, y: i32, ptr: u32, len: u32);
157
158        // Materials / textures (OBJ+MTL workflows)
159        //
160        // Given an `.mtl` file and one encoded texture blob (PNG/JPEG) + its filename, register the
161        // decoded texture under `texture_key` if the filename appears as a `map_Kd` entry.
162        #[link_name = "wasm96_graphics_mtl_register_texture"]
163        pub fn graphics_mtl_register_texture(
164            texture_key: u64,
165            mtl_ptr: u32,
166            mtl_len: u32,
167            tex_filename_ptr: u32,
168            tex_filename_len: u32,
169            tex_ptr: u32,
170            tex_len: u32,
171        ) -> u32;
172
173        // --- Keyed resources (hashed keys) ---
174        // SVG
175        #[link_name = "wasm96_graphics_svg_register"]
176        pub fn graphics_svg_register(key: u64, data_ptr: u32, data_len: u32) -> u32;
177        #[link_name = "wasm96_graphics_svg_draw_key"]
178        pub fn graphics_svg_draw_key(key: u64, x: i32, y: i32, w: u32, h: u32);
179        #[link_name = "wasm96_graphics_svg_unregister"]
180        pub fn graphics_svg_unregister(key: u64);
181
182        // GIF
183        #[link_name = "wasm96_graphics_gif_register"]
184        pub fn graphics_gif_register(key: u64, data_ptr: u32, data_len: u32) -> u32;
185        #[link_name = "wasm96_graphics_gif_draw_key"]
186        pub fn graphics_gif_draw_key(key: u64, x: i32, y: i32);
187        #[link_name = "wasm96_graphics_gif_draw_key_scaled"]
188        pub fn graphics_gif_draw_key_scaled(key: u64, x: i32, y: i32, w: u32, h: u32);
189        #[link_name = "wasm96_graphics_gif_unregister"]
190        pub fn graphics_gif_unregister(key: u64);
191
192        // PNG
193        #[link_name = "wasm96_graphics_png_register"]
194        pub fn graphics_png_register(key: u64, data_ptr: u32, data_len: u32) -> u32;
195        #[link_name = "wasm96_graphics_png_draw_key"]
196        pub fn graphics_png_draw_key(key: u64, x: i32, y: i32);
197        #[link_name = "wasm96_graphics_png_draw_key_scaled"]
198        pub fn graphics_png_draw_key_scaled(key: u64, x: i32, y: i32, w: u32, h: u32);
199        #[link_name = "wasm96_graphics_png_unregister"]
200        pub fn graphics_png_unregister(key: u64);
201
202        // JPEG
203        #[link_name = "wasm96_graphics_jpeg_register"]
204        pub fn graphics_jpeg_register(key: u64, data_ptr: u32, data_len: u32) -> u32;
205        #[link_name = "wasm96_graphics_jpeg_draw_key"]
206        pub fn graphics_jpeg_draw_key(key: u64, x: i32, y: i32);
207        #[link_name = "wasm96_graphics_jpeg_draw_key_scaled"]
208        pub fn graphics_jpeg_draw_key_scaled(key: u64, x: i32, y: i32, w: u32, h: u32);
209        #[link_name = "wasm96_graphics_jpeg_unregister"]
210        pub fn graphics_jpeg_unregister(key: u64);
211
212        // Fonts + text (keyed by string)
213        //
214        // The host maintains a map of `u64 font_key -> font resource`.
215        // Keys are arbitrary. The Rust SDK hashes `&str` keys with FNV-1a to a `u64`.
216        //
217        // Registration functions return 1 on success, 0 on failure.
218        //
219        // IMPORTANT HOST BEHAVIOR:
220        // - If you call `graphics_text_key` / `graphics_text_measure_key` with a font_key that has
221        //   never been registered, the host falls back to built-in Spleen at size 16.
222        //
223        // This makes text work out-of-the-box, but for stable metrics you should explicitly
224        // register a font under a deterministic key during `setup()`.
225        #[link_name = "wasm96_graphics_font_register_ttf"]
226        pub fn graphics_font_register_ttf(key: u64, data_ptr: u32, data_len: u32) -> u32;
227        #[link_name = "wasm96_graphics_font_register_bdf"]
228        pub fn graphics_font_register_bdf(key: u64, data_ptr: u32, data_len: u32) -> u32;
229        #[link_name = "wasm96_graphics_font_register_spleen"]
230        pub fn graphics_font_register_spleen(key: u64, size: u32) -> u32;
231        #[link_name = "wasm96_graphics_font_unregister"]
232        pub fn graphics_font_unregister(key: u64);
233
234        // Draw text with a keyed font.
235        // - `text_ptr/text_len` are UTF-8 bytes in guest memory (host expects valid UTF-8).
236        // - If `font_key` is unknown, host falls back to Spleen size 16.
237        #[link_name = "wasm96_graphics_text_key"]
238        pub fn graphics_text_key(x: i32, y: i32, font_key: u64, text_ptr: u32, text_len: u32);
239
240        // Measure text with a keyed font.
241        // - Returns a packed u64: (width<<32) | height.
242        // - If `font_key` is unknown, host falls back to Spleen size 16.
243        #[link_name = "wasm96_graphics_text_measure_key"]
244        pub fn graphics_text_measure_key(font_key: u64, text_ptr: u32, text_len: u32) -> u64;
245
246        #[link_name = "wasm96_graphics_triangle"]
247        pub fn graphics_triangle(x1: i32, y1: i32, x2: i32, y2: i32, x3: i32, y3: i32);
248
249        #[link_name = "wasm96_graphics_triangle_outline"]
250        pub fn graphics_triangle_outline(x1: i32, y1: i32, x2: i32, y2: i32, x3: i32, y3: i32);
251
252        #[link_name = "wasm96_graphics_bezier_quadratic"]
253        pub fn graphics_bezier_quadratic(
254            x1: i32,
255            y1: i32,
256            cx: i32,
257            cy: i32,
258            x2: i32,
259            y2: i32,
260            segments: u32,
261        );
262
263        #[link_name = "wasm96_graphics_bezier_cubic"]
264        pub fn graphics_bezier_cubic(
265            x1: i32,
266            y1: i32,
267            cx1: i32,
268            cy1: i32,
269            cx2: i32,
270            cy2: i32,
271            x2: i32,
272            y2: i32,
273            segments: u32,
274        );
275
276        #[link_name = "wasm96_graphics_pill"]
277        pub fn graphics_pill(x: i32, y: i32, w: u32, h: u32);
278
279        #[link_name = "wasm96_graphics_pill_outline"]
280        pub fn graphics_pill_outline(x: i32, y: i32, w: u32, h: u32);
281
282        // 3D Graphics
283        #[link_name = "wasm96_graphics_set_3d"]
284        pub fn graphics_set_3d(enable: u32);
285
286        #[link_name = "wasm96_graphics_camera_look_at"]
287        pub fn graphics_camera_look_at(
288            eye_x: f32,
289            eye_y: f32,
290            eye_z: f32,
291            target_x: f32,
292            target_y: f32,
293            target_z: f32,
294            up_x: f32,
295            up_y: f32,
296            up_z: f32,
297        );
298
299        #[link_name = "wasm96_graphics_camera_perspective"]
300        pub fn graphics_camera_perspective(fovy: f32, aspect: f32, near: f32, far: f32);
301
302        #[link_name = "wasm96_graphics_mesh_create"]
303        pub fn graphics_mesh_create(
304            key: u64,
305            v_ptr: *const f32,
306            v_len: usize,
307            i_ptr: *const u32,
308            i_len: usize,
309        ) -> u32;
310
311        #[link_name = "wasm96_graphics_mesh_create_obj"]
312        pub fn graphics_mesh_create_obj(key: u64, ptr: *const u8, len: usize) -> u32;
313
314        #[link_name = "wasm96_graphics_mesh_create_stl"]
315        pub fn graphics_mesh_create_stl(key: u64, ptr: *const u8, len: usize) -> u32;
316
317        #[link_name = "wasm96_graphics_mesh_set_texture"]
318        pub fn graphics_mesh_set_texture(mesh_key: u64, image_key: u64) -> u32;
319
320        #[link_name = "wasm96_graphics_mesh_draw"]
321        pub fn graphics_mesh_draw(
322            key: u64,
323            x: f32,
324            y: f32,
325            z: f32,
326            rx: f32,
327            ry: f32,
328            rz: f32,
329            sx: f32,
330            sy: f32,
331            sz: f32,
332        );
333
334        // Input
335        #[link_name = "wasm96_input_is_button_down"]
336        pub fn input_is_button_down(port: u32, btn: u32) -> u32;
337        #[link_name = "wasm96_input_is_key_down"]
338        pub fn input_is_key_down(key: u32) -> u32;
339        #[link_name = "wasm96_input_get_mouse_x"]
340        pub fn input_get_mouse_x() -> i32;
341        #[link_name = "wasm96_input_get_mouse_y"]
342        pub fn input_get_mouse_y() -> i32;
343        #[link_name = "wasm96_input_is_mouse_down"]
344        pub fn input_is_mouse_down(btn: u32) -> u32;
345
346        // Audio
347        #[link_name = "wasm96_audio_init"]
348        pub fn audio_init(sample_rate: u32) -> u32;
349        #[link_name = "wasm96_audio_push_samples"]
350        pub fn audio_push_samples(ptr: u32, len: u32);
351
352        #[link_name = "wasm96_audio_play_wav"]
353        pub fn audio_play_wav(ptr: u32, len: u32);
354
355        #[link_name = "wasm96_audio_play_qoa"]
356        pub fn audio_play_qoa(ptr: u32, len: u32);
357
358        #[link_name = "wasm96_audio_play_xm"]
359        pub fn audio_play_xm(ptr: u32, len: u32);
360
361        // Storage
362        #[link_name = "wasm96_storage_save"]
363        pub fn storage_save(key: u64, data_ptr: u32, data_len: u32);
364        #[link_name = "wasm96_storage_load"]
365        pub fn storage_load(key: u64) -> u64;
366        #[link_name = "wasm96_storage_free"]
367        pub fn storage_free(ptr: u32, len: u32);
368
369        // System
370        #[link_name = "wasm96_system_log"]
371        pub fn system_log(ptr: u32, len: u32);
372        #[link_name = "wasm96_system_millis"]
373        pub fn system_millis() -> u64;
374    }
375}
376
377/// Graphics API.
378pub mod graphics {
379    use super::sys;
380    use crate::TextSize;
381
382    pub(crate) fn hash_key(key: &str) -> u64 {
383        let mut hash: u64 = 0xcbf29ce484222325;
384        for byte in key.bytes() {
385            hash ^= byte as u64;
386            hash = hash.wrapping_mul(0x100000001b3);
387        }
388        hash
389    }
390
391    /// Set the screen dimensions.
392    pub fn set_size(width: u32, height: u32) {
393        unsafe { sys::graphics_set_size(width, height) }
394    }
395
396    /// Set the current drawing color (RGBA).
397    pub fn set_color(r: u8, g: u8, b: u8, a: u8) {
398        unsafe { sys::graphics_set_color(r as u32, g as u32, b as u32, a as u32) }
399    }
400
401    /// Clear the screen with a specific color (RGB).
402    pub fn background(r: u8, g: u8, b: u8) {
403        unsafe { sys::graphics_background(r as u32, g as u32, b as u32) }
404    }
405
406    /// Draw a single pixel at (x, y).
407    pub fn point(x: i32, y: i32) {
408        unsafe { sys::graphics_point(x, y) }
409    }
410
411    /// Draw a line from (x1, y1) to (x2, y2).
412    pub fn line(x1: i32, y1: i32, x2: i32, y2: i32) {
413        unsafe { sys::graphics_line(x1, y1, x2, y2) }
414    }
415
416    /// Draw a filled rectangle.
417    pub fn rect(x: i32, y: i32, w: u32, h: u32) {
418        unsafe { sys::graphics_rect(x, y, w, h) }
419    }
420
421    /// Draw a rectangle outline.
422    pub fn rect_outline(x: i32, y: i32, w: u32, h: u32) {
423        unsafe { sys::graphics_rect_outline(x, y, w, h) }
424    }
425
426    /// Draw a filled circle.
427    pub fn circle(x: i32, y: i32, r: u32) {
428        unsafe { sys::graphics_circle(x, y, r) }
429    }
430
431    /// Draw a circle outline.
432    pub fn circle_outline(x: i32, y: i32, r: u32) {
433        unsafe { sys::graphics_circle_outline(x, y, r) }
434    }
435
436    /// Draw an image/sprite.
437    /// `data` is a slice of RGBA bytes (4 bytes per pixel).
438    pub fn image(x: i32, y: i32, w: u32, h: u32, data: &[u8]) {
439        unsafe { sys::graphics_image(x, y, w, h, data.as_ptr() as u32, data.len() as u32) }
440    }
441
442    /// Draw an image from raw PNG bytes.
443    pub fn image_png(x: i32, y: i32, data: &[u8]) {
444        unsafe { sys::graphics_image_png(x, y, data.as_ptr() as u32, data.len() as u32) }
445    }
446
447    /// Draw an image from raw JPEG bytes.
448    pub fn image_jpeg(x: i32, y: i32, data: &[u8]) {
449        unsafe { sys::graphics_image_jpeg(x, y, data.as_ptr() as u32, data.len() as u32) }
450    }
451
452    /// Register an encoded PNG (bytes) with the host under a string key.
453    /// Register a GIF resource (encoded bytes) under a string key.
454    /// Returns true on success.
455    pub fn gif_register(key: &str, gif_bytes: &[u8]) -> bool {
456        unsafe {
457            sys::graphics_gif_register(
458                hash_key(key),
459                gif_bytes.as_ptr() as u32,
460                gif_bytes.len() as u32,
461            ) != 0
462        }
463    }
464
465    /// Draw a registered GIF by key at natural size.
466    pub fn gif_draw_key(key: &str, x: i32, y: i32) {
467        unsafe { sys::graphics_gif_draw_key(hash_key(key), x, y) }
468    }
469
470    /// Draw a registered GIF by key scaled.
471    pub fn gif_draw_key_scaled(key: &str, x: i32, y: i32, w: u32, h: u32) {
472        unsafe { sys::graphics_gif_draw_key_scaled(hash_key(key), x, y, w, h) }
473    }
474
475    /// Unregister a GIF by key.
476    pub fn gif_unregister(key: &str) {
477        unsafe { sys::graphics_gif_unregister(hash_key(key)) }
478    }
479
480    /// Draw a filled triangle.
481    pub fn triangle(x1: i32, y1: i32, x2: i32, y2: i32, x3: i32, y3: i32) {
482        unsafe { sys::graphics_triangle(x1, y1, x2, y2, x3, y3) }
483    }
484
485    /// Draw a triangle outline.
486    pub fn triangle_outline(x1: i32, y1: i32, x2: i32, y2: i32, x3: i32, y3: i32) {
487        unsafe { sys::graphics_triangle_outline(x1, y1, x2, y2, x3, y3) }
488    }
489
490    /// Draw a quadratic Bezier curve.
491    pub fn bezier_quadratic(x1: i32, y1: i32, cx: i32, cy: i32, x2: i32, y2: i32, segments: u32) {
492        unsafe { sys::graphics_bezier_quadratic(x1, y1, cx, cy, x2, y2, segments) }
493    }
494
495    /// Draw a cubic Bezier curve.
496    pub fn bezier_cubic(
497        x1: i32,
498        y1: i32,
499        cx1: i32,
500        cy1: i32,
501        cx2: i32,
502        cy2: i32,
503        x2: i32,
504        y2: i32,
505        segments: u32,
506    ) {
507        unsafe { sys::graphics_bezier_cubic(x1, y1, cx1, cy1, cx2, cy2, x2, y2, segments) }
508    }
509
510    /// Draw a filled pill.
511    pub fn pill(x: i32, y: i32, w: u32, h: u32) {
512        unsafe { sys::graphics_pill(x, y, w, h) }
513    }
514
515    /// Draw a pill outline.
516    pub fn pill_outline(x: i32, y: i32, w: u32, h: u32) {
517        unsafe { sys::graphics_pill_outline(x, y, w, h) }
518    }
519
520    // =========================
521    // 3D Graphics
522    // =========================
523
524    pub fn set_3d(enable: bool) {
525        unsafe { sys::graphics_set_3d(enable as u32) }
526    }
527
528    pub fn camera_look_at(eye: (f32, f32, f32), target: (f32, f32, f32), up: (f32, f32, f32)) {
529        unsafe {
530            sys::graphics_camera_look_at(
531                eye.0, eye.1, eye.2, target.0, target.1, target.2, up.0, up.1, up.2,
532            )
533        }
534    }
535
536    pub fn camera_perspective(fovy: f32, aspect: f32, near: f32, far: f32) {
537        unsafe { sys::graphics_camera_perspective(fovy, aspect, near, far) }
538    }
539
540    pub fn mesh_create(key: &str, vertices: &[f32], indices: &[u32]) -> bool {
541        let k = hash_key(key);
542        unsafe {
543            sys::graphics_mesh_create(
544                k,
545                vertices.as_ptr(),
546                vertices.len(),
547                indices.as_ptr(),
548                indices.len(),
549            ) != 0
550        }
551    }
552
553    pub fn mesh_create_obj(key: &str, obj_data: &[u8]) -> bool {
554        let k = hash_key(key);
555        unsafe { sys::graphics_mesh_create_obj(k, obj_data.as_ptr(), obj_data.len()) != 0 }
556    }
557
558    pub fn mesh_create_stl(key: &str, stl_data: &[u8]) -> bool {
559        let k = hash_key(key);
560        unsafe { sys::graphics_mesh_create_stl(k, stl_data.as_ptr(), stl_data.len()) != 0 }
561    }
562
563    pub fn mesh_draw(
564        key: &str,
565        pos: (f32, f32, f32),
566        rot: (f32, f32, f32),
567        scale: (f32, f32, f32),
568    ) {
569        let k = hash_key(key);
570        unsafe {
571            sys::graphics_mesh_draw(
572                k, pos.0, pos.1, pos.2, rot.0, rot.1, rot.2, scale.0, scale.1, scale.2,
573            )
574        }
575    }
576
577    /// Bind a keyed decoded image (PNG/JPEG) as the texture for a mesh.
578    /// Returns true on success.
579    ///
580    /// Notes:
581    /// - PNG alpha is respected (RGBA).
582    /// - JPEG is treated as opaque (RGB), but may still be uploaded as RGBA with A=255 on host.
583    pub fn mesh_set_texture(mesh_key: &str, image_key: &str) -> bool {
584        unsafe { sys::graphics_mesh_set_texture(hash_key(mesh_key), hash_key(image_key)) != 0 }
585    }
586
587    /// Register an SVG resource under a string key.
588    /// Register an SVG resource (encoded bytes) under a string key.
589    /// Returns true on success.
590    pub fn svg_register(key: &str, svg_bytes: &[u8]) -> bool {
591        unsafe {
592            sys::graphics_svg_register(
593                hash_key(key),
594                svg_bytes.as_ptr() as u32,
595                svg_bytes.len() as u32,
596            ) != 0
597        }
598    }
599
600    /// Draw a keyed SVG.
601    pub fn svg_draw_key(key: &str, x: i32, y: i32, w: u32, h: u32) {
602        unsafe { sys::graphics_svg_draw_key(hash_key(key), x, y, w, h) }
603    }
604
605    /// Unregister a keyed SVG.
606    pub fn svg_unregister(key: &str) {
607        unsafe { sys::graphics_svg_unregister(hash_key(key)) }
608    }
609
610    /// Register a PNG resource (encoded bytes) under a string key.
611    /// Returns true on success.
612    pub fn png_register(key: &str, png_bytes: &[u8]) -> bool {
613        unsafe {
614            sys::graphics_png_register(
615                hash_key(key),
616                png_bytes.as_ptr() as u32,
617                png_bytes.len() as u32,
618            ) != 0
619        }
620    }
621
622    /// Register an encoded texture referenced by an `.mtl` file (`map_Kd`) under `texture_key`.
623    ///
624    /// Usage pattern (guest-side):
625    /// - You already have the `.mtl` bytes (`mtl_bytes`)
626    /// - For each texture file referenced by `map_Kd`, call this passing:
627    ///   - `texture_key`: the key you want to register the decoded image under
628    ///   - `tex_filename`: the *exact* filename string as used in `map_Kd`
629    ///   - `tex_bytes`: the encoded PNG/JPEG bytes for that filename
630    ///
631    /// Returns `true` if it registered (filename matched + decode succeeded), else `false`.
632    pub fn mtl_register_texture(
633        texture_key: &str,
634        mtl_bytes: &[u8],
635        tex_filename: &str,
636        tex_bytes: &[u8],
637    ) -> bool {
638        unsafe {
639            sys::graphics_mtl_register_texture(
640                hash_key(texture_key),
641                mtl_bytes.as_ptr() as u32,
642                mtl_bytes.len() as u32,
643                tex_filename.as_ptr() as u32,
644                tex_filename.len() as u32,
645                tex_bytes.as_ptr() as u32,
646                tex_bytes.len() as u32,
647            ) != 0
648        }
649    }
650
651    /// Register a JPEG resource (encoded bytes) under a string key.
652    /// Returns true on success.
653    pub fn jpeg_register(key: &str, jpeg_bytes: &[u8]) -> bool {
654        unsafe {
655            sys::graphics_jpeg_register(
656                hash_key(key),
657                jpeg_bytes.as_ptr() as u32,
658                jpeg_bytes.len() as u32,
659            ) != 0
660        }
661    }
662
663    /// Draw a registered PNG by key at natural size.
664    pub fn png_draw_key(key: &str, x: i32, y: i32) {
665        unsafe { sys::graphics_png_draw_key(hash_key(key), x, y) }
666    }
667
668    /// Draw a registered JPEG by key at natural size.
669    pub fn jpeg_draw_key(key: &str, x: i32, y: i32) {
670        unsafe { sys::graphics_jpeg_draw_key(hash_key(key), x, y) }
671    }
672
673    /// Draw a registered PNG by key scaled.
674    pub fn png_draw_key_scaled(key: &str, x: i32, y: i32, w: u32, h: u32) {
675        unsafe { sys::graphics_png_draw_key_scaled(hash_key(key), x, y, w, h) }
676    }
677
678    /// Draw a registered JPEG by key scaled.
679    pub fn jpeg_draw_key_scaled(key: &str, x: i32, y: i32, w: u32, h: u32) {
680        unsafe { sys::graphics_jpeg_draw_key_scaled(hash_key(key), x, y, w, h) }
681    }
682
683    /// Unregister a PNG by key.
684    pub fn png_unregister(key: &str) {
685        unsafe { sys::graphics_png_unregister(hash_key(key)) }
686    }
687
688    /// Unregister a JPEG by key.
689    pub fn jpeg_unregister(key: &str) {
690        unsafe { sys::graphics_jpeg_unregister(hash_key(key)) }
691    }
692
693    /// Register a TTF/OTF font under a string key.
694    ///
695    /// ## What the host does
696    /// - The host reads the encoded font bytes immediately during this call.
697    /// - It attempts to parse them as a TTF/OTF via its font rasterizer.
698    /// - On success it stores the font in a host-side table and associates it with `hash_key(key)`.
699    ///
700    /// ## When to call this
701    /// Prefer calling during `setup()` (once), *not per-frame*.
702    ///
703    /// ## Return value
704    /// Returns `true` if the host successfully parsed and registered the font.
705    ///
706    /// ## Notes
707    /// - If you never register a font for a key, `text_key`/`text_measure_key` will still work due
708    ///   to host fallback (Spleen size 16), but metrics/appearance may differ from what you expect.
709    pub fn font_register_ttf(key: &str, data: &[u8]) -> bool {
710        unsafe {
711            sys::graphics_font_register_ttf(hash_key(key), data.as_ptr() as u32, data.len() as u32)
712                != 0
713        }
714    }
715
716    /// Register a BDF bitmap font under a string key.
717    ///
718    /// ## What the host does
719    /// - The host reads `data` immediately and parses the BDF text.
720    /// - It extracts the font bounding box (pixel width/height) and glyph bitmaps.
721    /// - On success, the parsed font is stored and associated with `hash_key(key)`.
722    ///
723    /// ## When to use BDF
724    /// Use BDF for crisp pixel fonts, debug overlays, and UIs where you want deterministic
725    /// bitmap metrics and a retro look.
726    ///
727    /// ## Return value
728    /// Returns `true` if parsing succeeded and the font was registered.
729    pub fn font_register_bdf(key: &str, data: &[u8]) -> bool {
730        unsafe {
731            sys::graphics_font_register_bdf(hash_key(key), data.as_ptr() as u32, data.len() as u32)
732                != 0
733        }
734    }
735
736    /// Register the built-in Spleen font under a string key.
737    ///
738    /// Spleen is a bitmap font family bundled with the host (wasm96-core).
739    /// This function associates a chosen Spleen size with your `key`, so you can refer to it
740    /// via `text_key`/`text_measure_key`.
741    ///
742    /// ## Supported sizes
743    /// The host currently supports: **8, 16, 24, 32, 64**.
744    /// Other sizes return `false`.
745    ///
746    /// ## Why register Spleen if there is already a fallback?
747    /// The host fallback is Spleen size 16 **only when the key is missing**.
748    /// If you want a different size (or want to be explicit for layout stability),
749    /// register it under your own key.
750    ///
751    /// Returns `true` if successful.
752    pub fn font_register_spleen(key: &str, size: u32) -> bool {
753        unsafe { sys::graphics_font_register_spleen(hash_key(key), size) != 0 }
754    }
755
756    /// Unregister a font by key.
757    ///
758    /// After unregistering:
759    /// - The mapping from `hash_key(key)` to the font resource is removed on the host.
760    /// - Future calls to `text_key`/`text_measure_key` using this key will use the host fallback
761    ///   (Spleen size 16) unless you register another font under the same key.
762    ///
763    /// This is mainly useful for apps that dynamically load/unload large fonts and want to
764    /// reclaim host-side memory.
765    pub fn font_unregister(key: &str) {
766        unsafe { sys::graphics_font_unregister(hash_key(key)) }
767    }
768
769    /// Draw text using a keyed font.
770    ///
771    /// ## Parameters
772    /// - `x`, `y`: top-left origin where the text will be drawn in screen coordinates.
773    /// - `font_key`: the font key you previously registered (or any string; see fallback).
774    /// - `text`: UTF-8 text to draw.
775    ///
776    /// ## Fallback
777    /// If `font_key` is not registered, the host will draw using built-in Spleen size 16.
778    ///
779    /// ## Performance tips
780    /// - Keep `text` reasonably small per call; prefer drawing fewer, longer strings vs many
781    ///   single-character calls.
782    /// - Register fonts once in `setup()`; do not register fonts in `draw()`.
783    pub fn text_key(x: i32, y: i32, font_key: &str, text: &str) {
784        unsafe {
785            sys::graphics_text_key(
786                x,
787                y,
788                hash_key(font_key),
789                text.as_ptr() as u32,
790                text.len() as u32,
791            )
792        }
793    }
794
795    /// Measure text using a keyed font.
796    ///
797    /// This is intended for UI/layout work (centering, right-aligning, wrapping decisions).
798    ///
799    /// ## Return value
800    /// Returns a [`TextSize`] where:
801    /// - `width` is the pixel width of the rendered text
802    /// - `height` is the pixel height of the rendered text
803    ///
804    /// Internally, the host returns a packed `u64`:
805    /// `(width << 32) | height`.
806    ///
807    /// ## Fallback
808    /// If `font_key` is not registered, the host measures with Spleen size 16, matching
809    /// the draw fallback behavior of [`text_key`].
810    ///
811    /// ## Caveats
812    /// - The host expects `text` to be valid UTF-8.
813    /// - Metrics are defined by host-side font implementation; if you change fonts or sizes,
814    ///   your layout can change accordingly.
815    pub fn text_measure_key(font_key: &str, text: &str) -> TextSize {
816        let packed = unsafe {
817            sys::graphics_text_measure_key(
818                hash_key(font_key),
819                text.as_ptr() as u32,
820                text.len() as u32,
821            )
822        };
823
824        TextSize {
825            width: (packed >> 32) as u32,
826            height: (packed & 0xFFFF_FFFF) as u32,
827        }
828    }
829}
830
831/// Input API.
832pub mod input {
833    use super::{Button, sys};
834
835    /// Returns true if the specified button is currently held down.
836    pub fn is_button_down(port: u32, btn: Button) -> bool {
837        unsafe { sys::input_is_button_down(port, btn as u32) != 0 }
838    }
839
840    /// Returns true if the specified key is currently held down.
841    pub fn is_key_down(key: u32) -> bool {
842        unsafe { sys::input_is_key_down(key) != 0 }
843    }
844
845    /// Get current mouse X position.
846    pub fn get_mouse_x() -> i32 {
847        unsafe { sys::input_get_mouse_x() }
848    }
849
850    /// Get current mouse Y position.
851    pub fn get_mouse_y() -> i32 {
852        unsafe { sys::input_get_mouse_y() }
853    }
854
855    /// Returns true if the specified mouse button is held down.
856    /// 0 = Left, 1 = Right, 2 = Middle.
857    pub fn is_mouse_down(btn: u32) -> bool {
858        unsafe { sys::input_is_mouse_down(btn) != 0 }
859    }
860}
861
862/// Audio API.
863pub mod audio {
864    use super::sys;
865
866    /// Initialize audio system.
867    pub fn init(sample_rate: u32) -> u32 {
868        unsafe { sys::audio_init(sample_rate) }
869    }
870
871    /// Push a chunk of audio samples.
872    /// Samples are interleaved stereo (L, R, L, R...) signed 16-bit integers.
873    pub fn push_samples(samples: &[i16]) {
874        unsafe { sys::audio_push_samples(samples.as_ptr() as u32, samples.len() as u32) }
875    }
876
877    /// Play a WAV file.
878    /// The WAV data is decoded and played as a one-shot audio channel.
879    pub fn play_wav(data: &[u8]) {
880        unsafe { sys::audio_play_wav(data.as_ptr() as u32, data.len() as u32) }
881    }
882
883    /// Play a QOA file.
884    /// The QOA data is decoded and played as a looping audio channel.
885    pub fn play_qoa(data: &[u8]) {
886        unsafe { sys::audio_play_qoa(data.as_ptr() as u32, data.len() as u32) }
887    }
888
889    /// Play an XM file.
890    /// Play an XM file.
891    /// The XM data is decoded using xmrsplayer and played as a looping audio channel.
892    pub fn play_xm(data: &[u8]) {
893        unsafe { sys::audio_play_xm(data.as_ptr() as u32, data.len() as u32) }
894    }
895}
896
897/// Storage API.
898pub mod storage {
899    use super::sys;
900
901    /// Save data to persistent storage.
902    pub fn save(key: &str, data: &[u8]) {
903        unsafe {
904            sys::storage_save(
905                super::graphics::hash_key(key),
906                data.as_ptr() as u32,
907                data.len() as u32,
908            )
909        }
910    }
911
912    /// Load data from persistent storage.
913    /// Returns `Some(data)` if found, `None` otherwise.
914    pub fn load(key: &str) -> Option<Vec<u8>> {
915        let packed = unsafe { sys::storage_load(super::graphics::hash_key(key)) };
916        if packed == 0 {
917            return None;
918        }
919
920        let ptr = (packed >> 32) as u32;
921        let len = packed as u32;
922
923        // Read data from guest memory
924        let mut data = Vec::with_capacity(len as usize);
925        unsafe {
926            core::ptr::copy_nonoverlapping(ptr as *const u8, data.as_mut_ptr(), len as usize);
927            data.set_len(len as usize);
928        }
929
930        // Free the memory in guest space
931        unsafe { sys::storage_free(ptr, len) };
932
933        Some(data)
934    }
935}
936
937/// System API.
938pub mod system {
939    use super::sys;
940
941    /// Log a message to the host console.
942    pub fn log(message: &str) {
943        unsafe { sys::system_log(message.as_ptr() as u32, message.len() as u32) }
944    }
945
946    /// Get the number of milliseconds since the app started.
947    pub fn millis() -> u64 {
948        unsafe { sys::system_millis() }
949    }
950}
951
952/// Convenience prelude for guest apps.
953pub mod prelude {
954    pub use crate::Button;
955    pub use crate::TextSize;
956    pub use crate::audio;
957    pub use crate::graphics;
958    pub use crate::input;
959    pub use crate::storage;
960    pub use crate::system;
961}
962
963// Keep `c_void` referenced so it doesn't look unused in some configurations.
964#[allow(dead_code)]
965const _C_VOID: *const c_void = core::ptr::null();