a-game

2D platformer written from scratch.
git clone git://git.amin.space/a-game.git
Log | Files | Refs | README | LICENSE

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 }