commit cef315be54cde8a1e1c201396060c8f68458996f
parent c0855c54283731633610d318693898b162beebd2
Author: amin <dev@aminmesbah.com>
Date:   Tue,  2 Jul 2019 00:46:19 +0000
Merge branch 'features/wasm-platform-layer'
FossilOrigin-Name: 023877e4ec56e8fc7330b64bf970192fe987e690589166ef53a1bfe3ba3d91f3
Diffstat:
19 files changed, 916 insertions(+), 38 deletions(-)
diff --git a/build_wasm.sh b/build_wasm.sh
@@ -0,0 +1,43 @@
+#!/usr/bin/env bash
+
+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
+
+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 \
+    -o $wasm_dir/wasm.bc \
+    src/platform_wasm.c
+
+llvm-link -o $wasm_dir/wasm.bc $wasm_dir/wasm.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=init \
+    --export=render \
+    --export=window_resize \
+    --export=key_callback \
+    --import-memory
+
+rm $wasm_dir/*.o
+rm $wasm_dir/*.bc
diff --git a/shader/main_f.glsl b/shader/main_f.glsl
@@ -1,4 +1,9 @@
-#version 330 core
+#version 300 es
+
+// NOTE(amin): this is necessary for webgl compat
+precision highp float;
+
+// TODO: dynamically instert the version header after loading the file
 
 in vec4 vertex_color;
 
diff --git a/shader/main_v.glsl b/shader/main_v.glsl
@@ -1,4 +1,6 @@
-#version 330 core
+#version 300 es
+
+// TODO: dynamically instert the version header after loading the file
 
 layout (location = 0) in vec2 position;
 
diff --git a/src/am_math.h b/src/am_math.h
@@ -38,27 +38,20 @@ internal inline f32 math_wrap(f32 n, f32 min, f32 max)
     }
 }
 
-internal inline i32 math_floor(f32 x)
+// TODO: Test this against floorf
+internal inline f32 math_floor(f32 x)
 {
-    i32 result = floorf(x);
+    f32 result = (f32)((i32)x - (x < 0.0f));
     return result;
 }
 
+// TODO: Test this against ceilf
 internal inline i32 math_ceil(f32 x)
 {
-    i32 result = ceilf(x);
+    i32 result = -math_floor(-x);
     return result;
 }
 
-internal inline f32 math_rand(f32 min, f32 max)
-{
-    assert(min < max);
-    f32 random = ((f32) rand()) / (f32) RAND_MAX;
-    f32 diff = max - min;
-    f32 r = random * diff;
-    return min + r;
-}
-
 internal inline bool math_f32_is_i32(f32 x)
 {
     bool result = ((i32)x) == x;
diff --git a/src/game.c b/src/game.c
@@ -1,5 +1,9 @@
 #include "game.h"
+
+#ifndef GAME_WEBGL
 #include "glad.c"
+#endif
+
 #include "shader.c"
 #include "memory.c"
 #include "collision.c"
@@ -170,9 +174,13 @@ internal void game_init(struct GameMemory *game_memory, v2u framebuffer)
         struct PlatformApi platform = game_memory->platform;
         char *v_source = platform.platform_read_entire_file("shader/main_v.glsl");
         char *f_source = platform.platform_read_entire_file("shader/main_f.glsl");
-        struct Shader main_shader = shader_compile(v_source, f_source);
-        free(v_source);
-        free(f_source);
+        struct Shader main_shader = {0};
+        if (!shader_compile(v_source, f_source, &main_shader))
+        {
+            // TODO: handle error
+        }
+        platform.platform_memory_free(v_source);
+        platform.platform_memory_free(f_source);
         game_state->renderer.shader = main_shader;
     }
 }
diff --git a/src/game.h b/src/game.h
@@ -1,5 +1,3 @@
-#include <assert.h>
-
 // NOTE(amin): On windows, even with clang and -std=c11, assert.h doesn't
 // define static_assert.
 #ifndef static_assert
@@ -7,14 +5,73 @@
 #endif
 
 #include <limits.h>
-#include <math.h>
 #include <stdalign.h>
 #include <stdbool.h>
+#include <stddef.h>
 #include <stdint.h>
+
+// TODO: only include this in the non-wasm builds
+#include <math.h>
+
+#ifndef GAME_WEBGL
+
+#include <assert.h>
 #include <stdio.h>
 #include <stdlib.h>
 
-#include <glad/glad.h>
+#else
+// TODO: organize this stuff
+
+#define assert(x) (void)0
+
+int32_t printf(const char *format, ...)
+{
+    // lol
+    return 0;
+}
+
+// looooooooooool
+#define exit(status) (void)0
+
+void *memset(void *s, int32_t c, size_t n)
+{
+    uint8_t *p = s;
+    while(n > 0)
+    {
+        *p = (uint8_t)c;
+        p++;
+        n--;
+    }
+    return s;
+}
+
+// TODO: assert no overlap
+void *memcpy(void *dst, const void *src, size_t n)
+{
+  uint8_t *destination = (uint8_t *)dst;
+  uint8_t *source = (uint8_t *)src;
+
+  while (n--) {
+    *destination = *source;
+    destination++;
+    source++;
+  }
+
+  return dst;
+}
+
+// TODO: actually implement
+void *memmove(void *dst, const void *src, size_t n)
+{
+    return memcpy(dst, src, n);
+}
+#endif // GAME_WEBGL
+
+#ifdef GAME_WEBGL
+#include "webgl.h"
+#else
+#include "glad/glad.h"
+#endif
 
 #include "types.h"
 #include "intrinsics.h"
diff --git a/src/platform.h b/src/platform.h
@@ -1,7 +1,5 @@
 #pragma once
 
-#include <time.h>
-
 #include "types.h"
 
 #define KIBIBYTES(n) (n) * 1024LL
@@ -10,9 +8,13 @@
 #define PLATFORM_READ_ENTIRE_FILE(name) char *(name)(char *file_path)
 typedef PLATFORM_READ_ENTIRE_FILE(platform_read_entire_file_func);
 
+#define PLATFORM_MEMORY_FREE(name) void (name)(void *ptr)
+typedef PLATFORM_MEMORY_FREE(platform_memory_free_func);
+
 struct PlatformApi
 {
     platform_read_entire_file_func* platform_read_entire_file;
+    platform_memory_free_func* platform_memory_free;
 };
 
 struct GameMemory
@@ -23,6 +25,7 @@ struct GameMemory
 };
 
 #ifdef PLATFORM_HOTLOAD_GAME_CODE
+#include <time.h>
 // We need to call this from the platform layer in order for the game, when
 // built as a shared object library to have access to the OpenGL symbols.
 // https://github.com/Dav1dde/glad/issues/151
diff --git a/src/platform_linux.c b/src/platform_linux.c
@@ -64,6 +64,7 @@ int main(void)
         .buffer_size = MEBIBYTES(64),
         .platform = {
             &linux_read_entire_file,
+            &linux_memory_free,
         },
     };
     // TODO: replace with mmap
@@ -201,6 +202,11 @@ internal time_t file_get_modified_time(char *file_path)
     return mtime;
 }
 
+PLATFORM_MEMORY_FREE(linux_memory_free)
+{
+    free(ptr);
+}
+
 internal PLATFORM_READ_ENTIRE_FILE(linux_read_entire_file)
 {
     FILE *handle = fopen(file_path, "r");
diff --git a/src/platform_linux.h b/src/platform_linux.h
@@ -5,6 +5,7 @@ internal void error_callback(int error, const char* description);
 internal void framebuffer_size_callback(GLFWwindow* window, int width, int height);
 internal void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods);
 internal PLATFORM_READ_ENTIRE_FILE(linux_read_entire_file);
+PLATFORM_MEMORY_FREE(linux_memory_free);
 
 #ifdef PLATFORM_HOTLOAD_GAME_CODE
 #define PLATFORM_GAME_LIB_PATH "./out/release/game.so"
diff --git a/src/platform_wasm.c b/src/platform_wasm.c
@@ -0,0 +1,134 @@
+#include "game.c"
+#include "platform.h"
+#include "platform_wasm.h"
+
+extern unsigned char __heap_base;
+
+// There's no standardized, nondeprecated way to get a numeric ID of a keyboard
+// key, so I guess we have to do that ourselves.
+enum WasmKey {
+    WASM_KEY_LEFT,
+    WASM_KEY_RIGHT,
+    WASM_KEY_UP,
+    WASM_KEY_DOWN,
+    WASM_KEY_S,
+    WASM_KEY_F11,
+};
+
+// TODO: map this in a nicer way
+global_variable int platform_input_map[NUM_GAME_BUTTONS] = {
+    [BTN_LEFT] = WASM_KEY_LEFT,
+    [BTN_RIGHT] = WASM_KEY_RIGHT,
+    [BTN_UP] = WASM_KEY_UP,
+    [BTN_DOWN] = WASM_KEY_DOWN,
+    [BTN_JUMP] = WASM_KEY_S,
+    [BTN_DEBUG_FLOAT] = WASM_KEY_F11,
+};
+
+global_variable struct GameMemory game_memory = {0};
+global_variable struct GameInput game_input = {0};
+global_variable i32 g_width = PLATFORM_SCR_WIDTH;
+global_variable i32 g_height = PLATFORM_SCR_HEIGHT;
+// TODO: replace with allocator
+global_variable char g_mem_buffer[1000] = {0};
+global_variable i32 g_mem_buffer_i = 0;
+global_variable u32 previous_time = 0;
+
+i32 str_length(const char *str)
+{
+    i32 i = 0;
+    while(str[i] != '\0')
+    {
+        i++;
+    }
+    return i;
+}
+
+int wasm_print(const char *format, ...)
+{
+    i32 len = str_length(format);
+    js_print((i32)format, len);
+    return 0;
+}
+
+bool init(i32 num_wasm_memory_pages)
+{
+    bool init_successful = true;
+    game_memory = (struct GameMemory) {
+        // NOTE(amin): each wasm memory page is 64KiB
+        .buffer_size = num_wasm_memory_pages * KIBIBYTES(64),
+        .platform = {
+            &wasm_read_entire_file,
+            &wasm_memory_free,
+        },
+        .buffer = &__heap_base,
+    };
+    if (!game_memory.buffer)
+    {
+        wasm_print("Game memory allocation failed\n");
+        init_successful = false;
+    }
+    game_init(&game_memory, (v2u) {g_width, g_height});
+    return init_successful;
+}
+
+void render(f64 current_time)
+{
+    game_input.dt = current_time - previous_time;
+    previous_time = current_time;
+    game_update_and_render(&game_memory, &game_input, (v2u) {g_width, g_height});
+}
+
+void window_resize(int w, int h)
+{
+    g_width = w;
+    g_height = h;
+    glViewport(0, 0, g_width, g_height);
+}
+
+void key_callback(enum WasmKey key, bool key_action_is_pressed)
+{
+    enum GameButton game_button = NULL_GAME_BUTTON;
+    // TODO: Determine the button in a nicer way
+    for (enum GameButton b = 0; b < NUM_GAME_BUTTONS; b++)
+    {
+        if (platform_input_map[b] == key)
+        {
+            game_button = b;
+        }
+    }
+    if (game_button != NULL_GAME_BUTTON)
+    {
+        if (key_action_is_pressed)
+        {
+            game_input.button_states |= (1 << game_button);
+        }
+        else
+        {
+            game_input.button_states &= ~(1 << game_button);
+        }
+    }
+}
+
+PLATFORM_MEMORY_FREE(wasm_memory_free)
+{
+    // do nothing
+}
+
+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;
+}
diff --git a/src/platform_wasm.h b/src/platform_wasm.h
@@ -0,0 +1,9 @@
+#define PLATFORM_SCR_WIDTH 600
+#define PLATFORM_SCR_HEIGHT 600
+
+PLATFORM_READ_ENTIRE_FILE(wasm_read_entire_file);
+PLATFORM_MEMORY_FREE(wasm_memory_free);
+
+// Functions implemented in the JS loader
+char *js_read_entire_file(i32 file_path, i32 name_len, char *file_data);
+int js_print(i32 string, i32 len);
diff --git a/src/platform_wasm_index.html b/src/platform_wasm_index.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>A Game</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/src/platform_wasm_js_symbols.txt b/src/platform_wasm_js_symbols.txt
@@ -0,0 +1,37 @@
+webglAttachShader
+webglBindBuffer
+webglBindVertexArray
+webglBlendColor
+webglBlendFunc
+webglBufferData
+webglClear
+webglClearColor
+webglCompileShader
+webglCreateBuffer
+webglCreateProgram
+webglCreateShader
+webglCreateVertexArray
+webglDeleteBuffer
+webglDeleteShader
+webglDeleteVertexArray
+webglDepthMask
+webglDisable
+webglDrawElements
+webglEnable
+webglEnableVertexAttribArray
+webglGetProgramInfoLog
+webglGetProgramParameter
+webglGetShaderInfoLog
+webglGetShaderParameter
+webglGetUniformLocation
+webglLinkProgram
+webglShaderSource
+webglUniform1f
+webglUniform1i
+webglUniform3f
+webglUniformMatrix4fv
+webglUseProgram
+webglVertexAttribPointer
+webglViewport
+js_read_entire_file
+js_print
diff --git a/src/platform_wasm_loader.js b/src/platform_wasm_loader.js
@@ -0,0 +1,272 @@
+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];
+// NOTE(amin): These values _must_ match the corresponding ones used by the
+// platform layer in `enum WasmKey`.
+// By the way: Thanks a lot, W3C, for not having any standardized _numeric_
+// representation of a key.
+const key_numeric_codes = {
+    // Code strings are from:
+    // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code/code_values
+    ArrowLeft: 0,
+    ArrowRight: 1,
+    ArrowUp: 2,
+    ArrowDown: 3,
+    KeyS: 4,
+    F11: 5,
+};
+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/main_f.glsl") {
+        var file = files[1];
+    } else if (file_name == "shader/main_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() {
+    let timestamp_in_ms = performance.now();
+    exports['render'](timestamp_in_ms);
+    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;
+}
+function on_key_event(e, key_is_down) {
+    let sane_numeric_code = key_numeric_codes[e.code];
+    if (sane_numeric_code !== undefined) {
+        exports['key_callback'](sane_numeric_code, key_is_down);
+    }
+}
+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/main_f.glsl");
+    files[2] = file_load("shader/main_v.glsl");
+    for(var i = 0; i < files.length; i++) {
+        files[i] = await files[i];
+    }
+    let binary = files[0];
+    let num_memory_pages = 64; // 64pages * 64KiB/page = 4096KiB = 4MiB
+    imports['memory'] = new WebAssembly.Memory({initial: num_memory_pages});
+    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);
+    window.addEventListener("keyup", (event) => {on_key_event(event, false)}, false);
+    window.addEventListener("keydown", (event) => {on_key_event(event, true)}, false);
+    if(!exports['init'](num_memory_pages)) {
+        error_fatal("Game initialization failed.");
+    }
+    canvas_render();
+}
diff --git a/src/platform_windows.c b/src/platform_windows.c
@@ -65,6 +65,7 @@ int main(void)
         .buffer_size = MEBIBYTES(64),
         .platform = {
             &windows_read_entire_file,
+            &windows_memory_free,
         },
     };
     // TODO: replace with VirtualAlloc
@@ -165,6 +166,11 @@ internal void key_callback(GLFWwindow *window, int key, int scancode, int action
     }
 }
 
+PLATFORM_MEMORY_FREE(windows_memory_free)
+{
+    free(ptr);
+}
+
 internal PLATFORM_READ_ENTIRE_FILE(windows_read_entire_file)
 {
     FILE *handle = fopen(file_path, "rb");
diff --git a/src/platform_windows.h b/src/platform_windows.h
@@ -5,6 +5,7 @@ internal void error_callback(int error, const char* description);
 internal void framebuffer_size_callback(GLFWwindow* window, int width, int height);
 internal void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods);
 internal PLATFORM_READ_ENTIRE_FILE(windows_read_entire_file);
+PLATFORM_MEMORY_FREE(windows_memory_free);
 
 #ifdef GLAD_DEBUG
 internal void pre_gl_callback(const char *func_name, void *func_ptr, int len_args, ...);
diff --git a/src/shader.c b/src/shader.c
@@ -1,12 +1,14 @@
-internal struct Shader shader_compile(const GLchar *vertex_shader_source, const GLchar *fragment_shader_source)
+bool shader_compile(const GLchar *vertex_shader_source, const GLchar *fragment_shader_source, struct Shader *compiled_shader)
 {
+    bool compilation_successful = true;
     GLint success;
     GLchar info_log[512];
 
     if (!(vertex_shader_source && fragment_shader_source))
     {
         printf("Error: One or more shader source files weren't loaded.\n");
-        exit(1);
+        // TODO: handle errors here in a better way
+        compilation_successful = false;
     }
     else
     {
@@ -19,7 +21,7 @@ internal struct Shader shader_compile(const GLchar *vertex_shader_source, const 
             glGetShaderInfoLog(vertex_shader, 512, NULL, info_log);
             printf("ERROR::SHADER::VERTEX::COMPILATION_FAILED\n %s\n", info_log);
             // TODO: handle errors here in a better way
-            exit(1);
+            compilation_successful = false;
         }
 
         GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
@@ -31,28 +33,26 @@ internal struct Shader shader_compile(const GLchar *vertex_shader_source, const 
             glGetShaderInfoLog(fragment_shader, 512, NULL, info_log);
             printf("ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n %s\n", info_log);
             // TODO: handle errors here in a better way
-            exit(1);
+            compilation_successful = false;
         }
 
-        struct Shader s;
-        s.program = glCreateProgram();
-        glAttachShader(s.program, vertex_shader);
-        glAttachShader(s.program, fragment_shader);
-        glLinkProgram(s.program);
-        glGetProgramiv(s.program, GL_LINK_STATUS, &success);
+        compiled_shader->program = glCreateProgram();
+        glAttachShader(compiled_shader->program, vertex_shader);
+        glAttachShader(compiled_shader->program, fragment_shader);
+        glLinkProgram(compiled_shader->program);
+        glGetProgramiv(compiled_shader->program, GL_LINK_STATUS, &success);
         if (!success)
         {
-            glGetShaderInfoLog(s.program, 512, NULL, info_log);
+            glGetShaderInfoLog(compiled_shader->program, 512, NULL, info_log);
             printf("ERROR::SHADER::LINKING_FAILED\n %s\n", info_log);
             // TODO: handle errors here in a better way
-            exit(1);
+            compilation_successful = false;
         }
 
         glDeleteShader(fragment_shader);
         glDeleteShader(vertex_shader);
-
-        return s;
     }
+    return compilation_successful;
 }
 
 
diff --git a/src/shader.h b/src/shader.h
@@ -3,7 +3,7 @@ struct Shader
     u32 program;
 };
 
-internal struct Shader shader_compile(const GLchar *vertex_shader_source, const GLchar *fragment_shader_source);
+internal bool shader_compile(const GLchar *vertex_shader_source, const GLchar *fragment_shader_source, struct Shader *compiled_shader);
 internal void shader_use(struct Shader *s);
 internal void shader_setb(struct Shader *s, char *name, bool value);
 internal void shader_seti(struct Shader *s, char *name, int value);
diff --git a/src/webgl.h b/src/webgl.h
@@ -0,0 +1,286 @@
+#pragma once
+
+typedef int    i32;
+typedef float  f32;
+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 program, i32 shader);
+void webglBindBuffer(i32 target, i32 buffer);
+void webglBindVertexArray(i32 vao);
+void webglBlendColor(f32 r, f32 g, f32 b, f32 a);
+void webglBlendFunc(i32 sfactor, i32 dfactor);
+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);
+i32  webglCreateVertexArray(void);
+void webglDeleteBuffer(i32 bo);
+void webglDeleteShader(i32 shader);
+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 webglGetProgramInfoLog(void);
+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, 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);
+void webglUniformMatrix4fv(i32 location, i32 transpose, const f32 data[static 16]);
+void webglUseProgram(i32 program);
+void webglVertexAttribPointer(i32 index, i32 size, i32 type, i32 normalized, i32 stride, i32 offset);
+void webglViewport(i32 x, i32 y, i32 width, i32 height);
+
+#define GL_DEPTH_BUFFER_BIT 0x00000100
+#define GL_COLOR_BUFFER_BIT 0x00004000
+#define GL_FALSE 0
+#define GL_TRUE 1
+#define GL_LINES 0x0001
+#define GL_TRIANGLES 0x0004
+#define GL_SRC_ALPHA 0x0302
+#define GL_FRONT_AND_BACK 0x0408
+#define GL_DEPTH_TEST 0x0B71
+#define GL_BLEND 0x0BE2
+#define GL_UNSIGNED_INT 0x1405
+#define GL_FLOAT 0x1406
+#define GL_FILL 0x1B02
+#define GL_CONSTANT_ALPHA 0x8003
+#define GL_ARRAY_BUFFER 0x8892
+#define GL_ELEMENT_ARRAY_BUFFER 0x8893
+#define GL_STATIC_DRAW 0x88E4
+#define GL_FRAGMENT_SHADER 0x8B30
+#define GL_VERTEX_SHADER 0x8B31
+#define GL_COMPILE_STATUS 0x8B81
+#define GL_LINK_STATUS 0x8B82
+
+typedef unsigned int  GLenum;
+typedef unsigned char GLboolean;
+typedef unsigned int  GLbitfield;
+typedef void          GLvoid;
+typedef int           GLint;
+typedef unsigned int  GLuint;
+typedef int           GLsizei;
+typedef float         GLfloat;
+typedef char          GLchar;
+typedef long          GLsizeiptr;
+
+// TODO: add a version with safety checks
+#define WEBGL_CAST_I32(x) (i32)(x)
+
+static inline i32 _webgl_strlen(const char *str)
+{
+    i32 len = 0;
+    while(str[len] != '\0')
+    {
+        len++;
+    }
+    return len;
+}
+
+inline void glAttachShader(GLuint program, GLuint shader)
+{
+    webglAttachShader(WEBGL_CAST_I32(program), WEBGL_CAST_I32(shader));
+}
+
+inline void glBindBuffer(GLenum target, GLuint buffer)
+{
+    webglBindBuffer(WEBGL_CAST_I32(target), WEBGL_CAST_I32(buffer));
+}
+
+inline void glBindVertexArray(GLuint array)
+{
+    webglBindVertexArray(WEBGL_CAST_I32(array));
+}
+
+inline void glBlendColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)
+{
+    webglBlendColor((f32)red, (f32)green, (f32)blue, (f32)alpha);
+}
+
+inline void glBlendFunc(GLenum sfactor, GLenum dfactor)
+{
+    webglBlendFunc(WEBGL_CAST_I32(sfactor), WEBGL_CAST_I32(dfactor));
+}
+
+inline void glBufferData(GLenum target, GLsizeiptr size, const void *data, GLenum usage)
+{
+    webglBufferData(WEBGL_CAST_I32(target), WEBGL_CAST_I32(size), WEBGL_CAST_I32(data), WEBGL_CAST_I32(usage));
+}
+
+inline void glClear(GLbitfield mask)
+{
+    webglClear(WEBGL_CAST_I32(mask));
+}
+
+inline void glClearColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)
+{
+    webglClearColor((f32)red, (f32)green, (f32)blue, (f32)alpha);
+}
+
+inline void glCompileShader(GLuint shader)
+{
+    webglCompileShader(WEBGL_CAST_I32(shader));
+}
+
+inline GLuint glCreateProgram(void)
+{
+    i32 program_id = webglCreateProgram();
+    return (GLuint)program_id;
+}
+
+inline GLuint glCreateShader(GLenum type)
+{
+    return webglCreateShader(WEBGL_CAST_I32(type));
+}
+
+inline void glDeleteBuffers(GLsizei n, const GLuint *buffers)
+{
+    assert(n == 1);
+    i32 the_buffer = WEBGL_CAST_I32(buffers[0]);
+    webglDeleteBuffer(the_buffer);
+}
+
+inline void glDeleteShader(GLuint shader)
+{
+    webglDeleteShader(WEBGL_CAST_I32(shader));
+}
+
+inline void glDeleteVertexArrays(GLsizei n, const GLuint *arrays)
+{
+    assert(n == 1);
+    i32 the_array = WEBGL_CAST_I32(arrays[0]);
+    webglDeleteVertexArray(the_array);
+}
+
+inline void glDepthMask(GLboolean flag)
+{
+    webglDepthMask(WEBGL_CAST_I32(flag));
+}
+
+inline void glDisable(GLenum cap)
+{
+    webglDisable(WEBGL_CAST_I32(cap));
+}
+
+inline void glDrawElements(GLenum mode, GLsizei count, GLenum type, const void *indices)
+{
+    webglDrawElements(WEBGL_CAST_I32(mode), WEBGL_CAST_I32(count), WEBGL_CAST_I32(type), WEBGL_CAST_I32(indices));
+}
+
+inline void glEnable(GLenum cap)
+{
+    webglEnable(WEBGL_CAST_I32(cap));
+}
+
+inline void glEnableVertexAttribArray(GLuint index)
+{
+    webglEnableVertexAttribArray(WEBGL_CAST_I32(index));
+}
+
+inline void glGenBuffers(GLsizei n, GLuint *buffers)
+{
+    assert(n == 1);
+    i32 buffer_id = webglCreateBuffer();
+    assert(buffer_id >= 0);
+    *buffers = (GLuint)buffer_id;
+}
+
+inline void glGenVertexArrays(GLsizei n, GLuint *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)
+{
+    // TODO: implement
+    //webglGetProgramInfoLog(WEBGL_CAST_I32(program), WEBGL_CAST_I32(bufsize), WEBGL_CAST_I32(*length), WEBGL_CAST_I32(*infoLog));
+}
+
+inline void glGetProgramiv(GLuint program, GLenum pname, GLint *params)
+{
+    *params = webglGetProgramParameter(WEBGL_CAST_I32(program), WEBGL_CAST_I32(pname));
+}
+
+inline void glGetShaderInfoLog(GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog)
+{
+    webglGetShaderInfoLog(WEBGL_CAST_I32(shader), infoLog);
+}
+
+inline void glGetShaderiv(GLuint shader, GLenum pname, GLint *params)
+{
+    *params = webglGetShaderParameter(WEBGL_CAST_I32(shader), WEBGL_CAST_I32(pname));
+}
+
+inline GLint glGetUniformLocation(GLuint program, const GLchar *name)
+{
+    i32 name_len = _webgl_strlen(name);
+    return webglGetUniformLocation(WEBGL_CAST_I32(program), name, name_len);
+}
+
+inline void glLinkProgram(GLuint program)
+{
+    webglLinkProgram(WEBGL_CAST_I32(program));
+}
+
+inline void glPolygonMode(GLenum face, GLenum mode)
+{
+    // No Op. This doesn't exist in webgl
+}
+
+inline void glShaderSource(GLuint shader, GLsizei count, const GLchar *const *string, const GLint *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)
+{
+    webglUniform1f(WEBGL_CAST_I32(location), (f32)v0);
+}
+
+inline void glUniform1i(GLint location, GLint v0)
+{
+    webglUniform1i(WEBGL_CAST_I32(location), WEBGL_CAST_I32(v0));
+}
+
+inline void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2)
+{
+    webglUniform3f(WEBGL_CAST_I32(location), (f32)v0, (f32)v1, (f32)v2);
+}
+
+inline void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value)
+{
+    webglUniformMatrix4fv(WEBGL_CAST_I32(location), WEBGL_CAST_I32(transpose), value);
+}
+
+inline void glUseProgram(GLuint program)
+{
+    webglUseProgram(WEBGL_CAST_I32(program));
+}
+
+inline void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer)
+{
+    webglVertexAttribPointer(WEBGL_CAST_I32(index), WEBGL_CAST_I32(size), WEBGL_CAST_I32(type), WEBGL_CAST_I32(normalized), WEBGL_CAST_I32(stride), WEBGL_CAST_I32(pointer));
+}
+
+inline void glViewport(GLint x, GLint y, GLsizei width, GLsizei height)
+{
+    webglViewport(x, y, WEBGL_CAST_I32(width), WEBGL_CAST_I32(height));
+}
+
+#undef WEBGL_CAST_I32