Back to shtanton's homepage
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCharlie Stanton <charlie@shtanton.xyz>2025-04-05 08:10:21 +0100
committerCharlie Stanton <charlie@shtanton.xyz>2025-04-05 08:10:21 +0100
commit935f69ca25bca24fd36166e7192ebb69af2d35e9 (patch)
tree182cd864dc0d486ca64868f037da306b60ec51e2
parentf0bf72a54477fb18b1666293a652b52f53320a1d (diff)
downloadldjam57-935f69ca25bca24fd36166e7192ebb69af2d35e9.tar
Get web assembly working
-rw-r--r--.gitignore1
-rw-r--r--Makefile19
-rw-r--r--src/all.c83
-rw-r--r--src/all.h13
-rw-r--r--src/index.html.in105
5 files changed, 185 insertions, 36 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..378eac2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+build
diff --git a/Makefile b/Makefile
index bfb6190..fc6af8f 100644
--- a/Makefile
+++ b/Makefile
@@ -1,2 +1,17 @@
-main: src/all.c src/all.h
- gcc -Wall -Wextra -Wpedantic -DSDL $$(pkg-config --libs sdl3) -o main src/all.c
+build/main: src/all.c src/all.h
+ mkdir -p build
+ gcc -Wall -Wextra -Wpedantic -DSDL $$(pkg-config --libs sdl3) -o build/main src/all.c
+
+build/main.wasm: src/all.c src/all.h
+ mkdir -p build
+ clang --target=wasm32 -nostdlib -DWASM -Wall -Wextra -Wpedantic \
+ -Wl,--no-entry -fno-builtin -o build/main.wasm src/all.c
+
+build/main.wasm.b64: build/main.wasm
+ (printf '"'; base64 < build/main.wasm | tr -d '\n'; printf '"') > build/main.wasm.b64
+
+build/index.html: src/index.html.in build/main.wasm.b64
+ clang -E -P -undef -nostdinc -x c -o build/index.html src/index.html.in
+
+clean:
+ rm -r build
diff --git a/src/all.c b/src/all.c
index f14a3d2..e8e5b11 100644
--- a/src/all.c
+++ b/src/all.c
@@ -1,4 +1,15 @@
-#include "all.h"
+#include <stddef.h>
+#include <stdint.h>
+
+typedef struct {
+ int x, y, w, h;
+ unsigned char r, g, b, a;
+} DrawElement;
+
+typedef struct {
+ int len;
+ DrawElement els[256];
+} DrawList;
#define MEM_SIZE (1<<16)
@@ -30,6 +41,7 @@ typedef struct {
int x, y;
} State;
+// Mirror these in src/index.html.in
enum {
INPUT_NONE,
INPUT_UP,
@@ -50,18 +62,14 @@ static DrawList *render(State state, UI *ui, Arena *a) {
DrawList *drawList = new(a, 1, DrawList);
drawList->len = 1;
drawList->els[0] = (DrawElement) {
- .rect = (SDL_Rect) {
- .x = state.x,
- .y = state.y,
- .w = 100,
- .h = 100,
- },
- .color = (SDL_Color) {
- .r = 255,
- .g = 0,
- .b = 0,
- .a = 255,
- },
+ .x = state.x,
+ .y = state.y,
+ .w = 100,
+ .h = 100,
+ .r = 255,
+ .g = 0,
+ .b = 0,
+ .a = 255,
};
return drawList;
@@ -88,6 +96,8 @@ static void update(Game *game, Arena a) {
#if SDL
+#include <SDL3/SDL.h>
+
int main(int argc, char **argv) {
(void) argc;
(void) argv;
@@ -154,16 +164,16 @@ int main(int argc, char **argv) {
for (int i = 0; i < drawList->len; i++) {
SDL_SetRenderDrawColor(
r,
- drawList->els[i].color.r,
- drawList->els[i].color.g,
- drawList->els[i].color.b,
- drawList->els[i].color.a
+ drawList->els[i].r,
+ drawList->els[i].g,
+ drawList->els[i].b,
+ drawList->els[i].a
);
SDL_FRect rect = {
- .x = (float) drawList->els[i].rect.x,
- .y = (float) drawList->els[i].rect.y,
- .w = (float) drawList->els[i].rect.w,
- .h = (float) drawList->els[i].rect.h
+ .x = (float) drawList->els[i].x,
+ .y = (float) drawList->els[i].y,
+ .w = (float) drawList->els[i].w,
+ .h = (float) drawList->els[i].h
};
SDL_RenderFillRect(r, &rect);
}
@@ -177,4 +187,35 @@ int main(int argc, char **argv) {
}
#elif WASM
+
+static Game *game;
+static Arena perm;
+
+__attribute((export_name("game_init")))
+void game_init(void) {
+ static char heap[MEM_SIZE];
+ perm.start = heap;
+ perm.end = heap + MEM_SIZE;
+
+ game = new(&perm, 1, Game);
+ game->state = (State) {
+ .x = 100,
+ .y = 100,
+ };
+}
+
+__attribute((export_name("game_render")))
+DrawList *game_render(int width, int height) {
+ game->ui.width = width;
+ game->ui.height = height;
+ Arena scratch = perm;
+ return render(game->state, &game->ui, &scratch);
+}
+
+__attribute((export_name("game_update")))
+void game_update(int input) {
+ game->input = input;
+ update(game, perm);
+}
+
#endif
diff --git a/src/all.h b/src/all.h
index 19cc6e5..e69de29 100644
--- a/src/all.h
+++ b/src/all.h
@@ -1,13 +0,0 @@
-#include <SDL3/SDL.h>
-#include <stddef.h>
-#include <stdint.h>
-
-typedef struct {
- SDL_Rect rect;
- SDL_Color color;
-} DrawElement;
-
-typedef struct {
- int len;
- DrawElement els[256];
-} DrawList;
diff --git a/src/index.html.in b/src/index.html.in
new file mode 100644
index 0000000..fcfb8ef
--- /dev/null
+++ b/src/index.html.in
@@ -0,0 +1,105 @@
+<!doctype html>
+<title>LDJam 57</title>
+<meta charset="utf-8"/>
+<meta name="viewport" content="width=device-width">
+<style>
+html, body {
+ background: #222222;
+ font-family: sans-serif;
+ height: 100%;
+ margin: 0;
+ overflow: hidden;
+ text-align: center;
+ width: 100%;
+}
+h1 {
+ color: white;
+ margin: 0;
+}
+button {
+ font-size: 150%;
+ height: 8%;
+ margin: 0.3em;
+ width: 25%;
+}
+</style>
+
+<canvas></canvas>
+
+<script>
+// Mirror these in src/all.c
+const INPUT_NONE = 0;
+const INPUT_UP = 1;
+const INPUT_DOWN = 2;
+const INPUT_LEFT = 3;
+const INPUT_RIGHT = 4;
+
+const WASM =
+#include "../build/main.wasm.b64"
+
+async function main() {
+ let bytes = Uint8Array.from(atob(WASM), function(c) {
+ return c.charCodeAt(0);
+ });
+ let module = await WebAssembly.compile(bytes);
+ let wasm = await WebAssembly.instantiate(module);
+ let exports = wasm.exports;
+ let html = document.querySelector("html");
+ let canvas = document.querySelector("canvas");
+ let ctx = canvas.getContext("2d");
+ let memory = exports.memory;
+
+ function min(a, b) {
+ return b<a ? b : a;
+ }
+
+ function max_width() {
+ return html.clientHeight * 0.7 | 0;
+ }
+
+ function render() {
+ let width = canvas.width = min(html.clientWidth, max_width());
+ let height = canvas.height = width;
+ let ptr = exports.game_render(width, height);
+ let dl = new Int32Array(memory.buffer, ptr);
+ let len = dl[0];
+ let ops = dl.subarray(1);
+
+ for (let i = 0; i < len; i++) {
+ let op = ops.subarray(5*i, 5*i+5);
+ const color = new Uint8Array(new Uint32Array(ops.subarray(4, 5)).buffer);
+ let style = `#${color[0].toString(16).padStart(2, "0")}${color[1].toString(16).padStart(2, "0")}${color[2].toString(16).padStart(2, "0")}`;
+ ctx.fillStyle = style;
+ ctx.fillRect(op[0], op[1], op[2], op[3]);
+ }
+ }
+
+ function onresize() { html.style.maxWidth = `${max_width()}px`; }
+ window.addEventListener("resize", onresize);
+ onresize();
+
+ document.addEventListener("keydown", function(e) {
+ if (e.key == "w") {
+ exports.game_update(INPUT_UP);
+ } else if (e.key == "a") {
+ exports.game_update(INPUT_LEFT);
+ } else if (e.key == "s") {
+ exports.game_update(INPUT_DOWN);
+ } else if (e.key == "d") {
+ exports.game_update(INPUT_RIGHT);
+ }
+ })
+
+ function animate() {
+ // TODO: stop requesting frames when state is static
+ requestAnimationFrame(animate);
+ exports.game_update(INPUT_NONE);
+ render();
+ }
+ requestAnimationFrame(animate);
+
+ exports.game_init();
+}
+
+main()
+</script>