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