transparent-cube

Minimal cross-platform native/wasm graphics example.
git clone git://git.amin.space/transparent-cube.git
Log | Files | Refs | README | LICENSE

platform_linux.c (7854B)


      1 #include <inttypes.h>
      2 #include <stdarg.h>
      3 #include <stdint.h>
      4 #include <stdio.h>
      5 #include <time.h>
      6 
      7 #include <dlfcn.h>
      8 #include <sys/stat.h>
      9 
     10 #include <glad/glad.h>
     11 #include <GLFW/glfw3.h>
     12 
     13 #include "game.c"
     14 #include "platform.h"
     15 #include "platform_linux.h"
     16 
     17 void error_callback(int error, const char* description);
     18 void framebuffer_size_callback(GLFWwindow* window, int width, int height);
     19 
     20 #ifdef PLATFORM_HOTLOAD_GAME_CODE
     21 time_t file_get_modified_time(char *file_path);
     22 void unload_game_code(struct GameCode *game_code);
     23 struct GameCode load_game_code(char *source_lib_path);
     24 #endif // PLATFORM_HOTLOAD_GAME_CODE
     25 
     26 #ifdef GLAD_DEBUG
     27 void pre_gl_callback(const char *func_name, void *func_ptr, int len_args, ...)
     28 {
     29     printf("Calling: %s (%d arguments)\n", func_name, len_args);
     30 }
     31 #endif // GLAD_DEBUG
     32 
     33 
     34 int main(void)
     35 {
     36     glfwSetErrorCallback(error_callback);
     37 
     38     if (!glfwInit())
     39     {
     40         fprintf(stderr, "GLFW initialization failed\n");
     41         return -1;
     42     }
     43 
     44     glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
     45     glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
     46     glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
     47     glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
     48     glfwWindowHint(GLFW_SAMPLES, 4);
     49 
     50     GLFWwindow* window = glfwCreateWindow(PLATFORM_SCR_WIDTH, PLATFORM_SCR_HEIGHT, "Quaternion Demo", NULL, NULL);
     51     if (!window)
     52     {
     53         fprintf(stderr, "GLFW window creation failed\n");
     54         glfwTerminate();
     55         return -1;
     56     }
     57     glfwMakeContextCurrent(window);
     58     glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
     59     glfwSwapInterval(1);
     60 
     61     if (!gladLoadGLLoader((GLADloadproc) glfwGetProcAddress))
     62     {
     63         fprintf(stderr, "glad initialization failed\n");
     64         glfwDestroyWindow(window);
     65         glfwTerminate();
     66         return -1;
     67     }
     68 
     69 #ifdef USE_TEST_SEED
     70     srand((uint32_t)0);
     71 #else
     72     srand((uint32_t)time(NULL));
     73 #endif // USE_TEST_SEED
     74 
     75     struct GameState game_state = {0};
     76     game_state.platform.platform_read_entire_file = &linux_read_entire_file;
     77     game_state.platform.platform_print = &linux_print;
     78     game_state.platform.platform_memory_free = &linux_memory_free;
     79 
     80     game_init(&game_state, PLATFORM_SCR_WIDTH, PLATFORM_SCR_HEIGHT);
     81 
     82 #ifdef PLATFORM_HOTLOAD_GAME_CODE
     83     struct GameCode game_code = load_game_code(PLATFORM_GAME_LIB_PATH);
     84     game_code.game_load_opengl_symbols();
     85 #endif // PLATFORM_HOTLOAD_GAME_CODE
     86 
     87     uint64_t lag = 0;
     88     uint64_t previous_ms = (glfwGetTimerValue() * PLATFORM_SECOND) / glfwGetTimerFrequency();
     89 
     90     while (!glfwWindowShouldClose(window))
     91     {
     92         uint64_t current_ms = (glfwGetTimerValue() * PLATFORM_SECOND) / glfwGetTimerFrequency();
     93         uint64_t elapsed_ms = current_ms - previous_ms;
     94         previous_ms = current_ms;
     95         lag += elapsed_ms;
     96         //printf("%" PRIu64 ", %" PRIu64 ", %f\n", elapsed_ms, lag, PLATFORM_MS_PER_UPDATE);
     97 
     98         int32_t framebuffer_width = PLATFORM_SCR_WIDTH;
     99         int32_t framebuffer_height = PLATFORM_SCR_HEIGHT;
    100         glfwGetFramebufferSize(window, &framebuffer_width, &framebuffer_height);
    101 
    102 #ifdef PLATFORM_HOTLOAD_GAME_CODE
    103         time_t last_write_time = file_get_modified_time(PLATFORM_GAME_LIB_PATH);
    104         bool game_code_has_changed = last_write_time && (last_write_time != game_code.last_write_time);
    105         if (game_code_has_changed)
    106         {
    107             unload_game_code(&game_code);
    108             game_code = load_game_code(PLATFORM_GAME_LIB_PATH);
    109             game_code.game_load_opengl_symbols();
    110             if (!game_code.is_valid)
    111             {
    112                 // TODO: fall back to backup?
    113             }
    114         }
    115         game_code.game_update_and_render(&game_state, lag/PLATFORM_SECOND, framebuffer_width, framebuffer_height);
    116 #else
    117         game_update_and_render(&game_state, lag/PLATFORM_SECOND, framebuffer_width, framebuffer_height);
    118 #endif // PLATFORM_HOTLOAD_GAME_CODE
    119 
    120         glfwSwapBuffers(window);
    121         glfwPollEvents();
    122     }
    123 
    124     game_cleanup(&game_state);
    125 
    126     glfwDestroyWindow(window);
    127     glfwTerminate();
    128     return 0;
    129 }
    130 
    131 
    132 void error_callback(int error, const char* description)
    133 {
    134     fprintf(stderr, "Error: %s\n", description);
    135 }
    136 
    137 
    138 void framebuffer_size_callback(GLFWwindow* window, int width, int height)
    139 {
    140     glViewport(0, 0, width, height);
    141 }
    142 
    143 
    144 time_t file_get_modified_time(char *file_path)
    145 {
    146     time_t mtime = 0;
    147     struct stat file_status = {0};
    148     if (stat(file_path, &file_status) == 0)
    149     {
    150         //printf("File: %s last modified: %s\n", file_path, ctime(&file_status.st_mtime));
    151         mtime = file_status.st_mtime;
    152     }
    153     else
    154     {
    155         fprintf(stderr, "ERROR: Failed to stat file: %s\n", file_path);
    156     }
    157     return mtime;
    158 }
    159 
    160 PLATFORM_MEMORY_FREE(linux_memory_free)
    161 {
    162     free(ptr);
    163 }
    164 
    165 PLATFORM_PRINT(linux_print)
    166 {
    167     va_list args;
    168     va_start(args, format);
    169     int num_chars_printed = vprintf(format, args);
    170     va_end(args);
    171     return num_chars_printed;
    172 }
    173 
    174 PLATFORM_READ_ENTIRE_FILE(linux_read_entire_file)
    175 {
    176     FILE *handle = fopen(file_path, "r");
    177     char *buffer = NULL;
    178 
    179     if (handle)
    180     {
    181         // get file size
    182         fseek(handle, 0, SEEK_END);
    183         u32 num_bytes_in_file = ftell(handle);
    184         rewind(handle);
    185 
    186         // TODO: replace malloc with own allocator so I stop having nightmares
    187         buffer = (char*) malloc(sizeof(char) * (num_bytes_in_file + 1) );
    188 
    189         u32 bytes_read = fread(buffer, sizeof(char), num_bytes_in_file, handle);
    190         // IMPORTANT! fread() doesn't add the '\0'
    191         buffer[num_bytes_in_file] = '\0';
    192 
    193         if (num_bytes_in_file != bytes_read)
    194         {
    195             free(buffer);
    196             buffer = NULL;
    197         }
    198 
    199         fclose(handle);
    200     }
    201     else
    202     {
    203         printf("Error: Couldn't open file at path: %s", file_path);
    204         // TODO: handle errors here in a better way
    205         exit(1);
    206     }
    207 
    208     return buffer;
    209 }
    210 
    211 #ifdef PLATFORM_HOTLOAD_GAME_CODE
    212 void unload_game_code(struct GameCode *game_code)
    213 {
    214     if (!game_code)
    215     {
    216         fprintf(stderr, "ERROR: Invalid pointer *game_code\n");
    217         return;
    218     }
    219 
    220     if (game_code->game_code_library)
    221     {
    222         dlclose(game_code->game_code_library);
    223         game_code->game_code_library = NULL;
    224     }
    225     game_code->is_valid = false;
    226     game_code->game_load_opengl_symbols = NULL;
    227     game_code->game_update_and_render = NULL;
    228 }
    229 
    230 
    231 // TODO: Add backup dll in case loading fails
    232 struct GameCode load_game_code(char *source_lib_path)
    233 {
    234     struct GameCode game_code = {0};
    235 
    236     game_code.last_write_time = file_get_modified_time(source_lib_path);
    237     if (game_code.last_write_time)
    238     {
    239         game_code.game_code_library = dlopen(source_lib_path, RTLD_LAZY);
    240         if (game_code.game_code_library)
    241         {
    242             // NOTE: The C standard (as of C99) distinguishes function pointers
    243             // from object pointers (`void *` is an object pointer). Technically it
    244             // is undefined behavior to cast between these two pointer types. In
    245             // practice, it works on most modern platforms.
    246             //
    247             // In this case, we are protected by POSIX, which specifies that
    248             // function and data pointers must be the same size. We will only ever
    249             // be using dlsym on POSIX-compliant platforms.
    250             game_code.game_load_opengl_symbols = (game_load_opengl_symbols_func *) dlsym(game_code.game_code_library, "game_load_opengl_symbols");
    251             game_code.game_update_and_render = (game_update_and_render_func *) dlsym(game_code.game_code_library, "game_update_and_render");
    252             game_code.is_valid = (game_code.game_load_opengl_symbols && game_code.game_update_and_render);
    253         }
    254     }
    255 
    256     if (!game_code.is_valid)
    257     {
    258         fprintf(stderr, "ERROR: Game code is not valid: %s\n", dlerror());
    259     }
    260 
    261     return game_code;
    262 }
    263 #endif // PLATFORM_HOTLOAD_GAME_CODE