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#[cfg(not(feature = "std"))]
15extern crate alloc;
16
17use core::ffi::c_void;
18
19/// Joypad button ids.
20#[repr(u32)]
21#[derive(Copy, Clone, Debug, Eq, PartialEq)]
22pub enum Button {
23    B = 0,
24    Y = 1,
25    Select = 2,
26    Start = 3,
27    Up = 4,
28    Down = 5,
29    Left = 6,
30    Right = 7,
31    A = 8,
32    X = 9,
33    L1 = 10,
34    R1 = 11,
35    L2 = 12,
36    R2 = 13,
37    L3 = 14,
38    R3 = 15,
39}
40
41/// Text size dimensions.
42#[repr(C)]
43#[derive(Copy, Clone, Debug, Eq, PartialEq)]
44pub struct TextSize {
45    pub width: u32,
46    pub height: u32,
47}
48
49/// Low-level raw ABI imports.
50#[allow(non_camel_case_types)]
51pub mod sys {
52    unsafe extern "C" {
53        // Graphics
54        #[link_name = "wasm96_graphics_set_size"]
55        pub fn graphics_set_size(width: u32, height: u32);
56        #[link_name = "wasm96_graphics_set_color"]
57        pub fn graphics_set_color(r: u32, g: u32, b: u32, a: u32);
58        #[link_name = "wasm96_graphics_background"]
59        pub fn graphics_background(r: u32, g: u32, b: u32);
60        #[link_name = "wasm96_graphics_point"]
61        pub fn graphics_point(x: i32, y: i32);
62        #[link_name = "wasm96_graphics_line"]
63        pub fn graphics_line(x1: i32, y1: i32, x2: i32, y2: i32);
64        #[link_name = "wasm96_graphics_rect"]
65        pub fn graphics_rect(x: i32, y: i32, w: u32, h: u32);
66        #[link_name = "wasm96_graphics_rect_outline"]
67        pub fn graphics_rect_outline(x: i32, y: i32, w: u32, h: u32);
68        #[link_name = "wasm96_graphics_circle"]
69        pub fn graphics_circle(x: i32, y: i32, r: u32);
70        #[link_name = "wasm96_graphics_circle_outline"]
71        pub fn graphics_circle_outline(x: i32, y: i32, r: u32);
72        #[link_name = "wasm96_graphics_image"]
73        pub fn graphics_image(x: i32, y: i32, w: u32, h: u32, ptr: u32, len: u32);
74        #[link_name = "wasm96_graphics_image_png"]
75        pub fn graphics_image_png(x: i32, y: i32, ptr: u32, len: u32);
76
77        // --- Keyed resources (hashed keys) ---
78        // SVG
79        #[link_name = "wasm96_graphics_svg_register"]
80        pub fn graphics_svg_register(key: u64, data_ptr: u32, data_len: u32) -> u32;
81        #[link_name = "wasm96_graphics_svg_draw_key"]
82        pub fn graphics_svg_draw_key(key: u64, x: i32, y: i32, w: u32, h: u32);
83        #[link_name = "wasm96_graphics_svg_unregister"]
84        pub fn graphics_svg_unregister(key: u64);
85
86        // GIF
87        #[link_name = "wasm96_graphics_gif_register"]
88        pub fn graphics_gif_register(key: u64, data_ptr: u32, data_len: u32) -> u32;
89        #[link_name = "wasm96_graphics_gif_draw_key"]
90        pub fn graphics_gif_draw_key(key: u64, x: i32, y: i32);
91        #[link_name = "wasm96_graphics_gif_draw_key_scaled"]
92        pub fn graphics_gif_draw_key_scaled(key: u64, x: i32, y: i32, w: u32, h: u32);
93        #[link_name = "wasm96_graphics_gif_unregister"]
94        pub fn graphics_gif_unregister(key: u64);
95
96        // PNG
97        #[link_name = "wasm96_graphics_png_register"]
98        pub fn graphics_png_register(key: u64, data_ptr: u32, data_len: u32) -> u32;
99        #[link_name = "wasm96_graphics_png_draw_key"]
100        pub fn graphics_png_draw_key(key: u64, x: i32, y: i32);
101        #[link_name = "wasm96_graphics_png_draw_key_scaled"]
102        pub fn graphics_png_draw_key_scaled(key: u64, x: i32, y: i32, w: u32, h: u32);
103        #[link_name = "wasm96_graphics_png_unregister"]
104        pub fn graphics_png_unregister(key: u64);
105
106        // Fonts + text (keyed by string)
107        #[link_name = "wasm96_graphics_font_register_ttf"]
108        pub fn graphics_font_register_ttf(key: u64, data_ptr: u32, data_len: u32) -> u32;
109        #[link_name = "wasm96_graphics_font_register_spleen"]
110        pub fn graphics_font_register_spleen(key: u64, size: u32) -> u32;
111        #[link_name = "wasm96_graphics_font_unregister"]
112        pub fn graphics_font_unregister(key: u64);
113
114        #[link_name = "wasm96_graphics_text_key"]
115        pub fn graphics_text_key(x: i32, y: i32, font_key: u64, text_ptr: u32, text_len: u32);
116
117        #[link_name = "wasm96_graphics_text_measure_key"]
118        pub fn graphics_text_measure_key(font_key: u64, text_ptr: u32, text_len: u32) -> u64;
119
120        #[link_name = "wasm96_graphics_triangle"]
121        pub fn graphics_triangle(x1: i32, y1: i32, x2: i32, y2: i32, x3: i32, y3: i32);
122
123        #[link_name = "wasm96_graphics_triangle_outline"]
124        pub fn graphics_triangle_outline(x1: i32, y1: i32, x2: i32, y2: i32, x3: i32, y3: i32);
125
126        #[link_name = "wasm96_graphics_bezier_quadratic"]
127        pub fn graphics_bezier_quadratic(
128            x1: i32,
129            y1: i32,
130            cx: i32,
131            cy: i32,
132            x2: i32,
133            y2: i32,
134            segments: u32,
135        );
136
137        #[link_name = "wasm96_graphics_bezier_cubic"]
138        pub fn graphics_bezier_cubic(
139            x1: i32,
140            y1: i32,
141            cx1: i32,
142            cy1: i32,
143            cx2: i32,
144            cy2: i32,
145            x2: i32,
146            y2: i32,
147            segments: u32,
148        );
149
150        #[link_name = "wasm96_graphics_pill"]
151        pub fn graphics_pill(x: i32, y: i32, w: u32, h: u32);
152
153        #[link_name = "wasm96_graphics_pill_outline"]
154        pub fn graphics_pill_outline(x: i32, y: i32, w: u32, h: u32);
155
156        // Input
157        #[link_name = "wasm96_input_is_button_down"]
158        pub fn input_is_button_down(port: u32, btn: u32) -> u32;
159        #[link_name = "wasm96_input_is_key_down"]
160        pub fn input_is_key_down(key: u32) -> u32;
161        #[link_name = "wasm96_input_get_mouse_x"]
162        pub fn input_get_mouse_x() -> i32;
163        #[link_name = "wasm96_input_get_mouse_y"]
164        pub fn input_get_mouse_y() -> i32;
165        #[link_name = "wasm96_input_is_mouse_down"]
166        pub fn input_is_mouse_down(btn: u32) -> u32;
167
168        // Audio
169        #[link_name = "wasm96_audio_init"]
170        pub fn audio_init(sample_rate: u32) -> u32;
171        #[link_name = "wasm96_audio_push_samples"]
172        pub fn audio_push_samples(ptr: u32, len: u32);
173
174        #[link_name = "wasm96_audio_play_wav"]
175        pub fn audio_play_wav(ptr: u32, len: u32);
176
177        #[link_name = "wasm96_audio_play_qoa"]
178        pub fn audio_play_qoa(ptr: u32, len: u32);
179
180        #[link_name = "wasm96_audio_play_xm"]
181        pub fn audio_play_xm(ptr: u32, len: u32);
182
183        // System
184        #[link_name = "wasm96_system_log"]
185        pub fn system_log(ptr: u32, len: u32);
186        #[link_name = "wasm96_system_millis"]
187        pub fn system_millis() -> u64;
188    }
189}
190
191/// Graphics API.
192pub mod graphics {
193    use super::sys;
194    use crate::TextSize;
195
196    fn hash_key(key: &str) -> u64 {
197        let mut hash: u64 = 0xcbf29ce484222325;
198        for byte in key.bytes() {
199            hash ^= byte as u64;
200            hash = hash.wrapping_mul(0x100000001b3);
201        }
202        hash
203    }
204
205    /// Set the screen dimensions.
206    pub fn set_size(width: u32, height: u32) {
207        unsafe { sys::graphics_set_size(width, height) }
208    }
209
210    /// Set the current drawing color (RGBA).
211    pub fn set_color(r: u8, g: u8, b: u8, a: u8) {
212        unsafe { sys::graphics_set_color(r as u32, g as u32, b as u32, a as u32) }
213    }
214
215    /// Clear the screen with a specific color (RGB).
216    pub fn background(r: u8, g: u8, b: u8) {
217        unsafe { sys::graphics_background(r as u32, g as u32, b as u32) }
218    }
219
220    /// Draw a single pixel at (x, y).
221    pub fn point(x: i32, y: i32) {
222        unsafe { sys::graphics_point(x, y) }
223    }
224
225    /// Draw a line from (x1, y1) to (x2, y2).
226    pub fn line(x1: i32, y1: i32, x2: i32, y2: i32) {
227        unsafe { sys::graphics_line(x1, y1, x2, y2) }
228    }
229
230    /// Draw a filled rectangle.
231    pub fn rect(x: i32, y: i32, w: u32, h: u32) {
232        unsafe { sys::graphics_rect(x, y, w, h) }
233    }
234
235    /// Draw a rectangle outline.
236    pub fn rect_outline(x: i32, y: i32, w: u32, h: u32) {
237        unsafe { sys::graphics_rect_outline(x, y, w, h) }
238    }
239
240    /// Draw a filled circle.
241    pub fn circle(x: i32, y: i32, r: u32) {
242        unsafe { sys::graphics_circle(x, y, r) }
243    }
244
245    /// Draw a circle outline.
246    pub fn circle_outline(x: i32, y: i32, r: u32) {
247        unsafe { sys::graphics_circle_outline(x, y, r) }
248    }
249
250    /// Draw an image/sprite.
251    /// `data` is a slice of RGBA bytes (4 bytes per pixel).
252    pub fn image(x: i32, y: i32, w: u32, h: u32, data: &[u8]) {
253        unsafe { sys::graphics_image(x, y, w, h, data.as_ptr() as u32, data.len() as u32) }
254    }
255
256    /// Draw an image from raw PNG bytes.
257    pub fn image_png(x: i32, y: i32, data: &[u8]) {
258        unsafe { sys::graphics_image_png(x, y, data.as_ptr() as u32, data.len() as u32) }
259    }
260
261    /// Register an encoded PNG (bytes) with the host under a string key.
262    /// Register a GIF resource (encoded bytes) under a string key.
263    /// Returns true on success.
264    pub fn gif_register(key: &str, gif_bytes: &[u8]) -> bool {
265        unsafe {
266            sys::graphics_gif_register(
267                hash_key(key),
268                gif_bytes.as_ptr() as u32,
269                gif_bytes.len() as u32,
270            ) != 0
271        }
272    }
273
274    /// Draw a registered GIF by key at natural size.
275    pub fn gif_draw_key(key: &str, x: i32, y: i32) {
276        unsafe { sys::graphics_gif_draw_key(hash_key(key), x, y) }
277    }
278
279    /// Draw a registered GIF by key scaled.
280    pub fn gif_draw_key_scaled(key: &str, x: i32, y: i32, w: u32, h: u32) {
281        unsafe { sys::graphics_gif_draw_key_scaled(hash_key(key), x, y, w, h) }
282    }
283
284    /// Unregister a GIF by key.
285    pub fn gif_unregister(key: &str) {
286        unsafe { sys::graphics_gif_unregister(hash_key(key)) }
287    }
288
289    /// Draw a filled triangle.
290    pub fn triangle(x1: i32, y1: i32, x2: i32, y2: i32, x3: i32, y3: i32) {
291        unsafe { sys::graphics_triangle(x1, y1, x2, y2, x3, y3) }
292    }
293
294    /// Draw a triangle outline.
295    pub fn triangle_outline(x1: i32, y1: i32, x2: i32, y2: i32, x3: i32, y3: i32) {
296        unsafe { sys::graphics_triangle_outline(x1, y1, x2, y2, x3, y3) }
297    }
298
299    /// Draw a quadratic Bezier curve.
300    pub fn bezier_quadratic(x1: i32, y1: i32, cx: i32, cy: i32, x2: i32, y2: i32, segments: u32) {
301        unsafe { sys::graphics_bezier_quadratic(x1, y1, cx, cy, x2, y2, segments) }
302    }
303
304    /// Draw a cubic Bezier curve.
305    pub fn bezier_cubic(
306        x1: i32,
307        y1: i32,
308        cx1: i32,
309        cy1: i32,
310        cx2: i32,
311        cy2: i32,
312        x2: i32,
313        y2: i32,
314        segments: u32,
315    ) {
316        unsafe { sys::graphics_bezier_cubic(x1, y1, cx1, cy1, cx2, cy2, x2, y2, segments) }
317    }
318
319    /// Draw a filled pill.
320    pub fn pill(x: i32, y: i32, w: u32, h: u32) {
321        unsafe { sys::graphics_pill(x, y, w, h) }
322    }
323
324    /// Draw a pill outline.
325    pub fn pill_outline(x: i32, y: i32, w: u32, h: u32) {
326        unsafe { sys::graphics_pill_outline(x, y, w, h) }
327    }
328
329    /// Register an SVG resource (encoded bytes) under a string key.
330    /// Returns true on success.
331    pub fn svg_register(key: &str, svg_bytes: &[u8]) -> bool {
332        unsafe {
333            sys::graphics_svg_register(
334                hash_key(key),
335                svg_bytes.as_ptr() as u32,
336                svg_bytes.len() as u32,
337            ) != 0
338        }
339    }
340
341    /// Draw a keyed SVG.
342    pub fn svg_draw_key(key: &str, x: i32, y: i32, w: u32, h: u32) {
343        unsafe { sys::graphics_svg_draw_key(hash_key(key), x, y, w, h) }
344    }
345
346    /// Unregister a keyed SVG.
347    pub fn svg_unregister(key: &str) {
348        unsafe { sys::graphics_svg_unregister(hash_key(key)) }
349    }
350
351    /// Register a PNG resource (encoded bytes) under a string key.
352    /// Returns true on success.
353    pub fn png_register(key: &str, png_bytes: &[u8]) -> bool {
354        unsafe {
355            sys::graphics_png_register(
356                hash_key(key),
357                png_bytes.as_ptr() as u32,
358                png_bytes.len() as u32,
359            ) != 0
360        }
361    }
362
363    /// Draw a registered PNG by key at natural size.
364    pub fn png_draw_key(key: &str, x: i32, y: i32) {
365        unsafe { sys::graphics_png_draw_key(hash_key(key), x, y) }
366    }
367
368    /// Draw a registered PNG by key scaled.
369    pub fn png_draw_key_scaled(key: &str, x: i32, y: i32, w: u32, h: u32) {
370        unsafe { sys::graphics_png_draw_key_scaled(hash_key(key), x, y, w, h) }
371    }
372
373    /// Unregister a PNG by key.
374    pub fn png_unregister(key: &str) {
375        unsafe { sys::graphics_png_unregister(hash_key(key)) }
376    }
377
378    /// Register a TTF font under a string key.
379    ///
380    /// Returns `true` if successful.
381    pub fn font_register_ttf(key: &str, data: &[u8]) -> bool {
382        unsafe {
383            sys::graphics_font_register_ttf(hash_key(key), data.as_ptr() as u32, data.len() as u32)
384                != 0
385        }
386    }
387
388    /// Register the built-in Spleen font under a string key.
389    ///
390    /// Returns `true` if successful.
391    pub fn font_register_spleen(key: &str, size: u32) -> bool {
392        unsafe { sys::graphics_font_register_spleen(hash_key(key), size) != 0 }
393    }
394
395    /// Unregister a font.
396    pub fn font_unregister(key: &str) {
397        unsafe { sys::graphics_font_unregister(hash_key(key)) }
398    }
399
400    /// Draw text using a registered font.
401    pub fn text_key(x: i32, y: i32, font_key: &str, text: &str) {
402        unsafe {
403            sys::graphics_text_key(
404                x,
405                y,
406                hash_key(font_key),
407                text.as_ptr() as u32,
408                text.len() as u32,
409            )
410        }
411    }
412
413    /// Measure text using a registered font.
414    pub fn text_measure_key(font_key: &str, text: &str) -> TextSize {
415        let packed = unsafe {
416            sys::graphics_text_measure_key(
417                hash_key(font_key),
418                text.as_ptr() as u32,
419                text.len() as u32,
420            )
421        };
422        TextSize {
423            width: (packed >> 32) as u32,
424            height: packed as u32,
425        }
426    }
427}
428
429/// Input API.
430pub mod input {
431    use super::{Button, sys};
432
433    /// Returns true if the specified button is currently held down.
434    pub fn is_button_down(port: u32, btn: Button) -> bool {
435        unsafe { sys::input_is_button_down(port, btn as u32) != 0 }
436    }
437
438    /// Returns true if the specified key is currently held down.
439    pub fn is_key_down(key: u32) -> bool {
440        unsafe { sys::input_is_key_down(key) != 0 }
441    }
442
443    /// Get current mouse X position.
444    pub fn get_mouse_x() -> i32 {
445        unsafe { sys::input_get_mouse_x() }
446    }
447
448    /// Get current mouse Y position.
449    pub fn get_mouse_y() -> i32 {
450        unsafe { sys::input_get_mouse_y() }
451    }
452
453    /// Returns true if the specified mouse button is held down.
454    /// 0 = Left, 1 = Right, 2 = Middle.
455    pub fn is_mouse_down(btn: u32) -> bool {
456        unsafe { sys::input_is_mouse_down(btn) != 0 }
457    }
458}
459
460/// Audio API.
461pub mod audio {
462    use super::sys;
463
464    /// Initialize audio system.
465    pub fn init(sample_rate: u32) -> u32 {
466        unsafe { sys::audio_init(sample_rate) }
467    }
468
469    /// Push a chunk of audio samples.
470    /// Samples are interleaved stereo (L, R, L, R...) signed 16-bit integers.
471    pub fn push_samples(samples: &[i16]) {
472        unsafe { sys::audio_push_samples(samples.as_ptr() as u32, samples.len() as u32) }
473    }
474
475    /// Play a WAV file.
476    /// The WAV data is decoded and played as a one-shot audio channel.
477    pub fn play_wav(data: &[u8]) {
478        unsafe { sys::audio_play_wav(data.as_ptr() as u32, data.len() as u32) }
479    }
480
481    /// Play a QOA file.
482    /// The QOA data is decoded and played as a looping audio channel.
483    pub fn play_qoa(data: &[u8]) {
484        unsafe { sys::audio_play_qoa(data.as_ptr() as u32, data.len() as u32) }
485    }
486
487    /// Play an XM file.
488    /// Play an XM file.
489    /// The XM data is decoded using xmrsplayer and played as a looping audio channel.
490    pub fn play_xm(data: &[u8]) {
491        unsafe { sys::audio_play_xm(data.as_ptr() as u32, data.len() as u32) }
492    }
493}
494
495/// System API.
496pub mod system {
497    use super::sys;
498
499    /// Log a message to the host console.
500    pub fn log(message: &str) {
501        unsafe { sys::system_log(message.as_ptr() as u32, message.len() as u32) }
502    }
503
504    /// Get the number of milliseconds since the app started.
505    pub fn millis() -> u64 {
506        unsafe { sys::system_millis() }
507    }
508}
509
510/// Convenience prelude for guest apps.
511pub mod prelude {
512    pub use crate::Button;
513    pub use crate::TextSize;
514    pub use crate::audio;
515    pub use crate::graphics;
516    pub use crate::input;
517    pub use crate::system;
518}
519
520// Keep `c_void` referenced so it doesn't look unused in some configurations.
521#[allow(dead_code)]
522const _C_VOID: *const c_void = core::ptr::null();