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
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# wasmcart GL Surface Specification
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The wasmcart GL surface is **WebGL2 / OpenGL ES 3.0**. This is the same on all hosts:
|
|
6
|
+
browser, Node.js, wasmcart-native, and RetroArch (libretro).
|
|
7
|
+
|
|
8
|
+
The host reports `GL_VERSION = "OpenGL ES 3.0 wasmcart"` regardless of the actual
|
|
9
|
+
driver. Real driver extensions pass through for engines that need them (e.g., Godot
|
|
10
|
+
texture format detection).
|
|
11
|
+
|
|
12
|
+
## Shader Requirements
|
|
13
|
+
|
|
14
|
+
All shaders MUST use one of:
|
|
15
|
+
- `#version 100` (GLES 2.0 - works everywhere)
|
|
16
|
+
- `#version 300 es` (GLES 3.0 / WebGL2 - recommended)
|
|
17
|
+
|
|
18
|
+
Desktop GL version strings (`#version 110`, `#version 120`, `#version 330 core`)
|
|
19
|
+
are NOT supported. The host does NOT translate shaders.
|
|
20
|
+
|
|
21
|
+
## Texture Formats
|
|
22
|
+
|
|
23
|
+
The host translates legacy/unsized formats to ES 3.0 equivalents:
|
|
24
|
+
|
|
25
|
+
| Legacy Format | ES 3.0 Internal Format | ES 3.0 Format |
|
|
26
|
+
|--------------|----------------------|--------------|
|
|
27
|
+
| `GL_LUMINANCE` | `GL_R8` | `GL_RED` |
|
|
28
|
+
| `GL_LUMINANCE_ALPHA` | `GL_RG8` | `GL_RG` |
|
|
29
|
+
| `GL_ALPHA` | `GL_R8` | `GL_RED` |
|
|
30
|
+
| `GL_RGB` (ubyte) | `GL_RGB8` | `GL_RGB` |
|
|
31
|
+
| `GL_RGBA` (ubyte) | `GL_RGBA8` | `GL_RGBA` |
|
|
32
|
+
|
|
33
|
+
Carts SHOULD use sized internal formats (`GL_RGBA8`, `GL_R8`, etc.) directly.
|
|
34
|
+
The host fixup exists for compatibility with legacy GL code.
|
|
35
|
+
|
|
36
|
+
## Porting GL 1.x Games
|
|
37
|
+
|
|
38
|
+
Games written for OpenGL 1.x (fixed-function pipeline) need a renderer rewrite
|
|
39
|
+
to target ES 3.0. There are two approaches:
|
|
40
|
+
|
|
41
|
+
### 1. Purpose-built gl_compat (recommended)
|
|
42
|
+
|
|
43
|
+
Write a small translation layer (~500-1000 lines) that replaces GL 1.x calls
|
|
44
|
+
with ES 3.0 shader-based equivalents. This is compiled into the cart.
|
|
45
|
+
|
|
46
|
+
Example: `chromium_bsu/gl_compat.cpp` - batch renderer with `#version 300 es`
|
|
47
|
+
shaders. Handles glBegin/glEnd, matrix stack, texture state, blending.
|
|
48
|
+
|
|
49
|
+
Advantages:
|
|
50
|
+
- No external dependencies
|
|
51
|
+
- Small binary size
|
|
52
|
+
- Only translates what the game actually uses
|
|
53
|
+
- Full ES 3.0 compliance
|
|
54
|
+
|
|
55
|
+
### 2. gl4es (legacy compatibility)
|
|
56
|
+
|
|
57
|
+
Use [ptitSeb/gl4es](https://github.com/ptitSeb/gl4es) to translate GL 1.x → GLES 2.0.
|
|
58
|
+
gl4es is compiled to WASM as a static library and linked into the cart.
|
|
59
|
+
|
|
60
|
+
Current gl4es carts: Neverball, Neverputt, Extreme Tux Racer.
|
|
61
|
+
|
|
62
|
+
Limitations:
|
|
63
|
+
- gl4es only targets GLES 2.0, not ES 3.0
|
|
64
|
+
- Requires host-side format fixup and extension injection
|
|
65
|
+
- Large dependency (~150K lines)
|
|
66
|
+
- Shader version issues on Core 3.3 contexts (RetroArch desktop)
|
|
67
|
+
|
|
68
|
+
gl4es patches required for wasmcart:
|
|
69
|
+
- `hardext.c`: disable `#version 120` detection, force `esversion = 3`
|
|
70
|
+
- Build with `NO_LOADER=ON`, `NOEGL=ON`, `STATICLIB=ON`
|
|
71
|
+
|
|
72
|
+
### Games that DON'T need translation
|
|
73
|
+
|
|
74
|
+
Games with native GLES 2.0/3.0 renderers work on all hosts with zero translation:
|
|
75
|
+
- Godot 4.x (GLES3 Compatibility renderer)
|
|
76
|
+
- ioquake3 renderergl2 (GLES2/3 path)
|
|
77
|
+
- GZDoom (GLES2 renderer)
|
|
78
|
+
- Any Emscripten/WebGL2 game
|
|
79
|
+
- SDL2 games using GLES backend
|
|
80
|
+
|
|
81
|
+
## FBO Redirect
|
|
82
|
+
|
|
83
|
+
The host intercepts `glBindFramebuffer(GL_FRAMEBUFFER, 0)` and redirects to an
|
|
84
|
+
internal FBO with depth24+stencil8 attachments. This FBO is then blitted to the
|
|
85
|
+
display surface (EGL window, RetroArch hw_render FBO, or canvas backbuffer).
|
|
86
|
+
|
|
87
|
+
Carts should bind FBO 0 when they want to render to the "screen". The host
|
|
88
|
+
handles the actual presentation.
|
|
89
|
+
|
|
90
|
+
## VAO Handling
|
|
91
|
+
|
|
92
|
+
On GLES 3.0, VAO 0 is the default VAO. On Core 3.3 (RetroArch desktop),
|
|
93
|
+
VAO 0 is invalid. The host creates an isolated cart VAO and redirects
|
|
94
|
+
`glBindVertexArray(0)` to it. The host also binds this VAO at frame start
|
|
95
|
+
for carts that don't use VAOs at all (gl4es).
|
|
96
|
+
|
|
97
|
+
## Extension Passthrough
|
|
98
|
+
|
|
99
|
+
`GL_EXTENSIONS` returns real driver extensions. On Core 3.3 contexts,
|
|
100
|
+
the host also injects GLES-equivalent extension names (`GL_OES_*`, `GL_EXT_*`)
|
|
101
|
+
so GLES-targeting code (like gl4es) can detect features.
|
|
102
|
+
|
|
103
|
+
`GL_SHADING_LANGUAGE_VERSION` passes through real values so carts can
|
|
104
|
+
detect Core vs GLES contexts if needed.
|
|
105
|
+
|
|
106
|
+
## Buffer Orphaning
|
|
107
|
+
|
|
108
|
+
The host applies buffer orphaning on `glBufferSubData` with offset 0:
|
|
109
|
+
`glBufferData(target, size, NULL, GL_STREAM_DRAW)` is called first to
|
|
110
|
+
avoid GPU sync stalls on mobile drivers (Mali, Adreno). This is transparent
|
|
111
|
+
to the cart.
|
package/docs/input.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Input - Gamepad, Pointer, Keyboard
|
|
2
|
+
|
|
3
|
+
## Gamepad Input (Always Available)
|
|
4
|
+
|
|
5
|
+
Every wasmcart host normalizes controller input to the **W3C Standard Gamepad** layout before writing to `wc_pads[]`. The cart always sees the same button/axis mapping regardless of the physical controller.
|
|
6
|
+
|
|
7
|
+
### Normalization Pipeline
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Physical controller (Xbox, PS5, 8BitDo, Switch Pro, etc.)
|
|
11
|
+
↓
|
|
12
|
+
Host SDL / HID driver
|
|
13
|
+
↓
|
|
14
|
+
SDL GameController mapping (gamecontrollerdb.txt - 2000+ controller definitions)
|
|
15
|
+
↓
|
|
16
|
+
Normalized to W3C Standard Gamepad layout
|
|
17
|
+
↓
|
|
18
|
+
Written to wc_pad_t[4] shared memory
|
|
19
|
+
↓
|
|
20
|
+
Cart reads buttons/axes - every controller looks the same
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This means:
|
|
24
|
+
- An Xbox controller's A button = `WC_BTN_A` = W3C `buttons[0]`
|
|
25
|
+
- A PlayStation DualSense's X button = `WC_BTN_A` = W3C `buttons[0]`
|
|
26
|
+
- A Nintendo Pro Controller's B button = `WC_BTN_A` = W3C `buttons[0]`
|
|
27
|
+
- A virtual touchscreen overlay's south button = `WC_BTN_A` = W3C `buttons[0]`
|
|
28
|
+
|
|
29
|
+
The cart never needs to know what controller is physically connected.
|
|
30
|
+
|
|
31
|
+
### wc_pad_t Layout (16 bytes per pad)
|
|
32
|
+
|
|
33
|
+
```c
|
|
34
|
+
typedef struct {
|
|
35
|
+
uint16_t buttons; // Bitmask (WC_BTN_A, WC_BTN_B, etc.)
|
|
36
|
+
int16_t left_x; // Left stick X: -32768 to 32767
|
|
37
|
+
int16_t left_y; // Left stick Y: -32768 to 32767
|
|
38
|
+
int16_t right_x; // Right stick X: -32768 to 32767
|
|
39
|
+
int16_t right_y; // Right stick Y: -32768 to 32767
|
|
40
|
+
uint8_t left_trigger; // Left trigger: 0-255
|
|
41
|
+
uint8_t right_trigger; // Right trigger: 0-255
|
|
42
|
+
uint8_t connected; // 1 if controller is connected
|
|
43
|
+
uint8_t _pad[3]; // Alignment padding
|
|
44
|
+
} wc_pad_t;
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Button Mapping (W3C Standard Gamepad)
|
|
48
|
+
|
|
49
|
+
| Bit | Constant | W3C Index | Xbox | PlayStation | Nintendo |
|
|
50
|
+
|-----|----------|-----------|------|-------------|----------|
|
|
51
|
+
| 0 | `WC_BTN_A` | buttons[0] | A | Cross | B |
|
|
52
|
+
| 1 | `WC_BTN_B` | buttons[1] | B | Circle | A |
|
|
53
|
+
| 2 | `WC_BTN_X` | buttons[2] | X | Square | Y |
|
|
54
|
+
| 3 | `WC_BTN_Y` | buttons[3] | Y | Triangle | X |
|
|
55
|
+
| 4 | `WC_BTN_L` | buttons[4] | LB | L1 | L |
|
|
56
|
+
| 5 | `WC_BTN_R` | buttons[5] | RB | R1 | R |
|
|
57
|
+
| 6 | - | buttons[6] | LT (analog) | L2 (analog) | ZL |
|
|
58
|
+
| 7 | - | buttons[7] | RT (analog) | R2 (analog) | ZR |
|
|
59
|
+
| 8 | `WC_BTN_SELECT` | buttons[8] | Back/View | Share | - |
|
|
60
|
+
| 9 | `WC_BTN_START` | buttons[9] | Start/Menu | Options | + |
|
|
61
|
+
| 10 | `WC_BTN_UP` | buttons[12] | D-pad Up | D-pad Up | D-pad Up |
|
|
62
|
+
| 11 | `WC_BTN_DOWN` | buttons[13] | D-pad Down | D-pad Down | D-pad Down |
|
|
63
|
+
| 12 | `WC_BTN_LEFT` | buttons[14] | D-pad Left | D-pad Left | D-pad Left |
|
|
64
|
+
| 13 | `WC_BTN_RIGHT` | buttons[15] | D-pad Right | D-pad Right | D-pad Right |
|
|
65
|
+
| 14 | `WC_BTN_L3` | buttons[10] | LS Click | L3 | LS Click |
|
|
66
|
+
| 15 | `WC_BTN_R3` | buttons[11] | RS Click | R3 | RS Click |
|
|
67
|
+
|
|
68
|
+
Triggers (buttons[6]/[7]) are analog - use `left_trigger`/`right_trigger` (0-255) for analog values, or the button bit for digital pressed/not-pressed.
|
|
69
|
+
|
|
70
|
+
### Axes
|
|
71
|
+
|
|
72
|
+
| Axis | W3C Index | Range |
|
|
73
|
+
|------|-----------|-------|
|
|
74
|
+
| Left Stick X | axes[0] | -32768 to 32767 (left to right) |
|
|
75
|
+
| Left Stick Y | axes[1] | -32768 to 32767 (up to down) |
|
|
76
|
+
| Right Stick X | axes[2] | -32768 to 32767 |
|
|
77
|
+
| Right Stick Y | axes[3] | -32768 to 32767 |
|
|
78
|
+
|
|
79
|
+
Note: W3C Gamepad API uses -1.0 to 1.0 floats. wasmcart uses int16. JS game runtimes (wasmcart-jsgame) convert: `axes[i] = pad.left_x / 32767.0`.
|
|
80
|
+
|
|
81
|
+
### For JS Game Runtimes
|
|
82
|
+
|
|
83
|
+
When exposing `navigator.getGamepads()` to JavaScript games, the gamepad object **must** include `mapping: 'standard'`. Many game frameworks (Phaser, custom engines) filter gamepads by this property:
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
// Common pattern in game code:
|
|
87
|
+
gamepads = navigator.getGamepads().filter(gp => gp && gp.mapping === 'standard');
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Without `mapping: 'standard'`, the gamepad is invisible to the game even though input data is available.
|
|
91
|
+
|
|
92
|
+
### 4-Player Support
|
|
93
|
+
|
|
94
|
+
`wc_pads[0]` through `wc_pads[3]` support up to 4 controllers. The host maps physical controllers to pad slots in connection order. `connected` field indicates which slots are active.
|
|
95
|
+
|
|
96
|
+
## Pointer Input (Opt-In, ABI v3)
|
|
97
|
+
|
|
98
|
+
Unified mouse + multitouch. Cart declares `"pointer": true` in manifest. Host writes `wc_pointer_t[10]` state each frame.
|
|
99
|
+
|
|
100
|
+
## Keyboard Input (Opt-In, ABI v3)
|
|
101
|
+
|
|
102
|
+
256-bit key state bitmask using USB HID scancodes. Cart declares `"keyboard": true` in manifest. Host writes `uint8_t[32]` bitmask each frame.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Networking - Future Design Notes
|
|
2
|
+
|
|
3
|
+
## Current ABI
|
|
4
|
+
|
|
5
|
+
WebSocket client-only:
|
|
6
|
+
- `wc_ws_open(url, len)` - connect outbound
|
|
7
|
+
- `wc_ws_send` / `wc_ws_send_text` - send data
|
|
8
|
+
- `wc_ws_close` / `wc_ws_state` - lifecycle
|
|
9
|
+
- `wc_ws_on_open` / `wc_ws_on_message` / `wc_ws_on_close` - callbacks (host → cart)
|
|
10
|
+
|
|
11
|
+
Manifest allowlist controls which domains the cart can connect to.
|
|
12
|
+
|
|
13
|
+
## Planned: Peer-to-Peer Multiplayer
|
|
14
|
+
|
|
15
|
+
### Concept
|
|
16
|
+
One player "hosts", another "joins" - like classic LAN play. No cloud infrastructure required.
|
|
17
|
+
|
|
18
|
+
### Cart-Side API
|
|
19
|
+
The cart uses the same WebSocket ABI with one addition:
|
|
20
|
+
|
|
21
|
+
- `wc_ws_listen(port)` - cart thinks it's hosting a WebSocket server
|
|
22
|
+
- `wc_ws_on_connect(conn_id)` - host notifies cart of new peer
|
|
23
|
+
- All data flows through existing `wc_ws_send` / `wc_ws_on_message`
|
|
24
|
+
|
|
25
|
+
The cart has NO idea what transport is underneath.
|
|
26
|
+
|
|
27
|
+
### Host-Side Reality
|
|
28
|
+
The host implements "listen" as WebRTC data channels:
|
|
29
|
+
|
|
30
|
+
1. Cart calls `wc_ws_listen` → host creates WebRTC peer connection
|
|
31
|
+
2. Signaling handshake (SDP/ICE exchange) - could be:
|
|
32
|
+
- Local network discovery (mDNS/broadcast)
|
|
33
|
+
- QR code displayed on screen
|
|
34
|
+
- Simple signaling server (tiny, stateless)
|
|
35
|
+
3. Data channel established → host calls `wc_ws_on_connect(conn_id)`
|
|
36
|
+
4. All subsequent send/receive goes through the data channel
|
|
37
|
+
5. Cart sees it as a normal WebSocket connection
|
|
38
|
+
|
|
39
|
+
### Why WebRTC Under the Hood
|
|
40
|
+
- **NAT traversal** - works over the internet, not just LAN
|
|
41
|
+
- **Peer-to-peer** - no relay server needed (STUN is free, Google/Cloudflare run public ones)
|
|
42
|
+
- **Low latency** - UDP-based data channels, ideal for games
|
|
43
|
+
- **No infrastructure** - the user doesn't need to run servers
|
|
44
|
+
- **Browser-compatible** - browser wasmcart hosts get WebRTC for free
|
|
45
|
+
|
|
46
|
+
### Why the Cart Doesn't Know
|
|
47
|
+
- Same ABI for LAN and internet play
|
|
48
|
+
- Cart code stays simple - just WebSocket send/receive
|
|
49
|
+
- Host can swap transport (WebRTC, actual WebSocket, Bluetooth, etc.) without cart changes
|
|
50
|
+
- Security - cart can't access raw WebRTC APIs (no fingerprinting, no media capture)
|
|
51
|
+
|
|
52
|
+
### Signaling Options (Lightweight)
|
|
53
|
+
- **QR code** - host player's screen shows QR with SDP offer, joining player scans it
|
|
54
|
+
- **Local network** - mDNS/UDP broadcast discovers peers on same WiFi
|
|
55
|
+
- **Room codes** - 4-digit code, tiny stateless signaling relay matches peers
|
|
56
|
+
- **Manual IP** - old school, type in the IP (works on LAN without any service)
|
|
57
|
+
|
|
58
|
+
### Example Flow
|
|
59
|
+
```
|
|
60
|
+
Player 1 (host):
|
|
61
|
+
game calls wc_ws_listen(0)
|
|
62
|
+
host creates WebRTC peer, shows room code "ABCD" on screen
|
|
63
|
+
waits for peer...
|
|
64
|
+
|
|
65
|
+
Player 2 (join):
|
|
66
|
+
game calls wc_ws_open("room:ABCD")
|
|
67
|
+
host creates WebRTC peer, signals via room code
|
|
68
|
+
data channel established
|
|
69
|
+
|
|
70
|
+
Both carts:
|
|
71
|
+
wc_ws_on_connect(conn_id) fires
|
|
72
|
+
wc_ws_send(conn_id, game_packet, len) - just works
|
|
73
|
+
wc_ws_on_message(conn_id, data, len) - just works
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Planned: wc_fetch
|
|
77
|
+
|
|
78
|
+
See [fetch.md](fetch.md). Lower priority - games ship assets in .wasc, WebSocket covers real-time comms. Fetch is mainly for leaderboards/analytics which most non-browser games don't need.
|
package/docs/porting.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Porting Games to wasmcart
|
|
2
|
+
|
|
3
|
+
## Target: ES 3.0 / WebGL2
|
|
4
|
+
|
|
5
|
+
wasmcart's GL surface is ES 3.0. All carts should use GLES 2.0 or 3.0
|
|
6
|
+
shaders. See [gl-surface.md](gl-surface.md) for the full spec.
|
|
7
|
+
|
|
8
|
+
## Easiest Ports (zero GL translation needed)
|
|
9
|
+
|
|
10
|
+
| Source | Examples | Notes |
|
|
11
|
+
|--------|----------|-------|
|
|
12
|
+
| GLES 2.0/3.0 native | Godot, GZDoom | Engine handles GL, just wire up ABI |
|
|
13
|
+
| SDL2 + GLES | Many modern games | Use wasmcart SDL2 backend (sdl2_wc/) |
|
|
14
|
+
| Emscripten/WebGL2 | Browser games | Already WASM, add wasmcart ABI |
|
|
15
|
+
| ioquake3-based | OpenArena, Quake 3 | renderergl2 has GLES path |
|
|
16
|
+
| Canvas 2D / pixel buffer | Retro games | Use wc_gl_blit.h for GPU upload |
|
|
17
|
+
|
|
18
|
+
## GL 1.x Games
|
|
19
|
+
|
|
20
|
+
Games using OpenGL 1.x (glBegin/glEnd, fixed-function lighting, display lists)
|
|
21
|
+
need a renderer rewrite. Two approaches:
|
|
22
|
+
|
|
23
|
+
### Approach 1: Custom gl_compat (recommended)
|
|
24
|
+
|
|
25
|
+
Write a purpose-built ES 3.0 batch renderer for the specific game.
|
|
26
|
+
Only translate the GL 1.x calls the game actually uses.
|
|
27
|
+
|
|
28
|
+
**Example:** Chromium B.S.U. - `gl_compat.cpp` (~500 lines)
|
|
29
|
+
- Replaces immediate mode with VBO batching
|
|
30
|
+
- `#version 300 es` shaders
|
|
31
|
+
- Matrix stack, texture state, blending
|
|
32
|
+
- Zero external dependencies
|
|
33
|
+
- Compiled with `-include chromium_compat.h` (zero original files modified)
|
|
34
|
+
|
|
35
|
+
**Upstream value:** These renderers can be submitted as PRs to the original
|
|
36
|
+
game projects, benefiting the entire community.
|
|
37
|
+
|
|
38
|
+
### Approach 2: gl4es (quick start, more complexity)
|
|
39
|
+
|
|
40
|
+
Use [gl4es](https://github.com/ptitSeb/gl4es) for automatic translation.
|
|
41
|
+
See [gl-surface.md](gl-surface.md#gl4es-legacy-compatibility) for details.
|
|
42
|
+
|
|
43
|
+
## Porting Checklist
|
|
44
|
+
|
|
45
|
+
1. **Build to WASM** - Emscripten with `-sWASM=1`
|
|
46
|
+
2. **Export ABI** - `wc_get_info`, `wc_init`, `wc_render`
|
|
47
|
+
3. **Set gpu_api** - `wc_info_t.gpu_api = 1` (GL) for all carts
|
|
48
|
+
4. **Use ES 3.0 shaders** - `#version 100` or `#version 300 es`
|
|
49
|
+
5. **Assets via .wasc** - Pack with `wasmcart-pack`, load via `wc_asset_size`/`wc_load_asset`
|
|
50
|
+
6. **Audio** - Write to ring buffer, set sample rate + format flags
|
|
51
|
+
7. **Input** - Read `wc_pad_t` array (Xbox/W3C button layout)
|
|
52
|
+
8. **Test on all hosts** - Browser, Node.js, wasmcart-native, RetroArch
|
|
53
|
+
|
|
54
|
+
## Shared Porting Libraries
|
|
55
|
+
|
|
56
|
+
Located at `wasmcart/porting/include/`:
|
|
57
|
+
|
|
58
|
+
| Header | Purpose |
|
|
59
|
+
|--------|---------|
|
|
60
|
+
| `wc_cart.h` | Buffer declarations, WC_FILL_INFO macro |
|
|
61
|
+
| `wc_gl.h` | Shader compile/link, VAO/VBO helpers |
|
|
62
|
+
| `wc_gl_blit.h` | Upload CPU pixels as GL texture (2D→GL) |
|
|
63
|
+
| `wc_fb.h` | 2D framebuffer drawing (fill_rect, blit) |
|
|
64
|
+
| `wc_math.h` | sin, cos, sqrt, atan2, clamp, lerp |
|
|
65
|
+
| `wc_mat4.h` | 4x4 column-major matrix ops |
|
|
66
|
+
| `wc_vec3.h` | 3D vector operations |
|
|
67
|
+
| `wc_pcm_mixer.h` | Multi-channel PCM audio mixer |
|
|
68
|
+
| `wc_sdl_stubs.h` | SDL2 type defs + no-op stubs |
|
|
69
|
+
| `stb_image.h` | Image loading (JPEG, PNG, BMP) |
|
|
70
|
+
| `audio_bridge.h/c` | SDL2_mixer → ring buffer bridge |
|
|
71
|
+
| `emstubs.c` | Emscripten runtime stubs |
|
|
72
|
+
|
|
73
|
+
## SDL2 Games (Emscripten Backend)
|
|
74
|
+
|
|
75
|
+
Use the reusable `sdl2_wc/` backends for SDL2 games:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Compile: use SDL2 headers from Emscripten
|
|
79
|
+
-sUSE_SDL=2 # at compile time (headers only)
|
|
80
|
+
|
|
81
|
+
# Link: use wasmcart's SDL2 backend, not Emscripten's
|
|
82
|
+
-sUSE_SDL=0 # at link time
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The SDL2 backend provides video (GL surface), audio (ring buffer),
|
|
86
|
+
and input (gamepad) - all wired to the wasmcart ABI.
|
|
87
|
+
|
|
88
|
+
Validated on: Neverball ES, Neverputt ES, Celeste Classic ES, Flare ES.
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* wc_cart.h - Cart boilerplate macros for wasmcart
|
|
3
|
+
*
|
|
4
|
+
* Reduces the ~40 lines of buffer declarations + wc_get_info() that
|
|
5
|
+
* every cart copy-pastes. Include this AFTER wasmcart.h.
|
|
6
|
+
*
|
|
7
|
+
* USAGE (GL cart):
|
|
8
|
+
*
|
|
9
|
+
* #define WC_USE_GL
|
|
10
|
+
* #include "wasmcart.h"
|
|
11
|
+
* #include "wc_cart.h"
|
|
12
|
+
*
|
|
13
|
+
* // Define your resolution and audio settings:
|
|
14
|
+
* #define DEFAULT_WIDTH 640
|
|
15
|
+
* #define DEFAULT_HEIGHT 480
|
|
16
|
+
* #define MAX_WIDTH 1920
|
|
17
|
+
* #define MAX_HEIGHT 1080
|
|
18
|
+
* #define AUDIO_CAP 4096
|
|
19
|
+
*
|
|
20
|
+
* // Declare all cart buffers at once:
|
|
21
|
+
* WC_CART_BUFFERS;
|
|
22
|
+
*
|
|
23
|
+
* // In your wc_get_info():
|
|
24
|
+
* WC_EXPORT wc_info_t *wc_get_info(void) {
|
|
25
|
+
* WC_FILL_INFO(0);
|
|
26
|
+
* return &wc_info;
|
|
27
|
+
* }
|
|
28
|
+
*
|
|
29
|
+
* USAGE (2D framebuffer cart):
|
|
30
|
+
*
|
|
31
|
+
* #include "wasmcart.h"
|
|
32
|
+
* #include "wc_cart.h"
|
|
33
|
+
*
|
|
34
|
+
* #define DEFAULT_WIDTH 320
|
|
35
|
+
* #define DEFAULT_HEIGHT 240
|
|
36
|
+
* #define MAX_WIDTH 320
|
|
37
|
+
* #define MAX_HEIGHT 240
|
|
38
|
+
* #define AUDIO_CAP 4096
|
|
39
|
+
*
|
|
40
|
+
* WC_CART_BUFFERS;
|
|
41
|
+
*
|
|
42
|
+
* WC_EXPORT wc_info_t *wc_get_info(void) {
|
|
43
|
+
* WC_FILL_INFO(0);
|
|
44
|
+
* return &wc_info;
|
|
45
|
+
* }
|
|
46
|
+
*
|
|
47
|
+
* WHAT THIS PROVIDES:
|
|
48
|
+
* WC_CART_BUFFERS - declares all standard cart globals
|
|
49
|
+
* WC_FILL_INFO(f) - fills wc_info struct fields
|
|
50
|
+
* WC_EXPORT - shorthand for __attribute__((export_name(...)))
|
|
51
|
+
*
|
|
52
|
+
* You still write your own wc_init() and wc_render() - those have
|
|
53
|
+
* game-specific logic that can't be templated.
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
#ifndef WC_CART_H
|
|
57
|
+
#define WC_CART_H
|
|
58
|
+
|
|
59
|
+
#ifndef WASMCART_H
|
|
60
|
+
#error "Include wasmcart.h before wc_cart.h"
|
|
61
|
+
#endif
|
|
62
|
+
|
|
63
|
+
/* ── Export attribute shorthand ────────────────────────────────────── */
|
|
64
|
+
|
|
65
|
+
#ifndef WC_EXPORT
|
|
66
|
+
#define WC_EXPORT __attribute__((export_name("wc_get_info")))
|
|
67
|
+
#endif
|
|
68
|
+
|
|
69
|
+
/* Helper for other exports */
|
|
70
|
+
#define WC_EXPORT_INIT __attribute__((export_name("wc_init")))
|
|
71
|
+
#define WC_EXPORT_RENDER __attribute__((export_name("wc_render")))
|
|
72
|
+
|
|
73
|
+
/* ── Audio sample type ────────────────────────────────────────────── */
|
|
74
|
+
|
|
75
|
+
/*
|
|
76
|
+
* Default audio format is Float32 (WC_FLAG_AUDIO_F32 set automatically).
|
|
77
|
+
* Define WC_AUDIO_FORMAT_I16 before including this header to use int16
|
|
78
|
+
* ring buffers instead (legacy format).
|
|
79
|
+
*/
|
|
80
|
+
#ifdef WC_AUDIO_FORMAT_I16
|
|
81
|
+
#define _WC_AUDIO_SAMPLE_T int16_t
|
|
82
|
+
#else
|
|
83
|
+
#define _WC_AUDIO_SAMPLE_T float
|
|
84
|
+
#endif
|
|
85
|
+
|
|
86
|
+
/* ── Standard cart buffer declarations ────────────────────────────── */
|
|
87
|
+
|
|
88
|
+
/*
|
|
89
|
+
* Declares all the standard globals every cart needs.
|
|
90
|
+
* Uses DEFAULT_WIDTH, DEFAULT_HEIGHT, MAX_WIDTH, MAX_HEIGHT, AUDIO_CAP
|
|
91
|
+
* which must be #defined before this macro.
|
|
92
|
+
*
|
|
93
|
+
* Names match the convention used across all existing carts.
|
|
94
|
+
*/
|
|
95
|
+
#define WC_CART_BUFFERS \
|
|
96
|
+
static uint32_t wc_cur_width = DEFAULT_WIDTH; \
|
|
97
|
+
static uint32_t wc_cur_height = DEFAULT_HEIGHT; \
|
|
98
|
+
static uint32_t wc_framebuffer[MAX_WIDTH * MAX_HEIGHT]; \
|
|
99
|
+
static _WC_AUDIO_SAMPLE_T wc_audio_ring[AUDIO_CAP * 2]; \
|
|
100
|
+
static uint32_t wc_audio_write_cursor; \
|
|
101
|
+
static wc_pad_t wc_pads[4]; \
|
|
102
|
+
static wc_time_t wc_time; \
|
|
103
|
+
static wc_info_t wc_info; \
|
|
104
|
+
static wc_host_info_t wc_host_info; \
|
|
105
|
+
static wc_pointer_t wc_pointers[10]; \
|
|
106
|
+
static uint8_t wc_keys[32]
|
|
107
|
+
|
|
108
|
+
/* ── Fill info struct ─────────────────────────────────────────────── */
|
|
109
|
+
|
|
110
|
+
/*
|
|
111
|
+
* Fills the wc_info struct with standard field values.
|
|
112
|
+
* Call inside wc_get_info(). Pass flags like WC_FLAG_AUDIO_F32 or 0.
|
|
113
|
+
*
|
|
114
|
+
* For carts that don't use save data, save_ptr and save_size are set to 0.
|
|
115
|
+
* Override them after this macro if you need save support.
|
|
116
|
+
*/
|
|
117
|
+
#define WC_FILL_INFO(_wc_flags) do { \
|
|
118
|
+
wc_info.version = WC_ABI_VERSION; \
|
|
119
|
+
wc_info.width = DEFAULT_WIDTH; \
|
|
120
|
+
wc_info.height = DEFAULT_HEIGHT; \
|
|
121
|
+
wc_info.fb_ptr = (uint32_t)(uintptr_t)wc_framebuffer; \
|
|
122
|
+
wc_info.audio_ptr = (uint32_t)(uintptr_t)wc_audio_ring; \
|
|
123
|
+
wc_info.audio_cap = AUDIO_CAP; \
|
|
124
|
+
wc_info.audio_write_ptr = (uint32_t)(uintptr_t)&wc_audio_write_cursor; \
|
|
125
|
+
wc_info.input_ptr = (uint32_t)(uintptr_t)wc_pads; \
|
|
126
|
+
wc_info.save_ptr = 0; \
|
|
127
|
+
wc_info.save_size = 0; \
|
|
128
|
+
wc_info.time_ptr = (uint32_t)(uintptr_t)&wc_time; \
|
|
129
|
+
wc_info.host_info_ptr = (uint32_t)(uintptr_t)&wc_host_info; \
|
|
130
|
+
wc_info.flags = (_wc_flags) \
|
|
131
|
+
_WC_FILL_INFO_AUDIO_FLAG; \
|
|
132
|
+
wc_info.audio_sample_rate = 0; /* 0 = let host decide */ \
|
|
133
|
+
wc_info.pointer_ptr = (uint32_t)(uintptr_t)wc_pointers; \
|
|
134
|
+
wc_info.keys_ptr = (uint32_t)(uintptr_t)wc_keys; \
|
|
135
|
+
} while (0)
|
|
136
|
+
|
|
137
|
+
/* Auto-set WC_FLAG_AUDIO_F32 unless cart opts into legacy I16 */
|
|
138
|
+
#ifdef WC_AUDIO_FORMAT_I16
|
|
139
|
+
#define _WC_FILL_INFO_AUDIO_FLAG
|
|
140
|
+
#else
|
|
141
|
+
#define _WC_FILL_INFO_AUDIO_FLAG | WC_FLAG_AUDIO_F32
|
|
142
|
+
#endif
|
|
143
|
+
|
|
144
|
+
#endif /* WC_CART_H */
|