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