transparent-cube

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

commit 09f7cdd7434f94d62d635c9d386d1ad2e4510ee7
parent e3e6a1a07cfe80b68f2e35f41dc86ab7c08af43b
Author: amin <dev@aminmesbah.com>
Date:   Thu, 20 Jun 2019 07:06:02 +0000

Get wasm build compiling and partially running

FossilOrigin-Name: fc698c85303b25f95b9c642849eaee8721b25ab3c74332bd83b8cd41afd4db1e
Diffstat:
Mbuild_wasm.sh | 16++++++++++++++--
Aindex.html | 17+++++++++++++++++
Ajs/globals.js | 8++++++++
Ajs/imports.js | 165+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ajs/loader.js | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ajs/utils.js | 28++++++++++++++++++++++++++++
Msrc/game.c | 1+
Msrc/game.h | 41+++++++++++++++++++++++++----------------
Asrc/platform_wasm.c | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/platform_wasm.h | 6++++++
Msrc/webgl.h | 53++++++++++++++++++++++++++++++-----------------------
Mwasm_js_implemented_symbols.txt | 14++++++++------
12 files changed, 451 insertions(+), 47 deletions(-)

diff --git a/build_wasm.sh b/build_wasm.sh @@ -1,11 +1,23 @@ + 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/ + clang -cc1 -Ofast -emit-llvm-bc -triple=wasm32-unknown-unknown-unknown-wasm -std=c11 \ + -ffreestanding \ + -fno-builtin \ -DGAME_WEBGL \ - src/game.c + 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 binary.wasm --strip-all -allow-undefined-file wasm_js_implemented_symbols.txt --import-memory + +wasm-ld --no-entry wasm.o \ + -o ./out/wasm/binary.wasm \ + -allow-undefined-file wasm_js_implemented_symbols.txt \ + --export-all \ + --import-memory #clang \ # --target=wasm32 \ diff --git a/index.html b/index.html @@ -0,0 +1,17 @@ +<!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 @@ -0,0 +1,8 @@ +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 @@ -0,0 +1,165 @@ +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+length); + 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['uniform1i'](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, data) { + let loc = gl_id_map[location_id]; + let dataslice = memory.slice(data, data + 4 * 16); + gl.uniformMatrix4fv(loc, false, 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["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 @@ -0,0 +1,62 @@ +function canvas_resize() { + let pr = window.devicePixelRatio; + let w = window.innerWidth; + let h = window.innerHeight; + gl.canvas.width = (w * pr) | 0; + gl.canvas.height = (h * pr) | 0; + exports['window_resize']((w * pr) | 0, (h * pr) | 0); + console.log("resize: [" + (w * pr | 0) + ", " + (h * pr | 0) + "] > [" + (w | 0) + ", " + (h | 0) + "]"); +} + +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 @@ -0,0 +1,28 @@ +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/game.c b/src/game.c @@ -106,6 +106,7 @@ void game_update_and_render(struct GameState *game_state, float dt, u32 screen_w shader_use(&game_state->cube_shader); shader_setm4(&game_state->cube_shader, "view", &view); + print("boooooooork"); shader_setm4(&game_state->cube_shader, "projection", &projection); m4 model = glmth_m4_init_id(); diff --git a/src/game.h b/src/game.h @@ -1,30 +1,39 @@ #pragma once -// TODO: Clean up -#ifndef __has_builtin -#define __has_builtin(x) 0 -#endif - #include <stddef.h> #include <stdint.h> #define static_assert _Static_assert -static_assert(__has_builtin(__builtin_cosf), "cosf"); -static_assert(__has_builtin(__builtin_sinf), "sinf"); -static_assert(__has_builtin(__builtin_tanf), "tanf"); -static_assert(__has_builtin(__builtin_sqrtf), "sqrtf"); -static_assert(__has_builtin(__builtin_powf), "powf"); -#define cosf __builtin_cosf -#define sinf __builtin_sinf -#define tanf __builtin_tanf -#define sqrtf __builtin_sqrtf -#define powf __builtin_powf - #define NULL ((void*)0) // TODO: fix this #define assert(x) (void)0 +inline float sinf(float a) +{ + return a; +} + +inline float cosf(float a) +{ + return a; +} + +inline float powf(float x, float p) +{ + return x; +} + +inline float sqrtf(float a) +{ + return a; +} + +inline float tanf(float a) +{ + return a; +} + typedef _Bool bool; #define true 1 #define false 0 diff --git a/src/platform_wasm.c b/src/platform_wasm.c @@ -0,0 +1,87 @@ +#include "game.c" +#include "platform.h" +#include "platform_wasm.h" + +#define export __attribute__( ( visibility( "default" ) ) ) + +char *js_read_entire_file(i32 file_path, i32 name_len, char *file_data); +int js_print(i32 string, i32 len); + +struct GameState g_game_state = {0}; +i32 g_width = PLATFORM_SCR_WIDTH; +i32 g_height = PLATFORM_SCR_HEIGHT; +char g_mem_buffer[1000] = {0}; +i32 g_mem_buffer_i = 0; + +export bool init(void) +{ + struct GameState g_game_state = {0}; + g_game_state.platform.platform_read_entire_file = &wasm_read_entire_file; + g_game_state.platform.platform_print = &wasm_print; + g_game_state.platform.platform_memory_free = &wasm_memory_free; + game_init(&g_game_state, g_width, g_height); + return true; +} + +export void render(void) +{ + game_update_and_render(&g_game_state, 16, g_width, g_height); +} + +export void window_resize(int w, int h) +{ + g_width = w; + g_height = h; +} + +i32 str_length(const char *str) +{ + i32 i = 0; + while(str[i] != '\0') + { + i++; + } + return i; +} + +void *memset(void *s, int c, size_t n) +{ + u8 *p = s; + while(n > 0) + { + *p = (u8)c; + p++; + n--; + } + return s; +} + +PLATFORM_READ_ENTIRE_FILE(wasm_read_entire_file) +{ + g_mem_buffer_i++; + char *buf = &g_mem_buffer[g_mem_buffer_i]; + if(js_read_entire_file((i32)file_path, str_length(file_path), buf)) + { + i32 file_data_length = str_length(buf); + g_mem_buffer_i += file_data_length; + g_mem_buffer[g_mem_buffer_i] = '\0'; + wasm_print("Succeeded in reading file."); + } + else + { + wasm_print("Failed to read file."); + } + return buf; +} + +PLATFORM_PRINT(wasm_print) +{ + i32 len = str_length(format); + js_print((i32)format, len); + return 0; +} + +PLATFORM_MEMORY_FREE(wasm_memory_free) +{ + g_mem_buffer_i = 0; +} diff --git a/src/platform_wasm.h b/src/platform_wasm.h @@ -0,0 +1,6 @@ +#define PLATFORM_SCR_WIDTH 600 +#define PLATFORM_SCR_HEIGHT 600 + +PLATFORM_READ_ENTIRE_FILE(wasm_read_entire_file); +PLATFORM_PRINT(wasm_print); +PLATFORM_MEMORY_FREE(wasm_memory_free); diff --git a/src/webgl.h b/src/webgl.h @@ -6,7 +6,7 @@ typedef double f64; // NOTE(amin): Since these functions will be implemented in javascript, we can // only use i32, f32, and f64 params. -void webglAttachShader(i32 shader, i32 program); +void webglAttachShader(i32 program, i32 shader); void webglBindBuffer(i32 target, i32 buffer); void webglBindVertexArray(i32 vao); void webglBlendColor(f32 r, f32 g, f32 b, f32 a); @@ -15,25 +15,25 @@ void webglBufferData(i32 target, i32 size, i32 data, i32 usage); void webglClear(i32 mask); void webglClearColor(f32 r, f32 g, f32 b, f32 a); void webglCompileShader(i32 shader); +i32 webglCreateBuffer(void); i32 webglCreateProgram(void); i32 webglCreateShader(i32 type); -void webglDeleteBuffers(i32 bo); +i32 webglCreateVertexArray(void); +void webglDeleteBuffer(i32 bo); void webglDeleteShader(i32 shader); -void webglDeleteVertexArrays(i32 vao); -void webglDepthMask(i32 b); +void webglDeleteVertexArray(i32 vao); +void webglDepthMask(i32 flag); void webglDisable(i32 cap); void webglDrawElements(i32 mode, i32 count, i32 type, i32 offset); void webglEnable(i32 cap); void webglEnableVertexAttribArray(i32 index); -void webglGenBuffers(i32 n, i32 buffers); -void webglGenVertexArrays(i32 n, i32 arrays); void webglGetProgramInfoLog(void); -int webglGetProgramiv(i32 program, i32 param); -void webglGetShaderInfoLog(void); -int webglGetShaderiv(i32 shader, i32 param); +int webglGetProgramParameter(i32 program, i32 param); +void webglGetShaderInfoLog(i32 shader, char *out_buf); +int webglGetShaderParameter(i32 shader, i32 param); i32 webglGetUniformLocation(i32 program, const char name[static 1], i32 name_len); void webglLinkProgram(i32 program); -void webglShaderSource(i32 shader, i32 count, const char source[static 1], i32 source_len); +void webglShaderSource(i32 shader, const char source[static 1], i32 source_len); void webglUniform1f(i32 location, f32 value); void webglUniform1i(i32 location, i32 value); void webglUniform3f(i32 location, f32 x, f32 y, f32 z); @@ -141,9 +141,9 @@ inline GLuint glCreateShader(GLenum type) inline void glDeleteBuffers(GLsizei n, const GLuint *buffers) { - // TODO: assert n == 1 + assert(n == 1); i32 the_buffer = WEBGL_CAST_I32(buffers[0]); - webglDeleteBuffers(the_buffer); + webglDeleteBuffer(the_buffer); } inline void glDeleteShader(GLuint shader) @@ -153,9 +153,9 @@ inline void glDeleteShader(GLuint shader) inline void glDeleteVertexArrays(GLsizei n, const GLuint *arrays) { - // TODO: assert n == 1 + assert(n == 1); i32 the_array = WEBGL_CAST_I32(arrays[0]); - webglDeleteVertexArrays(the_array); + webglDeleteVertexArray(the_array); } inline void glDepthMask(GLboolean flag) @@ -185,12 +185,18 @@ inline void glEnableVertexAttribArray(GLuint index) inline void glGenBuffers(GLsizei n, GLuint *buffers) { - webglGenBuffers(WEBGL_CAST_I32(n), WEBGL_CAST_I32(*buffers)); + assert(n == 1); + i32 buffer_id = webglCreateBuffer(); + assert(buffer_id >= 0); + *buffers = (GLuint)buffer_id; } inline void glGenVertexArrays(GLsizei n, GLuint *arrays) { - webglGenVertexArrays(WEBGL_CAST_I32(n), WEBGL_CAST_I32(*arrays)); + assert(n == 1); + i32 vao_id = webglCreateVertexArray(); + assert(vao_id >= 0); + *arrays = (GLuint)vao_id; } inline void glGetProgramInfoLog(GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog) @@ -201,18 +207,17 @@ inline void glGetProgramInfoLog(GLuint program, GLsizei bufSize, GLsizei *length inline void glGetProgramiv(GLuint program, GLenum pname, GLint *params) { - *params = webglGetProgramiv(WEBGL_CAST_I32(program), WEBGL_CAST_I32(pname)); + *params = webglGetProgramParameter(WEBGL_CAST_I32(program), WEBGL_CAST_I32(pname)); } inline void glGetShaderInfoLog(GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog) { - // TODO: implement - //webglGetShaderInfoLog + webglGetShaderInfoLog(WEBGL_CAST_I32(shader), infoLog); } inline void glGetShaderiv(GLuint shader, GLenum pname, GLint *params) { - *params = webglGetShaderiv(WEBGL_CAST_I32(shader), WEBGL_CAST_I32(pname)); + *params = webglGetShaderParameter(WEBGL_CAST_I32(shader), WEBGL_CAST_I32(pname)); } inline GLint glGetUniformLocation(GLuint program, const GLchar *name) @@ -226,9 +231,11 @@ inline void glLinkProgram(GLuint program) webglLinkProgram(WEBGL_CAST_I32(program)); } -inline void glShaderSource(GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length) +inline void glShaderSource(GLuint shader, GLsizei count, const GLchar *const *string, const GLint *length) { - webglShaderSource(WEBGL_CAST_I32(shader), WEBGL_CAST_I32(count), *string, WEBGL_CAST_I32(*length)); + const GLchar *s = (void *)*string; + i32 l = _webgl_strlen(s); + webglShaderSource(WEBGL_CAST_I32(shader), s, l); } inline void glUniform1f(GLint location, GLfloat v0) @@ -248,7 +255,7 @@ inline void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2) inline void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value) { - webglUniformMatrix4fv(WEBGL_CAST_I32(location), value); + webglUniformMatrix4fv(WEBGL_CAST_I32(location), WEBGL_CAST_I32(value)); } inline void glUseProgram(GLuint program) diff --git a/wasm_js_implemented_symbols.txt b/wasm_js_implemented_symbols.txt @@ -7,22 +7,22 @@ webglBufferData webglClear webglClearColor webglCompileShader +webglCreateBuffer webglCreateProgram webglCreateShader -webglDeleteBuffers +webglCreateVertexArray +webglDeleteBuffer webglDeleteShader -webglDeleteVertexArrays +webglDeleteVertexArray webglDepthMask webglDisable webglDrawElements webglEnable webglEnableVertexAttribArray -webglGenBuffers -webglGenVertexArrays webglGetProgramInfoLog -webglGetProgramiv +webglGetProgramParameter webglGetShaderInfoLog -webglGetShaderiv +webglGetShaderParameter webglGetUniformLocation webglLinkProgram webglShaderSource @@ -32,3 +32,5 @@ webglUniform3f webglUniformMatrix4fv webglUseProgram webglVertexAttribPointer +js_read_entire_file +js_print