//-------------------------------------------------------------------------------- //NOTE(martin): global app state //-------------------------------------------------------------------------------- var APP = { canvas: document.getElementById('wa_canvas'), instance: null, memory: null, }; var HEAP32, HEAPU8, HEAPU16, HEAPU32, HEAPF32; var WASM_MEMORY, WASM_HEAP; var WASM_HEAP_MAX = 256*1024*1024; //max 256MB //-------------------------------------------------------------------------------- //NOTE(martin): log and abort functions //-------------------------------------------------------------------------------- function log_error(code, msg) { console.log('ERROR (' + code + '): ' + msg + '\n'); } var ABORT = false; function abort(code, msg) { ABORT = true; log_error(code, msg); throw 'abort'; } const utf8Decoder = new TextDecoder('utf-8'); function console_log(ptr, len) { console.log(utf8Decoder.decode(new Uint8Array(APP.memory.buffer, ptr, len))); } //-------------------------------------------------------------------------------- //NOTE(martin): Memory interaction //-------------------------------------------------------------------------------- function MemorySetBufferViews() { var buf = APP.memory.buffer; HEAP32 = new Int32Array(buf); HEAPU8 = new Uint8Array(buf); HEAPU16 = new Uint16Array(buf); HEAPU32 = new Uint32Array(buf); HEAPF32 = new Float32Array(buf); } function WriteHeapString(str, ptr, max_length) { // Put a string from javascript onto the wasm memory heap (encoded as UTF8) if(!(0>6,r[f++]=128|63&k;} else if(k<=65535){if(t<=f+2)break;r[f++]=224|k>>12,r[f++]=128|k>>6&63,r[f++]=128|63&k;} else if(k<=2097151){if(t<=f+3)break;r[f++]=240|k>>18,r[f++]=128|k>>12&63,r[f++]=128|k>>6&63,r[f++]=128|63&k;} else if(k<=67108863){if(t<=f+4)break;r[f++]=248|k>>24,r[f++]=128|k>>18&63,r[f++]=128|k>>12&63,r[f++]=128|k>>6&63,r[f++]=128|63&k;} else{if(t<=f+5)break;r[f++]=252|k>>30,r[f++]=128|k>>24&63,r[f++]=128|k>>18&63,r[f++]=128|k>>12&63,r[f++]=128|k>>6&63,r[f++]=128|63&k;} } return r[f]=0,f-a; } function ReadHeapString(ptr, length) { // Read a string from the wasm memory heap to javascript (decoded as UTF8) if (length === 0 || !ptr) return ''; for (var hasUtf = 0, t, i = 0; !length || i != length; i++) { t = HEAPU8[((ptr)+(i))>>0]; if (t == 0 && !length) break; hasUtf |= t; } if (!length) length = i; if (hasUtf & 128) { for(var r=HEAPU8,o=ptr,p=ptr+length,F=String.fromCharCode,e,f,i,n,C,t,a,g='';;) { if(o==p||(e=r[o++],!e)) return g; 128&e?(f=63&r[o++],192!=(224&e)?(i=63&r[o++],224==(240&e)?e=(15&e)<<12|f<<6|i:(n=63&r[o++],240==(248&e)?e=(7&e)<<18|f<<12|i<<6|n:(C=63&r[o++],248==(252&e)?e=(3&e)<<24|f<<18|i<<12|n<<6|C:(t=63&r[o++],e=(1&e)<<30|f<<24|i<<18|n<<12|C<<6|t))),65536>e?g+=F(e):(a=e-65536,g+=F(55296|a>>10,56320|1023&a))):g+=F((31&e)<<6|f)):g+=F(e); } } // split up into chunks, because .apply on a huge string can overflow the stack for (var ret = '', curr; length > 0; ptr += 1024, length -= 1024) ret += String.fromCharCode.apply(String, HEAPU8.subarray(ptr, ptr + Math.min(length, 1024))); return ret; } //------------------------------------------------------------------------------------------------ //NOTE(martin): WebGL bindings //------------------------------------------------------------------------------------------------ var GLctx = null; var GLcounter = 1; var GLbuffers = []; var GLprograms = []; var GLframebuffers = []; var GLtextures = []; var GLuniforms = []; var GLshaders = []; var GLprogramInfos = {}; var GLpackAlignment = 4; var GLunpackAlignment = 4; var GLMINI_TEMP_BUFFER_SIZE = 256; var GLminiTempBuffer = null; var GLminiTempBufferViews = [0]; function GLsetupContext(canvas, attr) { var attr = { majorVersion: 2, minorVersion: 0, antialias: false, alpha: false }; var errorInfo = ''; try { let onContextCreationError = function(event) { errorInfo = event.statusMessage || errorInfo; }; canvas.addEventListener('webglcontextcreationerror', onContextCreationError, false); try { GLctx = canvas.getContext('webgl2', attr); } finally { canvas.removeEventListener('webglcontextcreationerror', onContextCreationError, false); } if (!GLctx) throw 'Could not create context'; } catch (e) { abort('WEBGL', e + (errorInfo ? ' (' + errorInfo + ')' : '')); } var exts = GLctx.getSupportedExtensions(); if (exts && exts.length > 0) { // These are the 'safe' feature-enabling extensions that don't add any performance impact related to e.g. debugging, and // should be enabled by default so that client GLES2/GL code will not need to go through extra hoops to get its stuff working. // As new extensions are ratified at http://www.khronos.org/registry/webgl/extensions/ , feel free to add your new extensions // here, as long as they don't produce a performance impact for users that might not be using those extensions. // E.g. debugging-related extensions should probably be off by default. var W = 'WEBGL_', O = 'OES_', E = 'EXT_', T = 'texture_', C = 'compressed_'+T; var automaticallyEnabledExtensions = [ // Khronos ratified WebGL extensions ordered by number (no debug extensions): O+T+'float', O+T+'half_float', O+'standard_derivatives', O+'vertex_array_object', W+C+'s3tc', W+'depth_texture', O+'element_index_uint', E+T+'filter_anisotropic', E+'frag_depth', W+'draw_buffers', 'ANGLE_instanced_arrays', O+T+'float_linear', O+T+'half_float_linear', E+'blend_minmax', E+'shader_texture_lod', // Community approved WebGL extensions ordered by number: W+C+'pvrtc', E+'color_buffer_half_float', W+'color_buffer_float', E+'sRGB', W+C+'etc1', E+'disjoint_timer_query', W+C+'etc', W+C+'astc', E+'color_buffer_float', W+C+'s3tc_srgb', E+'disjoint_timer_query_webgl2']; exts.forEach(function(ext) { if (automaticallyEnabledExtensions.indexOf(ext) != -1) { // Calling .getExtension enables that extension permanently, no need to store the return value to be enabled. GLctx.getExtension(ext); } }); } return true; }; var GLlastError; function GLBindImports(env) { GLminiTempBuffer = new Float32Array(GLMINI_TEMP_BUFFER_SIZE); for (var i = 0; i < GLMINI_TEMP_BUFFER_SIZE; i++) GLminiTempBufferViews[i] = GLminiTempBuffer.subarray(0, i+1); function getNewId(table) { var ret = GLcounter++; for (var i = table.length; i < ret; i++) table[i] = null; return ret; } function getSource(shader, count, string, length) { var source = ''; for (var i = 0; i < count; ++i) { var frag; if (length) { var len = HEAP32[(((length)+(i*4))>>2)]; if (len < 0) frag = ReadHeapString(HEAP32[(((string)+(i*4))>>2)]); else frag = ReadHeapString(HEAP32[(((string)+(i*4))>>2)], len); } else frag = ReadHeapString(HEAP32[(((string)+(i*4))>>2)]); source += frag; } return source; } function populateUniformTable(program) { var p = GLprograms[program]; GLprogramInfos[program] = { uniforms: {}, maxUniformLength: 0, // This is eagerly computed below, since we already enumerate all uniforms anyway. maxAttributeLength: -1, // This is lazily computed and cached, computed when/if first asked, '-1' meaning not computed yet. maxUniformBlockNameLength: -1 // Lazily computed as well }; var ptable = GLprogramInfos[program]; var utable = ptable.uniforms; // A program's uniform table maps the string name of an uniform to an integer location of that uniform. // The global GLuniforms map maps integer locations to WebGLUniformLocations. var numUniforms = GLctx.getProgramParameter(p, GLctx.ACTIVE_UNIFORMS); for (var i = 0; i < numUniforms; ++i) { var u = GLctx.getActiveUniform(p, i); var name = u.name; ptable.maxUniformLength = Math.max(ptable.maxUniformLength, name.length+1); // Strip off any trailing array specifier we might have got, e.g. '[0]'. if (name.indexOf(']', name.length-1) !== -1) { var ls = name.lastIndexOf('['); name = name.slice(0, ls); } // Optimize memory usage slightly: If we have an array of uniforms, e.g. 'vec3 colors[3];', then // only store the string 'colors' in utable, and 'colors[0]', 'colors[1]' and 'colors[2]' will be parsed as 'colors'+i. // Note that for the GLuniforms table, we still need to fetch the all WebGLUniformLocations for all the indices. var loc = GLctx.getUniformLocation(p, name); if (loc != null) { var id = getNewId(GLuniforms); utable[name] = [u.size, id]; GLuniforms[id] = loc; for (var j = 1; j < u.size; ++j) { var n = name + '['+j+']'; loc = GLctx.getUniformLocation(p, n); id = getNewId(GLuniforms); GLuniforms[id] = loc; } } } } function GLrecordError(err) { if (!GLlastError) GLlastError = err; } env.glActiveTexture = function(x0) { GLctx.activeTexture(x0); }; env.glAttachShader = function(program, shader) { GLctx.attachShader(GLprograms[program], GLshaders[shader]); }; env.glBindAttribLocation = function(program, index, name) { GLctx.bindAttribLocation(GLprograms[program], index, ReadHeapString(name)); }; env.glBindBuffer = function(target, buffer) { GLctx.bindBuffer(target, buffer ? GLbuffers[buffer] : null); }; env.glBindFramebuffer = function(target, framebuffer) { GLctx.bindFramebuffer(target, framebuffer ? GLframebuffers[framebuffer] : null); }; env.glBindTexture = function(target, texture) { GLctx.bindTexture(target, texture ? GLtextures[texture] : null); }; env.glBlendFunc = function(x0, x1) { GLctx.blendFunc(x0, x1); }; env.glBlendFuncSeparate = function(x0, x1, x2, x3) { GLctx.blendFuncSeparate(x0, x1, x2, x3); } env.glBlendColor = function(x0, x1, x2, x3) { GLctx.blendColor(x0, x1, x2, x3); } env.glBlendEquation = function(x0) { GLctx.blendEquation(x0); } env.glBlendEquationSeparate = function(x0, x1) { GLctx.blendEquationSeparate(x0, x1); } env.glBufferData = function(target, size, data, usage) { if (!data) GLctx.bufferData(target, size, usage); else GLctx.bufferData(target, HEAPU8.subarray(data, data+size), usage); }; env.glBufferSubData = function(target, offset, size, data) { GLctx.bufferSubData(target, offset, HEAPU8.subarray(data, data+size)); }; env.glCheckFramebufferStatus = function(target) { return(GLctx.checkFramebufferStatus(target));}; env.glClear = function(x0) { GLctx.clear(x0); }; env.glClearColor = function(x0, x1, x2, x3) { GLctx.clearColor(x0, x1, x2, x3); }; env.glColorMask = function(red, green, blue, alpha) { GLctx.colorMask(!!red, !!green, !!blue, !!alpha); }; env.glCompileShader = function(shader) { GLctx.compileShader(GLshaders[shader]); var message = GLctx.getShaderInfoLog(GLshaders[shader]); if(message.length > 0) { throw message; } }; env.glCreateProgram = function() { var id = getNewId(GLprograms); var program = GLctx.createProgram(); program.name = id; GLprograms[id] = program; return id; }; env.glCreateShader = function(shaderType) { var id = getNewId(GLshaders); GLshaders[id] = GLctx.createShader(shaderType); return id; }; env.glDeleteBuffers = function(n, buffers) { for (var i = 0; i < n; i++) { var id = HEAP32[(((buffers)+(i*4))>>2)]; var buffer = GLbuffers[id]; // From spec: "glDeleteBuffers silently ignores 0's and names that do not correspond to existing buffer objects." if (!buffer) continue; GLctx.deleteBuffer(buffer); buffer.name = 0; GLbuffers[id] = null; } }; env.glDeleteFramebuffers = function(n, framebuffers) { for (var i = 0; i < n; ++i) { var id = HEAP32[(((framebuffers)+(i*4))>>2)]; var framebuffer = GLframebuffers[id]; if (!framebuffer) continue; // GL spec: "glDeleteFramebuffers silently ignores 0s and names that do not correspond to existing framebuffer objects". GLctx.deleteFramebuffer(framebuffer); framebuffer.name = 0; GLframebuffers[id] = null; } }; env.glDeleteProgram = function(id) { if (!id) return; var program = GLprograms[id]; if (!program) { // glDeleteProgram actually signals an error when deleting a nonexisting object, unlike some other GL delete functions. GLrecordError(0x0501); // GL_INVALID_VALUE return; } GLctx.deleteProgram(program); program.name = 0; GLprograms[id] = null; GLprogramInfos[id] = null; }; env.glDeleteShader = function(id) { if (!id) return; var shader = GLshaders[id]; if (!shader) { // glDeleteShader actually signals an error when deleting a nonexisting object, unlike some other GL delete functions. GLrecordError(0x0501); // GL_INVALID_VALUE return; } GLctx.deleteShader(shader); GLshaders[id] = null; }; env.glDeleteTextures = function(n, textures) { for (var i = 0; i < n; i++) { var id = HEAP32[(((textures)+(i*4))>>2)]; var texture = GLtextures[id]; if (!texture) continue; // GL spec: "glDeleteTextures silently ignores 0s and names that do not correspond to existing textures". GLctx.deleteTexture(texture); texture.name = 0; GLtextures[id] = null; } }; env.glDepthFunc = function(x0) { GLctx.depthFunc(x0); }; env.glDepthMask = function(flag) { GLctx.depthMask(!!flag); }; env.glDetachShader = function(program, shader) { GLctx.detachShader(GLprograms[program], GLshaders[shader]); }; env.glDisable = function(x0) { GLctx.disable(x0); }; env.glDisableVertexAttribArray = function(index) { GLctx.disableVertexAttribArray(index); }; env.glDrawArrays = function(mode, first, count) { GLctx.drawArrays(mode, first, count); }; env.glDrawElements = function(mode, count, type, indices) { GLctx.drawElements(mode, count, type, indices); }; env.glEnable = function(x0) { GLctx.enable(x0); }; env.glEnableVertexAttribArray = function(index) { GLctx.enableVertexAttribArray(index); }; env.glFramebufferTexture2D = function(target, attachment, textarget, texture, level) { GLctx.framebufferTexture2D(target, attachment, textarget, GLtextures[texture], level); }; env.glGenBuffers = function(n, buffers) { for (var i = 0; i < n; i++) { var buffer = GLctx.createBuffer(); if (!buffer) { GLrecordError(0x0502); // GL_INVALID_OPERATION while(i < n) HEAP32[(((buffers)+(i++*4))>>2)]=0; return; } var id = getNewId(GLbuffers); buffer.name = id; GLbuffers[id] = buffer; HEAP32[(((buffers)+(i*4))>>2)]=id; } }; env.glGenFramebuffers = function(n, ids) { for (var i = 0; i < n; ++i) { var framebuffer = GLctx.createFramebuffer(); if (!framebuffer) { GLrecordError(0x0502); // GL_INVALID_OPERATION while(i < n) HEAP32[(((ids)+(i++*4))>>2)]=0; return; } var id = getNewId(GLframebuffers); framebuffer.name = id; GLframebuffers[id] = framebuffer; HEAP32[(((ids)+(i*4))>>2)] = id; } }; env.glGenTextures = function(n, textures) { for (var i = 0; i < n; i++) { var texture = GLctx.createTexture(); if (!texture) { // GLES + EGL specs don't specify what should happen here, so best to issue an error and create IDs with 0. GLrecordError(0x0502); // GL_INVALID_OPERATION while(i < n) HEAP32[(((textures)+(i++*4))>>2)]=0; return; } var id = getNewId(GLtextures); texture.name = id; GLtextures[id] = texture; HEAP32[(((textures)+(i*4))>>2)]=id; } }; env.glGetActiveUniform = function(program, index, bufSize, length, size, type, name) { program = GLprograms[program]; var info = GLctx.getActiveUniform(program, index); if (!info) return; // If an error occurs, nothing will be written to length, size, type and name. if (bufSize > 0 && name) { var numBytesWrittenExclNull = WriteHeapString(info.name, name, bufSize); if (length) HEAP32[((length)>>2)]=numBytesWrittenExclNull; } else { if (length) HEAP32[((length)>>2)]=0; } if (size) HEAP32[((size)>>2)]=info.size; if (type) HEAP32[((type)>>2)]=info.type; }; env.glGetAttribLocation = function(program, name) { program = GLprograms[program]; name = ReadHeapString(name); return GLctx.getAttribLocation(program, name); }; function webGLGet(name_, p, type) { // Guard against user passing a null pointer. // Note that GLES2 spec does not say anything about how passing a null pointer should be treated. // Testing on desktop core GL 3, the application crashes on glGetIntegerv to a null pointer, but // better to report an error instead of doing anything random. if (!p) { GLrecordError(0x0501); return; } // GL_INVALID_VALUE var ret = undefined; switch(name_) { // Handle a few trivial GLES values case 0x8DFA: ret = 1; break; // GL_SHADER_COMPILER case 0x8DF8: // GL_SHADER_BINARY_FORMATS if (type !== 'Integer' && type !== 'Integer64') GLrecordError(0x0500); // GL_INVALID_ENUM return; // Do not write anything to the out pointer, since no binary formats are supported. case 0x8DF9: ret = 0; break; // GL_NUM_SHADER_BINARY_FORMATS case 0x86A2: // GL_NUM_COMPRESSED_TEXTURE_FORMATS // WebGL doesn't have GL_NUM_COMPRESSED_TEXTURE_FORMATS (it's obsolete since GL_COMPRESSED_TEXTURE_FORMATS returns a JS array that can be queried for length), // so implement it ourselves to allow C++ GLES2 code get the length. var formats = GLctx.getParameter(0x86A3); // GL_COMPRESSED_TEXTURE_FORMATS ret = formats.length; break; } if (ret === undefined) { var result = GLctx.getParameter(name_); switch (typeof(result)) { case 'number': ret = result; break; case 'boolean': ret = result ? 1 : 0; break; case 'string': GLrecordError(0x0500); // GL_INVALID_ENUM return; case 'object': if (result === null) { // null is a valid result for some (e.g., which buffer is bound - perhaps nothing is bound), but otherwise // can mean an invalid name_, which we need to report as an error switch(name_) { case 0x8894: // ARRAY_BUFFER_BINDING case 0x8B8D: // CURRENT_PROGRAM case 0x8895: // ELEMENT_ARRAY_BUFFER_BINDING case 0x8CA6: // FRAMEBUFFER_BINDING case 0x8CA7: // RENDERBUFFER_BINDING case 0x8069: // TEXTURE_BINDING_2D case 0x8514: // TEXTURE_BINDING_CUBE_MAP ret = 0; break; default: GLrecordError(0x0500); // GL_INVALID_ENUM return; } } else if (result instanceof Float32Array || result instanceof Uint32Array || result instanceof Int32Array || result instanceof Array) { for (var i = 0; i < result.length; ++i) { switch (type) { case 'Integer': HEAP32[(((p)+(i*4))>>2)]=result[i]; break; case 'Float': HEAPF32[(((p)+(i*4))>>2)]=result[i]; break; case 'Boolean': HEAPU8[(((p)+(i))>>0)]=result[i] ? 1 : 0; break; default: abort('WEBGL', 'internal glGet error, bad type: ' + type); } } return; } else if (result instanceof WebGLBuffer || result instanceof WebGLProgram || result instanceof WebGLFramebuffer || result instanceof WebGLRenderbuffer || result instanceof WebGLTexture) { ret = result.name | 0; } else { GLrecordError(0x0500); // GL_INVALID_ENUM return; } break; default: GLrecordError(0x0500); // GL_INVALID_ENUM return; } } switch (type) { case 'Integer64': (tempI64 = [ret>>>0,(tempDouble=ret,(+(Math.abs(tempDouble))) >= 1.0 ? (tempDouble > 0.0 ? ((Math.min((+(Math.floor((tempDouble)/4294967296.0))), 4294967295.0))|0)>>>0 : (~~((+(Math.ceil((tempDouble - +(((~~(tempDouble)))>>>0))/4294967296.0)))))>>>0) : 0)],HEAP32[((p)>>2)]=tempI64[0],HEAP32[(((p)+(4))>>2)]=tempI64[1]); break; case 'Integer': HEAP32[((p)>>2)] = ret; break; case 'Float': HEAPF32[((p)>>2)] = ret; break; case 'Boolean': HEAPU8[((p)>>0)] = ret ? 1 : 0; break; default: abort('WEBGL', 'internal glGet error, bad type: ' + type); } } env.glGetError = function() { if (GLlastError) { var e = GLlastError; GLlastError = 0; return e; } return GLctx.getError(); }; env.glGetIntegerv = function(name_, p) { webGLGet(name_, p, 'Integer'); }; env.glGetProgramInfoLog = function(program, maxLength, length, infoLog) { var log = GLctx.getProgramInfoLog(GLprograms[program]); if (log === null) log = '(unknown error)'; if (maxLength > 0 && infoLog) { var numBytesWrittenExclNull = WriteHeapString(log, infoLog, maxLength); if (length) HEAP32[((length)>>2)]=numBytesWrittenExclNull; } else if (length) HEAP32[((length)>>2)]=0; }; env.glGetProgramiv = function(program, pname, p) { if (!p) { // GLES2 specification does not specify how to behave if p is a null pointer. Since calling this function does not make sense // if p == null, issue a GL error to notify user about it. GLrecordError(0x0501); // GL_INVALID_VALUE return; } if (program >= GLcounter) { GLrecordError(0x0501); // GL_INVALID_VALUE return; } var ptable = GLprogramInfos[program]; if (!ptable) { GLrecordError(0x0502); //GL_INVALID_OPERATION return; } if (pname == 0x8B84) // GL_INFO_LOG_LENGTH { var log = GLctx.getProgramInfoLog(GLprograms[program]); if (log === null) log = '(unknown error)'; HEAP32[((p)>>2)] = log.length + 1; } else if (pname == 0x8B87) //GL_ACTIVE_UNIFORM_MAX_LENGTH { HEAP32[((p)>>2)] = ptable.maxUniformLength; } else if (pname == 0x8B8A) //GL_ACTIVE_ATTRIBUTE_MAX_LENGTH { if (ptable.maxAttributeLength == -1) { program = GLprograms[program]; var numAttribs = GLctx.getProgramParameter(program, GLctx.ACTIVE_ATTRIBUTES); ptable.maxAttributeLength = 0; // Spec says if there are no active attribs, 0 must be returned. for (var i = 0; i < numAttribs; ++i) { var activeAttrib = GLctx.getActiveAttrib(program, i); ptable.maxAttributeLength = Math.max(ptable.maxAttributeLength, activeAttrib.name.length+1); } } HEAP32[((p)>>2)] = ptable.maxAttributeLength; } else if (pname == 0x8A35) //GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH { if (ptable.maxUniformBlockNameLength == -1) { program = GLprograms[program]; var numBlocks = GLctx.getProgramParameter(program, GLctx.ACTIVE_UNIFORM_BLOCKS); ptable.maxUniformBlockNameLength = 0; for (var i = 0; i < numBlocks; ++i) { var activeBlockName = GLctx.getActiveUniformBlockName(program, i); ptable.maxUniformBlockNameLength = Math.max(ptable.maxUniformBlockNameLength, activeBlockName.length+1); } } HEAP32[((p)>>2)] = ptable.maxUniformBlockNameLength; } else { HEAP32[((p)>>2)] = GLctx.getProgramParameter(GLprograms[program], pname); } }; env.glGetShaderInfoLog = function(shader, maxLength, length, infoLog) { var log = GLctx.getShaderInfoLog(GLshaders[shader]); if (log === null) log = '(unknown error)'; if (maxLength > 0 && infoLog) { var numBytesWrittenExclNull = WriteHeapString(log, infoLog, maxLength); if (length) HEAP32[((length)>>2)] = numBytesWrittenExclNull; } else if (length) HEAP32[((length)>>2)] = 0; }; env.glGetShaderiv = function(shader, pname, p) { if (!p) { // GLES2 specification does not specify how to behave if p is a null pointer. Since calling this function does not make sense // if p == null, issue a GL error to notify user about it. GLrecordError(0x0501); // GL_INVALID_VALUE return; } if (pname == 0x8B84) // GL_INFO_LOG_LENGTH { var log = GLctx.getShaderInfoLog(GLshaders[shader]); if (log === null) log = '(unknown error)'; HEAP32[((p)>>2)] = log.length + 1; } else if (pname == 0x8B88) // GL_SHADER_SOURCE_LENGTH { var source = GLctx.getShaderSource(GLshaders[shader]); var sourceLength = (source === null || source.length == 0) ? 0 : source.length + 1; HEAP32[((p)>>2)] = sourceLength; } else HEAP32[((p)>>2)] = GLctx.getShaderParameter(GLshaders[shader], pname); }; env.glGetUniformLocation = function(program, name) { name = ReadHeapString(name); var arrayOffset = 0; if (name.indexOf(']', name.length-1) !== -1) { // If user passed an array accessor "[index]", parse the array index off the accessor. var ls = name.lastIndexOf('['); var arrayIndex = name.slice(ls+1, -1); if (arrayIndex.length > 0) { arrayOffset = parseInt(arrayIndex); if (arrayOffset < 0) return -1; } name = name.slice(0, ls); } var ptable = GLprogramInfos[program]; if (!ptable){ console.log("ptable == null"); return -1}; var utable = ptable.uniforms; var uniformInfo = utable[name]; // returns pair [ dimension_of_uniform_array, uniform_location ] if (uniformInfo && arrayOffset < uniformInfo[0]) { // Check if user asked for an out-of-bounds element, i.e. for 'vec4 colors[3];' user could ask for 'colors[10]' which should return -1. return uniformInfo[1] + arrayOffset; } return -1; }; env.glLineWidth = function(x0) { GLctx.lineWidth(x0); }; env.glLinkProgram = function(program) { GLctx.linkProgram(GLprograms[program]); GLprogramInfos[program] = null; // uniforms no longer keep the same names after linking populateUniformTable(program); }; env.glPixelStorei = function(pname, param) { if (pname == 0x0D05) GLpackAlignment = param; //GL_PACK_ALIGNMENT else if (pname == 0x0cf5) GLunpackAlignment = param; //GL_UNPACK_ALIGNMENT GLctx.pixelStorei(pname, param); }; function webGLGetTexPixelData(type, format, width, height, pixels, internalFormat) { var sizePerPixel; var numChannels; switch(format) { case 0x1906: case 0x1909: case 0x1902: numChannels = 1; break; //GL_ALPHA, GL_LUMINANCE, GL_DEPTH_COMPONENT case 0x190A: numChannels = 2; break; //GL_LUMINANCE_ALPHA case 0x1907: case 0x8C40: numChannels = 3; break; //GL_RGB, GL_SRGB_EXT case 0x1908: case 0x8C42: numChannels = 4; break; //GL_RGBA, GL_SRGB_ALPHA_EXT default: GLrecordError(0x0500); return null; //GL_INVALID_ENUM } switch (type) { case 0x1401: sizePerPixel = numChannels*1; break; //GL_UNSIGNED_BYTE case 0x1403: case 0x8D61: sizePerPixel = numChannels*2; break; //GL_UNSIGNED_SHORT, GL_HALF_FLOAT_OES case 0x1405: case 0x1406: sizePerPixel = numChannels*4; break; //GL_UNSIGNED_INT, GL_FLOAT case 0x84FA: sizePerPixel = 4; break; //GL_UNSIGNED_INT_24_8_WEBGL/GL_UNSIGNED_INT_24_8 case 0x8363: case 0x8033: case 0x8034: sizePerPixel = 2; break; //GL_UNSIGNED_SHORT_5_6_5, GL_UNSIGNED_SHORT_4_4_4_4, GL_UNSIGNED_SHORT_5_5_5_1 default: GLrecordError(0x0500); return null; //GL_INVALID_ENUM } function roundedToNextMultipleOf(x, y) { return Math.floor((x + y - 1) / y) * y; } var plainRowSize = width * sizePerPixel; var alignedRowSize = roundedToNextMultipleOf(plainRowSize, GLunpackAlignment); var bytes = (height <= 0 ? 0 : ((height - 1) * alignedRowSize + plainRowSize)); switch(type) { case 0x1401: return HEAPU8.subarray((pixels),(pixels+bytes)); //GL_UNSIGNED_BYTE case 0x1406: return HEAPF32.subarray((pixels)>>2,(pixels+bytes)>>2); //GL_FLOAT case 0x1405: case 0x84FA: return HEAPU32.subarray((pixels)>>2,(pixels+bytes)>>2); //GL_UNSIGNED_INT, GL_UNSIGNED_INT_24_8_WEBGL/GL_UNSIGNED_INT_24_8 case 0x1403: case 0x8363: case 0x8033: case 0x8034: case 0x8D61: return HEAPU16.subarray((pixels)>>1,(pixels+bytes)>>1); //GL_UNSIGNED_SHORT, GL_UNSIGNED_SHORT_5_6_5, GL_UNSIGNED_SHORT_4_4_4_4, GL_UNSIGNED_SHORT_5_5_5_1, GL_HALF_FLOAT_OES default: GLrecordError(0x0500); return null; //GL_INVALID_ENUM } } env.glReadPixels = function(x, y, width, height, format, type, pixels) { var pixelData = webGLGetTexPixelData(type, format, width, height, pixels, format); if (!pixelData) return GLrecordError(0x0500); // GL_INVALID_ENUM GLctx.readPixels(x, y, width, height, format, type, pixelData); }; env.glScissor = function(x0, x1, x2, x3) { GLctx.scissor(x0, x1, x2, x3) }; env.glShaderSource = function(shader, count, string, length) { var source = getSource(shader, count, string, length); GLctx.shaderSource(GLshaders[shader], source); }; env.glTexImage2D = function(target, level, internalFormat, width, height, border, format, type, pixels) { var pixelData = null; if (pixels) pixelData = webGLGetTexPixelData(type, format, width, height, pixels, internalFormat); GLctx.texImage2D(target, level, internalFormat, width, height, border, format, type, pixelData); }; env.glTexParameteri = function(x0, x1, x2) { GLctx.texParameteri(x0, x1, x2); }; env.glTexSubImage2D = function(target, level, xoffset, yoffset, width, height, format, type, pixels) { var pixelData = null; if (pixels) pixelData = webGLGetTexPixelData(type, format, width, height, pixels, 0); GLctx.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixelData); }; env.glUniform1f = function(loc, v0) { GLctx.uniform1f(GLuniforms[loc], v0); }; env.glUniform1i = function(loc, v0) { GLctx.uniform1i(GLuniforms[loc], v0); }; env.glUniform2i = function(loc, v0, v1) { GLctx.uniform2i(GLuniforms[loc], v0, v1); }; env.glUniform2f = function(loc, v0, v1) { GLctx.uniform2f(GLuniforms[loc], v0, v1); }; env.glUniform3f = function(loc, v0, v1, v2) { GLctx.uniform3f(GLuniforms[loc], v0, v1, v2); }; env.glUniform3fv = function(loc, count, value) { var view; if (3*count <= GLMINI_TEMP_BUFFER_SIZE) { // avoid allocation when uploading few enough uniforms view = GLminiTempBufferViews[3*count-1]; for (var ptr = value>>2, i = 0; i != 3*count; i++) { view[i] = HEAPF32[ptr+i]; } } else view = HEAPF32.subarray((value)>>2,(value+count*12)>>2); GLctx.uniform3fv(GLuniforms[loc], view); }; env.glUniform4f = function(loc, v0, v1, v2, v3) { GLctx.uniform4f(GLuniforms[loc], v0, v1, v2, v3); }; env.glUniformMatrix4fv = function(loc, count, transpose, value) { count<<=4; var view; if (count <= GLMINI_TEMP_BUFFER_SIZE) { // avoid allocation when uploading few enough uniforms view = GLminiTempBufferViews[count-1]; for (var ptr = value>>2, i = 0; i != count; i += 4) { view[i ] = HEAPF32[ptr+i ]; view[i+1] = HEAPF32[ptr+i+1]; view[i+2] = HEAPF32[ptr+i+2]; view[i+3] = HEAPF32[ptr+i+3]; } } else view = HEAPF32.subarray((value)>>2,(value+count*4)>>2); GLctx.uniformMatrix4fv(GLuniforms[loc], !!transpose, view); }; env.glUseProgram = function(program) { GLctx.useProgram(program ? GLprograms[program] : null); }; env.glVertexAttrib4f = function(x0, x1, x2, x3, x4) { GLctx.vertexAttrib4f(x0, x1, x2, x3, x4); }; env.glVertexAttrib4fv = function(index, v) { GLctx.vertexAttrib4f(index, HEAPF32[v>>2], HEAPF32[v+4>>2], HEAPF32[v+8>>2], HEAPF32[v+12>>2]); }; env.glVertexAttribPointer = function(index, size, type, normalized, stride, ptr) { GLctx.vertexAttribPointer(index, size, type, !!normalized, stride, ptr); }; env.glViewport = function(x0, x1, x2, x3) { GLctx.viewport(x0, x1, x2, x3); }; var initTime; env.WAJS_SetupCanvas = function(width, height) { var cnvs = APP.canvas; cnvs.width = width; cnvs.height = height; cnvs.height = cnvs.clientHeight; cnvs.width = cnvs.clientWidth; if (!GLsetupContext(cnvs)) return; initTime = Date.now(); var draw_func_ex = function() { if (ABORT) return; window.requestAnimationFrame(draw_func_ex); APP.instance.exports.WAFNDraw(); }; window.requestAnimationFrame(draw_func_ex); }; env.WAJS_GetTime = function(type) { return Date.now() - initTime; }; } //------------------------------------------------------------------------------------------------ //NOTE(martin): Fill environments with functions //------------------------------------------------------------------------------------------------ const env = {}; env.console_log = console_log; env.cosf = Math.cos; env.sinf = Math.sin; env.tanf = Math.tan; env.sqrtf = Math.sqrt; env.floorf = Math.floor; env.canvas_width = function(){return(APP.canvas.width);}; env.canvas_height = function(){return(APP.canvas.height);}; env.window_width = function(){return(window.innerWidth);}; env.window_height = function(){return(window.innerHeight);}; GLBindImports(env); //------------------------------------------------------------------------------------------------ //NOTE(martin): wasm instantiation //------------------------------------------------------------------------------------------------ async function runWasm(canvasSize) { try { if(WebAssembly.instantiateStreaming) { const response = fetch('./main.wasm'); ({ instance: APP.instance } = await WebAssembly.instantiateStreaming(response, { env })); } else { const response = await fetch('./main.wasm'); const bytes = await response.arrayBuffer(); ({ instance: APP.instance } = await WebAssembly.instantiate(bytes, { env })); } } catch(e) { console.log(`error instancing wasm: ${e}`); return; } APP.memory = APP.instance.exports.memory; MemorySetBufferViews(); APP.canvas.addEventListener('dblclick', APP.instance.exports.reset); APP.canvas.addEventListener('mouseup', (ev) => APP.instance.exports.mouse_up()); APP.canvas.addEventListener('mousedown', (ev) => APP.instance.exports.mouse_down(ev.offsetX, ev.offsetY)); APP.canvas.addEventListener('mousemove', (ev) => APP.instance.exports.mouse_move(ev.offsetX, ev.offsetY, ev.movementX, ev.movementY)); if(APP.instance.exports.init) { var result = APP.instance.exports.init(canvasSize) } else { console.log('no exported init function'); } } console.log("Hello world, from js"); runWasm(800);