From b62f0d279b2c5981f8765dcb26278bc620db9268 Mon Sep 17 00:00:00 2001 From: Taylor Robbins Date: Mon, 1 Sep 2025 21:53:24 -0700 Subject: [PATCH] Add cAllocMem to cwasm.c which the javascript side can use to allocate memory from an opaque Arena pointer that was passed to it. Used this to implement jsGlGetParameterString, jsGlGetShaderInfoLog, and jsGlGetProgramInfoLog in gl_functions.js. Added NotNull macro and fixed bug in ScratchBegin macro. Added a bool return for App_Initialize which allows us to stop the main loop from beginning (which prevents log spew when something goes wrong) --- CWasm.sublime-project | 9 ++++++ cwasm.c | 8 +++++ cwasm.h | 1 + cwasm_arena.c | 2 +- cwasm_webgl_js_imports.h | 8 +++++ data/gl_functions.js | 68 ++++++++++++++++++++++++++++++++++++++-- data/globals.js | 1 + data/main.js | 22 ++++++++----- data/wasm_functions.js | 11 +++++++ test_app.c | 23 +++++++++----- 10 files changed, 135 insertions(+), 18 deletions(-) diff --git a/CWasm.sublime-project b/CWasm.sublime-project index e33dba2..6a110a3 100644 --- a/CWasm.sublime-project +++ b/CWasm.sublime-project @@ -67,6 +67,7 @@ "#define ArrayCount(array)", "#define Assert(condition)", "#define IsAlignedTo(pntr, alignment)", + "#define NotNull(expression)", "#define PrintLine_D(formatStrNt, ...)", "#define PrintLine_E(formatStrNt, ...)", "#define PrintLine_I(formatStrNt, ...)", @@ -92,16 +93,24 @@ "ArenaMark GetArenaMark(Arena* arena)", "ArenaMark GetScratch()", "ArenaMark GetScratch1(Arena* conflictArena)", + "GlEnum jsGlGetParameterEnum(GlEnum parameter)", "GlId jsGlCreateBuffer()", "GlId jsGlCreateProgram()", "GlId jsGlCreateShader(GlEnum shaderType)", "GlId jsGlCreateTexture()", "GlId jsGlCreateVertexArray()", "GlId jsGlGetUniformLocation(GlId programId, int nameLength, const char* namePntr)", + "bool jsGlGetParameterBool(GlEnum parameter)", "bool jsGlGetProgramParameterBool(GlId programId, GlEnum parameter)", "bool jsGlGetShaderParameterBool(GlId shaderId, GlEnum parameter)", + "char* jsGlGetParameterString(void* arenaPntr, GlEnum parameter)", + "char* jsGlGetProgramInfoLog(void* arenaPntr, GlId programId)", + "char* jsGlGetShaderInfoLog(void* arenaPntr, GlId shaderId)", + "float jsGlGetParameterFloat(GlEnum parameter)", "inline void* ReallocMem(Arena* arena, void* oldPntr, u32 oldSize, u32 newSize)", "inline void* ReallocMemUnaligned(Arena* arena, void* oldPntr, u32 oldSize, u32 newSize)", + "int jsGlGetError()", + "int jsGlGetParameterInt(GlEnum parameter)", "int jsGlGetProgramParameterInt(GlId programId, GlEnum parameter)", "int jsGlGetShaderParameterInt(GlId shaderId, GlEnum parameter)", "void InitGlobalArenas(u32 scratchArenasSize)", diff --git a/cwasm.c b/cwasm.c index 927466d..067e552 100644 --- a/cwasm.c +++ b/cwasm.c @@ -20,3 +20,11 @@ void InitializeCWasm(u32 scratchArenasSize) { InitGlobalArenas(scratchArenasSize); } + +WASM_EXPORT(cAllocMem) void* cAllocMem(Arena* arenaPntr, int numBytes, int alignment) +{ + NotNull(arenaPntr); + Assert(numBytes >= 0); + Assert(alignment >= 0); + return AllocMemAligned(arenaPntr, (u32)numBytes, (u32)alignment); +} diff --git a/cwasm.h b/cwasm.h index 1163c1a..3a0e6ed 100644 --- a/cwasm.h +++ b/cwasm.h @@ -129,6 +129,7 @@ Description: #define Assert(condition) sizeof(condition) //do nothing, but make sure anything used in condition is not treated as "unused" when assertions are disabled #define AssertMsg(condition, message) sizeof(condition) #endif +#define NotNull(expression) Assert((expression) != nullptr) //Actual Value of Pi: 3.1415926535897932384626433832795... #define Pi64 3.14159265358979311599796346854 //accurate to 15 digits diff --git a/cwasm_arena.c b/cwasm_arena.c index d25eb2e..9df9c4c 100644 --- a/cwasm_arena.c +++ b/cwasm_arena.c @@ -178,5 +178,5 @@ ArenaMark GetScratch1(Arena* conflictArena) static inline ArenaMark GetScratch() { return GetScratch1(nullptr); } #define ScratchBegin1(scratchName, conflictArenaPntr) ArenaMark scratchName##Mark = GetScratch1(conflictArenaPntr); Arena* scratchName = scratchName##Mark.arena -#define ScratchBegin(scratchName) ScratchBegin(scratchName, nullptr) +#define ScratchBegin(scratchName) ScratchBegin1(scratchName, nullptr) #define ScratchEnd(scratchName) ResetToArenaMark(scratchName##Mark) diff --git a/cwasm_webgl_js_imports.h b/cwasm_webgl_js_imports.h index 05b7a6d..d4f278e 100644 --- a/cwasm_webgl_js_imports.h +++ b/cwasm_webgl_js_imports.h @@ -10,6 +10,12 @@ Date: 09\01\2025 typedef int GlId; // Really this is just an index into a javascript array that holds the real reference to the WebGL object typedef int GlEnum; +MAYBE_EXTERN_C int jsGlGetError(); +MAYBE_EXTERN_C bool jsGlGetParameterBool(GlEnum parameter); +MAYBE_EXTERN_C GlEnum jsGlGetParameterEnum(GlEnum parameter); +MAYBE_EXTERN_C int jsGlGetParameterInt(GlEnum parameter); +MAYBE_EXTERN_C float jsGlGetParameterFloat(GlEnum parameter); +MAYBE_EXTERN_C char* jsGlGetParameterString(void* arenaPntr, GlEnum parameter); MAYBE_EXTERN_C void jsGlEnable(GlEnum capability); MAYBE_EXTERN_C void jsGlDisable(GlEnum capability); MAYBE_EXTERN_C void jsGlBlendFunc(GlEnum srcFactor, GlEnum dstFactor); @@ -38,10 +44,12 @@ MAYBE_EXTERN_C void jsGlShaderSource(GlId shaderId, int sourceLength, const char MAYBE_EXTERN_C void jsGlCompileShader(GlId shaderId); MAYBE_EXTERN_C bool jsGlGetShaderParameterBool(GlId shaderId, GlEnum parameter); MAYBE_EXTERN_C int jsGlGetShaderParameterInt(GlId shaderId, GlEnum parameter); +MAYBE_EXTERN_C char* jsGlGetShaderInfoLog(void* arenaPntr, GlId shaderId); MAYBE_EXTERN_C void jsGlDeleteProgram(GlId programId); MAYBE_EXTERN_C GlId jsGlCreateProgram(); MAYBE_EXTERN_C void jsGlAttachShader(GlId programId, GlId shaderId); MAYBE_EXTERN_C void jsGlLinkProgram(GlId programId); +MAYBE_EXTERN_C char* jsGlGetProgramInfoLog(void* arenaPntr, GlId programId); MAYBE_EXTERN_C void jsGlUseProgram(GlId programId); MAYBE_EXTERN_C bool jsGlGetProgramParameterBool(GlId programId, GlEnum parameter); MAYBE_EXTERN_C int jsGlGetProgramParameterInt(GlId programId, GlEnum parameter); diff --git a/data/gl_functions.js b/data/gl_functions.js index 694d129..6c03f44 100644 --- a/data/gl_functions.js +++ b/data/gl_functions.js @@ -1,6 +1,6 @@ import { appGlobals } from './globals.js' -import { wasmPntrToJsString, wasmPntrAndLengthToJsString } from './wasm_functions.js' +import { wasmPntrToJsString, wasmPntrAndLengthToJsString, jsStringToWasmPntr } from './wasm_functions.js' export var glObjects = { buffers: [ null ], @@ -79,6 +79,43 @@ function verifyParameter(verifyResult, functionName, parameterName, parameterVal // +--------------------------------------------------------------+ // | WebGL API | // +--------------------------------------------------------------+ +export function jsGlGetError(capability) +{ + return appGlobals.glContext.getError(); +} + +export function jsGlGetParameterBool(parameter) +{ + let paramValue = appGlobals.glContext.getParameter(parameter); + if (typeof(paramValue) != "boolean") { console.error("Tried to get GL parameter " + parameter + " as bool when it's actually: " + typeof(paramValue)); return false; } + return paramValue; +} +export function jsGlGetParameterEnum(parameter) +{ + let paramValue = appGlobals.glContext.getParameter(parameter); + if (typeof(paramValue) != "number") { console.error("Tried to get GL parameter " + parameter + " as enum when it's actually: " + typeof(paramValue)); return false; } + return paramValue; +} +export function jsGlGetParameterInt(parameter) +{ + let paramValue = appGlobals.glContext.getParameter(parameter); + if (typeof(paramValue) != "number") { console.error("Tried to get GL parameter " + parameter + " as int when it's actually: " + typeof(paramValue)); return false; } + return paramValue; +} +export function jsGlGetParameterFloat(parameter) +{ + let paramValue = appGlobals.glContext.getParameter(parameter); + if (typeof(paramValue) != "number") { console.error("Tried to get GL parameter " + parameter + " as float when it's actually: " + typeof(paramValue)); return false; } + return paramValue; +} +export function jsGlGetParameterString(arenaPntr, parameter) +{ + let paramValue = appGlobals.glContext.getParameter(parameter); + if (typeof(paramValue) != "string") { console.error("Tried to get GL parameter " + parameter + " as string when it's actually: " + typeof(paramValue)); return false; } + let paramValuePntr = jsStringToWasmPntr(arenaPntr, paramValue, true); + return paramValuePntr; +} + export function jsGlEnable(capability) { appGlobals.glContext.enable(capability); @@ -168,7 +205,6 @@ export function jsGlBindTexture(target, textureId) export function jsGlTexImage2D(target, level, internalFormat, width, height, border, format, type, dataLength, dataPntr) { - // let dataBuffer = appGlobals.memDataView.buffer.slice(dataPntr, dataPntr + dataLength); let dataBuffer = new Uint8Array(appGlobals.memDataView.buffer, dataPntr, dataLength); appGlobals.glContext.texImage2D(target, level, internalFormat, width, height, border, format, type, dataBuffer); } @@ -266,6 +302,16 @@ export function jsGlGetShaderParameterInt(shaderId, parameter) return paramValue; } +export function jsGlGetShaderInfoLog(arenaPntr, shaderId) +{ + if (!verifyParameter(verifyGlShaderId(shaderId, false), "gl.getShaderInfoLog", "shaderId", shaderId)) { return false; } + let shader = glObjects.shaders[shaderId]; + let logString = appGlobals.glContext.getShaderInfoLog(shader); + if (logString.length == 0) { return null; } + let logStringPntr = jsStringToWasmPntr(arenaPntr, logString, true); + return logStringPntr; +} + export function jsGlDeleteProgram(programId) { if (!verifyParameter(verifyGlProgramId(programId, false), "gl.deleteProgram", "programId", programId)) { return; } @@ -296,6 +342,16 @@ export function jsGlLinkProgram(programId) appGlobals.glContext.linkProgram(program); } +export function jsGlGetProgramInfoLog(arenaPntr, programId) +{ + if (!verifyParameter(verifyGlProgramId(programId, false), "gl.getProgramInfoLog", "programId", programId)) { return false; } + let program = glObjects.programs[programId]; + let logString = appGlobals.glContext.getProgramInfoLog(program); + if (logString.length == 0) { return null; } + let logStringPntr = jsStringToWasmPntr(arenaPntr, logString, true); + return logStringPntr; +} + export function jsGlUseProgram(programId) { if (!verifyParameter(verifyGlProgramId(programId, true), "gl.useProgram", "programId", programId)) { return; } @@ -497,6 +553,12 @@ export function jsGlUniformMatrix4fv(locationId, valuesPntr) // | Functions List | // +==============================+ export let jsGlFunctions = { + jsGlGetError: jsGlGetError, + jsGlGetParameterBool: jsGlGetParameterBool, + jsGlGetParameterEnum: jsGlGetParameterEnum, + jsGlGetParameterInt: jsGlGetParameterInt, + jsGlGetParameterFloat: jsGlGetParameterFloat, + jsGlGetParameterString: jsGlGetParameterString, jsGlEnable: jsGlEnable, jsGlDisable: jsGlDisable, jsGlBlendFunc: jsGlBlendFunc, @@ -525,10 +587,12 @@ export let jsGlFunctions = { jsGlCompileShader: jsGlCompileShader, jsGlGetShaderParameterBool: jsGlGetShaderParameterBool, jsGlGetShaderParameterInt: jsGlGetShaderParameterInt, + jsGlGetShaderInfoLog: jsGlGetShaderInfoLog, jsGlDeleteProgram: jsGlDeleteProgram, jsGlCreateProgram: jsGlCreateProgram, jsGlAttachShader: jsGlAttachShader, jsGlLinkProgram: jsGlLinkProgram, + jsGlGetProgramInfoLog: jsGlGetProgramInfoLog, jsGlUseProgram: jsGlUseProgram, jsGlGetProgramParameterBool: jsGlGetProgramParameterBool, jsGlGetProgramParameterInt: jsGlGetProgramParameterInt, diff --git a/data/globals.js b/data/globals.js index 0c9a20d..5c959c2 100644 --- a/data/globals.js +++ b/data/globals.js @@ -10,5 +10,6 @@ export var appGlobals = glContext: null, memDataView: null, wasmModule: null, + textEncoder: null, textDecoder: null, }; diff --git a/data/main.js b/data/main.js index 4eb4c7b..4f878e1 100644 --- a/data/main.js +++ b/data/main.js @@ -26,6 +26,7 @@ function AcquireCanvas(canvasWidth, canvasHeight) async function LoadWasmModule(wasmFilePath, initialWasmPageCount) { appGlobals.textDecoder = new TextDecoder("utf-8"); + appGlobals.textEncoder = new TextEncoder("utf-8"); appGlobals.wasmModule = await loadWasmModule(wasmFilePath, { ...jsStdFunctions, ...jsGlFunctions }); appGlobals.memDataView = new DataView(new Uint8Array(appGlobals.wasmModule.exports.memory.buffer).buffer); let memorySize = appGlobals.wasmModule.exports.memory.buffer.byteLength; @@ -50,16 +51,23 @@ async function MainLoop() console.log("Loading WASM Module..."); await LoadWasmModule("app.wasm", 4); - appGlobals.wasmModule.exports.App_Initialize(); + let initSuccess = appGlobals.wasmModule.exports.App_Initialize(); - console.log("Running!"); - function renderFrame(currentTime) + if (initSuccess) { - let shouldContinue = appGlobals.wasmModule.exports.App_UpdateAndRender(currentTime); - if (shouldContinue) { window.requestAnimationFrame(renderFrame); } - else { appGlobals.wasmModule.exports.App_Close(); } + console.log("Running!"); + function renderFrame(currentTime) + { + let shouldContinue = appGlobals.wasmModule.exports.App_UpdateAndRender(currentTime); + if (shouldContinue) { window.requestAnimationFrame(renderFrame); } + else { appGlobals.wasmModule.exports.App_Close(); } + } + window.requestAnimationFrame(renderFrame); + } + else + { + console.error("Initialization failed!"); } - window.requestAnimationFrame(renderFrame); } MainLoop(); \ No newline at end of file diff --git a/data/wasm_functions.js b/data/wasm_functions.js index 7177602..6c09b9d 100644 --- a/data/wasm_functions.js +++ b/data/wasm_functions.js @@ -40,3 +40,14 @@ export function wasmPntrAndLengthToJsString(ptr, length) appGlobals.memDataView.buffer.slice(ptr, ptr + length) ); } + +export function jsStringToWasmPntr(arenaPntr, jsString, addNullTerm) +{ + let encodedBytes = appGlobals.textEncoder.encode(jsString); + let memPntr = appGlobals.wasmModule.exports.cAllocMem(arenaPntr, encodedBytes.length + (addNullTerm ? 1 : 0), 0); + if (memPntr == 0) { return memPntr; } + const writeArray = new Uint8Array(appGlobals.wasmModule.exports.memory.buffer); + writeArray.set(encodedBytes, memPntr); + if (addNullTerm) { writeArray[encodedBytes.length] = 0; } + return memPntr; +} diff --git a/test_app.c b/test_app.c index 9b1fc91..fd49513 100644 --- a/test_app.c +++ b/test_app.c @@ -41,9 +41,10 @@ r32 OscillateBy(u64 timeSource, r32 min, r32 max, u64 periodMs, u64 offset) // +--------------------------------------------------------------+ // | Initialize | // +--------------------------------------------------------------+ -WASM_EXPORT(App_Initialize) void App_Initialize() +WASM_EXPORT(App_Initialize) bool App_Initialize() { InitializeCWasm(Kilobytes(128)); + memset(&app, 0x00, sizeof(app)); #if 0 Write_D("Hello\nWorld!"); @@ -54,7 +55,10 @@ WASM_EXPORT(App_Initialize) void App_Initialize() WriteLine_E("Where"); #endif - memset(&app, 0x00, sizeof(app)); + ScratchBegin(scratch); + + PrintLine_D("GL_VERSION: \"%s\"", jsGlGetParameterString(scratch, GL_VERSION)); + PrintLine_D("GL_VENDOR: \"%s\"", jsGlGetParameterString(scratch, GL_VENDOR)); r32 positions[] = { 0.0, 0.0, @@ -163,9 +167,9 @@ WASM_EXPORT(App_Initialize) void App_Initialize() 0xFFEEEEEE, 0xFFDDDDEE, 0xFFCCCCEE, 0xFFBBBBEE, 0xFFAAAAEE, 0xFF9999EE, 0xFF8888EE, 0xFF7777EE, 0xFF6666EE, 0xFF5555EE, 0xFFEEEEDD, 0xFFDDDDDD, 0xFFCCCCDD, 0xFFBBBBDD, 0xFFAAAADD, 0xFF9999DD, 0xFF8888DD, 0xFF7777DD, 0xFF6666DD, 0xFF5555DD, 0xFFEEEECC, 0xFFDDDDCC, 0xFFCCCCCC, 0xFFBBBBCC, 0xFFAAAACC, 0xFF9999CC, 0xFF8888CC, 0xFF7777CC, 0xFF6666CC, 0xFF5555CC, - 0xFFEEEEBB, 0xFFDDDDBB, 0xFFCCCCBB, 0xFFBBBBBB, 0xFFAAAABB, 0xFF9999BB, 0xFF8888BB, 0xFF7777BB, 0xFF6666BB, 0xFF5555BB, - 0xFFEEEEAA, 0xFFDDDDAA, 0xFFCCCCAA, 0xFFBBBBAA, 0xFFAAAAAA, 0xFF9999AA, 0xFF8888AA, 0xFF7777AA, 0xFF6666AA, 0xFF5555AA, - 0xFFEEEE99, 0xFFDDDD99, 0xFFCCCC99, 0xFFBBBB99, 0xFFAAAA99, 0xFF999999, 0xFF888899, 0xFF777799, 0xFF666699, 0xFF555599, + 0xFFEEEEBB, 0xFFDDDDBB, 0xFFCCCCBB, 0xFFBBBBBB, 0xFFFF0000, 0xFF9999BB, 0xFF8888BB, 0xFF7777BB, 0xFF6666BB, 0xFF5555BB, + 0xFFEEEEAA, 0xFFDDDDAA, 0xFFCCCCAA, 0xFFBBBBAA, 0xFF00FF00, 0xFF9999AA, 0xFF8888AA, 0xFF7777AA, 0xFF6666AA, 0xFF5555AA, + 0xFFEEEE99, 0xFFDDDD99, 0xFFCCCC99, 0xFFBBBB99, 0xFF0000FF, 0xFF999999, 0xFF888899, 0xFF777799, 0xFF666699, 0xFF555599, 0xFFEEEE88, 0xFFDDDD88, 0xFFCCCC88, 0xFFBBBB88, 0xFFAAAA88, 0xFF999988, 0xFF888888, 0xFF777788, 0xFF666688, 0xFF555588, 0xFFEEEE77, 0xFFDDDD77, 0xFFCCCC77, 0xFFBBBB77, 0xFFAAAA77, 0xFF999977, 0xFF888877, 0xFF777777, 0xFF666677, 0xFF555577, 0xFFEEEE66, 0xFFDDDD66, 0xFFCCCC66, 0xFFBBBB66, 0xFFAAAA66, 0xFF999966, 0xFF888866, 0xFF777766, 0xFF666666, 0xFF555566, @@ -192,17 +196,17 @@ WASM_EXPORT(App_Initialize) void App_Initialize() app.vertexShader = jsGlCreateShader(GL_VERTEX_SHADER); jsGlShaderSource(app.vertexShader, (int)strlen(VertexShaderCodeStr), VertexShaderCodeStr); jsGlCompileShader(app.vertexShader); - if (!jsGlGetShaderParameterBool(app.vertexShader, GL_COMPILE_STATUS)) { WriteLine_E("Failed to compile vertex shader!"); } //TODO: jsGlGetShaderInfoLog + if (!jsGlGetShaderParameterBool(app.vertexShader, GL_COMPILE_STATUS)) { PrintLine_E("Failed to compile vertex shader: \"%s\"", jsGlGetShaderInfoLog(scratch, app.vertexShader)); return false; } app.fragmentShader = jsGlCreateShader(GL_FRAGMENT_SHADER); jsGlShaderSource(app.fragmentShader, (int)strlen(FragmentShaderCodeStr), FragmentShaderCodeStr); jsGlCompileShader(app.fragmentShader); - if (!jsGlGetShaderParameterBool(app.fragmentShader, GL_COMPILE_STATUS)) { WriteLine_E("Failed to compile fragment shader!"); } //TODO: jsGlGetShaderInfoLog + if (!jsGlGetShaderParameterBool(app.fragmentShader, GL_COMPILE_STATUS)) { PrintLine_E("Failed to compile fragment shader: \"%s\"", jsGlGetShaderInfoLog(scratch, app.fragmentShader)); return false; } app.testShader = jsGlCreateProgram(); jsGlAttachShader(app.testShader, app.vertexShader); jsGlAttachShader(app.testShader, app.fragmentShader); jsGlLinkProgram(app.testShader); // also debug the program status - if (!jsGlGetProgramParameterBool(app.testShader, GL_LINK_STATUS)) { WriteLine_E("Failed to link shader program!"); } //TODO: jsGlGetProgramInfoLog + if (!jsGlGetProgramParameterBool(app.testShader, GL_LINK_STATUS)) { PrintLine_E("Failed to link shader program: \"%s\"", jsGlGetProgramInfoLog(scratch, app.testShader)); return false; } // const char* uniformName = "TestUniform"; // app.testUniformLocation = jsGlGetUniformLocation(app.testShader, (int)strlen(uniformName), uniformName); @@ -214,6 +218,9 @@ WASM_EXPORT(App_Initialize) void App_Initialize() app.viewMatrixLocation = jsGlGetUniformLocation(app.testShader, (int)strlen(viewMatrixName), viewMatrixName); const char* projMatrixName = "ProjMatrix"; app.projMatrixLocation = jsGlGetUniformLocation(app.testShader, (int)strlen(projMatrixName), projMatrixName); + + ScratchEnd(scratch); + return true; } // +--------------------------------------------------------------+