From 2484b82873ba126092e011d9d5be7affc710547d Mon Sep 17 00:00:00 2001 From: Pabloader Date: Mon, 19 May 2025 14:48:03 +0000 Subject: [PATCH] Image data c api --- .clang-format | 11 ++- build/assets/include/image_data.h | 22 ++++++ build/assets/{ => include}/stdlib.h | 6 +- build/assets/lib/image_data.c | 20 +++++ build/assets/{ => lib}/stdlib.c | 10 +-- build/wasmPlugin.ts | 6 +- compile_flags.txt | 4 +- src/common/components/PointerCanvas.tsx | 51 ++++++++++++ .../{modal.module.css => Modal.module.css} | 0 .../components/modal/{modal.tsx => Modal.tsx} | 2 +- .../ai-story/components/header/header.tsx | 2 +- .../ai-story/components/minichat/minichat.tsx | 2 +- src/games/life/index.ts | 27 +++---- src/games/life/life.c | 77 ++++++++----------- 14 files changed, 166 insertions(+), 74 deletions(-) create mode 100644 build/assets/include/image_data.h rename build/assets/{ => include}/stdlib.h (76%) create mode 100644 build/assets/lib/image_data.c rename build/assets/{ => lib}/stdlib.c (94%) create mode 100644 src/common/components/PointerCanvas.tsx rename src/common/components/modal/{modal.module.css => Modal.module.css} (100%) rename src/common/components/modal/{modal.tsx => Modal.tsx} (96%) diff --git a/.clang-format b/.clang-format index fe1cc65..f13cb9d 100644 --- a/.clang-format +++ b/.clang-format @@ -1,3 +1,12 @@ IndentWidth: 4 ColumnLimit: 240 -PointerAlignment: Left \ No newline at end of file +PointerAlignment: Left +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: true +AllowShortCompoundRequirementOnASingleLine: true +AllowShortEnumsOnASingleLine: true +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: None +AllowShortLoopsOnASingleLine: false +InsertBraces: true \ No newline at end of file diff --git a/build/assets/include/image_data.h b/build/assets/include/image_data.h new file mode 100644 index 0000000..6671794 --- /dev/null +++ b/build/assets/include/image_data.h @@ -0,0 +1,22 @@ +#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/stdlib.h b/build/assets/include/stdlib.h similarity index 76% rename from build/assets/stdlib.h rename to build/assets/include/stdlib.h index c11f280..481680b 100644 --- a/build/assets/stdlib.h +++ b/build/assets/include/stdlib.h @@ -16,9 +16,9 @@ void* malloc(size_t); void free(void*); void* realloc(void*, size_t); -void* memset(void* s, uint8_t c, uint32_t n); -void* memcpy(void* dest, const void* src, uint32_t n); -int memcmp(const void* s1, const void* s2, uint32_t n); +void* memset(void* d, uint8_t c, size_t n); +void* memcpy(void* dest, const void* src, size_t n); +int memcmp(const void* s1, const void* s2, size_t n); IMPORT(log) void printf(const char* format, ...); diff --git a/build/assets/lib/image_data.c b/build/assets/lib/image_data.c new file mode 100644 index 0000000..159862f --- /dev/null +++ b/build/assets/lib/image_data.c @@ -0,0 +1,20 @@ +#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/assets/stdlib.c b/build/assets/lib/stdlib.c similarity index 94% rename from build/assets/stdlib.c rename to build/assets/lib/stdlib.c index 78a5ebd..b6ac081 100644 --- a/build/assets/stdlib.c +++ b/build/assets/lib/stdlib.c @@ -137,15 +137,15 @@ void* realloc(void* block, size_t size) { return ret; } -void* memset(void* s, uint8_t c, uint32_t n) { - uint8_t* p = (uint8_t*)s; +void* memset(void* d, uint8_t c, size_t n) { + uint8_t* p = (uint8_t*)d; while (n--) { *p++ = c; } - return s; + return d; } -void* memcpy(void* dest, const void* src, uint32_t n) { +void* memcpy(void* dest, const void* src, size_t n) { uint8_t* d = (uint8_t*)dest; const uint8_t* s = (const uint8_t*)src; while (n--) { @@ -154,7 +154,7 @@ void* memcpy(void* dest, const void* src, uint32_t n) { return dest; } -int memcmp(const void* s1, const void* s2, uint32_t n) { +int memcmp(const void* s1, const void* s2, size_t n) { const uint8_t* p1 = (const uint8_t*)s1; const uint8_t* p2 = (const uint8_t*)s2; diff --git a/build/wasmPlugin.ts b/build/wasmPlugin.ts index c64d1f0..bda2765 100644 --- a/build/wasmPlugin.ts +++ b/build/wasmPlugin.ts @@ -83,9 +83,11 @@ const wasmPlugin = ({ production, portable }: WasmLoaderConfig = {}): BunPlugin wasmPath = args.path; } else { const buildAssets = path.resolve(import.meta.dir, 'assets'); - const stdlib = `${buildAssets}/stdlib.c`; + const include = `${buildAssets}/include`; + 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 ${buildAssets} -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 -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/compile_flags.txt b/compile_flags.txt index 71ef868..e15216f 100644 --- a/compile_flags.txt +++ b/compile_flags.txt @@ -1,8 +1,8 @@ - --target=wasm32 +--target=wasm32 -fno-builtin --no-standard-libraries -I -build/assets/ +build/assets/include -Wall -Wextra -Wpedantic diff --git a/src/common/components/PointerCanvas.tsx b/src/common/components/PointerCanvas.tsx new file mode 100644 index 0000000..a51b475 --- /dev/null +++ b/src/common/components/PointerCanvas.tsx @@ -0,0 +1,51 @@ +import type { HTMLAttributes } from "preact/compat"; +import { useEffect, useMemo, useRef } from "preact/hooks"; + +interface Props extends HTMLAttributes { + dataView: DataView; + pointer: number; +} + +export const PointerCanvas = ({ dataView, pointer, ...otherProps }: Props) => { + const canvasRef = useRef(null); + const ctx = useMemo(() => canvasRef.current?.getContext('2d'), [canvasRef.current]); + + const imageData = useMemo(() => loadImageData(dataView, pointer), [dataView, pointer]); + + useEffect(() => { + if (canvasRef.current) { + canvasRef.current.width = imageData.width; + canvasRef.current.height = imageData.height; + } + }, [imageData, canvasRef.current]); + + useEffect(() => { + let raf: number; + const frame = () => { + if (ctx) { + ctx.putImageData(imageData, 0, 0); + } + raf = requestAnimationFrame(frame); + } + raf = requestAnimationFrame(frame); + + return () => { + cancelAnimationFrame(raf); + }; + }, [ctx, imageData]); + + return <> + + ; +}; + +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/components/modal/modal.module.css b/src/common/components/modal/Modal.module.css similarity index 100% rename from src/common/components/modal/modal.module.css rename to src/common/components/modal/Modal.module.css diff --git a/src/common/components/modal/modal.tsx b/src/common/components/modal/Modal.tsx similarity index 96% rename from src/common/components/modal/modal.tsx rename to src/common/components/modal/Modal.tsx index 9ae6f9d..3e38681 100644 --- a/src/common/components/modal/modal.tsx +++ b/src/common/components/modal/Modal.tsx @@ -1,7 +1,7 @@ import type { ComponentChildren } from "preact"; import { useCallback, useEffect, useRef } from "preact/hooks"; -import styles from './modal.module.css'; +import styles from './Modal.module.css'; interface IProps { open: boolean; diff --git a/src/games/ai-story/components/header/header.tsx b/src/games/ai-story/components/header/header.tsx index 4d762a0..c4516f6 100644 --- a/src/games/ai-story/components/header/header.tsx +++ b/src/games/ai-story/components/header/header.tsx @@ -1,6 +1,6 @@ import { useCallback, useContext, useEffect, useMemo, useState } from "preact/hooks"; import { useBool } from "@common/hooks/useBool"; -import { Modal } from "@common/components/modal/modal"; +import { Modal } from "@common/components/modal/Modal"; import { StateContext } from "../../contexts/state"; import { LLMContext } from "../../contexts/llm"; diff --git a/src/games/ai-story/components/minichat/minichat.tsx b/src/games/ai-story/components/minichat/minichat.tsx index 5b421f1..cff746b 100644 --- a/src/games/ai-story/components/minichat/minichat.tsx +++ b/src/games/ai-story/components/minichat/minichat.tsx @@ -1,6 +1,6 @@ import { MessageTools, type IMessage } from "../../tools/messages" import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "preact/hooks"; -import { Modal } from "@common/components/modal/modal"; +import { Modal } from "@common/components/modal/Modal"; import { DOMTools } from "../../tools/dom"; import styles from './minichat.module.css'; diff --git a/src/games/life/index.ts b/src/games/life/index.ts index dfb70ad..275cb92 100644 --- a/src/games/life/index.ts +++ b/src/games/life/index.ts @@ -1,21 +1,18 @@ import { createCanvas } from "@common/display/canvas"; import life from "./life.c"; +import { loadImageData } from "@common/components/PointerCanvas"; -const width = life.getWidth(); -const height = life.getHeight(); - -const canvas = createCanvas(width, height); -const context = canvas.getContext('2d')!; +let context: CanvasRenderingContext2D | null; let imageData: ImageData; -const step = life.step as CallableFunction; - export default function main() { - const pixelsPtr = life.initField(); - const pixels = new Uint8ClampedArray(life.memory.buffer, pixelsPtr, width * height * 4); - imageData = new ImageData(pixels, width, height); + const imageDataPtr = life.initField(); + imageData = loadImageData(life.data, imageDataPtr); - console.log(life, pixels.length); + const canvas = createCanvas(imageData.width, imageData.height); + context = canvas.getContext('2d'); + + console.log(life, imageData); requestAnimationFrame(loop); } @@ -26,16 +23,16 @@ let count = 0; async function loop() { const start = performance.now(); - step(); + life.step(); - context.putImageData(imageData, 0, 0); - context.clearRect(0, 0, 35, 15); + context?.putImageData(imageData, 0, 0); + context?.clearRect(0, 0, 35, 15); const end = performance.now(); sum += end - start; count++; - context.fillText(`${(sum / count).toFixed(1)} ms`, 2, 10); + context?.fillText(`${(sum / count).toFixed(1)} ms`, 2, 10); requestAnimationFrame(loop); } \ No newline at end of file diff --git a/src/games/life/life.c b/src/games/life/life.c index 74289c6..04dbe4a 100644 --- a/src/games/life/life.c +++ b/src/games/life/life.c @@ -1,63 +1,54 @@ #include #include +#include -#define width 512 -#define height 512 +#define WIDTH 512 +#define HEIGHT 512 static uint8_t* field; -static uint8_t* nextField; -static uint8_t* pixels; +static uint8_t* next_field; +static image_data_t image_data; -static int countNeighbours(int x, int y); - -EXPORT(getWidth) int get_width(void) { return width; } -EXPORT(getHeight) int get_height(void) { return height; } +static int count_neighbours(int x, int y); EXPORT(step) void step(void) { - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - int count = countNeighbours(x, y); - uint8_t currentCell = field[x + y * width]; - int nextCell = currentCell; - if (currentCell && count < 2) - nextCell = 0; - else if (currentCell && count > 3) - nextCell = 0; - else if (!currentCell && count == 3) - nextCell = 1; + for (int y = 0; y < HEIGHT; y++) { + for (int x = 0; x < WIDTH; x++) { + int count = count_neighbours(x, y); + uint8_t current_cell = field[x + y * WIDTH]; + int next_cell = current_cell; + if (current_cell && count < 2) + next_cell = 0; + else if (current_cell && count > 3) + next_cell = 0; + else if (!current_cell && count == 3) + next_cell = 1; - nextField[x + y * width] = nextCell; + next_field[x + y * WIDTH] = next_cell; } } - for (int i = 0; i < width * height; i++) { - field[i] = nextField[i]; - uint8_t px = field[i]; + for (int i = 0; i < WIDTH * HEIGHT; i++) { + field[i] = next_field[i]; + uint8_t px = (!field[i]) * 255; - if (px) { - pixels[(i << 2) + 0] = 0; - pixels[(i << 2) + 1] = 0; - pixels[(i << 2) + 2] = 0; - } else { - pixels[(i << 2) + 0] = 255; - pixels[(i << 2) + 1] = 255; - pixels[(i << 2) + 2] = 255; - } - pixels[(i << 2) + 3] = 255; + image_data.pixels[i].r = px; + image_data.pixels[i].g = px; + image_data.pixels[i].b = px; } } -static int countNeighbours(int x, int y) { +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) continue; - int xx = (x + i + width) % width; - int yy = (y + j + height) % height; + int xx = (x + i + WIDTH) % WIDTH; + int yy = (y + j + HEIGHT) % HEIGHT; - if (field[xx + yy * width]) { + if (field[xx + yy * WIDTH]) { count++; } } @@ -66,14 +57,14 @@ static int countNeighbours(int x, int y) { } -EXPORT(initField) uint8_t* init_field(void) { - field = malloc(width * height); - nextField = malloc(width * height); - pixels = malloc(width * height * 4); +EXPORT(initField) image_data_t* init_field(void) { + field = malloc(WIDTH * HEIGHT); + next_field = malloc(WIDTH * HEIGHT); + image_data = create_image(WIDTH, HEIGHT); - for (int i = 0; i < width * height; i++) { + for (int i = 0; i < WIDTH * HEIGHT; i++) { field[i] = rand() & 1; } - return pixels; + return &image_data; }