From 5f0723937422530369ef56f641a7621e09b73683 Mon Sep 17 00:00:00 2001 From: James Bulman Date: Sat, 4 Oct 2025 00:46:26 +0100 Subject: [PATCH] Added memory arena Fixed frames in flight validation error in vulkan Added some utility macros Added function decorator macros Added some consolidation headers/code include files --- code/core/arena.h | 91 ++++++++++++++++ code/core/core.c | 1 + code/core/core.h | 9 ++ code/core/impl/arena.c | 210 ++++++++++++++++++++++++++++++++++++ code/core/macros.h | 33 ++++++ code/core/platform.h | 1 + code/first.c | 7 +- code/os/core.c | 3 + code/os/core.h | 14 +++ code/os/impl/windows/core.c | 38 +++++++ code/vulkan/core.c | 12 ++- code/vulkan/core.h | 21 ++-- code/vulkan/functions.h | 1 + 13 files changed, 429 insertions(+), 12 deletions(-) create mode 100644 code/core/arena.h create mode 100644 code/core/core.c create mode 100644 code/core/core.h create mode 100644 code/core/impl/arena.c create mode 100644 code/os/core.c create mode 100644 code/os/core.h create mode 100644 code/os/impl/windows/core.c diff --git a/code/core/arena.h b/code/core/arena.h new file mode 100644 index 0000000..ba65059 --- /dev/null +++ b/code/core/arena.h @@ -0,0 +1,91 @@ +#if !defined(LD_CORE_ARENA_H_) +#define LD_CORE_ARENA_H_ + +#define AlignUp(x, a) (((x) + ~((a) - 1)) & ~((a) - 1)) +#define AlignDown(x, a) (((x)) & ~((a) - 1)) + +#define KB(x) ((U64) (x) << 10) +#define MB(x) ((U64) (x) << 20) +#define GB(x) ((U64) (x) << 30) + +#if !defined(M_ARENA_CHAIN_RESERVE) + #define M_ARENA_CHAIN_RESERVE MB(4) +#endif + +function void *M_ZeroSize(void *base, U64 size); +function void *M_CopySize(void *dst, void *src, U64 size); + +typedef U32 M_ArenaFlags; +enum { + // Arena is fixed size, so will not chain + M_ARENA_FIXED_SIZE = (1 << 0), + // Don't clear allocation + M_ARENA_NO_ZERO = (1 << 1) + +}; + +typedef union M_Arena M_Arena; +union M_Arena { + struct { + M_Arena *current; + M_Arena *prev; + + U64 base; + U64 limit; + U64 offset; + + U64 committed; + U64 increment; + + M_ArenaFlags flags; + }; + + U8 __pad[64]; +}; + +typedef struct M_ArenaOpts M_ArenaOpts; +struct M_ArenaOpts { + U64 initial; + U64 increment; + + M_ArenaFlags flags; +}; + +typedef struct M_ArenaPushOpts M_ArenaPushOpts; +struct M_ArenaPushOpts { + U64 count; + U64 align; + + M_ArenaFlags flags; +}; + +function M_Arena *_M_ArenaAlloc(U64 limit, M_ArenaOpts *opts); +function void *_M_ArenaPush(M_Arena *arena, U64 esize, M_ArenaPushOpts *opts); +function void *_M_ArenaPushCopy(M_Arena *arena, void *src, U64 esize, M_ArenaPushOpts *opts); + +#define M_ArenaAlloc(limit, ...) _M_ArenaAlloc(limit, &(M_ArenaOpts) { .initial = MB(1), .increment = KB(64), ##__VA_ARGS__ }) +#define M_ArenaPush(arena, T, ...) (T *) _M_ArenaPush(arena, sizeof(T), &(M_ArenaOpts) { .count = 1, .align = Alignof(T), ##__VA_ARGS__ }) +#define M_ArenaPushCopy(arena, T, src, ...) (T *) _M_ArenaPush(arena, src, sizeof(T), &(M_ArenaOpts) { .count = 1, .align = Alignof(T), ##__VA_ARGS__ }) + +function void M_ArenaReset(M_Arena *arena); +function void M_ArenaRelease(M_Arena *arena); + +function U64 M_ArenaOffset(M_Arena *arena); + +function void M_ArenaPop(M_Arena *arena, U64 offset); +function void M_ArenaPopSize(M_Arena *arena, U64 size); + +// Temporary memory + +typedef struct M_Temp M_Temp; +struct M_Temp { + M_Arena *arena; + U64 offset; +}; + +function M_Temp M_TempAcquire(U64 count, M_Arena **conflicts); +function void M_TempRelease(M_Temp temp); + +#define M_TempScope(n, c) for (M_Temp temp = M_TempAcquire(n, c); temp.arena != 0; M_TempRelease(temp), temp.arena = 0) + +#endif // LD_CORE_ARENA_H_ diff --git a/code/core/core.c b/code/core/core.c new file mode 100644 index 0000000..d45a634 --- /dev/null +++ b/code/core/core.c @@ -0,0 +1 @@ +#include "impl/arena.c" diff --git a/code/core/core.h b/code/core/core.h new file mode 100644 index 0000000..216a612 --- /dev/null +++ b/code/core/core.h @@ -0,0 +1,9 @@ +#if !defined(LD_CORE_CORE_H_) +#define LD_CORE_CORE_H_ + +#include "types.h" +#include "platform.h" +#include "macros.h" +#include "arena.h" + +#endif // LD_CORE_CORE_H_ diff --git a/code/core/impl/arena.c b/code/core/impl/arena.c new file mode 100644 index 0000000..a86d81f --- /dev/null +++ b/code/core/impl/arena.c @@ -0,0 +1,210 @@ +void *M_ZeroSize(void *base, U64 size) { + U8 *bytes = (U8 *) base; + + while (size--) { + *bytes++ = 0; + } + + return base; +} + +void *M_CopySize(void *dst, void *src, U64 size) { + U8 *se = (U8 *) src + (size - 1); + U8 *de = (U8 *) dst + (size - 1); + + while (size--) { + *de-- = *se--; + } + + return dst; +} + +M_Arena *_M_ArenaAlloc(U64 limit, M_ArenaOpts *opts) { + M_Arena *result = 0; + + local_persist M_ArenaOpts _opts; + if (!opts) { + opts = &_opts; + } + + U64 page_size = VM_PageSize(); + U64 granularity = VM_AllocationGranularity(); + + U64 reserve = Max(AlignUp(limit, granularity), granularity); + U64 initial = Clamp(page_size, AlignUp(opts->initial, page_size), reserve); + + void *base = VM_Reserve(reserve); + if (base != 0) { + if (VM_Commit(base, initial)) { + result = cast(M_Arena *) base; + + result->current = result; + result->prev = 0; + + result->base = 0; + result->offset = sizeof(M_Arena); + result->limit = reserve; + + result->committed = initial; + result->increment = Max(AlignUp(opts->increment, page_size), page_size); + + result->flags = opts->flags; + } + else { + VM_Release(base, reserve); + } + } + + Assert(result != 0); + + return result; +} + +void *_M_ArenaPush(M_Arena *arena, U64 esize, M_ArenaPushOpts *opts) { + void *result = 0; + + local_persist M_ArenaPushOpts _opts = { .count = 1, .align = 8 }; + if (!opts) { + opts = &_opts; + } + + U64 alignment = Clamp(1, opts->align, 4096); + M_Arena *current = arena->current; + + U64 total = esize * opts->count; + U64 offset = AlignUp(current->offset, alignment); + U64 end = offset + total; + + if (end > current->limit) { + // Not enough space, chain a new arena if flags allow + if ((arena->flags & M_ARENA_FIXED_SIZE) == 0) { + U64 reserve = Min(total + sizeof(M_Arena), M_ARENA_CHAIN_RESERVE); + M_Arena *next = M_ArenaAlloc(reserve, .flags = arena->flags); + + next->base = current->base + current->limit; + + SLL_PushN(arena->current, next, prev); + + current = next; + offset = AlignUp(current->offset, alignment); + end = offset + total; + } + } + + if (end > current->committed) { + // Not enough committed, commit more memory + U64 commit_offset = AlignUp(end, current->increment); + U64 commit_limit = Min(commit_offset, current->limit); + + U8 *commit_base = cast(U8 *) current + current->committed; + U64 commit_size = commit_limit - current->committed; + + if (VM_Commit(commit_base, commit_size)) { + current->committed = commit_limit; + } + } + + if (end <= current->committed) { + // Successfully got enough memory, push the allocation + result = cast(U8 *) current + offset; + current->offset = end; + + if (((opts->flags | arena->flags) & M_ARENA_NO_ZERO) == 0){ + M_ZeroSize(result, total); + } + } + + Assert(result != 0); + Assert(((U64) result & (alignment - 1)) == 0); + + return result; +} + +void *_M_ArenaPushCopy(M_Arena *arena, void *from, U64 size, M_ArenaPushOpts *opts) { + void *result = M_CopySize(_M_ArenaPush(arena, size, opts), from, size); + return result; +} + +void M_ArenaReset(M_Arena *arena) { + M_Arena *base = arena->current; + while (base->base != 0) { + M_Arena *prev = base->prev; + + VM_Release(base, base->limit); + base = prev; + } + + Assert(arena == base); + + // @Todo: We could decommit some of the memory in the base arena, do we want to give a + // parameter to choose how much :decommit + base->offset = sizeof(M_Arena); + arena->current = base; +} + +void M_ArenaRelease(M_Arena *arena) { + M_ArenaReset(arena); + VM_Release(arena, arena->limit); +} + +U64 M_ArenaOffset(M_Arena *arena) { + U64 result = arena->current->base + arena->current->offset; + return result; +} + +void M_ArenaPop(M_Arena *arena, U64 offset) { + M_Arena *base = arena->current; + while (base->base > offset) { + M_Arena *prev = base->prev; + + VM_Release(base, base->limit); + base = prev; + } + + // :decommit + arena->current = base; + base->offset = Max(offset - base->base, sizeof(M_Arena)); +} + +void M_ArenaPopSize(M_Arena *arena, U64 size) { + U64 offset = M_ArenaOffset(arena); + offset -= Min(offset, size); + + M_ArenaPop(arena, offset); +} + +#define M_TEMP_ARENA_LIMIT GB(4) +static thread_var M_Arena *__tls_temp[2]; + +M_Temp M_TempAcquire(U64 count, M_Arena **conflicts) { + M_Temp result = { 0 }; + + for (U32 it = 0; it < ArraySize(__tls_temp); ++it) { + if (!__tls_temp[it]) { + __tls_temp[it] = M_ArenaAlloc(M_TEMP_ARENA_LIMIT, .initial = MB(1), .increment = MB(1)); + } + + result.arena = __tls_temp[it]; + + for (U32 c = 0; c < count; ++c) { + if (__tls_temp[it] == conflicts[c]) { + result.arena = 0; + break; + } + } + + if (result.arena) { + result.offset = M_ArenaOffset(result.arena); + break; + } + } + + Assert(result.arena != 0); + + return result; +} + +void M_TempRelease(M_Temp temp) { + M_ArenaPop(temp.arena, temp.offset); +} + diff --git a/code/core/macros.h b/code/core/macros.h index f1487db..f2d89ff 100644 --- a/code/core/macros.h +++ b/code/core/macros.h @@ -1,9 +1,17 @@ #if !defined(LD_CORE_MACROS_H_) #define LD_CORE_MACROS_H_ +#include + +#define Assert(exp) assert(exp) + #define ArraySize(x) (sizeof(x) / sizeof((x)[0])) #define Min(a, b) ((a) < (b) ? (a) : (b)) #define Max(a, b) ((a) > (b) ? (a) : (b)) +#define Clamp(min, x, max) (Min(Max(min, x), max)) +#define Clamp01(x) Clamp(0, x, 1) + +#define cast(x) (x) #define _Glue(a, b) a##b #define _Stringify(x) #x @@ -11,5 +19,30 @@ #define Glue(a, b) _Glue(a, b) #define Stringify(x) _Stringify(x) +#if LANG_CPP + #define Alignof(x) alignof(x) +#else + #define Alignof(x) _Alignof(x) +#endif + +// Singly linked lists (named members) +// +#define SLL_EnqueueN(h, t, n, next) (((h) == 0) ? ((h) = (t) = (n), (n)->next = 0) : ((t)->next = (n), (t) = (n), (n)->next = 0)) +#define SLL_EnqueueFrontN(h, t, n, next) (((h) == 0) ? ((h) = (t) = (n), (n)->next = 0) : ((n)->next = (h), (h) = (n))) +#define SLL_DequeueN(h, t, next) ((h) == (t) ? ((h) = 0, (t) = 0) : ((h) = (h)->next)) + +#define SLL_PushN(h, n, next) ((n)->next = (h), (h) = (n)) +#define SLL_PopN(h, next) (((h) != 0) ? (h) = (h)->next : 0) + +#define function static +#define internal static +#define global_var static +#define local_persist static + +#if COMPILER_CL + #define thread_var __declspec(thread) +#else + #define thread_var __thread +#endif #endif // LD_CORE_MACROS_H_ diff --git a/code/core/platform.h b/code/core/platform.h index 995e76d..a6cbda1 100644 --- a/code/core/platform.h +++ b/code/core/platform.h @@ -68,6 +68,7 @@ #if OS_WINDOWS #define WIN32_LEAN_AND_MEAN 1 #include + #pragma warning(disable : 4201) #elif OS_LINUX #include #endif diff --git a/code/first.c b/code/first.c index 08fe9c4..3202aea 100644 --- a/code/first.c +++ b/code/first.c @@ -2,9 +2,8 @@ #include #include -#include "core/types.h" -#include "core/platform.h" -#include "core/macros.h" +#include "core/core.h" +#include "os/core.h" #include "vulkan/core.h" @@ -114,4 +113,6 @@ int main(int argc, char **argv) { return 0; } +#include "core/core.c" +#include "os/core.c" #include "vulkan/core.c" diff --git a/code/os/core.c b/code/os/core.c new file mode 100644 index 0000000..e442952 --- /dev/null +++ b/code/os/core.c @@ -0,0 +1,3 @@ +#if OS_WINDOWS + #include "impl/windows/core.c" +#endif diff --git a/code/os/core.h b/code/os/core.h new file mode 100644 index 0000000..55ac3b8 --- /dev/null +++ b/code/os/core.h @@ -0,0 +1,14 @@ +#if !defined(LD_OS_CORE_H_) +#define LD_OS_CORE_H_ + +// Virtual memory + +function U64 VM_PageSize(); +function U64 VM_AllocationGranularity(); + +function void *VM_Reserve(U64 size); +function B32 VM_Commit(void *base, U64 size); +function void VM_Decommit(void *base, U64 size); +function void VM_Release(void *base, U64 size); + +#endif // LD_OS_CORE_H_ diff --git a/code/os/impl/windows/core.c b/code/os/impl/windows/core.c new file mode 100644 index 0000000..7c95e39 --- /dev/null +++ b/code/os/impl/windows/core.c @@ -0,0 +1,38 @@ +// Virtual memory + +U64 VM_PageSize() { + SYSTEM_INFO info; + GetSystemInfo(&info); + + U64 result = info.dwPageSize; + return result; +} + +U64 VM_AllocationGranularity() { + SYSTEM_INFO info; + GetSystemInfo(&info); + + U64 result = info.dwAllocationGranularity; + return result; +} + +void *VM_Reserve(U64 size) { + void *result = VirtualAlloc(0, size, MEM_RESERVE, PAGE_NOACCESS); + return result; +} + +B32 VM_Commit(void *base, U64 size) { + B32 result = VirtualAlloc(base, size, MEM_COMMIT, PAGE_READWRITE) != 0; + return result; +} + +void VM_Decommit(void *base, U64 size) { + VirtualFree(base, size, MEM_DECOMMIT); +} + +void VM_Release(void *base, U64 size) { + (void) size; + + VirtualFree(base, 0, MEM_RELEASE); +} + diff --git a/code/vulkan/core.c b/code/vulkan/core.c index e22392f..8b7f8c1 100644 --- a/code/vulkan/core.c +++ b/code/vulkan/core.c @@ -294,10 +294,16 @@ bool Vk_Setup(SDL_Window *window) { vk.GetSwapchainImagesKHR(vk.device, vk.swapchain.handle, &n_images, 0); vk.GetSwapchainImagesKHR(vk.device, vk.swapchain.handle, &n_images, vk.swapchain.images); + vk.in_flight = n_images + 1; + if (n_images != vk.swapchain.n_images) { printf("[Warn] :: Swapchain image count mismatch\n"); vk.swapchain.n_images = n_images; } + else if (n_images >= VK_MAX_FRAMES_IN_FLIGHT) { + printf("[Warn] :: Min image count too high: %d\n", n_images); + vk.in_flight = VK_MAX_FRAMES_IN_FLIGHT; + } VkImageViewCreateInfo create_info = { 0 }; create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; @@ -314,7 +320,7 @@ bool Vk_Setup(SDL_Window *window) { } } - for (U32 it = 0; it < VK_FRAMES_IN_FLIGHT && vk.err == VK_SUCCESS; ++it) { + for (U32 it = 0; it < vk.in_flight && vk.err == VK_SUCCESS; ++it) { Vk_Frame *frame = &vk.frames[it]; VkCommandPoolCreateInfo pool = { 0 }; @@ -353,7 +359,7 @@ bool Vk_Setup(SDL_Window *window) { Vk_Frame *Vk_FrameBegin(SDL_Window *window) { (void) window; // might need this for the resize later - Vk_Frame *frame = &vk.frames[vk.n_frames & 1]; + Vk_Frame *frame = &vk.frames[vk.n_frames % vk.in_flight]; vk.WaitForFences(vk.device, 1, &frame->fence, VK_TRUE, UINT64_MAX); @@ -372,7 +378,7 @@ Vk_Frame *Vk_FrameBegin(SDL_Window *window) { } void Vk_FrameEnd() { - Vk_Frame *frame = &vk.frames[vk.n_frames & 1]; + Vk_Frame *frame = &vk.frames[vk.n_frames % vk.in_flight]; vk.EndCommandBuffer(frame->cmd); diff --git a/code/vulkan/core.h b/code/vulkan/core.h index d4aee9a..88825ea 100644 --- a/code/vulkan/core.h +++ b/code/vulkan/core.h @@ -13,10 +13,16 @@ #define VK_USE_PLATFORM_WAYLAND_KHR 1 #endif +#if defined(function) + #undef function +#endif + #define VK_NO_PROTOTYPES 1 #include -#define VK_FRAMES_IN_FLIGHT 2 +#define function static + +#define VK_MAX_FRAMES_IN_FLIGHT 8 typedef struct Vk_Frame Vk_Frame; struct Vk_Frame { @@ -49,8 +55,10 @@ struct Vk_Context { VkPhysicalDevice gpu; VkDevice device; - U32 n_frames; // monotonically increasing - Vk_Frame frames[VK_FRAMES_IN_FLIGHT]; + U32 n_frames; // monotonically increasing + U32 in_flight; // number of frames in flight, number of swapchain images + 1 + + Vk_Frame frames[VK_MAX_FRAMES_IN_FLIGHT]; struct { U32 family; @@ -73,8 +81,9 @@ struct Vk_Context { extern Vk_Context vk; -static bool Vk_Setup(SDL_Window *window); -static Vk_Frame *Vk_FrameBegin(SDL_Window *window); -static void Vk_FrameEnd(); +function bool Vk_Setup(SDL_Window *window); + +function Vk_Frame *Vk_FrameBegin(SDL_Window *window); +function void Vk_FrameEnd(); #endif // LD_VULKAN_CORE_H_ diff --git a/code/vulkan/functions.h b/code/vulkan/functions.h index 45288df..426bb55 100644 --- a/code/vulkan/functions.h +++ b/code/vulkan/functions.h @@ -38,6 +38,7 @@ VK_FUNC(QueuePresentKHR); VK_FUNC(BeginCommandBuffer); VK_FUNC(EndCommandBuffer); + VK_FUNC(DeviceWaitIdle); VK_FUNC(CmdPipelineBarrier2); VK_FUNC(CmdBeginRendering);