a-game

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

commit c4077d84754b08872a16fde5d4e962aad6268f73
parent fbf146edf989a236edea2b95bf4eacc75e9cd106
Author: amin <dev@aminmesbah.com>
Date:   Wed, 17 Apr 2019 02:37:24 +0000

Port to Windows

Compiles as long as `..\include\glfw\` exists. We'll fix that.

FossilOrigin-Name: 92f335710a8f24153c294eadc3926a8af65a1a0d0aec144de9b872a3853d1b88
Diffstat:
Abuild.bat | 12++++++++++++
Asrc/platform_windows.c | 294+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/platform_windows.h | 18++++++++++++++++++
Msrc/shader.h | 1-
4 files changed, 324 insertions(+), 1 deletion(-)

diff --git a/build.bat b/build.bat @@ -0,0 +1,12 @@ +@echo off + +set CFLAGS=-std=c99 -nostdlib -D_CRT_SECURE_NO_WARNINGS -Ilib -I../include/glfw/include -Wall -Wextra -Wshadow -Wswitch-enum -Wno-unused-parameter -Wno-unused-function -Wno-missing-braces +set LDFLAGS=-luser32 -lgdi32 -lwinmm -lopengl32 -lshell32 +set RELCFLAGS=-O2 -Os + +IF NOT EXIST ".\out" mkdir ".\out" +IF NOT EXIST ".\out\release" mkdir ".\out\release" +clang %CFLAGS% %RELCFLAGS%^ + src\platform_windows.c^ + ..\include\glfw\lib-vc2015\glfw3.lib^ + -o .\out\release\a-game.exe %LDFLAGS% diff --git a/src/platform_windows.c b/src/platform_windows.c @@ -0,0 +1,294 @@ +#include <time.h> + +#include <glad/glad.h> +#include <GLFW/glfw3.h> + +// LOL, Windef.h +#undef near +#undef far + +#include "game.c" +#include "platform.h" +#include "platform_windows.h" + +int main(void) +{ + glfwSetErrorCallback(error_callback); + + if (!glfwInit()) + { + fprintf(stderr, "GLFW initialization failed\n"); + return -1; + } + + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + + GLFWwindow* window = glfwCreateWindow(PLATFORM_SCR_WIDTH, PLATFORM_SCR_HEIGHT, "A Game", NULL, NULL); + if (!window) + { + fprintf(stderr, "GLFW window creation failed\n"); + glfwTerminate(); + return -1; + } + glfwMakeContextCurrent(window); + glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); + glfwSetKeyCallback(window, key_callback); + + if (!gladLoadGLLoader((GLADloadproc) glfwGetProcAddress)) + { + fprintf(stderr, "glad initialization failed\n"); + glfwDestroyWindow(window); + glfwTerminate(); + return -1; + } + +#ifdef USE_TEST_SEED + srand((uint32_t)0); +#else + srand((uint32_t)time(NULL)); +#endif + + struct GameState game_state = {0}; + struct GameMemory game_memory = { + .game_state = &game_state, + .platform = { + &windows_read_entire_file, + }, + }; + + struct GameInput game_input = {0}; + glfwSetWindowUserPointer(window, &game_input); + game_init(&game_memory, (v2u) {PLATFORM_SCR_WIDTH, PLATFORM_SCR_HEIGHT}); + +#ifdef PLATFORM_HOTLOAD_GAME_CODE + struct GameCode game_code = load_game_code(PLATFORM_GAME_LIB_PATH); + game_code.game_load_opengl_symbols(); +#endif + + // NOTE(amin): lag should always be very small, so it's fine to use a float + // to store it. We will not run out of precision. + uint64_t previous_wall_clock_time = glfwGetTimerValue(); + uint64_t timer_frequency = glfwGetTimerFrequency(); + + while (!glfwWindowShouldClose(window)) + { + uint64_t current_wall_clock_time = glfwGetTimerValue(); + + { + uint64_t previous_frame_duration = current_wall_clock_time - previous_wall_clock_time; + previous_wall_clock_time = current_wall_clock_time; + f32 dt = (float)previous_frame_duration / (float)timer_frequency; + // TODO: Debug periodic dropped frame on linux when fullscreen + //printf("ms per frame: %f\n", dt * 1000.0f); + game_input.dt = dt; + } + + int32_t framebuffer_width = PLATFORM_SCR_WIDTH; + int32_t framebuffer_height = PLATFORM_SCR_HEIGHT; + glfwGetFramebufferSize(window, &framebuffer_width, &framebuffer_height); + assert(framebuffer_width >= 0); + assert(framebuffer_height >= 0); + v2u framebuffer = {framebuffer_width, framebuffer_height}; + +#ifdef PLATFORM_HOTLOAD_GAME_CODE + time_t last_write_time = file_get_modified_time(PLATFORM_GAME_LIB_PATH); + bool game_code_has_changed = last_write_time && (last_write_time != game_code.last_write_time); + if (game_code_has_changed) + { + unload_game_code(&game_code); + game_code = load_game_code(PLATFORM_GAME_LIB_PATH); + game_code.game_load_opengl_symbols(); + if (!game_code.is_valid) + { + // TODO: fall back to backup? + } + } + game_code.game_update_and_render(&game_memory, &game_input, framebuffer); +#else + game_update_and_render(&game_memory, &game_input, framebuffer); +#endif // PLATFORM_HOTLOAD_GAME_CODE + + // TODO: Do something sane when vsync is disabled + glfwSwapBuffers(window); + glfwPollEvents(); + } + + game_cleanup(&game_memory); + + glfwDestroyWindow(window); + glfwTerminate(); + return 0; +} + +internal void error_callback(int error, const char* description) +{ + fprintf(stderr, "Error: %s\n", description); +} + +internal void framebuffer_size_callback(GLFWwindow* window, int width, int height) +{ + glViewport(0, 0, width, height); +} + +internal void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) +{ + struct GameInput *game_input = (struct GameInput*)glfwGetWindowUserPointer(window); + + enum InputKeyAction game_input_key_action = 0; + switch (action) + { + case GLFW_PRESS: + game_input_key_action = KEY_PRESS; + break; + case GLFW_RELEASE: + game_input_key_action = KEY_RELEASE; + break; + case GLFW_REPEAT: + game_input_key_action = KEY_REPEAT; + break; + default: + exit(1); + break; + } + + enum InputKeyAction* game_key = NULL; + switch (key) + { + case GLFW_KEY_LEFT: + game_key = &(game_input->key_left); + break; + case GLFW_KEY_RIGHT: + game_key = &(game_input->key_right); + break; + case GLFW_KEY_UP: + game_key = &(game_input->key_up); + break; + case GLFW_KEY_DOWN: + game_key = &(game_input->key_down); + break; + default: + break; + } + + if (game_key) + { + *game_key = game_input_key_action; + } +} + +//internal time_t file_get_modified_time(char *file_path) +//{ +// time_t mtime = 0; +// struct stat file_status = {0}; +// if (stat(file_path, &file_status) == 0) +// { +// //printf("File: %s last modified: %s\n", file_path, ctime(&file_status.st_mtime)); +// mtime = file_status.st_mtime; +// } +// else +// { +// fprintf(stderr, "ERROR: Failed to stat file: %s\n", file_path); +// } +// return mtime; +//} + +internal PLATFORM_READ_ENTIRE_FILE(windows_read_entire_file) +{ + FILE *handle = fopen(file_path, "rb"); + char *buffer = NULL; + + if (handle) + { + // get file size + fseek(handle, 0, SEEK_END); + u32 num_bytes_in_file = ftell(handle); + rewind(handle); + + // TODO: replace malloc with own allocator so I stop having nightmares + buffer = (char*) malloc(sizeof(char) * (num_bytes_in_file + 1) ); + + u32 bytes_read = fread(buffer, sizeof(char), num_bytes_in_file, handle); + // IMPORTANT! fread() doesn't add the '\0' + buffer[num_bytes_in_file] = '\0'; + + if (num_bytes_in_file != bytes_read) + { + free(buffer); + buffer = NULL; + } + + fclose(handle); + } + else + { + printf("Error: Couldn't open file at path: %s", file_path); + // TODO: handle errors here in a better way + exit(1); + } + + return buffer; +} + +#ifdef PLATFORM_HOTLOAD_GAME_CODE +internal void unload_game_code(struct GameCode *game_code) +{ + if (!game_code) + { + fprintf(stderr, "ERROR: Invalid pointer *game_code\n"); + return; + } + + if (game_code->game_code_library) + { + dlclose(game_code->game_code_library); + game_code->game_code_library = NULL; + } + game_code->is_valid = false; + game_code->game_load_opengl_symbols = NULL; + game_code->game_update_and_render = NULL; +} + + +// TODO: Add backup dll in case loading fails +internal struct GameCode load_game_code(char *source_lib_path) +{ + struct GameCode game_code = {0}; + + game_code.last_write_time = file_get_modified_time(source_lib_path); + if (game_code.last_write_time) + { + game_code.game_code_library = dlopen(source_lib_path, RTLD_LAZY); + if (game_code.game_code_library) + { + // NOTE: The C standard (as of C99) distinguishes function pointers + // from object pointers (`void *` is an object pointer). Technically it + // is undefined behavior to cast between these two pointer types. In + // practice, it works on most modern platforms. + // + // In this case, we are protected by POSIX, which specifies that + // function and data pointers must be the same size. We will only ever + // be using dlsym on POSIX-compliant platforms. + game_code.game_load_opengl_symbols = (game_load_opengl_symbols_func *) dlsym(game_code.game_code_library, "game_load_opengl_symbols"); + game_code.game_update_and_render = (game_update_and_render_func *) dlsym(game_code.game_code_library, "game_update_and_render"); + game_code.is_valid = (game_code.game_load_opengl_symbols && game_code.game_update_and_render); + } + } + + if (!game_code.is_valid) + { + fprintf(stderr, "ERROR: Game code is not valid: %s\n", dlerror()); + } + + return game_code; +} +#endif // PLATFORM_HOTLOAD_GAME_CODE + +#ifdef GLAD_DEBUG +internal void pre_gl_callback(const char *func_name, void *func_ptr, int len_args, ...) +{ + printf("Calling: %s (%d arguments)\n", func_name, len_args); +} +#endif diff --git a/src/platform_windows.h b/src/platform_windows.h @@ -0,0 +1,18 @@ +#define PLATFORM_SCR_WIDTH 600u +#define PLATFORM_SCR_HEIGHT 600u + +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); + +#ifdef PLATFORM_HOTLOAD_GAME_CODE +#define PLATFORM_GAME_LIB_PATH "./out/release/game.so" +internal time_t file_get_modified_time(char *file_path); +internal void unload_game_code(struct GameCode *game_code); +internal struct GameCode load_game_code(char *source_lib_path); +#endif + +#ifdef GLAD_DEBUG +internal void pre_gl_callback(const char *func_name, void *func_ptr, int len_args, ...); +#endif diff --git a/src/shader.h b/src/shader.h @@ -3,7 +3,6 @@ struct Shader u32 program; }; -internal char *read_file(char *file_path); internal struct Shader shader_compile(const GLchar *vertex_shader_source, const GLchar *fragment_shader_source); internal void shader_use(struct Shader *s); internal void shader_setb(struct Shader *s, char *name, bool value);