wasmcart 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +410 -0
- package/SPEC.md +477 -0
- package/bin/wasmcart-pack.js +257 -0
- package/docs/bind_framebuffer.md +275 -0
- package/docs/fetch.md +105 -0
- package/docs/gl-surface.md +111 -0
- package/docs/input.md +102 -0
- package/docs/networking.md +78 -0
- package/docs/porting.md +88 -0
- package/include/wc_cart.h +144 -0
- package/include/wc_fb.h +275 -0
- package/include/wc_gl.h +224 -0
- package/include/wc_gl_blit.h +129 -0
- package/include/wc_mat4.h +210 -0
- package/include/wc_math.h +116 -0
- package/include/wc_pcm_mixer.h +487 -0
- package/include/wc_vec3.h +80 -0
- package/index.js +3 -0
- package/package.json +55 -0
- package/src/CartHost.js +1713 -0
- package/src/CartHostWeb.js +1381 -0
- package/src/abi.js +94 -0
- package/src/cartWorker.js +201 -0
- package/src/cartWorkerWeb.js +170 -0
- package/src/webgl_imports.js +1483 -0
- package/web.js +3 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Luis Montes
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
# wasmcart
|
|
2
|
+
|
|
3
|
+
**A virtual cartridge format for safe, portable games.** A wasmcart cart is a
|
|
4
|
+
standalone WebAssembly module - a self-contained game that owns its own memory and
|
|
5
|
+
talks to the outside world only through a tiny, well-defined contract: the host
|
|
6
|
+
writes input + timing, calls `wc_render()` each frame, and reads back pixels and
|
|
7
|
+
audio. No filesystem, no syscalls, no ambient authority. Just pixels, sound, input,
|
|
8
|
+
and opt-in networking.
|
|
9
|
+
|
|
10
|
+
Because a cart is only WebAssembly + a fixed ABI, **the same cart runs anywhere a
|
|
11
|
+
conforming host exists** - Node.js, the browser, a libretro core in RetroArch, a
|
|
12
|
+
native player, a terminal - on any OS and any hardware with enough power. Write the
|
|
13
|
+
game once; it runs on all of them, sandboxed.
|
|
14
|
+
|
|
15
|
+
This repository is the **specification** and its **reference implementations**.
|
|
16
|
+
|
|
17
|
+
- 📄 **[SPEC.md](SPEC.md)** - the normative host↔cart contract (current ABI: v3)
|
|
18
|
+
- 🧩 **[`src/abi.js`](src/abi.js)** - the machine-readable contract (constants, layouts)
|
|
19
|
+
- 🖥️ **[`include/wc_cart.h`](include/wc_cart.h)** - the C side of the contract for cart authors
|
|
20
|
+
- 📚 **[`docs/`](docs/)** - per-subsystem guides (input, networking, GL, framebuffer, fetch, porting)
|
|
21
|
+
|
|
22
|
+
## Reference implementations
|
|
23
|
+
|
|
24
|
+
Two reference hosts ship in this package - they define, by example, what a
|
|
25
|
+
conforming host does. Both are pure JavaScript (MIT).
|
|
26
|
+
|
|
27
|
+
| Import | Class | Runs on |
|
|
28
|
+
|--------|-------|---------|
|
|
29
|
+
| `wasmcart` | `CartHost` | Node.js (native GLES3 via a supplied WebGL2 context) |
|
|
30
|
+
| `wasmcart/web` | `CartHostWeb` | Browsers (WebGL2 from a `<canvas>`) |
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
import { CartHost } from 'wasmcart'; // Node
|
|
34
|
+
import { CartHostWeb } from 'wasmcart/web'; // browser
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Other hosts in the wasmcart org (own repos) run the *same* carts: a libretro core
|
|
38
|
+
(`wasmcart-libretro`), native players (`wasmcart-native-host`), and the terminal
|
|
39
|
+
emulator (`retroemu`). See **[The wasmcart org](#the-wasmcart-org)** below.
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm install wasmcart
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Requires Node.js >= 22.
|
|
48
|
+
|
|
49
|
+
## Cart Formats
|
|
50
|
+
|
|
51
|
+
| Format | Description |
|
|
52
|
+
|--------|-------------|
|
|
53
|
+
| `.wasm` | Standalone WASM file, assets embedded as C arrays |
|
|
54
|
+
| `.wasc` | ZIP archive: `manifest.json` + `cart.wasm` + `assets/` (recommended for games with assets) |
|
|
55
|
+
|
|
56
|
+
## The ABI
|
|
57
|
+
|
|
58
|
+
Every cart exports three functions:
|
|
59
|
+
|
|
60
|
+
- **`wc_get_info()`** - returns a pointer to a struct describing the cart's memory layout (framebuffer, audio ring, input pads, save data, timing)
|
|
61
|
+
- **`wc_init()`** - called once at startup
|
|
62
|
+
- **`wc_render()`** - called every frame (~60fps)
|
|
63
|
+
|
|
64
|
+
The cart declares all buffers as static globals. The host reads their locations from `wc_get_info()`, writes input/timing before each frame, and reads pixels/audio after `wc_render()` returns.
|
|
65
|
+
|
|
66
|
+
See [`examples/hello/wasmcart.h`](examples/hello/wasmcart.h) for the complete ABI header.
|
|
67
|
+
|
|
68
|
+
### Rendering Mode
|
|
69
|
+
|
|
70
|
+
Every cart declares its rendering mode via `wc_info_t.gpu_api`:
|
|
71
|
+
|
|
72
|
+
| Value | Mode | Description |
|
|
73
|
+
|-------|------|-------------|
|
|
74
|
+
| 0 | **2D Framebuffer** | Cart writes ARGB8888 pixels to the framebuffer. Host reads and displays them. *(legacy - prefer gpu_api=1)* |
|
|
75
|
+
| 1 | **WebGL2 / GLES3** | Cart renders via GL function imports. The GPU output is the primary display. **Recommended for all carts.** |
|
|
76
|
+
| 2 | **WebGPU** | *(reserved for future use)* |
|
|
77
|
+
| 3 | **Vulkan** | *(reserved for future use)* |
|
|
78
|
+
|
|
79
|
+
**Rendering mode is declared once** in `wc_get_info()` and does not change during the cart's lifetime.
|
|
80
|
+
|
|
81
|
+
### Recommended: All Carts Use GPU (gpu_api = 1)
|
|
82
|
+
|
|
83
|
+
**Every wasmcart host has OpenGL.** The recommended approach is for all carts to set `gpu_api = 1` and render all output through GL - even 2D pixel-buffer carts.
|
|
84
|
+
|
|
85
|
+
For carts that render pixels to a CPU buffer (software renderers, SDL2 2D games), use the `wc_gl_blit()` helper to upload the pixel buffer as a GL texture and draw a fullscreen quad:
|
|
86
|
+
|
|
87
|
+
```c
|
|
88
|
+
#define WC_USE_GL
|
|
89
|
+
#include "wasmcart.h"
|
|
90
|
+
#include "wc_gl_blit.h" // single-header GL blit library
|
|
91
|
+
|
|
92
|
+
// In wc_get_info():
|
|
93
|
+
info.gpu_api = 1;
|
|
94
|
+
|
|
95
|
+
// In wc_render(), after drawing to your pixel buffer:
|
|
96
|
+
wc_gl_blit(my_pixels, width, height); // uploads as GL texture + draws quad
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
This eliminates the host-side complexity of detecting 2D vs GL carts and managing two display paths. One rendering path for all carts, all hosts.
|
|
100
|
+
|
|
101
|
+
**Performance:** `glTexImage2D` is a DMA transfer - the GPU pulls pixel data without CPU waiting. At 1080p, this is significantly faster than the old CPU-side pixel copy + format conversion. 2D games that previously ran at 30fps at 1080p now run at 60fps with this approach.
|
|
102
|
+
|
|
103
|
+
**SDL2 carts** using the `sdl2_wc` backend can enable GL blit automatically:
|
|
104
|
+
```c
|
|
105
|
+
info.gpu_api = 1; // in wc_get_info()
|
|
106
|
+
SDL_WASMCART_SetGLBlit(1); // in wc_init(), after SDL_Init
|
|
107
|
+
// Link with: sdl2_wc/sdl2_gl_blit.c
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
SDL's software renderer draws pixels as usual. The `sdl2_wc` backend uploads them to GL on `SDL_RenderPresent`. No game code changes needed.
|
|
111
|
+
|
|
112
|
+
### Legacy: 2D Framebuffer (gpu_api = 0)
|
|
113
|
+
|
|
114
|
+
Still supported for simplicity. The cart writes ARGB8888 pixels to a framebuffer, the host reads and displays them. No GL imports needed.
|
|
115
|
+
|
|
116
|
+
- Simplest possible cart - just write pixels to a buffer
|
|
117
|
+
- Host handles format conversion and display
|
|
118
|
+
- Performance limited by CPU pixel copy at high resolutions
|
|
119
|
+
|
|
120
|
+
### GPU Carts (gpu_api = 1)
|
|
121
|
+
|
|
122
|
+
- Render via GL function imports (`"gl"` WASM module)
|
|
123
|
+
- The host displays GL output directly (swapBuffers)
|
|
124
|
+
- If the host needs pixels (terminal rendering, screenshots), the **host** performs readback (`glReadPixels`) at whatever frequency it chooses
|
|
125
|
+
- 2D and 3D content can coexist on the same GL context
|
|
126
|
+
|
|
127
|
+
**Compositing** (e.g., 2D HUD over 3D scene) is the cart's responsibility within its chosen GPU API. There is no hybrid mode - a cart that uses GL for 3D and wants a 2D overlay renders both through GL.
|
|
128
|
+
|
|
129
|
+
**Hosts should reject carts with unsupported gpu_api values** gracefully (e.g., "This host does not support WebGPU carts").
|
|
130
|
+
|
|
131
|
+
### Resolution Negotiation
|
|
132
|
+
|
|
133
|
+
The host and cart negotiate resolution through a two-step process:
|
|
134
|
+
|
|
135
|
+
1. **Host → Cart**: Before calling `wc_init()`, the host writes its preferred resolution to `wc_host_info_t.preferred_width` and `preferred_height`. This is a *suggestion* - the host's display capability, not a requirement. A value of 0 means "no preference."
|
|
136
|
+
|
|
137
|
+
2. **Cart → Host**: During `wc_init()`, the cart reads the host's preference and decides its actual rendering resolution. It may use the preference directly, scale it, clamp it, or ignore it entirely. The cart writes its chosen resolution to `wc_info_t.width` and `wc_info_t.height`.
|
|
138
|
+
|
|
139
|
+
After `wc_init()` returns, the host reads the cart's actual width/height. These dimensions define:
|
|
140
|
+
- **2D carts**: the framebuffer size in pixels (ARGB8888, `width × height × 4` bytes)
|
|
141
|
+
- **GL carts**: the viewport/render target dimensions for GL calls
|
|
142
|
+
|
|
143
|
+
**Display scaling** is the host's responsibility:
|
|
144
|
+
- The host creates its display surface at whatever size it wants (its own preferred resolution, fullscreen, user-resizable window, etc.)
|
|
145
|
+
- The host scales the cart's output to fit the display, **preserving the cart's aspect ratio** with letterboxing/pillarboxing as needed
|
|
146
|
+
- The cart never knows or cares about the actual display size
|
|
147
|
+
|
|
148
|
+
**If no preferred resolution is specified** (both 0), the host should create its window at the cart's returned dimensions - a 1:1 pixel match with no scaling.
|
|
149
|
+
|
|
150
|
+
Example flow:
|
|
151
|
+
```
|
|
152
|
+
Host sets preferred: 1920×1080
|
|
153
|
+
Cart reads preference, decides: 640×360 (16:9, manageable for this engine)
|
|
154
|
+
Host creates window: 1920×1080
|
|
155
|
+
Host scales 640×360 → 1920×1080 (exact 3x, no letterboxing needed)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
Host sets preferred: 0×0 (no preference)
|
|
160
|
+
Cart uses its default: 320×240
|
|
161
|
+
Host creates window: 320×240 (1:1 match)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
Host sets preferred: 1920×1080
|
|
166
|
+
Cart ignores it, uses fixed: 960×540
|
|
167
|
+
Host creates window: 1920×1080
|
|
168
|
+
Host scales 960×540 → 1920×1080 (exact 2x)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
This design means:
|
|
172
|
+
- The same `.wasc` cart works on any display size - phone, desktop, 4K TV, RetroArch
|
|
173
|
+
- The cart controls its rendering budget - a simple game can render at 320×240, a complex game at 1080p
|
|
174
|
+
- The host controls the display - letterboxing, fullscreen, window resize all work without cart cooperation
|
|
175
|
+
|
|
176
|
+
### Manifest (`.wasc` carts)
|
|
177
|
+
|
|
178
|
+
The `manifest.json` inside a `.wasc` archive describes the cart:
|
|
179
|
+
|
|
180
|
+
```json
|
|
181
|
+
{
|
|
182
|
+
"name": "My Game",
|
|
183
|
+
"version": "1.0.0",
|
|
184
|
+
"abi": 3,
|
|
185
|
+
"entry": "cart.wasm",
|
|
186
|
+
"players": 2,
|
|
187
|
+
"pointer": true,
|
|
188
|
+
"keyboard": true,
|
|
189
|
+
"net": {
|
|
190
|
+
"websocket": ["api.mygame.com"],
|
|
191
|
+
"data-channel": true
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
All fields except `name`, `abi`, and `entry` are optional. `pointer`, `keyboard`, and `net` are ABI v3 features - gamepad input is always available regardless.
|
|
197
|
+
|
|
198
|
+
### ABI v3: Networking & Extended Input
|
|
199
|
+
|
|
200
|
+
ABI v3 adds opt-in features beyond the core framebuffer/audio/gamepad loop:
|
|
201
|
+
|
|
202
|
+
- **Pointer input** (`"pointer": true`) - host writes `wc_pointer_t[10]` state (unified mouse + multitouch) and optionally calls `wc_ptr_on_down`, `wc_ptr_on_move`, `wc_ptr_on_up` exports
|
|
203
|
+
- **Keyboard input** (`"keyboard": true`) - host writes `uint8_t[32]` key state bitmask (USB HID scancodes) and optionally calls `wc_kb_on_down`, `wc_kb_on_up` exports
|
|
204
|
+
- **WebSocket** (`"net": {"websocket": [...]}`) - cart calls `wc_ws_open`/`send`/`close` imports, host delivers events via `wc_ws_on_open`/`on_message`/`on_close` exports
|
|
205
|
+
- **Data channels** (`"net": {"data-channel": true}`) - peer-to-peer via `wc_dc_send`/`broadcast` imports and `wc_dc_on_connect`/`on_message`/`on_disconnect` exports
|
|
206
|
+
|
|
207
|
+
All v3 exports are optional - the host silently skips events if the cart doesn't export the callbacks. Existing v2 carts work unchanged.
|
|
208
|
+
|
|
209
|
+
## GPU ABI
|
|
210
|
+
|
|
211
|
+
There is **one GPU ABI: WebGL2 (OpenGL ES 3.0)**. All hosts present the same ES 3.0 GL surface. This is the ceiling - no host may expose ES 3.1+ or desktop GL features.
|
|
212
|
+
|
|
213
|
+
A cart that doesn't use the GPU at all can write pixels directly to a shared-memory framebuffer (ARGB8888). This is not a second GPU ABI - it's just pixels in a buffer, no GL involved.
|
|
214
|
+
|
|
215
|
+
### Rules for GPU carts
|
|
216
|
+
|
|
217
|
+
1. **ES 3.0 core only.** Do not use ES 3.1+ features (compute shaders, SSBO, image load/store). The browser host is WebGL2 which is ES 3.0. Native hosts cap `GL_VERSION` to ES 3.0.
|
|
218
|
+
|
|
219
|
+
2. **Declare all GL functions as WASM imports at compile time.** There is no `eglGetProcAddress` or runtime function discovery in WASM. If a function isn't in the cart's import table, it cannot be called.
|
|
220
|
+
|
|
221
|
+
3. **Extensions are informational, not guaranteed.** Hosts pass through real driver extensions via `GL_EXTENSIONS` (some carts like Godot need them for format detection). But extension *function pointers* are only available if the cart declares them as WASM imports. Calling an undeclared extension function traps.
|
|
222
|
+
|
|
223
|
+
4. **GPU engines with getProcAddress callbacks** (Skia Ganesh, ANGLE, etc.) must override `glGetString(GL_EXTENSIONS)` in their callback to return empty - preventing the engine from probing for extension function pointers that don't exist as WASM imports. See the porting notes in the [wasmcart-sdl2](https://github.com/wasmcart/wasmcart-sdl2) repo for the full pattern.
|
|
224
|
+
|
|
225
|
+
5. **Same `.wasc` runs everywhere.** If a cart works in the browser, it must work on Node.js, native, and RetroArch hosts. Staying within ES 3.0 core guarantees this.
|
|
226
|
+
|
|
227
|
+
## Features
|
|
228
|
+
|
|
229
|
+
- **2D framebuffer** - ARGB8888 pixel buffer for software-rendered carts (no GL)
|
|
230
|
+
- **WebGL2 GPU** - one GL ABI everywhere. Cart imports WebGL2 functions, host provides them (native GLES3 on Node.js, WebGL2 in browser). Emscripten's GL output works directly.
|
|
231
|
+
- **Stereo audio** - Float32 or Int16 ring buffer, cart-declared sample rate
|
|
232
|
+
- **Gamepad input** - 4 pads with buttons, analog sticks, triggers (always available)
|
|
233
|
+
- **Pointer input** - unified mouse + touch via shared memory state + event callbacks (opt-in)
|
|
234
|
+
- **Keyboard input** - 256-bit key state bitmask (USB HID scancodes) + event callbacks (opt-in)
|
|
235
|
+
- **WebSocket networking** - event-driven WebSocket API with domain allowlist (opt-in)
|
|
236
|
+
- **Data channels** - peer-to-peer communication via host-managed connections (opt-in)
|
|
237
|
+
- **Save data** - persistent save blob (host manages storage)
|
|
238
|
+
- **Asset loading** - `.wasc` carts load files at runtime via `wc_asset_size()` / `wc_load_asset()`
|
|
239
|
+
- **WASI threads** - carts compiled with wasi-sdk `-pthread` can spawn background threads via pthreads
|
|
240
|
+
|
|
241
|
+
## Node.js API
|
|
242
|
+
|
|
243
|
+
```js
|
|
244
|
+
import { CartHost } from 'wasmcart';
|
|
245
|
+
|
|
246
|
+
const cart = new CartHost();
|
|
247
|
+
await cart.load('game.wasc');
|
|
248
|
+
|
|
249
|
+
// Main loop
|
|
250
|
+
const gamepads = []; // array of { buttons, axes, ... }
|
|
251
|
+
const frame = cart.runFrame(gamepads);
|
|
252
|
+
|
|
253
|
+
// frame.framebuffer - Uint8Array of ARGB pixels (for 2D carts)
|
|
254
|
+
// frame.audio - Int16Array of stereo PCM samples
|
|
255
|
+
// frame.saveData - Uint8Array (if cart uses save)
|
|
256
|
+
|
|
257
|
+
cart.destroy();
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Options
|
|
261
|
+
|
|
262
|
+
```js
|
|
263
|
+
await cart.load('game.wasc', {
|
|
264
|
+
glBackend: gl, // required for GL carts (any WebGL2-compatible context)
|
|
265
|
+
preferredWidth: 800, // hint for resolution negotiation
|
|
266
|
+
preferredHeight: 600,
|
|
267
|
+
saveData: existingSaveBuffer, // restore previous save
|
|
268
|
+
});
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### GL Carts
|
|
272
|
+
|
|
273
|
+
GL carts import functions from the `"gl"` WASM module. The host must provide a WebGL2-compatible context:
|
|
274
|
+
|
|
275
|
+
```js
|
|
276
|
+
// Browser
|
|
277
|
+
const canvas = document.createElement('canvas');
|
|
278
|
+
const gl = canvas.getContext('webgl2');
|
|
279
|
+
await cart.load('gl_game.wasm', { glBackend: gl });
|
|
280
|
+
|
|
281
|
+
// Node.js - provide any WebGL2-compatible context
|
|
282
|
+
await cart.load('gl_game.wasm', { glBackend: glContext });
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## CLI Tools
|
|
286
|
+
|
|
287
|
+
### wasmcart-pack
|
|
288
|
+
|
|
289
|
+
Create `.wasc` archives from a `.wasm` file and an assets directory:
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
npx wasmcart-pack --wasm cart.wasm --assets assets/ -o game.wasc
|
|
293
|
+
npx wasmcart-pack --wasm cart.wasm --assets assets/ -o game.wasc --name "My Game" --version "1.0"
|
|
294
|
+
|
|
295
|
+
# With ABI v3 features
|
|
296
|
+
npx wasmcart-pack --wasm cart.wasm -o game.wasc --pointer --keyboard
|
|
297
|
+
npx wasmcart-pack --wasm cart.wasm -o game.wasc --players 4 --ws api.mygame.com --data-channel
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## Writing Carts
|
|
301
|
+
|
|
302
|
+
### Minimal 2D cart (C + Emscripten)
|
|
303
|
+
|
|
304
|
+
```c
|
|
305
|
+
#include "wasmcart.h"
|
|
306
|
+
#include <string.h>
|
|
307
|
+
|
|
308
|
+
#define WIDTH 320
|
|
309
|
+
#define HEIGHT 240
|
|
310
|
+
|
|
311
|
+
static uint32_t framebuffer[WIDTH * HEIGHT];
|
|
312
|
+
static wc_info_t info;
|
|
313
|
+
|
|
314
|
+
__attribute__((export_name("wc_get_info")))
|
|
315
|
+
wc_info_t* wc_get_info(void) {
|
|
316
|
+
info.version = 3;
|
|
317
|
+
info.width = WIDTH;
|
|
318
|
+
info.height = HEIGHT;
|
|
319
|
+
info.fb_ptr = (uint32_t)(uintptr_t)framebuffer;
|
|
320
|
+
return &info;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
__attribute__((export_name("wc_init")))
|
|
324
|
+
void wc_init(void) {}
|
|
325
|
+
|
|
326
|
+
__attribute__((export_name("wc_render")))
|
|
327
|
+
void wc_render(void) {
|
|
328
|
+
// Fill screen red
|
|
329
|
+
for (int i = 0; i < WIDTH * HEIGHT; i++)
|
|
330
|
+
framebuffer[i] = 0xFFFF0000;
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
```bash
|
|
335
|
+
emcc -sSTANDALONE_WASM=1 -sALLOW_MEMORY_GROWTH=1 --no-entry -O2 -o cart.wasm cart.c
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Shared cart-author libraries
|
|
339
|
+
|
|
340
|
+
The [`include/`](include/) directory ships reusable C headers:
|
|
341
|
+
|
|
342
|
+
| Header | Purpose |
|
|
343
|
+
|--------|---------|
|
|
344
|
+
| `wc_cart.h` | **The C-side contract** - buffer declarations + `WC_FILL_INFO` macro |
|
|
345
|
+
| `wc_fb.h` | 2D drawing (fill_rect, blit, alpha blend) |
|
|
346
|
+
| `wc_gl.h` / `wc_gl_blit.h` | Shader compile/link, VAO/VBO helpers, CPU→GPU blit |
|
|
347
|
+
| `wc_math.h` | sin, cos, sqrt, atan2 (no libm) |
|
|
348
|
+
| `wc_mat4.h` / `wc_vec3.h` | 4x4 matrix + 3D vector ops |
|
|
349
|
+
| `wc_pcm_mixer.h` | Multi-channel PCM mixer + WAV parser |
|
|
350
|
+
|
|
351
|
+
For porting *existing* C/SDL games (the SDL2 backend + `stb_*` decoders), see the
|
|
352
|
+
**wasmcart-sdl2** repo.
|
|
353
|
+
|
|
354
|
+
### Threading (wasi-sdk)
|
|
355
|
+
|
|
356
|
+
Carts can spawn background threads using standard pthreads. Requires wasi-sdk (not Emscripten):
|
|
357
|
+
|
|
358
|
+
```bash
|
|
359
|
+
${WASI_SDK}/bin/clang --target=wasm32-wasip1-threads -pthread \
|
|
360
|
+
-Wl,--import-memory,--shared-memory,--max-memory=67108864 \
|
|
361
|
+
-Wl,--no-entry -nostartfiles -O2 -o cart.wasm cart.c
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
See [`examples/hello_threads/`](examples/hello_threads/) and the Threading section in the Porting Guide (in the [wasmcart-sdl2](https://github.com/wasmcart/wasmcart-sdl2) repo).
|
|
365
|
+
|
|
366
|
+
## Examples
|
|
367
|
+
|
|
368
|
+
34 example carts ranging from minimal (`hello`) to full game ports:
|
|
369
|
+
|
|
370
|
+
| Example | Type | Description |
|
|
371
|
+
|---------|------|-------------|
|
|
372
|
+
| `hello` | 2D | Minimal ABI demo |
|
|
373
|
+
| `hello_gl` | GL | Minimal GL triangle |
|
|
374
|
+
| `hello_threads` | 2D + threads | WASI threads demo |
|
|
375
|
+
| `snake`, `breakout`, `tetris` | 2D | Classic arcade games |
|
|
376
|
+
| `doom` | 2D | DOOM (doomgeneric) |
|
|
377
|
+
| `neverball`, `neverputt` | GL | GL1.x via gl4es |
|
|
378
|
+
| `chromium_bsu` | GL | GL1.x shoot-em-up |
|
|
379
|
+
| `etr` | GL | Extreme Tux Racer (SFML port) |
|
|
380
|
+
| `openarena2` | GL | Quake III Arena (ioquake3) |
|
|
381
|
+
| `flare`, `flare_es` | 2D | FLARE RPG (hand-port and SDL2 backend) |
|
|
382
|
+
|
|
383
|
+
## Documentation
|
|
384
|
+
|
|
385
|
+
- **[SPEC.md](SPEC.md)** - the normative specification
|
|
386
|
+
- **[`docs/`](docs/)** - per-subsystem guides: [input](docs/input.md), [networking](docs/networking.md), [GL surface](docs/gl-surface.md), [framebuffer](docs/bind_framebuffer.md), [fetch](docs/fetch.md), [porting](docs/porting.md)
|
|
387
|
+
- **[`include/`](include/)** - C headers for cart authors (`wc_cart.h` is the contract; `wc_fb.h`/`wc_gl.h`/math/mixer are a lightweight SDK)
|
|
388
|
+
|
|
389
|
+
Porting existing C/SDL games (the SDL2 backend, `stb_*` helpers, and the full
|
|
390
|
+
porting guide) lives in the **wasmcart-sdl2** repo - see below.
|
|
391
|
+
|
|
392
|
+
## The wasmcart org
|
|
393
|
+
|
|
394
|
+
wasmcart is a small ecosystem. This repo is the spec + JS reference hosts; the rest
|
|
395
|
+
are separate repos, all running the *same* carts:
|
|
396
|
+
|
|
397
|
+
| Repo | What it is |
|
|
398
|
+
|------|------------|
|
|
399
|
+
| **wasmcart** (this repo) | Spec, JS reference hosts (`CartHost`, `CartHostWeb`), `wasmcart-pack` |
|
|
400
|
+
| **wasmcart-sdl2** | SDL2 backend + `stb_*` helpers + porting guide - for porting existing C/SDL games |
|
|
401
|
+
| **wasmcart-native-host** | `libwasmcart` C host + `wasmcart-run` standalone SDL2 player (wasmtime / libnode) |
|
|
402
|
+
| **wasmcart-libretro** | libretro core - run carts in RetroArch / RetroDECK |
|
|
403
|
+
| **retroemu** | terminal + SDL host (libretro cores *and* wasmcart carts) |
|
|
404
|
+
| **wasmcart-website** | wasmcart.org - docs site |
|
|
405
|
+
| game port forks | each an upstream game fork on a `wasmcart` branch (`.wasc` shipped as Release artifacts) |
|
|
406
|
+
|
|
407
|
+
## License
|
|
408
|
+
|
|
409
|
+
MIT - see [LICENSE](LICENSE). Compatible with all dependencies (fflate, yauzl,
|
|
410
|
+
yazl - all MIT).
|