commit ff54c2437ae91570adfc8b390f54263ef4e4fa01
parent 2d04210e1cce62eccd2d89eec19bab9a26c1c33c
Author: MikoĊaj Lenczewski <mblenczewski@gmail.com>
Date: Sun, 12 May 2024 16:19:44 +0000
Add vulkand renderer backend
Diffstat:
19 files changed, 1510 insertions(+), 231 deletions(-)
diff --git a/README b/README
@@ -14,6 +14,7 @@ When building for linux, our dependency list is:
- wayland-protocols
- wayland-client
- alsa-libs
+- vulkan
```
When building for windows, our dependency list is:
diff --git a/api/starfield/intrinsics.h b/api/starfield/intrinsics.h
diff --git a/api/starfield/maths.h b/api/starfield/maths.h
diff --git a/api/starfield/types.h b/api/starfield/types.h
@@ -0,0 +1,64 @@
+#ifndef STARFIELD_TYPES_H
+#define STARFIELD_TYPES_H
+
+#include <inttypes.h>
+#include <limits.h>
+#include <stdalign.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+typedef int32_t b32;
+
+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;
+
+struct str_t {
+ c8 *ptr;
+ u64 len;
+};
+
+#define KiB (1024ULL)
+#define MiB (1024 * KiB)
+#define GiB (1024 * MiB)
+#define TiB (1024 * GiB)
+
+#define MSECS (1000ULL)
+#define USECS (MSECS * 1000)
+#define NSECS (USECS * 1000)
+
+#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 TESTBITS(v, mask) (((v) & (mask)) == (mask))
+
+#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))
+
+#ifdef STARFIELD_DEBUG
+ #define ASSERT(cond) if (!(cond)) { *((volatile u8 *) 0) = 0; }
+#else
+ #define ASSERT(cond)
+#endif
+
+#define internal static
+#define func_local static
+
+#endif /* STARFIELD_TYPES_H */
diff --git a/api/starfield_api.c b/api/starfield_api.c
@@ -1,7 +0,0 @@
-#include "starfield_api.h"
-
-extern inline void
-arena_reset(struct starfield_arena *arena);
-
-extern inline void *
-arena_alloc(struct starfield_arena *arena, u64 size, u64 alignment);
diff --git a/api/starfield_api.h b/api/starfield_api.h
@@ -5,64 +5,9 @@
extern "C" {
#endif /* __cplusplus */
-#include <inttypes.h>
-#include <limits.h>
-#include <stdalign.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <stdint.h>
-
-typedef int32_t b32;
-
-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;
-
-struct str_t {
- c8 *ptr;
- u64 len;
-};
-
-#define KiB (1024ULL)
-#define MiB (1024 * KiB)
-#define GiB (1024 * MiB)
-#define TiB (1024 * GiB)
-
-#define MSECS (1000ULL)
-#define USECS (MSECS * 1000)
-#define NSECS (USECS * 1000)
-
-#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 TESTBITS(v, mask) (((v) & (mask)) == (mask))
-
-#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))
-
-#ifdef STARFIELD_DEBUG
- #define ASSERT(cond) if (!(cond)) { *((volatile u8 *) 0) = 0; }
-#else
- #define ASSERT(cond)
-#endif
-
-#define internal static
-#define func_local static
+#include "starfield/types.h"
+#include "starfield/maths.h"
+#include "starfield/intrinsics.h"
/* platform api definitions */
@@ -96,43 +41,65 @@ struct platform_api {
#endif
};
-/* starfield api definitions */
+/* renderer api definitions */
-#define STARFIELD_DATADIR "data"
-#define STARFIELD_TEMPDIR "temp"
+struct renderer;
+struct render_frame;
-struct starfield_thread;
+#define RENDER_BEGIN_FRAME(name) \
+ struct render_frame *name(struct renderer *renderer, u32 width, u32 height)
-struct starfield_arena {
- void *ptr;
- u64 cap, len;
-};
+#define RENDER_BEGIN_RENDER_PASS(name) \
+ void name(struct renderer *renderer, struct render_frame *frame)
-inline void
-arena_reset(struct starfield_arena *arena)
-{
- arena->len = 0;
-}
+#define RENDER_BIND_PIPELINE(name) \
+ void name(struct renderer *renderer, struct render_frame *frame)
-inline void *
-arena_alloc(struct starfield_arena *arena, u64 size, u64 alignment)
-{
- u64 aligned_len = ALIGN_NEXT(arena->len, alignment);
+#define RENDER_SET_VIEWPORT(name) \
+ void name(struct renderer *renderer, struct render_frame *frame)
- if (arena->cap < aligned_len + size)
- return NULL;
+#define RENDER_SET_SCISSOR(name) \
+ void name(struct renderer *renderer, struct render_frame *frame)
- void *ptr = (u8 *) arena->ptr + aligned_len;
- arena->len = aligned_len + size;
+#define RENDER_DRAW_VERTICES(name) \
+ void name(struct renderer *renderer, struct render_frame *frame)
- return ptr;
-}
+#define RENDER_END_RENDER_PASS(name) \
+ void name(struct renderer *renderer, struct render_frame *frame)
+
+#define RENDER_END_FRAME(name) \
+ void name(struct renderer *renderer, struct render_frame *frame)
+
+typedef RENDER_BEGIN_FRAME(render_api_begin_frame_t);
+typedef RENDER_BEGIN_RENDER_PASS(render_api_begin_render_pass_t);
+typedef RENDER_BIND_PIPELINE(render_api_bind_pipeline_t);
+typedef RENDER_SET_VIEWPORT(render_api_set_viewport_t);
+typedef RENDER_SET_SCISSOR(render_api_set_scissor_t);
+typedef RENDER_DRAW_VERTICES(render_api_draw_vertices_t);
+typedef RENDER_END_RENDER_PASS(render_api_end_render_pass_t);
+typedef RENDER_END_FRAME(render_api_end_frame_t);
+
+struct render_api {
+ render_api_begin_frame_t *begin_frame;
+ render_api_begin_render_pass_t *begin_render_pass;
+ render_api_bind_pipeline_t *bind_pipeline;
+ render_api_set_viewport_t *set_viewport;
+ render_api_set_scissor_t *set_scissor;
+ render_api_draw_vertices_t *draw_vertices;
+ render_api_end_render_pass_t *end_render_pass;
+ render_api_end_frame_t *end_frame;
+};
+
+/* starfield api definitions */
-#define PUSH_SIZED(arena, T) arena_alloc(arena, sizeof(T), alignof(T))
-#define PUSH_ARRAY(arena, T, n) arena_alloc(arena, sizeof(T) * (n), alignof(T))
+#define STARFIELD_DATADIR "data"
+#define STARFIELD_TEMPDIR "temp"
+
+struct starfield_thread;
struct starfield_memory {
- struct starfield_arena permanent, temporary;
+ void *ptr;
+ u64 cap, len;
};
struct starfield_button_input {
@@ -192,12 +159,6 @@ struct starfield_input {
#define STARFIELD_VIDEO_BYTES_PER_PIXEL 4
-struct starfield_video_buffer {
- void *buffer;
- u32 width, height;
- u32 pitch;
-};
-
#define STARFIELD_AUDIO_CHANNELS 2
#define STARFIELD_AUDIO_SAMPLE_RATE 48000
#define STARFIELD_AUDIO_SAMPLE_BITS 32
@@ -214,7 +175,7 @@ struct starfield_audio_buffer {
};
#define STARFIELD_INIT(name) \
- void name(struct starfield_thread *thread, \
+ b32 name(struct starfield_thread *thread, \
struct starfield_memory *memory, \
struct platform_api *platform)
@@ -229,8 +190,9 @@ struct starfield_audio_buffer {
void name(struct starfield_thread *thread, \
struct starfield_memory *memory, \
struct platform_api *platform, \
- struct starfield_video_buffer *video, \
- f32 dt)
+ struct render_api *render_api, \
+ struct renderer *renderer, \
+ u32 width, u32 height, f32 dt)
#define STARFIELD_SAMPLE(name) \
void name(struct starfield_thread *thread, \
diff --git a/build_linux.sh b/build_linux.sh
@@ -1,28 +1,28 @@
#!/bin/sh
-CC="clang"
-PKGCONF="pkg-config"
-
-WL_PROTO="/usr/share/wayland-protocols"
+PROTOCOLS="/usr/share/wayland-protocols"
WARNINGS="-Wall -Wextra -Wpedantic -Werror"
CFLAGS="-std=c11 -O0 -g"
CPPFLAGS="-DSTARFIELD_DEBUG -Iapi"
-LDFLAGS="-fuse-ld=lld"
+LDFLAGS="-fuse-ld=lld -Wl,--as-needed"
-LINUX_LIBS="$($PKGCONF --cflags --libs alsa wayland-client wayland-egl)"
+LINUX_LIBS="$(pkg-config --cflags --libs alsa wayland-client vulkan)"
set -ex
mkdir -p bin dep
-$CC -shared -o bin/starfield.so starfield/starfield.c \
+clang -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
+glslc -mfmt=c shaders/shader.vert -o dep/shader.vert.spv.inc
+glslc -mfmt=c shaders/shader.frag -o dep/shader.frag.spv.inc
+
+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
-# TODO: how to build a static binary that supports loading starfield.so?
-$CC -o bin/linux_starfield platform/linux_starfield.c dep/xdg-shell.c \
- $WARNINGS $CFLAGS $CPPFLAGS $LDFLAGS -lc -ldl $LINUX_LIBS -Idep
+# TODO: can we build a static binary that supports loading starfield.so?
+clang -o bin/linux_starfield platform/linux_starfield.c dep/xdg-shell.c -Idep \
+ $WARNINGS $CFLAGS $CPPFLAGS $LDFLAGS -lc -ldl $LINUX_LIBS
diff --git a/build_win.sh b/build_win.sh
@@ -24,10 +24,13 @@ WIN_LIBS="-luser32 -lgdi32 -lwinmm -lksuser -luuid"
set -ex
-mkdir -p bin
+mkdir -p bin dep
$CC -shared -o bin/starfield.dll starfield/starfield.c \
$WARNINGS $CFLAGS $CPPFLAGS $LDFLAGS $EXPORTS
-$CC -static -o bin/win_starfield.exe platform/win_starfield.c \
+glslc -mfmt=c shaders/shader.vert -o dep/shader.vert.spv.inc
+glslc -mfmt=c shaders/shader.frag -o dep/shader.frag.spv.inc
+
+$CC -static -o bin/win_starfield.exe platform/win_starfield.c -Idep \
$WARNINGS $CFLAGS $CPPFLAGS $LDFLAGS $WIN_LIBS -Wl,/subsystem:windows
diff --git a/platform/linux_starfield.c b/platform/linux_starfield.c
@@ -9,6 +9,9 @@ main(int argc, char **argv, char **envp)
(void) argc;
(void) envp;
+ state.platform_api = load_platform_api();
+ state.render_api = load_render_api();
+
/* NOTE: we expect the library to be alongside our platform-specific
* executable. the unix loader does not search this path by default,
* so we build the absolute path to the library here as a workaround
@@ -22,19 +25,14 @@ main(int argc, char **argv, char **envp)
_exit(1);
}
- state.platform_api = load_platform_api();
-
struct starfield_thread thread = {
.id = 0,
};
- if (!init_memory(&state.platform_memory, 64 * MiB, 64 * MiB)) {
- fprintf(stderr, "Failed to initialise platform memory\n");
- _exit(1);
- }
-
- if (!init_memory(&state.starfield_memory, 64 * MiB, 64 * MiB)) {
- fprintf(stderr, "Failed to initialise starfield memory\n");
+ if (!init_memory(&state.platform_arena, 32 * MiB,
+ &state.renderer_arena, 32 * MiB,
+ &state.starfield_arena, 64 * MiB)) {
+ fprintf(stderr, "Failed to initialise memory\n");
_exit(1);
}
@@ -46,17 +44,33 @@ main(int argc, char **argv, char **envp)
(void) target_us_per_tick;
u32 period_us = target_us_per_frame, buffered_periods = 2;
- if (!init_audio(&state.audio, period_us, buffered_periods)) {
+ if (!init_audio(&state.platform_arena, &state.audio, period_us, buffered_periods)) {
fprintf(stderr, "Failed to initialise platform audio\n");
}
char *display_name = NULL;
- if (!init_video(&state.video, display_name)) {
+ if (!init_video(&state.platform_arena, &state.video, display_name)) {
fprintf(stderr, "Failed to initialise platform video\n");
_exit(1);
}
- state.starfield_api.init(&thread, &state.starfield_memory, &state.platform_api);
+ if (!init_vulkan(&state.renderer_arena, &state.vulkan,
+ state.video.display, state.video.surface,
+ state.video.width, state.video.height)) {
+ fprintf(stderr, "Failed to initialise platform renderer\n");
+ _exit(1);
+ }
+
+ struct starfield_memory starfield_memory = {
+ .ptr = state.starfield_arena.ptr,
+ .cap = state.starfield_arena.cap,
+ .len = state.starfield_arena.len,
+ };
+
+ if (!state.starfield_api.init(&thread, &starfield_memory, &state.platform_api)) {
+ fprintf(stderr, "Failed to initialise starfield\n");
+ _exit(1);
+ }
state.running = 1;
@@ -65,18 +79,26 @@ main(int argc, char **argv, char **envp)
struct timespec start_nanos, end_nanos;
clock_gettime(CLOCK_MONOTONIC, &start_nanos);
+ fprintf(stderr, "Timings: ");
+
+ struct timespec update_start, update_end;
+ clock_gettime(CLOCK_MONOTONIC, &update_start);
+
struct starfield_input input = {
0,
};
- state.starfield_api.update(&thread, &state.starfield_memory,
+ state.starfield_api.update(&thread, &starfield_memory,
&state.platform_api, &input, dt);
- state.starfield_api.render(&thread, &state.starfield_memory,
- &state.platform_api, &state.video.buffer, dt);
+ clock_gettime(CLOCK_MONOTONIC, &update_end);
+ fprintf(stderr, "update: %" PRIu64 " ns, ",
+ linux_elapsed_ns(&update_start, &update_end));
if (state.audio.enabled) {
- state.starfield_api.sample(&thread, &state.starfield_memory,
+ struct timespec sample_start, sample_end;
+ clock_gettime(CLOCK_MONOTONIC, &sample_start);
+ state.starfield_api.sample(&thread, &starfield_memory,
&state.platform_api, &state.audio.buffer, dt);
#ifdef STARFIELD_DEBUG
@@ -103,9 +125,23 @@ main(int argc, char **argv, char **envp)
#endif
play_audio(&state.audio);
+
+ clock_gettime(CLOCK_MONOTONIC, &sample_end);
+ fprintf(stderr, "sample: %" PRIu64 " ns, ",
+ linux_elapsed_ns(&sample_start, &sample_end));
}
- blit_video(&state.video);
+ struct timespec render_start, render_end;
+ clock_gettime(CLOCK_MONOTONIC, &render_start);
+ state.starfield_api.render(&thread, &starfield_memory, &state.platform_api,
+ &state.render_api, state.vulkan.renderer,
+ state.video.width, state.video.height, dt);
+
+ draw_frame(&state.video);
+
+ clock_gettime(CLOCK_MONOTONIC, &render_end);
+ fprintf(stderr, "render: %" PRIu64 " ns, ",
+ linux_elapsed_ns(&render_start, &render_end));
clock_gettime(CLOCK_MONOTONIC, &end_nanos);
@@ -125,9 +161,13 @@ main(int argc, char **argv, char **envp)
}
dt = (f32) elapsed_us / USECS;
+
+ fprintf(stderr, "total: %f\n", dt);
}
- state.starfield_api.free(&thread, &state.starfield_memory, &state.platform_api);
+ state.starfield_api.free(&thread, &starfield_memory, &state.platform_api);
+
+ free_vulkan(&state.vulkan);
_exit(0);
}
@@ -147,26 +187,19 @@ load_starfield_api(char const *filename, struct starfield_api *api)
void *library = dlopen(filename, RTLD_NOW);
if (!library) return false;
- if (!LOAD_SYMBOL(api->init, starfield_api_init_t, "starfield_init"))
- return false;
-
- if (!LOAD_SYMBOL(api->update, starfield_api_update_t, "starfield_update"))
- return false;
-
- if (!LOAD_SYMBOL(api->render, starfield_api_render_t, "starfield_render"))
- return false;
-
- if (!LOAD_SYMBOL(api->sample, starfield_api_sample_t, "starfield_sample"))
- return false;
-
- if (!LOAD_SYMBOL(api->free, starfield_api_free_t, "starfield_free"))
- return false;
+ LOAD_SYMBOL(api->init, starfield_api_init_t, "starfield_init");
+ LOAD_SYMBOL(api->update, starfield_api_update_t, "starfield_update");
+ LOAD_SYMBOL(api->render, starfield_api_render_t, "starfield_render");
+ LOAD_SYMBOL(api->sample, starfield_api_sample_t, "starfield_sample");
+ LOAD_SYMBOL(api->free, starfield_api_free_t, "starfield_free");
return true;
}
internal b32
-init_memory(struct starfield_memory *memory, u64 permanent_memory_cap, u64 temporary_memory_cap)
+init_memory(struct arena *platform_arena, u64 platform_capacity,
+ struct arena *renderer_arena, u64 renderer_capacity,
+ struct arena *starfield_arena, u64 starfield_capacity)
{
#ifdef STARFIELD_DEBUG
void *base_addr = (void *) (2 * TiB);
@@ -174,7 +207,7 @@ init_memory(struct starfield_memory *memory, u64 permanent_memory_cap, u64 tempo
void *base_addr = NULL;
#endif
- u64 len = permanent_memory_cap + temporary_memory_cap;
+ u64 len = platform_capacity + renderer_capacity + starfield_capacity;
int prot = PROT_READ | PROT_WRITE;
int flags = MAP_PRIVATE | MAP_ANONYMOUS;
@@ -193,19 +226,23 @@ init_memory(struct starfield_memory *memory, u64 permanent_memory_cap, u64 tempo
/* TODO: can we make this faster? can we avoid this entirely? */
memset(base_addr, 0, len);
- memory->permanent.ptr = ptr;
- memory->permanent.cap = permanent_memory_cap;
- memory->permanent.len = 0;
+ platform_arena->ptr = ptr;
+ platform_arena->cap = platform_capacity;
+ platform_arena->len = 0;
- memory->temporary.ptr = (u8 *) ptr + permanent_memory_cap;
- memory->temporary.cap = temporary_memory_cap;
- memory->temporary.len = 0;
+ renderer_arena->ptr = (u8 *) ptr + platform_capacity;
+ renderer_arena->cap = renderer_capacity;
+ renderer_arena->len = 0;
+
+ starfield_arena->ptr = (u8 *) ptr + platform_capacity + renderer_capacity;
+ starfield_arena->cap = starfield_capacity;
+ starfield_arena->len = 0;
return true;
}
internal b32
-init_audio(struct linux_audio *audio, u32 period_us, u32 buffered_periods)
+init_audio(struct arena *arena, struct linux_audio *audio, u32 period_us, u32 buffered_periods)
{
audio->enabled = false;
@@ -233,12 +270,10 @@ init_audio(struct linux_audio *audio, u32 period_us, u32 buffered_periods)
u64 frames = (period_us * STARFIELD_AUDIO_SAMPLE_RATE * STARFIELD_AUDIO_FRAME_BYTES) / USECS;
-#ifdef STARFIELD_DEBUG
fprintf(stderr, "Audio buffer period (us): %" PRIu32 "\n", period_us);
fprintf(stderr, "Audio buffer frames: %" PRIu64 "\n", frames);
-#endif
- audio->buffer.buffer = PUSH_ARRAY(&state.platform_memory.permanent, s32, frames);
+ audio->buffer.buffer = PUSH_ARRAY(arena, s32, frames);
assert(audio->buffer.buffer);
audio->buffer.channels = STARFIELD_AUDIO_CHANNELS;
@@ -290,13 +325,12 @@ internal const struct xdg_surface_listener xdg_surface_listener = {
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
+ struct linux_video *video = data;
+ video->width = width ? width : STARFIELD_VIDEO_HRES;
+ video->height = height ? height : STARFIELD_VIDEO_HRES;
}
internal void
@@ -311,12 +345,11 @@ xdg_toplevel_handle_close(void *data, struct xdg_toplevel *toplevel)
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
+ struct linux_video *video = data;
+ video->width = width ? width : STARFIELD_VIDEO_HRES;
+ video->height = height ? height : STARFIELD_VIDEO_HRES;
}
internal void
@@ -365,8 +398,10 @@ internal const struct wl_registry_listener wl_registry_listener = {
};
internal b32
-init_video(struct linux_video *video, char const *display_name)
+init_video(struct arena *arena, struct linux_video *video, char const *display_name)
{
+ (void) arena;
+
if (!(video->display = wl_display_connect(display_name))) {
fprintf(stderr, "wl_display_connect: could not connect to %s\n", display_name);
return false;
@@ -386,27 +421,148 @@ init_video(struct linux_video *video, char const *display_name)
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);
+ 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->toplevel, "starfield");
- xdg_toplevel_set_title(video->toplevel, "starfield");
+ xdg_toplevel_set_app_id(video->xdg_toplevel, "starfield");
+ xdg_toplevel_set_title(video->xdg_toplevel, "starfield");
wl_surface_commit(video->surface);
-
wl_display_roundtrip(video->display);
return true;
}
internal void
-blit_video(struct linux_video *video)
+draw_frame(struct linux_video *video)
+{
+ wl_display_dispatch_pending(video->display);
+}
+
+internal VKAPI_ATTR VkBool32 VKAPI_CALL
+vk_debug_callback(VkDebugUtilsMessageSeverityFlagBitsEXT severity,
+ VkDebugUtilsMessageTypeFlagsEXT type,
+ const VkDebugUtilsMessengerCallbackDataEXT *callback_data,
+ void *user_data)
+{
+ (void) severity;
+ (void) type;
+ (void) user_data;
+
+ fprintf(stderr, "vk: validation: %s\n", callback_data->pMessage);
+
+ return VK_FALSE;
+}
+
+internal b32
+init_vulkan(struct arena *arena, struct linux_vulkan *vulkan,
+ struct wl_display *display, struct wl_surface *surface,
+ u32 width, u32 height)
+{
+ VkApplicationInfo app_info = {
+ .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
+ .pApplicationName = "starfield",
+ .applicationVersion = VK_MAKE_VERSION(1, 0, 0),
+ .pEngineName = "No Engine",
+ .engineVersion = VK_MAKE_VERSION(1, 0, 0),
+ .apiVersion = VK_API_VERSION_1_0,
+ };
+
+ const char *instance_extensions[] = {
+ VK_KHR_SURFACE_EXTENSION_NAME,
+ VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME,
+ VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME,
+ VK_EXT_DEBUG_UTILS_EXTENSION_NAME,
+ };
+
+ const char *instance_layers[] = {
+ "VK_LAYER_KHRONOS_validation",
+ };
+
+ uint32_t debug_severity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT
+ | VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT
+ | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT
+ | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
+
+ uint32_t debug_type = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT
+ | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT
+ | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
+
+ VkDebugUtilsMessengerCreateInfoEXT debug_info = {
+ .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
+ .messageSeverity = debug_severity,
+ .messageType = debug_type,
+ .pfnUserCallback = vk_debug_callback,
+ .pUserData = NULL,
+ };
+
+ VkInstanceCreateInfo instance_info = {
+ .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+ .pApplicationInfo = &app_info,
+ .enabledExtensionCount = ARRLEN(instance_extensions),
+ .ppEnabledExtensionNames = instance_extensions,
+ .enabledLayerCount = ARRLEN(instance_layers),
+ .ppEnabledLayerNames = instance_layers,
+ .pNext = &debug_info,
+ };
+
+ if (vkCreateInstance(&instance_info, NULL, &vulkan->instance) != VK_SUCCESS) {
+ fprintf(stderr, "Failed to create Vulkan instance!\n");
+ return false;
+ }
+
+ PFN_vkCreateDebugUtilsMessengerEXT vk_create_debug_utils_messenger_ext =
+ (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(vulkan->instance,
+ "vkCreateDebugUtilsMessengerEXT");
+ if (!vk_create_debug_utils_messenger_ext) {
+ fprintf(stderr, "Failed to get proc address of vkCreateDebugUtilsMessengerEXT!\n");
+ return false;
+ }
+
+ if (vk_create_debug_utils_messenger_ext(vulkan->instance,
+ &debug_info, NULL,
+ &vulkan->debug_messenger) != VK_SUCCESS) {
+ fprintf(stderr, "Failed to create Vulkan debug messenger!\n");
+ return false;
+ }
+
+ VkWaylandSurfaceCreateInfoKHR surface_info = {
+ .sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR,
+ .display = display,
+ .surface = surface,
+ };
+
+ if (vkCreateWaylandSurfaceKHR(vulkan->instance, &surface_info, NULL, &vulkan->surface) != VK_SUCCESS) {
+ fprintf(stderr, "Failed to create Vulkan surface!\n");
+ return false;
+ }
+
+ if (!(vulkan->renderer = create_renderer(arena, vulkan->instance, vulkan->surface, width, height))) {
+ fprintf(stderr, "Failed to create Vulkan renderer\n");
+ return false;
+ }
+
+ return true;
+}
+
+internal void
+free_vulkan(struct linux_vulkan *vulkan)
{
- int res = wl_display_dispatch_pending(video->display);
- if (res < 0) {
- fprintf(stderr, "wl_display_dispatch_pending error\n");
+ destroy_renderer(vulkan->renderer);
+
+ vkDestroySurfaceKHR(vulkan->instance, vulkan->surface, NULL);
+
+ PFN_vkDestroyDebugUtilsMessengerEXT vk_destroy_debug_utils_messenger_ext =
+ (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(vulkan->instance,
+ "vkDestroyDebugUtilsMessengerEXT");
+ if (!vk_destroy_debug_utils_messenger_ext) {
+ fprintf(stderr, "Failed to get proc address of vkDestroyDebugUtilsMessengerEXT!\n");
return;
}
+
+ vk_destroy_debug_utils_messenger_ext(vulkan->instance, vulkan->debug_messenger, NULL);
+
+ vkDestroyInstance(vulkan->instance, NULL);
}
/* platform implementation
@@ -454,4 +610,5 @@ load_platform_api(void)
* ===========================================================================
*/
-#include "starfield_api.c"
+#include "utils.c"
+#include "renderer.c"
diff --git a/platform/linux_starfield.h b/platform/linux_starfield.h
@@ -16,10 +16,15 @@
#include <alsa/asoundlib.h>
#include <wayland-client.h>
-#include <wayland-egl.h>
#include "xdg-shell.h"
+#define VK_USE_PLATFORM_WAYLAND_KHR
+#include <vulkan/vulkan.h>
+#include <vulkan/vulkan_wayland.h>
+
#include "starfield_api.h"
+#include "renderer.h"
+#include "utils.h"
struct starfield_thread {
u32 id;
@@ -40,22 +45,35 @@ struct linux_video {
struct wl_registry *registry;
struct wl_compositor *compositor;
- struct wl_surface *surface;
-
struct xdg_wm_base *wm_base;
+
+ struct wl_surface *surface;
struct xdg_surface *xdg_surface;
- struct xdg_toplevel *toplevel;
+ struct xdg_toplevel *xdg_toplevel;
+
+ u32 width, height;
+};
+
+/* vulkan state */
+struct linux_vulkan {
+ VkInstance instance;
+ VkDebugUtilsMessengerEXT debug_messenger;
+ VkSurfaceKHR surface;
- struct starfield_video_buffer buffer;
+ struct renderer *renderer;
};
struct linux_state {
- struct starfield_memory platform_memory, starfield_memory;
+ struct arena platform_arena;
+ struct arena renderer_arena;
+ struct arena starfield_arena;
struct linux_audio audio;
struct linux_video video;
+ struct linux_vulkan vulkan;
struct platform_api platform_api;
+ struct render_api render_api;
struct starfield_api starfield_api;
b32 running;
@@ -71,18 +89,28 @@ internal struct platform_api
load_platform_api(void);
internal b32
-init_memory(struct starfield_memory *memory, u64 permanent_memory_cap, u64 temporary_memory_cap);
+init_memory(struct arena *platform_arena, u64 platform_capacity,
+ struct arena *renderer_arena, u64 renderer_capacity,
+ struct arena *starfield_arena, u64 starfield_capacity);
internal b32
-init_audio(struct linux_audio *audio, u32 period_us, u32 buffered_periods);
+init_audio(struct arena *arena, struct linux_audio *audio, u32 period_us, u32 buffered_periods);
internal void
play_audio(struct linux_audio *audio);
internal b32
-init_video(struct linux_video *video, char const *display_name);
+init_video(struct arena *arena, struct linux_video *video, char const *display_name);
+
+internal void
+draw_frame(struct linux_video *video);
+
+internal b32
+init_vulkan(struct arena *arena, struct linux_vulkan *vulkan,
+ struct wl_display *display, struct wl_surface *surface,
+ u32 width, u32 height);
internal void
-blit_video(struct linux_video *video);
+free_vulkan(struct linux_vulkan *vulkan);
#endif /* LINUX_STARFIELD_H */
diff --git a/platform/renderer.c b/platform/renderer.c
@@ -0,0 +1,835 @@
+#include "renderer.h"
+
+#define MAX_FRAMES_IN_FLIGHT 2
+
+struct renderer {
+ struct arena permanent_arena, temporary_arena;
+
+ struct {
+ VkInstance instance;
+ VkSurfaceKHR surface;
+
+ /* boilerplate vulkan state */
+ VkPhysicalDevice physical_device;
+ VkDevice device;
+
+ VkQueue graphics_queue;
+ VkQueue present_queue;
+
+ VkSwapchainKHR swapchain;
+ VkFormat swapchain_image_format;
+ VkExtent2D swapchain_extent;
+
+ struct {
+ VkImage *ptr;
+ u32 len;
+ } swapchain_images;
+
+ struct {
+ VkImageView *ptr;
+ u32 len;
+ } swapchain_image_views;
+
+ struct {
+ VkFramebuffer *ptr;
+ u32 len;
+ } swapchain_framebuffers;
+
+ VkRenderPass render_pass;
+ VkPipelineLayout pipeline_layout;
+ VkPipeline pipeline;
+
+ VkCommandPool command_pool;
+ VkCommandBuffer command_buffers[MAX_FRAMES_IN_FLIGHT];
+
+ struct {
+ VkSemaphore image_available[MAX_FRAMES_IN_FLIGHT];
+ VkSemaphore render_finished[MAX_FRAMES_IN_FLIGHT];
+ VkFence in_flight[MAX_FRAMES_IN_FLIGHT];
+ } sync_objects;
+
+ u32 frame_index;
+ } vk;
+};
+
+internal u32 vert_shader[] =
+#include "shader.vert.spv.inc"
+;
+
+internal u32 frag_shader[] =
+#include "shader.frag.spv.inc"
+;
+
+struct queue_family_indices {
+ u32 graphics_family;
+ bool has_graphics_family;
+
+ u32 present_family;
+ bool has_present_family;
+};
+
+internal struct queue_family_indices
+get_device_queue_families(VkPhysicalDevice device, VkSurfaceKHR surface)
+{
+ struct queue_family_indices result = {0};
+
+ u32 queue_family_count;
+ vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, NULL);
+
+ VkQueueFamilyProperties *queue_families = alloca(queue_family_count * sizeof *queue_families);
+ vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, queue_families);
+
+ for (u32 i = 0; i < queue_family_count; i++) {
+ if (queue_families[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
+ result.has_graphics_family = true;
+ result.graphics_family = i;
+ }
+
+ VkBool32 present_support = false;
+ vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &present_support);
+ if (present_support) {
+ result.has_present_family = true;
+ result.present_family = i;
+ }
+
+ if (result.has_graphics_family && result.has_present_family)
+ break;
+ }
+
+ return result;
+}
+
+internal b32
+is_device_suitable(VkPhysicalDevice device, VkSurfaceKHR surface,
+ const char **device_extensions, u32 device_extensions_len)
+{
+ struct queue_family_indices indices = get_device_queue_families(device, surface);
+
+ // NOTE: we require a device to have both graphics and present capability for its queue
+ if (!indices.has_graphics_family || !indices.has_present_family)
+ return false;
+
+ u32 extension_count;
+ vkEnumerateDeviceExtensionProperties(device, NULL, &extension_count, NULL);
+
+ VkExtensionProperties *extensions = alloca(extension_count * sizeof *extensions);
+ vkEnumerateDeviceExtensionProperties(device, NULL, &extension_count, extensions);
+
+ for (u32 i = 0; i < device_extensions_len; i++) {
+ for (u32 j = 0; j < extension_count; j++) {
+ if (strcmp(device_extensions[i], extensions[j].extensionName) == 0)
+ goto extension_found;
+ }
+
+ // NOTE: device does not support a required extension
+ return false;
+
+extension_found:
+ continue;
+ }
+
+ u32 format_count;
+ vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &format_count, NULL);
+ if (!format_count) return false;
+
+ u32 present_mode_count;
+ vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &present_mode_count, NULL);
+ if (!present_mode_count) return false;
+
+ return true;
+}
+
+internal VkExtent2D
+get_swapchain_extent(VkSurfaceCapabilitiesKHR *surface_caps, u32 width, u32 height)
+{
+ if (surface_caps->currentExtent.width == UINT32_MAX && surface_caps->currentExtent.height == UINT32_MAX) {
+ return (VkExtent2D) {
+ .width = CLAMP(width, surface_caps->minImageExtent.width, surface_caps->maxImageExtent.width),
+ .height = CLAMP(height, surface_caps->minImageExtent.height, surface_caps->maxImageExtent.height),
+ };
+ } else {
+ return surface_caps->currentExtent;
+ }
+}
+
+internal VkSurfaceFormatKHR
+get_swapchain_format(VkSurfaceFormatKHR *formats, u64 len)
+{
+ for (u64 i = 0; i < len; i++) {
+ if (formats[i].format == VK_FORMAT_B8G8R8A8_SRGB &&
+ formats[i].colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
+ return formats[i];
+ }
+
+ return formats[0];
+}
+
+internal VkPresentModeKHR
+get_swapchain_present_mode(VkPresentModeKHR *present_modes, u64 len)
+{
+ for (u64 i = 0; i < len; i++) {
+ if (present_modes[i] == VK_PRESENT_MODE_MAILBOX_KHR)
+ return present_modes[i];
+ }
+
+ return VK_PRESENT_MODE_FIFO_KHR;
+}
+
+internal b32
+recreate_swapchain(struct renderer *renderer, u32 width, u32 height, VkSwapchainKHR old_swapchain)
+{
+ struct queue_family_indices indices = get_device_queue_families(renderer->vk.physical_device, renderer->vk.surface);
+ assert(indices.has_graphics_family && indices.has_present_family);
+
+ VkSurfaceCapabilitiesKHR surface_caps;
+ vkGetPhysicalDeviceSurfaceCapabilitiesKHR(renderer->vk.physical_device, renderer->vk.surface, &surface_caps);
+
+ u32 format_count;
+ vkGetPhysicalDeviceSurfaceFormatsKHR(renderer->vk.physical_device, renderer->vk.surface, &format_count, NULL);
+
+ assert(format_count);
+ VkSurfaceFormatKHR *formats = alloca(format_count * sizeof *formats);
+ vkGetPhysicalDeviceSurfaceFormatsKHR(renderer->vk.physical_device, renderer->vk.surface, &format_count, formats);
+
+ u32 present_mode_count;
+ vkGetPhysicalDeviceSurfacePresentModesKHR(renderer->vk.physical_device, renderer->vk.surface, &present_mode_count, NULL);
+
+ assert(present_mode_count);
+ VkPresentModeKHR *present_modes = alloca(present_mode_count * sizeof *present_modes);
+ vkGetPhysicalDeviceSurfacePresentModesKHR(renderer->vk.physical_device, renderer->vk.surface, &present_mode_count, present_modes);
+
+ VkExtent2D extent = get_swapchain_extent(&surface_caps, width, height);
+ VkSurfaceFormatKHR format = get_swapchain_format(formats, format_count);
+ VkPresentModeKHR present_mode = get_swapchain_present_mode(present_modes, present_mode_count);
+
+ u32 image_count = surface_caps.minImageCount + 1;
+ if (surface_caps.maxImageCount)
+ image_count = MIN(image_count, surface_caps.maxImageCount);
+
+ assert(indices.graphics_family == indices.present_family);
+ VkSwapchainCreateInfoKHR swapchain_info = {
+ .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
+ .surface = renderer->vk.surface,
+ .minImageCount = image_count,
+ .imageFormat = format.format,
+ .imageColorSpace = format.colorSpace,
+ .imageExtent = extent,
+ .imageArrayLayers = 1,
+ .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
+ .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .preTransform = surface_caps.currentTransform,
+ .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
+ .presentMode = present_mode,
+ .clipped = VK_TRUE,
+ .oldSwapchain = old_swapchain,
+ };
+
+ renderer->vk.swapchain_image_format = format.format;
+ renderer->vk.swapchain_extent = extent;
+
+ if (vkCreateSwapchainKHR(renderer->vk.device, &swapchain_info, NULL, &renderer->vk.swapchain) != VK_SUCCESS)
+ return false;
+
+ vkGetSwapchainImagesKHR(renderer->vk.device, renderer->vk.swapchain, &image_count, NULL);
+
+ renderer->vk.swapchain_images.len = renderer->vk.swapchain_image_views.len = renderer->vk.swapchain_framebuffers.len = image_count;
+
+ renderer->vk.swapchain_images.ptr = malloc(renderer->vk.swapchain_images.len * sizeof *renderer->vk.swapchain_images.ptr);
+ assert(renderer->vk.swapchain_images.ptr);
+
+ vkGetSwapchainImagesKHR(renderer->vk.device, renderer->vk.swapchain, &renderer->vk.swapchain_images.len, renderer->vk.swapchain_images.ptr);
+
+ renderer->vk.swapchain_image_views.ptr = malloc(renderer->vk.swapchain_image_views.len * sizeof *renderer->vk.swapchain_image_views.ptr);
+ assert(renderer->vk.swapchain_image_views.ptr);
+
+ for (u32 i = 0; i < renderer->vk.swapchain_images.len; i++) {
+ VkImageViewCreateInfo image_view_info = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .image = renderer->vk.swapchain_images.ptr[i],
+ .viewType = VK_IMAGE_VIEW_TYPE_2D,
+ .format = renderer->vk.swapchain_image_format,
+ .components.r = VK_COMPONENT_SWIZZLE_IDENTITY,
+ .components.g = VK_COMPONENT_SWIZZLE_IDENTITY,
+ .components.b = VK_COMPONENT_SWIZZLE_IDENTITY,
+ .components.a = VK_COMPONENT_SWIZZLE_IDENTITY,
+ .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .subresourceRange.baseMipLevel = 0,
+ .subresourceRange.levelCount = 1,
+ .subresourceRange.baseArrayLayer = 0,
+ .subresourceRange.layerCount = 1,
+ };
+
+ if (vkCreateImageView(renderer->vk.device, &image_view_info, NULL, &renderer->vk.swapchain_image_views.ptr[i]) != VK_SUCCESS)
+ return false;
+ }
+
+ VkAttachmentDescription color_attachment = {
+ .format = renderer->vk.swapchain_image_format,
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
+ .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
+ .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
+ .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+ };
+
+ VkAttachmentReference color_attachment_ref = {
+ .attachment = 0,
+ .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ };
+
+ VkSubpassDescription subpass = {
+ .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
+ .colorAttachmentCount = 1,
+ .pColorAttachments = &color_attachment_ref,
+ };
+
+ VkSubpassDependency dependency = {
+ .srcSubpass = VK_SUBPASS_EXTERNAL,
+ .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+ .srcAccessMask = 0,
+ .dstSubpass = 0,
+ .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+ .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
+ };
+
+ VkRenderPassCreateInfo render_pass_info = {
+ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
+ .attachmentCount = 1,
+ .pAttachments = &color_attachment,
+ .subpassCount = 1,
+ .pSubpasses = &subpass,
+ .dependencyCount = 1,
+ .pDependencies = &dependency,
+ };
+
+ if (vkCreateRenderPass(renderer->vk.device, &render_pass_info, NULL, &renderer->vk.render_pass) != VK_SUCCESS)
+ return false;
+
+ renderer->vk.swapchain_framebuffers.ptr = malloc(renderer->vk.swapchain_framebuffers.len * sizeof *renderer->vk.swapchain_framebuffers.ptr);
+ assert(renderer->vk.swapchain_framebuffers.ptr);
+
+ for (u32 i = 0; i < renderer->vk.swapchain_framebuffers.len; i++) {
+ VkImageView attachments[] = { renderer->vk.swapchain_image_views.ptr[i], };
+
+ VkFramebufferCreateInfo framebuffer_info = {
+ .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
+ .renderPass = renderer->vk.render_pass,
+ .attachmentCount = ARRLEN(attachments),
+ .pAttachments = attachments,
+ .width = renderer->vk.swapchain_extent.width,
+ .height = renderer->vk.swapchain_extent.height,
+ .layers = 1,
+ };
+
+ if (vkCreateFramebuffer(renderer->vk.device, &framebuffer_info, NULL, &renderer->vk.swapchain_framebuffers.ptr[i]) != VK_SUCCESS)
+ return false;
+ }
+
+ return true;
+}
+
+internal void
+destroy_swapchain(struct renderer *renderer)
+{
+ // TODO: can this cause deadlock?
+ vkDeviceWaitIdle(renderer->vk.device);
+
+ for (u32 i = 0; i < renderer->vk.swapchain_framebuffers.len; i++) {
+ vkDestroyFramebuffer(renderer->vk.device, renderer->vk.swapchain_framebuffers.ptr[i], NULL);
+ }
+
+ free(renderer->vk.swapchain_framebuffers.ptr);
+
+ vkDestroyRenderPass(renderer->vk.device, renderer->vk.render_pass, NULL);
+
+ for (u32 i = 0; i < renderer->vk.swapchain_image_views.len; i++) {
+ vkDestroyImageView(renderer->vk.device, renderer->vk.swapchain_image_views.ptr[i], NULL);
+ }
+
+ free(renderer->vk.swapchain_image_views.ptr);
+
+ // TODO: is this safe?
+ free(renderer->vk.swapchain_images.ptr);
+
+ vkDestroySwapchainKHR(renderer->vk.device, renderer->vk.swapchain, NULL);
+}
+
+internal bool
+create_shader_module(VkDevice device, u32 *code, u64 size, VkShaderModule *out)
+{
+ VkShaderModuleCreateInfo module_info = {
+ .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
+ .codeSize = size,
+ .pCode = code,
+ };
+
+ return vkCreateShaderModule(device, &module_info, NULL, out) == VK_SUCCESS;
+}
+
+internal bool
+create_pipeline(struct renderer *renderer)
+{
+ VkShaderModule vert_shader_module;
+ if (!create_shader_module(renderer->vk.device, vert_shader, sizeof vert_shader, &vert_shader_module)) {
+ fprintf(stderr, "Failed to create vertex shader module!\n");
+ return false;
+ }
+
+ VkShaderModule frag_shader_module;
+ if (!create_shader_module(renderer->vk.device, frag_shader, sizeof frag_shader, &frag_shader_module)) {
+ fprintf(stderr, "Failed to create fragment shader module!\n");
+ return false;
+ }
+
+ VkPipelineShaderStageCreateInfo vert_stage_info = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+ .stage = VK_SHADER_STAGE_VERTEX_BIT,
+ .module = vert_shader_module,
+ .pName = "main",
+ };
+
+ VkPipelineShaderStageCreateInfo frag_stage_info = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+ .stage = VK_SHADER_STAGE_FRAGMENT_BIT,
+ .module = frag_shader_module,
+ .pName = "main",
+ };
+
+ VkPipelineShaderStageCreateInfo shader_stages[] = {
+ vert_stage_info,
+ frag_stage_info,
+ };
+
+ VkDynamicState dynamic_states[] = {
+ VK_DYNAMIC_STATE_VIEWPORT,
+ VK_DYNAMIC_STATE_SCISSOR,
+ };
+
+ VkPipelineDynamicStateCreateInfo dynamic_state_info = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
+ .dynamicStateCount = ARRLEN(dynamic_states),
+ .pDynamicStates = dynamic_states,
+ };
+
+ VkPipelineVertexInputStateCreateInfo vertex_input_info = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
+ .vertexBindingDescriptionCount = 0,
+ .pVertexBindingDescriptions = NULL,
+ .vertexAttributeDescriptionCount = 0,
+ .pVertexAttributeDescriptions = NULL,
+ };
+
+ VkPipelineInputAssemblyStateCreateInfo input_assembly_info = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+ .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
+ .primitiveRestartEnable = VK_FALSE,
+ };
+
+ VkPipelineViewportStateCreateInfo viewport_info = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
+ .viewportCount = 1,
+ .scissorCount = 1,
+ };
+
+ VkPipelineRasterizationStateCreateInfo rasteriser_info = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
+ .depthClampEnable = VK_FALSE,
+ .rasterizerDiscardEnable = VK_FALSE,
+ .polygonMode = VK_POLYGON_MODE_FILL,
+ .lineWidth = 1.0f,
+ .cullMode = VK_CULL_MODE_BACK_BIT,
+ .frontFace = VK_FRONT_FACE_CLOCKWISE,
+ .depthBiasEnable = VK_FALSE,
+ };
+
+ VkPipelineMultisampleStateCreateInfo multisample_info = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
+ .sampleShadingEnable = VK_FALSE,
+ .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
+ };
+
+ VkPipelineColorBlendAttachmentState color_blend_attachment = {
+ .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
+ .blendEnable = VK_FALSE,
+ };
+
+ VkPipelineColorBlendStateCreateInfo color_blend_info = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
+ .logicOpEnable = VK_FALSE,
+ .attachmentCount = 1,
+ .pAttachments = &color_blend_attachment,
+ };
+
+ VkPipelineLayoutCreateInfo pipeline_layout_info = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
+ };
+
+ if (vkCreatePipelineLayout(renderer->vk.device, &pipeline_layout_info, NULL, &renderer->vk.pipeline_layout) != VK_SUCCESS) {
+ fprintf(stderr, "Failed to create pipeline layout!\n");
+ return false;
+ }
+
+ VkGraphicsPipelineCreateInfo pipeline_info = {
+ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
+ .stageCount = 2,
+ .pStages = shader_stages,
+ .pVertexInputState = &vertex_input_info,
+ .pInputAssemblyState = &input_assembly_info,
+ .pViewportState = &viewport_info,
+ .pRasterizationState = &rasteriser_info,
+ .pMultisampleState = &multisample_info,
+ .pColorBlendState = &color_blend_info,
+ .pDynamicState = &dynamic_state_info,
+ .layout = renderer->vk.pipeline_layout,
+ .renderPass = renderer->vk.render_pass,
+ .subpass = 0,
+ .basePipelineHandle = VK_NULL_HANDLE,
+ .basePipelineIndex = -1,
+ };
+
+ VkPipeline pipeline;
+ if (vkCreateGraphicsPipelines(renderer->vk.device, VK_NULL_HANDLE, 1, &pipeline_info, NULL, &pipeline) != VK_SUCCESS) {
+ fprintf(stderr, "Failed to create pipeline!\n");
+ return false;
+ }
+
+ renderer->vk.pipeline = pipeline;
+
+ vkDestroyShaderModule(renderer->vk.device, frag_shader_module, NULL);
+ vkDestroyShaderModule(renderer->vk.device, vert_shader_module, NULL);
+
+ return true;
+}
+
+internal struct renderer *
+create_renderer(struct arena *arena, VkInstance instance, VkSurfaceKHR surface,
+ u32 width, u32 height)
+{
+ struct arena permanent_arena = {
+ .ptr = PUSH_ARRAY(arena, u8, arena->cap / 2),
+ .cap = arena->cap / 2,
+ .len = 0,
+ };
+
+ struct arena temporary_arena = {
+ .ptr = PUSH_ARRAY(arena, u8, arena->cap / 2),
+ .cap = arena->cap / 2,
+ .len = 0,
+ };
+
+ struct renderer *renderer = PUSH_SIZED(&permanent_arena, struct renderer);
+
+ renderer->permanent_arena = permanent_arena;
+ renderer->temporary_arena = temporary_arena;
+ renderer->vk.instance = instance;
+ renderer->vk.surface = surface;
+ renderer->vk.frame_index = 0;
+
+ u32 physical_device_count;
+ vkEnumeratePhysicalDevices(instance, &physical_device_count, NULL);
+
+ if (!physical_device_count) {
+ fprintf(stderr, "Failed to find physical devices supporting Vulkan 1.0!\n");
+ return NULL;
+ }
+
+ VkPhysicalDevice *physical_devices = alloca(physical_device_count * sizeof *physical_devices);
+ vkEnumeratePhysicalDevices(instance, &physical_device_count, physical_devices);
+
+ const char *device_extensions[] = {
+ VK_KHR_SWAPCHAIN_EXTENSION_NAME,
+ };
+
+ renderer->vk.physical_device = VK_NULL_HANDLE;
+ for (u32 i = 0; i < physical_device_count; i++) {
+ if (is_device_suitable(physical_devices[i], surface, device_extensions, ARRLEN(device_extensions)))
+ renderer->vk.physical_device = physical_devices[i];
+ }
+
+ if (renderer->vk.physical_device == VK_NULL_HANDLE) {
+ fprintf(stderr, "Failed to find suitable physical device!\n");
+ return NULL;
+ }
+
+ struct queue_family_indices device_queue_families = get_device_queue_families(renderer->vk.physical_device, renderer->vk.surface);
+
+ float queue_priority = 1.0f;
+
+ // TODO: handle case where graphics queue and present queue are separate
+ VkDeviceQueueCreateInfo queue_info = {
+ .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
+ .queueFamilyIndex = device_queue_families.graphics_family,
+ .queueCount = 1,
+ .pQueuePriorities = &queue_priority,
+ };
+
+ VkPhysicalDeviceFeatures device_features = {0};
+
+ VkDeviceCreateInfo device_info = {
+ .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
+ .pQueueCreateInfos = &queue_info,
+ .queueCreateInfoCount = 1,
+ .pEnabledFeatures = &device_features,
+ .enabledExtensionCount = ARRLEN(device_extensions),
+ .ppEnabledExtensionNames = device_extensions,
+ };
+
+ if (vkCreateDevice(renderer->vk.physical_device, &device_info, NULL, &renderer->vk.device) != VK_SUCCESS) {
+ fprintf(stderr, "Failed to create logical device!\n");
+ return NULL;
+ }
+
+ vkGetDeviceQueue(renderer->vk.device, device_queue_families.graphics_family, 0, &renderer->vk.graphics_queue);
+ vkGetDeviceQueue(renderer->vk.device, device_queue_families.present_family, 0, &renderer->vk.present_queue);
+
+ if (!recreate_swapchain(renderer, width, height, VK_NULL_HANDLE)) {
+ fprintf(stderr, "Failed to create swapchain!\n");
+ return NULL;
+ }
+
+ if (!create_pipeline(renderer)) {
+ fprintf(stderr, "Failed to create graphics pipeline!\n");
+ return NULL;
+ }
+
+ VkCommandPoolCreateInfo command_pool_info = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
+ .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
+ .queueFamilyIndex = device_queue_families.graphics_family,
+ };
+
+ if (vkCreateCommandPool(renderer->vk.device, &command_pool_info, NULL, &renderer->vk.command_pool) != VK_SUCCESS) {
+ fprintf(stderr, "Failed to create command pool!\n");
+ return NULL;
+ }
+
+ VkCommandBufferAllocateInfo command_buffer_alloc_info = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .commandPool = renderer->vk.command_pool,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+ .commandBufferCount = ARRLEN(renderer->vk.command_buffers),
+ };
+
+ if (vkAllocateCommandBuffers(renderer->vk.device, &command_buffer_alloc_info, renderer->vk.command_buffers) != VK_SUCCESS) {
+ fprintf(stderr, "Failed to allocate command buffers!\n");
+ return NULL;
+ }
+
+ VkSemaphoreCreateInfo semaphore_info = {
+ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
+ };
+
+ VkFenceCreateInfo fence_info = {
+ .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
+ .flags = VK_FENCE_CREATE_SIGNALED_BIT,
+ };
+
+ for (u32 i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
+ if (vkCreateSemaphore(renderer->vk.device, &semaphore_info, NULL, &renderer->vk.sync_objects.image_available[i]) != VK_SUCCESS ||
+ vkCreateSemaphore(renderer->vk.device, &semaphore_info, NULL, &renderer->vk.sync_objects.render_finished[i]) != VK_SUCCESS ||
+ vkCreateFence(renderer->vk.device, &fence_info, NULL, &renderer->vk.sync_objects.in_flight[i]) != VK_SUCCESS) {
+ fprintf(stderr, "Failed to create sync objects!\n");
+ return NULL;
+ }
+ }
+
+ return renderer;
+}
+
+internal void
+destroy_renderer(struct renderer *renderer)
+{
+ vkDeviceWaitIdle(renderer->vk.device);
+
+ destroy_swapchain(renderer);
+
+ vkDestroyPipeline(renderer->vk.device, renderer->vk.pipeline, NULL);
+ vkDestroyPipelineLayout(renderer->vk.device, renderer->vk.pipeline_layout, NULL);
+
+ for (u32 i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
+ vkDestroySemaphore(renderer->vk.device, renderer->vk.sync_objects.render_finished[i], NULL);
+ vkDestroySemaphore(renderer->vk.device, renderer->vk.sync_objects.image_available[i], NULL);
+ vkDestroyFence(renderer->vk.device, renderer->vk.sync_objects.in_flight[i], NULL);
+ }
+
+ vkDestroyCommandPool(renderer->vk.device, renderer->vk.command_pool, NULL);
+
+ vkDestroyDevice(renderer->vk.device, NULL);
+}
+
+struct render_frame {
+ u32 frame_index;
+ u32 image_index;
+ VkCommandBuffer command_buffer;
+ VkFramebuffer framebuffer;
+};
+
+internal struct render_frame *
+render_begin_frame(struct renderer *renderer, u32 width, u32 height)
+{
+ u32 frame_index = renderer->vk.frame_index;
+ renderer->vk.frame_index = (frame_index + 1) % MAX_FRAMES_IN_FLIGHT;
+
+ vkWaitForFences(renderer->vk.device, 1, &renderer->vk.sync_objects.in_flight[frame_index], VK_TRUE, UINT64_MAX);
+
+ u32 image_index;
+ VkResult result = vkAcquireNextImageKHR(renderer->vk.device, renderer->vk.swapchain, UINT64_MAX, renderer->vk.sync_objects.image_available[frame_index], VK_NULL_HANDLE, &image_index);
+
+ if (result == VK_ERROR_OUT_OF_DATE_KHR) {
+ destroy_swapchain(renderer);
+ if (!recreate_swapchain(renderer, width, height, VK_NULL_HANDLE))
+ fprintf(stderr, "Failed to recreate swapchain!\n");
+ return NULL;
+ } else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
+ fprintf(stderr, "Failed to aqcuire swapchain image!\n");
+ return NULL;
+ }
+
+ vkResetFences(renderer->vk.device, 1, &renderer->vk.sync_objects.in_flight[frame_index]);
+
+ arena_reset(&renderer->temporary_arena);
+
+ struct render_frame *frame = PUSH_SIZED(&renderer->temporary_arena, struct render_frame);
+ assert(frame);
+
+ frame->frame_index = frame_index;
+ frame->image_index = image_index;
+
+ frame->command_buffer = renderer->vk.command_buffers[frame_index];
+ vkResetCommandBuffer(frame->command_buffer, 0);
+
+ frame->framebuffer = renderer->vk.swapchain_framebuffers.ptr[image_index];
+
+ VkCommandBufferBeginInfo begin_info = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+ };
+
+ if (vkBeginCommandBuffer(frame->command_buffer, &begin_info) != VK_SUCCESS) {
+ fprintf(stderr, "Failed to begin recording command buffer!\n");
+ return NULL;
+ }
+
+ return frame;
+}
+
+internal void
+render_begin_render_pass(struct renderer *renderer, struct render_frame *frame)
+{
+ VkClearValue clear_color = {{{0.0f, 0.0f, 0.0f, 1.0f}}};
+
+ VkRenderPassBeginInfo render_pass_begin_info = {
+ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
+ .renderPass = renderer->vk.render_pass,
+ .framebuffer = frame->framebuffer, // renderer->vk.swapchain_framebuffers.ptr[frame->image_index],
+ .renderArea.offset = {0, 0},
+ .renderArea.extent = renderer->vk.swapchain_extent,
+ .clearValueCount = 1,
+ .pClearValues = &clear_color,
+ };
+
+ vkCmdBeginRenderPass(frame->command_buffer, &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE);
+}
+
+internal void
+render_bind_pipeline(struct renderer *renderer, struct render_frame *frame)
+{
+ vkCmdBindPipeline(frame->command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, renderer->vk.pipeline);
+}
+
+internal void
+render_set_viewport(struct renderer *renderer, struct render_frame *frame)
+{
+ VkViewport viewport = {
+ .x = 0.0f,
+ .y = 0.0f,
+ .width = (float) renderer->vk.swapchain_extent.width,
+ .height = (float) renderer->vk.swapchain_extent.height,
+ .minDepth = 0.0f,
+ .maxDepth = 1.0f,
+ };
+
+ vkCmdSetViewport(frame->command_buffer, 0, 1, &viewport);
+}
+
+internal void
+render_set_scissor(struct renderer *renderer, struct render_frame *frame)
+{
+ VkRect2D scissor = {
+ .offset = {0, 0},
+ .extent = renderer->vk.swapchain_extent,
+ };
+
+ vkCmdSetScissor(frame->command_buffer, 0, 1, &scissor);
+}
+
+internal void
+render_draw_vertices(struct renderer *renderer, struct render_frame *frame)
+{
+ (void) renderer;
+
+ u32 vertex_count = 3, instance_count = 1, first_vertex = 0, first_instance = 0;
+ vkCmdDraw(frame->command_buffer, vertex_count, instance_count, first_vertex, first_instance);
+}
+
+internal void
+render_end_render_pass(struct renderer *renderer, struct render_frame *frame)
+{
+ (void) renderer;
+
+ vkCmdEndRenderPass(frame->command_buffer);
+}
+
+internal void
+render_end_frame(struct renderer *renderer, struct render_frame *frame)
+{
+ if (vkEndCommandBuffer(frame->command_buffer) != VK_SUCCESS) {
+ fprintf(stderr, "Failed to end recording command buffer!\n");
+ return;
+ }
+
+ VkPipelineStageFlags wait_stages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
+
+ VkSubmitInfo submit_info = {
+ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
+ .waitSemaphoreCount = 1,
+ .pWaitSemaphores = &renderer->vk.sync_objects.image_available[frame->frame_index],
+ .pWaitDstStageMask = wait_stages,
+ .signalSemaphoreCount = 1,
+ .pSignalSemaphores = &renderer->vk.sync_objects.render_finished[frame->frame_index],
+ .commandBufferCount = 1,
+ .pCommandBuffers = &frame->command_buffer, // &renderer->vk.command_buffers[frame->frame_index],
+ };
+
+ if (vkQueueSubmit(renderer->vk.graphics_queue, 1, &submit_info, renderer->vk.sync_objects.in_flight[frame->frame_index]) != VK_SUCCESS) {
+ fprintf(stderr, "Failed to submit command buffer!\n");
+ return;
+ }
+
+ VkPresentInfoKHR present_info = {
+ .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
+ .waitSemaphoreCount = 1,
+ .pWaitSemaphores = &renderer->vk.sync_objects.render_finished[frame->frame_index],
+ .swapchainCount = 1,
+ .pSwapchains = &renderer->vk.swapchain,
+ .pImageIndices = &frame->image_index,
+ };
+
+ vkQueuePresentKHR(renderer->vk.present_queue, &present_info);
+}
+
+internal struct render_api
+load_render_api(void)
+{
+ return (struct render_api) {
+ .begin_frame = render_begin_frame,
+ .begin_render_pass = render_begin_render_pass,
+ .bind_pipeline = render_bind_pipeline,
+ .set_viewport = render_set_viewport,
+ .set_scissor = render_set_scissor,
+ .draw_vertices = render_draw_vertices,
+ .end_render_pass = render_end_render_pass,
+ .end_frame = render_end_frame,
+ };
+}
diff --git a/platform/renderer.h b/platform/renderer.h
@@ -0,0 +1,19 @@
+#ifndef RENDERER_H
+#define RENDERER_H
+
+#include <vulkan/vulkan.h>
+
+#include "starfield_api.h"
+#include "utils.h"
+
+internal struct render_api
+load_render_api(void);
+
+internal struct renderer *
+create_renderer(struct arena *arena, VkInstance instance, VkSurfaceKHR surface,
+ u32 width, u32 height);
+
+internal void
+destroy_renderer(struct renderer *renderer);
+
+#endif /* RENDERER_H */
diff --git a/platform/utils.c b/platform/utils.c
@@ -0,0 +1,10 @@
+#include "utils.h"
+
+extern inline void
+arena_reset(struct arena *arena);
+
+extern inline void *
+arena_alloc(struct arena *arena, u64 size, u64 alignment);
+
+extern inline struct arena
+arena_partition(struct arena *arena, u64 size, u64 alignment);
diff --git a/platform/utils.h b/platform/utils.h
@@ -0,0 +1,34 @@
+#ifndef UTILS_H
+#define UTILS_H
+
+#include "starfield/types.h"
+
+struct arena {
+ void *ptr;
+ u64 cap, len;
+};
+
+inline void
+arena_reset(struct arena *arena)
+{
+ arena->len = 0;
+}
+
+inline void *
+arena_alloc(struct arena *arena, u64 size, u64 alignment)
+{
+ u64 aligned_len = ALIGN_NEXT(arena->len, alignment);
+
+ if (arena->cap < aligned_len + size)
+ return NULL;
+
+ void *ptr = (u8 *) arena->ptr + aligned_len;
+ arena->len = aligned_len + size;
+
+ return ptr;
+}
+
+#define PUSH_SIZED(arena, T) arena_alloc(arena, sizeof(T), alignof(T))
+#define PUSH_ARRAY(arena, T, n) arena_alloc(arena, sizeof(T) * (n), alignof(T))
+
+#endif /* UTILS_H */
diff --git a/platform/win_starfield.c b/platform/win_starfield.c
@@ -27,17 +27,42 @@ WinMain(HINSTANCE instance, HINSTANCE prev_instance, LPSTR cmdline, int cmdshow)
.id = 0,
};
- if (!init_memory(&state.platform_memory, 64 * MiB, 64 * MiB)) {
- fprintf(stderr, "Failed to allocate platform memory\n");
+ if (!init_memory(&state.platform_arena, 32 * MiB,
+ &state.renderer_arena, 32 * MiB,
+ &state.starfield_arena, 64 * MiB)) {
+ fprintf(stderr, "Failed to initialise memory\n");
_exit(1);
}
- if (!init_memory(&state.starfield_memory, 64 * MiB, 64 * MiB)) {
- fprintf(stderr, "Failed to allocate starfield memory\n");
+ 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_us = target_us_per_frame, buffered_periods = 2;
+ if (!init_audio(&state.platform_arena, &state.audio, period_us, buffered_periods)) {
+ fprintf(stderr, "Failed to initialise platform audio\n");
+ }
+
+ if (!init_video(&state.platform_arena, &state.video, instance)) {
+ fprintf(stderr, "Failed to initialise platform video\n");
+ _exit(1);
+ }
+
+ if (!init_vulkan(&state.renderer_arena, &state.vulkan)) {
+ fprintf(stderr, "Failed to initialise platform renderer\n");
_exit(1);
}
- state.starfield_api.init(&thread, &state.starfield_memory, &state.platform_api);
+ struct starfield_memory starfield_memory = {
+ .ptr = state.starfield_arena.ptr,
+ .cap = state.starfield_arena.cap,
+ .len = state.starfield_arena.len,
+ };
+
+ state.starfield_api.init(&thread, &starfield_memory, &state.platform_api);
state.running = 1;
@@ -47,28 +72,29 @@ WinMain(HINSTANCE instance, HINSTANCE prev_instance, LPSTR cmdline, int cmdshow)
0,
};
- state.starfield_api.update(&thread, &state.starfield_memory,
+ state.starfield_api.update(&thread, &starfield_memory,
&state.platform_api, &input, dt);
- struct starfield_video_buffer video = {
- 0,
- };
+ if (state.audio.enabled) {
+ state.starfield_api.sample(&thread, &starfield_memory,
+ &state.platform_api, &state.audio.buffer, dt);
- state.starfield_api.render(&thread, &state.starfield_memory,
- &state.platform_api, &video, dt);
+ play_audio(&state.audio);
+ }
- struct starfield_audio_buffer audio = {
- 0,
- };
+ state.starfield_api.render(&thread, &starfield_memory,
+ &state.platform_api, &state.renderer_api,
+ state.video.width, state.video.height, dt);
- state.starfield_api.sample(&thread, &state.starfield_memory,
- &state.platform_api, &audio, dt);
+ draw_frame(&state.video);
Sleep(1000);
}
state.starfield_api.free(&thread, &state.starfield_memory, &state.platform_api);
+ free_vulkan(&state.vulkan);
+
_exit(0);
}
@@ -81,26 +107,19 @@ load_starfield_api(char const *filename, struct starfield_api *api)
HMODULE library = LoadLibraryA(filename);
if (!library) return false;
- if (!LOAD_SYMBOL(api->init, starfield_api_init_t, "starfield_init"))
- return false;
-
- if (!LOAD_SYMBOL(api->update, starfield_api_update_t, "starfield_update"))
- return false;
-
- if (!LOAD_SYMBOL(api->render, starfield_api_render_t, "starfield_render"))
- return false;
-
- if (!LOAD_SYMBOL(api->sample, starfield_api_sample_t, "starfield_sample"))
- return false;
-
- if (!LOAD_SYMBOL(api->free, starfield_api_free_t, "starfield_free"))
- return false;
+ LOAD_SYMBOL(api->init, starfield_api_init_t, "starfield_init");
+ LOAD_SYMBOL(api->update, starfield_api_update_t, "starfield_update");
+ LOAD_SYMBOL(api->render, starfield_api_render_t, "starfield_render");
+ LOAD_SYMBOL(api->sample, starfield_api_sample_t, "starfield_sample");
+ LOAD_SYMBOL(api->free, starfield_api_free_t, "starfield_free");
return true;
}
internal b32
-init_memory(struct starfield_memory *memory, u64 permanent_memory_cap, u64 temporary_memory_cap)
+init_memory(struct arena *platform_arena, u64 platform_capacity,
+ struct arena *renderer_arena, u64 renderer_capacity,
+ struct arena *starfield_arena, u64 starfield_capacity)
{
#ifdef STARFIELD_DEBUG
void *base_addr = (void *) (2 * TiB);
@@ -109,7 +128,8 @@ init_memory(struct starfield_memory *memory, u64 permanent_memory_cap, u64 tempo
#endif
u64 huge_page_alignment = GetLargePageMinimum();
- u64 len = ALIGN_NEXT(permanent_memory_cap + temporary_memory_cap, huge_page_alignment);
+ u64 len = ALIGN_NEXT(platform_capacity + renderer_capacity + starfield_capacity,
+ huge_page_alignment);
/* TODO: add hugetlb support?
* https://learn.microsoft.com/en-us/windows/win32/memory/large-page-support
@@ -129,17 +149,69 @@ init_memory(struct starfield_memory *memory, u64 permanent_memory_cap, u64 tempo
* is zero-ed, so no need to memset it here
*/
- memory->permanent.ptr = ptr;
- memory->permanent.cap = permanent_memory_cap;
- memory->permanent.len = 0;
+ platform_arena->ptr = ptr;
+ platform_arena->cap = platform_capacity;
+ platform_arena->len = 0;
- memory->temporary.ptr = (u8 *) ptr + permanent_memory_cap;
- memory->temporary.cap = temporary_memory_cap;
- memory->temporary.len = 0;
+ renderer_arena->ptr = (u8 *) ptr + platform_capacity;
+ renderer_arena->cap = renderer_capacity;
+ renderer_arena->len = 0;
+
+ starfield_arena->ptr = (u8 *) ptr + platform_capacity + renderer_capacity;
+ starfield_arena->cap = starfield_capacity;
+ starfield_arena->len = 0;
return true;
}
+internal b32
+init_audio(struct arena *arena, struct win_audio *audio, u64 period_us, u32 buffered_periods)
+{
+ (void) arena;
+ (void) audio;
+ (void) period_us;
+ (void) buffered_periods;
+
+ return false;
+}
+
+internal void
+play_audio(struct win_audio *audio)
+{
+ (void) audio;
+}
+
+internal b32
+init_video(struct arena *arena, struct win_video *video, HINSTANCE instance)
+{
+ (void) arena;
+ (void) video;
+ (void) instance;
+
+ return false;
+}
+
+internal void
+draw_frame(struct win_video *video)
+{
+ (void) video;
+}
+
+internal b32
+init_vulkan(struct arena *arena, struct win_vulkan *vulkan)
+{
+ (void) arena;
+ (void) vulkan;
+
+ return false;
+}
+
+internal void
+free_vulkan(struct win_vulkan *vulkan)
+{
+ (void) vulkan;
+}
+
/* platform implementation
* ===========================================================================
*/
@@ -185,4 +257,4 @@ load_platform_api(void)
* ===========================================================================
*/
-#include "starfield_api.c"
+// TODO: implement renderer
diff --git a/platform/win_starfield.h b/platform/win_starfield.h
@@ -5,16 +5,54 @@
#include <windows.h>
+#define VK_USE_PLATFORM_WAYLAND_WIN32
+#include <vulkan/vulkan.h>
+#include <vulkan/vulkan_win32.h>
+
#include "starfield_api.h"
+#include "renderer.h"
+#include "utils.h"
struct starfield_thread {
u32 id;
};
+/* wasapi state */
+struct win_audio {
+ // TODO
+
+ struct starfield_audio_buffer buffer;
+
+ b32 enabled;
+};
+
+/* dwm state */
+struct win_video {
+ // TODO
+
+ u32 width, height;
+};
+
+/* vulkan state */
+struct win_vulkan {
+ VkInstance instance;
+ VkDebugUtilsMessengerEXT debug_messenger;
+ VkSurfaceKHR surface;
+
+ struct renderer *renderer;
+};
+
struct win_starfield_state {
- struct starfield_memory platform_memory, starfield_memory;
+ struct arena platform_arena;
+ struct arena renderer_arena;
+ struct arena starfield_arena;
+
+ struct win_audio audio;
+ struct win_video video;
+ struct win_vulkan vulkan;
struct platform_api platform_api;
+ struct renderer_api renderer_api;
struct starfield_api starfield_api;
b32 running;
@@ -27,6 +65,26 @@ internal struct platform_api
load_platform_api(void);
internal b32
-init_memory(struct starfield_memory *memory, u64 permanent_memory_cap, u64 temporary_memory_cap);
+init_memory(struct arena *platform_arena, u64 platform_capacity,
+ struct arena *renderer_arena, u64 renderer_capacity,
+ struct arena *starfield_arena, u64 starfield_capacity);
+
+internal b32
+init_audio(struct arena *arena, struct win_audio *audio, u32 period_us, u32 buffered_periods);
+
+internal void
+play_audio(struct win_audio *audio);
+
+internal b32
+init_video(struct arena *arena, struct win_video *video, HINSTANCE instance);
+
+internal void
+draw_frame(struct win_video *video);
+
+internal b32
+init_vulkan(struct arena *arena, struct win_vulkan *vulkan);
+
+internal void
+free_vulkan(struct win_vulkan *vulkan);
#endif /* WIN_STARFIELD_H */
diff --git a/shaders/shader.frag b/shaders/shader.frag
@@ -0,0 +1,8 @@
+#version 450
+
+layout(location = 0) in vec3 fragColor;
+layout(location = 0) out vec4 outColor;
+
+void main() {
+ outColor = vec4(fragColor, 1.0);
+}
diff --git a/shaders/shader.vert b/shaders/shader.vert
@@ -0,0 +1,20 @@
+#version 450
+
+layout(location = 0) out vec3 fragColor;
+
+vec2 positions[3] = vec2[](
+ vec2(0.0, -0.5),
+ vec2(0.5, 0.5),
+ vec2(-0.5, 0.5)
+);
+
+vec3 colors[3] = vec3[](
+ vec3(1.0, 0.0, 0.0),
+ vec3(0.0, 1.0, 0.0),
+ vec3(0.0, 0.0, 1.0)
+);
+
+void main() {
+ gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
+ fragColor = colors[gl_VertexIndex];
+}
diff --git a/starfield/starfield.c b/starfield/starfield.c
@@ -5,6 +5,8 @@ STARFIELD_INIT(starfield_init)
(void) thread;
(void) memory;
(void) platform;
+
+ return true;
}
STARFIELD_UPDATE(starfield_update)
@@ -21,8 +23,21 @@ STARFIELD_RENDER(starfield_render)
(void) thread;
(void) memory;
(void) platform;
- (void) video;
+ (void) renderer;
+ (void) width;
+ (void) height;
(void) dt;
+
+ struct render_frame *frame = render_api->begin_frame(renderer, width, height);
+
+ render_api->begin_render_pass(renderer, frame);
+ render_api->bind_pipeline(renderer, frame);
+ render_api->set_viewport(renderer, frame);
+ render_api->set_scissor(renderer, frame);
+ render_api->draw_vertices(renderer, frame);
+ render_api->end_render_pass(renderer, frame);
+
+ render_api->end_frame(renderer, frame);
}
STARFIELD_SAMPLE(starfield_sample)
@@ -33,6 +48,7 @@ STARFIELD_SAMPLE(starfield_sample)
(void) audio;
(void) dt;
+#if 0
/* TODO: temporary test tone */
s32 tone_hz = 256;
s32 tone_volume = INT32_MAX / 16;
@@ -53,6 +69,7 @@ STARFIELD_SAMPLE(starfield_sample)
if (tone_t >= 2 * PI)
tone_t -= 2 * PI;
}
+#endif
}
STARFIELD_FREE(starfield_free)
@@ -61,5 +78,3 @@ STARFIELD_FREE(starfield_free)
(void) memory;
(void) platform;
}
-
-#include "starfield_api.c"