rts

rts.git
git clone git://git.lenczewski.org/rts.git
Log | Files | Refs

linux.c (21834B)


      1 #define RTS_API_IMPL 1
      2 
      3 #include "linux.h"
      4 
      5 internal struct linux_state state;
      6 
      7 enum debug_level loglevel;
      8 
      9 int
     10 main(int argc, char **argv)
     11 {
     12 #ifdef RTS_LOGLEVEL
     13 	loglevel = RTS_LOGLEVEL;
     14 #else
     15 	loglevel = INFO; // WARN;
     16 #endif
     17 
     18 	memset(&state, 0, sizeof state);
     19 
     20 	load_platform_api(&state.platform_api);
     21 	load_renderer_api(&state.renderer_api);
     22 
     23 	char game_lib_path[PATH_MAX];
     24 	strcpy(game_lib_path, argv[0]);
     25 	strcpy(strrchr(game_lib_path, '/'), "/rts.so");
     26 
     27 	if (!load_game_api(game_lib_path, &state.rts_api)) {
     28 		dbglog(ERROR, "Failed to load game api: %s\n", game_lib_path);
     29 		exit(EXIT_FAILURE);
     30 	}
     31 
     32 	struct thread thread = {
     33 		.id = 0,
     34 	};
     35 
     36 	u64 total_memory = PLATFORM_MEMORY + GAME_MEMORY;
     37 	if (!init_memory(&state.memory, total_memory)) {
     38 		dbglog(ERROR, "Failed to initialise platform and game memory\n");
     39 		exit(EXIT_FAILURE);
     40 	}
     41 
     42 	struct arena platform_arena = {
     43 		.ptr = state.memory.ptr,
     44 		.cap = PLATFORM_MEMORY,
     45 		.len = 0,
     46 	};
     47 
     48 	struct memory game_memory = {
     49 		.ptr = (u8 *) state.memory.ptr + PLATFORM_MEMORY,
     50 		.len = GAME_MEMORY,
     51 	};
     52 
     53 	f32 target_render_rate = 60.0, target_physics_rate = 60.0;
     54 	u32 target_us_per_frame = USECS / target_render_rate;
     55 	u32 target_us_per_tick = USECS / target_physics_rate;
     56 
     57 	(void) target_us_per_tick;
     58 
     59 	u32 period_samples = RTS_AUDIO_SAMPLE_RATE / target_render_rate;
     60 	u32 buffer_samples = RTS_AUDIO_SAMPLE_RATE;
     61 	if (!init_audio(&platform_arena, &state.audio,
     62 			period_samples, buffer_samples)) {
     63 		dbglog(ERROR, "Failed to initialise platform audio\n");
     64 		exit(EXIT_FAILURE);
     65 	}
     66 
     67 	char const *title = "RTS Demo";
     68 	if (!init_video(&platform_arena, &state.video, title)) {
     69 		dbglog(ERROR, "Failed to initialise platform video\n");
     70 		exit(EXIT_FAILURE);
     71 	}
     72 
     73 	if (!state.rts_api.init(&thread, &game_memory, &state.platform_api)) {
     74 		dbglog(ERROR, "Failed to initialise rts game\n");
     75 		exit(EXIT_FAILURE);
     76 	}
     77 
     78 	state.running = 1;
     79 
     80 	f32 dt = 1 / target_render_rate;
     81 	while (state.running) {
     82 		struct timespec frame_start, frame_end;
     83 		clock_gettime(CLOCK_MONOTONIC, &frame_start);
     84 
     85 		struct timespec update_start, update_end;
     86 		clock_gettime(CLOCK_MONOTONIC, &update_start);
     87 
     88 		handle_pending_events(&state);
     89 
     90 #ifdef RTS_DEBUG
     91 		dbglog(INFO, "input: W/A/S/D: %d/%d/%d/%d , X/Y/Z: %03d/%03d/%03d\n",
     92 				state.video.input.keyboard.buttons[KEYBOARD_UP].was_pressed,
     93 				state.video.input.keyboard.buttons[KEYBOARD_LEFT].was_pressed,
     94 				state.video.input.keyboard.buttons[KEYBOARD_DOWN].was_pressed,
     95 				state.video.input.keyboard.buttons[KEYBOARD_RIGHT].was_pressed,
     96 				state.video.input.mouse.x,
     97 				state.video.input.mouse.y,
     98 				state.video.input.mouse.z);
     99 #endif
    100 
    101 		state.rts_api.update(&thread, &game_memory,
    102 				     &state.platform_api,
    103 				     &state.video.input,
    104 				     state.video.width, state.video.height, dt);
    105 
    106 		clock_gettime(CLOCK_MONOTONIC, &update_end);
    107 
    108 		struct timespec sample_start, sample_end;
    109 		clock_gettime(CLOCK_MONOTONIC, &sample_start);
    110 
    111 		if (state.audio.enabled) {
    112 			update_expected_frames(&state.audio, dt);
    113 
    114 			state.rts_api.sample(&thread, &game_memory,
    115 					     &state.platform_api,
    116 					     &state.audio.audio_buffer,
    117 					     dt);
    118 
    119 			play_audio(&state.audio);
    120 		} else {
    121 			reset_audio(&state.audio);
    122 		}
    123 
    124 		clock_gettime(CLOCK_MONOTONIC, &sample_end);
    125 
    126 		struct timespec render_start, render_end;
    127 		clock_gettime(CLOCK_MONOTONIC, &render_start);
    128 
    129 		state.rts_api.render(&thread, &game_memory,
    130 				     &state.platform_api,
    131 				     &state.renderer_api, state.video.renderer,
    132 				     state.video.width, state.video.height, dt);
    133 
    134 		draw_frame(&state.video);
    135 
    136 		clock_gettime(CLOCK_MONOTONIC, &render_end);
    137 
    138 		clock_gettime(CLOCK_MONOTONIC, &frame_end);
    139 
    140 		u32 elapsed_us = linux_elapsed_ns(&frame_start, &frame_end) / 1000;
    141 		if (elapsed_us < target_us_per_frame) {
    142 			struct timespec delay = {
    143 				.tv_sec = 0,
    144 				.tv_nsec = (target_us_per_frame - elapsed_us) * 1000,
    145 			};
    146 
    147 			while (nanosleep(&delay, &delay) < 0);
    148 
    149 			clock_gettime(CLOCK_MONOTONIC, &frame_end);
    150 			elapsed_us = linux_elapsed_ns(&frame_start, &frame_end) / 1000;
    151 		} else {
    152 			/* TODO: missed frame */
    153 		}
    154 
    155 		dt = (f32) elapsed_us / USECS;
    156 
    157 #ifdef RTS_DEBUG
    158 		dbglog(INFO, "timings: "
    159 				"update: %" PRIu64 " ns, "
    160 				"sample: %" PRIu64 " ns, "
    161 				"render: %" PRIu64 " ns, "
    162 				"total: %" PRIu64 " ns, "
    163 				"\n",
    164 				linux_elapsed_ns(&update_start, &update_end),
    165 				linux_elapsed_ns(&sample_start, &sample_end),
    166 				linux_elapsed_ns(&render_start, &render_end),
    167 				linux_elapsed_ns(&frame_start, &frame_end));
    168 #endif
    169 	}
    170 
    171 	exit(EXIT_SUCCESS);
    172 }
    173 
    174 internal u64
    175 linux_elapsed_ns(struct timespec *restrict start, struct timespec *restrict end)
    176 {
    177 	return (end->tv_sec - start->tv_sec) * NSECS + (end->tv_nsec - start->tv_nsec);
    178 }
    179 
    180 #define LOADSYM(val, T, lib, sym) ((val) = (T *) dlsym(lib, sym))
    181 
    182 internal b32
    183 load_game_api(char const *path, struct rts_api *api)
    184 {
    185 	void *library = dlopen(path, RTLD_NOW);
    186 	if (!library)
    187 		return false;
    188 
    189 	LOADSYM(api->init, rts_api_init_t, library, "rts_init");
    190 	LOADSYM(api->update, rts_api_update_t, library, "rts_update");
    191 	LOADSYM(api->render, rts_api_render_t, library, "rts_render");
    192 	LOADSYM(api->sample, rts_api_sample_t, library, "rts_sample");
    193 	LOADSYM(api->free, rts_api_free_t, library, "rts_free");
    194 
    195 	return true;
    196 }
    197 
    198 internal b32
    199 init_memory(struct memory *memory, u64 capacity)
    200 {
    201 #ifdef RTS_DEBUG
    202 	void *base = (void *) (2 * TiB);
    203 #else
    204 	void *base = NULL;
    205 #endif
    206 
    207 	int prot = PROT_READ | PROT_WRITE;
    208 	int flags = MAP_PRIVATE | MAP_ANONYMOUS;
    209 
    210 #ifdef RTS_DEBUG
    211 	flags |= MAP_FIXED;
    212 #endif
    213 
    214 	memory->len = capacity;
    215 	memory->ptr = mmap(base, memory->len, prot, flags, -1, 0);
    216 	if (memory->ptr == MAP_FAILED)
    217 		return false;
    218 
    219 	madvise(memory->ptr, memory->len, MADV_HUGEPAGE);
    220 
    221 	return true;
    222 }
    223 
    224 internal void
    225 handle_pending_events(struct linux_state *state)
    226 {
    227 	wl_display_dispatch_pending(state->video.display);
    228 }
    229 
    230 internal b32
    231 init_audio(struct arena *arena, struct linux_audio *audio,
    232 	   u32 period_samples, u32 buffer_samples)
    233 {
    234 	audio->enabled = false;
    235 
    236 	int err;
    237 	if ((err = snd_pcm_open(&audio->handle, "default",
    238 				SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) {
    239 		dbglog(WARN, "snd_pcm_open: %s\n", snd_strerror(err));
    240 		return false;
    241 	}
    242 
    243 	snd_pcm_hw_params_t *hw_params = alloca(snd_pcm_hw_params_sizeof());
    244 	assert(hw_params);
    245 
    246 	snd_pcm_hw_params_any(audio->handle, hw_params);
    247 	snd_pcm_hw_params_set_access(audio->handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
    248 	snd_pcm_hw_params_set_format(audio->handle, hw_params, SND_PCM_FORMAT_S32_LE);
    249 	snd_pcm_hw_params_set_channels(audio->handle, hw_params, RTS_AUDIO_CHANNELS);
    250 	snd_pcm_hw_params_set_rate(audio->handle, hw_params, RTS_AUDIO_SAMPLE_RATE, 0);
    251 	snd_pcm_hw_params_set_period_size(audio->handle, hw_params, period_samples, 0);
    252 	snd_pcm_hw_params_set_buffer_size(audio->handle, hw_params, buffer_samples);
    253 
    254 	if ((err = snd_pcm_hw_params(audio->handle, hw_params)) < 0) {
    255 		dbglog(WARN, "and_pcm_hw_params: %s\n", snd_strerror(err));
    256 		return false;
    257 	}
    258 
    259 	int dir;
    260 	snd_pcm_hw_params_get_period_time(hw_params, &audio->period_us, &dir);
    261 	snd_pcm_hw_params_get_period_size(hw_params, &audio->period_frames, &dir);
    262 	snd_pcm_hw_params_get_buffer_size(hw_params, &audio->buffer_frames);
    263 
    264 	dbglog(INFO, "Audio period frames: %" PRIu64 ", %" PRIu32 " us\n",
    265 		     audio->period_frames, audio->period_us);
    266 	dbglog(INFO, "Audio buffer frames: %" PRIu64 "\n", audio->buffer_frames);
    267 
    268 	audio->audio_buffer.buffer = ALLOC_ARRAY(arena, u8, RTS_AUDIO_FRAME_BYTES * audio->buffer_frames);
    269 	assert(audio->audio_buffer.buffer);
    270 
    271 	audio->audio_buffer.channels = RTS_AUDIO_CHANNELS;
    272 	audio->audio_buffer.sample_rate = RTS_AUDIO_SAMPLE_RATE;
    273 	audio->audio_buffer.sample_bits = RTS_AUDIO_SAMPLE_BITS;
    274 	audio->audio_buffer.frames = audio->buffer_frames;
    275 
    276 	audio->enabled = true;
    277 
    278 	return true;
    279 }
    280 
    281 internal void
    282 reset_audio(struct linux_audio *audio)
    283 {
    284 	if (snd_pcm_prepare(audio->handle) == 0)
    285 		audio->enabled = true;
    286 }
    287 
    288 internal void
    289 update_expected_frames(struct linux_audio *audio, f32 dt)
    290 {
    291 	snd_pcm_sframes_t total = audio->buffer_frames;
    292 	snd_pcm_sframes_t expected = RTS_AUDIO_SAMPLE_RATE * dt;
    293 
    294 	snd_pcm_sframes_t avail, delay;
    295 	if (snd_pcm_avail_delay(audio->handle, &avail, &delay) < 0) {
    296 		audio->enabled = false;
    297 		return;
    298 	}
    299 
    300 	dbglog(DEBUG, "total: %ld, expected: %ld, avail: %ld, pending: %ld\n",
    301 			total, expected, avail, delay);
    302 
    303 	audio->audio_buffer.frames = avail;
    304 }
    305 
    306 internal void
    307 play_audio(struct linux_audio *audio)
    308 {
    309 	s32 written_frames = snd_pcm_writei(audio->handle,
    310 					    audio->audio_buffer.buffer,
    311 					    audio->audio_buffer.frames);
    312 
    313 	if (written_frames < 0) {
    314 		written_frames = snd_pcm_recover(audio->handle, written_frames, 0);
    315 	} if ((u32) written_frames < audio->audio_buffer.frames) {
    316 		dbglog(DEBUG, "underrun! wrote %d/%" PRIu32 " samples\n",
    317 				written_frames, audio->audio_buffer.frames);
    318 	}
    319 
    320 	snd_pcm_sframes_t total = audio->buffer_frames, avail, delay;
    321 	snd_pcm_avail_delay(audio->handle, &avail, &delay);
    322 	dbglog(DEBUG, "total: %ld, written: %d, new avail: %ld, new pending: %ld\n",
    323 			total, written_frames, avail, delay);
    324 }
    325 
    326 internal void
    327 process_button_input(struct button_input *buttons, s32 id, b32 is_pressed)
    328 {
    329 	struct button_input *input = buttons + id;
    330 
    331 	if (input->was_pressed != is_pressed)
    332 		input->half_transition_count++;
    333 
    334 	input->was_pressed = is_pressed;
    335 }
    336 
    337 internal void
    338 process_button(struct input *input, u32 button, b32 is_pressed)
    339 {
    340 	switch (button) {
    341 	case BTN_LEFT:
    342 		process_button_input(input->mouse.buttons, MOUSE_L, is_pressed);
    343 		break;
    344 
    345 	case BTN_RIGHT:
    346 		process_button_input(input->mouse.buttons, MOUSE_R, is_pressed);
    347 		break;
    348 
    349 	case BTN_MIDDLE:
    350 		process_button_input(input->mouse.buttons, MOUSE_M, is_pressed);
    351 		break;
    352 
    353 	case BTN_X:
    354 		process_button_input(input->mouse.buttons, MOUSE_X, is_pressed);
    355 		break;
    356 
    357 	case BTN_Y:
    358 		process_button_input(input->mouse.buttons, MOUSE_Y, is_pressed);
    359 		break;
    360 	}
    361 }
    362 
    363 internal void
    364 wl_pointer_enter(void *data, struct wl_pointer *pointer, u32 serial,
    365 		 struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y)
    366 {
    367 	struct linux_video *video = data;
    368 	video->input.mouse.x = wl_fixed_to_int(x);
    369 	video->input.mouse.y = wl_fixed_to_int(y);
    370 }
    371 
    372 internal void
    373 wl_pointer_leave(void *data, struct wl_pointer *pointer, u32 serial,
    374 		 struct wl_surface *surface)
    375 {
    376 	struct linux_video *video = data;
    377 	video->input.mouse.x = video->input.mouse.y = video->input.mouse.z = 0;
    378 }
    379 
    380 internal void
    381 wl_pointer_motion(void *data, struct wl_pointer *pointer, u32 serial,
    382 		  wl_fixed_t x, wl_fixed_t y)
    383 {
    384 	struct linux_video *video = data;
    385 	video->input.mouse.x = wl_fixed_to_int(x);
    386 	video->input.mouse.y = wl_fixed_to_int(y);
    387 }
    388 
    389 internal void
    390 wl_pointer_button(void *data, struct wl_pointer *pointer, u32 serial,
    391 		  u32 time, u32 button, u32 state)
    392 {
    393 	struct linux_video *video = data;
    394 	process_button(&video->input, button, state);
    395 }
    396 
    397 internal void
    398 wl_pointer_axis(void *data, struct wl_pointer *pointer, u32 serial,
    399 		u32 axis, wl_fixed_t value)
    400 {
    401 	struct linux_video *video = data;
    402 	if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL)
    403 		video->input.mouse.z = wl_fixed_to_int(value);
    404 }
    405 
    406 internal const struct wl_pointer_listener wl_pointer_listener = {
    407 	.enter = wl_pointer_enter,
    408 	.leave = wl_pointer_leave,
    409 	.motion = wl_pointer_motion,
    410 	.button = wl_pointer_button,
    411 	.axis = wl_pointer_axis,
    412 };
    413 
    414 internal void
    415 process_keysym(struct input *input, xkb_keysym_t keysym, b32 is_pressed)
    416 {
    417 	switch (keysym) {
    418 	case XKB_KEY_w:
    419 	case XKB_KEY_Up:
    420 		process_button_input(input->keyboard.buttons, KEYBOARD_UP, is_pressed);
    421 		break;
    422 
    423 	case XKB_KEY_a:
    424 	case XKB_KEY_Left:
    425 		process_button_input(input->keyboard.buttons, KEYBOARD_LEFT, is_pressed);
    426 		break;
    427 
    428 	case XKB_KEY_s:
    429 	case XKB_KEY_Down:
    430 		process_button_input(input->keyboard.buttons, KEYBOARD_DOWN, is_pressed);
    431 		break;
    432 
    433 	case XKB_KEY_d:
    434 	case XKB_KEY_Right:
    435 		process_button_input(input->keyboard.buttons, KEYBOARD_RIGHT, is_pressed);
    436 		break;
    437 
    438 	case XKB_KEY_q:
    439 		process_button_input(input->keyboard.buttons, KEYBOARD_Q, is_pressed);
    440 		break;
    441 
    442 	case XKB_KEY_e:
    443 		process_button_input(input->keyboard.buttons, KEYBOARD_E, is_pressed);
    444 		break;
    445 
    446 	case XKB_KEY_r:
    447 		process_button_input(input->keyboard.buttons, KEYBOARD_R, is_pressed);
    448 		break;
    449 
    450 	case XKB_KEY_t:
    451 		process_button_input(input->keyboard.buttons, KEYBOARD_T, is_pressed);
    452 		break;
    453 
    454 	case XKB_KEY_i:
    455 		process_button_input(input->keyboard.buttons, KEYBOARD_I, is_pressed);
    456 		break;
    457 
    458 	case XKB_KEY_j:
    459 		process_button_input(input->keyboard.buttons, KEYBOARD_J, is_pressed);
    460 		break;
    461 
    462 	case XKB_KEY_k:
    463 		process_button_input(input->keyboard.buttons, KEYBOARD_K, is_pressed);
    464 		break;
    465 
    466 	case XKB_KEY_l:
    467 		process_button_input(input->keyboard.buttons, KEYBOARD_L, is_pressed);
    468 		break;
    469 
    470 	case XKB_KEY_space:
    471 		process_button_input(input->keyboard.buttons, KEYBOARD_SPACE, is_pressed);
    472 		break;
    473 
    474 	case XKB_KEY_Escape:
    475 		process_button_input(input->keyboard.buttons, KEYBOARD_ESCAPE, is_pressed);
    476 		break;
    477 	}
    478 }
    479 
    480 internal void
    481 wl_keyboard_keymap(void *data ,struct wl_keyboard *keyboard, u32 format,
    482 		   int fd, u32 size)
    483 {
    484 	assert(format = WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1);
    485 
    486 	struct linux_video *video = data;
    487 
    488 	char *mapped = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
    489 	assert(mapped != MAP_FAILED);
    490 
    491 	xkb_keymap_unref(video->xkb_keymap);
    492 	video->xkb_keymap = xkb_keymap_new_from_string(video->xkb_context, mapped,
    493 						       XKB_KEYMAP_FORMAT_TEXT_V1,
    494 						       XKB_KEYMAP_COMPILE_NO_FLAGS);
    495 
    496 	munmap(mapped, size);
    497 	close(fd);
    498 
    499 	xkb_state_unref(video->xkb_state);
    500 	video->xkb_state = xkb_state_new(video->xkb_keymap);
    501 }
    502 
    503 internal void
    504 wl_keyboard_enter(void *data, struct wl_keyboard *keyboard, u32 serial,
    505 		  struct wl_surface *surface, struct wl_array *keys)
    506 {
    507 	struct linux_video *video = data;
    508 
    509 	u32 *key;
    510 	wl_array_for_each(key, keys) {
    511 		xkb_keysym_t sym = xkb_state_key_get_one_sym(video->xkb_state, *key + 8);
    512 		process_keysym(&video->input, sym, 1);
    513 	}
    514 }
    515 
    516 internal void
    517 wl_keyboard_leave(void *data, struct wl_keyboard *keyboard, u32 serial,
    518 		  struct wl_surface *surface)
    519 {
    520 }
    521 
    522 internal void
    523 wl_keyboard_key(void *data, struct wl_keyboard *keyboard, u32 serial,
    524 		u32 time, u32 key, u32 state)
    525 {
    526 	struct linux_video *video = data;
    527 	xkb_keysym_t sym = xkb_state_key_get_one_sym(video->xkb_state, key + 8);
    528 	process_keysym(&video->input, sym, state);
    529 }
    530 
    531 internal void
    532 wl_keyboard_modifiers(void *data, struct wl_keyboard *keyboard, u32 serial,
    533 		      u32 depressed, u32 latched, u32 locked, u32 group)
    534 {
    535 	struct linux_video *video = data;
    536 	xkb_state_update_mask(video->xkb_state, depressed, latched, locked, 0, 0, group);
    537 }
    538 
    539 internal const struct wl_keyboard_listener wl_keyboard_listener = {
    540 	.keymap = wl_keyboard_keymap,
    541 	.enter = wl_keyboard_enter,
    542 	.leave = wl_keyboard_leave,
    543 	.key = wl_keyboard_key,
    544 	.modifiers = wl_keyboard_modifiers,
    545 };
    546 
    547 internal void
    548 wl_seat_capabilities(void *data, struct wl_seat *seat, u32 caps)
    549 {
    550 	struct linux_video *video = data;
    551 
    552 	b32 has_pointer = TESTBITS(caps, WL_SEAT_CAPABILITY_POINTER);
    553 	if (has_pointer && !video->pointer) {
    554 		video->pointer = wl_seat_get_pointer(seat);
    555 		wl_pointer_add_listener(video->pointer, &wl_pointer_listener, video);
    556 	} else if (!has_pointer && video->pointer) {
    557 		wl_pointer_release(video->pointer);
    558 		video->pointer = NULL;
    559 	}
    560 
    561 	b32 has_keyboard = TESTBITS(caps, WL_SEAT_CAPABILITY_KEYBOARD);
    562 	if (has_keyboard && !video->keyboard) {
    563 		video->keyboard = wl_seat_get_keyboard(seat);
    564 		wl_keyboard_add_listener(video->keyboard, &wl_keyboard_listener, video);
    565 	} else if (!has_keyboard && video->keyboard) {
    566 		wl_keyboard_release(video->keyboard);
    567 		video->keyboard = NULL;
    568 	}
    569 }
    570 
    571 internal void
    572 wl_seat_name(void *data, struct wl_seat *seat, char const *name)
    573 {
    574 }
    575 
    576 internal const struct wl_seat_listener wl_seat_listener = {
    577 	.capabilities = wl_seat_capabilities,
    578 	.name = wl_seat_name,
    579 };
    580 
    581 internal void
    582 xdg_wm_base_handle_ping(void *data, struct xdg_wm_base *wm_base, u32 serial)
    583 {
    584 	xdg_wm_base_pong(wm_base, serial);
    585 }
    586 
    587 internal const struct xdg_wm_base_listener xdg_wm_base_listener = {
    588 	.ping = xdg_wm_base_handle_ping,
    589 };
    590 
    591 internal void
    592 xdg_surface_handle_configure(void *data, struct xdg_surface *xdg_surface, u32 serial)
    593 {
    594 	xdg_surface_ack_configure(xdg_surface, serial);
    595 }
    596 
    597 internal const struct xdg_surface_listener xdg_surface_listener = {
    598 	.configure = xdg_surface_handle_configure,
    599 };
    600 
    601 internal void
    602 xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *toplevel, s32 width, s32 height, struct wl_array *states)
    603 {
    604 	struct linux_video *video = data;
    605 	video->width = width ? width : RTS_VIDEO_HRES;
    606 	video->height = height ? height : RTS_VIDEO_HRES;
    607 	wl_egl_window_resize(video->egl_window, video->width, video->height, 0, 0);
    608 }
    609 
    610 internal void
    611 xdg_toplevel_handle_close(void *data, struct xdg_toplevel *toplevel)
    612 {
    613 	state.running = false;
    614 }
    615 
    616 internal void
    617 xdg_toplevel_handle_configure_bounds(void *data, struct xdg_toplevel *toplevel, s32 width, s32 height)
    618 {
    619 	struct linux_video *video = data;
    620 	video->width = width ? width : RTS_VIDEO_HRES;
    621 	video->height = height ? height : RTS_VIDEO_HRES;
    622 	wl_egl_window_resize(video->egl_window, video->width, video->height, 0, 0);
    623 }
    624 
    625 internal void
    626 xdg_toplevel_handle_wm_capabilities(void *data, struct xdg_toplevel *toplevel, struct wl_array *caps)
    627 {
    628 	// TODO: handle me, do we care about any special capabilities?
    629 }
    630 
    631 internal const struct xdg_toplevel_listener xdg_toplevel_listener = {
    632 	.configure = xdg_toplevel_handle_configure,
    633 	.close = xdg_toplevel_handle_close,
    634 	.configure_bounds = xdg_toplevel_handle_configure_bounds,
    635 	.wm_capabilities = xdg_toplevel_handle_wm_capabilities,
    636 };
    637 
    638 internal void
    639 wl_registry_handle_global(void *data, struct wl_registry *registry, u32 name, char const *interface, u32 version)
    640 {
    641 	struct linux_video *video = data;
    642 
    643 	dbglog(DEBUG, "wl: interface: %u, name: %s, version: %u\n", name, interface, version);
    644 
    645 	if (strcmp(interface, wl_compositor_interface.name) == 0) {
    646 		video->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 6);
    647 	} else if (strcmp(interface, wl_seat_interface.name) == 0) {
    648 		video->seat = wl_registry_bind(registry, name, &wl_seat_interface, 3);
    649 		wl_seat_add_listener(video->seat, &wl_seat_listener, data);
    650 	} else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
    651 		video->wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 6);
    652 		xdg_wm_base_add_listener(video->wm_base, &xdg_wm_base_listener, data);
    653 	}
    654 }
    655 
    656 internal void
    657 wl_registry_handle_global_remove(void *data, struct wl_registry *registry, u32 name)
    658 {
    659 }
    660 
    661 internal const struct wl_registry_listener wl_registry_listener = {
    662 	.global = wl_registry_handle_global,
    663 	.global_remove = wl_registry_handle_global_remove,
    664 };
    665 
    666 internal b32
    667 init_video(struct arena *arena, struct linux_video *video, char const *title)
    668 {
    669 	video->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
    670 
    671 	if (!(video->display = wl_display_connect(NULL))) {
    672 		dbglog(WARN, "wl_display_connect: could not connect\n");
    673 		return false;
    674 	}
    675 
    676 	video->registry = wl_display_get_registry(video->display);
    677 	wl_registry_add_listener(video->registry, &wl_registry_listener, video);
    678 	wl_display_roundtrip(video->display);
    679 
    680 	assert(video->compositor);
    681 	assert(video->seat);
    682 	assert(video->wm_base);
    683 
    684 	video->surface = wl_compositor_create_surface(video->compositor);
    685 	video->egl_window = wl_egl_window_create(video->surface, RTS_VIDEO_HRES, RTS_VIDEO_VRES);
    686 
    687 	video->xdg_surface = xdg_wm_base_get_xdg_surface(video->wm_base, video->surface);
    688 	xdg_surface_add_listener(video->xdg_surface, &xdg_surface_listener, NULL);
    689 
    690 	video->xdg_toplevel = xdg_surface_get_toplevel(video->xdg_surface);
    691 	xdg_toplevel_add_listener(video->xdg_toplevel, &xdg_toplevel_listener, video);
    692 
    693 	xdg_toplevel_set_app_id(video->xdg_toplevel, title);
    694 	xdg_toplevel_set_title(video->xdg_toplevel, title);
    695 
    696 	wl_surface_commit(video->surface);
    697 	wl_display_roundtrip(video->display);
    698 
    699 	video->egl_display = eglGetDisplay(video->display);
    700 
    701 	EGLint major, minor;
    702 	eglInitialize(video->egl_display, &major, &minor);
    703 
    704 	dbglog(INFO, "EGL version: %d.%d\n", major, minor);
    705 
    706 	eglBindAPI(EGL_OPENGL_API);
    707 
    708 	EGLint attrs[] = {
    709 		EGL_RED_SIZE, 8,
    710 		EGL_GREEN_SIZE, 8,
    711 		EGL_BLUE_SIZE, 8,
    712 		EGL_ALPHA_SIZE, 8,
    713 		EGL_NONE,
    714 	};
    715 
    716 	EGLConfig config;
    717 	EGLint num_config;
    718 	eglChooseConfig(video->egl_display, attrs, &config, 1, &num_config);
    719 
    720 	video->egl_context = eglCreateContext(video->egl_display, config, EGL_NO_CONTEXT, NULL);
    721 
    722 	video->egl_surface = eglCreateWindowSurface(video->egl_display, config, video->egl_window, NULL);
    723 	if (video->egl_surface == EGL_NO_SURFACE) {
    724 		dbglog(WARN, "Failed to create surface!\n");
    725 		return false;
    726 	}
    727 
    728 	eglMakeCurrent(video->egl_display, video->egl_surface, video->egl_surface, video->egl_context);
    729 	eglSwapInterval(video->egl_display, 0); // make eglSwapBuffers non-blocking, at cost of frame tearing
    730 
    731 	if (!(video->renderer = create_renderer(arena, video->width, video->height))) {
    732 		dbglog(DEBUG, "Failed to initialise egl renderer\n");
    733 		return false;
    734 	}
    735 
    736 	return true;
    737 }
    738 
    739 internal void
    740 draw_frame(struct linux_video *video)
    741 {
    742 	eglSwapBuffers(video->egl_display, video->egl_surface);
    743 }
    744 
    745 /* platform api
    746  * ===========================================================================
    747  */
    748 
    749 #ifdef RTS_DEBUG
    750 
    751 DEBUG_PLATFORM_READ_FILE(DEBUG_platform_read_file)
    752 {
    753 	return (struct DEBUG_platform_read_file_result) {0};
    754 }
    755 
    756 DEBUG_PLATFORM_WRITE_FILE(DEBUG_platform_write_file)
    757 {
    758 	return false;
    759 }
    760 
    761 DEBUG_PLATFORM_FREE_FILE(DEBUG_platform_free_file)
    762 {
    763 }
    764 
    765 #endif /* RTS_DEBUG */
    766 
    767 PLATFORM_PRINTF(platform_printf)
    768 {
    769 	va_list ap;
    770 	va_start(ap, fmt);
    771 	size_t res = vprintf(fmt, ap);
    772 	va_end(ap);
    773 
    774 	return res;
    775 }
    776 
    777 internal void
    778 load_platform_api(struct platform_api *api)
    779 {
    780 #ifdef RTS_DEBUG
    781 	api->DEBUG_read_file = DEBUG_platform_read_file;
    782 	api->DEBUG_write_file = DEBUG_platform_write_file;
    783 	api->DEBUG_free_file = DEBUG_platform_free_file;
    784 #endif
    785 	api->printf = platform_printf;
    786 }
    787 
    788 #include "renderer.c"