/* File: test_app.c Author: Taylor Robbins Date: 08\28\2025 Description: ** When no other application is present, this serves as a simple test case for the CWasm layer */ #define CWASM_DEBUG 1 #include "cwasm.c" struct { GlId positionBuffer; GlId colorBuffer; GlId texCoordBuffer; GlId vao; GlId dotTexture; GlId testTexture; GlId vertexShader; GlId fragmentShader; GlId testShader; GlId texture1Location; GlId worldMatrixLocation; GlId viewMatrixLocation; GlId projMatrixLocation; GlId backgroundTextures[5]; u32 frameIndex; r64 prevProgramTimeR64; } app; static const char* VertexShaderCodeStr; static const char* FragmentShaderCodeStr; r32 OscillateBy(u64 timeSource, r32 min, r32 max, u64 periodMs, u64 offset) { r32 lerpValue = (sinf((((timeSource + offset) % periodMs) / (r32)periodMs) * 2*Pi32) + 1.0f) / 2.0f; return min + (max - min) * lerpValue; } // +--------------------------------------------------------------+ // | Load Resources | // +--------------------------------------------------------------+ WASM_EXPORT(App_GetResourcePath) const char* App_GetResourcePath(int resourceIndex) { switch (resourceIndex) { case 0: return "parallax-mountain-bg.png"; case 1: return "parallax-mountain-montain-far.png"; case 2: return "parallax-mountain-mountains.png"; case 3: return "parallax-mountain-trees.png"; case 4: return "parallax-mountain-foreground-trees.png"; default: return nullptr; } } WASM_EXPORT(App_ResourceLoaded) void App_ResourceLoaded(int resourceIndex, int fileSize, u8* fileBytes) { switch (resourceIndex) { case 0: case 1: case 2: case 3: case 4: { 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); #if 0 //NOTE: Firefox claims that this is deprecated? jsGlPixelStorei(GL_UNPACK_FLIP_Y_WEBGL, true); #else //We need to reverse the rows of pixels so the image is not upside-down 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); } #endif #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.backgroundTextures[resourceIndex] = jsGlCreateTexture(); jsGlActiveTexture(GL_TEXTURE0); jsGlBindTexture(GL_TEXTURE_2D, app.backgroundTextures[resourceIndex]); // jsGlPixelStorei(GL_UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1); 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_NEAREST_MIPMAP_NEAREST); jsGlTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 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 | // +--------------------------------------------------------------+ WASM_EXPORT(App_Initialize) bool App_Initialize() { InitializeCWasm(Megabytes(2)); memset(&app, 0x00, sizeof(app)); #if 0 Write_D("Hello\nWorld!"); PrintLine_I(" Fuzzy %u Bunnies!\n%s", 31415926, "What"); WriteLine_D(""); WriteLine_W("When"); Write_D("\n"); WriteLine_E("Where"); #endif ScratchBegin(scratch); PrintLine_D("GL_VERSION: \"%s\"", jsGlGetParameterString(scratch, GL_VERSION)); PrintLine_D("GL_VENDOR: \"%s\"", jsGlGetParameterString(scratch, GL_VENDOR)); #if 1 SetLabelPrint("glVersionLabel", "GL_VERSION: %s", jsGlGetParameterString(scratch, GL_VERSION)); SetLabelPrint("glVendorLabel", "GL_VENDOR: %s", jsGlGetParameterString(scratch, GL_VENDOR)); #endif jsGlEnable(GL_BLEND); jsGlBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); jsGlBlendEquation(GL_FUNC_ADD); r32 positions[] = { 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 0.0 }; app.positionBuffer = jsGlCreateBuffer(); jsGlBindBuffer(GL_ARRAY_BUFFER, app.positionBuffer); jsGlBufferData(GL_ARRAY_BUFFER, sizeof(positions), &positions[0], GL_STATIC_DRAW); u8 colors[] = { // R , G , B 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, }; app.colorBuffer = jsGlCreateBuffer(); jsGlBindBuffer(GL_ARRAY_BUFFER, app.colorBuffer); jsGlBufferData(GL_ARRAY_BUFFER, sizeof(colors), &colors[0], GL_STATIC_DRAW); r32 texCoords[] = { // U, V 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, }; app.texCoordBuffer = jsGlCreateBuffer(); jsGlBindBuffer(GL_ARRAY_BUFFER, app.texCoordBuffer); jsGlBufferData(GL_ARRAY_BUFFER, sizeof(texCoords), &texCoords[0], GL_STATIC_DRAW); app.vao = jsGlCreateVertexArray(); jsGlBindVertexArray(app.vao); // start "recording" // position attribute data jsGlBindBuffer(GL_ARRAY_BUFFER, app.positionBuffer); jsGlEnableVertexAttribArray(0); jsGlVertexAttribPointer( 0, // attrib location 2, // components per element: vec2 for our postition data GL_FLOAT, false, // whether the data is normalized to 0.0 1.0 range in shaders 0, // stride, not important atm 0 // offset, not important atm ); // color attribute data jsGlBindBuffer(GL_ARRAY_BUFFER, app.colorBuffer); jsGlEnableVertexAttribArray(1); jsGlVertexAttribPointer( 1, // attrib location 3, // components per element: GL_UNSIGNED_BYTE, // we have Uint8Array true, // the 0..255 is normalized into 0.0...1.0 in shaders 0, //stride 0 //offset ); // texCoord attribute data jsGlBindBuffer(GL_ARRAY_BUFFER, app.texCoordBuffer); jsGlEnableVertexAttribArray(2); jsGlVertexAttribPointer( 2, // attrib location 2, // components per element: GL_FLOAT, false, // not normalized 0, //stride 0 //offset ); jsGlBindVertexArray(0); // end "recording" app.dotTexture = jsGlCreateTexture(); jsGlActiveTexture(GL_TEXTURE0); jsGlBindTexture(GL_TEXTURE_2D, app.dotTexture); u32 dotPixel = 0xFFFFFFFF; jsGlTexImage2D( GL_TEXTURE_2D, //bound texture type 0, //image level GL_RGBA, //internal format 1, //image width 1, //image height 0, //border GL_RGBA, //format GL_UNSIGNED_BYTE, //type sizeof(dotPixel), //dataLength &dotPixel //dataPntr ); jsGlTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST); jsGlTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); jsGlTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); jsGlTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); jsGlGenerateMipmap(GL_TEXTURE_2D); app.testTexture = jsGlCreateTexture(); jsGlActiveTexture(GL_TEXTURE0); jsGlBindTexture(GL_TEXTURE_2D, app.testTexture); u32 testPixels[] = { 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, 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, 0xFFEEEE55, 0xFFDDDD55, 0xFFCCCC55, 0xFFBBBB55, 0xFFAAAA55, 0xFF999955, 0xFF888855, 0xFF777755, 0xFF666655, 0xFF555555, }; jsGlTexImage2D( GL_TEXTURE_2D, //bound texture type 0, //image level GL_RGBA, //internal format 10, //image width 10, //image height 0, //border GL_RGBA, //format GL_UNSIGNED_BYTE, //type sizeof(testPixels), //dataLength &testPixels //dataPntr ); jsGlTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST); jsGlTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 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); app.vertexShader = jsGlCreateShader(GL_VERTEX_SHADER); jsGlShaderSource(app.vertexShader, (int)strlen(VertexShaderCodeStr), VertexShaderCodeStr); jsGlCompileShader(app.vertexShader); 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)) { 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)) { 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); const char* texture1Name = "Texture1"; app.texture1Location = jsGlGetUniformLocation(app.testShader, (int)strlen(texture1Name), texture1Name); const char* worldMatrixName = "WorldMatrix"; app.worldMatrixLocation = jsGlGetUniformLocation(app.testShader, (int)strlen(worldMatrixName), worldMatrixName); const char* viewMatrixName = "ViewMatrix"; 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; } // +--------------------------------------------------------------+ // | Close | // +--------------------------------------------------------------+ WASM_EXPORT(App_Close) void App_Close() { jsGlDeleteProgram(app.testShader); jsGlDeleteShader(app.vertexShader); jsGlDeleteShader(app.fragmentShader); jsGlDeleteVertexArray(app.vao); jsGlDeleteBuffer(app.positionBuffer); jsGlDeleteBuffer(app.colorBuffer); } // +--------------------------------------------------------------+ // | Update and Render | // +--------------------------------------------------------------+ WASM_EXPORT(App_UpdateAndRender) bool App_UpdateAndRender(r64 programTimeR64) { bool shouldContinue = true; r64 timeScaleR64 = (programTimeR64 - app.prevProgramTimeR64) / (1000.0 / 60.0); if (fabs(timeScaleR64 - 1.0) < 0.001) { timeScaleR64 = 1.0; } if (timeScaleR64 > 4.0) { timeScaleR64 = 4.0; } if (timeScaleR64 < 0.0) { timeScaleR64 = 0.0; } r32 timeScale = (r32)timeScaleR64; r32 programTimef = (r32)programTimeR64; u64 programTime = (u64)programTimeR64; u64 elapsedMs = programTime - (u64)app.prevProgramTimeR64; // jsGlClearColor(OscillateBy(programTime, 0.0f, 1.0f, 3700, 1500), OscillateBy(programTime, 0.0f, 1.0f, 5300, 2000), OscillateBy(programTime, 0.0f, 1.0f, 2300, 500), 1.0f); jsGlClearColor(32/255.0f, 32/255.0f, 32/255.0f, 1.0f); jsGlClear(GL_COLOR_BUFFER_BIT); jsGlBindVertexArray(app.vao); // our vertex array object jsGlUseProgram(app.testShader); // our shader program mat4 identityMatrix = Mat4_Identity; jsGlUniformMatrix4fv(app.viewMatrixLocation, &identityMatrix); jsGlUniformMatrix4fv(app.projMatrixLocation, &identityMatrix); #if 0 const u64 numTris = 75; for (u64 tIndex = 0; tIndex < numTris; tIndex++) { // r32 uniformValues[] = { // OscillateBy(programTime, 0.0f, 1.0f, 2000, 0 + tIndex*(2000/numTris)), // OscillateBy(programTime, 0.0f, 1.0f, 2000, 1200 + tIndex*(2000/numTris)), // OscillateBy(programTime, 0.0f, 1.0f, 2000, 700 + tIndex*(2000/numTris)), // }; // jsGlUniform1fv(app.testUniformLocation, ArrayCount(uniformValues), &uniformValues[0]); r32 offsetX = OscillateBy(programTime, -0.1f, 0.1f, 3000, 0 + tIndex*(3000/numTris)); r32 offsetY = OscillateBy(programTime, -0.1f, 0.1f, 3000, 750 + tIndex*(3000/numTris)); r32 scaleX = OscillateBy(programTime, 0.9f - tIndex*(0.9f/numTris), 1.1f - tIndex*(0.9f/numTris), 3000, 0 + tIndex*(3000/numTris)); r32 scaleY = OscillateBy(programTime, 0.9f - tIndex*(0.9f/numTris), 1.1f - tIndex*(0.9f/numTris), 3000, 1500 + tIndex*(3000/numTris)); r32 worldMatrix[] = { scaleX, 0.0f, 0.0f, offsetX, 0.0f, scaleY, 0.0f, offsetY, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, }; jsGlUniformMatrix4fv(app.worldMatrixLocation, &worldMatrix[0]); jsGlDrawArrays(GL_TRIANGLES, 0, 6); } #endif for (int bIndex = 0; bIndex < ArrayCount(app.backgroundTextures); bIndex++) { mat4 worldMatrix = Mat4_Identity_Const; worldMatrix = MulMat4(MakeScaleMat4(3.4f, 2.0f, 1.0f), worldMatrix); r32 parrallax = ((r32)bIndex / (r32)ArrayCount(app.backgroundTextures)); worldMatrix = MulMat4(MakeTranslateMat4(-1.7f + OscillateBy(programTime, -0.2f*parrallax, 0.2f*parrallax, 15000, 0), -1.0f, 0.0f), worldMatrix); jsGlUniformMatrix4fv(app.worldMatrixLocation, &worldMatrix); jsGlActiveTexture(GL_TEXTURE0); jsGlBindTexture(GL_TEXTURE_2D, app.backgroundTextures[bIndex]); jsGlDrawArrays(GL_TRIANGLES, 0, 6); } SetLabelPrint("offsetLabel", "Offset: %f", OscillateBy(programTime, -1.0f, 1.0f, 15000, 0)); SetLabelPrint("memoryLabel", "Mem: %u pages", stdCurrentPageCount); #if 1 { r32 offsetX = -0.4f; r32 offsetY = -0.4f; r32 scaleX = 0.8f; r32 scaleY = 0.8f; #if 1 mat4 worldMatrix = Mat4_Identity_Const; worldMatrix = MulMat4(MakeScaleMat4(scaleX, scaleY, 1.0f), worldMatrix); worldMatrix = MulMat4(MakeTranslateMat4(offsetX, offsetY, 0.0f), worldMatrix); #else mat4 worldMatrix = NewMat4_Const( scaleX, 0.0f, 0.0f, offsetX, 0.0f, scaleY, 0.0f, offsetY, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f ); #endif jsGlUniformMatrix4fv(app.worldMatrixLocation, &worldMatrix); jsGlActiveTexture(GL_TEXTURE0); jsGlBindTexture(GL_TEXTURE_2D, app.testTexture); jsGlDrawArrays(GL_TRIANGLES, 0, 6); } #endif // if (programTime > 5000) { shouldContinue = false; } app.frameIndex++; app.prevProgramTimeR64 = programTimeR64; return shouldContinue; } // +--------------------------------------------------------------+ // | Shaders | // +--------------------------------------------------------------+ static const char* VertexShaderCodeStr = "#version 300 es\n" "// ^^^\n" "// the version definition has to be the first line in\n" "// the string.\n" "\n" "// sets the precision level for all float and vec\n" "// data types\n" "precision highp float;\n" "\n" "uniform mat4 WorldMatrix;\n" "uniform mat4 ViewMatrix;\n" "uniform mat4 ProjMatrix;\n" "\n" "// this is the vertex attribute at index 0\n" "// which we defined in the vertex array object.\n" "// we can use any name for this in the glsl code\n" "// the important bit is the location=0\n" "layout(location=0) in vec2 aPos;\n" "\n" "// this is the color attrib at index: 1\n" "layout(location=1) in vec3 aCol;\n" "\n" "// this is the texCoord attrib at index: 2\n" "layout(location=2) in vec2 aTexCoord;\n" "\n" "// this is the interpolate color which is\n" "// passed to the fragment shader\n" "out vec3 vCol;\n" "\n" "out vec2 vTexCoord;\n" "\n" "void main(){\n" " vCol = aCol; // just pass through the value \n" " vTexCoord = aTexCoord; // just pass through the value \n" " \n" " // vertex position for the shader program\n" " // always a vec4 value\n" " gl_Position = ((vec4(aPos, 0.0, 1.0) * WorldMatrix) * ViewMatrix) * ProjMatrix;\n" "}\n"; static const char* FragmentShaderCodeStr = "#version 300 es\n" "precision highp float;\n" "\n" "uniform sampler2D Texture1;\n" "\n" "in vec3 vCol; // the data from vertex shader\n" "in vec2 vTexCoord; // the data from vertex shader\n" "\n" "// fragment output value\n" "// essentially the color of the output pixel\n" "out vec4 outCol;\n" "\n" "void main(){\n" " vec4 sampleColor = texture(Texture1, vTexCoord);\n" " outCol = vec4(vCol, 1.0) * sampleColor;\n" "}\n";