linux_starfield.c (17721B)
1 #include "linux_starfield.h" 2 3 internal struct linux_state state; 4 5 int 6 main(int argc, char **argv, char **envp) 7 { 8 /* TODO: parse out commandline arguments for debug behaviour? */ 9 (void) argc; 10 (void) envp; 11 12 state.platform_api = load_platform_api(); 13 state.render_api = load_render_api(); 14 15 /* NOTE: we expect the library to be alongside our platform-specific 16 * executable. the unix loader does not search this path by default, 17 * so we build the absolute path to the library here as a workaround 18 */ 19 char starfield_library_path[PATH_MAX]; 20 strcpy(starfield_library_path, argv[0]); 21 strcpy(strrchr(starfield_library_path, '/'), "/starfield.so"); 22 23 if (!load_starfield_api(starfield_library_path, &state.starfield_api)) { 24 fprintf(stderr, "Failed to load starfield library: %s\n", starfield_library_path); 25 _exit(1); 26 } 27 28 struct starfield_thread thread = { 29 .id = 0, 30 }; 31 32 if (!init_memory(&state.platform_arena, 32 * MiB, 33 &state.renderer_arena, 32 * MiB, 34 &state.starfield_arena, 64 * MiB)) { 35 fprintf(stderr, "Failed to initialise memory\n"); 36 _exit(1); 37 } 38 39 f32 target_render_rate = 60.0, target_physics_rate = 60.0; 40 41 u32 target_us_per_frame = USECS / target_render_rate; 42 u32 target_us_per_tick = USECS / target_physics_rate; 43 44 (void) target_us_per_tick; 45 46 u32 period_us = target_us_per_frame, buffered_periods = 2; 47 if (!init_audio(&state.platform_arena, &state.audio, period_us, buffered_periods)) { 48 fprintf(stderr, "Failed to initialise platform audio\n"); 49 } 50 51 char *display_name = NULL; 52 if (!init_video(&state.platform_arena, &state.video, display_name)) { 53 fprintf(stderr, "Failed to initialise platform video\n"); 54 _exit(1); 55 } 56 57 if (!init_vulkan(&state.renderer_arena, &state.vulkan, 58 state.video.display, state.video.surface, 59 state.video.width, state.video.height)) { 60 fprintf(stderr, "Failed to initialise platform renderer\n"); 61 _exit(1); 62 } 63 64 struct starfield_memory starfield_memory = { 65 .ptr = state.starfield_arena.ptr, 66 .cap = state.starfield_arena.cap, 67 .len = state.starfield_arena.len, 68 }; 69 70 if (!state.starfield_api.init(&thread, &starfield_memory, &state.platform_api)) { 71 fprintf(stderr, "Failed to initialise starfield\n"); 72 _exit(1); 73 } 74 75 state.running = 1; 76 77 f32 dt = 1.0f / target_render_rate; 78 while (state.running) { 79 struct timespec start_nanos, end_nanos; 80 clock_gettime(CLOCK_MONOTONIC, &start_nanos); 81 82 fprintf(stderr, "Timings: "); 83 84 struct timespec update_start, update_end; 85 clock_gettime(CLOCK_MONOTONIC, &update_start); 86 87 struct starfield_input input = { 88 0, 89 }; 90 91 state.starfield_api.update(&thread, &starfield_memory, 92 &state.platform_api, &input, dt); 93 94 clock_gettime(CLOCK_MONOTONIC, &update_end); 95 fprintf(stderr, "update: %" PRIu64 " ns, ", 96 linux_elapsed_ns(&update_start, &update_end)); 97 98 if (state.audio.enabled) { 99 struct timespec sample_start, sample_end; 100 clock_gettime(CLOCK_MONOTONIC, &sample_start); 101 state.starfield_api.sample(&thread, &starfield_memory, 102 &state.platform_api, &state.audio.buffer, dt); 103 104 #ifdef STARFIELD_DEBUG 105 /* TODO: temporary test tone */ 106 s32 tone_hz = 256; 107 s32 tone_volume = INT32_MAX / 16; 108 func_local f32 tone_t = 0.0f; 109 110 const f32 PI = 3.1415926535f; 111 const u32 wave_period = state.audio.buffer.sample_rate / tone_hz; 112 113 s32 *sample = state.audio.buffer.buffer; 114 for (u32 i = 0; i < state.audio.buffer.expected_frames; i++) { 115 f32 v = sinf(tone_t); 116 117 int32_t sample_value = (int32_t) (v * tone_volume); 118 for (u32 j = 0; j < state.audio.buffer.channels; j++) 119 *sample++ = sample_value; 120 121 tone_t += 2 * PI / wave_period; 122 if (tone_t >= 2 * PI) 123 tone_t -= 2 * PI; 124 } 125 #endif 126 127 play_audio(&state.audio); 128 129 clock_gettime(CLOCK_MONOTONIC, &sample_end); 130 fprintf(stderr, "sample: %" PRIu64 " ns, ", 131 linux_elapsed_ns(&sample_start, &sample_end)); 132 } 133 134 struct timespec render_start, render_end; 135 clock_gettime(CLOCK_MONOTONIC, &render_start); 136 state.starfield_api.render(&thread, &starfield_memory, &state.platform_api, 137 &state.render_api, state.vulkan.renderer, 138 state.video.width, state.video.height, dt); 139 140 draw_frame(&state.video); 141 142 clock_gettime(CLOCK_MONOTONIC, &render_end); 143 fprintf(stderr, "render: %" PRIu64 " ns, ", 144 linux_elapsed_ns(&render_start, &render_end)); 145 146 clock_gettime(CLOCK_MONOTONIC, &end_nanos); 147 148 u64 elapsed_us = linux_elapsed_ns(&start_nanos, &end_nanos) / 1000; 149 if (elapsed_us < target_us_per_frame) { 150 struct timespec req = { 151 .tv_sec = 0, 152 .tv_nsec = (target_us_per_frame - elapsed_us) * 1000, 153 }; 154 155 while (nanosleep(&req, &req) < 0); 156 157 clock_gettime(CLOCK_MONOTONIC, &end_nanos); 158 elapsed_us = linux_elapsed_ns(&start_nanos, &end_nanos) / 1000; 159 } else { 160 /* TODO: missed frame */ 161 } 162 163 dt = (f32) elapsed_us / USECS; 164 165 fprintf(stderr, "total: %f\n", dt); 166 } 167 168 state.starfield_api.free(&thread, &starfield_memory, &state.platform_api); 169 170 free_vulkan(&state.vulkan); 171 172 _exit(0); 173 } 174 175 internal u64 176 linux_elapsed_ns(struct timespec *restrict start, struct timespec *restrict end) 177 { 178 return ((end->tv_sec - start->tv_sec) * NSECS + (end->tv_nsec - start->tv_nsec)); 179 } 180 181 #define LOAD_SYMBOL(loc, T, sym) \ 182 ((loc) = (T *) dlsym(library, sym)) 183 184 internal b32 185 load_starfield_api(char const *filename, struct starfield_api *api) 186 { 187 void *library = dlopen(filename, RTLD_NOW); 188 if (!library) return false; 189 190 LOAD_SYMBOL(api->init, starfield_api_init_t, "starfield_init"); 191 LOAD_SYMBOL(api->update, starfield_api_update_t, "starfield_update"); 192 LOAD_SYMBOL(api->render, starfield_api_render_t, "starfield_render"); 193 LOAD_SYMBOL(api->sample, starfield_api_sample_t, "starfield_sample"); 194 LOAD_SYMBOL(api->free, starfield_api_free_t, "starfield_free"); 195 196 return true; 197 } 198 199 internal b32 200 init_memory(struct arena *platform_arena, u64 platform_capacity, 201 struct arena *renderer_arena, u64 renderer_capacity, 202 struct arena *starfield_arena, u64 starfield_capacity) 203 { 204 #ifdef STARFIELD_DEBUG 205 void *base_addr = (void *) (2 * TiB); 206 #else 207 void *base_addr = NULL; 208 #endif 209 210 u64 len = platform_capacity + renderer_capacity + starfield_capacity; 211 212 int prot = PROT_READ | PROT_WRITE; 213 int flags = MAP_PRIVATE | MAP_ANONYMOUS; 214 215 #ifdef STARFIELD_DEBUG 216 flags |= MAP_FIXED; 217 #endif 218 219 void *ptr = mmap(base_addr, len, prot, flags | MAP_HUGETLB | MAP_HUGE_2MB, -1, 0); 220 if (ptr == MAP_FAILED) 221 ptr = mmap(base_addr, len, prot, flags, -1, 0); 222 223 if (ptr == MAP_FAILED) 224 return false; 225 226 /* TODO: can we make this faster? can we avoid this entirely? */ 227 memset(base_addr, 0, len); 228 229 platform_arena->ptr = ptr; 230 platform_arena->cap = platform_capacity; 231 platform_arena->len = 0; 232 233 renderer_arena->ptr = (u8 *) ptr + platform_capacity; 234 renderer_arena->cap = renderer_capacity; 235 renderer_arena->len = 0; 236 237 starfield_arena->ptr = (u8 *) ptr + platform_capacity + renderer_capacity; 238 starfield_arena->cap = starfield_capacity; 239 starfield_arena->len = 0; 240 241 return true; 242 } 243 244 internal b32 245 init_audio(struct arena *arena, struct linux_audio *audio, u32 period_us, u32 buffered_periods) 246 { 247 audio->enabled = false; 248 249 int err; 250 if ((err = snd_pcm_open(&audio->handle, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0) { 251 fprintf(stderr, "snd_pcm_open: %s\n", snd_strerror(err)); 252 return false; 253 } 254 255 snd_pcm_hw_params_t *hw_params = alloca(snd_pcm_hw_params_sizeof()); 256 assert(hw_params); 257 258 snd_pcm_hw_params_any(audio->handle, hw_params); 259 snd_pcm_hw_params_set_access(audio->handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); 260 snd_pcm_hw_params_set_format(audio->handle, hw_params, SND_PCM_FORMAT_S32_LE); 261 snd_pcm_hw_params_set_channels(audio->handle, hw_params, STARFIELD_AUDIO_CHANNELS); 262 snd_pcm_hw_params_set_rate(audio->handle, hw_params, STARFIELD_AUDIO_SAMPLE_RATE, 0); 263 snd_pcm_hw_params_set_periods(audio->handle, hw_params, buffered_periods, 0); 264 snd_pcm_hw_params_set_period_time(audio->handle, hw_params, period_us, 0); 265 266 if ((err = snd_pcm_hw_params(audio->handle, hw_params)) < 0) { 267 fprintf(stderr, "and_pcm_hw_params: %s\n", snd_strerror(err)); 268 return false; 269 } 270 271 u64 frames = (period_us * STARFIELD_AUDIO_SAMPLE_RATE * STARFIELD_AUDIO_FRAME_BYTES) / USECS; 272 273 fprintf(stderr, "Audio buffer period (us): %" PRIu32 "\n", period_us); 274 fprintf(stderr, "Audio buffer frames: %" PRIu64 "\n", frames); 275 276 audio->buffer.buffer = PUSH_ARRAY(arena, s32, frames); 277 assert(audio->buffer.buffer); 278 279 audio->buffer.channels = STARFIELD_AUDIO_CHANNELS; 280 audio->buffer.sample_rate = STARFIELD_AUDIO_SAMPLE_RATE; 281 audio->buffer.sample_bits = STARFIELD_AUDIO_SAMPLE_BITS; 282 audio->buffer.expected_frames = frames; 283 284 audio->enabled = true; 285 286 return true; 287 } 288 289 internal void 290 play_audio(struct linux_audio *audio) 291 { 292 void *buf = audio->buffer.buffer; 293 u32 frames = audio->buffer.expected_frames; 294 295 int err; 296 if ((err = snd_pcm_writei(audio->handle, buf, frames)) < 0) { 297 fprintf(stderr, "snd_pcm_write: %s\n", snd_strerror(err)); 298 } 299 } 300 301 internal void 302 xdg_wm_base_handle_ping(void *data, struct xdg_wm_base *wm_base, u32 serial) 303 { 304 (void) data; 305 306 xdg_wm_base_pong(wm_base, serial); 307 } 308 309 internal const struct xdg_wm_base_listener xdg_wm_base_listener = { 310 .ping = xdg_wm_base_handle_ping, 311 }; 312 313 internal void 314 xdg_surface_handle_configure(void *data, struct xdg_surface *xdg_surface, u32 serial) 315 { 316 (void) data; 317 318 xdg_surface_ack_configure(xdg_surface, serial); 319 } 320 321 internal const struct xdg_surface_listener xdg_surface_listener = { 322 .configure = xdg_surface_handle_configure, 323 }; 324 325 internal void 326 xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *toplevel, s32 width, s32 height, struct wl_array *states) 327 { 328 (void) toplevel; 329 (void) states; 330 331 struct linux_video *video = data; 332 video->width = width ? width : STARFIELD_VIDEO_HRES; 333 video->height = height ? height : STARFIELD_VIDEO_HRES; 334 } 335 336 internal void 337 xdg_toplevel_handle_close(void *data, struct xdg_toplevel *toplevel) 338 { 339 (void) data; 340 (void) toplevel; 341 342 state.running = false; 343 } 344 345 internal void 346 xdg_toplevel_handle_configure_bounds(void *data, struct xdg_toplevel *toplevel, s32 width, s32 height) 347 { 348 (void) toplevel; 349 350 struct linux_video *video = data; 351 video->width = width ? width : STARFIELD_VIDEO_HRES; 352 video->height = height ? height : STARFIELD_VIDEO_HRES; 353 } 354 355 internal void 356 xdg_toplevel_handle_wm_capabilities(void *data, struct xdg_toplevel *toplevel, struct wl_array *caps) 357 { 358 (void) data; 359 (void) toplevel; 360 (void) caps; 361 362 // TODO: handle me, do we care about any special capabilities? 363 } 364 365 internal const struct xdg_toplevel_listener xdg_toplevel_listener = { 366 .configure = xdg_toplevel_handle_configure, 367 .close = xdg_toplevel_handle_close, 368 .configure_bounds = xdg_toplevel_handle_configure_bounds, 369 .wm_capabilities = xdg_toplevel_handle_wm_capabilities, 370 }; 371 372 internal void 373 wl_registry_handle_global(void *data, struct wl_registry *registry, u32 name, char const *interface, u32 version) 374 { 375 struct linux_video *video = data; 376 377 fprintf(stderr, "wl: interface: %u, name: %s, version: %u\n", name, interface, version); 378 379 if (strcmp(interface, wl_compositor_interface.name) == 0) { 380 video->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 6); 381 } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { 382 video->wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 6); 383 xdg_wm_base_add_listener(video->wm_base, &xdg_wm_base_listener, data); 384 } 385 } 386 387 internal void 388 wl_registry_handle_global_remove(void *data, struct wl_registry *registry, u32 name) 389 { 390 (void) data; 391 (void) registry; 392 (void) name; 393 } 394 395 internal const struct wl_registry_listener wl_registry_listener = { 396 .global = wl_registry_handle_global, 397 .global_remove = wl_registry_handle_global_remove, 398 }; 399 400 internal b32 401 init_video(struct arena *arena, struct linux_video *video, char const *display_name) 402 { 403 (void) arena; 404 405 if (!(video->display = wl_display_connect(display_name))) { 406 fprintf(stderr, "wl_display_connect: could not connect to %s\n", display_name); 407 return false; 408 } 409 410 video->registry = wl_display_get_registry(video->display); 411 412 wl_registry_add_listener(video->registry, &wl_registry_listener, video); 413 414 wl_display_roundtrip(video->display); 415 416 assert(video->compositor); 417 assert(video->wm_base); 418 419 video->surface = wl_compositor_create_surface(video->compositor); 420 421 video->xdg_surface = xdg_wm_base_get_xdg_surface(video->wm_base, video->surface); 422 xdg_surface_add_listener(video->xdg_surface, &xdg_surface_listener, video); 423 424 video->xdg_toplevel = xdg_surface_get_toplevel(video->xdg_surface); 425 xdg_toplevel_add_listener(video->xdg_toplevel, &xdg_toplevel_listener, video); 426 427 xdg_toplevel_set_app_id(video->xdg_toplevel, "starfield"); 428 xdg_toplevel_set_title(video->xdg_toplevel, "starfield"); 429 430 wl_surface_commit(video->surface); 431 wl_display_roundtrip(video->display); 432 433 return true; 434 } 435 436 internal void 437 draw_frame(struct linux_video *video) 438 { 439 wl_display_dispatch_pending(video->display); 440 } 441 442 internal VKAPI_ATTR VkBool32 VKAPI_CALL 443 vk_debug_callback(VkDebugUtilsMessageSeverityFlagBitsEXT severity, 444 VkDebugUtilsMessageTypeFlagsEXT type, 445 const VkDebugUtilsMessengerCallbackDataEXT *callback_data, 446 void *user_data) 447 { 448 (void) severity; 449 (void) type; 450 (void) user_data; 451 452 fprintf(stderr, "vk: validation: %s\n", callback_data->pMessage); 453 454 return VK_FALSE; 455 } 456 457 internal b32 458 init_vulkan(struct arena *arena, struct linux_vulkan *vulkan, 459 struct wl_display *display, struct wl_surface *surface, 460 u32 width, u32 height) 461 { 462 VkApplicationInfo app_info = { 463 .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, 464 .pApplicationName = "starfield", 465 .applicationVersion = VK_MAKE_VERSION(1, 0, 0), 466 .pEngineName = "No Engine", 467 .engineVersion = VK_MAKE_VERSION(1, 0, 0), 468 .apiVersion = VK_API_VERSION_1_0, 469 }; 470 471 const char *instance_extensions[] = { 472 VK_KHR_SURFACE_EXTENSION_NAME, 473 VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME, 474 VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME, 475 VK_EXT_DEBUG_UTILS_EXTENSION_NAME, 476 }; 477 478 const char *instance_layers[] = { 479 "VK_LAYER_KHRONOS_validation", 480 }; 481 482 uint32_t debug_severity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT 483 | VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT 484 | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT 485 | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; 486 487 uint32_t debug_type = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT 488 | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT 489 | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; 490 491 VkDebugUtilsMessengerCreateInfoEXT debug_info = { 492 .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, 493 .messageSeverity = debug_severity, 494 .messageType = debug_type, 495 .pfnUserCallback = vk_debug_callback, 496 .pUserData = NULL, 497 }; 498 499 VkInstanceCreateInfo instance_info = { 500 .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, 501 .pApplicationInfo = &app_info, 502 .enabledExtensionCount = ARRLEN(instance_extensions), 503 .ppEnabledExtensionNames = instance_extensions, 504 .enabledLayerCount = ARRLEN(instance_layers), 505 .ppEnabledLayerNames = instance_layers, 506 .pNext = &debug_info, 507 }; 508 509 if (vkCreateInstance(&instance_info, NULL, &vulkan->instance) != VK_SUCCESS) { 510 fprintf(stderr, "Failed to create Vulkan instance!\n"); 511 return false; 512 } 513 514 PFN_vkCreateDebugUtilsMessengerEXT vk_create_debug_utils_messenger_ext = 515 (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(vulkan->instance, 516 "vkCreateDebugUtilsMessengerEXT"); 517 if (!vk_create_debug_utils_messenger_ext) { 518 fprintf(stderr, "Failed to get proc address of vkCreateDebugUtilsMessengerEXT!\n"); 519 return false; 520 } 521 522 if (vk_create_debug_utils_messenger_ext(vulkan->instance, 523 &debug_info, NULL, 524 &vulkan->debug_messenger) != VK_SUCCESS) { 525 fprintf(stderr, "Failed to create Vulkan debug messenger!\n"); 526 return false; 527 } 528 529 VkWaylandSurfaceCreateInfoKHR surface_info = { 530 .sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, 531 .display = display, 532 .surface = surface, 533 }; 534 535 if (vkCreateWaylandSurfaceKHR(vulkan->instance, &surface_info, NULL, &vulkan->surface) != VK_SUCCESS) { 536 fprintf(stderr, "Failed to create Vulkan surface!\n"); 537 return false; 538 } 539 540 if (!(vulkan->renderer = create_renderer(arena, vulkan->instance, vulkan->surface, width, height))) { 541 fprintf(stderr, "Failed to create Vulkan renderer\n"); 542 return false; 543 } 544 545 return true; 546 } 547 548 internal void 549 free_vulkan(struct linux_vulkan *vulkan) 550 { 551 destroy_renderer(vulkan->renderer); 552 553 vkDestroySurfaceKHR(vulkan->instance, vulkan->surface, NULL); 554 555 PFN_vkDestroyDebugUtilsMessengerEXT vk_destroy_debug_utils_messenger_ext = 556 (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(vulkan->instance, 557 "vkDestroyDebugUtilsMessengerEXT"); 558 if (!vk_destroy_debug_utils_messenger_ext) { 559 fprintf(stderr, "Failed to get proc address of vkDestroyDebugUtilsMessengerEXT!\n"); 560 return; 561 } 562 563 vk_destroy_debug_utils_messenger_ext(vulkan->instance, vulkan->debug_messenger, NULL); 564 565 vkDestroyInstance(vulkan->instance, NULL); 566 } 567 568 /* platform implementation 569 * =========================================================================== 570 */ 571 572 #ifdef STARFIELD_DEBUG 573 574 DEBUG_PLATFORM_READ_FILE(DEBUG_platform_read_file) 575 { 576 (void) filename; 577 578 return (struct DEBUG_platform_read_file_result) {0}; 579 } 580 581 DEBUG_PLATFORM_WRITE_FILE(DEBUG_platform_write_file) 582 { 583 (void) filename; 584 (void) buf; 585 (void) len; 586 587 return true; 588 } 589 590 DEBUG_PLATFORM_FREE_FILE_MEMORY(DEBUG_platform_free_file_memory) 591 { 592 (void) ptr; 593 } 594 595 #endif 596 597 internal struct platform_api 598 load_platform_api(void) 599 { 600 return (struct platform_api) { 601 #ifdef STARFIELD_DEBUG 602 .DEBUG_read_file = DEBUG_platform_read_file, 603 .DEBUG_write_file = DEBUG_platform_write_file, 604 .DEBUG_free_file_memory = DEBUG_platform_free_file_memory, 605 #endif 606 }; 607 } 608 609 /* utils 610 * =========================================================================== 611 */ 612 613 #include "utils.c" 614 #include "renderer.c"