Skip to content

Commit a40e71b

Browse files
committed
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
1 parent 2a4cf2c commit a40e71b

File tree

6 files changed

+176
-5
lines changed

6 files changed

+176
-5
lines changed

build.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@ clang -I./include/ -o build/core_input_keys ./examples/core_input_keys.c -L./lib
1010
clang -I./include/ -o build/shapes_colors_palette ./examples/shapes_colors_palette.c -L./lib/ -lraylib -lm
1111
clang -I./include/ -o build/game ./game.c -L./lib/ -lraylib -lm
1212
clang -I./include/ -o ./build/core_input_mouse_wheel ./examples/core_input_mouse_wheel.c -L./lib/ -lraylib -lm
13+
clang -I./include/ -o ./build/textures_logo_raylib ./examples/textures_logo_raylib.c -L./lib/ -lraylib -lm
1314

1415
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
1516
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
1617
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
1718
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
1819
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
1920
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
21+
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
2022

2123
# Instrument all wasm files with Asyncify
22-
ls wasm | xargs -I{} wasm-opt --asyncify wasm/{} --pass-arg=asyncify-imports@env.WindowShouldClose -o wasm/{}
24+
ls wasm | xargs -I{} wasm-opt --asyncify wasm/{} --pass-arg=asyncify-imports@env.WindowShouldClose,env.LoadTexture -o wasm/{}

examples/resources/raylib_logo.png

3.59 KB
Loading

examples/textures_logo_raylib.c

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*******************************************************************************************
2+
*
3+
* raylib [textures] example - Texture loading and drawing
4+
*
5+
* Example originally created with raylib 1.0, last time updated with raylib 1.0
6+
*
7+
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
8+
* BSD-like license that allows static linking with closed source software
9+
*
10+
* Copyright (c) 2014-2024 Ramon Santamaria (@raysan5)
11+
*
12+
********************************************************************************************/
13+
14+
#include "raylib.h"
15+
16+
//------------------------------------------------------------------------------------
17+
// Program main entry point
18+
//------------------------------------------------------------------------------------
19+
int main(void)
20+
{
21+
// Initialization
22+
//--------------------------------------------------------------------------------------
23+
const int screenWidth = 800;
24+
const int screenHeight = 450;
25+
26+
InitWindow(screenWidth, screenHeight, "raylib [textures] example - texture loading and drawing");
27+
28+
// NOTE: Textures MUST be loaded after Window initialization (OpenGL context is required)
29+
Texture2D texture = LoadTexture("resources/raylib_logo.png"); // Texture loading
30+
//---------------------------------------------------------------------------------------
31+
32+
// Main game loop
33+
while (!WindowShouldClose()) // Detect window close button or ESC key
34+
{
35+
// Update
36+
//----------------------------------------------------------------------------------
37+
// TODO: Update your variables here
38+
//----------------------------------------------------------------------------------
39+
40+
// Draw
41+
//----------------------------------------------------------------------------------
42+
BeginDrawing();
43+
44+
ClearBackground(RAYWHITE);
45+
46+
DrawTexture(texture, screenWidth/2 - texture.width/2, screenHeight/2 - texture.height/2, WHITE);
47+
48+
DrawText("this IS a texture!", 360, 370, 10, GRAY);
49+
50+
EndDrawing();
51+
//----------------------------------------------------------------------------------
52+
}
53+
54+
// De-Initialization
55+
//--------------------------------------------------------------------------------------
56+
UnloadTexture(texture); // Texture unloading
57+
58+
CloseWindow(); // Close window and OpenGL context
59+
//--------------------------------------------------------------------------------------
60+
61+
return 0;
62+
}

index.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@
4747
const wasmPaths = {
4848
"tsoding": ["game",],
4949
"core": ["core_basic_window", "core_basic_screen_manager", "core_input_keys", "core_input_mouse_wheel",],
50-
"shapes": ["shapes_colors_palette"]
50+
"shapes": ["shapes_colors_palette"],
51+
"textures": ["textures_logo_raylib"]
5152
}
5253

5354
const raylibExampleSelect = document.getElementById("raylib-example-select");
@@ -76,6 +77,7 @@
7677
raylibJs.start({
7778
wasmPath: `wasm/${selectedWasm}.wasm`,
7879
canvasId: "game",
80+
publicDir: "examples/"
7981
});
8082
} else {
8183
window.addEventListener("load", () => {

raylib.js

Lines changed: 108 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export default class RaylibJs {
3434

3535
/**
3636
* @typedef {(event: T) => void} EventHandler
37-
* @template {Event} T
37+
* @template {Event} T
3838
*/
3939

4040
/** @type {EventHandler<KeyEvent> =} */
@@ -48,15 +48,24 @@ export default class RaylibJs {
4848

4949
#reset() {
5050
this.previous = undefined;
51+
/** @type {{instance: WebAssembly.Instance, module: WebAssembly.Module}} */
5152
this.wasm = undefined;
53+
this.publicDir = undefined;
54+
/** @type {CanvasRenderingContext2D} */
5255
this.ctx = undefined;
56+
/** @type {CanvasRenderingContext2D} */
5357
this.dt = undefined;
5458
this.targetFPS = 60;
5559
this.prevPressedKeyState = new Set();
5660
this.currentPressedKeyState = new Set();
5761
this.currentMouseWheelMoveState = 0;
5862
this.currentMousePosition = {x: 0, y: 0};
5963
this.quit = false;
64+
// FIXME: Could theoretically be an array
65+
/** @type {Map<number, HTMLImageElement>}*/
66+
this.textures = new Map();
67+
/** @type {Map<string, number>} */
68+
this.textureIDs = new Map();
6069
}
6170

6271
constructor() {
@@ -67,17 +76,20 @@ export default class RaylibJs {
6776
this.quit = true;
6877
}
6978

70-
async start({ wasmPath, canvasId }) {
79+
async start({ wasmPath, canvasId, publicDir = "./" }) {
7180
if (this.wasm !== undefined) {
7281
console.error("The game is already running. Please stop() it first.");
7382
return;
7483
}
7584

7685
const canvas = document.getElementById(canvasId);
7786
this.ctx = canvas.getContext("2d");
78-
if (this.ctx === null) {
87+
const bgCanvas = document.createElement("canvas");
88+
this.btx = bgCanvas.getContext("2d");
89+
if (this.ctx === null || this.btx === null) {
7990
throw new Error("Could not create 2d canvas context");
8091
}
92+
this.publicDir = publicDir;
8193
this.wasm = await Asyncify.instantiateStreaming(fetch(wasmPath), {
8294
env: make_environment(this)
8395
});
@@ -87,6 +99,8 @@ export default class RaylibJs {
8799
InitWindow(width, height, title_ptr) {
88100
this.ctx.canvas.width = width;
89101
this.ctx.canvas.height = height;
102+
this.btx.canvas.width = width;
103+
this.btx.canvas.height = height;
90104
const buffer = this.wasm.instance.exports.memory.buffer;
91105
document.title = cstr_by_ptr(buffer, title_ptr);
92106

@@ -193,6 +207,32 @@ export default class RaylibJs {
193207
this.ctx.fillStyle = color;
194208
this.ctx.fillRect(posX, posY, width, height);
195209
}
210+
211+
// RLAPI void DrawTexture(Texture2D texture, int posX, int posY, Color tint); // Draw a Texture2D
212+
DrawTexture(texture_ptr, posX, posY, tint_ptr) {
213+
/** @type {ArrayBuffer} */
214+
const buffer = this.wasm.instance.exports.memory.buffer;
215+
const tint = getColorFromMemory(buffer, tint_ptr);
216+
const textureID = new Uint32Array(buffer, texture_ptr)[0];
217+
const texture = this.textures.get(textureID);
218+
// TODO: actually use width / height from the passed struct
219+
const width = texture.width;
220+
const height = texture.height;
221+
if (texture === undefined) {
222+
// TODO: Better error reporting
223+
throw new Error(`textureID ${textureID} not found.`);
224+
}
225+
this.btx.clearRect(0, 0, width, height);
226+
this.btx.drawImage(texture, 0, 0);
227+
this.btx.fillStyle = tint;
228+
this.btx.globalCompositeOperation = "multiply";
229+
this.btx.fillRect(0, 0, width, height);
230+
this.btx.globalCompositeOperation = "destination-in";
231+
this.btx.drawImage(texture, 0, 0);
232+
this.btx.globalCompositeOperation = "source-over";
233+
this.ctx.drawImage(this.btx.canvas, 0, 0, width, height, posX, posY, width, height);
234+
235+
}
196236

197237
IsKeyPressed(key) {
198238
return !this.prevPressedKeyState.has(key) && this.currentPressedKeyState.has(key);
@@ -260,6 +300,71 @@ export default class RaylibJs {
260300
return this.ctx.measureText(text).width;
261301
}
262302

303+
LoadTexture(result_ptr, fileName_ptr) {
304+
const buffer = this.wasm.instance.exports.memory.buffer;
305+
const fileName = this.publicDir + cstr_by_ptr(buffer, fileName_ptr);
306+
const result = new DataView(buffer, result_ptr);
307+
if (this.textureIDs.has(fileName)) {
308+
const img = this.textures.get(this.textureIDs.get(fileName));
309+
this.#setTexture(result, img, fileName);
310+
return;
311+
}
312+
const img = new Image();
313+
// Wrap image loading in a promise
314+
const promise = new Promise((resolve, reject) => {
315+
function wrapResolve() {
316+
img.removeEventListener("error", wrapReject);
317+
resolve();
318+
};
319+
function wrapReject() {
320+
img.removeEventListener("load", wrapResolve);
321+
reject();
322+
};
323+
img.addEventListener("load", wrapResolve, { once: true });
324+
img.addEventListener("error", wrapReject, { once: true });
325+
});
326+
img.src = fileName;
327+
return promise.then(() => {
328+
this.#setTexture(result, img, fileName);
329+
console.log("Loaded texture", fileName);
330+
}).catch((err) => {
331+
// TODO: Proper image error handling
332+
console.error(err);
333+
throw err;
334+
});
335+
}
336+
337+
/**
338+
* @param {DataView} buffer
339+
* @param {HTMLImageElement} img
340+
* @param {string} fileName
341+
*/
342+
#setTexture(buffer, img, fileName) {
343+
/*
344+
// Texture, tex data stored in GPU memory (VRAM)
345+
typedef struct Texture {
346+
unsigned int id; // OpenGL texture id
347+
int width; // Texture base width
348+
int height; // Texture base height
349+
int mipmaps; // Mipmap levels, 1 by default
350+
int format; // Data format (PixelFormat type)
351+
} Texture;
352+
*/
353+
const id = this.textureIDs.size;
354+
buffer.setUint32(0, id);
355+
this.textures.set(id, img);
356+
this.textureIDs.set(fileName, id);
357+
const PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 = 7;
358+
new Int32Array(
359+
buffer.buffer, buffer.byteOffset + 4, 4 * 4
360+
).set([
361+
img.width,
362+
img.height,
363+
1,
364+
PIXELFORMAT_UNCOMPRESSED_R8G8B8A8
365+
]);
366+
}
367+
263368
memcpy(dest_ptr, src_ptr, count) {
264369
const buffer = this.wasm.instance.exports.memory.buffer;
265370
// TODO: Why this does not fix asyncify problems

wasm/textures_logo_raylib.wasm

3.26 KB
Binary file not shown.

0 commit comments

Comments
 (0)