From 24ee518f1f7226ae8be264a36b924233f0ea0846 Mon Sep 17 00:00:00 2001 From: AntonPieper Date: Mon, 12 Feb 2024 21:26:54 +0100 Subject: [PATCH] Add textures example - Uses `LoadTexture` asynchronously - maps file paths to IDs - maps IDs to images - adds `publicDir` as a parameter for specifying the root of all resource loading - adds a second 2D context to draw tinted images on --- build.sh | 4 +- examples/resources/raylib_logo.png | Bin 0 -> 3672 bytes examples/textures_logo_raylib.c | 62 ++++++++++++++++ index.html | 4 +- raylib.js | 109 ++++++++++++++++++++++++++++- wasm/textures_logo_raylib.wasm | Bin 0 -> 3341 bytes 6 files changed, 175 insertions(+), 4 deletions(-) create mode 100644 examples/resources/raylib_logo.png create mode 100644 examples/textures_logo_raylib.c create mode 100755 wasm/textures_logo_raylib.wasm diff --git a/build.sh b/build.sh index 278c21d..2f1883a 100755 --- a/build.sh +++ b/build.sh @@ -10,6 +10,7 @@ clang -I./include/ -o build/core_input_keys ./examples/core_input_keys.c -L./lib clang -I./include/ -o build/shapes_colors_palette ./examples/shapes_colors_palette.c -L./lib/ -lraylib -lm clang -I./include/ -o build/game ./game.c -L./lib/ -lraylib -lm clang -I./include/ -o ./build/core_input_mouse_wheel ./examples/core_input_mouse_wheel.c -L./lib/ -lraylib -lm +clang -I./include/ -o ./build/textures_logo_raylib ./examples/textures_logo_raylib.c -L./lib/ -lraylib -lm clang --target=wasm32 -I./include --no-standard-libraries -Wl,--export-table -Wl,--no-entry -Wl,--allow-undefined -Wl,--export=main -o wasm/core_basic_window.wasm ./examples/core_basic_window.c -DPLATFORM_WEB clang --target=wasm32 -I./include --no-standard-libraries -Wl,--export-table -Wl,--no-entry -Wl,--allow-undefined -Wl,--export=main -o wasm/core_basic_screen_manager.wasm ./examples/core_basic_screen_manager.c -DPLATFORM_WEB @@ -17,6 +18,7 @@ clang --target=wasm32 -I./include --no-standard-libraries -Wl,--export-table -Wl clang --target=wasm32 -I./include --no-standard-libraries -Wl,--export-table -Wl,--no-entry -Wl,--allow-undefined -Wl,--export=main -o wasm/shapes_colors_palette.wasm ./examples/shapes_colors_palette.c -DPLATFORM_WEB clang --target=wasm32 -I./include --no-standard-libraries -Wl,--export-table -Wl,--no-entry -Wl,--allow-undefined -Wl,--export=main -o wasm/game.wasm game.c -DPLATFORM_WEB clang --target=wasm32 -I./include --no-standard-libraries -Wl,--export-table -Wl,--no-entry -Wl,--allow-undefined -Wl,--export=main -o wasm/core_input_mouse_wheel.wasm ./examples/core_input_mouse_wheel.c -DPLATFORM_WEB +clang --target=wasm32 -I./include --no-standard-libraries -Wl,--export-table -Wl,--no-entry -Wl,--allow-undefined -Wl,--export=main -o wasm/textures_logo_raylib.wasm ./examples/textures_logo_raylib.c -DPLATFORM_WEB # Instrument all wasm files with Asyncify -ls wasm | xargs -I{} wasm-opt --asyncify wasm/{} --pass-arg=asyncify-imports@env.WindowShouldClose -o wasm/{} +ls wasm | xargs -I{} wasm-opt --asyncify wasm/{} --pass-arg=asyncify-imports@env.WindowShouldClose,env.LoadTexture -o wasm/{} diff --git a/examples/resources/raylib_logo.png b/examples/resources/raylib_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..15bbaa2f42d01ac30fb672d31ab00701e6c32895 GIT binary patch literal 3672 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 F{U1buITru` literal 0 HcmV?d00001 diff --git a/examples/textures_logo_raylib.c b/examples/textures_logo_raylib.c new file mode 100644 index 0000000..f170dab --- /dev/null +++ b/examples/textures_logo_raylib.c @@ -0,0 +1,62 @@ +/******************************************************************************************* +* +* raylib [textures] example - Texture loading and drawing +* +* Example originally created with raylib 1.0, last time updated with raylib 1.0 +* +* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified, +* BSD-like license that allows static linking with closed source software +* +* Copyright (c) 2014-2024 Ramon Santamaria (@raysan5) +* +********************************************************************************************/ + +#include "raylib.h" + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + + InitWindow(screenWidth, screenHeight, "raylib [textures] example - texture loading and drawing"); + + // NOTE: Textures MUST be loaded after Window initialization (OpenGL context is required) + Texture2D texture = LoadTexture("resources/raylib_logo.png"); // Texture loading + //--------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + // TODO: Update your variables here + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + DrawTexture(texture, screenWidth/2 - texture.width/2, screenHeight/2 - texture.height/2, WHITE); + + DrawText("this IS a texture!", 360, 370, 10, GRAY); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + UnloadTexture(texture); // Texture unloading + + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} \ No newline at end of file diff --git a/index.html b/index.html index 32dfee8..6a3dc0a 100644 --- a/index.html +++ b/index.html @@ -47,7 +47,8 @@ const wasmPaths = { "tsoding": ["game",], "core": ["core_basic_window", "core_basic_screen_manager", "core_input_keys", "core_input_mouse_wheel",], - "shapes": ["shapes_colors_palette"] + "shapes": ["shapes_colors_palette"], + "textures": ["textures_logo_raylib"] } const raylibExampleSelect = document.getElementById("raylib-example-select"); @@ -76,6 +77,7 @@ raylibJs.start({ wasmPath: `wasm/${selectedWasm}.wasm`, canvasId: "game", + publicDir: "examples/" }); } else { window.addEventListener("load", () => { diff --git a/raylib.js b/raylib.js index 4f01bf4..97b92fe 100644 --- a/raylib.js +++ b/raylib.js @@ -48,8 +48,12 @@ export default class RaylibJs { #reset() { this.previous = undefined; + /** @type {{instance: WebAssembly.Instance, module: WebAssembly.Module}} */ this.wasm = undefined; + this.publicDir = undefined; + /** @type {CanvasRenderingContext2D} */ this.ctx = undefined; + /** @type {CanvasRenderingContext2D} */ this.dt = undefined; this.targetFPS = 60; this.prevPressedKeyState = new Set(); @@ -57,6 +61,11 @@ export default class RaylibJs { this.currentMouseWheelMoveState = 0; this.currentMousePosition = {x: 0, y: 0}; this.quit = false; + // FIXME: Could theoretically be an array + /** @type {Map}*/ + this.textures = new Map(); + /** @type {Map} */ + this.textureIDs = new Map(); } constructor() { @@ -67,7 +76,7 @@ export default class RaylibJs { this.quit = true; } - async start({ wasmPath, canvasId }) { + async start({ wasmPath, canvasId, publicDir = "./" }) { if (this.wasm !== undefined) { console.error("The game is already running. Please stop() it first."); return; @@ -75,9 +84,12 @@ export default class RaylibJs { const canvas = document.getElementById(canvasId); this.ctx = canvas.getContext("2d"); - if (this.ctx === null) { + const bgCanvas = document.createElement("canvas"); + this.btx = bgCanvas.getContext("2d"); + if (this.ctx === null || this.btx === null) { throw new Error("Could not create 2d canvas context"); } + this.publicDir = publicDir; this.wasm = await Asyncify.instantiateStreaming(fetch(wasmPath), { env: make_environment(this) }); @@ -87,6 +99,8 @@ export default class RaylibJs { InitWindow(width, height, title_ptr) { this.ctx.canvas.width = width; this.ctx.canvas.height = height; + this.btx.canvas.width = width; + this.btx.canvas.height = height; const buffer = this.wasm.instance.exports.memory.buffer; document.title = cstr_by_ptr(buffer, title_ptr); @@ -193,6 +207,32 @@ export default class RaylibJs { this.ctx.fillStyle = color; this.ctx.fillRect(posX, posY, width, height); } + + // RLAPI void DrawTexture(Texture2D texture, int posX, int posY, Color tint); // Draw a Texture2D + DrawTexture(texture_ptr, posX, posY, tint_ptr) { + /** @type {ArrayBuffer} */ + const buffer = this.wasm.instance.exports.memory.buffer; + const tint = getColorFromMemory(buffer, tint_ptr); + const textureID = new Uint32Array(buffer, texture_ptr)[0]; + const texture = this.textures.get(textureID); + // TODO: actually use width / height from the passed struct + const width = texture.width; + const height = texture.height; + if (texture === undefined) { + // TODO: Better error reporting + throw new Error(`textureID ${textureID} not found.`); + } + this.btx.clearRect(0, 0, width, height); + this.btx.drawImage(texture, 0, 0); + this.btx.fillStyle = tint; + this.btx.globalCompositeOperation = "multiply"; + this.btx.fillRect(0, 0, width, height); + this.btx.globalCompositeOperation = "destination-in"; + this.btx.drawImage(texture, 0, 0); + this.btx.globalCompositeOperation = "source-over"; + this.ctx.drawImage(this.btx.canvas, 0, 0, width, height, posX, posY, width, height); + + } IsKeyPressed(key) { return !this.prevPressedKeyState.has(key) && this.currentPressedKeyState.has(key); @@ -260,6 +300,71 @@ export default class RaylibJs { return this.ctx.measureText(text).width; } + LoadTexture(result_ptr, fileName_ptr) { + const buffer = this.wasm.instance.exports.memory.buffer; + const fileName = this.publicDir + cstr_by_ptr(buffer, fileName_ptr); + const result = new DataView(buffer, result_ptr); + if (this.textureIDs.has(fileName)) { + const img = this.textures.get(this.textureIDs.get(fileName)); + this.#setTexture(result, img, fileName); + return; + } + const img = new Image(); + // Wrap image loading in a promise + const promise = new Promise((resolve, reject) => { + function wrapResolve() { + img.removeEventListener("error", wrapReject); + resolve(); + }; + function wrapReject() { + img.removeEventListener("load", wrapResolve); + reject(); + }; + img.addEventListener("load", wrapResolve, { once: true }); + img.addEventListener("error", wrapReject, { once: true }); + }); + img.src = fileName; + return promise.then(() => { + this.#setTexture(result, img, fileName); + console.log("Loaded texture", fileName); + }).catch((err) => { + // TODO: Proper image error handling + console.error(err); + throw err; + }); + } + + /** + * @param {DataView} buffer + * @param {HTMLImageElement} img + * @param {string} fileName + */ + #setTexture(buffer, img, fileName) { + /* + // Texture, tex data stored in GPU memory (VRAM) + typedef struct Texture { + unsigned int id; // OpenGL texture id + int width; // Texture base width + int height; // Texture base height + int mipmaps; // Mipmap levels, 1 by default + int format; // Data format (PixelFormat type) + } Texture; + */ + const id = this.textureIDs.size; + buffer.setUint32(0, id); + this.textures.set(id, img); + this.textureIDs.set(fileName, id); + const PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 = 7; + new Int32Array( + buffer.buffer, buffer.byteOffset + 4, 4 * 4 + ).set([ + img.width, + img.height, + 1, + PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 + ]); + } + memcpy(dest_ptr, src_ptr, count) { const buffer = this.wasm.instance.exports.memory.buffer; // TODO: Why this does not fix asyncify problems diff --git a/wasm/textures_logo_raylib.wasm b/wasm/textures_logo_raylib.wasm new file mode 100755 index 0000000000000000000000000000000000000000..dcb7aa5cdd9e9f6b8a6abdd9376b44c04fc443a2 GIT binary patch literal 3341 zcmc&%33Qaz8GZN7BpEV+3gODr-z zM+B!N!gbRki_(4_ofIOeJ^Sk&m@D8l@2Ng{2t6&!b`Aw0gM6%Tye^WRbH^J`J)mm~i8L3TI&ufX- zrlW~?bvj%Vi@tTGNxt;EKqYwzerd?| z1JanqB8dfU##Y&?BT_6ml)?qCwn4QU_Xs01Ia7?c&j@JbLP9R$g}kUyW^tB2#ma2X zE;XJJpSI`FCl_-LFRrk&yO{N$Jeal45GSq?zt$Lq6|g zNEfZQgO-uY`2>Hu?!NP<7ES=xIwdR1UEuX@Lq$Q$P?u%@C4ZeDsIwjo59Txy2?aTZUIHMf{I&FX@)xf zB;~h(C&_kDal7)v9ABdR4zNUaf{HtpuXKE=^4|bUE2FHQWN;0_np(aw~YNPLGk>z}p~wStj2G`^xR0;_Ys6bV%j;>8JU>1NM_UK*c*W z&7I(#kpAp1-v!I%E>Q6%OS%w{^#H@ zc>+{?LRWl{<13Z_1z0Ihf{ITnKiKiZmH#C;T%H0IpHjZU?VJ(H{|X!-PlJk2D?h}0 zF3`i0&#LEcfhYE`D^=`qh zz9;|h9*1sKJ)Yw{%6Ofi--F}j6;SaNouF62S0NKPLH+=alYOA#J~g}s>Wy-SI|mar z?2q6?c^y=IU9E3`Z$Kt-lKjbqy$LG5sfM?}w;*ToOxX|a&mPaW!M7okd4~KMoGkBv zitn@?&nY^rvo!x-z_a9CQ1M+&^B(vf9W#tr7iy7?L!lEzp^SP zdbrAFMNduv`47n#S!bz#&gf8izr)4~8*mt_utn{%Ddd7w0X_y4X7s=L;gafKddR5^ zcR~BUj&KUJNr0?c2;^i%s8BkzYXub*d8%Q2hc>n9CATPk`rS;ml~|B1iUt3Yog^#-cUVu|$2MU$ct*T%WVjx%Y$A97Yz0o0?-0ma~nc6Yly#z;L{db!{&P>G{zV zCrxFz-Ajz=)uB0=sB5W>BvUBTramtfsVlE(MP6+z9IwwGIHcczeif!~vC42Kmghyn f?xiGU`xG~|WW{=UeJoKEj-~npQqlT&xen@|wdc7w literal 0 HcmV?d00001