Skip to content

Commit

Permalink
Add textures example
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
AntonPieper committed Feb 12, 2024
1 parent 2a4cf2c commit a40e71b
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 5 deletions.
4 changes: 3 additions & 1 deletion build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ 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
clang --target=wasm32 -I./include --no-standard-libraries -Wl,--export-table -Wl,--no-entry -Wl,--allow-undefined -Wl,--export=main -o wasm/core_input_keys.wasm ./examples/core_input_keys.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/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/{} [email protected] -o wasm/{}
ls wasm | xargs -I{} wasm-opt --asyncify wasm/{} [email protected],env.LoadTexture -o wasm/{}
Binary file added examples/resources/raylib_logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
62 changes: 62 additions & 0 deletions examples/textures_logo_raylib.c
Original file line number Diff line number Diff line change
@@ -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;
}
4 changes: 3 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -76,6 +77,7 @@
raylibJs.start({
wasmPath: `wasm/${selectedWasm}.wasm`,
canvasId: "game",
publicDir: "examples/"
});
} else {
window.addEventListener("load", () => {
Expand Down
111 changes: 108 additions & 3 deletions raylib.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export default class RaylibJs {

/**
* @typedef {(event: T) => void} EventHandler
* @template {Event} T
* @template {Event} T
*/

/** @type {EventHandler<KeyEvent> =} */
Expand All @@ -48,15 +48,24 @@ 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();
this.currentPressedKeyState = new Set();
this.currentMouseWheelMoveState = 0;
this.currentMousePosition = {x: 0, y: 0};
this.quit = false;
// FIXME: Could theoretically be an array
/** @type {Map<number, HTMLImageElement>}*/
this.textures = new Map();
/** @type {Map<string, number>} */
this.textureIDs = new Map();
}

constructor() {
Expand All @@ -67,17 +76,20 @@ 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;
}

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)
});
Expand All @@ -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);

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down
Binary file added wasm/textures_logo_raylib.wasm
Binary file not shown.

0 comments on commit a40e71b

Please sign in to comment.