starfield

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

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:
MREADME | 1+
Aapi/starfield/intrinsics.h | 0
Aapi/starfield/maths.h | 0
Aapi/starfield/types.h | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dapi/starfield_api.c | 7-------
Mapi/starfield_api.h | 148+++++++++++++++++++++++++++++--------------------------------------------------
Mbuild_linux.sh | 24++++++++++++------------
Mbuild_win.sh | 7+++++--
Mplatform/linux_starfield.c | 283+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Mplatform/linux_starfield.h | 48++++++++++++++++++++++++++++++++++++++----------
Aplatform/renderer.c | 835+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aplatform/renderer.h | 19+++++++++++++++++++
Aplatform/utils.c | 10++++++++++
Aplatform/utils.h | 34++++++++++++++++++++++++++++++++++
Mplatform/win_starfield.c | 150++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Mplatform/win_starfield.h | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Ashaders/shader.frag | 8++++++++
Ashaders/shader.vert | 20++++++++++++++++++++
Mstarfield/starfield.c | 21++++++++++++++++++---
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"