transparent-cube

Minimal cross-platform native/wasm graphics example.
git clone git://git.amin.space/transparent-cube.git
Log | Files | Refs | README | LICENSE

commit f2dbe36ae6172eccd92c798745967e06b5b2973d
parent 0a48c423fd3db9aaa717f07964f2652246f1db2b
Author: amin <dev@aminmesbah.com>
Date:   Sun, 23 Jun 2019 21:48:29 +0000

Clean wasm stuff up

FossilOrigin-Name: e0ddeb4051ddf4c6f6c8385751a8ae6f24f225dc655f19508f9668ac1319f039
Diffstat:
Mbuild_wasm.sh | 42+++++++++++++++++++++++++++++++-----------
Dindex.html | 17-----------------
Djs/globals.js | 8--------
Djs/imports.js | 168-------------------------------------------------------------------------------
Djs/loader.js | 65-----------------------------------------------------------------
Djs/utils.js | 28----------------------------
Asrc/platform_wasm_index.html | 15+++++++++++++++
Rwasm_js_implemented_symbols.txt -> src/platform_wasm_js_symbols.txt | 0
Asrc/platform_wasm_loader.js | 248+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
9 files changed, 294 insertions(+), 297 deletions(-)

diff --git a/build_wasm.sh b/build_wasm.sh @@ -1,20 +1,40 @@ +#!/usr/bin/env bash -mkdir -p ./out/wasm -cat js/globals.js js/utils.js js/imports.js js/loader.js > ./out/wasm/script.js -cp index.html ./out/wasm/index.html -cp -r shader/ ./out/wasm/ +set -e # fail if any command has a non-zero exit status +set -u # fail if any undefined variable is referenced +set -o pipefail # propagate failure exit status through a pipeline +shopt -s globstar nullglob # enable recursive and null globbing -clang -cc1 -Ofast -emit-llvm-bc -triple=wasm32-unknown-unknown-unknown-wasm -std=c11 \ +out_dir="./out" +wasm_dir="${out_dir}/wasm" + +mkdir -p $wasm_dir +cp src/platform_wasm_loader.js $wasm_dir/script.js +cp src/platform_wasm_index.html $wasm_dir/index.html +cp -r shader/ $wasm_dir + +clang \ + -cc1 \ + -Ofast \ + -emit-llvm-bc \ + -triple=wasm32-unknown-unknown-unknown-wasm \ -ffreestanding \ -fno-builtin \ + -std=c11 \ -DGAME_WEBGL \ src/platform_wasm.c -llvm-link -o wasm.bc src/*.bc -opt -O3 -disable-simplify-libcalls wasm.bc -o wasm.bc -llc -O3 -disable-simplify-libcalls -filetype=obj wasm.bc -o wasm.o -wasm-ld --no-entry wasm.o \ - -o ./out/wasm/binary.wasm \ - -allow-undefined-file wasm_js_implemented_symbols.txt \ +llvm-link -o $wasm_dir/wasm.bc src/*.bc +opt -O3 -disable-simplify-libcalls $wasm_dir/wasm.bc -o $wasm_dir/wasm.bc +llc -O3 -disable-simplify-libcalls -filetype=obj $wasm_dir/wasm.bc -o $wasm_dir/wasm.o + +wasm-ld \ + --no-entry $wasm_dir/wasm.o \ + -o $wasm_dir/binary.wasm \ + -allow-undefined-file src/platform_wasm_js_symbols.txt \ --export-all \ --import-memory + +rm $wasm_dir/*.o +rm $wasm_dir/*.bc +rm src/*.bc diff --git a/index.html b/index.html @@ -1,17 +0,0 @@ -<!DOCTYPE html> -<title>WASM Demo</title> -<meta name=viewport content='width=device-width,initial-scale=1' charset='utf-8'> - -<style> -body{ - margin: 0; - overflow: hidden; -} -#mcanvas{ - width: 100vw; - height: 100vh; -} -</style> - -<canvas id=mcanvas></canvas> -<script src='script.js' defer></script> diff --git a/js/globals.js b/js/globals.js @@ -1,8 +0,0 @@ -let utf8decoder = new TextDecoder("utf-8"); -let memory = null; -let exports = {}; -let imports = {}; -let files = []; -let gl = null; -let gl_id_freelist = []; -let gl_id_map = [null]; diff --git a/js/imports.js b/js/imports.js @@ -1,168 +0,0 @@ -imports["webglAttachShader"] = function(program_id, shader_id) { - let program = gl_id_map[program_id]; - let shader = gl_id_map[shader_id]; - gl.attachShader(program, shader); -} -imports["webglBindBuffer"] = function(target, buffer_id) { - let buffer = gl_id_map[buffer_id]; - gl.bindBuffer(target, buffer); -} -imports["webglBindVertexArray"] = function(vao_id) { - let vao = gl_id_map[vao_id]; - gl.bindVertexArray(vao); -} -imports["webglBlendColor"] = function(red, green, blue, alpha) { - gl.blendColor(red, green, blue, alpha); -} -imports["webglBlendFunc"] = function(sfactor, dfactor) { - gl.blendFunc(sfactor, dfactor); -} -imports["webglBufferData"] = function(target, size, data, usage) { - let dataslice = memory.subarray(data, data + size); - gl.bufferData(target, dataslice, usage); -} -imports["webglClear"] = function(mask) { - gl.clear(mask); -} -imports["webglClearColor"] = function(red, green, blue, alpha) { - gl.clearColor(red, green, blue, alpha); -} -imports["webglCompileShader"] = function(shader_id) { - let shader = gl_id_map[shader_id]; - gl.compileShader(shader); -} -imports["webglCreateBuffer"] = function() { - let buffer = gl.createBuffer(); - let buffer_id = webgl_id_new(buffer); - return buffer_id -} -imports["webglCreateProgram"] = function() { - let program = gl.createProgram(); - let program_id = webgl_id_new(program); - return program_id; -} -imports["webglCreateShader"] = function(type) { - let shader = gl.createShader(type); - let shader_id = webgl_id_new(shader); - return shader_id; -} -imports["webglCreateVertexArray"] = function() { - let vao = gl.createVertexArray() - let vao_id = webgl_id_new(vao); - return vao_id -} -imports["webglDeleteBuffer"] = function(buffer_id) { - let buffer = gl_id_map[buffer_id]; - gl.deleteBuffer(buffer); - webgl_id_remove(buffer_id); -} -imports["webglDeleteShader"] = function(shader_id) { - let shader = gl_id_map[shader_id]; - gl.deleteShader(shader); - webgl_id_remove(shader_id); -} -imports["webglDeleteVertexArray"] = function(vao_id) { - let vao = gl_id_map[vao_id]; - gl.deleteVertexArray(vao); - webgl_id_remove(vao_id); -} -imports["webglDepthMask"] = function(flag) { - gl.depthMask(flag); -} -imports["webglDisable"] = function(cap) { - gl.disable(cap); -} -imports["webglDrawElements"] = function(mode, count, type, offset) { - gl.drawElements(mode, count, type, offset); -} -imports["webglEnable"] = function(cap) { - gl.enable(cap); -} -imports["webglEnableVertexAttribArray"] = function(index) { - gl.enableVertexAttribArray(index); -} -imports["webglGetProgramInfoLog"] = function() { -} -imports["webglGetProgramParameter"] = function(program_id, param) { - let program = gl_id_map[program_id]; - return gl.getProgramParameter(program, param); -} -imports["webglGetShaderInfoLog"] = function(shader_id, out_buf) { - let shader = gl_id_map[shader_id]; - let info_log = gl.getShaderInfoLog(shader); - - // TODO: remove this once we get sprintf workingk - console.log(info_log); - - let arr = memory.subarray(info_log, out_buf + info_log.byteLength); - arr.set(new Uint8Array(info_log)); - return true; -} -imports["webglGetShaderParameter"] = function(shader_id, param) { - let shader = gl_id_map[shader_id]; - let result = gl.getShaderParameter(shader, param); - return result; -} -imports["webglGetUniformLocation"] = function(program_id, name_ptr, name_len) { - let program = gl_id_map[program_id]; - let name = utf8decoder.decode(memory.subarray(name_ptr, name_ptr+name_len)); - let loc = gl.getUniformLocation(program, name); - let location_id = webgl_id_new(loc); - return location_id; -} -imports["webglLinkProgram"] = function(program_id) { - let program = gl_id_map[program_id]; - gl.linkProgram(program); -} -imports["webglShaderSource"] = function(shader_id, source_ptr, src_len) { - let shader = gl_id_map[shader_id]; - let s = buf2str(source_ptr, src_len); - gl.shaderSource(shader, s); -} -imports["webglUniform1f"] = function(location_id, value) { - let loc = gl_id_map[location_id]; - gl['uniform1f'](loc, value); -} -imports["webglUniform1i"] = function(location_id, value) { - let loc = gl_id_map[location_id]; - gl['uniform1i'](loc, value); -} -imports["webglUniform3f"] = function(location_id, x, y, z) { - let loc = gl_id_map[location_id]; - gl['uniform3fv'](loc, [x, y, z]); -} -imports["webglUniformMatrix4fv"] = function(location_id, transpose, data) { - let loc = gl_id_map[location_id]; - let dataslice = memory.slice(data, data + 4 * 16); - gl.uniformMatrix4fv(loc, transpose, new Float32Array(dataslice.buffer)); -} -imports["webglUseProgram"] = function(program_id) { - let program = gl_id_map[program_id]; - gl.useProgram(program); -} -imports["webglVertexAttribPointer"] = function(index, size, type, normalized, stride, offset) { - gl.vertexAttribPointer(index, size, type, normalized, stride, offset); -} -imports["webglViewport"] = function(x, y, width, height) { - gl.viewport(x, y, width, height); -} - -imports["js_read_entire_file"] = function(name, name_len, out_buf) { - let file_name = utf8decoder.decode(memory.subarray(name, name + name_len)) - if (file_name == "shader/cube_f.glsl") { - var file = files[1]; - } else if (file_name == "shader/cube_v.glsl") { - var file = files[2]; - } else { - return false; - } - let arr = memory.subarray(out_buf, out_buf + file.byteLength); - let s = String.fromCharCode.apply(null, arr); - arr.set(new Uint8Array(file)); - return true; -} - -imports["js_print"] = function(s, len) { - let arr = memory.subarray(s, s + len); - console.log(utf8decoder.decode(arr)); -} diff --git a/js/loader.js b/js/loader.js @@ -1,65 +0,0 @@ -function canvas_resize() { - let pr = window.devicePixelRatio; - let w = window.innerWidth; - let h = window.innerHeight; - // Bitwise OR does float truncation - let w_pixels = (w * pr) | 0; - let h_pixels = (h * pr) | 0; - gl.canvas.width = w_pixels; - gl.canvas.height = h_pixels - exports['window_resize'](w_pixels, h_pixels); - console.log("resize: (" + w_pixels + ", " + h_pixels + ")"); -} - -function canvas_render() { - exports['render'](); - window.requestAnimationFrame(canvas_render); -} - -function file_load(name) { - let promise = new Promise((ok, nope) => { - fetch(name).then(resp => { - resp.arrayBuffer().then(arr => ok(arr)); - }); - }); - return promise; -} - -window.onload = async function() { - let ctxopts = { - alpha: false, - depth: true, - stencil: false, - antialias: true, - preserveDrawingBuffer: false - }; - - gl = document.getElementById("mcanvas").getContext("webgl2", ctxopts); - if(!gl) { - error_fatal("Your browser does not support WebGL 2"); - } - - files[0] = file_load("binary.wasm"); - files[1] = file_load("shader/cube_f.glsl"); - files[2] = file_load("shader/cube_v.glsl"); - for(var i=0; i<files.length; i++) { - files[i] = await files[i]; - } - - let binary = files[0]; - imports['memory'] = new WebAssembly.Memory({'initial':32}); - memory = new Uint8Array(imports['memory']['buffer']); - let program = await WebAssembly.instantiate(binary, {"env":imports}); - - let instance = program['instance']; - exports = instance['exports']; - - canvas_resize(); - window.addEventListener("resize", canvas_resize); - - if(!exports['init']()) { - error_fatal("Engine initialization failed"); - } - - canvas_render(); -} diff --git a/js/utils.js b/js/utils.js @@ -1,28 +0,0 @@ -function buf2str(ptr, len) { - let arr = memory.subarray(ptr, ptr + len); - return utf8decoder.decode(arr); -} - -function error_fatal(message) { - console.log(message); - throw message; -} - -function webgl_id_new(obj) { - if(gl_id_freelist.length == 0) - { - gl_id_map.push(obj); - return gl_id_map.length - 1; - } - else - { - let id = gl_id_freelist.shift(); - gl_id_map[id] = obj; - return id; - } -} - -function webgl_id_remove(id){ - delete gl_id_map[id]; - gl_id_freelist.push(id); -} diff --git a/src/platform_wasm_index.html b/src/platform_wasm_index.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<title>Transparent Cube</title> +<meta name=viewport content='width=device-width,initial-scale=1' charset='utf-8'> +<style> +body { + margin: 0; + overflow: hidden; +} +#webglcanvas { + width: 100vw; + height: 100vh; +} +</style> +<canvas id=webglcanvas></canvas> +<script src='script.js' defer></script> diff --git a/wasm_js_implemented_symbols.txt b/src/platform_wasm_js_symbols.txt diff --git a/src/platform_wasm_loader.js b/src/platform_wasm_loader.js @@ -0,0 +1,248 @@ +let utf8decoder = new TextDecoder("utf-8"); +let memory = null; +let exports = {}; +let imports = {}; +let files = []; +let gl = null; +let gl_id_freelist = []; +let gl_id_map = [null]; +imports["webglAttachShader"] = function(program_id, shader_id) { + let program = gl_id_map[program_id]; + let shader = gl_id_map[shader_id]; + gl.attachShader(program, shader); +} +imports["webglBindBuffer"] = function(target, buffer_id) { + let buffer = gl_id_map[buffer_id]; + gl.bindBuffer(target, buffer); +} +imports["webglBindVertexArray"] = function(vao_id) { + let vao = gl_id_map[vao_id]; + gl.bindVertexArray(vao); +} +imports["webglBlendColor"] = function(red, green, blue, alpha) { + gl.blendColor(red, green, blue, alpha); +} +imports["webglBlendFunc"] = function(sfactor, dfactor) { + gl.blendFunc(sfactor, dfactor); +} +imports["webglBufferData"] = function(target, size, data, usage) { + let dataslice = memory.subarray(data, data + size); + gl.bufferData(target, dataslice, usage); +} +imports["webglClear"] = function(mask) { + gl.clear(mask); +} +imports["webglClearColor"] = function(red, green, blue, alpha) { + gl.clearColor(red, green, blue, alpha); +} +imports["webglCompileShader"] = function(shader_id) { + let shader = gl_id_map[shader_id]; + gl.compileShader(shader); +} +imports["webglCreateBuffer"] = function() { + let buffer = gl.createBuffer(); + let buffer_id = webgl_id_new(buffer); + return buffer_id +} +imports["webglCreateProgram"] = function() { + let program = gl.createProgram(); + let program_id = webgl_id_new(program); + return program_id; +} +imports["webglCreateShader"] = function(type) { + let shader = gl.createShader(type); + let shader_id = webgl_id_new(shader); + return shader_id; +} +imports["webglCreateVertexArray"] = function() { + let vao = gl.createVertexArray() + let vao_id = webgl_id_new(vao); + return vao_id +} +imports["webglDeleteBuffer"] = function(buffer_id) { + let buffer = gl_id_map[buffer_id]; + gl.deleteBuffer(buffer); + webgl_id_remove(buffer_id); +} +imports["webglDeleteShader"] = function(shader_id) { + let shader = gl_id_map[shader_id]; + gl.deleteShader(shader); + webgl_id_remove(shader_id); +} +imports["webglDeleteVertexArray"] = function(vao_id) { + let vao = gl_id_map[vao_id]; + gl.deleteVertexArray(vao); + webgl_id_remove(vao_id); +} +imports["webglDepthMask"] = function(flag) { + gl.depthMask(flag); +} +imports["webglDisable"] = function(cap) { + gl.disable(cap); +} +imports["webglDrawElements"] = function(mode, count, type, offset) { + gl.drawElements(mode, count, type, offset); +} +imports["webglEnable"] = function(cap) { + gl.enable(cap); +} +imports["webglEnableVertexAttribArray"] = function(index) { + gl.enableVertexAttribArray(index); +} +imports["webglGetProgramInfoLog"] = function() { +} +imports["webglGetProgramParameter"] = function(program_id, param) { + let program = gl_id_map[program_id]; + return gl.getProgramParameter(program, param); +} +imports["webglGetShaderInfoLog"] = function(shader_id, out_buf) { + let shader = gl_id_map[shader_id]; + let info_log = gl.getShaderInfoLog(shader); + + // TODO: remove this once we get sprintf workingk + console.log(info_log); + + let arr = memory.subarray(info_log, out_buf + info_log.byteLength); + arr.set(new Uint8Array(info_log)); + return true; +} +imports["webglGetShaderParameter"] = function(shader_id, param) { + let shader = gl_id_map[shader_id]; + let result = gl.getShaderParameter(shader, param); + return result; +} +imports["webglGetUniformLocation"] = function(program_id, name_ptr, name_len) { + let program = gl_id_map[program_id]; + let name = utf8decoder.decode(memory.subarray(name_ptr, name_ptr+name_len)); + let loc = gl.getUniformLocation(program, name); + let location_id = webgl_id_new(loc); + return location_id; +} +imports["webglLinkProgram"] = function(program_id) { + let program = gl_id_map[program_id]; + gl.linkProgram(program); +} +imports["webglShaderSource"] = function(shader_id, source_ptr, source_len) { + let shader = gl_id_map[shader_id]; + let arr = memory.subarray(source_ptr, source_ptr + source_len); + let s = utf8decoder.decode(arr); + gl.shaderSource(shader, s); +} +imports["webglUniform1f"] = function(location_id, value) { + let loc = gl_id_map[location_id]; + gl['uniform1f'](loc, value); +} +imports["webglUniform1i"] = function(location_id, value) { + let loc = gl_id_map[location_id]; + gl['uniform1i'](loc, value); +} +imports["webglUniform3f"] = function(location_id, x, y, z) { + let loc = gl_id_map[location_id]; + gl['uniform3fv'](loc, [x, y, z]); +} +imports["webglUniformMatrix4fv"] = function(location_id, transpose, data) { + let loc = gl_id_map[location_id]; + let dataslice = memory.slice(data, data + 4 * 16); + gl.uniformMatrix4fv(loc, transpose, new Float32Array(dataslice.buffer)); +} +imports["webglUseProgram"] = function(program_id) { + let program = gl_id_map[program_id]; + gl.useProgram(program); +} +imports["webglVertexAttribPointer"] = function(index, size, type, normalized, stride, offset) { + gl.vertexAttribPointer(index, size, type, normalized, stride, offset); +} +imports["webglViewport"] = function(x, y, width, height) { + gl.viewport(x, y, width, height); +} +imports["js_read_entire_file"] = function(name, name_len, out_buf) { + let file_name = utf8decoder.decode(memory.subarray(name, name + name_len)) + if (file_name == "shader/cube_f.glsl") { + var file = files[1]; + } else if (file_name == "shader/cube_v.glsl") { + var file = files[2]; + } else { + return false; + } + let arr = memory.subarray(out_buf, out_buf + file.byteLength); + let s = String.fromCharCode.apply(null, arr); + arr.set(new Uint8Array(file)); + return true; +} +imports["js_print"] = function(s, len) { + let arr = memory.subarray(s, s + len); + console.log(utf8decoder.decode(arr)); +} +function error_fatal(message) { + console.log(message); + throw message; +} +function webgl_id_new(obj) { + if(gl_id_freelist.length == 0) { + gl_id_map.push(obj); + return gl_id_map.length - 1; + } else { + let id = gl_id_freelist.shift(); + gl_id_map[id] = obj; + return id; + } +} +function webgl_id_remove(id) { + delete gl_id_map[id]; + gl_id_freelist.push(id); +} +function canvas_resize() { + let pr = window.devicePixelRatio; + let w = window.innerWidth; + let h = window.innerHeight; + // Bitwise OR does float truncation + let w_pixels = (w * pr) | 0; + let h_pixels = (h * pr) | 0; + gl.canvas.width = w_pixels; + gl.canvas.height = h_pixels + exports['window_resize'](w_pixels, h_pixels); + console.log("resize: (" + w_pixels + ", " + h_pixels + ")"); +} +function canvas_render() { + exports['render'](); + window.requestAnimationFrame(canvas_render); +} +function file_load(name) { + let promise = new Promise((resolve, reject) => { + fetch(name).then(resp => { + resp.arrayBuffer().then(arr => resolve(arr)); + }); + }); + return promise; +} +window.onload = async function() { + let ctxopts = { + alpha: false, + depth: true, + stencil: false, + antialias: true, + preserveDrawingBuffer: false + }; + gl = document.getElementById("webglcanvas").getContext("webgl2", ctxopts); + if(!gl) { + error_fatal("Your browser does not support WebGL 2."); + } + files[0] = file_load("binary.wasm"); + files[1] = file_load("shader/cube_f.glsl"); + files[2] = file_load("shader/cube_v.glsl"); + for(var i=0; i<files.length; i++) { + files[i] = await files[i]; + } + let binary = files[0]; + imports['memory'] = new WebAssembly.Memory({'initial':32}); + memory = new Uint8Array(imports['memory']['buffer']); + let program = await WebAssembly.instantiate(binary, {"env":imports}); + let instance = program['instance']; + exports = instance['exports']; + canvas_resize(); + window.addEventListener("resize", canvas_resize); + if(!exports['init']()) { + error_fatal("Game initialization failed."); + } + canvas_render(); +}