Various improvements

- Support static linking
- Buffer manager
This commit is contained in:
Kevin Trogant 2023-11-23 22:03:02 +01:00
parent b2037519cc
commit 11ec5a0cd2
14 changed files with 372 additions and 21 deletions

View File

@ -1,5 +1,5 @@
project('voyage', 'c',
default_options: ['buildtype=debugoptimized', 'b_sanitize=address', 'c_std=c17', 'warning_level=3'])
default_options: ['buildtype=debug', 'b_sanitize=address', 'c_std=c17', 'warning_level=3'])
compiler = meson.get_compiler('c')
buildtype = get_option('buildtype')
@ -21,6 +21,10 @@ elif compiler.get_argument_syntax() == 'msvc'
endif
endif
if get_option('default_library') == 'static'
add_project_arguments(['-DVY_STATIC_LIB'], language : 'c')
endif
# Debug specific flags
if buildtype == 'debug' or buildtype == 'debugoptimized'
add_project_arguments([ '-DVY_DEBUG'], language : 'c')
@ -51,6 +55,7 @@ runtime_lib = library('vyrt',
'src/runtime/jobs.h',
'src/runtime/aio.h',
'src/runtime/file_tab.h',
'src/runtime/buffer_manager.h',
'src/runtime/error_report.c',
'src/runtime/gfx_main.c',
@ -65,6 +70,7 @@ runtime_lib = library('vyrt',
'src/runtime/jobs.c',
'src/runtime/aio.c',
'src/runtime/file_tab.c',
'src/runtime/buffer_manager.c',
# Contrib Sources
'contrib/xxhash/xxhash.c',
@ -72,6 +78,10 @@ runtime_lib = library('vyrt',
include_directories : incdir,
c_pch : 'pch/rt_pch.h')
# Renderer libraries
static_renderer_lib = 'NONE'
if vk_dep.found()
platform_defs = []
if get_option('use_xlib')
@ -98,12 +108,22 @@ if vk_dep.found()
link_with : [runtime_lib],
c_pch : 'pch/vk_pch.h',
c_args : platform_defs)
static_renderer_lib = vk_renderer_lib
endif
game_link_libs = []
if get_option('default_library') == 'static'
game_link_libs = [runtime_lib, static_renderer_lib]
else
game_link_libs = [runtime_lib]
endif
executable('voyage',
'src/game/voyage.c',
include_directories : incdir,
link_with : [runtime_lib],
link_with : game_link_libs,
win_subsystem : 'windows')

View File

@ -52,8 +52,7 @@ static void ReleasePipelineSlot(vy_gfx_pipeline_handle id) {
_storage.generation_in_use[slot] &= ~0x1;
}
VY_DLLEXPORT vy_gfx_pipeline_handle
vyCompileComputePipeline(const vy_compute_pipeline_info *info) {
vy_gfx_pipeline_handle VY_RENDERER_API_FN(CompileComputePipeline)(const vy_compute_pipeline_info *info) {
#if 0
char info_log[512];
@ -94,8 +93,7 @@ vyCompileComputePipeline(const vy_compute_pipeline_info *info) {
return StorePipeline(pipeline);
}
VY_DLLEXPORT vy_gfx_pipeline_handle
vyCompileGraphicsPipeline(const vy_graphics_pipeline_info *info) {
vy_gfx_pipeline_handle VY_RENDERER_API_FN(CompileGraphicsPipeline)(const vy_graphics_pipeline_info *info) {
#if 0
char info_log[512];

View File

@ -85,7 +85,7 @@ DebugUtilsMessengerCb(VkDebugUtilsMessageSeverityFlagBitsEXT severity,
extern vy_cvar r_VkPreferredSwapchainImages;
extern vy_cvar r_VkPreferMailboxMode;
VY_DLLEXPORT void vyRegisterCVars(void) {
void VY_RENDERER_API_FN(RegisterCVars)(void) {
vyRegisterCVAR(&r_VkEnableAPIAllocTracking);
vyRegisterCVAR(&r_VkPhysDeviceName);
vyRegisterCVAR(&r_VkPreferredSwapchainImages);
@ -470,7 +470,7 @@ static vy_result CreateDevice(void) {
return VY_SUCCESS;
}
VY_DLLEXPORT vy_result vyInit(const vy_renderer_init_info *info) {
vy_result VY_RENDERER_API_FN(Init)(const vy_renderer_init_info *info) {
vyLog("vk", "Init");
_tracking_alloc_cbs.pUserData = NULL;
@ -503,7 +503,7 @@ VY_DLLEXPORT vy_result vyInit(const vy_renderer_init_info *info) {
return VY_SUCCESS;
}
VY_DLLEXPORT void vyShutdown(void) {
void VY_RENDERER_API_FN(Shutdown)(void) {
vyLog("vk", "Shutdown");
vkDeviceWaitIdle(g_gpu.device);
vyDestroySwapchain();

View File

@ -3,6 +3,7 @@
#include "gfx.h"
#include "aio.h"
#include "renderer_api.h"
#include "buffer_manager.h"
extern void __RegisterRuntimeCVars(void);
@ -37,6 +38,11 @@ VY_DLLEXPORT int vyWin32Entry(HINSTANCE hInstance,
/* TODO: Parse the cvar config file */
if (vyInitBufferManager() != VY_SUCCESS) {
vyReportError("BUFFERMGR", "Init failed.");
return 1;
}
if (vyInitFileTab(1024) != VY_SUCCESS) {
vyReportError("FTAB", "Init failed.");
return 1;
@ -131,6 +137,7 @@ VY_DLLEXPORT int vyWin32Entry(HINSTANCE hInstance,
vyShutdownAIO();
vyShutdownAIO();
vyShutdownFileTab();
vyShutdownBufferManager();
return 0;
}
@ -180,6 +187,11 @@ VY_DLLEXPORT int vyXlibEntry(int argc, char **argv) {
__RegisterRuntimeCVars();
vyRegisterRendererCVars();
if (vyInitBufferManager() != VY_SUCCESS) {
vyReportError("BUFFERMGR", "Init failed.");
return 1;
}
if (vyInitFileTab(1024) != VY_SUCCESS) {
vyReportError("FTAB", "Init failed.");
return 1;
@ -260,6 +272,7 @@ VY_DLLEXPORT int vyXlibEntry(int argc, char **argv) {
XCloseDisplay(dpy);
vyShutdownAIO();
vyShutdownFileTab();
vyShutdownBufferManager();
return 0;
}

View File

@ -0,0 +1,250 @@
#include "runtime.h"
#include "threading.h"
#include "config.h"
#include "buffer_manager.h"
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <assert.h>
typedef struct vy_buffer_region_s {
void *memory;
int16_t *refcounts; // One per block
uint32_t *bitmap;
size_t block_count;
vy_mutex *guard;
} vy_buffer_region;
/* Count leading zeroes.
* Note that the return value of __builtin_clz(0) is undefined. */
#ifdef _MSC_VER
#include <intrin.h>
#define lzcnt32(x) __lzcnt((x))
#define popcnt32(x) __popcnt((x))
static __forceinline uint32_t tzcnt32(uint32_t x) {
unsigned long i;
_BitScanForward(&i, x);
return (uint32_t)i;
}
static inline bool IsLZCNTSupported(void) {
#define Type 0x80000001
int info[4];
__cpuid(info, Type);
return (info[2] & (1 << 5)) != 0;
#undef Type
}
#elif defined(__GNUC__)
#define lzcnt32(x) __builtin_clz((x))
#define tzcnt32(x) __builtin_ctz((x))
#define popcnt32(x) __builtin_popcount((x))
#define IsLZCNTSupported() true
#endif
/* NOTE(Kevin): Keep these sorted! */
static size_t _block_sizes[] = {VY_KB(512), VY_MB(1), VY_MB(4), VY_MB(8) };
#define NUM_BLOCK_SIZES (sizeof(_block_sizes) / sizeof(_block_sizes[0]))
static vy_buffer_region _regions[NUM_BLOCK_SIZES];
VY_CVAR_SZ(
rt_BufferManagerMemory,
"Total number of bytes allocated for the buffer manager. Default: 1GB", VY_GB(1));
VY_DLLEXPORT vy_result vyInitBufferManager(void) {
if ((rt_BufferManagerMemory.sz % NUM_BLOCK_SIZES) != 0)
vyLog("BUFFERMGR",
"Configured memory amount is not dividable by number of block "
"sizes: %u MB/%u",
rt_BufferManagerMemory.sz / (1024 * 1024),
NUM_BLOCK_SIZES);
size_t mem_per_size = rt_BufferManagerMemory.sz / NUM_BLOCK_SIZES;
for (unsigned int i = 0; i < NUM_BLOCK_SIZES; ++i) {
if ((mem_per_size % _block_sizes[i]) != 0)
vyLog("BUFFERMGR",
"Memory per block size is not dividable by block size: %u "
"MB/%u KB",
mem_per_size / (1024 * 1024),
_block_sizes[i] / 1024);
size_t block_count = mem_per_size / _block_sizes[i];
_regions[i].block_count = block_count;
_regions[i].guard = vyCreateMutex();
if (!_regions[i].guard) {
vyReportError("BUFFERMGR", "Failed to create guard mutex %u", i);
return VY_BUFFER_MGR_MUTEX_CREATION_FAILED;
}
_regions[i].memory = malloc(mem_per_size);
if (!_regions[i].memory) {
vyDestroyMutex(_regions[i].guard);
vyReportError("BUFFERMGR", "Failed to allocate memory.", i);
return VY_BUFFER_MGR_OUT_OF_MEMORY;
}
_regions[i].bitmap = calloc((block_count + 31) / 32, sizeof(uint32_t));
if (!_regions[i].bitmap) {
vyDestroyMutex(_regions[i].guard);
free(_regions[i].memory);
vyReportError("BUFFERMGR", "Failed to allocate memory.", i);
return VY_BUFFER_MGR_OUT_OF_MEMORY;
}
_regions[i].refcounts = calloc(block_count, sizeof(uint16_t));
if (!_regions[i].refcounts) {
vyDestroyMutex(_regions[i].guard);
free(_regions[i].memory);
free(_regions[i].bitmap);
vyReportError("BUFFERMGR", "Failed to allocate memory.", i);
return VY_BUFFER_MGR_OUT_OF_MEMORY;
}
}
return VY_SUCCESS;
}
VY_DLLEXPORT void vyShutdownBufferManager(void) {
for (unsigned int i = 0; i < NUM_BLOCK_SIZES; ++i) {
vyDestroyMutex(_regions[i].guard);
free(_regions[i].memory);
free(_regions[i].bitmap);
free(_regions[i].refcounts);
}
}
VY_DLLEXPORT void *vyAllocBuffer(size_t size) {
assert(IsLZCNTSupported());
// Determine the best block size to use
size_t required_blocks = (size + _block_sizes[0] - 1) / _block_sizes[0];
size_t best_fit = 0;
for (size_t i = 1; i < NUM_BLOCK_SIZES; ++i) {
size_t block_count = (size + _block_sizes[i] - 1) / _block_sizes[i];
if (block_count < required_blocks && size >= _block_sizes[i]) {
required_blocks = block_count;
best_fit = i;
}
}
void *result = NULL;
vy_buffer_region *region = &_regions[best_fit];
vyLockMutex(region->guard);
size_t dword_count = (region->block_count + 31) / 32;
if (required_blocks < 32) {
/* Fast path for allocations that potentially fit into one dword */
uint32_t in_use_mask = (1ull << required_blocks) - 1;
size_t max_occupancy = 32 - required_blocks;
for (size_t i = 0; i < dword_count; ++i) {
size_t block_index = 0;
if (region->bitmap[i] != 0 && popcnt32(region->bitmap[i]) < max_occupancy) {
size_t free_high_blocks = lzcnt32(region->bitmap[i]);
if (free_high_blocks >= required_blocks) {
/* High blocks are free */
size_t first_free = 32 - free_high_blocks;
region->bitmap[i] |= (in_use_mask << first_free);
block_index = i * 32 + first_free;
result = (char *)region->memory +
block_index * _block_sizes[best_fit];
} else if (tzcnt32(region->bitmap[i]) >= required_blocks) {
/* Low blocks are free */
region->bitmap[i] |= in_use_mask;
block_index = i * 32;
result = (char *)region->memory + block_index * _block_sizes[best_fit];
} else {
/* Check if we can find a large enough range of free blocks.
* Start after the first set bit.
*/
for (uint32_t j = tzcnt32(region->bitmap[i]) + 1; j < 32 - required_blocks; ++j) {
if ((region->bitmap[i] & in_use_mask << j) == 0) {
region->bitmap[i] |= (in_use_mask << j);
block_index = i * 32 + j;
result = (char *)region->memory + block_index * _block_sizes[best_fit];
break;
}
}
}
} else if (region->bitmap[i] == 0) {
/* All free */
region->bitmap[i] = in_use_mask;
block_index = i * 32;
result = (char *)region->memory + block_index * _block_sizes[best_fit];
} else if (i < dword_count - 1) {
/* Check if we can use high blocks from this dword and low blocks from the next one */
size_t high_blocks = lzcnt32(region->bitmap[i]);
size_t low_blocks = (region->bitmap[i + 1] != 0) ? tzcnt32(region->bitmap[i + 1]) : 32;
if (high_blocks + low_blocks >= required_blocks) {
size_t high_mask = (1u << high_blocks) - 1;
size_t first_free = 32 - high_blocks;
size_t low_mask = (1u << (required_blocks - high_blocks)) - 1;
region->bitmap[i] |= (high_mask << first_free);
region->bitmap[i + 1] |= low_mask;
block_index = i * 32 + first_free;
result = (char *)region->memory + block_index * _block_sizes[best_fit];
}
}
if (result) {
for (size_t j = 0; j < required_blocks; ++j)
region->refcounts[block_index + j] = 1;
break;
}
}
}
vyUnlockMutex(region->guard);
return result;
}
VY_DLLEXPORT void vyReleaseBuffer(const void *begin, size_t size) {
uintptr_t begin_addr = (uintptr_t)begin;
for (unsigned int i = 0; i < NUM_BLOCK_SIZES; ++i) {
uintptr_t region_addr = (uintptr_t)_regions[i].memory;
size_t region_size = _block_sizes[i] * _regions[i].block_count;
if (begin_addr >= region_addr &&
begin_addr + size <= region_addr + region_size) {
size_t block_count = (size + _block_sizes[i] - 1) / _block_sizes[i];
size_t first_block = (begin_addr - region_addr) / _block_sizes[i];
vyLockMutex(_regions[i].guard);
for (size_t j = 0; j < block_count; ++j) {
size_t dword = (first_block + j) / 32;
size_t bit = (first_block + j) % 32;
if (--_regions[i].refcounts[first_block + j] == 0)
_regions[i].bitmap[dword] &= ~(1u << bit);
}
vyUnlockMutex(_regions[i].guard);
return;
}
}
vyLog("BUFFERMGR", "Tried to release an invalid buffer");
}
VY_DLLEXPORT void vyIncreaseBufferRefCount(const void *begin, size_t size) {
uintptr_t begin_addr = (uintptr_t)begin;
for (unsigned int i = 0; i < NUM_BLOCK_SIZES; ++i) {
uintptr_t region_addr = (uintptr_t)_regions[i].memory;
size_t region_size = _block_sizes[i] * _regions[i].block_count;
if (begin_addr >= region_addr &&
begin_addr + size <= region_addr + region_size) {
size_t block_count = (size + _block_sizes[i] - 1) / _block_sizes[i];
size_t first_block = (begin_addr - region_addr) / _block_sizes[i];
vyLockMutex(_regions[i].guard);
for (size_t j = 0; j < block_count; ++j) {
++_regions[i].refcounts[first_block + j];
}
vyUnlockMutex(_regions[i].guard);
return;
}
}
vyLog("BUFFERMGR", "Tried to increase the refcount of an invalid buffer");
}

View File

@ -0,0 +1,21 @@
#ifndef VY_BUFFER_MANAGER_H
#define VY_BUFFER_MANAGER_H
#include "runtime.h"
enum {
VY_BUFFER_MGR_OUT_OF_MEMORY = VY_SUCCESS + 1,
VY_BUFFER_MGR_MUTEX_CREATION_FAILED,
};
VY_DLLEXPORT vy_result vyInitBufferManager(void);
VY_DLLEXPORT void vyShutdownBufferManager(void);
VY_DLLEXPORT void *vyAllocBuffer(size_t size);
VY_DLLEXPORT void vyReleaseBuffer(const void *begin, size_t size);
VY_DLLEXPORT void vyIncreaseBufferRefCount(const void *begin, size_t size);
#endif

View File

@ -8,6 +8,7 @@ typedef enum
VY_CVAR_TYPE_INT,
VY_CVAR_TYPE_FLOAT,
VY_CVAR_TYPE_STRING,
VY_CVAR_TYPE_SIZE,
} vy_cvar_type;
typedef struct
@ -18,6 +19,7 @@ typedef struct
int i;
float f;
const char *s;
size_t sz;
};
vy_cvar_type type;
} vy_cvar;
@ -37,6 +39,11 @@ typedef struct
.description = d, \
.s = (v), \
.type = VY_CVAR_TYPE_STRING}
#define VY_CVAR_SZ(n, d, v) \
vy_cvar n = {.name = #n, \
.description = d, \
.sz = (v), \
.type = VY_CVAR_TYPE_SIZE}
VY_DLLEXPORT void vyRegisterCVAR(vy_cvar *cvar);

View File

@ -4,6 +4,10 @@
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
VY_DLLEXPORT vy_dynlib vyOpenCallerLib(void) {
return (vy_dynlib)GetModuleHandleW(NULL);
}
VY_DLLEXPORT vy_dynlib vyOpenLib(const char *libname) {
wchar_t libname_w[MAX_PATH];
MultiByteToWideChar(CP_UTF8,
@ -27,6 +31,10 @@ VY_DLLEXPORT void vyCloseLib(vy_dynlib lib) {
#elif defined(__linux__)
#include <dlfcn.h>
VY_DLLEXPORT vy_dynlib vyOpenCallerLib(void) {
return dlopen(NULL, RTLD_NOW | RTLD_LOCAL);
}
VY_DLLEXPORT vy_dynlib vyOpenLib(const char *libname) {
return dlopen(libname, RTLD_NOW | RTLD_LOCAL);
}

View File

@ -17,6 +17,8 @@ typedef void *vy_dynlib;
VY_DLLEXPORT vy_dynlib vyOpenLib(const char *libname);
VY_DLLEXPORT vy_dynlib vyOpenCallerLib(void);
VY_DLLEXPORT void *vyGetSymbol(vy_dynlib lib, const char *symbol);
VY_DLLEXPORT void vyCloseLib(vy_dynlib lib);

View File

@ -24,9 +24,21 @@ VY_CVAR_S(rt_Renderer,
extern bool
vyLoadShaders(const char **paths, vy_shader *shaders, unsigned int count);
#ifdef VY_STATIC_LIB
extern void VY_RENDERER_API_FN(RegisterCVars)(void);
extern vy_result VY_RENDERER_API_FN(Init)(const vy_renderer_init_info *);
extern void VY_RENDERER_API_FN(Shutdown)(void);
extern vy_gfx_pipeline_handle VY_RENDERER_API_FN(CompileComputePipeline)(
const vy_compute_pipeline_info *);
extern vy_gfx_pipeline_handle VY_RENDERER_API_FN(CompileGraphicsPipeline)(
const vy_graphics_pipeline_info *);
#endif
static bool LoadRenderer(void) {
#if !defined(VY_STATIC_LIB)
#define RETRIEVE_SYMBOL(name, type) \
g_renderer.name = (type *)vyGetSymbol(_renderer_lib, "vy" #name); \
g_renderer.name = (type *)vyGetSymbol(_renderer_lib, "vyRen" #name); \
if (!g_renderer.name) { \
vyReportError( \
"GFX", \
@ -49,7 +61,6 @@ static bool LoadRenderer(void) {
RETRIEVE_SYMBOL(CompileComputePipeline, vy_compile_compute_pipeline_fn);
RETRIEVE_SYMBOL(CompileGraphicsPipeline,
vy_compile_graphics_pipeline_fn);
return true;
} else {
vyReportError("GFX",
"Unsupported renderer backend: (%s) %s",
@ -58,6 +69,14 @@ static bool LoadRenderer(void) {
return false;
}
#undef RETRIEVE_SYMBOL
#else
g_renderer.RegisterCVars = &vyRenRegisterCVars;
g_renderer.Init = &vyRenInit;
g_renderer.Shutdown = &vyRenShutdown;
g_renderer.CompileComputePipeline = &vyRenCompileComputePipeline;
g_renderer.CompileGraphicsPipeline = &vyRenCompileGraphicsPipeline;
#endif
return true;
}
VY_DLLEXPORT void vyRegisterRendererCVars(void) {

View File

@ -1,3 +1,8 @@
/* TODO(Kevin):
* This should move into a standalone tool.
*/
#include <assert.h>
#include <limits.h>
#include <stdbool.h>

View File

@ -54,6 +54,8 @@ typedef struct {
vy_compile_graphics_pipeline_fn *CompileGraphicsPipeline;
} vy_renderer_api;
#define VY_RENDERER_API_FN(name) VY_DLLEXPORT vyRen##name
#ifndef VY_DONT_DEFINE_RENDERER_GLOBAL
extern vy_renderer_api g_renderer;
#endif

View File

@ -5,7 +5,7 @@
#include <stddef.h>
#ifdef _WIN32
#if defined(_WIN32)
#define VY_DLLEXPORT __declspec(dllexport)
#else
#define VY_DLLEXPORT
@ -14,6 +14,10 @@
#define VY_UNUSED(x) ((void)sizeof((x)))
#define VY_ARRAY_COUNT(x) (sizeof((x)) / sizeof((x)[0]))
#define VY_KB(n) ((n) * 1024U)
#define VY_MB(n) ((n) * 1024U * 1024U)
#define VY_GB(n) ((n) * 1024U * 1024U * 1024U)
typedef unsigned int vy_result;
#define VY_SUCCESS 0

View File

@ -4,6 +4,7 @@ extern vy_cvar rt_Renderer;
extern vy_cvar rt_Fullscreen;
extern vy_cvar rt_WindowWidth;
extern vy_cvar rt_WindowHeight;
extern vy_cvar rt_BufferManagerMemory;
void __RegisterRuntimeCVars(void)
{
@ -11,4 +12,5 @@ void __RegisterRuntimeCVars(void)
vyRegisterCVAR(&rt_Fullscreen);
vyRegisterCVAR(&rt_WindowWidth);
vyRegisterCVAR(&rt_WindowHeight);
vyRegisterCVAR(&rt_BufferManagerMemory);
}