platform_wasm_loader.js (11879B)
1 'use strict'; 2 let utf8decoder = new TextDecoder("utf-8"); 3 let memory = null; 4 let exports = {}; 5 let imports = {}; 6 let files = new Map; 7 let gl = null; 8 let gl_id_freelist = []; 9 let gl_id_map = [null]; 10 // NOTE(amin): These values _must_ match the corresponding ones used by the 11 // platform layer in `enum WasmKey`. 12 // By the way: Thanks a lot, W3C, for not having any standardized _numeric_ 13 // representation of a key. 14 const key_numeric_codes = { 15 // Code strings are from: 16 // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code/code_values 17 ArrowLeft: 0, 18 ArrowRight: 1, 19 ArrowUp: 2, 20 ArrowDown: 3, 21 KeyS: 4, 22 F11: 5, 23 }; 24 imports["webglActiveTexture"] = function(texture) { 25 gl.activeTexture(texture); 26 } 27 imports["webglAttachShader"] = function(program_id, shader_id) { 28 let program = gl_id_map[program_id]; 29 let shader = gl_id_map[shader_id]; 30 gl.attachShader(program, shader); 31 } 32 imports["webglBindBuffer"] = function(target, buffer_id) { 33 let buffer = gl_id_map[buffer_id]; 34 gl.bindBuffer(target, buffer); 35 } 36 imports["webglBindTexture"] = function(target, texture_id) { 37 let texture = gl_id_map[texture_id]; 38 gl.bindTexture(target, texture); 39 } 40 imports["webglBindVertexArray"] = function(vao_id) { 41 let vao = gl_id_map[vao_id]; 42 gl.bindVertexArray(vao); 43 } 44 imports["webglBlendColor"] = function(red, green, blue, alpha) { 45 gl.blendColor(red, green, blue, alpha); 46 } 47 imports["webglBlendFunc"] = function(sfactor, dfactor) { 48 gl.blendFunc(sfactor, dfactor); 49 } 50 imports["webglBufferData"] = function(target, size, data, usage) { 51 let dataslice = memory.subarray(data, data + size); 52 gl.bufferData(target, dataslice, usage); 53 } 54 imports["webglClear"] = function(mask) { 55 gl.clear(mask); 56 } 57 imports["webglClearColor"] = function(red, green, blue, alpha) { 58 gl.clearColor(red, green, blue, alpha); 59 } 60 imports["webglCompileShader"] = function(shader_id) { 61 let shader = gl_id_map[shader_id]; 62 gl.compileShader(shader); 63 } 64 imports["webglCreateBuffer"] = function() { 65 let buffer = gl.createBuffer(); 66 let buffer_id = webgl_id_new(buffer); 67 return buffer_id 68 } 69 imports["webglCreateProgram"] = function() { 70 let program = gl.createProgram(); 71 let program_id = webgl_id_new(program); 72 return program_id; 73 } 74 imports["webglCreateShader"] = function(type) { 75 let shader = gl.createShader(type); 76 let shader_id = webgl_id_new(shader); 77 return shader_id; 78 } 79 imports["webglCreateTexture"] = function(type) { 80 let texture = gl.createTexture(); 81 let texture_id = webgl_id_new(texture); 82 return texture_id; 83 } 84 imports["webglCreateVertexArray"] = function() { 85 let vao = gl.createVertexArray() 86 let vao_id = webgl_id_new(vao); 87 return vao_id 88 } 89 imports["webglDeleteBuffer"] = function(buffer_id) { 90 let buffer = gl_id_map[buffer_id]; 91 gl.deleteBuffer(buffer); 92 webgl_id_remove(buffer_id); 93 } 94 imports["webglDeleteShader"] = function(shader_id) { 95 let shader = gl_id_map[shader_id]; 96 gl.deleteShader(shader); 97 webgl_id_remove(shader_id); 98 } 99 imports["webglDeleteVertexArray"] = function(vao_id) { 100 let vao = gl_id_map[vao_id]; 101 gl.deleteVertexArray(vao); 102 webgl_id_remove(vao_id); 103 } 104 imports["webglDepthMask"] = function(flag) { 105 gl.depthMask(flag); 106 } 107 imports["webglDisable"] = function(cap) { 108 gl.disable(cap); 109 } 110 imports["webglDrawElements"] = function(mode, count, type, offset) { 111 gl.drawElements(mode, count, type, offset); 112 } 113 imports["webglEnable"] = function(cap) { 114 gl.enable(cap); 115 } 116 imports["webglEnableVertexAttribArray"] = function(index) { 117 gl.enableVertexAttribArray(index); 118 } 119 imports["webglGenerateMipmap"] = function(target) { 120 gl.generateMipmap(target); 121 } 122 imports["webglGetProgramInfoLog"] = function() { 123 } 124 imports["webglGetProgramParameter"] = function(program_id, param) { 125 let program = gl_id_map[program_id]; 126 return gl.getProgramParameter(program, param); 127 } 128 imports["webglGetShaderInfoLog"] = function(shader_id, out_buf) { 129 let shader = gl_id_map[shader_id]; 130 let info_log = gl.getShaderInfoLog(shader); 131 132 // TODO: remove this once we get sprintf workingk 133 console.log(info_log); 134 135 let arr = memory.subarray(info_log, out_buf + info_log.byteLength); 136 arr.set(new Uint8Array(info_log)); 137 return true; 138 } 139 imports["webglGetShaderParameter"] = function(shader_id, param) { 140 let shader = gl_id_map[shader_id]; 141 let result = gl.getShaderParameter(shader, param); 142 return result; 143 } 144 imports["webglGetUniformLocation"] = function(program_id, name_ptr, name_len) { 145 let program = gl_id_map[program_id]; 146 let name = utf8decoder.decode(memory.subarray(name_ptr, name_ptr+name_len)); 147 let loc = gl.getUniformLocation(program, name); 148 let location_id = webgl_id_new(loc); 149 return location_id; 150 } 151 imports["webglLinkProgram"] = function(program_id) { 152 let program = gl_id_map[program_id]; 153 gl.linkProgram(program); 154 } 155 imports["webglScissor"] = function(x, y, width, height) { 156 gl.scissor(x, y, width, height); 157 } 158 imports["webglShaderSource"] = function(shader_id, source_ptr, source_len) { 159 let shader = gl_id_map[shader_id]; 160 let arr = memory.subarray(source_ptr, source_ptr + source_len); 161 let s = utf8decoder.decode(arr); 162 gl.shaderSource(shader, s); 163 } 164 imports["webglTexImage2D"] = function(target, level, internalformat, width, height, border, format, type, pixels) { 165 const bytes_per_pixel = 4; 166 let dataslice = memory.subarray(pixels, pixels + (width * height * bytes_per_pixel)); 167 gl.texImage2D(target, level, internalformat, width, height, border, format, type, dataslice); 168 } 169 imports["webglTexImage3D"] = function(target, level, internalformat, width, height, depth, border, format, type, pixels) { 170 const bytes_per_pixel = 4; 171 let dataslice = null; 172 // TODO: We're probably in trouble if NULL != 0 in C 173 if (pixels !== 0) { 174 dataslice = memory.subarray(pixels, pixels + (width * height * depth * bytes_per_pixel)); 175 } 176 gl.texImage3D(target, level, internalformat, width, height, depth, border, format, type, dataslice); 177 } 178 imports["webglTexParameteri"] = function(target, pname, param) { 179 gl.texParameteri(target, pname, param); 180 } 181 imports["webglTexStorage3D"] = function(target, levels, internalformat, width, height, depth) { 182 gl.texStorage3D(target, levels, internalformat, width, height, depth); 183 } 184 imports["webglTexSubImage3D"] = function(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels) { 185 const bytes_per_pixel = 4; 186 let dataslice = memory.subarray(pixels, pixels + (width * height * depth * bytes_per_pixel)); 187 gl.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, dataslice); 188 } 189 imports["webglUniform1f"] = function(location_id, value) { 190 let loc = gl_id_map[location_id]; 191 gl['uniform1f'](loc, value); 192 } 193 imports["webglUniform1i"] = function(location_id, value) { 194 let loc = gl_id_map[location_id]; 195 gl['uniform1i'](loc, value); 196 } 197 imports["webglUniform3f"] = function(location_id, x, y, z) { 198 let loc = gl_id_map[location_id]; 199 gl['uniform3fv'](loc, [x, y, z]); 200 } 201 imports["webglUniformMatrix4fv"] = function(location_id, transpose, data) { 202 let loc = gl_id_map[location_id]; 203 let dataslice = memory.slice(data, data + 4 * 16); 204 gl.uniformMatrix4fv(loc, transpose, new Float32Array(dataslice.buffer)); 205 } 206 imports["webglUseProgram"] = function(program_id) { 207 let program = gl_id_map[program_id]; 208 gl.useProgram(program); 209 } 210 imports["webglVertexAttribPointer"] = function(index, size, type, normalized, stride, offset) { 211 gl.vertexAttribPointer(index, size, type, normalized, stride, offset); 212 } 213 imports["webglViewport"] = function(x, y, width, height) { 214 gl.viewport(x, y, width, height); 215 } 216 imports["js_cosf"] = function(x) { 217 return Math.cos(x); 218 } 219 imports["js_fabsf"] = function(x) { 220 return Math.abs(x); 221 } 222 imports["js_get_file_size"] = function(name, name_len) { 223 let file_name = utf8decoder.decode(memory.subarray(name, name + name_len)) 224 let file_size = 0; 225 let file = files[file_name]; 226 if (file !== undefined) { 227 file_size = file.byteLength; 228 } 229 return file_size; 230 } 231 imports["js_powf"] = function(x, y) { 232 return Math.pow(x, y); 233 } 234 imports["js_print"] = function(s, len) { 235 let arr = memory.subarray(s, s + len); 236 console.log(utf8decoder.decode(arr)); 237 } 238 imports["js_read_entire_file"] = function(name, name_len, out_buf) { 239 let file_name = utf8decoder.decode(memory.subarray(name, name + name_len)) 240 let file_size = 0; 241 let file = files[file_name]; 242 let success = true; 243 if (file !== undefined) { 244 let arr = memory.subarray(out_buf, out_buf + file.byteLength); 245 arr.set(new Uint8Array(file)); 246 } else { 247 success = false; 248 } 249 return success; 250 } 251 imports["js_sinf"] = function(x) { 252 return Math.sin(x); 253 } 254 imports["js_tanf"] = function(x) { 255 return Math.tan(x); 256 } 257 function error_fatal(message) { 258 console.log(message); 259 throw message; 260 } 261 function webgl_id_new(obj) { 262 if(gl_id_freelist.length == 0) { 263 gl_id_map.push(obj); 264 return gl_id_map.length - 1; 265 } else { 266 let id = gl_id_freelist.shift(); 267 gl_id_map[id] = obj; 268 return id; 269 } 270 } 271 function webgl_id_remove(id) { 272 delete gl_id_map[id]; 273 gl_id_freelist.push(id); 274 } 275 function canvas_resize() { 276 let pr = window.devicePixelRatio; 277 let w = window.innerWidth; 278 let h = window.innerHeight; 279 // Bitwise OR does float truncation 280 let w_pixels = (w * pr) | 0; 281 let h_pixels = (h * pr) | 0; 282 gl.canvas.width = w_pixels; 283 gl.canvas.height = h_pixels 284 exports['window_resize'](w_pixels, h_pixels); 285 console.log("resize: (" + w_pixels + ", " + h_pixels + ")"); 286 } 287 function canvas_render() { 288 let timestamp_in_ms = performance.now(); 289 exports['render'](timestamp_in_ms); 290 window.requestAnimationFrame(canvas_render); 291 } 292 function file_load(name) { 293 let promise = new Promise((resolve, reject) => { 294 fetch(name).then(resp => { 295 resp.arrayBuffer().then(arr => resolve(arr)); 296 }); 297 }); 298 return promise; 299 } 300 function on_key_event(e, key_is_down) { 301 let sane_numeric_code = key_numeric_codes[e.code]; 302 if (sane_numeric_code !== undefined) { 303 if (sane_numeric_code === 5) { 304 // Don't toggle full screen 305 e.preventDefault(); 306 } 307 exports['key_callback'](sane_numeric_code, key_is_down); 308 } 309 } 310 window.onload = async function() { 311 let webgl_options = { 312 alpha: false, 313 depth: true, 314 stencil: false, 315 antialias: true, 316 preserveDrawingBuffer: false 317 }; 318 gl = document.getElementById("webglcanvas").getContext("webgl2", webgl_options); 319 if(!gl) { 320 error_fatal("Your browser does not support WebGL 2."); 321 } 322 323 let file_names = [ 324 'binary.wasm', 325 'shader/main_f.glsl', 326 'shader/main_v.glsl', 327 'assets/tile0.tga', 328 'assets/tileset0.tga', 329 ]; 330 for (const name of file_names.values()) { 331 files[name] = await file_load(name); 332 } 333 let binary = files['binary.wasm']; 334 let num_memory_pages = 64; // 64pages * 64KiB/page = 4096KiB = 4MiB 335 imports['memory'] = new WebAssembly.Memory({initial: num_memory_pages}); 336 memory = new Uint8Array(imports['memory']['buffer']); 337 let program = await WebAssembly.instantiate(binary, {"env":imports}); 338 let instance = program['instance']; 339 exports = instance['exports']; 340 canvas_resize(); 341 window.addEventListener("resize", canvas_resize); 342 window.addEventListener("keyup", (event) => {on_key_event(event, false)}, false); 343 window.addEventListener("keydown", (event) => {on_key_event(event, true)}, false); 344 if(!exports['init'](num_memory_pages)) { 345 error_fatal("Game initialization failed."); 346 } 347 canvas_render(); 348 }