a-game

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

commit cf822b5b0ae2d9d7f4a63dc9135801d04bfd12e0
parent a0e9824657d278530015246bc9e9f2d7571d93da
Author: amin <dev@aminmesbah.com>
Date:   Mon,  8 Jul 2019 20:38:58 +0000

Merge branch 'features/textures'

FossilOrigin-Name: 993303fd18c0d0d15e103ed76f35037fb4bcc539fcdcf2404d8ea8162f2ddffc
Diffstat:
Aassets/test_sw_origin.tga | 0
Aassets/test_sw_origin_rle.tga | 0
Aassets/tile0.tga | 0
Mbuild_wasm.sh | 1+
Mshader/main_f.glsl | 6+++++-
Mshader/main_v.glsl | 3+++
Msrc/am_math.h | 10++++++++++
Msrc/game.c | 57+++++++++++++++++++++++++++++++++++++++++----------------
Msrc/game.h | 3+++
Asrc/image.c | 129+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/image.h | 25+++++++++++++++++++++++++
Msrc/memory.c | 5++++-
Msrc/memory.h | 3+++
Msrc/platform.h | 5+----
Msrc/platform_linux.c | 11++++++++---
Msrc/platform_wasm.c | 11+++++++----
Msrc/platform_wasm.h | 4++--
Msrc/platform_wasm_js_symbols.txt | 6++++++
Msrc/platform_wasm_loader.js | 39++++++++++++++++++++++++++++++++++++---
Msrc/platform_windows.c | 11++++++++---
Msrc/render.c | 42+++++++++++++++++++++++++++++++++---------
Msrc/render.h | 2++
Msrc/shader.c | 3+++
Msrc/shader.h | 1+
Msrc/webgl.h | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
25 files changed, 396 insertions(+), 46 deletions(-)

diff --git a/assets/test_sw_origin.tga b/assets/test_sw_origin.tga Binary files differ. diff --git a/assets/test_sw_origin_rle.tga b/assets/test_sw_origin_rle.tga Binary files differ. diff --git a/assets/tile0.tga b/assets/tile0.tga Binary files differ. diff --git a/build_wasm.sh b/build_wasm.sh @@ -12,6 +12,7 @@ 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 +cp -r assets/ $wasm_dir clang \ -cc1 \ diff --git a/shader/main_f.glsl b/shader/main_f.glsl @@ -6,10 +6,14 @@ precision highp float; // TODO: dynamically instert the version header after loading the file in vec4 vertex_color; +in vec2 tex_coord; out vec4 frag_color; +uniform float tex_interp; +uniform sampler2D main_texture; + void main() { - frag_color = vertex_color; + frag_color = mix(vertex_color, texture(main_texture, tex_coord), tex_interp); } diff --git a/shader/main_v.glsl b/shader/main_v.glsl @@ -3,8 +3,10 @@ // TODO: dynamically instert the version header after loading the file layout (location = 0) in vec2 position; +layout (location = 1) in vec2 a_tex_coords; out vec4 vertex_color; +out vec2 tex_coord; uniform mat4 model; uniform mat4 view; @@ -15,4 +17,5 @@ void main() { gl_Position = projection * view * model * vec4(position, 0.0f, 1.0f); vertex_color = vec4(color, 1.0f); + tex_coord = a_tex_coords; } diff --git a/src/am_math.h b/src/am_math.h @@ -270,6 +270,16 @@ internal inline v3 math_v3_s(v3 vec1, v3 vec2) return math_v3_a(vec1, math_v3_negate(vec2)); } +internal inline v3 math_v3_from_rgb(i32 rgb) +{ + v3 result = { + .x = ((rgb >> 16) & 0xFF) / 255.0f, + .y = ((rgb >> 8) & 0xFF) / 255.0f, + .z = ((rgb >> 0) & 0xFF) / 255.0f, + }; + return result; +} + internal inline bool math_v4v4_eq(v4 vec1, v4 vec2) { for (u8 i = 0; i < 4; ++i) diff --git a/src/game.c b/src/game.c @@ -10,6 +10,7 @@ #include "render.c" #include "world.c" #include "input.c" +#include "image.c" internal void move_mode_print(enum MoveMode s) { @@ -89,10 +90,16 @@ internal void game_init(struct GameMemory *game_memory, v2u framebuffer) game_state->player.move_mode = MOVE_MODE_FALLING; game_state->player.facing = DIR_RIGHT; + size_t temp_memory_size = MEBIBYTES(1); + size_t world_memory_size = game_memory->buffer_size - (sizeof(struct GameState) + temp_memory_size); mem_st_init( - &game_state->world_allocator, + &game_state->temp_allocator, game_memory->buffer + sizeof(struct GameState), - game_memory->buffer_size - sizeof(struct GameState)); + temp_memory_size); + mem_st_init( + &game_state->world_allocator, + game_state->temp_allocator.base + temp_memory_size, + world_memory_size); game_state->world = mem_st_alloc_struct(&game_state->world_allocator, struct World); @@ -167,13 +174,11 @@ internal void game_init(struct GameMemory *game_memory, v2u framebuffer) } } - renderer_init(&game_state->renderer, &game_state->world_allocator); - + struct PlatformApi platform = game_memory->platform; // game_shader_load { - 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"); + char *v_source = (char *)platform.platform_read_entire_file("shader/main_v.glsl", NULL); + char *f_source = (char *)platform.platform_read_entire_file("shader/main_f.glsl", NULL); struct Shader main_shader = {0}; if (!shader_compile(v_source, f_source, &main_shader)) { @@ -183,6 +188,27 @@ internal void game_init(struct GameMemory *game_memory, v2u framebuffer) platform.platform_memory_free(f_source); game_state->renderer.shader = main_shader; } + + size_t temp_free_marker = mem_st_get_marker(&game_state->temp_allocator); + struct Image img = {0}; + // game_load_images + { + size_t file_len = 0; + u8 *t = platform.platform_read_entire_file("assets/tile0.tga", &file_len); + assert(t); + + i32 img_w = 0; + i32 img_h = 0; + u8 *data = img_load_from_memory(t, file_len, &img_w, &img_h, &game_state->temp_allocator); + assert(data); + platform.platform_memory_free(t); + + img.data = data; + img.dim = (v2u) {img_w, img_h}; + } + + renderer_init(&game_state->renderer, &img, 1, &game_state->world_allocator); + mem_st_free_to_marker(&game_state->temp_allocator, temp_free_marker); } // NOTE(amin): For now updating and rendering are interleaved. We simulate the @@ -192,6 +218,7 @@ void game_update_and_render(struct GameMemory *game_memory, struct GameInput *ga { assert(sizeof(struct GameState) <= game_memory->buffer_size); struct GameState *game_state = (struct GameState *)game_memory->buffer; + f32 dt = game_input->dt; rect viewport = {0}; f32 ppm = 0.0f; @@ -223,15 +250,11 @@ void game_update_and_render(struct GameMemory *game_memory, struct GameInput *ga viewport.max = math_v2_a(viewport.min, viewport_size); m4 view = math_m4_init_id(); - // World origin is in the lower left - view = math_translate(view, (v3) {0.0f, framebuffer.height, 0.0f}); - view = math_scale(view, (v3) {1.0f, -1.0f, 1.0f}); view = math_translate(view, (v3) {viewport.min.x, viewport.min.y, 0.0f}); view = math_scale(view, (v3) {ppm, ppm, 1.0f}); game_state->renderer.view = view; - // Screen origin is in the upper left - game_state->renderer.projection = math_projection_ortho(0.0f, framebuffer.width, framebuffer.height, 0.0f, -1.0f, 0.0f); + game_state->renderer.projection = math_projection_ortho(0.0f, framebuffer.width, 0.0f, framebuffer.height, -1.0f, 0.0f); } v2i current_room_i = game_state->player.pos.room; @@ -251,8 +274,7 @@ void game_update_and_render(struct GameMemory *game_memory, struct GameInput *ga // game_update_player { struct Entity *player = &game_state->player; - move_mode_print(player->move_mode); - f32 dt = game_input->dt; + //move_mode_print(player->move_mode); u32 button_states = game_input->button_states; if (dt >= 0.5f) { @@ -387,7 +409,7 @@ void game_update_and_render(struct GameMemory *game_memory, struct GameInput *ga // game_detect_collisions { -#if 1 +#if 0 #define RENDER_COLLISION_DEBUG_QUAD(r, c) renderer_debug_quad_draw(&game_state->renderer, (r), (c)); #else #define RENDER_COLLISION_DEBUG_QUAD(r, c) @@ -566,13 +588,15 @@ void game_update_and_render(struct GameMemory *game_memory, struct GameInput *ga v3 color; bool solid = world_tile_in_room_is_solid(current_room->tiles, tile_pos); + f32 tex_interp = 0.0f; if (solid) { color = (v3) {0.4f, 0.4f, 0.4f}; + tex_interp = 1.0f; } else { - color = (v3) {0.3f, 0.3f, 0.3f}; + color = (math_v3_from_rgb(0x16161D)); } renderer_job_enqueue( @@ -581,6 +605,7 @@ void game_update_and_render(struct GameMemory *game_memory, struct GameInput *ga .ebo = game_state->renderer.quad_ebo, .color = color, .model = model, + .tex_interp = tex_interp, .layer = RENDER_LAYER_TILES, }); } diff --git a/src/game.h b/src/game.h @@ -81,6 +81,8 @@ void *memmove(void *dst, const void *src, size_t n) #include "render.h" #include "world.h" #include "input.h" +#include "image.h" + #include "platform.h" static_assert(-1 == ~0, "Implementation doesn't use two's complement"); @@ -127,6 +129,7 @@ struct GameState { struct RendererState renderer; struct Entity player; + struct StackAllocator temp_allocator; struct StackAllocator world_allocator; struct World *world; }; diff --git a/src/image.c b/src/image.c @@ -0,0 +1,129 @@ +internal void img_tga_header_print(struct ImgTgaHeader h) +{ + printf("id_length: %u\n", h.id_length); + printf("color_map_type: %u\n", h.color_map_type); + printf("data_type_code: %u\n", h.data_type_code); + printf("color_map_origin: %u\n", h.color_map_origin); + printf("color_map_length: %u\n", h.color_map_length); + printf("color_map_depth: %u\n", h.color_map_depth); + printf("x_origin: %u\n", h.x_origin); + printf("y_origin: %u\n", h.y_origin); + printf("width: %u\n", h.width); + printf("height: %u\n", h.height); + printf("bits_per_pixel: %u\n", h.bits_per_pixel); + printf("image_descriptor: %u\n", h.image_descriptor); +} + +internal u8 img_consume_byte(u8 **buffer, uintptr_t end_address) +{ + u8 byte = 0; + if ((uintptr_t)*buffer < end_address) + { + byte = **buffer; + (*buffer)++; + } + return byte; +} + +internal u8 *img_load_from_memory(u8 *buffer, size_t len, i32 *out_width, i32 *out_height, struct StackAllocator *allocator) +{ + assert(buffer); + uintptr_t buf_end = (uintptr_t)buffer + (len * sizeof(u8)); + + assert(len >= sizeof(struct ImgTgaHeader)); + struct ImgTgaHeader h = *(struct ImgTgaHeader *)buffer; + buffer += sizeof(struct ImgTgaHeader); + //img_tga_header_print(h); + + // Image descriptor not currently accounted for + assert(h.id_length == 0); + // Color maps not currently supported + assert(h.color_map_type == 0); + // Only RGB is supported + assert(h.data_type_code == 2 || h.data_type_code == 10); + assert(h.bits_per_pixel <= 32); + + if (out_width) + { + *out_width = h.width; + } + if (out_height) + { + *out_height = h.height; + } + + u8 bytes_per_pixel = h.bits_per_pixel / 8; + size_t bytes_per_row = h.width * bytes_per_pixel; + size_t img_data_buf_len = h.height * bytes_per_row; + u8 *img_data = mem_st_alloc_buffer(allocator, u8, img_data_buf_len); + assert(img_data); + + if (h.data_type_code == 2) + { + // uncompressed RGB + for (size_t b = 0; b < img_data_buf_len; b++) + { + img_data[b] = img_consume_byte(&buffer, buf_end); + } + } + else if (h.data_type_code == 10) + { + // Run Length Encoded RGB + size_t img_bytes_read = 0; + while (img_bytes_read < img_data_buf_len) + { + u8 packet_header = img_consume_byte(&buffer, buf_end); + u8 run_length_header_bit = packet_header >> 7; + u32 run_count = (packet_header & (~(1 << 7))) + 1; + assert(run_count <= 128); + u32 run_length_in_bytes = run_count * bytes_per_pixel; + + if (run_length_header_bit) + { + u8 pixel[4] = {0}; + for (u32 b = 0; b < bytes_per_pixel; b++) + { + pixel[b] = img_consume_byte(&buffer, buf_end); + } + for (u32 b = 0; b < run_length_in_bytes; b++) + { + img_data[img_bytes_read + b] = pixel[b % bytes_per_pixel]; + } + img_bytes_read += run_length_in_bytes; + } + else + { + for (u32 b = 0; b < run_length_in_bytes; b++) + { + img_data[img_bytes_read + b] = img_consume_byte(&buffer, buf_end); + } + img_bytes_read += run_length_in_bytes; + } + } + } + + // NOTE(amin): we convert BGRA to RGBA because OpenGL ES and WebGL don't + // support the GL_BGRA texture format. + for (size_t b = 0; b < img_data_buf_len; b += 4) + { + u8 temp = img_data[b]; + img_data[b + 0] = img_data[b + 2]; + img_data[b + 2] = temp; + } + + // NOTE(amin): Intentional truncating division. If height is odd, we want + // our last row offset to be 1 less than the middle row. + u32 half_height = h.height / 2; + for (size_t row_offset = 0; row_offset < half_height; row_offset++) + { + u8 *top_row = &img_data[row_offset * bytes_per_row]; + u8 *bot_row = &img_data[(h.height - row_offset - 1) * bytes_per_row]; + for (size_t b = 0; b < bytes_per_row; b++) + { + u8 temp = top_row[b]; + top_row[b] = bot_row[b]; + bot_row[b] = temp; + } + } + return img_data; +} diff --git a/src/image.h b/src/image.h @@ -0,0 +1,25 @@ +struct Image +{ + v2u dim; + u8 *data; +}; + +#pragma pack(push, 1) +struct ImgTgaHeader +{ + // http://paulbourke.net/dataformats/tga/ + u8 id_length; + u8 color_map_type; + u8 data_type_code; + u16 color_map_origin; + u16 color_map_length; + u8 color_map_depth; + u16 x_origin; + u16 y_origin; + u16 width; + u16 height; + u8 bits_per_pixel; + u8 image_descriptor; +}; +#pragma pack(pop) +static_assert(sizeof(struct ImgTgaHeader) == 18, ""); diff --git a/src/memory.c b/src/memory.c @@ -38,7 +38,10 @@ internal size_t mem_st_get_marker(struct StackAllocator *allocator) internal void mem_st_free_to_marker(struct StackAllocator *allocator, size_t marker) { - allocator->used = marker; + if (allocator->used > marker) + { + allocator->used = marker; + } } internal void mem_st_free_all(struct StackAllocator *allocator) diff --git a/src/memory.h b/src/memory.h @@ -4,3 +4,6 @@ struct StackAllocator size_t size; size_t used; }; + +#define KIBIBYTES(n) (n) * 1024LL +#define MEBIBYTES(n) KIBIBYTES((n) * 1024LL) diff --git a/src/platform.h b/src/platform.h @@ -2,10 +2,7 @@ #include "types.h" -#define KIBIBYTES(n) (n) * 1024LL -#define MEBIBYTES(n) KIBIBYTES((n) * 1024LL) - -#define PLATFORM_READ_ENTIRE_FILE(name) char *(name)(char *file_path) +#define PLATFORM_READ_ENTIRE_FILE(name) u8 *(name)(char *file_path, size_t *out_num_bytes) typedef PLATFORM_READ_ENTIRE_FILE(platform_read_entire_file_func); #define PLATFORM_MEMORY_FREE(name) void (name)(void *ptr) diff --git a/src/platform_linux.c b/src/platform_linux.c @@ -210,7 +210,7 @@ PLATFORM_MEMORY_FREE(linux_memory_free) internal PLATFORM_READ_ENTIRE_FILE(linux_read_entire_file) { FILE *handle = fopen(file_path, "r"); - char *buffer = NULL; + u8 *buffer = NULL; if (handle) { @@ -219,10 +219,15 @@ internal PLATFORM_READ_ENTIRE_FILE(linux_read_entire_file) u32 num_bytes_in_file = ftell(handle); rewind(handle); + if (out_num_bytes) + { + *out_num_bytes = num_bytes_in_file; + } + // TODO: replace malloc with own allocator so I stop having nightmares - buffer = (char*) malloc(sizeof(char) * (num_bytes_in_file + 1) ); + buffer = malloc(sizeof(u8) * (num_bytes_in_file + 1) ); - u32 bytes_read = fread(buffer, sizeof(char), num_bytes_in_file, handle); + u32 bytes_read = fread(buffer, sizeof(u8), num_bytes_in_file, handle); // IMPORTANT! fread() doesn't add the '\0' buffer[num_bytes_in_file] = '\0'; diff --git a/src/platform_wasm.c b/src/platform_wasm.c @@ -30,7 +30,8 @@ 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}; +// NOTE: If this buffer is too small, the browser will freeze up +global_variable u8 g_mem_buffer[10000] = {0}; global_variable i32 g_mem_buffer_i = 0; global_variable u32 previous_time = 0; @@ -115,13 +116,15 @@ PLATFORM_MEMORY_FREE(wasm_memory_free) // do nothing } +// TODO: robustly return file length 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)) + u8 *buf = &g_mem_buffer[g_mem_buffer_i]; + i32 file_data_length = js_read_entire_file((i32)file_path, str_length(file_path), buf); + if(file_data_length) { - i32 file_data_length = str_length(buf); + *out_num_bytes = file_data_length; g_mem_buffer_i += file_data_length; g_mem_buffer[g_mem_buffer_i] = '\0'; wasm_print("Succeeded in reading file."); diff --git a/src/platform_wasm.h b/src/platform_wasm.h @@ -5,5 +5,5 @@ 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); +i32 js_read_entire_file(i32 file_path, i32 name_len, u8 *file_data); +i32 js_print(i32 string, i32 len); diff --git a/src/platform_wasm_js_symbols.txt b/src/platform_wasm_js_symbols.txt @@ -1,6 +1,8 @@ +webglActiveTexture webglAttachShader webglBindBuffer webglBindVertexArray +webglBindTexture webglBlendColor webglBlendFunc webglBufferData @@ -10,6 +12,7 @@ webglCompileShader webglCreateBuffer webglCreateProgram webglCreateShader +webglCreateTexture webglCreateVertexArray webglDeleteBuffer webglDeleteShader @@ -19,6 +22,7 @@ webglDisable webglDrawElements webglEnable webglEnableVertexAttribArray +webglGenerateMipmap webglGetProgramInfoLog webglGetProgramParameter webglGetShaderInfoLog @@ -26,6 +30,8 @@ webglGetShaderParameter webglGetUniformLocation webglLinkProgram webglShaderSource +webglTexImage2D +webglTexParameteri webglUniform1f webglUniform1i webglUniform3f diff --git a/src/platform_wasm_loader.js b/src/platform_wasm_loader.js @@ -1,3 +1,4 @@ +'use strict'; let utf8decoder = new TextDecoder("utf-8"); let memory = null; let exports = {}; @@ -20,6 +21,9 @@ const key_numeric_codes = { KeyS: 4, F11: 5, }; +imports["webglActiveTexture"] = function(texture) { + gl.activeTexture(texture); +} imports["webglAttachShader"] = function(program_id, shader_id) { let program = gl_id_map[program_id]; let shader = gl_id_map[shader_id]; @@ -29,6 +33,10 @@ imports["webglBindBuffer"] = function(target, buffer_id) { let buffer = gl_id_map[buffer_id]; gl.bindBuffer(target, buffer); } +imports["webglBindTexture"] = function(target, texture_id) { + let texture = gl_id_map[texture_id]; + gl.bindTexture(target, texture); +} imports["webglBindVertexArray"] = function(vao_id) { let vao = gl_id_map[vao_id]; gl.bindVertexArray(vao); @@ -68,6 +76,11 @@ imports["webglCreateShader"] = function(type) { let shader_id = webgl_id_new(shader); return shader_id; } +imports["webglCreateTexture"] = function(type) { + let texture = gl.createTexture(); + let texture_id = webgl_id_new(texture); + return texture_id; +} imports["webglCreateVertexArray"] = function() { let vao = gl.createVertexArray() let vao_id = webgl_id_new(vao); @@ -103,6 +116,9 @@ imports["webglEnable"] = function(cap) { imports["webglEnableVertexAttribArray"] = function(index) { gl.enableVertexAttribArray(index); } +imports["webglGenerateMipmap"] = function(target) { + gl.generateMipmap(target); +} imports["webglGetProgramInfoLog"] = function() { } imports["webglGetProgramParameter"] = function(program_id, param) { @@ -142,6 +158,14 @@ imports["webglShaderSource"] = function(shader_id, source_ptr, source_len) { let s = utf8decoder.decode(arr); gl.shaderSource(shader, s); } +imports["webglTexImage2D"] = function(target, level, internalformat, width, height, border, format, type, pixels) { + const bytes_per_pixel = 4; + let dataslice = memory.subarray(pixels, pixels + (width * height * bytes_per_pixel)); + gl.texImage2D(target, level, internalformat, width, height, border, format, type, dataslice); +} +imports["webglTexParameteri"] = function(target, pname, param) { + gl.texParameteri(target, pname, param); +} imports["webglUniform1f"] = function(location_id, value) { let loc = gl_id_map[location_id]; gl['uniform1f'](loc, value); @@ -169,19 +193,23 @@ imports["webglVertexAttribPointer"] = function(index, size, type, normalized, st imports["webglViewport"] = function(x, y, width, height) { gl.viewport(x, y, width, height); } -imports["js_read_entire_file"] = function(name, name_len, out_buf) { +imports["js_read_entire_file"] = function(name, name_len, out_buf, out_len) { let file_name = utf8decoder.decode(memory.subarray(name, name + name_len)) + let file_size = 0; if (file_name == "shader/main_f.glsl") { var file = files[1]; } else if (file_name == "shader/main_v.glsl") { var file = files[2]; + } else if (file_name == "assets/tile0.tga") { + var file = files[3]; } else { - return false; + return 0; } let arr = memory.subarray(out_buf, out_buf + file.byteLength); + file_size = arr.length * arr.BYTES_PER_ELEMENT; let s = String.fromCharCode.apply(null, arr); arr.set(new Uint8Array(file)); - return true; + return file_size; } imports["js_print"] = function(s, len) { let arr = memory.subarray(s, s + len); @@ -233,6 +261,10 @@ function file_load(name) { function on_key_event(e, key_is_down) { let sane_numeric_code = key_numeric_codes[e.code]; if (sane_numeric_code !== undefined) { + if (sane_numeric_code === 5) { + // Don't toggle full screen + e.preventDefault(); + } exports['key_callback'](sane_numeric_code, key_is_down); } } @@ -251,6 +283,7 @@ window.onload = async function() { files[0] = file_load("binary.wasm"); files[1] = file_load("shader/main_f.glsl"); files[2] = file_load("shader/main_v.glsl"); + files[3] = file_load("assets/tile0.tga"); for(var i = 0; i < files.length; i++) { files[i] = await files[i]; } diff --git a/src/platform_windows.c b/src/platform_windows.c @@ -174,7 +174,7 @@ PLATFORM_MEMORY_FREE(windows_memory_free) internal PLATFORM_READ_ENTIRE_FILE(windows_read_entire_file) { FILE *handle = fopen(file_path, "rb"); - char *buffer = NULL; + u8 *buffer = NULL; if (handle) { @@ -183,10 +183,15 @@ internal PLATFORM_READ_ENTIRE_FILE(windows_read_entire_file) u32 num_bytes_in_file = ftell(handle); rewind(handle); + if (out_num_bytes) + { + *out_num_bytes = num_bytes_in_file; + } + // TODO: replace malloc with own allocator so I stop having nightmares - buffer = (char*) malloc(sizeof(char) * (num_bytes_in_file + 1) ); + buffer = malloc(sizeof(u8) * (num_bytes_in_file + 1)); - u32 bytes_read = fread(buffer, sizeof(char), num_bytes_in_file, handle); + u32 bytes_read = fread(buffer, sizeof(u8), num_bytes_in_file, handle); // IMPORTANT! fread() doesn't add the '\0' buffer[num_bytes_in_file] = '\0'; diff --git a/src/render.c b/src/render.c @@ -1,14 +1,14 @@ -internal void renderer_init(struct RendererState *renderer, struct StackAllocator *allocator) +internal void renderer_init(struct RendererState *renderer, struct Image *images, size_t num_images, struct StackAllocator *allocator) { GLfloat quad_vertices[] = { - 0.5f, 0.5f, - 0.5f, -0.5f, - -0.5f, -0.5f, - -0.5f, 0.5f, + 0.5f, 0.5f, 1.0f, 0.0f, + 0.5f, -0.5f, 1.0f, 1.0f, + -0.5f, -0.5f, 0.0f, 1.0f, + -0.5f, 0.5f, 0.0f, 0.0f, }; - GLuint quad_elements[] = { 0, 1, 3, 1, 2, 3 }; - GLuint rect_elements[] = { 0, 1, 1, 2, 2, 3, 3, 0 }; + GLuint quad_elements[] = {0, 1, 3, 1, 2, 3}; + GLuint rect_elements[] = {0, 1, 1, 2, 2, 3, 3, 0}; GLuint vao; GLuint quad_vbo; @@ -30,11 +30,15 @@ internal void renderer_init(struct RendererState *renderer, struct StackAllocato 2, // Number of values GL_FLOAT, // Data type GL_FALSE, // Normalize data - 2 * sizeof(GLfloat), // Stride + 4 * sizeof(GLfloat), // Stride (GLvoid*)0 // Position data offset (0) ); - glEnableVertexAttribArray(0); + + // texture coordinates + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (GLvoid*)(2 * sizeof(GLfloat))); + glEnableVertexAttribArray(1); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, quad_ebo); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(quad_elements), quad_elements, GL_STATIC_DRAW); @@ -43,12 +47,31 @@ internal void renderer_init(struct RendererState *renderer, struct StackAllocato glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, quad_ebo); + for (u32 i = 0; i < num_images; i++) + { + struct Image img = images[i]; + u32 texture_id; + glGenTextures(1, &texture_id); + glBindTexture(GL_TEXTURE_2D, texture_id); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, img.dim.width, img.dim.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, img.data); + glGenerateMipmap(GL_TEXTURE_2D); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glActiveTexture(GL_TEXTURE0); + } + renderer->vao = vao; renderer->quad_vbo = quad_vbo; renderer->quad_ebo = quad_ebo; renderer->rect_ebo = rect_ebo; renderer->queue = mem_st_alloc_buffer(allocator, struct RenderJob, RENDER_QUEUE_SIZE); renderer->queue_count = 0; + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } internal void renderer_jobs_draw(struct RendererState *renderer) @@ -78,6 +101,7 @@ internal void renderer_jobs_draw(struct RendererState *renderer) } shader_setv3(&renderer->shader, SHADER_UNIFORM_COLOR, &j.color); shader_setm4(&renderer->shader, SHADER_UNIFORM_MODEL, &j.model); + shader_setf(&renderer->shader, SHADER_UNIFORM_TEX_INTERP, j.tex_interp); if (j.ebo == renderer->quad_ebo) { glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); diff --git a/src/render.h b/src/render.h @@ -9,11 +9,13 @@ enum RenderLayer NUM_RENDER_LAYERS, }; +// TODO: optimize packing struct RenderJob { GLuint ebo; v3 color; m4 model; + f32 tex_interp; enum RenderLayer layer; }; diff --git a/src/shader.c b/src/shader.c @@ -168,6 +168,9 @@ char *shader_uniform_get_name(enum ShaderUniform u) case SHADER_UNIFORM_COLOR: name = "color"; break; + case SHADER_UNIFORM_TEX_INTERP: + name = "tex_interp"; + break; case NUM_SHADER_UNIFORMS: // fallthrough default: diff --git a/src/shader.h b/src/shader.h @@ -4,6 +4,7 @@ enum ShaderUniform SHADER_UNIFORM_VIEW, SHADER_UNIFORM_PROJECTION, SHADER_UNIFORM_COLOR, + SHADER_UNIFORM_TEX_INTERP, NUM_SHADER_UNIFORMS, }; diff --git a/src/webgl.h b/src/webgl.h @@ -6,8 +6,10 @@ typedef double f64; // NOTE(amin): Since these functions will be implemented in javascript, we can // only use i32, f32, and f64 params. +void webglActiveTexture(i32 texture); void webglAttachShader(i32 program, i32 shader); void webglBindBuffer(i32 target, i32 buffer); +void webglBindTexture(i32 target, i32 texture); void webglBindVertexArray(i32 vao); void webglBlendColor(f32 r, f32 g, f32 b, f32 a); void webglBlendFunc(i32 sfactor, i32 dfactor); @@ -18,6 +20,7 @@ void webglCompileShader(i32 shader); i32 webglCreateBuffer(void); i32 webglCreateProgram(void); i32 webglCreateShader(i32 type); +i32 webglCreateTexture(void); i32 webglCreateVertexArray(void); void webglDeleteBuffer(i32 bo); void webglDeleteShader(i32 shader); @@ -27,6 +30,7 @@ void webglDisable(i32 cap); void webglDrawElements(i32 mode, i32 count, i32 type, i32 offset); void webglEnable(i32 cap); void webglEnableVertexAttribArray(i32 index); +void webglGenerateMipmap(i32 target); void webglGetProgramInfoLog(void); int webglGetProgramParameter(i32 program, i32 param); void webglGetShaderInfoLog(i32 shader, char *out_buf); @@ -34,6 +38,8 @@ 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 webglTexImage2D(i32 target, i32 level, i32 internalformat, i32 width, i32 height, i32 border, i32 format, i32 type, i32 pixels); +void webglTexParameteri(i32 target, i32 pname, i32 param); void webglUniform1f(i32 location, f32 value); void webglUniform1i(i32 location, i32 value); void webglUniform3f(i32 location, f32 x, f32 y, f32 z); @@ -49,13 +55,31 @@ void webglViewport(i32 x, i32 y, i32 width, i32 height); #define GL_LINES 0x0001 #define GL_TRIANGLES 0x0004 #define GL_SRC_ALPHA 0x0302 +#define GL_ONE_MINUS_SRC_ALPHA 0x0303 #define GL_FRONT_AND_BACK 0x0408 #define GL_DEPTH_TEST 0x0B71 #define GL_BLEND 0x0BE2 +#define GL_TEXTURE_2D 0x0DE1 +#define GL_UNSIGNED_BYTE 0x1401 #define GL_UNSIGNED_INT 0x1405 #define GL_FLOAT 0x1406 +#define GL_RGBA 0x1908 #define GL_FILL 0x1B02 +#define GL_LINEAR 0x2601 +#define GL_LINEAR_MIPMAP_LINEAR 0x2703 +#define GL_TEXTURE_MAG_FILTER 0x2800 +#define GL_TEXTURE_MIN_FILTER 0x2801 +#define GL_TEXTURE_WRAP_S 0x2802 +#define GL_TEXTURE_WRAP_T 0x2803 #define GL_CONSTANT_ALPHA 0x8003 +// NOTE(amin): this should normally be: `#define GL_BGRA 0x80E1`, however +// OpenGL ES 3.0, and consequently WebGL 2.0, doesn't support GL_BGRA, so we +// resort to this hideous hack just to get things working, along with a texture +// swizzle in the shader. +// TODO(amin): fix this in a better way +#define GL_BGRA GL_RGBA +#define GL_CLAMP_TO_EDGE 0x812F +#define GL_TEXTURE0 0x84C0 #define GL_ARRAY_BUFFER 0x8892 #define GL_ELEMENT_ARRAY_BUFFER 0x8893 #define GL_STATIC_DRAW 0x88E4 @@ -88,6 +112,11 @@ static inline i32 _webgl_strlen(const char *str) return len; } +void glActiveTexture(GLenum texture) +{ + webglActiveTexture(WEBGL_CAST_I32(texture)); +} + inline void glAttachShader(GLuint program, GLuint shader) { webglAttachShader(WEBGL_CAST_I32(program), WEBGL_CAST_I32(shader)); @@ -98,6 +127,11 @@ inline void glBindBuffer(GLenum target, GLuint buffer) webglBindBuffer(WEBGL_CAST_I32(target), WEBGL_CAST_I32(buffer)); } +void glBindTexture(GLenum target, GLuint texture) +{ + webglBindTexture(WEBGL_CAST_I32(target), WEBGL_CAST_I32(texture)); +} + inline void glBindVertexArray(GLuint array) { webglBindVertexArray(WEBGL_CAST_I32(array)); @@ -196,6 +230,18 @@ inline void glGenBuffers(GLsizei n, GLuint *buffers) *buffers = (GLuint)buffer_id; } +void glGenerateMipmap(GLenum target) +{ + webglGenerateMipmap(WEBGL_CAST_I32(target)); +} + +void glGenTextures(GLsizei n, GLuint *textures) +{ + assert(n == 1); + i32 texture_id = webglCreateTexture(); + *textures = (GLuint)texture_id; +} + inline void glGenVertexArrays(GLsizei n, GLuint *arrays) { assert(n == 1); @@ -248,6 +294,25 @@ inline void glShaderSource(GLuint shader, GLsizei count, const GLchar *const *st webglShaderSource(WEBGL_CAST_I32(shader), s, l); } +void glTexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels) +{ + webglTexImage2D( + WEBGL_CAST_I32(target), + WEBGL_CAST_I32(level), + WEBGL_CAST_I32(internalformat), + WEBGL_CAST_I32(width), + WEBGL_CAST_I32(height), + WEBGL_CAST_I32(border), + WEBGL_CAST_I32(format), + WEBGL_CAST_I32(type), + WEBGL_CAST_I32(pixels)); +} + +void glTexParameteri(GLenum target, GLenum pname, GLint param) +{ + webglTexParameteri(WEBGL_CAST_I32(target), WEBGL_CAST_I32(pname), WEBGL_CAST_I32(param)); +} + inline void glUniform1f(GLint location, GLfloat v0) { webglUniform1f(WEBGL_CAST_I32(location), (f32)v0);