2025-01-29 10:55:49 +01:00
// Includes snippets from https://surma.dev/things/c-to-webassembly/ and https://developer.mozilla.org/en-US/docs/WebAssembly/Using_the_JavaScript_API -->
let memory = null ;
let heapu8 = null ;
let heapu16 = null ;
let heapu32 = null ;
let heapi32 = null ;
let heapf32 = null ;
let mod = null ;
let instance = null ;
2026-06-09 12:45:01 -07:00
let audio _thread _started = false ;
2025-01-29 10:55:49 +01:00
function create _thread ( func ) {
console . log ( 'Creating thread' ) ;
const thread _starter = new Worker ( 'thread_starter.js' ) ;
const arr = new Uint8Array ( memory . buffer , func , 256 ) ;
let str = '' ;
for ( let i = 0 ; arr [ i ] != 0 ; ++ i ) {
str += String . fromCharCode ( arr [ i ] ) ;
}
thread _starter . postMessage ( { mod , memory , func : str } ) ;
}
async function start _audio _thread ( ) {
const audioContext = new AudioContext ( ) ;
await audioContext . audioWorklet . addModule ( 'audio-thread.js' ) ;
2026-06-09 12:45:01 -07:00
const audioThreadNode = new AudioWorkletNode ( audioContext , 'audio-thread' , { processorOptions : { mod , memory } } ) ;
audioThreadNode . port . onmessage = ( message ) => {
console . log ( message . data ) ;
} ;
2025-01-29 10:55:49 +01:00
audioThreadNode . connect ( audioContext . destination ) ;
}
function read _string ( ptr ) {
let str = '' ;
for ( let i = 0 ; heapu8 [ ptr + i ] != 0 ; ++ i ) {
str += String . fromCharCode ( heapu8 [ ptr + i ] ) ;
}
return str ;
}
function write _string ( ptr , str ) {
for ( let i = 0 ; i < str . length ; ++ i ) {
heapu8 [ ptr + i ] = str . charCodeAt ( i ) ;
}
heapu8 [ ptr + str . length ] = 0 ;
}
async function init ( ) {
let wasm _bytes = null ;
await fetch ( "./ShaderTest.wasm" ) . then ( res => res . arrayBuffer ( ) ) . then ( buffer => wasm _bytes = new Uint8Array ( buffer ) ) ;
// Read memory size from wasm file
let memory _size = 0 ;
let i = 8 ;
while ( i < wasm _bytes . length ) {
function read _leb ( ) {
let result = 0 ;
let shift = 0 ;
while ( true ) {
let byte = wasm _bytes [ i ++ ] ;
result |= ( byte & 0x7f ) << shift ;
if ( ( byte & 0x80 ) == 0 ) return result ;
shift += 7 ;
}
}
let type = read _leb ( )
let length = read _leb ( )
if ( type == 6 ) {
read _leb ( ) ; // count
i ++ ; // gtype
i ++ ; // mutable
read _leb ( ) ; // opcode
memory _size = read _leb ( ) / 65536 + 1 ;
break ;
}
i += length ;
}
memory = new WebAssembly . Memory ( { initial : memory _size , maximum : memory _size , shared : true } ) ;
heapu8 = new Uint8Array ( memory . buffer ) ;
heapu16 = new Uint16Array ( memory . buffer ) ;
heapu32 = new Uint32Array ( memory . buffer ) ;
heapi32 = new Int32Array ( memory . buffer ) ;
heapf32 = new Float32Array ( memory . buffer ) ;
const kanvas = document . getElementById ( 'kanvas' ) ;
const gl = kanvas . getContext ( 'webgl2' , { antialias : false , alpha : false } ) ;
// gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1);
gl . getExtension ( "EXT_color_buffer_float" ) ;
gl . getExtension ( "OES_texture_float_linear" ) ;
gl . getExtension ( "OES_texture_half_float_linear" ) ;
gl . getExtension ( "EXT_texture_filter_anisotropic" ) ;
let file _buffer = null ;
let file _buffer _pos = 0 ;
let gl _programs = [ null ] ;
let gl _shaders = [ null ] ;
let gl _buffers = [ null ] ;
let gl _framebuffers = [ null ] ;
let gl _renderbuffers = [ null ] ;
let gl _textures = [ null ] ;
let gl _locations = [ null ] ;
const result = await WebAssembly . instantiate (
wasm _bytes , {
env : { memory } ,
imports : {
create _thread ,
glViewport : function ( x , y , width , height ) {
gl . viewport ( x , y , width , height ) ;
} ,
glScissor : function ( x , y , width , height ) {
gl . scissor ( x , y , width , height ) ;
} ,
glGetIntegerv : function ( pname , data ) {
if ( pname == 2 ) { // GL_MAJOR_VERSION
heapu32 [ data / 4 ] = 3 ;
}
else {
heapu32 [ data / 4 ] = gl . getParameter ( pname ) ;
}
} ,
glGetFloatv : function ( pname , data ) {
heapf32 [ data / 4 ] = gl . getParameter ( pname ) ;
} ,
glGetString : function ( name ) {
// return gl.getParameter(name);
} ,
glDrawElements : function ( mode , count , type , offset ) {
gl . drawElements ( mode , count , type , offset ) ;
} ,
glDrawElementsInstanced : function ( mode , count , type , indices , instancecount ) {
gl . drawElementsInstanced ( mode , count , type , indices , instancecount ) ;
} ,
glVertexAttribDivisor : function ( index , divisor ) {
gl . vertexAttribDivisor ( index , divisor ) ;
} ,
glBindFramebuffer : function ( target , framebuffer ) {
gl . bindFramebuffer ( target , gl _framebuffers [ framebuffer ] ) ;
} ,
glFramebufferTexture2D : function ( target , attachment , textarget , texture , level ) {
gl . framebufferTexture2D ( target , attachment , textarget , gl _textures [ texture ] , level ) ;
if ( gl . checkFramebufferStatus ( gl . FRAMEBUFFER ) != gl . FRAMEBUFFER _COMPLETE ) {
console . log ( "Incomplete framebuffer" ) ;
}
} ,
glGenFramebuffers : function ( n , framebuffers ) {
for ( let i = 0 ; i < n ; ++ i ) {
gl _framebuffers . push ( gl . createFramebuffer ( ) ) ;
heapu32 [ framebuffers / 4 + i ] = gl _framebuffers . length - 1 ;
}
} ,
glGenRenderbuffers : function ( n , renderbuffers ) {
for ( let i = 0 ; i < n ; ++ i ) {
gl _renderbuffers . push ( gl . createRenderbuffer ( ) ) ;
heapu32 [ renderbuffers / 4 + i ] = gl _renderbuffers . length - 1 ;
}
} ,
glBindRenderbuffer : function ( target , renderbuffer ) {
gl . bindRenderbuffer ( target , gl _renderbuffers [ renderbuffer ] ) ;
} ,
glRenderbufferStorage : function ( target , internalformat , width , height ) {
gl . renderbufferStorage ( target , internalformat , width , height )
} ,
glFramebufferRenderbuffer : function ( target , attachment , renderbuffertarget , renderbuffer ) {
gl . framebufferRenderbuffer ( target , attachment , renderbuffertarget , gl _renderbuffers [ renderbuffer ] ) ;
} ,
glReadPixels : function ( x , y , width , height , format , type , data ) {
let pixels = type == gl . FLOAT ? heapf32 . subarray ( data / 4 ) : heapu8 . subarray ( data ) ;
gl . readPixels ( x , y , width , height , format , type , pixels ) ;
} ,
glTexSubImage2D : function ( target , level , xoffset , yoffset , width , height , format , type , pixels ) {
gl . texSubImage2D ( target , level , xoffset , yoffset , width , height , format , type , heapu8 . subarray ( pixels ) ) ;
} ,
glEnable : function ( cap ) {
gl . enable ( cap ) ;
} ,
glDisable : function ( cap ) {
gl . disable ( cap ) ;
} ,
glColorMask : function ( red , green , blue , alpha ) {
gl . colorMask ( red , green , blue , alpha ) ;
} ,
glClearColor : function ( red , green , blue , alpha ) {
gl . clearColor ( red , green , blue , alpha ) ;
} ,
glDepthMask : function ( flag ) {
gl . depthMask ( flag ) ;
} ,
glClearDepthf : function ( depth ) {
gl . clearDepth ( depth ) ;
} ,
glStencilMask : function ( mask ) {
gl . stencilMask ( mask ) ;
} ,
glClearStencil : function ( s ) {
gl . clearStencil ( s ) ;
} ,
glClear : function ( mask ) {
gl . clear ( mask ) ;
} ,
glBindBuffer : function ( target , buffer ) {
gl . bindBuffer ( target , gl _buffers [ buffer ] ) ;
} ,
glUseProgram : function ( program ) {
gl . useProgram ( gl _programs [ program ] ) ;
} ,
glStencilMaskSeparate : function ( face , mask ) {
gl . stencilMaskSeparate ( face , mask ) ;
} ,
glStencilOpSeparate : function ( face , fail , zfail , zpass ) {
gl . stencilOpSeparate ( face , fail , zfail , zpass ) ;
} ,
glStencilFuncSeparate : function ( face , func , ref , mask ) {
gl . stencilFuncSeparate ( face , func , ref , mask ) ;
} ,
glDepthFunc : function ( func ) {
gl . depthFunc ( func ) ;
} ,
glCullFace : function ( mode ) {
gl . cullFace ( mode ) ;
} ,
glBlendFuncSeparate : function ( src _rgb , dst _rgb , src _alpha , dst _alpha ) {
gl . blendFuncSeparate ( src _rgb , dst _rgb , src _alpha , dst _alpha ) ;
} ,
glBlendEquationSeparate : function ( mode _rgb , mode _alpha ) {
gl . blendEquationSeparate ( mode _rgb , mode _alpha ) ;
} ,
glGenBuffers : function ( n , buffers ) {
for ( let i = 0 ; i < n ; ++ i ) {
gl _buffers . push ( gl . createBuffer ( ) ) ;
heapu32 [ buffers / 4 + i ] = gl _buffers . length - 1 ;
}
} ,
glBufferData : function ( target , size , data , usage ) {
gl . bufferData ( target , heapu8 . subarray ( data , data + Number ( size ) ) , usage ) ;
} ,
glCreateProgram : function ( ) {
gl _programs . push ( gl . createProgram ( ) ) ;
return gl _programs . length - 1 ;
} ,
glAttachShader : function ( program , shader ) {
gl . attachShader ( gl _programs [ program ] , gl _shaders [ shader ] ) ;
} ,
glBindAttribLocation : function ( program , index , name ) {
gl . bindAttribLocation ( gl _programs [ program ] , index , read _string ( name ) ) ;
} ,
glLinkProgram : function ( program ) {
gl . linkProgram ( gl _programs [ program ] ) ;
} ,
glGetProgramiv : function ( program , pname , params ) {
heapu32 [ params / 4 ] = gl . getProgramParameter ( gl _programs [ program ] , pname ) ;
} ,
glGetProgramInfoLog : function ( program ) {
console . log ( gl . getProgramInfoLog ( gl _programs [ program ] ) ) ;
} ,
glCreateShader : function ( type ) {
gl _shaders . push ( gl . createShader ( type ) ) ;
return gl _shaders . length - 1 ;
} ,
glShaderSource : function ( shader , count , source , length ) {
gl . shaderSource ( gl _shaders [ shader ] , read _string ( heapu32 [ source / 4 ] ) ) ;
} ,
glCompileShader : function ( shader ) {
gl . compileShader ( gl _shaders [ shader ] ) ;
} ,
glGetShaderiv : function ( shader , pname , params ) {
heapu32 [ params / 4 ] = gl . getShaderParameter ( gl _shaders [ shader ] , pname ) ;
} ,
glGetShaderInfoLog : function ( shader ) {
console . log ( gl . getShaderInfoLog ( gl _shaders [ shader ] ) ) ;
} ,
glBufferSubData : function ( target , offset , size , data ) {
gl . bufferSubData ( target , Number ( offset ) , heapu8 . subarray ( data , data + Number ( size ) ) , 0 ) ;
} ,
glEnableVertexAttribArray : function ( index ) {
gl . enableVertexAttribArray ( index ) ;
} ,
glVertexAttribPointer : function ( index , size , type , normalized , stride , offset ) {
gl . vertexAttribPointer ( index , size , type , normalized , stride , offset ) ;
} ,
glDisableVertexAttribArray : function ( index ) {
gl . disableVertexAttribArray ( index ) ;
} ,
glGetUniformLocation : function ( program , name ) {
gl _locations . push ( gl . getUniformLocation ( gl _programs [ program ] , read _string ( name ) ) ) ;
return gl _locations . length - 1 ;
} ,
glUniform1i : function ( location , v0 ) {
gl . uniform1i ( gl _locations [ location ] , v0 ) ;
} ,
glUniform2i : function ( location , v0 , v1 ) {
gl . uniform2i ( gl _locations [ location ] , v0 , v1 ) ;
} ,
glUniform3i : function ( location , v0 , v1 , v2 ) {
gl . uniform3i ( gl _locations [ location ] , v0 , v1 , v2 ) ;
} ,
glUniform4i : function ( location , v0 , v1 , v2 , v3 ) {
gl . uniform4i ( gl _locations [ location ] , v0 , v1 , v2 , v3 ) ;
} ,
glUniform1iv : function ( location , count , value ) {
gl . uniform1iv ( gl _locations [ location ] , count , heapi32 . subarray ( value / 4 ) ) ;
} ,
glUniform2iv : function ( location , count , value ) {
gl . uniform2iv ( gl _locations [ location ] , count , heapi32 . subarray ( value / 4 ) ) ;
} ,
glUniform3iv : function ( location , count , value ) {
gl . uniform3iv ( gl _locations [ location ] , count , heapi32 . subarray ( value / 4 ) ) ;
} ,
glUniform4iv : function ( location , count , value ) {
gl . uniform4iv ( gl _locations [ location ] , count , heapi32 . subarray ( value / 4 ) ) ;
} ,
glUniform1f : function ( location , v0 ) {
gl . uniform1f ( gl _locations [ location ] , v0 ) ;
} ,
glUniform2f : function ( location , v0 , v1 ) {
gl . uniform2f ( gl _locations [ location ] , v0 , v1 ) ;
} ,
glUniform3f : function ( location , v0 , v1 , v2 ) {
gl . uniform3f ( gl _locations [ location ] , v0 , v1 , v2 ) ;
} ,
glUniform4f : function ( location , v0 , v1 , v2 , v3 ) {
gl . uniform4f ( gl _locations [ location ] , v0 , v1 , v2 , v3 ) ;
} ,
glUniform1fv : function ( location , count , value ) {
var f32 = new Float32Array ( memory . buffer , value , count ) ;
gl . uniform1fv ( gl _locations [ location ] , f32 ) ;
} ,
glUniform2fv : function ( location , count , value ) {
var f32 = new Float32Array ( memory . buffer , value , count * 2 ) ;
gl . uniform2fv ( gl _locations [ location ] , f32 ) ;
} ,
glUniform3fv : function ( location , count , value ) {
var f32 = new Float32Array ( memory . buffer , value , count * 3 ) ;
gl . uniform3fv ( gl _locations [ location ] , f32 ) ;
} ,
glUniform4fv : function ( location , count , value ) {
var f32 = new Float32Array ( memory . buffer , value , count * 4 ) ;
gl . uniform4fv ( gl _locations [ location ] , f32 ) ;
} ,
glUniformMatrix3fv : function ( location , count , transpose , value ) {
var f32 = new Float32Array ( memory . buffer , value , 3 * 3 ) ;
gl . uniformMatrix3fv ( gl _locations [ location ] , transpose , f32 ) ;
} ,
glUniformMatrix4fv : function ( location , count , transpose , value ) {
var f32 = new Float32Array ( memory . buffer , value , 4 * 4 ) ;
gl . uniformMatrix4fv ( gl _locations [ location ] , transpose , f32 ) ;
} ,
glTexParameterf : function ( target , pname , param ) {
gl . texParameterf ( target , pname , param ) ;
} ,
glActiveTexture : function ( texture ) {
gl . activeTexture ( texture ) ;
} ,
glBindTexture : function ( target , texture ) {
gl . bindTexture ( target , gl _textures [ texture ] ) ;
} ,
glTexParameteri : function ( target , pname , param ) {
gl . texParameteri ( target , pname , param ) ;
} ,
glGetActiveUniform : function ( program , index , bufSize , length , size , type , name ) {
let u = gl . getActiveUniform ( gl _programs [ program ] , index ) ;
heapu32 [ size / 4 ] = u . size ;
heapu32 [ type / 4 ] = u . type ;
write _string ( name , u . name ) ;
} ,
glGenTextures : function ( n , textures ) {
for ( let i = 0 ; i < n ; ++ i ) {
gl _textures . push ( gl . createTexture ( ) ) ;
heapu32 [ textures / 4 + i ] = gl _textures . length - 1 ;
}
} ,
glTexImage2D : function ( target , level , internalformat , width , height , border , format , type , data ) {
let pixels = type == gl . FLOAT ? heapf32 . subarray ( data / 4 ) :
type == gl . UNSIGNED _INT ? heapu32 . subarray ( data / 4 ) :
type == gl . UNSIGNED _SHORT ? heapu16 . subarray ( data / 2 ) :
type == gl . HALF _FLOAT ? heapu16 . subarray ( data / 2 ) : heapu8 . subarray ( data ) ;
gl . texImage2D ( target , level , internalformat , width , height , border , format , type , pixels ) ;
} ,
glPixelStorei : function ( pname , param ) {
gl . pixelStorei ( pname , param ) ;
} ,
glCompressedTexImage2D : function ( target , level , internalformat , width , height , border , imageSize , data ) {
gl . compressedTexImage2D ( target , level , internalformat , width , height , border , imageSize , heapu8 . subarray ( data ) ) ;
} ,
glDrawBuffers : function ( n , bufs ) {
let ar = [ ] ;
for ( let i = 0 ; i < n ; ++ i ) {
ar . push ( gl . COLOR _ATTACHMENT0 + i ) ;
}
gl . drawBuffers ( ar ) ;
} ,
glGenerateMipmap : function ( target ) {
gl . generateMipmap ( target ) ;
} ,
glFlush : function ( ) {
gl . flush ( ) ;
} ,
glDeleteBuffers : function ( n , buffers ) {
for ( let i = 0 ; i < n ; ++ i ) {
gl . deleteBuffer ( gl _buffers [ heapu32 [ buffers / 4 + i ] ] ) ;
}
} ,
glDeleteTextures : function ( n , textures ) {
for ( let i = 0 ; i < n ; ++ i ) {
gl . deleteTexture ( gl _textures [ heapu32 [ textures / 4 + i ] ] ) ;
}
} ,
glDeleteFramebuffers : function ( n , framebuffers ) {
for ( let i = 0 ; i < n ; ++ i ) {
gl . deleteFramebuffer ( gl _framebuffers [ heapu32 [ framebuffers / 4 + i ] ] ) ;
}
} ,
glDeleteProgram : function ( program ) {
gl . deleteProgram ( gl _programs [ program ] ) ;
} ,
glDeleteShader : function ( shader ) {
gl . deleteShader ( gl _shaders [ shader ] ) ;
} ,
js _fprintf : function ( format ) {
console . log ( read _string ( format ) ) ;
} ,
js _fopen : function ( filename ) {
const req = new XMLHttpRequest ( ) ;
req . open ( "GET" , read _string ( filename ) , false ) ;
req . overrideMimeType ( "text/plain; charset=x-user-defined" ) ;
req . send ( ) ;
let str = req . response ;
file _buffer _pos = 0 ;
file _buffer = new ArrayBuffer ( str . length ) ;
let buf _view = new Uint8Array ( file _buffer ) ;
for ( let i = 0 ; i < str . length ; ++ i ) {
buf _view [ i ] = str . charCodeAt ( i ) ;
}
return 1 ;
} ,
js _ftell : function ( stream ) {
return file _buffer _pos ;
} ,
js _fseek : function ( stream , offset , origin ) {
file _buffer _pos = offset ;
if ( origin == 1 ) file _buffer _pos += file _buffer . byteLength ; // SEEK_END
return 0 ;
} ,
js _fread : function ( ptr , size , count , stream ) {
let buf _view = new Uint8Array ( file _buffer ) ;
for ( let i = 0 ; i < count ; ++ i ) {
heapu8 [ ptr + i ] = buf _view [ file _buffer _pos ++ ] ;
}
return count ;
} ,
js _time : function ( ) {
return window . performance . now ( ) ;
} ,
js _pow : function ( x ) {
return Math . pow ( x ) ;
} ,
js _floor : function ( x ) {
return Math . floor ( x ) ;
} ,
js _sin : function ( x ) {
return Math . sin ( x ) ;
} ,
js _cos : function ( x ) {
return Math . cos ( x ) ;
} ,
js _tan : function ( x ) {
return Math . tan ( x ) ;
} ,
js _log : function ( base , exponent ) {
return Math . log ( base , exponent ) ;
} ,
js _exp : function ( x ) {
return Math . exp ( x ) ;
} ,
js _sqrt : function ( x ) {
return Math . sqrt ( x ) ;
} ,
js _eval : function ( str ) {
( 1 , eval ) ( read _string ( str ) ) ;
}
}
}
) ;
mod = result . module ;
instance = result . instance ;
instance . exports . _start ( ) ;
function update ( ) {
instance . exports . _update ( ) ;
window . requestAnimationFrame ( update ) ;
}
window . requestAnimationFrame ( update ) ;
kanvas . addEventListener ( 'click' , ( event ) => {
2026-06-09 12:45:01 -07:00
if ( ! audio _thread _started ) {
start _audio _thread ( ) ;
audio _thread _started = true ;
}
2025-01-29 10:55:49 +01:00
} ) ;
kanvas . addEventListener ( 'contextmenu' , ( event ) => {
event . preventDefault ( ) ;
} ) ;
kanvas . addEventListener ( 'mousedown' , ( event ) => {
instance . exports . _mousedown ( event . button , event . clientX , event . clientY ) ;
} ) ;
kanvas . addEventListener ( 'mouseup' , ( event ) => {
instance . exports . _mouseup ( event . button , event . clientX , event . clientY ) ;
} ) ;
kanvas . addEventListener ( 'mousemove' , ( event ) => {
instance . exports . _mousemove ( event . clientX , event . clientY ) ;
} ) ;
kanvas . addEventListener ( 'wheel' , ( event ) => {
instance . exports . _wheel ( event . deltaY ) ;
} ) ;
kanvas . addEventListener ( 'keydown' , ( event ) => {
if ( event . repeat ) {
event . preventDefault ( ) ;
return ;
}
instance . exports . _keydown ( event . keyCode ) ;
} ) ;
kanvas . addEventListener ( 'keyup' , ( event ) => {
if ( event . repeat ) {
event . preventDefault ( ) ;
return ;
}
instance . exports . _keyup ( event . keyCode ) ;
} ) ;
}
init ( ) ;