rts

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

commit 973b00e231554a33e79b152c324301c13ce5414e
Author: MikoĊ‚aj Lenczewski <mikolaj@lenczewski.org>
Date:   Sun, 23 Feb 2025 15:25:06 +0000

Initial commit

Diffstat:
A.editorconfig | 17+++++++++++++++++
A.gitignore | 4++++
Abuild.sh | 31+++++++++++++++++++++++++++++++
Aclean.sh | 5+++++
Aplatform/linux.c | 788+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aplatform/linux.h | 169+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aplatform/renderer.c | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Aplatform/renderer.h | 14++++++++++++++
Arts/api.h | 457+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Arts/rts.c | 179+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Arts/rts.h | 6++++++
Arts/utils.c | 2++
12 files changed, 1722 insertions(+), 0 deletions(-)

diff --git a/.editorconfig b/.editorconfig @@ -0,0 +1,17 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 + +guidelines = 80, 120, 160 + +[*.{c,h}] +indent_style = tab +indent_size = 8 + +[*.{md,txt}] +indent_style = space +indent_size = 2 diff --git a/.gitignore b/.gitignore @@ -0,0 +1,4 @@ +bin/ +dep/ + +**/.*.swp diff --git a/build.sh b/build.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +CC="${CC:-clang}" + +WARNINGS="-Wall -Wextra -Wpedantic ${WERROR:+-Werror} -Wno-unused-parameter" + +FLAGS="-DRTS_DEBUG" # -DRTS_LOGLEVEL=0" + +CFLAGS="-std=c11 -g -O0" +CPPFLAGS="$FLAGS" +LDFLAGS="-nostdlib" + +PLATFORM_CFLAGS="-std=c11 -g -O0" +PLATFORM_CPPFLAGS="$FLAGS" +PLATFORM_LDFLAGS="-Wl,--as-needed" + +PROTOCOLS=/usr/share/wayland-protocols +DEPS="$(pkg-config --cflags --libs alsa xkbcommon wayland-client wayland-egl egl glesv2)" + +set -ex + +mkdir -p bin dep + +$CC -shared -o bin/rts.so rts/rts.c \ + $WARNINGS $CFLAGS $CPPFLAGS $LDFLAGS + +wayland-scanner client-header $PROTOCOLS/stable/xdg-shell/xdg-shell.xml dep/xdg-shell.h +wayland-scanner private-code $PROTOCOLS/stable/xdg-shell/xdg-shell.xml dep/xdg-shell.c + +$CC -o bin/linux_rts platform/linux.c dep/xdg-shell.c \ + $WARNINGS $PLATFORM_CFLAGS $PLATFORM_CPPFLAGS -Idep $PLATFORM_LDFLAGS $DEPS diff --git a/clean.sh b/clean.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +set -ex + +rm -rf bin dep diff --git a/platform/linux.c b/platform/linux.c @@ -0,0 +1,788 @@ +#define RTS_API_IMPL 1 + +#include "linux.h" + +internal struct linux_state state; + +enum debug_level loglevel; + +int +main(int argc, char **argv) +{ +#ifdef RTS_LOGLEVEL + loglevel = RTS_LOGLEVEL; +#else + loglevel = INFO; // WARN; +#endif + + memset(&state, 0, sizeof state); + + load_platform_api(&state.platform_api); + load_renderer_api(&state.renderer_api); + + char game_lib_path[PATH_MAX]; + strcpy(game_lib_path, argv[0]); + strcpy(strrchr(game_lib_path, '/'), "/rts.so"); + + if (!load_game_api(game_lib_path, &state.rts_api)) { + dbglog(ERROR, "Failed to load game api: %s\n", game_lib_path); + exit(EXIT_FAILURE); + } + + struct thread thread = { + .id = 0, + }; + + u64 total_memory = PLATFORM_MEMORY + GAME_MEMORY; + if (!init_memory(&state.memory, total_memory)) { + dbglog(ERROR, "Failed to initialise platform and game memory\n"); + exit(EXIT_FAILURE); + } + + struct arena platform_arena = { + .ptr = state.memory.ptr, + .cap = PLATFORM_MEMORY, + .len = 0, + }; + + struct memory game_memory = { + .ptr = (u8 *) state.memory.ptr + PLATFORM_MEMORY, + .len = GAME_MEMORY, + }; + + f32 target_render_rate = 60.0, target_physics_rate = 60.0; + u32 target_us_per_frame = USECS / target_render_rate; + u32 target_us_per_tick = USECS / target_physics_rate; + + (void) target_us_per_tick; + + u32 period_samples = RTS_AUDIO_SAMPLE_RATE / target_render_rate; + u32 buffer_samples = RTS_AUDIO_SAMPLE_RATE; + if (!init_audio(&platform_arena, &state.audio, + period_samples, buffer_samples)) { + dbglog(ERROR, "Failed to initialise platform audio\n"); + exit(EXIT_FAILURE); + } + + char const *title = "RTS Demo"; + if (!init_video(&platform_arena, &state.video, title)) { + dbglog(ERROR, "Failed to initialise platform video\n"); + exit(EXIT_FAILURE); + } + + if (!state.rts_api.init(&thread, &game_memory, &state.platform_api)) { + dbglog(ERROR, "Failed to initialise rts game\n"); + exit(EXIT_FAILURE); + } + + state.running = 1; + + f32 dt = 1 / target_render_rate; + while (state.running) { + struct timespec frame_start, frame_end; + clock_gettime(CLOCK_MONOTONIC, &frame_start); + + struct timespec update_start, update_end; + clock_gettime(CLOCK_MONOTONIC, &update_start); + + handle_pending_events(&state); + +#ifdef RTS_DEBUG + dbglog(INFO, "input: W/A/S/D: %d/%d/%d/%d , X/Y/Z: %03d/%03d/%03d\n", + state.video.input.keyboard.buttons[KEYBOARD_UP].was_pressed, + state.video.input.keyboard.buttons[KEYBOARD_LEFT].was_pressed, + state.video.input.keyboard.buttons[KEYBOARD_DOWN].was_pressed, + state.video.input.keyboard.buttons[KEYBOARD_RIGHT].was_pressed, + state.video.input.mouse.x, + state.video.input.mouse.y, + state.video.input.mouse.z); +#endif + + state.rts_api.update(&thread, &game_memory, + &state.platform_api, + &state.video.input, + state.video.width, state.video.height, dt); + + clock_gettime(CLOCK_MONOTONIC, &update_end); + + struct timespec sample_start, sample_end; + clock_gettime(CLOCK_MONOTONIC, &sample_start); + + if (state.audio.enabled) { + update_expected_frames(&state.audio, dt); + + state.rts_api.sample(&thread, &game_memory, + &state.platform_api, + &state.audio.audio_buffer, + dt); + + play_audio(&state.audio); + } else { + reset_audio(&state.audio); + } + + clock_gettime(CLOCK_MONOTONIC, &sample_end); + + struct timespec render_start, render_end; + clock_gettime(CLOCK_MONOTONIC, &render_start); + + state.rts_api.render(&thread, &game_memory, + &state.platform_api, + &state.renderer_api, state.video.renderer, + state.video.width, state.video.height, dt); + + draw_frame(&state.video); + + clock_gettime(CLOCK_MONOTONIC, &render_end); + + clock_gettime(CLOCK_MONOTONIC, &frame_end); + + u32 elapsed_us = linux_elapsed_ns(&frame_start, &frame_end) / 1000; + if (elapsed_us < target_us_per_frame) { + struct timespec delay = { + .tv_sec = 0, + .tv_nsec = (target_us_per_frame - elapsed_us) * 1000, + }; + + while (nanosleep(&delay, &delay) < 0); + + clock_gettime(CLOCK_MONOTONIC, &frame_end); + elapsed_us = linux_elapsed_ns(&frame_start, &frame_end) / 1000; + } else { + /* TODO: missed frame */ + } + + dt = (f32) elapsed_us / USECS; + +#ifdef RTS_DEBUG + dbglog(INFO, "timings: " + "update: %" PRIu64 " ns, " + "sample: %" PRIu64 " ns, " + "render: %" PRIu64 " ns, " + "total: %" PRIu64 " ns, " + "\n", + linux_elapsed_ns(&update_start, &update_end), + linux_elapsed_ns(&sample_start, &sample_end), + linux_elapsed_ns(&render_start, &render_end), + linux_elapsed_ns(&frame_start, &frame_end)); +#endif + } + + exit(EXIT_SUCCESS); +} + +internal u64 +linux_elapsed_ns(struct timespec *restrict start, struct timespec *restrict end) +{ + return (end->tv_sec - start->tv_sec) * NSECS + (end->tv_nsec - start->tv_nsec); +} + +#define LOADSYM(val, T, lib, sym) ((val) = (T *) dlsym(lib, sym)) + +internal b32 +load_game_api(char const *path, struct rts_api *api) +{ + void *library = dlopen(path, RTLD_NOW); + if (!library) + return false; + + LOADSYM(api->init, rts_api_init_t, library, "rts_init"); + LOADSYM(api->update, rts_api_update_t, library, "rts_update"); + LOADSYM(api->render, rts_api_render_t, library, "rts_render"); + LOADSYM(api->sample, rts_api_sample_t, library, "rts_sample"); + LOADSYM(api->free, rts_api_free_t, library, "rts_free"); + + return true; +} + +internal b32 +init_memory(struct memory *memory, u64 capacity) +{ +#ifdef RTS_DEBUG + void *base = (void *) (2 * TiB); +#else + void *base = NULL; +#endif + + int prot = PROT_READ | PROT_WRITE; + int flags = MAP_PRIVATE | MAP_ANONYMOUS; + +#ifdef RTS_DEBUG + flags |= MAP_FIXED; +#endif + + memory->len = capacity; + memory->ptr = mmap(base, memory->len, prot, flags, -1, 0); + if (memory->ptr == MAP_FAILED) + return false; + + madvise(memory->ptr, memory->len, MADV_HUGEPAGE); + + return true; +} + +internal void +handle_pending_events(struct linux_state *state) +{ + wl_display_dispatch_pending(state->video.display); +} + +internal b32 +init_audio(struct arena *arena, struct linux_audio *audio, + u32 period_samples, u32 buffer_samples) +{ + audio->enabled = false; + + int err; + if ((err = snd_pcm_open(&audio->handle, "default", + SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + dbglog(WARN, "snd_pcm_open: %s\n", snd_strerror(err)); + return false; + } + + snd_pcm_hw_params_t *hw_params = alloca(snd_pcm_hw_params_sizeof()); + assert(hw_params); + + snd_pcm_hw_params_any(audio->handle, hw_params); + snd_pcm_hw_params_set_access(audio->handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); + snd_pcm_hw_params_set_format(audio->handle, hw_params, SND_PCM_FORMAT_S32_LE); + snd_pcm_hw_params_set_channels(audio->handle, hw_params, RTS_AUDIO_CHANNELS); + snd_pcm_hw_params_set_rate(audio->handle, hw_params, RTS_AUDIO_SAMPLE_RATE, 0); + snd_pcm_hw_params_set_period_size(audio->handle, hw_params, period_samples, 0); + snd_pcm_hw_params_set_buffer_size(audio->handle, hw_params, buffer_samples); + + if ((err = snd_pcm_hw_params(audio->handle, hw_params)) < 0) { + dbglog(WARN, "and_pcm_hw_params: %s\n", snd_strerror(err)); + return false; + } + + int dir; + snd_pcm_hw_params_get_period_time(hw_params, &audio->period_us, &dir); + snd_pcm_hw_params_get_period_size(hw_params, &audio->period_frames, &dir); + snd_pcm_hw_params_get_buffer_size(hw_params, &audio->buffer_frames); + + dbglog(INFO, "Audio period frames: %" PRIu64 ", %" PRIu32 " us\n", + audio->period_frames, audio->period_us); + dbglog(INFO, "Audio buffer frames: %" PRIu64 "\n", audio->buffer_frames); + + audio->audio_buffer.buffer = ALLOC_ARRAY(arena, u8, RTS_AUDIO_FRAME_BYTES * audio->buffer_frames); + assert(audio->audio_buffer.buffer); + + audio->audio_buffer.channels = RTS_AUDIO_CHANNELS; + audio->audio_buffer.sample_rate = RTS_AUDIO_SAMPLE_RATE; + audio->audio_buffer.sample_bits = RTS_AUDIO_SAMPLE_BITS; + audio->audio_buffer.samples = audio->buffer_frames; + + audio->enabled = true; + + return true; +} + +internal void +reset_audio(struct linux_audio *audio) +{ + if (snd_pcm_prepare(audio->handle) == 0) + audio->enabled = true; +} + +internal void +update_expected_frames(struct linux_audio *audio, f32 dt) +{ + snd_pcm_sframes_t total = audio->buffer_frames; + snd_pcm_sframes_t expected = RTS_AUDIO_SAMPLE_RATE * dt; + + snd_pcm_sframes_t avail, delay; + if (snd_pcm_avail_delay(audio->handle, &avail, &delay) < 0) { + audio->enabled = false; + return; + } + + dbglog(DEBUG, "total: %ld, expected: %ld, avail: %ld, pending: %ld\n", + total, expected, avail, delay); + + audio->audio_buffer.samples = avail; +} + +internal void +play_audio(struct linux_audio *audio) +{ + s32 written_samples = snd_pcm_writei(audio->handle, + audio->audio_buffer.buffer, + audio->audio_buffer.samples); + + if (written_samples < 0) { + written_samples = snd_pcm_recover(audio->handle, written_samples, 0); + } if ((u32) written_samples < audio->audio_buffer.samples) { + dbglog(DEBUG, "underrun! wrote %d/%" PRIu32 " samples\n", + written_samples, audio->audio_buffer.samples); + } + + snd_pcm_sframes_t total = audio->buffer_frames, avail, delay; + snd_pcm_avail_delay(audio->handle, &avail, &delay); + dbglog(DEBUG, "total: %ld, written: %d, new avail: %ld, new pending: %ld\n", + total, written_samples, avail, delay); +} + +internal void +process_button_input(struct button_input *buttons, s32 id, b32 is_pressed) +{ + struct button_input *input = buttons + id; + + if (input->was_pressed != is_pressed) + input->half_transition_count++; + + input->was_pressed = is_pressed; +} + +internal void +process_button(struct input *input, u32 button, b32 is_pressed) +{ + switch (button) { + case BTN_LEFT: + process_button_input(input->mouse.buttons, MOUSE_L, is_pressed); + break; + + case BTN_RIGHT: + process_button_input(input->mouse.buttons, MOUSE_R, is_pressed); + break; + + case BTN_MIDDLE: + process_button_input(input->mouse.buttons, MOUSE_M, is_pressed); + break; + + case BTN_X: + process_button_input(input->mouse.buttons, MOUSE_X, is_pressed); + break; + + case BTN_Y: + process_button_input(input->mouse.buttons, MOUSE_Y, is_pressed); + break; + } +} + +internal void +wl_pointer_enter(void *data, struct wl_pointer *pointer, u32 serial, + struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y) +{ + struct linux_video *video = data; + video->input.mouse.x = wl_fixed_to_int(x); + video->input.mouse.y = wl_fixed_to_int(y); +} + +internal void +wl_pointer_leave(void *data, struct wl_pointer *pointer, u32 serial, + struct wl_surface *surface) +{ + struct linux_video *video = data; + video->input.mouse.x = video->input.mouse.y = video->input.mouse.z = 0; +} + +internal void +wl_pointer_motion(void *data, struct wl_pointer *pointer, u32 serial, + wl_fixed_t x, wl_fixed_t y) +{ + struct linux_video *video = data; + video->input.mouse.x = wl_fixed_to_int(x); + video->input.mouse.y = wl_fixed_to_int(y); +} + +internal void +wl_pointer_button(void *data, struct wl_pointer *pointer, u32 serial, + u32 time, u32 button, u32 state) +{ + struct linux_video *video = data; + process_button(&video->input, button, state); +} + +internal void +wl_pointer_axis(void *data, struct wl_pointer *pointer, u32 serial, + u32 axis, wl_fixed_t value) +{ + struct linux_video *video = data; + if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) + video->input.mouse.z = wl_fixed_to_int(value); +} + +internal const struct wl_pointer_listener wl_pointer_listener = { + .enter = wl_pointer_enter, + .leave = wl_pointer_leave, + .motion = wl_pointer_motion, + .button = wl_pointer_button, + .axis = wl_pointer_axis, +}; + +internal void +process_keysym(struct input *input, xkb_keysym_t keysym, b32 is_pressed) +{ + switch (keysym) { + case XKB_KEY_w: + case XKB_KEY_Up: + process_button_input(input->keyboard.buttons, KEYBOARD_UP, is_pressed); + break; + + case XKB_KEY_a: + case XKB_KEY_Left: + process_button_input(input->keyboard.buttons, KEYBOARD_LEFT, is_pressed); + break; + + case XKB_KEY_s: + case XKB_KEY_Down: + process_button_input(input->keyboard.buttons, KEYBOARD_DOWN, is_pressed); + break; + + case XKB_KEY_d: + case XKB_KEY_Right: + process_button_input(input->keyboard.buttons, KEYBOARD_RIGHT, is_pressed); + break; + + case XKB_KEY_q: + process_button_input(input->keyboard.buttons, KEYBOARD_Q, is_pressed); + break; + + case XKB_KEY_e: + process_button_input(input->keyboard.buttons, KEYBOARD_E, is_pressed); + break; + + case XKB_KEY_r: + process_button_input(input->keyboard.buttons, KEYBOARD_R, is_pressed); + break; + + case XKB_KEY_t: + process_button_input(input->keyboard.buttons, KEYBOARD_T, is_pressed); + break; + + case XKB_KEY_i: + process_button_input(input->keyboard.buttons, KEYBOARD_I, is_pressed); + break; + + case XKB_KEY_j: + process_button_input(input->keyboard.buttons, KEYBOARD_J, is_pressed); + break; + + case XKB_KEY_k: + process_button_input(input->keyboard.buttons, KEYBOARD_K, is_pressed); + break; + + case XKB_KEY_l: + process_button_input(input->keyboard.buttons, KEYBOARD_L, is_pressed); + break; + + case XKB_KEY_space: + process_button_input(input->keyboard.buttons, KEYBOARD_SPACE, is_pressed); + break; + + case XKB_KEY_Escape: + process_button_input(input->keyboard.buttons, KEYBOARD_ESCAPE, is_pressed); + break; + } +} + +internal void +wl_keyboard_keymap(void *data ,struct wl_keyboard *keyboard, u32 format, + int fd, u32 size) +{ + assert(format = WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1); + + struct linux_video *video = data; + + char *mapped = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); + assert(mapped != MAP_FAILED); + + xkb_keymap_unref(video->xkb_keymap); + video->xkb_keymap = xkb_keymap_new_from_string(video->xkb_context, mapped, + XKB_KEYMAP_FORMAT_TEXT_V1, + XKB_KEYMAP_COMPILE_NO_FLAGS); + + munmap(mapped, size); + close(fd); + + xkb_state_unref(video->xkb_state); + video->xkb_state = xkb_state_new(video->xkb_keymap); +} + +internal void +wl_keyboard_enter(void *data, struct wl_keyboard *keyboard, u32 serial, + struct wl_surface *surface, struct wl_array *keys) +{ + struct linux_video *video = data; + + u32 *key; + wl_array_for_each(key, keys) { + xkb_keysym_t sym = xkb_state_key_get_one_sym(video->xkb_state, *key + 8); + process_keysym(&video->input, sym, 1); + } +} + +internal void +wl_keyboard_leave(void *data, struct wl_keyboard *keyboard, u32 serial, + struct wl_surface *surface) +{ +} + +internal void +wl_keyboard_key(void *data, struct wl_keyboard *keyboard, u32 serial, + u32 time, u32 key, u32 state) +{ + struct linux_video *video = data; + xkb_keysym_t sym = xkb_state_key_get_one_sym(video->xkb_state, key + 8); + process_keysym(&video->input, sym, state); +} + +internal void +wl_keyboard_modifiers(void *data, struct wl_keyboard *keyboard, u32 serial, + u32 depressed, u32 latched, u32 locked, u32 group) +{ + struct linux_video *video = data; + xkb_state_update_mask(video->xkb_state, depressed, latched, locked, 0, 0, group); +} + +internal const struct wl_keyboard_listener wl_keyboard_listener = { + .keymap = wl_keyboard_keymap, + .enter = wl_keyboard_enter, + .leave = wl_keyboard_leave, + .key = wl_keyboard_key, + .modifiers = wl_keyboard_modifiers, +}; + +internal void +wl_seat_capabilities(void *data, struct wl_seat *seat, u32 caps) +{ + struct linux_video *video = data; + + b32 has_pointer = TESTBITS(caps, WL_SEAT_CAPABILITY_POINTER); + if (has_pointer && !video->pointer) { + video->pointer = wl_seat_get_pointer(seat); + wl_pointer_add_listener(video->pointer, &wl_pointer_listener, video); + } else if (!has_pointer && video->pointer) { + wl_pointer_release(video->pointer); + video->pointer = NULL; + } + + b32 has_keyboard = TESTBITS(caps, WL_SEAT_CAPABILITY_KEYBOARD); + if (has_keyboard && !video->keyboard) { + video->keyboard = wl_seat_get_keyboard(seat); + wl_keyboard_add_listener(video->keyboard, &wl_keyboard_listener, video); + } else if (!has_keyboard && video->keyboard) { + wl_keyboard_release(video->keyboard); + video->keyboard = NULL; + } +} + +internal void +wl_seat_name(void *data, struct wl_seat *seat, char const *name) +{ +} + +internal const struct wl_seat_listener wl_seat_listener = { + .capabilities = wl_seat_capabilities, + .name = wl_seat_name, +}; + +internal void +xdg_wm_base_handle_ping(void *data, struct xdg_wm_base *wm_base, u32 serial) +{ + xdg_wm_base_pong(wm_base, serial); +} + +internal const struct xdg_wm_base_listener xdg_wm_base_listener = { + .ping = xdg_wm_base_handle_ping, +}; + +internal void +xdg_surface_handle_configure(void *data, struct xdg_surface *xdg_surface, u32 serial) +{ + xdg_surface_ack_configure(xdg_surface, serial); +} + +internal const struct xdg_surface_listener xdg_surface_listener = { + .configure = xdg_surface_handle_configure, +}; + +internal void +xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *toplevel, s32 width, s32 height, struct wl_array *states) +{ + struct linux_video *video = data; + video->width = width ? width : RTS_VIDEO_HRES; + video->height = height ? height : RTS_VIDEO_HRES; + wl_egl_window_resize(video->egl_window, video->width, video->height, 0, 0); +} + +internal void +xdg_toplevel_handle_close(void *data, struct xdg_toplevel *toplevel) +{ + state.running = false; +} + +internal void +xdg_toplevel_handle_configure_bounds(void *data, struct xdg_toplevel *toplevel, s32 width, s32 height) +{ + struct linux_video *video = data; + video->width = width ? width : RTS_VIDEO_HRES; + video->height = height ? height : RTS_VIDEO_HRES; + wl_egl_window_resize(video->egl_window, video->width, video->height, 0, 0); +} + +internal void +xdg_toplevel_handle_wm_capabilities(void *data, struct xdg_toplevel *toplevel, struct wl_array *caps) +{ + // TODO: handle me, do we care about any special capabilities? +} + +internal const struct xdg_toplevel_listener xdg_toplevel_listener = { + .configure = xdg_toplevel_handle_configure, + .close = xdg_toplevel_handle_close, + .configure_bounds = xdg_toplevel_handle_configure_bounds, + .wm_capabilities = xdg_toplevel_handle_wm_capabilities, +}; + +internal void +wl_registry_handle_global(void *data, struct wl_registry *registry, u32 name, char const *interface, u32 version) +{ + struct linux_video *video = data; + + dbglog(DEBUG, "wl: interface: %u, name: %s, version: %u\n", name, interface, version); + + if (strcmp(interface, wl_compositor_interface.name) == 0) { + video->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 6); + } else if (strcmp(interface, wl_seat_interface.name) == 0) { + video->seat = wl_registry_bind(registry, name, &wl_seat_interface, 3); + wl_seat_add_listener(video->seat, &wl_seat_listener, data); + } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { + video->wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 6); + xdg_wm_base_add_listener(video->wm_base, &xdg_wm_base_listener, data); + } +} + +internal void +wl_registry_handle_global_remove(void *data, struct wl_registry *registry, u32 name) +{ +} + +internal const struct wl_registry_listener wl_registry_listener = { + .global = wl_registry_handle_global, + .global_remove = wl_registry_handle_global_remove, +}; + +internal b32 +init_video(struct arena *arena, struct linux_video *video, char const *title) +{ + video->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + + if (!(video->display = wl_display_connect(NULL))) { + dbglog(WARN, "wl_display_connect: could not connect\n"); + return false; + } + + video->registry = wl_display_get_registry(video->display); + wl_registry_add_listener(video->registry, &wl_registry_listener, video); + wl_display_roundtrip(video->display); + + assert(video->compositor); + assert(video->seat); + assert(video->wm_base); + + video->surface = wl_compositor_create_surface(video->compositor); + video->egl_window = wl_egl_window_create(video->surface, RTS_VIDEO_HRES, RTS_VIDEO_VRES); + + video->xdg_surface = xdg_wm_base_get_xdg_surface(video->wm_base, video->surface); + xdg_surface_add_listener(video->xdg_surface, &xdg_surface_listener, NULL); + + video->xdg_toplevel = xdg_surface_get_toplevel(video->xdg_surface); + xdg_toplevel_add_listener(video->xdg_toplevel, &xdg_toplevel_listener, video); + + xdg_toplevel_set_app_id(video->xdg_toplevel, title); + xdg_toplevel_set_title(video->xdg_toplevel, title); + + wl_surface_commit(video->surface); + wl_display_roundtrip(video->display); + + video->egl_display = eglGetDisplay(video->display); + + EGLint major, minor; + eglInitialize(video->egl_display, &major, &minor); + + dbglog(INFO, "EGL version: %d.%d\n", major, minor); + + eglBindAPI(EGL_OPENGL_API); + + EGLint attrs[] = { + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_NONE, + }; + + EGLConfig config; + EGLint num_config; + eglChooseConfig(video->egl_display, attrs, &config, 1, &num_config); + + video->egl_context = eglCreateContext(video->egl_display, config, EGL_NO_CONTEXT, NULL); + + video->egl_surface = eglCreateWindowSurface(video->egl_display, config, video->egl_window, NULL); + if (video->egl_surface == EGL_NO_SURFACE) { + dbglog(WARN, "Failed to create surface!\n"); + return false; + } + + eglMakeCurrent(video->egl_display, video->egl_surface, video->egl_surface, video->egl_context); + eglSwapInterval(video->egl_display, 0); // make eglSwapBuffers non-blocking, at cost of frame tearing + + if (!(video->renderer = create_renderer(arena, video->width, video->height))) { + dbglog(DEBUG, "Failed to initialise egl renderer\n"); + return false; + } + + return true; +} + +internal void +draw_frame(struct linux_video *video) +{ + eglSwapBuffers(video->egl_display, video->egl_surface); +} + +/* platform api + * =========================================================================== + */ + +#ifdef RTS_DEBUG + +DEBUG_PLATFORM_READ_FILE(DEBUG_platform_read_file) +{ + return (struct DEBUG_platform_read_file_result) {0}; +} + +DEBUG_PLATFORM_WRITE_FILE(DEBUG_platform_write_file) +{ + return false; +} + +DEBUG_PLATFORM_FREE_FILE(DEBUG_platform_free_file) +{ +} + +#endif /* RTS_DEBUG */ + +PLATFORM_PRINTF(platform_printf) +{ + va_list ap; + va_start(ap, fmt); + size_t res = vprintf(fmt, ap); + va_end(ap); + + return res; +} + +internal void +load_platform_api(struct platform_api *api) +{ +#ifdef RTS_DEBUG + api->DEBUG_read_file = DEBUG_platform_read_file; + api->DEBUG_write_file = DEBUG_platform_write_file; + api->DEBUG_free_file = DEBUG_platform_free_file; +#endif + api->printf = platform_printf; +} + +#include "renderer.c" diff --git a/platform/linux.h b/platform/linux.h @@ -0,0 +1,169 @@ +#ifndef PLATFORM_LINUX_H +#define PLATFORM_LINUX_H + +#define _XOPEN_SOURCE 700 +#define _GNU_SOURCE 1 +#define _DEFAULT_SOURCE 1 + +#include <limits.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include <sys/mman.h> + +#include <dlfcn.h> + +#include <sys/stat.h> +#include <fcntl.h> + +#include <alsa/asoundlib.h> + +#include <xkbcommon/xkbcommon.h> +#include <linux/input-event-codes.h> + +#include <wayland-client.h> +#include "xdg-shell.h" + +#include <wayland-egl.h> +#include <EGL/egl.h> + +#include "../rts/api.h" +#include "renderer.h" + +enum debug_level { + ERROR, + WARN, + INFO, + DEBUG, +}; + +extern enum debug_level loglevel; + +static inline void +dbglog(enum debug_level level, char const *fmt, ...) +{ + if (level > loglevel) + return; + + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); +} + +#define PLATFORM_MEMORY (64 * MiB) +#define GAME_MEMORY (64 * MiB) + +struct linux_thread { + u32 id; +}; + +#if 0 +#define RTS_AUDIO_CHANNELS 2 +#define RTS_AUDIO_SAMPLE_RATE 48000 +#define RTS_AUDIO_SAMPLE_BITS 32 + +#define RTS_AUDIO_FRAME_BYTES \ + (RTS_AUDIO_CHANNELS * (RTS_AUDIO_SAMPLE_BITS / 8)) + +struct audio { + void *buffer; + u32 channels; + u32 sample_rate; + u32 sample_bits; + u32 expected_frames; +}; +#endif + +struct linux_audio { + snd_pcm_t *handle; + + u32 period_us; + snd_pcm_uframes_t period_frames; + snd_pcm_uframes_t buffer_frames; + + struct audio audio_buffer; + + b32 enabled; +}; + +struct linux_video { + struct wl_display *display; + struct wl_registry *registry; + + struct wl_compositor *compositor; + struct wl_seat *seat; + struct xdg_wm_base *wm_base; + + struct wl_pointer *pointer; + struct wl_keyboard *keyboard; + + struct xkb_context *xkb_context; + struct xkb_keymap *xkb_keymap; + struct xkb_state *xkb_state; + + struct input input; + + struct wl_surface *surface; + struct xdg_surface *xdg_surface; + struct xdg_toplevel *xdg_toplevel; + + struct wl_egl_window *egl_window; + EGLDisplay egl_display; + EGLContext egl_context; + EGLSurface egl_surface; + + struct renderer *renderer; + + u32 width, height; +}; + +struct linux_state { + struct memory memory; + + struct linux_audio audio; + struct linux_video video; + + struct platform_api platform_api; + struct renderer_api renderer_api; + struct rts_api rts_api; + + b32 running; +}; + +internal u64 +linux_elapsed_ns(struct timespec *restrict start, struct timespec *restrict end); + +internal b32 +load_game_api(char const *path, struct rts_api *api); + +internal void +load_platform_api(struct platform_api *api); + +internal b32 +init_memory(struct memory *memory, u64 capacity); + +internal void +handle_pending_events(struct linux_state *state); + +internal b32 +init_audio(struct arena *arena, struct linux_audio *audio, + u32 period_samples, u32 buffer_samples); + +internal void +reset_audio(struct linux_audio *audio); + +internal void +update_expected_frames(struct linux_audio *audio, f32 dt); + +internal void +play_audio(struct linux_audio *audio); + +internal b32 +init_video(struct arena *arena, struct linux_video *video, char const *title); + +internal void +draw_frame(struct linux_video *video); + +#endif /* PLATFORM_LINUX_H */ diff --git a/platform/renderer.c b/platform/renderer.c @@ -0,0 +1,50 @@ +#include "renderer.h" + +struct renderer { + u32 width, height; +}; + +internal struct renderer * +create_renderer(struct arena *arena, u32 width, u32 height) +{ + struct renderer *renderer = ALLOC_SIZED(arena, struct renderer); + assert(renderer); + + renderer->width = width; + renderer->height = height; + + return renderer; +} + +RENDERER_BEGIN_FRAME(renderer_begin_frame) +{ + return NULL; +} + +RENDERER_BEGIN_RENDER_PASS(renderer_begin_render_pass) +{ +} + +RENDERER_CLEAR_SCREEN(renderer_clear_screen) +{ + glClearColor(colour->r, colour->g, colour->b, colour->a); + glClear(GL_COLOR_BUFFER_BIT); +} + +RENDERER_END_RENDER_PASS(renderer_end_render_pass) +{ +} + +RENDERER_END_FRAME(renderer_end_frame) +{ +} + +internal void +load_renderer_api(struct renderer_api *api) +{ + api->begin_frame = renderer_begin_frame; + api->begin_render_pass = renderer_begin_render_pass; + api->clear_screen = renderer_clear_screen; + api->end_render_pass = renderer_end_render_pass; + api->end_frame = renderer_end_frame; +} diff --git a/platform/renderer.h b/platform/renderer.h @@ -0,0 +1,14 @@ +#ifndef PLATFORM_RENDERER_H +#define PLATFORM_RENDERER_H + +#include "../rts/api.h" + +#include <GLES/gl.h> + +internal struct renderer * +create_renderer(struct arena *arena, u32 width, u32 height); + +internal void +load_renderer_api(struct renderer_api *api); + +#endif /* PLATFORM_RENDERER_H */ diff --git a/rts/api.h b/rts/api.h @@ -0,0 +1,457 @@ +#ifndef RTS_API_H +#define RTS_API_H + +#include <stdalign.h> +#include <stddef.h> +#include <stdint.h> + +/* type definitions + * =========================================================================== + */ + +typedef int32_t b32; + +#define true 1 +#define false 0 + +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; + +typedef int8_t s8; +typedef int16_t s16; +typedef int32_t s32; +typedef int64_t s64; + +typedef float f32; +typedef double f64; + +typedef unsigned char c8; + +#define KiB 1024ULL +#define MiB (1024 * KiB) +#define GiB (1024 * MiB) +#define TiB (1024 * GiB) + +#define MSECS (1000ULL) +#define USECS (1000 * MSECS) +#define NSECS (1000 * USECS) + +#define ARRLEN(arr) (sizeof (arr) / sizeof (arr)[0]) + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define CLAMP(v, min, max) MIN(MAX(v, min), max) + +#define LERP(v0, v1, t) (((1 - (t)) * (v0)) + ((t) * (v1))) + +#define TESTBITS(v, mask) (((v) & (mask)) == (mask)) + +#define IS_POW2(v) (((v) & ((v) - 1)) == 0) +#define IS_ALIGNED(v, align) (((v) & ~((align) - 1)) == 0) + +#define ALIGN_PREV(v, align) ((v) & ~((align) - 1)) +#define ALIGN_NEXT(v, align) ALIGN_PREV((v) + (align) - 1, (align)) + +#define TO_PARENT(ptr, T, member) ((T *) ((uintptr_t) ptr - offsetof(T, member))) + +#ifdef STARFIELD_DEBUG +# define ASSERT(cond) (!(cond) && *((volatile u8 *) 0) = 0) +#else +# define ASSERT(cond) +#endif + +#define internal static +#define func_local static + +/* utility definitions + * =========================================================================== + */ + +struct str { + c8 *ptr; + u64 len; +}; + +inline c8 * +strnchr(c8 *src, c8 *end, c8 marker) +{ + while (src < end && *src != marker) + src++; + + if (src == end) + return NULL; + + return src; +} + +inline c8 * +strnstr(c8 *src, c8 *end, c8 *marker) +{ + while (src < end) { + c8 *ptr = src; + c8 *cur = marker; + + while (ptr < end && *cur && *cur == *ptr) { + ptr++; + cur++; + } + + if (!cur) + goto found; + + if (ptr == end) + goto not_found; + + src = ptr; + } + +not_found: + return NULL; + +found: + return src; +} + +struct arena { + u8 *ptr; + u64 cap, len; +}; + +inline void +arena_reset(struct arena *arena) +{ + arena->len = 0; +} + +inline void * +arena_alloc(struct arena *arena, size_t size, size_t align) +{ + u64 aligned_len = ALIGN_NEXT(arena->len, align); + if (aligned_len + size > arena->cap) + return NULL; + + void *ptr = (void *) (uintptr_t) (arena->ptr + aligned_len); + arena->len = aligned_len + size; + return ptr; +} + +#define ALLOC_ARRAY(arena, T, n) \ + arena_alloc((arena), sizeof(T) * (n), alignof(T)) + +#define ALLOC_SIZED(arena, T) ALLOC_ARRAY((arena), T, 1) + +struct list_node { + struct list_node *prev, *next; +}; + +#define FROM_NODE(node, T, member) TO_PARENT(node, T, member) + +#define LIST_NODE_ITER(node) \ + for (struct list_node *it = (node); it; it = it->next) + +#define LIST_NODE_RITER(node) \ + for (struct list_node *it = (node); it; it = it->prev) + +struct list { + struct list_node *head, *tail; +}; + +#define LIST_ITER(list) \ + LIST_NODE_ITER((list)->head) + +#define LIST_RITER(list) \ + LIST_NODE_RITER((list)->tail) + +inline void +list_push_head(struct list *restrict list, struct list_node *restrict node) +{ + if (!list->tail) + list->tail = node; + + if (list->head) + list->head->prev = node; + + node->next = list->head; + list->head = node; +} + +inline void +list_push_tail(struct list *restrict list, struct list_node *restrict node) +{ + if (!list->head) + list->head = node; + + if (list->tail) + list->tail->next = node; + + node->prev = list->tail; + list->tail = node; +} + +inline struct list_node * +list_pop_head(struct list *list) +{ + if (!list->head) + return NULL; + + struct list_node *node = list->head; + list->head = node->next; + return node; +} + +inline struct list_node * +list_pop_tail(struct list *list) +{ + if (!list->tail) + return NULL; + + struct list_node *node = list->tail; + list->tail = node->prev; + return node; +} + +/* platform api definitions + * =========================================================================== + */ + +#ifdef RTS_DEBUG + +struct DEBUG_platform_read_file_result { + void *ptr; + u64 len; +}; + +#define DEBUG_PLATFORM_READ_FILE(name) \ + struct DEBUG_platform_read_file_result name(c8 const *path) + +#define DEBUG_PLATFORM_WRITE_FILE(name) \ + b32 name(c8 const *path, void *buf, u64 len) + +#define DEBUG_PLATFORM_FREE_FILE(name) \ + void name(void *ptr) + +typedef DEBUG_PLATFORM_READ_FILE(DEBUG_platform_api_read_file_t); +typedef DEBUG_PLATFORM_WRITE_FILE(DEBUG_platform_api_write_file_t); +typedef DEBUG_PLATFORM_FREE_FILE(DEBUG_platform_api_free_file_t); + +#endif + +#define PLATFORM_PRINTF(name) \ + size_t name(char const *fmt, ...) + +typedef PLATFORM_PRINTF(platform_api_printf_t); + +struct platform_api { +#ifdef RTS_DEBUG + DEBUG_platform_api_read_file_t *DEBUG_read_file; + DEBUG_platform_api_write_file_t *DEBUG_write_file; + DEBUG_platform_api_free_file_t *DEBUG_free_file; +#endif + platform_api_printf_t *printf; +}; + +/* renderer api definitions + * =========================================================================== + */ + +struct renderer; +struct render_frame; + +struct colour { + f32 r, g, b, a; +}; + +#define RENDERER_BEGIN_FRAME(name) \ + struct render_frame *name(struct renderer *renderer, u32 width, u32 height) + +#define RENDERER_BEGIN_RENDER_PASS(name) \ + void name(struct renderer *renderer, struct render_frame *frame) + +#define RENDERER_CLEAR_SCREEN(name) \ + void name(struct renderer *renderer, struct render_frame *frame, struct colour *colour) + +#define RENDERER_END_RENDER_PASS(name) \ + void name(struct renderer *renderer, struct render_frame *frame) + +#define RENDERER_END_FRAME(name) \ + void name(struct renderer *renderer, struct render_frame *frame) + +typedef RENDERER_BEGIN_FRAME(renderer_api_begin_frame_t); +typedef RENDERER_BEGIN_RENDER_PASS(renderer_api_begin_render_pass_t); +typedef RENDERER_CLEAR_SCREEN(renderer_api_clear_screen_t); +typedef RENDERER_END_RENDER_PASS(renderer_api_end_render_pass_t); +typedef RENDERER_END_FRAME(renderer_api_end_frame_t); + +struct renderer_api { + renderer_api_begin_frame_t *begin_frame; + renderer_api_begin_render_pass_t *begin_render_pass; + renderer_api_clear_screen_t *clear_screen; + renderer_api_end_render_pass_t *end_render_pass; + renderer_api_end_frame_t *end_frame; +}; + +/* rts api definitions + * =========================================================================== + */ + +struct thread { + u32 id; +}; + +struct memory { + void *ptr; + u64 len; +}; + +struct button_input { + u32 half_transition_count; + b32 was_pressed; +}; + +enum mouse_button { + MOUSE_L, + MOUSE_R, + MOUSE_M, + + MOUSE_X, + MOUSE_Y, + + _MOUSE_BUTTON_COUNT, +}; + +struct mouse_input { + s16 x, y, z; + struct button_input buttons[_MOUSE_BUTTON_COUNT]; +}; + +enum keyboard_button { + KEYBOARD_ESCAPE, + + KEYBOARD_UP, + KEYBOARD_LEFT, + KEYBOARD_DOWN, + KEYBOARD_RIGHT, + + KEYBOARD_Q, + KEYBOARD_E, + KEYBOARD_R, + KEYBOARD_T, + + KEYBOARD_I, + KEYBOARD_J, + KEYBOARD_K, + KEYBOARD_L, + + KEYBOARD_SPACE, + KEYBOARD_SHIFT, + KEYBOARD_CTRL, + + _KEYBOARD_BUTTON_COUNT, +}; + +struct keyboard_input { + struct button_input buttons[_KEYBOARD_BUTTON_COUNT]; +}; + +struct input { + struct mouse_input mouse; + struct keyboard_input keyboard; +}; + +#define RTS_VIDEO_HRES 1024 // 1920 +#define RTS_VIDEO_VRES 576 // 1080 + +#define RTS_VIDEO_BYTES_PER_PIXEL 4 + +#define RTS_AUDIO_CHANNELS 2 +#define RTS_AUDIO_SAMPLE_RATE 48000 +#define RTS_AUDIO_SAMPLE_BITS 32 + +#define RTS_AUDIO_FRAME_BYTES \ + (RTS_AUDIO_CHANNELS * (RTS_AUDIO_SAMPLE_BITS / 8)) + +struct audio { + void *buffer; + u32 channels; + u32 sample_rate; + u32 sample_bits; + u32 samples; +}; + +#define RTS_INIT(name) \ + b32 name(struct thread *thread, \ + struct memory *memory, \ + struct platform_api *platform) + +#define RTS_FREE(name) \ + void name(struct thread *thread, \ + struct memory *memory, \ + struct platform_api *platform) + +#define RTS_UPDATE(name) \ + void name(struct thread *thread, \ + struct memory *memory, \ + struct platform_api *platform, \ + struct input *input, \ + u32 width, u32 height, f32 dt) + +#define RTS_SAMPLE(name) \ + void name(struct thread *thread, \ + struct memory *memory, \ + struct platform_api *platform, \ + struct audio *audio, \ + f32 dt) + +#define RTS_RENDER(name) \ + void name(struct thread *thread, \ + struct memory *memory, \ + struct platform_api *platform, \ + struct renderer_api *renderer_api, \ + struct renderer *renderer, \ + u32 width, u32 height, f32 dt) + +typedef RTS_INIT(rts_api_init_t); +typedef RTS_FREE(rts_api_free_t); +typedef RTS_UPDATE(rts_api_update_t); +typedef RTS_SAMPLE(rts_api_sample_t); +typedef RTS_RENDER(rts_api_render_t); + +struct rts_api { + rts_api_init_t *init; + rts_api_free_t *free; + rts_api_update_t *update; + rts_api_sample_t *sample; + rts_api_render_t *render; +}; + +#endif /* RTS_API_H */ + +#ifdef RTS_API_IMPL + +extern inline c8 * +strnchr(c8 *src, c8 *end, c8 marker); + +extern inline c8 * +strnstr(c8 *src, c8 *end, c8 *marker); + +extern inline void +arena_reset(struct arena *arena); + +extern inline void * +arena_alloc(struct arena *arena, size_t size, size_t align); + +extern inline void +list_push_head(struct list *list, struct list_node *node); + +extern inline void +list_push_tail(struct list *list, struct list_node *node); + +extern inline struct list_node * +list_pop_head(struct list *list); + +extern inline struct list_node * +list_pop_tail(struct list *list); + +#endif diff --git a/rts/rts.c b/rts/rts.c @@ -0,0 +1,179 @@ +#include "rts.h" + +static inline void * +memset(void *dst, unsigned char val, size_t len) +{ + unsigned char *lhs = dst; + while (len--) *lhs++ = val; + return dst; +} + +#if 0 + +static inline void * +memcpy(void *dst, void *src, size_t len) +{ + unsigned char *lhs = dst, *rhs = src; + while (len--) *lhs++ = *rhs++; + return dst; +} + +#endif + +struct sound { + s32 hz; + s32 volume; + u32 samples; +}; + +struct mixer_sound { + struct sound sound; + u32 ctr; + + struct list_node list_node; +}; + +struct mixer { + struct list sounds; +}; + +struct rts_state { + s32 tone_hz; + s32 tone_volume; + f32 tone_t; + + struct mixer_sound sound_a, sound_b; + + struct mixer mixer; +}; + +#define TONE_HZ_MIN 64 +#define TONE_HZ_MAX 1024 + +#define TONE_VOLUME_MIN (INT32_MAX / 16) +#define TONE_VOLUME_MAX (INT32_MAX / 8) + +RTS_INIT(rts_init) +{ + struct rts_state *state = memory->ptr; + + state->tone_hz = 0; + state->tone_volume = 0; + state->tone_t = 0; + + state->sound_a.sound.hz = 256; + state->sound_a.sound.volume = INT32_MAX / 16; + state->sound_a.sound.samples= RTS_AUDIO_SAMPLE_RATE; + state->sound_a.ctr = 0; + state->sound_a.list_node.prev = state->sound_a.list_node.next = NULL; + + state->sound_b.sound.hz = 1024; + state->sound_b.sound.volume = INT32_MAX / 24; + state->sound_b.sound.samples= RTS_AUDIO_SAMPLE_RATE / 2; + state->sound_b.ctr = 0; + state->sound_b.list_node.prev = state->sound_b.list_node.next = NULL; + + state->mixer.sounds.head = state->mixer.sounds.tail = NULL; + + return true; +} + +RTS_FREE(rts_free) +{ +} + +RTS_UPDATE(rts_update) +{ + struct rts_state *state = memory->ptr; + + if (input->keyboard.buttons[KEYBOARD_Q].was_pressed) + list_push_tail(&state->mixer.sounds, &state->sound_a.list_node); + + if (input->keyboard.buttons[KEYBOARD_E].was_pressed) + list_push_tail(&state->mixer.sounds, &state->sound_b.list_node); + + if (input->keyboard.buttons[KEYBOARD_R].was_pressed) + state->mixer.sounds.head = state->mixer.sounds.tail = NULL; +} + +RTS_SAMPLE(rts_sample) +{ + struct rts_state *state = memory->ptr; + + const f32 PI = 3.1415926535f; + +#if 1 + memset(audio->buffer, 0, audio->samples * RTS_AUDIO_FRAME_BYTES); + + struct list new_sound_list; + memset(&new_sound_list, 0, sizeof new_sound_list); + + size_t active_sounds = 0; + LIST_ITER(&state->mixer.sounds) { + struct mixer_sound *sound_info = FROM_NODE(it, struct mixer_sound, list_node); + + u32 available_samples = MIN(audio->samples, sound_info->sound.samples); + u32 wave_period = audio->sample_rate / sound_info->sound.hz; + + f64 t = 0, hz = sound_info->sound.hz; + s32 *ptr = audio->buffer; + + platform->printf("sound %zu: samples: %u/%u\n", + active_sounds, + available_samples, sound_info->sound.samples); + +#if 0 + for (size_t i = 0; i < available_samples; i++) { + f64 v = 0; + __asm__ volatile ("fsin" : "=t" (v) : "0" (hz)); + + s32 pcm = v * sound_info->sound.volume; + for (u32 j = 0; j < audio->channels; j++) + *ptr++ += pcm; + + t += 2 * PI / wave_period; + if (t >= 2 * PI) + t -= 2 * PI; + } +#endif + + sound_info->ctr += available_samples; + if (sound_info->ctr < sound_info->sound.samples) { + active_sounds++; + } else { + // TODO: mark sound as inactive + } + } + + platform->printf("active sounds: %zu\n", active_sounds); + + state->mixer.sounds = new_sound_list; +#else + s32 *sample = audio->buffer; + for (u32 i = 0; i < audio->samples; i++) { + f64 v = 0; + __asm__ volatile ("fsin" : "=t" (v) : "0" (state->tone_t)); + + int32_t sample_value = (int32_t) (v * state->tone_volume); + for (u32 j = 0; j < audio->channels; j++) + *sample++ = sample_value; + + state->tone_t += 2 * PI / wave_period; + if (state->tone_t >= 2 * PI) + state->tone_t -= 2 * PI; + } +#endif +} + +RTS_RENDER(rts_render) +{ + struct render_frame *frame = renderer_api->begin_frame(renderer, width, height); + ASSERT(frame); + + struct colour colour = { 0.0f, 0.0f, 0.0f, 1.0f, }; + renderer_api->clear_screen(renderer, frame, &colour); + + renderer_api->end_frame(renderer, frame); +} + +#include "utils.c" diff --git a/rts/rts.h b/rts/rts.h @@ -0,0 +1,6 @@ +#ifndef RTS_H +#define RTS_H + +#include "api.h" + +#endif /* RTS_H */ diff --git a/rts/utils.c b/rts/utils.c @@ -0,0 +1,2 @@ +#define RTS_API_IMPL +#include "api.h"