starfield

starfield.git
git clone git://git.lenczewski.org/starfield.git
Log | Files | Refs | Submodules | README | LICENSE

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"