Added an asynchronous resource loading mechanism through App_GetResourcePath and App_ResourceLoaded. Added stb_image.h and used it to parse a .png file loaded from the server and upload it to app.testTexture. There is some ugliness around the edges images with transparency, probably caused by something pre-multiplied alpha related.

This commit is contained in:
2025-09-02 11:43:19 -07:00
parent 1a9775d670
commit f2d590be13
7 changed files with 8166 additions and 1 deletions

View File

@@ -443,6 +443,7 @@
"void jsGlBindBuffer(GlEnum bufferType, GlId bufferId)", "void jsGlBindBuffer(GlEnum bufferType, GlId bufferId)",
"void jsGlBindTexture(GlEnum target, GlId textureId)", "void jsGlBindTexture(GlEnum target, GlId textureId)",
"void jsGlBindVertexArray(GlId vaoId)", "void jsGlBindVertexArray(GlId vaoId)",
"void jsGlBlendEquation(GlEnum equation)",
"void jsGlBlendFunc(GlEnum srcFactor, GlEnum dstFactor)", "void jsGlBlendFunc(GlEnum srcFactor, GlEnum dstFactor)",
"void jsGlBlendFuncSeparate(GlEnum srcRGB, GlEnum dstRGB, GlEnum srcAlpha, GlEnum dstAlpha)", "void jsGlBlendFuncSeparate(GlEnum srcRGB, GlEnum dstRGB, GlEnum srcAlpha, GlEnum dstAlpha)",
"void jsGlBufferData(GlEnum bufferType, u32 dataLength, const void* dataPntr, GlEnum usageHint)", "void jsGlBufferData(GlEnum bufferType, u32 dataLength, const void* dataPntr, GlEnum usageHint)",
@@ -462,6 +463,7 @@
"void jsGlFrontFace(GlEnum cullMode)", "void jsGlFrontFace(GlEnum cullMode)",
"void jsGlGenerateMipmap(GlEnum target)", "void jsGlGenerateMipmap(GlEnum target)",
"void jsGlLinkProgram(GlId programId)", "void jsGlLinkProgram(GlId programId)",
"void jsGlPixelStorei(GlEnum parameter, int value)",
"void jsGlShaderSource(GlId shaderId, int sourceLength, const char* sourcePntr)", "void jsGlShaderSource(GlId shaderId, int sourceLength, const char* sourcePntr)",
"void jsGlTexImage2D(GlEnum target, GlEnum level, GlEnum internalFormat, int width, int height, int border, GlEnum format, GlEnum type, int dataLength, const void* dataPntr)", "void jsGlTexImage2D(GlEnum target, GlEnum level, GlEnum internalFormat, int width, int height, int border, GlEnum format, GlEnum type, int dataLength, const void* dataPntr)",
"void jsGlTexParameteri(GlEnum target, GlEnum parameter, int value)", "void jsGlTexParameteri(GlEnum target, GlEnum parameter, int value)",

27
cwasm.c
View File

@@ -18,6 +18,18 @@ Description:
#include "cwasm_webgl_js_imports.h" #include "cwasm_webgl_js_imports.h"
#include "cwasm_webgl_constants.h" #include "cwasm_webgl_constants.h"
#define STB_IMAGE_IMPLEMENTATION
#define STBIDEF static
#define STBI_NO_STDIO
#define STBI_ASSERT(expression) Assert(expression)
#define STBI_MALLOC(numBytes) AllocMem(&ScratchArenas[0], numBytes)
#define STBI_REALLOC_SIZED(allocPntr, oldSize, newSize) ReallocMem(&ScratchArenas[0], (allocPntr), (oldSize), (newSize))
#define STBI_FREE(allocPntr) //nothing TODO: Once we have a general purpose arena we can implement this!
#define STBI_NO_SIMD
#define STBI_ONLY_PNG
#define STBI_NO_THREAD_LOCALS
#include "stb_image.h"
void InitializeCWasm(u32 scratchArenasSize) void InitializeCWasm(u32 scratchArenasSize)
{ {
InitGlobalArenas(scratchArenasSize); InitGlobalArenas(scratchArenasSize);
@@ -30,3 +42,18 @@ WASM_EXPORT(cAllocMem) void* cAllocMem(Arena* arenaPntr, int numBytes, int align
Assert(alignment >= 0); Assert(alignment >= 0);
return AllocMemAligned(arenaPntr, (u32)numBytes, (u32)alignment); return AllocMemAligned(arenaPntr, (u32)numBytes, (u32)alignment);
} }
WASM_EXPORT(cGetScratchArenaPntr) Arena* cGetScratchArenaPntr(Arena* conflictArenaPntr)
{
ArenaMark scratchMark = GetScratch1(conflictArenaPntr);
return scratchMark.arena;
}
WASM_EXPORT(cGetScratchArenaMark) u32 cGetScratchArenaMark(Arena* conflictArenaPntr)
{
ArenaMark scratchMark = GetScratch1(conflictArenaPntr);
return scratchMark.mark;
}
WASM_EXPORT(cEndScratchArena) void cEndScratchArena(Arena* arenaPntr, u32 mark)
{
ResetToMark(arenaPntr, mark);
}

View File

@@ -20,6 +20,7 @@ MAYBE_EXTERN_C void jsGlEnable(GlEnum capability);
MAYBE_EXTERN_C void jsGlDisable(GlEnum capability); MAYBE_EXTERN_C void jsGlDisable(GlEnum capability);
MAYBE_EXTERN_C void jsGlBlendFunc(GlEnum srcFactor, GlEnum dstFactor); MAYBE_EXTERN_C void jsGlBlendFunc(GlEnum srcFactor, GlEnum dstFactor);
MAYBE_EXTERN_C void jsGlBlendFuncSeparate(GlEnum srcRGB, GlEnum dstRGB, GlEnum srcAlpha, GlEnum dstAlpha); MAYBE_EXTERN_C void jsGlBlendFuncSeparate(GlEnum srcRGB, GlEnum dstRGB, GlEnum srcAlpha, GlEnum dstAlpha);
MAYBE_EXTERN_C void jsGlBlendEquation(GlEnum equation);
MAYBE_EXTERN_C void jsGlDepthFunc(GlEnum depthFunc); MAYBE_EXTERN_C void jsGlDepthFunc(GlEnum depthFunc);
MAYBE_EXTERN_C void jsGlFrontFace(GlEnum cullMode); MAYBE_EXTERN_C void jsGlFrontFace(GlEnum cullMode);
MAYBE_EXTERN_C void jsGlDeleteBuffer(GlId bufferId); MAYBE_EXTERN_C void jsGlDeleteBuffer(GlId bufferId);
@@ -30,6 +31,7 @@ MAYBE_EXTERN_C void jsGlDeleteTexture(GlId textureId);
MAYBE_EXTERN_C GlId jsGlCreateTexture(); MAYBE_EXTERN_C GlId jsGlCreateTexture();
MAYBE_EXTERN_C void jsGlActiveTexture(GlEnum textureIndex); MAYBE_EXTERN_C void jsGlActiveTexture(GlEnum textureIndex);
MAYBE_EXTERN_C void jsGlBindTexture(GlEnum target, GlId textureId); MAYBE_EXTERN_C void jsGlBindTexture(GlEnum target, GlId textureId);
MAYBE_EXTERN_C void jsGlPixelStorei(GlEnum parameter, int value);
MAYBE_EXTERN_C void jsGlTexImage2D(GlEnum target, GlEnum level, GlEnum internalFormat, int width, int height, int border, GlEnum format, GlEnum type, int dataLength, const void* dataPntr); MAYBE_EXTERN_C void jsGlTexImage2D(GlEnum target, GlEnum level, GlEnum internalFormat, int width, int height, int border, GlEnum format, GlEnum type, int dataLength, const void* dataPntr);
MAYBE_EXTERN_C void jsGlTexParameteri(GlEnum target, GlEnum parameter, int value); MAYBE_EXTERN_C void jsGlTexParameteri(GlEnum target, GlEnum parameter, int value);
MAYBE_EXTERN_C void jsGlGenerateMipmap(GlEnum target); MAYBE_EXTERN_C void jsGlGenerateMipmap(GlEnum target);

View File

@@ -133,6 +133,10 @@ export function jsGlBlendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha)
{ {
appGlobals.glContext.blendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha); appGlobals.glContext.blendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha);
} }
export function jsGlBlendEquation(equation)
{
appGlobals.glContext.blendEquation(equation);
}
export function jsGlDepthFunc(depthFunc) export function jsGlDepthFunc(depthFunc)
{ {
@@ -203,6 +207,11 @@ export function jsGlBindTexture(target, textureId)
appGlobals.glContext.bindTexture(target, texture); appGlobals.glContext.bindTexture(target, texture);
} }
export function jsGlPixelStorei(parameter, value)
{
appGlobals.glContext.pixelStorei(parameter, value);
}
export function jsGlTexImage2D(target, level, internalFormat, width, height, border, format, type, dataLength, dataPntr) export function jsGlTexImage2D(target, level, internalFormat, width, height, border, format, type, dataLength, dataPntr)
{ {
let dataBuffer = new Uint8Array(appGlobals.memDataView.buffer, dataPntr, dataLength); let dataBuffer = new Uint8Array(appGlobals.memDataView.buffer, dataPntr, dataLength);
@@ -563,6 +572,7 @@ export let jsGlFunctions = {
jsGlDisable: jsGlDisable, jsGlDisable: jsGlDisable,
jsGlBlendFunc: jsGlBlendFunc, jsGlBlendFunc: jsGlBlendFunc,
jsGlBlendFuncSeparate: jsGlBlendFuncSeparate, jsGlBlendFuncSeparate: jsGlBlendFuncSeparate,
jsGlBlendEquation: jsGlBlendEquation,
jsGlDepthFunc: jsGlDepthFunc, jsGlDepthFunc: jsGlDepthFunc,
jsGlFrontFace: jsGlFrontFace, jsGlFrontFace: jsGlFrontFace,
jsGlDeleteBuffer: jsGlDeleteBuffer, jsGlDeleteBuffer: jsGlDeleteBuffer,
@@ -573,6 +583,7 @@ export let jsGlFunctions = {
jsGlCreateTexture: jsGlCreateTexture, jsGlCreateTexture: jsGlCreateTexture,
jsGlActiveTexture: jsGlActiveTexture, jsGlActiveTexture: jsGlActiveTexture,
jsGlBindTexture: jsGlBindTexture, jsGlBindTexture: jsGlBindTexture,
jsGlPixelStorei: jsGlPixelStorei,
jsGlTexImage2D: jsGlTexImage2D, jsGlTexImage2D: jsGlTexImage2D,
jsGlTexParameteri: jsGlTexParameteri, jsGlTexParameteri: jsGlTexParameteri,
jsGlGenerateMipmap: jsGlGenerateMipmap, jsGlGenerateMipmap: jsGlGenerateMipmap,

View File

@@ -51,10 +51,22 @@ async function MainLoop()
console.log("Loading WASM Module..."); console.log("Loading WASM Module...");
await LoadWasmModule("app.wasm", 4); await LoadWasmModule("app.wasm", 4);
let initSuccess = appGlobals.wasmModule.exports.App_Initialize(); let initSuccess = appGlobals.wasmModule.exports.App_Initialize();
if (initSuccess) if (initSuccess)
{ {
var resourcePaths = [];
var resourceIndex = 0;
while (true)
{
let resourcePathPntr = appGlobals.wasmModule.exports.App_GetResourcePath(resourceIndex);
if (resourcePathPntr == 0) { break; }
let resourcePathStr = wasmPntrToJsString(resourcePathPntr);
resourcePaths.push(resourcePathStr);
resourceIndex++;
}
console.log("Running!"); console.log("Running!");
function renderFrame(currentTime) function renderFrame(currentTime)
{ {
@@ -63,6 +75,43 @@ async function MainLoop()
else { appGlobals.wasmModule.exports.App_Close(); } else { appGlobals.wasmModule.exports.App_Close(); }
} }
window.requestAnimationFrame(renderFrame); window.requestAnimationFrame(renderFrame);
if (resourcePaths.length > 0)
{
console.log("Loading " + resourcePaths.length + " Resource" + (resourcePaths.length == 1 ? "" : "s") + "...");
// console.dir(resourcePaths);
for (var rIndex = 0; rIndex < resourcePaths.length; rIndex++)
{
const file = await fetch(resourcePaths[rIndex]);
if (file.ok)
{
const fileBytes = await file.arrayBuffer();
// console.log(fileBytes);
var fileBytesPntr = 0;
var scratchArenaPntr = 0;
var scratchArenaMark = 0;
if (fileBytes.byteLength > 0)
{
scratchArenaPntr = appGlobals.wasmModule.exports.cGetScratchArenaPntr(0);
scratchArenaMark = appGlobals.wasmModule.exports.cGetScratchArenaMark(0);
fileBytesPntr = appGlobals.wasmModule.exports.cAllocMem(scratchArenaPntr, fileBytes.byteLength);
if (fileBytesPntr != 0)
{
const writeArray = new Uint8Array(appGlobals.wasmModule.exports.memory.buffer);
writeArray.set(new Uint8Array(fileBytes), fileBytesPntr);
}
else { console.error("Failed to allocate " + fileBytes.byteLength + " byte resource[" + rIndex + "] from \"" + resourcePaths[rIndex] + "\""); }
}
appGlobals.wasmModule.exports.App_ResourceLoaded(rIndex, fileBytes.byteLength, fileBytesPntr);
if (fileBytes.byteLength > 0 && fileBytesPntr != 0) { appGlobals.wasmModule.exports.cEndScratchArena(scratchArenaPntr, scratchArenaMark); }
}
else
{
console.error("Failed to fetch resource[" + rIndex + "] from \"" + resourcePaths[rIndex] + "\"");
appGlobals.wasmModule.exports.App_ResourceLoaded(rIndex, 0, 0);
}
}
}
} }
else else
{ {

7988
stb_image.h Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -38,12 +38,94 @@ r32 OscillateBy(u64 timeSource, r32 min, r32 max, u64 periodMs, u64 offset)
return min + (max - min) * lerpValue; return min + (max - min) * lerpValue;
} }
// +--------------------------------------------------------------+
// | Load Resources |
// +--------------------------------------------------------------+
WASM_EXPORT(App_GetResourcePath) const char* App_GetResourcePath(int resourceIndex)
{
switch (resourceIndex)
{
case 0: return "icon_64.png";
default: return nullptr;
}
}
WASM_EXPORT(App_ResourceLoaded) void App_ResourceLoaded(int resourceIndex, int fileSize, u8* fileBytes)
{
switch (resourceIndex)
{
case 0:
{
PrintLine_D("Got resource %d bytes: %p %02X %02X %02X %02X", fileSize, fileBytes, fileBytes[0], fileBytes[1], fileBytes[2], fileBytes[3]);
ScratchBegin(scratch); //NOTE: stbi_load_from_memory implicitly allocates from the first scratch arena
int imageWidth = 0;
int imageHeight = 0;
int imageChannelCount = 0;
stbi_uc* textureBytes = stbi_load_from_memory(fileBytes, fileSize, &imageWidth, &imageHeight, &imageChannelCount, 4);
if (textureBytes != nullptr)
{
PrintLine_D("Parsed image: %p %dx%d %d channel(s)", textureBytes, imageWidth, imageHeight, imageChannelCount);
//We need to reverse the rows of pixels so the image is not upside-down
//TODO: Replace with GL_UNPACK_FLIP_Y_WEBGL
u32 rowWidth = imageWidth * sizeof(u32);
u8* tempRowBuffer = AllocArray(u8, scratch, rowWidth);
for (int yIndex = 0; yIndex < imageHeight/2; yIndex++)
{
memcpy(tempRowBuffer, &textureBytes[yIndex * rowWidth], rowWidth);
memcpy(&textureBytes[yIndex * rowWidth], &textureBytes[(imageHeight-1 - yIndex) * rowWidth], rowWidth);
memcpy(&textureBytes[(imageHeight-1 - yIndex) * rowWidth], tempRowBuffer, rowWidth);
}
#if 0
for (int yIndex = 0; yIndex < imageHeight; yIndex++)
{
for (int xIndex = 0; xIndex < imageWidth; xIndex++)
{
u8* pixelPntr = &textureBytes[((yIndex * imageWidth) + xIndex) * sizeof(u32)];
r32 alpha = (pixelPntr[3]/255.0f);
pixelPntr[0] = (u8)((pixelPntr[0]/255.0f) * alpha * 255.0f);
pixelPntr[1] = (u8)((pixelPntr[1]/255.0f) * alpha * 255.0f);
pixelPntr[2] = (u8)((pixelPntr[2]/255.0f) * alpha * 255.0f);
}
}
#endif
if (app.testTexture != 0) { jsGlDeleteTexture(app.testTexture); }
app.testTexture = jsGlCreateTexture();
jsGlActiveTexture(GL_TEXTURE0);
jsGlBindTexture(GL_TEXTURE_2D, app.testTexture);
// jsGlPixelStorei(GL_UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1);
// jsGlPixelStorei(GL_UNPACK_FLIP_Y_WEBGL, true);
jsGlTexImage2D(
GL_TEXTURE_2D, //bound texture type
0, //image level
GL_RGBA, //internal format
imageWidth, //image width
imageHeight, //image height
0, //border
GL_RGBA, //format
GL_UNSIGNED_BYTE, //type
imageWidth*imageHeight*sizeof(u32), //dataLength
textureBytes //dataPntr
);
jsGlTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
jsGlTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
jsGlTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
jsGlTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
jsGlGenerateMipmap(GL_TEXTURE_2D);
}
else { PrintLine_E("Failed to parse %d byte image using stb_image.h!", fileSize); }
ScratchEnd(scratch);
} break;
}
}
// +--------------------------------------------------------------+ // +--------------------------------------------------------------+
// | Initialize | // | Initialize |
// +--------------------------------------------------------------+ // +--------------------------------------------------------------+
WASM_EXPORT(App_Initialize) bool App_Initialize() WASM_EXPORT(App_Initialize) bool App_Initialize()
{ {
InitializeCWasm(Kilobytes(128)); InitializeCWasm(Megabytes(2));
memset(&app, 0x00, sizeof(app)); memset(&app, 0x00, sizeof(app));
#if 0 #if 0
@@ -60,6 +142,10 @@ WASM_EXPORT(App_Initialize) bool App_Initialize()
PrintLine_D("GL_VERSION: \"%s\"", jsGlGetParameterString(scratch, GL_VERSION)); PrintLine_D("GL_VERSION: \"%s\"", jsGlGetParameterString(scratch, GL_VERSION));
PrintLine_D("GL_VENDOR: \"%s\"", jsGlGetParameterString(scratch, GL_VENDOR)); PrintLine_D("GL_VENDOR: \"%s\"", jsGlGetParameterString(scratch, GL_VENDOR));
jsGlEnable(GL_BLEND);
jsGlBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
jsGlBlendEquation(GL_FUNC_ADD);
r32 positions[] = { r32 positions[] = {
0.0, 0.0, 0.0, 0.0,
1.0, 0.0, 1.0, 0.0,