a-game

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

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:
Abuild_wasm.sh | 43+++++++++++++++++++++++++++++++++++++++++++
Mshader/main_f.glsl | 7++++++-
Mshader/main_v.glsl | 4+++-
Msrc/am_math.h | 17+++++------------
Msrc/game.c | 14+++++++++++---
Msrc/game.h | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Msrc/platform.h | 7+++++--
Msrc/platform_linux.c | 6++++++
Msrc/platform_linux.h | 1+
Asrc/platform_wasm.c | 134+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/platform_wasm.h | 9+++++++++
Asrc/platform_wasm_index.html | 15+++++++++++++++
Asrc/platform_wasm_js_symbols.txt | 37+++++++++++++++++++++++++++++++++++++
Asrc/platform_wasm_loader.js | 272+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/platform_windows.c | 6++++++
Msrc/platform_windows.h | 1+
Msrc/shader.c | 28++++++++++++++--------------
Msrc/shader.h | 2+-
Asrc/webgl.h | 286+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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