starfield

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

commit 2d04210e1cce62eccd2d89eec19bab9a26c1c33c
parent c8f59414fcf97183c94188eeab7af21393f0dba6
Author: MikoĊ‚aj Lenczewski <mblenczewski@gmail.com>
Date:   Thu,  9 May 2024 22:48:49 +0000

Initial linux wayland window system

Diffstat:
M.gitignore | 1+
MREADME | 3+++
Mapi/starfield_api.h | 1-
Mbuild_linux.sh | 14+++++++++++---
Mclean.sh | 2+-
Mplatform/linux_starfield.c | 241++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Mplatform/linux_starfield.h | 86++++++++++++++++---------------------------------------------------------------
Mstarfield/starfield.c | 5-----
8 files changed, 192 insertions(+), 161 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,4 +1,5 @@ bin/ +dep/ opt/ **/.*.swp diff --git a/README b/README @@ -11,6 +11,9 @@ Our expected build environment is linux, with the following dependencies: When building for linux, our dependency list is: ```text +- wayland-protocols +- wayland-client +- alsa-libs ``` When building for windows, our dependency list is: diff --git a/api/starfield_api.h b/api/starfield_api.h @@ -61,7 +61,6 @@ struct str_t { #define ASSERT(cond) #endif -#define global extern #define internal static #define func_local static diff --git a/build_linux.sh b/build_linux.sh @@ -1,6 +1,9 @@ #!/bin/sh CC="clang" +PKGCONF="pkg-config" + +WL_PROTO="/usr/share/wayland-protocols" WARNINGS="-Wall -Wextra -Wpedantic -Werror" @@ -8,13 +11,18 @@ CFLAGS="-std=c11 -O0 -g" CPPFLAGS="-DSTARFIELD_DEBUG -Iapi" LDFLAGS="-fuse-ld=lld" +LINUX_LIBS="$($PKGCONF --cflags --libs alsa wayland-client wayland-egl)" + set -ex -mkdir -p bin +mkdir -p bin dep $CC -shared -o bin/starfield.so starfield/starfield.c \ $WARNINGS $CFLAGS $CPPFLAGS $LDFLAGS -nostdlib +wayland-scanner client-header $WL_PROTO/stable/xdg-shell/xdg-shell.xml dep/xdg-shell.h +wayland-scanner private-code $WL_PROTO/stable/xdg-shell/xdg-shell.xml dep/xdg-shell.c + # TODO: how to build a static binary that supports loading starfield.so? -$CC -o bin/linux_starfield platform/linux_starfield.c \ - $WARNINGS $CFLAGS $CPPFLAGS $LDFLAGS -lc -ldl +$CC -o bin/linux_starfield platform/linux_starfield.c dep/xdg-shell.c \ + $WARNINGS $CFLAGS $CPPFLAGS $LDFLAGS -lc -ldl $LINUX_LIBS -Idep diff --git a/clean.sh b/clean.sh @@ -2,4 +2,4 @@ set -ex -rm -rf bin +rm -rf bin dep diff --git a/platform/linux_starfield.c b/platform/linux_starfield.c @@ -50,7 +50,8 @@ main(int argc, char **argv, char **envp) fprintf(stderr, "Failed to initialise platform audio\n"); } - if (!init_video(&state.video)) { + char *display_name = NULL; + if (!init_video(&state.video, display_name)) { fprintf(stderr, "Failed to initialise platform video\n"); _exit(1); } @@ -71,12 +72,8 @@ main(int argc, char **argv, char **envp) state.starfield_api.update(&thread, &state.starfield_memory, &state.platform_api, &input, dt); - struct starfield_video_buffer video = { - 0, - }; - state.starfield_api.render(&thread, &state.starfield_memory, - &state.platform_api, &video, dt); + &state.platform_api, &state.video.buffer, dt); if (state.audio.enabled) { state.starfield_api.sample(&thread, &state.starfield_memory, @@ -108,6 +105,8 @@ main(int argc, char **argv, char **envp) play_audio(&state.audio); } + blit_video(&state.video); + clock_gettime(CLOCK_MONOTONIC, &end_nanos); u64 elapsed_us = linux_elapsed_ns(&start_nanos, &end_nanos) / 1000; @@ -210,85 +209,25 @@ init_audio(struct linux_audio *audio, u32 period_us, u32 buffered_periods) { audio->enabled = false; - void *library = dlopen("libasound.so", RTLD_NOW); - if (!library) return false; - - if (!LOAD_SYMBOL(audio->api.snd_strerror, alsa_snd_strerror_t, "snd_strerror")) - return false; - - if (!LOAD_SYMBOL(audio->api.snd_pcm_open, alsa_snd_pcm_open_t, "snd_pcm_open")) - return false; - - if (!LOAD_SYMBOL(audio->api.snd_pcm_hw_params_sizeof, - alsa_snd_pcm_hw_params_sizeof_t, - "snd_pcm_hw_params_sizeof")) - return false; - - if (!LOAD_SYMBOL(audio->api.snd_pcm_hw_params_any, - alsa_snd_pcm_hw_params_any_t, - "snd_pcm_hw_params_any")) - return false; - - if (!LOAD_SYMBOL(audio->api.snd_pcm_hw_params_set_access, - alsa_snd_pcm_hw_params_set_access_t, - "snd_pcm_hw_params_set_access")) - return false; - - if (!LOAD_SYMBOL(audio->api.snd_pcm_hw_params_set_format, - alsa_snd_pcm_hw_params_set_format_t, - "snd_pcm_hw_params_set_format")) - return false; - - if (!LOAD_SYMBOL(audio->api.snd_pcm_hw_params_set_channels, - alsa_snd_pcm_hw_params_set_channels_t, - "snd_pcm_hw_params_set_channels")) - return false; - - if (!LOAD_SYMBOL(audio->api.snd_pcm_hw_params_set_rate, - alsa_snd_pcm_hw_params_set_rate_t, - "snd_pcm_hw_params_set_rate")) - return false; - - if (!LOAD_SYMBOL(audio->api.snd_pcm_hw_params_set_periods, - alsa_snd_pcm_hw_params_set_periods_t, - "snd_pcm_hw_params_set_periods")) - return false; - - if (!LOAD_SYMBOL(audio->api.snd_pcm_hw_params_set_period_time, - alsa_snd_pcm_hw_params_set_period_time_t, - "snd_pcm_hw_params_set_period_time")) - return false; - - if (!LOAD_SYMBOL(audio->api.snd_pcm_hw_params, - alsa_snd_pcm_hw_params_t, - "snd_pcm_hw_params")) - return false; - - if (!LOAD_SYMBOL(audio->api.snd_pcm_writei, - alsa_snd_pcm_writei_t, - "snd_pcm_writei")) - return false; - - /* initialise audio */ int err; - if ((err = audio->api.snd_pcm_open(&audio->handle, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0) { - fprintf(stderr, "snd_pcm_open: %s\n", audio->api.snd_strerror(err)); + if ((err = snd_pcm_open(&audio->handle, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0) { + fprintf(stderr, "snd_pcm_open: %s\n", snd_strerror(err)); return false; } - snd_pcm_hw_params_t *hw_params = alloca(audio->api.snd_pcm_hw_params_sizeof()); + snd_pcm_hw_params_t *hw_params = alloca(snd_pcm_hw_params_sizeof()); assert(hw_params); - audio->api.snd_pcm_hw_params_any(audio->handle, hw_params); - audio->api.snd_pcm_hw_params_set_access(audio->handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); - audio->api.snd_pcm_hw_params_set_format(audio->handle, hw_params, SND_PCM_FORMAT_S32_LE); - audio->api.snd_pcm_hw_params_set_channels(audio->handle, hw_params, STARFIELD_AUDIO_CHANNELS); - audio->api.snd_pcm_hw_params_set_rate(audio->handle, hw_params, STARFIELD_AUDIO_SAMPLE_RATE, 0); - audio->api.snd_pcm_hw_params_set_periods(audio->handle, hw_params, buffered_periods, 0); - audio->api.snd_pcm_hw_params_set_period_time(audio->handle, hw_params, period_us, 0); + 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, STARFIELD_AUDIO_CHANNELS); + snd_pcm_hw_params_set_rate(audio->handle, hw_params, STARFIELD_AUDIO_SAMPLE_RATE, 0); + snd_pcm_hw_params_set_periods(audio->handle, hw_params, buffered_periods, 0); + snd_pcm_hw_params_set_period_time(audio->handle, hw_params, period_us, 0); - if ((err = audio->api.snd_pcm_hw_params(audio->handle, hw_params)) < 0) { - fprintf(stderr, "and_pcm_hw_params: %s\n", audio->api.snd_strerror(err)); + if ((err = snd_pcm_hw_params(audio->handle, hw_params)) < 0) { + fprintf(stderr, "and_pcm_hw_params: %s\n", snd_strerror(err)); return false; } @@ -319,19 +258,157 @@ play_audio(struct linux_audio *audio) u32 frames = audio->buffer.expected_frames; int err; - if ((err = audio->api.snd_pcm_writei(audio->handle, buf, frames)) < 0) { - fprintf(stderr, "snd_pcm_write: %s\n", audio->api.snd_strerror(err)); + if ((err = snd_pcm_writei(audio->handle, buf, frames)) < 0) { + fprintf(stderr, "snd_pcm_write: %s\n", snd_strerror(err)); + } +} + +internal void +xdg_wm_base_handle_ping(void *data, struct xdg_wm_base *wm_base, u32 serial) +{ + (void) data; + + 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) +{ + (void) data; + + 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) +{ + (void) data; + (void) toplevel; + (void) width; + (void) height; + (void) states; + + // TODO: handle me, pass through a resize event to starfield +} + +internal void +xdg_toplevel_handle_close(void *data, struct xdg_toplevel *toplevel) +{ + (void) data; + (void) toplevel; + + state.running = false; +} + +internal void +xdg_toplevel_handle_configure_bounds(void *data, struct xdg_toplevel *toplevel, s32 width, s32 height) +{ + (void) data; + (void) toplevel; + (void) width; + (void) height; + + // TODO: handle me, pass through a resize event to starfield +} + +internal void +xdg_toplevel_handle_wm_capabilities(void *data, struct xdg_toplevel *toplevel, struct wl_array *caps) +{ + (void) data; + (void) toplevel; + (void) 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; + + fprintf(stderr, "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, 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) +{ + (void) data; + (void) registry; + (void) 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 linux_video *video) +init_video(struct linux_video *video, char const *display_name) { - (void) video; + if (!(video->display = wl_display_connect(display_name))) { + fprintf(stderr, "wl_display_connect: could not connect to %s\n", display_name); + 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->wm_base); + + video->surface = wl_compositor_create_surface(video->compositor); + + video->xdg_surface = xdg_wm_base_get_xdg_surface(video->wm_base, video->surface); + xdg_surface_add_listener(video->xdg_surface, &xdg_surface_listener, video); + + video->toplevel = xdg_surface_get_toplevel(video->xdg_surface); + xdg_toplevel_add_listener(video->toplevel, &xdg_toplevel_listener, video); + + xdg_toplevel_set_app_id(video->toplevel, "starfield"); + xdg_toplevel_set_title(video->toplevel, "starfield"); + + wl_surface_commit(video->surface); + + wl_display_roundtrip(video->display); return true; } +internal void +blit_video(struct linux_video *video) +{ + int res = wl_display_dispatch_pending(video->display); + if (res < 0) { + fprintf(stderr, "wl_display_dispatch_pending error\n"); + return; + } +} + /* platform implementation * =========================================================================== */ diff --git a/platform/linux_starfield.h b/platform/linux_starfield.h @@ -16,6 +16,8 @@ #include <alsa/asoundlib.h> #include <wayland-client.h> +#include <wayland-egl.h> +#include "xdg-shell.h" #include "starfield_api.h" @@ -24,74 +26,7 @@ struct starfield_thread { }; /* alsa state */ -#define ALSA_SND_STRERROR(name) \ - char const *name(int) - -#define ALSA_SND_PCM_OPEN(name) \ - int name(snd_pcm_t **, char const *, snd_pcm_stream_t, int) - -#define ALSA_SND_PCM_HW_PARAMS_SIZEOF(name) \ - size_t name(void) - -#define ALSA_SND_PCM_HW_PARAMS_ANY(name) \ - int name(snd_pcm_t *, snd_pcm_hw_params_t *) - -#define ALSA_SND_PCM_HW_PARAMS_SET_ACCESS(name) \ - int name(snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_access_t) - -#define ALSA_SND_PCM_HW_PARAMS_SET_FORMAT(name) \ - int name(snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_format_t) - -#define ALSA_SND_PCM_HW_PARAMS_SET_CHANNELS(name) \ - int name(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int) - -#define ALSA_SND_PCM_HW_PARAMS_SET_RATE(name) \ - int name(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int, int) - -#define ALSA_SND_PCM_HW_PARAMS_SET_PERIODS(name) \ - int name(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int, int) - -#define ALSA_SND_PCM_HW_PARAMS_SET_PERIOD_TIME(name) \ - int name(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int, int) - -#define ALSA_SND_PCM_HW_PARAMS(name) \ - int name(snd_pcm_t *, snd_pcm_hw_params_t *) - -#define ALSA_SND_PCM_WRITEI(name) \ - int name(snd_pcm_t *, void const *, snd_pcm_uframes_t) - -typedef ALSA_SND_STRERROR(alsa_snd_strerror_t); -typedef ALSA_SND_PCM_OPEN(alsa_snd_pcm_open_t); -typedef ALSA_SND_PCM_HW_PARAMS_SIZEOF(alsa_snd_pcm_hw_params_sizeof_t); -typedef ALSA_SND_PCM_HW_PARAMS_ANY(alsa_snd_pcm_hw_params_any_t); -typedef ALSA_SND_PCM_HW_PARAMS_SET_ACCESS(alsa_snd_pcm_hw_params_set_access_t); -typedef ALSA_SND_PCM_HW_PARAMS_SET_FORMAT(alsa_snd_pcm_hw_params_set_format_t); -typedef ALSA_SND_PCM_HW_PARAMS_SET_CHANNELS(alsa_snd_pcm_hw_params_set_channels_t); -typedef ALSA_SND_PCM_HW_PARAMS_SET_RATE(alsa_snd_pcm_hw_params_set_rate_t); -typedef ALSA_SND_PCM_HW_PARAMS_SET_PERIODS(alsa_snd_pcm_hw_params_set_periods_t); -typedef ALSA_SND_PCM_HW_PARAMS_SET_PERIOD_TIME(alsa_snd_pcm_hw_params_set_period_time_t); -typedef ALSA_SND_PCM_HW_PARAMS(alsa_snd_pcm_hw_params_t); -typedef ALSA_SND_PCM_WRITEI(alsa_snd_pcm_writei_t); - struct linux_audio { - /* api reference: - * https://www.alsa-project.org/alsa-doc/alsa-lib/modules.html - */ - struct { - alsa_snd_strerror_t *snd_strerror; - alsa_snd_pcm_open_t *snd_pcm_open; - alsa_snd_pcm_hw_params_sizeof_t *snd_pcm_hw_params_sizeof; - alsa_snd_pcm_hw_params_any_t *snd_pcm_hw_params_any; - alsa_snd_pcm_hw_params_set_access_t *snd_pcm_hw_params_set_access; - alsa_snd_pcm_hw_params_set_format_t *snd_pcm_hw_params_set_format; - alsa_snd_pcm_hw_params_set_channels_t *snd_pcm_hw_params_set_channels; - alsa_snd_pcm_hw_params_set_rate_t *snd_pcm_hw_params_set_rate; - alsa_snd_pcm_hw_params_set_periods_t *snd_pcm_hw_params_set_periods; - alsa_snd_pcm_hw_params_set_period_time_t *snd_pcm_hw_params_set_period_time; - alsa_snd_pcm_hw_params_t *snd_pcm_hw_params; - alsa_snd_pcm_writei_t *snd_pcm_writei; - } api; - snd_pcm_t *handle; struct starfield_audio_buffer buffer; @@ -101,7 +36,17 @@ struct linux_audio { /* wayland state */ struct linux_video { - u8 pad; + struct wl_display *display; + struct wl_registry *registry; + + struct wl_compositor *compositor; + struct wl_surface *surface; + + struct xdg_wm_base *wm_base; + struct xdg_surface *xdg_surface; + struct xdg_toplevel *toplevel; + + struct starfield_video_buffer buffer; }; struct linux_state { @@ -135,6 +80,9 @@ internal void play_audio(struct linux_audio *audio); internal b32 -init_video(struct linux_video *video); +init_video(struct linux_video *video, char const *display_name); + +internal void +blit_video(struct linux_video *video); #endif /* LINUX_STARFIELD_H */ diff --git a/starfield/starfield.c b/starfield/starfield.c @@ -1,6 +1,5 @@ #include "starfield.h" -global STARFIELD_INIT(starfield_init) { (void) thread; @@ -8,7 +7,6 @@ STARFIELD_INIT(starfield_init) (void) platform; } -global STARFIELD_UPDATE(starfield_update) { (void) thread; @@ -18,7 +16,6 @@ STARFIELD_UPDATE(starfield_update) (void) dt; } -global STARFIELD_RENDER(starfield_render) { (void) thread; @@ -28,7 +25,6 @@ STARFIELD_RENDER(starfield_render) (void) dt; } -global STARFIELD_SAMPLE(starfield_sample) { (void) thread; @@ -59,7 +55,6 @@ STARFIELD_SAMPLE(starfield_sample) } } -global STARFIELD_FREE(starfield_free) { (void) thread;