platform_linux.c (9952B)
1 #include <time.h> 2 3 #include <dlfcn.h> 4 #include <sys/stat.h> 5 6 #include "platform_info.h" 7 #include "am_gl.h" 8 #include <GLFW/glfw3.h> 9 10 #include "game.c" 11 #include "platform.h" 12 #include "platform_linux.h" 13 14 internal void framebuffer_size_callback(GLFWwindow* window, int width, int height); 15 internal void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods); 16 17 // TODO: map this in a nicer way 18 global_variable int platform_input_map[NUM_GAME_BUTTONS] = { 19 [BTN_LEFT] = GLFW_KEY_LEFT, 20 [BTN_RIGHT] = GLFW_KEY_RIGHT, 21 [BTN_UP] = GLFW_KEY_UP, 22 [BTN_DOWN] = GLFW_KEY_DOWN, 23 [BTN_JUMP] = GLFW_KEY_S, 24 [BTN_DEBUG_FLOAT] = GLFW_KEY_F11, 25 }; 26 27 int main(void) 28 { 29 glfwSetErrorCallback(error_callback); 30 31 if (!glfwInit()) 32 { 33 fprintf(stderr, "GLFW initialization failed\n"); 34 return -1; 35 } 36 37 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); 38 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); 39 glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); 40 glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); 41 42 GLFWwindow* window = glfwCreateWindow(PLATFORM_SCR_WIDTH, PLATFORM_SCR_HEIGHT, "A Game", NULL, NULL); 43 if (!window) 44 { 45 fprintf(stderr, "GLFW window creation failed\n"); 46 glfwTerminate(); 47 return -1; 48 } 49 glfwMakeContextCurrent(window); 50 glfwSetKeyCallback(window, key_callback); 51 52 #ifdef USE_TEST_SEED 53 srand((uint32_t)0); 54 #else 55 srand((uint32_t)time(NULL)); 56 #endif 57 58 if (!am_gl_init()) 59 { 60 fprintf(stderr, "OpenGL function loading failed\n."); 61 glfwTerminate(); 62 return -1; 63 } 64 65 struct GameMemory game_memory = { 66 .buffer_size = MEBIBYTES(64), 67 .platform = { 68 &linux_read_entire_file, 69 &linux_memory_free, 70 }, 71 }; 72 // TODO: replace with mmap 73 game_memory.buffer = malloc(game_memory.buffer_size); 74 if (!game_memory.buffer) 75 { 76 fprintf(stderr, "Game memory allocation failed\n"); 77 glfwTerminate(); 78 return -1; 79 } 80 81 struct GameInput game_input = {0}; 82 glfwSetWindowUserPointer(window, &game_input); 83 game_init(&game_memory, (v2u) {PLATFORM_SCR_WIDTH, PLATFORM_SCR_HEIGHT}); 84 85 #ifdef PLATFORM_HOTLOAD_GAME_CODE 86 struct GameCode game_code = load_game_code(PLATFORM_GAME_LIB_PATH); 87 game_code.game_load_opengl_symbols(); 88 #endif 89 90 // NOTE(amin): lag should always be very small, so it's fine to use a float 91 // to store it. We will not run out of precision. 92 uint64_t previous_wall_clock_time = glfwGetTimerValue(); 93 uint64_t timer_frequency = glfwGetTimerFrequency(); 94 95 while (!glfwWindowShouldClose(window)) 96 { 97 uint64_t current_wall_clock_time = glfwGetTimerValue(); 98 99 { 100 uint64_t previous_frame_duration = current_wall_clock_time - previous_wall_clock_time; 101 previous_wall_clock_time = current_wall_clock_time; 102 f32 dt = (float)previous_frame_duration / (float)timer_frequency; 103 // TODO: Debug periodic dropped frame on linux when fullscreen 104 //printf("ms per frame: %f\n", dt * 1000.0f); 105 game_input.dt = dt; 106 } 107 108 int32_t framebuffer_width = PLATFORM_SCR_WIDTH; 109 int32_t framebuffer_height = PLATFORM_SCR_HEIGHT; 110 glfwGetFramebufferSize(window, &framebuffer_width, &framebuffer_height); 111 assert(framebuffer_width >= 0); 112 assert(framebuffer_height >= 0); 113 v2u framebuffer = {framebuffer_width, framebuffer_height}; 114 115 #ifdef PLATFORM_HOTLOAD_GAME_CODE 116 time_t last_write_time = file_get_modified_time(PLATFORM_GAME_LIB_PATH); 117 bool game_code_has_changed = last_write_time && (last_write_time != game_code.last_write_time); 118 if (game_code_has_changed) 119 { 120 unload_game_code(&game_code); 121 game_code = load_game_code(PLATFORM_GAME_LIB_PATH); 122 game_code.game_load_opengl_symbols(); 123 if (!game_code.is_valid) 124 { 125 // TODO: fall back to backup? 126 } 127 } 128 game_code.game_update_and_render(&game_memory, &game_input, framebuffer); 129 #else 130 game_update_and_render(&game_memory, &game_input, framebuffer); 131 #endif // PLATFORM_HOTLOAD_GAME_CODE 132 133 // TODO: Do something sane when vsync is disabled 134 glfwSwapBuffers(window); 135 136 // ಠ_ಠ: We are affected here by a GLFW bug [1]. On my Thinkpad X220, if 137 // I use the built-in keyboard, simultaneous key events sometimes get 138 // dropped. This causes, for example, a press of the jump key to often 139 // not be registered when the left or right arrow key is pressed 140 // simultaneously. 141 // 142 // If I plug in my beloved Leopold FC750R, the problem does not 143 // manifest. 144 // 145 // I have reproduced this behavior using glfw/tests/events.c 146 // 147 // TODO: Ditch GLFW on Linux, hope this gets fixed soon, or build it 148 // from source and apply the workaround discussed in [1]. 149 // 150 // [1] https://github.com/glfw/glfw/issues/1112 151 glfwPollEvents(); 152 } 153 154 game_cleanup(&game_memory); 155 156 // TODO: replace with munmap 157 free(game_memory.buffer); 158 159 glfwDestroyWindow(window); 160 glfwTerminate(); 161 return 0; 162 } 163 164 internal void error_callback(int error, const char *description) 165 { 166 fprintf(stderr, "Error: %s\n", description); 167 } 168 169 internal void key_callback(GLFWwindow *window, int key, int scancode, int action, int mods) 170 { 171 struct GameInput *game_input = (struct GameInput *)glfwGetWindowUserPointer(window); 172 173 enum GameButton game_button = NULL_GAME_BUTTON; 174 // TODO: Determine the button in a nicer way 175 for (enum GameButton b = 0; b < NUM_GAME_BUTTONS; b++) 176 { 177 if (platform_input_map[b] == key) 178 { 179 game_button = b; 180 } 181 } 182 if (game_button != NULL_GAME_BUTTON) 183 { 184 switch (action) 185 { 186 case GLFW_PRESS: 187 game_input->button_states |= (1 << game_button); 188 break; 189 case GLFW_RELEASE: 190 game_input->button_states &= ~(1 << game_button); 191 break; 192 case GLFW_REPEAT: 193 // Do nothing 194 break; 195 default: 196 exit(1); 197 break; 198 } 199 } 200 } 201 202 internal time_t file_get_modified_time(char *file_path) 203 { 204 time_t mtime = 0; 205 struct stat file_status = {0}; 206 if (stat(file_path, &file_status) == 0) 207 { 208 //printf("File: %s last modified: %s\n", file_path, ctime(&file_status.st_mtime)); 209 mtime = file_status.st_mtime; 210 } 211 else 212 { 213 fprintf(stderr, "ERROR: Failed to stat file: %s\n", file_path); 214 } 215 return mtime; 216 } 217 218 PLATFORM_MEMORY_FREE(linux_memory_free) 219 { 220 free(ptr); 221 } 222 223 internal PLATFORM_READ_ENTIRE_FILE(linux_read_entire_file) 224 { 225 assert(a); 226 FILE *handle = fopen(file_path, "r"); 227 u8 *buffer = NULL; 228 229 if (handle) 230 { 231 // get file size 232 fseek(handle, 0, SEEK_END); 233 u32 num_bytes_in_file = ftell(handle); 234 rewind(handle); 235 236 if (out_num_bytes) 237 { 238 *out_num_bytes = num_bytes_in_file; 239 } 240 241 buffer = mem_st_alloc_buffer(a, u8, num_bytes_in_file + 1); 242 243 u32 bytes_read = fread(buffer, sizeof(u8), num_bytes_in_file, handle); 244 // IMPORTANT! fread() doesn't add the '\0' 245 buffer[num_bytes_in_file] = '\0'; 246 247 // TODO: handle this case more loudly 248 if (num_bytes_in_file != bytes_read) 249 { 250 buffer = NULL; 251 } 252 253 fclose(handle); 254 } 255 else 256 { 257 printf("Error: Couldn't open file at path: %s", file_path); 258 // TODO: handle errors here in a better way 259 exit(1); 260 } 261 262 return buffer; 263 } 264 265 #ifdef PLATFORM_HOTLOAD_GAME_CODE 266 internal void unload_game_code(struct GameCode *game_code) 267 { 268 if (!game_code) 269 { 270 fprintf(stderr, "ERROR: Invalid pointer *game_code\n"); 271 return; 272 } 273 274 if (game_code->game_code_library) 275 { 276 dlclose(game_code->game_code_library); 277 game_code->game_code_library = NULL; 278 } 279 game_code->is_valid = false; 280 game_code->game_load_opengl_symbols = NULL; 281 game_code->game_update_and_render = NULL; 282 } 283 284 285 // TODO: Add backup dll in case loading fails 286 internal struct GameCode load_game_code(char *source_lib_path) 287 { 288 struct GameCode game_code = {0}; 289 290 game_code.last_write_time = file_get_modified_time(source_lib_path); 291 if (game_code.last_write_time) 292 { 293 game_code.game_code_library = dlopen(source_lib_path, RTLD_LAZY); 294 if (game_code.game_code_library) 295 { 296 // NOTE: The C standard (as of C99) distinguishes function pointers 297 // from object pointers (`void *` is an object pointer). Technically it 298 // is undefined behavior to cast between these two pointer types. In 299 // practice, it works on most modern platforms. 300 // 301 // In this case, we are protected by POSIX, which specifies that 302 // function and data pointers must be the same size. We will only ever 303 // be using dlsym on POSIX-compliant platforms. 304 static_assert( 305 sizeof(void *) == sizeof(void(*)()), 306 "Object pointer must be the same size as function pointer" 307 ); 308 game_code.game_load_opengl_symbols = (game_load_opengl_symbols_func *) dlsym(game_code.game_code_library, "game_load_opengl_symbols"); 309 game_code.game_update_and_render = (game_update_and_render_func *) dlsym(game_code.game_code_library, "game_update_and_render"); 310 game_code.is_valid = (game_code.game_load_opengl_symbols && game_code.game_update_and_render); 311 } 312 } 313 314 if (!game_code.is_valid) 315 { 316 fprintf(stderr, "ERROR: Game code is not valid: %s\n", dlerror()); 317 } 318 319 return game_code; 320 } 321 #endif // PLATFORM_HOTLOAD_GAME_CODE