From 1aa082a156fc65160c87c6c590aba8d5c52e2555 Mon Sep 17 00:00:00 2001 From: Pabloader Date: Tue, 20 May 2025 07:23:42 +0000 Subject: [PATCH] Graphics library for wasm --- build/assets/include/graphics.h | 37 +++++++++ build/assets/include/image_data.h | 22 ----- build/assets/include/math.h | 5 ++ build/assets/lib/graphics.c | 106 ++++++++++++++++++++++++ build/assets/lib/image_data.c | 20 ----- build/wasmPlugin.ts | 2 +- src/common/components/PointerCanvas.tsx | 12 +-- src/common/display/canvas.ts | 33 +++++++- src/common/utils.ts | 2 +- src/games/life/index.ts | 3 +- src/games/life/life.c | 15 ++-- src/games/playground/awoo.cpp | 24 +++++- src/games/playground/index.tsx | 4 +- 13 files changed, 215 insertions(+), 70 deletions(-) create mode 100644 build/assets/include/graphics.h delete mode 100644 build/assets/include/image_data.h create mode 100644 build/assets/include/math.h create mode 100644 build/assets/lib/graphics.c delete mode 100644 build/assets/lib/image_data.c diff --git a/build/assets/include/graphics.h b/build/assets/include/graphics.h new file mode 100644 index 0000000..e67552d --- /dev/null +++ b/build/assets/include/graphics.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef union { + uint32_t color; + struct { + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a; + }; +} image_color_t; + +typedef struct { + uint16_t width; + uint16_t height; + image_color_t* pixels; +} image_data_t; + +image_data_t image_create(uint16_t width, uint16_t height); +void image_free(image_data_t image); + +void image_draw_point(image_data_t image, uint16_t x, uint16_t y, image_color_t color); +void image_draw_hline(image_data_t image, uint16_t x1, uint16_t x2, uint16_t y, image_color_t color); +void image_draw_vline(image_data_t image, uint16_t x, uint16_t y1, uint16_t y2, image_color_t color); +void image_draw_line(image_data_t image, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, image_color_t color); +void image_draw_rect(image_data_t image, uint16_t x, uint16_t y, uint16_t w, uint16_t h, image_color_t color); +void image_fill_rect(image_data_t image, uint16_t x, uint16_t y, uint16_t w, uint16_t h, image_color_t color); + +#ifdef __cplusplus +} +#endif diff --git a/build/assets/include/image_data.h b/build/assets/include/image_data.h deleted file mode 100644 index 6671794..0000000 --- a/build/assets/include/image_data.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include - -typedef union { - struct { - uint8_t r; - uint8_t g; - uint8_t b; - uint8_t a; - }; - uint32_t color; -} image_pixel_t; - -typedef struct { - uint16_t width; - uint16_t height; - image_pixel_t* pixels; -} image_data_t; - -image_data_t create_image(uint16_t width, uint16_t height); -void free_image(image_data_t image); diff --git a/build/assets/include/math.h b/build/assets/include/math.h new file mode 100644 index 0000000..6337b74 --- /dev/null +++ b/build/assets/include/math.h @@ -0,0 +1,5 @@ +#pragma once + +#define abs(x) (((x) < 0) ? -(x): (x)) +#define max(a, b) (((a) > (b)) ? (a): (b)) +#define min(a, b) (((a) < (b)) ? (a): (b)) diff --git a/build/assets/lib/graphics.c b/build/assets/lib/graphics.c new file mode 100644 index 0000000..35281f5 --- /dev/null +++ b/build/assets/lib/graphics.c @@ -0,0 +1,106 @@ +#include +#include +#include + +image_data_t image_create(uint16_t width, uint16_t height) { + image_data_t data; + data.width = width; + data.height = height; + size_t length = width * height * sizeof(image_color_t); + data.pixels = (image_color_t*)malloc(length); + + memset(data.pixels, 255, length); + + return data; +} + +void image_free(image_data_t image) { + if (image.pixels) { + free(image.pixels); + } +} + +static inline void set_point(image_data_t image, uint16_t x, uint16_t y, image_color_t color) { + image.pixels[x + y * image.width] = color; +} + +void image_draw_point(image_data_t image, uint16_t x, uint16_t y, image_color_t color) { + if (x >= image.width || y >= image.height) { + return; + } + set_point(image, x, y, color); +} + +void image_draw_hline(image_data_t image, uint16_t x1, uint16_t x2, uint16_t y, image_color_t color) { + if (x1 > x2) { + uint16_t temp = x1; + x1 = x2; + x2 = temp; + } + if (x1 > image.width) { + return; + } + x2 = min(x2, image.width); + + do { + image_draw_point(image, x1++, y, color); + } while (x1 < x2); +} + +void image_draw_vline(image_data_t image, uint16_t x, uint16_t y1, uint16_t y2, image_color_t color) { + if (y1 > y2) { + uint16_t temp = y1; + y1 = y2; + y2 = temp; + } + if (y1 > image.width) { + return; + } + y2 = min(y2, image.height); + + do { + image_draw_point(image, x, y1++, color); + } while (y1 < y2); +} + +void image_draw_line(image_data_t image, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, image_color_t color) { + int dx = abs(x2 - x1); + int dy = abs(y2 - y1); + int sx = (x1 < x2) ? 1 : -1; + int sy = (y1 < y2) ? 1 : -1; + int err = dx - dy; + int e2; + + while (1) { + if (x1 >= image.width || y1 >= image.height) { + break; + } + set_point(image, x1, y1, color); + if (x1 == x2 && y1 == y2) { + break; + } + e2 = 2 * err; + if (e2 > -dy) { + err -= dy; + x1 += sx; + } + if (e2 < dx) { + err += dx; + y1 += sy; + } + } +} + +void image_draw_rect(image_data_t image, uint16_t x, uint16_t y, uint16_t w, uint16_t h, image_color_t color) { + image_draw_hline(image, x, x + w - 1, y, color); + image_draw_hline(image, x, x + w, y + h - 1, color); + + image_draw_vline(image, x, y, y + h - 1, color); + image_draw_vline(image, x + w - 1, y, y + h - 1, color); +} + +void image_fill_rect(image_data_t image, uint16_t x, uint16_t y, uint16_t w, uint16_t h, image_color_t color) { + while (h--) { + image_draw_hline(image, x, x + w, y++, color); + }; +} diff --git a/build/assets/lib/image_data.c b/build/assets/lib/image_data.c deleted file mode 100644 index 159862f..0000000 --- a/build/assets/lib/image_data.c +++ /dev/null @@ -1,20 +0,0 @@ -#include -#include - -image_data_t create_image(uint16_t width, uint16_t height) { - image_data_t data; - data.width = width; - data.height = height; - size_t length = width * height * sizeof(image_pixel_t); - data.pixels = (image_pixel_t*)malloc(length); - - memset(data.pixels, 255, length); - - return data; -} - -void free_image(image_data_t image) { - if (image.pixels) { - free(image.pixels); - } -} diff --git a/build/wasmPlugin.ts b/build/wasmPlugin.ts index bda2765..7ad66f2 100644 --- a/build/wasmPlugin.ts +++ b/build/wasmPlugin.ts @@ -87,7 +87,7 @@ const wasmPlugin = ({ production, portable }: WasmLoaderConfig = {}): BunPlugin const glob = new Bun.Glob(`${buildAssets}/lib/**/*.c`); const stdlib = await Array.fromAsync(glob.scan()); const opt = production ? '-O3' : '-O0'; - const result = await $`clang --target=wasm32 ${opt} -flto -fno-builtin --no-standard-libraries -I ${include} -Wall -Wextra -Wpedantic -Werror -Wl,--lto-O3 -Wl,--no-entry -Wl,--import-memory -o ${wasmPath} ${args.path} ${stdlib}`; + const result = await $`clang --target=wasm32 ${opt} -flto -fno-builtin --no-standard-libraries -I ${include} -Wall -Wextra -Wpedantic -Werror -Wno-gnu-anonymous-struct -Wl,--lto-O3 -Wl,--no-entry -Wl,--import-memory -o ${wasmPath} ${args.path} ${stdlib}`; if (result.exitCode !== 0) { throw new Error('Compile failed, check output'); diff --git a/src/common/components/PointerCanvas.tsx b/src/common/components/PointerCanvas.tsx index a51b475..ed35a9a 100644 --- a/src/common/components/PointerCanvas.tsx +++ b/src/common/components/PointerCanvas.tsx @@ -1,3 +1,4 @@ +import { loadImageData } from "@common/display/canvas"; import type { HTMLAttributes } from "preact/compat"; import { useEffect, useMemo, useRef } from "preact/hooks"; @@ -38,14 +39,3 @@ export const PointerCanvas = ({ dataView, pointer, ...otherProps }: Props) => { ; }; - -export const loadImageData = (dataView: DataView, pointer: number) => { - const width = dataView.getUint16(pointer + 0, true); - const height = dataView.getUint16(pointer + 2, true); - - const dataPtr = dataView.getUint32(pointer + 4, true); - const imageBuffer = new Uint8ClampedArray(dataView.buffer, dataPtr, width * height * 4); - const imageData = new ImageData(imageBuffer, width, height); - - return imageData; -} \ No newline at end of file diff --git a/src/common/display/canvas.ts b/src/common/display/canvas.ts index cdca469..f4bda24 100644 --- a/src/common/display/canvas.ts +++ b/src/common/display/canvas.ts @@ -1,3 +1,14 @@ +export function loadImageData(dataView: DataView, pointer: number) { + const width = dataView.getUint16(pointer + 0, true); + const height = dataView.getUint16(pointer + 2, true); + + const dataPtr = dataView.getUint32(pointer + 4, true); + const imageBuffer = new Uint8ClampedArray(dataView.buffer, dataPtr, width * height * 4); + const imageData = new ImageData(imageBuffer, width, height); + + return imageData; +} + export function createCanvas(width: number, height: number) { const canvas = document.createElement('canvas'); @@ -13,6 +24,24 @@ export function createCanvas(width: number, height: number) { return canvas; } +export function createWasmCanvas(dataView: DataView, pointer: number, onFrame?: () => void) { + const imageData = loadImageData(dataView, pointer); + const canvas = createCanvas(imageData.width, imageData.height); + const context = canvas.getContext('bitmaprenderer'); + + if (context) { + const frame = async () => { + onFrame?.(); + const imageBitmap = await createImageBitmap(imageData); + context.transferFromImageBitmap(imageBitmap); + requestAnimationFrame(frame); + }; + frame(); + } + + return canvas; +} + export function getImageData(image: HTMLImageElement): ImageData { const canvas = document.createElement('canvas'); canvas.width = image.naturalWidth; @@ -20,6 +49,6 @@ export function getImageData(image: HTMLImageElement): ImageData { const ctx = canvas.getContext('2d')!; ctx.drawImage(image, 0, 0); const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); - + return imageData; -} \ No newline at end of file +} diff --git a/src/common/utils.ts b/src/common/utils.ts index 1deed16..9b7dd2f 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -77,4 +77,4 @@ export const throttle = function #include #include -#include #define WIDTH 512 #define HEIGHT 512 @@ -17,12 +17,13 @@ EXPORT(step) void step(void) { int count = count_neighbours(x, y); uint8_t current_cell = field[x + y * WIDTH]; int next_cell = current_cell; - if (current_cell && count < 2) + if (current_cell && count < 2) { next_cell = 0; - else if (current_cell && count > 3) + } else if (current_cell && count > 3) { next_cell = 0; - else if (!current_cell && count == 3) + } else if (!current_cell && count == 3) { next_cell = 1; + } next_field[x + y * WIDTH] = next_cell; } @@ -42,8 +43,9 @@ static int count_neighbours(int x, int y) { int count = 0; for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) { - if (i == 0 && j == 0) + if (i == 0 && j == 0) { continue; + } int xx = (x + i + WIDTH) % WIDTH; int yy = (y + j + HEIGHT) % HEIGHT; @@ -56,11 +58,10 @@ static int count_neighbours(int x, int y) { return count; } - EXPORT(initField) image_data_t* init_field(void) { field = malloc(WIDTH * HEIGHT); next_field = malloc(WIDTH * HEIGHT); - image_data = create_image(WIDTH, HEIGHT); + image_data = image_create(WIDTH, HEIGHT); for (int i = 0; i < WIDTH * HEIGHT; i++) { field[i] = rand() & 1; diff --git a/src/games/playground/awoo.cpp b/src/games/playground/awoo.cpp index 8f78c97..006fc3d 100644 --- a/src/games/playground/awoo.cpp +++ b/src/games/playground/awoo.cpp @@ -1,5 +1,23 @@ +#include #include -EXPORT(cpptest) void cpptest() { - printf("Awoo! %t%%\n"); -} \ No newline at end of file +static image_data_t image; + +EXPORT(step) void step() { + // for (int i = 0; i < 1; i++) { + // int x1 = rand() % image.width; + // int y1 = rand() % image.height; + // int x2 = rand() % image.width; + // int y2 = rand() % image.height; + + // image_fill_rect(image, x1, y1, x2 - x1, y2 - y1, {(uint32_t)(rand() | 0xFF000000)}); + // } +} + +EXPORT(cpptest) image_data_t* cpptest(uint16_t w, uint16_t h) { + image = image_create(w, h); + + image_fill_rect(image, 0, 0, w, h, {0xFF000000}); + + return ℑ +} diff --git a/src/games/playground/index.tsx b/src/games/playground/index.tsx index c7d65ad..36738da 100644 --- a/src/games/playground/index.tsx +++ b/src/games/playground/index.tsx @@ -1,6 +1,8 @@ +import { createWasmCanvas } from "@common/display/canvas"; import awoo from "./awoo.cpp"; export default function main() { - awoo.cpptest(); + const data = awoo.cpptest(window.innerWidth >> 2, window.innerHeight >> 2); + createWasmCanvas(awoo.data, data, awoo.step); console.log(awoo); } \ No newline at end of file