Write the first compiled resources to files

This commit is contained in:
Kevin Trogant 2024-01-31 22:48:51 +01:00
parent 9670844bb2
commit 3a9f9d4986
24 changed files with 3265 additions and 370 deletions

1910
contrib/stb_sprintf.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
#include "aio.h" #include "aio.h"
#include "threading.h"
#include "config.h" #include "config.h"
#include "threading.h"
#ifdef _WIN32 #ifdef _WIN32
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
@ -111,7 +111,7 @@ RT_CVAR_I(rt_MaxConcurrentAsyncIO,
rt_result InitAIO(void) { rt_result InitAIO(void) {
unsigned int max_concurrent_operations = rt_MaxConcurrentAsyncIO.i; unsigned int max_concurrent_operations = rt_MaxConcurrentAsyncIO.i;
_ringbuffer.guard = rtCreateMutex(); _ringbuffer.guard = rtCreateMutex();
if (!_ringbuffer.guard) { if (!_ringbuffer.guard) {
return RT_AIO_OUT_OF_MEMORY; return RT_AIO_OUT_OF_MEMORY;
} }
@ -223,6 +223,96 @@ RT_DLLEXPORT rt_result rtSubmitLoadBatch(const rt_load_batch *batch, rt_aio_hand
return RT_SUCCESS; return RT_SUCCESS;
} }
RT_DLLEXPORT rt_result rtSubmitWriteBatch(const rt_write_batch *batch, rt_aio_handle *handles) {
if (batch->num_writes > RT_LOAD_BATCH_MAX_SIZE) {
return RT_AIO_WRITE_TOO_LARGE;
}
rt_ringbuffer_space rbspace = ReserveRingbufferSpace(batch->num_writes);
if (!rbspace.a) {
rtReportError("aio", "Too many pending file operations");
return RT_AIO_TOO_MANY_OPERATIONS;
}
for (unsigned int i = 0; i < batch->num_writes; ++i) {
rt_aio *op = (i < rbspace.a_count) ? &rbspace.a[i] : &rbspace.b[i - rbspace.a_count];
op->state = RT_AIO_STATE_PENDING;
const char *file_path = rtGetFilePath(batch->writes[i].file);
if (!file_path) {
rtReportError("aio", "Failed to resolve file path for a batched write");
op->state = RT_AIO_STATE_INVALID;
handles[i] = RT_AIO_INVALID_HANDLE;
continue;
}
#ifdef _WIN32
op->overlapped = (OVERLAPPED){
/* ReadFileEx does not use hEvent and we are free to use it for our own purposes. */
.hEvent = (HANDLE)(op),
.Internal = 0,
.InternalHigh = 0,
.Offset = (DWORD)(batch->writes[i].offset & MAXDWORD),
.OffsetHigh = (DWORD)(batch->writes[i].offset >> 32),
};
WCHAR wpath[MAX_PATH];
if (MultiByteToWideChar(CP_UTF8,
MB_PRECOMPOSED,
file_path,
-1,
wpath,
RT_ARRAY_COUNT(wpath)) == 0) {
rtReportError("aio", "MultiByteToWideChar failed with error code: %u", GetLastError());
op->state = RT_AIO_STATE_FINISHED;
handles[i] = RT_AIO_INVALID_HANDLE;
continue;
}
HANDLE file_handle = CreateFileW(wpath,
GENERIC_WRITE,
0,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
NULL);
if (file_handle == INVALID_HANDLE_VALUE) {
DWORD err = GetLastError();
char error_msg[256];
Win32ErrorToString(err, error_msg, 256);
rtReportError("aio",
"CreateFileW failed for file: %s with error code: %u (%s)",
file_path,
err,
error_msg);
op->state = RT_AIO_STATE_INVALID;
handles[i] = RT_AIO_INVALID_HANDLE;
continue;
}
op->file_handle = file_handle;
BOOL result = WriteFileEx(file_handle,
batch->writes[i].buffer,
(DWORD)batch->writes[i].num_bytes,
&op->overlapped,
win32CompletionRoutine);
DWORD err = GetLastError();
if (!result || (err != ERROR_SUCCESS && err != ERROR_ALREADY_EXISTS)) {
char error_msg[256];
Win32ErrorToString(err, error_msg, 256);
rtReportError("aio", "WriteFileEx failed with error code: %u (%s)", err, error_msg);
op->state = RT_AIO_STATE_FINISHED;
handles[i] = RT_AIO_INVALID_HANDLE;
CloseHandle(file_handle);
op->file_handle = NULL;
}
/* Handle is the index into the ringbuffer + 1 */
ptrdiff_t op_idx = op - _ringbuffer.storage;
handles[i] = (uint32_t)op_idx + 1;
#endif
}
return RT_SUCCESS;
}
RT_DLLEXPORT volatile rt_aio_state rtGetAIOState(rt_aio_handle handle) { RT_DLLEXPORT volatile rt_aio_state rtGetAIOState(rt_aio_handle handle) {
if (handle == RT_AIO_INVALID_HANDLE || handle > _ringbuffer.capacity) if (handle == RT_AIO_INVALID_HANDLE || handle > _ringbuffer.capacity)
return RT_AIO_STATE_INVALID; return RT_AIO_STATE_INVALID;
@ -270,10 +360,9 @@ RT_DLLEXPORT rt_aio_state rtWaitForAIOCompletion(rt_aio_handle handle) {
return state; return state;
} }
RT_DLLEXPORT rt_result rtSubmitSingleLoad(rt_file_load load, rt_aio_handle *handle) { RT_DLLEXPORT rt_result rtSubmitSingleLoad(rt_file_load load, rt_aio_handle *handle) {
rt_load_batch batch; rt_load_batch batch;
batch.loads[0] = load; batch.loads[0] = load;
batch.num_loads = 1; batch.num_loads = 1;
return rtSubmitLoadBatch(&batch, handle); return rtSubmitLoadBatch(&batch, handle);
} }

View File

@ -33,13 +33,35 @@ typedef struct {
unsigned int num_loads; unsigned int num_loads;
} rt_load_batch; } rt_load_batch;
typedef struct {
size_t num_bytes; /** Number of bytes to write */
size_t offset; /** Offset at which to start writing */
/* Source buffer with at least num_bytes bytes.
* Must be valid until the write is finished.
*/
const void *buffer;
rt_file_id file;
} rt_file_write;
#define RT_WRITE_OFFSET_APPEND ((size_t)-1)
#define RT_WRITE_BATCH_MAX_SIZE 64
/* A batch of writes that will be started together.
* The aio system will hand these to the OS. */
typedef struct {
rt_file_write writes[RT_WRITE_BATCH_MAX_SIZE];
unsigned int num_writes;
} rt_write_batch;
#define RT_AIO_INVALID_HANDLE 0 #define RT_AIO_INVALID_HANDLE 0
/** Handle for an async io operation. Can be used to query the state and result. */ /** Handle for an async io operation. Can be used to query the state and result. */
typedef uint32_t rt_aio_handle; typedef uint32_t rt_aio_handle;
enum { enum {
RT_AIO_LOAD_TOO_LARGE = (RT_SUCCESS + 1), RT_AIO_LOAD_TOO_LARGE = RT_CUSTOM_ERROR_START,
RT_AIO_WRITE_TOO_LARGE,
RT_AIO_TOO_MANY_OPERATIONS, RT_AIO_TOO_MANY_OPERATIONS,
RT_AIO_OUT_OF_MEMORY, RT_AIO_OUT_OF_MEMORY,
}; };
@ -51,14 +73,16 @@ typedef enum {
RT_AIO_STATE_FAILED, RT_AIO_STATE_FAILED,
} rt_aio_state; } rt_aio_state;
RT_DLLEXPORT rt_result rtSubmitLoadBatch(const rt_load_batch *batch, rt_aio_handle *handles);
RT_DLLEXPORT volatile rt_aio_state rtGetAIOState(rt_aio_handle handle); RT_DLLEXPORT volatile rt_aio_state rtGetAIOState(rt_aio_handle handle);
/* Blocks until the given operation is no longer pending. /* Blocks until the given operation is no longer pending.
* Returns the state that caused the wait to end. The handle is still valid after this function returned. */ * Returns the state that caused the wait to end. The handle is still valid after this function
* returned. */
RT_DLLEXPORT rt_aio_state rtWaitForAIOCompletion(rt_aio_handle handle); RT_DLLEXPORT rt_aio_state rtWaitForAIOCompletion(rt_aio_handle handle);
RT_DLLEXPORT rt_result rtSubmitLoadBatch(const rt_load_batch *batch, rt_aio_handle *handles);
/* Releases the internal storage for the operation. /* Releases the internal storage for the operation.
* The system is allowed to re-use the same handle value for new operations after this was called. * The system is allowed to re-use the same handle value for new operations after this was called.
*/ */
@ -70,6 +94,8 @@ RT_DLLEXPORT rt_result rtSubmitSingleLoad(rt_file_load load, rt_aio_handle *hand
* Returns the state that caused the wait for completion to return. */ * Returns the state that caused the wait for completion to return. */
RT_DLLEXPORT rt_aio_state rtSubmitSingleLoadSync(rt_file_load load); RT_DLLEXPORT rt_aio_state rtSubmitSingleLoadSync(rt_file_load load);
RT_DLLEXPORT rt_result rtSubmitWriteBatch(const rt_write_batch *batch, rt_aio_handle *handles);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

45
src/runtime/assert.c Normal file
View File

@ -0,0 +1,45 @@
#include "runtime.h"
#include "config.h"
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#endif
#include <stdio.h>
RT_CVAR_I(rt_AssertEnabled, "Enables or disables asserts in non-release builds. Default: 1", 1);
#define ASSERT_HANDLER_DBGBREAK 0
#define ASSERT_HANDLER_CONTINUE 1
RT_DLLEXPORT int rtAssertHandler(const char *expr, const char *msg, const char *file, int line) {
if (!rt_AssertEnabled.i)
return ASSERT_HANDLER_CONTINUE;
rtLog("ASSERT", "[%s:%d] Assertion (%s) failed: %s", file, line, expr, msg);
#ifdef _WIN32
char outmessage[512];
snprintf(outmessage,
511,
"Assertion failed: %s\nMessage: %s\n%s:%d\nPress \"Yes\" to debug-break, \"No\" to "
"continue with asserts enabled or \"Cancel\" to disable asserts.",
expr,
msg,
file,
line);
outmessage[511] = '\0';
DWORD action = MessageBoxA(NULL, outmessage, "Assertion Failed", MB_YESNOCANCEL | MB_ICONERROR);
if (action == IDYES) {
return ASSERT_HANDLER_DBGBREAK;
} else if (action == IDCANCEL) {
rt_AssertEnabled.i = 0;
} else if (action != IDNO) {
rtReportError("CORE", "MessageBoxA for a failed assertion failed.");
__debugbreak();
ExitProcess(1);
}
#endif
return ASSERT_HANDLER_CONTINUE;
}

View File

@ -1,15 +1,17 @@
#include "asset_compiler.h"
#include "buffer_manager.h"
#include "config.h"
#include "file_tab.h"
#include "fsutils.h"
#include "mem_arena.h"
#include "resources.h"
#include "runtime.h" #include "runtime.h"
#include "threading.h" #include "threading.h"
#include "config.h"
#include "fsutils.h"
#include "file_tab.h"
#include "mem_arena.h"
#include "buffer_manager.h"
#include <string.h>
#include <assert.h> #include <assert.h>
#include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef RT_BUILD_ASSET_COMPILER #ifndef RT_BUILD_ASSET_COMPILER
#error This should only be built when RT_BUILD_ASSET_COMPILER is defined. #error This should only be built when RT_BUILD_ASSET_COMPILER is defined.
@ -17,15 +19,17 @@
typedef struct { typedef struct {
uint64_t last_processed; uint64_t last_processed;
rt_resource_id resources[RT_MAX_RESOURCES_PER_ASSET];
unsigned int resource_count;
bool in_processing;
} rt_asset_data; } rt_asset_data;
typedef struct { typedef struct {
rt_file_id *files; rt_file_id *files;
rt_asset_data *data; rt_asset_data *data;
rt_rwlock lock;
} rt_asset_db; } rt_asset_db;
typedef rt_result rt_asset_processor_fn(rt_file_id file, rt_arena *arena);
typedef struct { typedef struct {
const char *file_ext; const char *file_ext;
rt_asset_processor_fn *proc; rt_asset_processor_fn *proc;
@ -60,7 +64,7 @@ static rt_asset_db _asset_db;
static rt_processing_queue _processing_queue; static rt_processing_queue _processing_queue;
extern rt_result PipelineProcessor(rt_file_id file, rt_arena *arena); extern RT_ASSET_PROCESSOR_FN(PipelineProcessor);
static rt_asset_processor _processors[] = { static rt_asset_processor _processors[] = {
{.file_ext = ".pipeline", .proc = PipelineProcessor} {.file_ext = ".pipeline", .proc = PipelineProcessor}
@ -78,6 +82,12 @@ rt_result InitAssetCompiler(void) {
_asset_db.files = mem; _asset_db.files = mem;
_asset_db.data = (rt_asset_data *)(_asset_db.files + db_size); _asset_db.data = (rt_asset_data *)(_asset_db.files + db_size);
memset(mem, 0, (sizeof(rt_file_id) + sizeof(rt_asset_data)) * db_size); memset(mem, 0, (sizeof(rt_file_id) + sizeof(rt_asset_data)) * db_size);
rt_create_rwlock_result lock_create = rtCreateRWLock();
if (!lock_create.ok) {
free(mem);
return RT_UNKNOWN_ERROR;
}
_asset_db.lock = lock_create.lock;
_processing_queue.lock = rtCreateConditionVar(); _processing_queue.lock = rtCreateConditionVar();
if (!_processing_queue.lock) { if (!_processing_queue.lock) {
@ -94,7 +104,7 @@ rt_result InitAssetCompiler(void) {
rt_AssetProcessingThreads.i = MAX_PROCESSING_THREADS; rt_AssetProcessingThreads.i = MAX_PROCESSING_THREADS;
for (int i = 0; i < rt_AssetProcessingThreads.i; ++i) { for (int i = 0; i < rt_AssetProcessingThreads.i; ++i) {
char name[64]; char name[64];
snprintf(name, 64, "AssetProcessorThread %d", i); rtSPrint(name, 64, "AssetProcessorThread %d", i);
_processing_threads[i] = rtSpawnThread(ProcessorThreadEntry, NULL, name); _processing_threads[i] = rtSpawnThread(ProcessorThreadEntry, NULL, name);
if (!_processing_threads[i]) { if (!_processing_threads[i]) {
/* Wake the processing threads */ /* Wake the processing threads */
@ -120,12 +130,13 @@ void ShutdownAssetCompiler(void) {
rtJoinThread(_processing_threads[i]); rtJoinThread(_processing_threads[i]);
free(_asset_db.files); free(_asset_db.files);
rtDestroyConditionVar(_processing_queue.lock); rtDestroyConditionVar(_processing_queue.lock);
rtDestroyRWLock(&_asset_db.lock);
} }
static int DiscoverAssets(void) { static int DiscoverAssets(void) {
/* Recursive descend into the asset directory */ /* Recursive descend into the asset directory */
#define MAX_DISCOVERY_DEPTH 64 #define MAX_DISCOVERY_DEPTH 64
#define MAX_FILENAME_LEN 260 #define MAX_FILENAME_LEN 260
static char directory_stack[MAX_DISCOVERY_DEPTH][MAX_FILENAME_LEN]; static char directory_stack[MAX_DISCOVERY_DEPTH][MAX_FILENAME_LEN];
static unsigned int path_lens[MAX_DISCOVERY_DEPTH]; static unsigned int path_lens[MAX_DISCOVERY_DEPTH];
unsigned int top = 0; unsigned int top = 0;
@ -187,20 +198,29 @@ static int DiscoverAssets(void) {
rt_file_id fid = rtAddFile(file); rt_file_id fid = rtAddFile(file);
unsigned int i = 0; unsigned int i = 0;
rtLockWrite(&_asset_db.lock);
while (i < (unsigned int)rt_AssetDBSize.i) { while (i < (unsigned int)rt_AssetDBSize.i) {
unsigned int slot = (fid + i) % (unsigned int)rt_AssetDBSize.i; unsigned int slot = (fid + i) % (unsigned int)rt_AssetDBSize.i;
if (_asset_db.files[slot] == fid) { if (_asset_db.files[slot] == fid) {
break; break;
} else if (_asset_db.files[slot] == 0) { } else if (_asset_db.files[slot] == 0) {
_asset_db.files[slot] = fid; _asset_db.files[slot] = fid;
_asset_db.data[slot].last_processed = 0; _asset_db.data[slot].last_processed = 0;
memset(&_asset_db.data[slot].resources,
0,
sizeof(_asset_db.data[slot].resources));
_asset_db.data[slot].resource_count = 0;
_asset_db.data[slot].in_processing = false;
++discovery_count; ++discovery_count;
break; break;
} }
++i; ++i;
} }
rtUnlockWrite(&_asset_db.lock);
if (i == (unsigned int)rt_AssetDBSize.i) { if (i == (unsigned int)rt_AssetDBSize.i) {
rtLog("AC", "Failed to add %s to AssetDB, because no free slots are left.", file); rtLog("AC",
"Failed to add %s to AssetDB, because no free slots are left.",
file);
} }
} }
} while (!entry.is_last); } while (!entry.is_last);
@ -214,17 +234,38 @@ static int DiscoverAssets(void) {
static int CheckUpdatedAssets(void) { static int CheckUpdatedAssets(void) {
int updated_count = 0; int updated_count = 0;
for (int i = 0; i < rt_AssetDBSize.i; ++i) { for (int i = 0; i < rt_AssetDBSize.i; ++i) {
if (_asset_db.files[i] == 0) rtLockRead(&_asset_db.lock);
if (_asset_db.files[i] == 0) {
rtUnlockRead(&_asset_db.lock);
continue; continue;
const char *path = rtGetFilePath(_asset_db.files[i]); }
const char *path = rtGetFilePath(_asset_db.files[i]);
uint64_t last_changed = rtGetFileModificationTimestamp(path); uint64_t last_changed = rtGetFileModificationTimestamp(path);
if (_asset_db.data[i].last_processed < last_changed) { if (!_asset_db.data[i].in_processing && _asset_db.data[i].last_processed < last_changed) {
/* Check that we have not already added this file */
rtLockConditionVar(_processing_queue.lock);
bool already_in_queue = false;
for (size_t entry_idx = _processing_queue.head; entry_idx != _processing_queue.tail;
entry_idx = (entry_idx + 1) % RT_ARRAY_COUNT(_processing_queue.entries)) {
if (_processing_queue.entries[entry_idx].fid == _asset_db.files[i]) {
already_in_queue = true;
break;
}
}
rtUnlockConditionVar(_processing_queue.lock, false);
if (already_in_queue) {
rtUnlockRead(&_asset_db.lock);
continue;
}
const char *ext = path + strlen(path); const char *ext = path + strlen(path);
while (*ext != '.' && ext != path) while (*ext != '.' && ext != path)
--ext; --ext;
if (*ext != '.') if (*ext != '.')
break; break;
bool found_processor = false;
for (unsigned int j = 0; j < RT_ARRAY_COUNT(_processors); ++j) { for (unsigned int j = 0; j < RT_ARRAY_COUNT(_processors); ++j) {
if (strcmp(ext, _processors[j].file_ext) == 0) { if (strcmp(ext, _processors[j].file_ext) == 0) {
rt_processing_queue_entry entry; rt_processing_queue_entry entry;
@ -232,6 +273,8 @@ static int CheckUpdatedAssets(void) {
entry.processor_index = j; entry.processor_index = j;
entry.db_index = i; entry.db_index = i;
found_processor = true;
while (true) { while (true) {
bool inserted = false; bool inserted = false;
rtLockConditionVar(_processing_queue.lock); rtLockConditionVar(_processing_queue.lock);
@ -240,7 +283,7 @@ static int CheckUpdatedAssets(void) {
if (next_tail != _processing_queue.head) { if (next_tail != _processing_queue.head) {
_processing_queue.entries[_processing_queue.tail] = entry; _processing_queue.entries[_processing_queue.tail] = entry;
_processing_queue.tail = next_tail; _processing_queue.tail = next_tail;
inserted = true; inserted = true;
} }
rtUnlockConditionVar(_processing_queue.lock, inserted); rtUnlockConditionVar(_processing_queue.lock, inserted);
if (inserted) if (inserted)
@ -248,8 +291,10 @@ static int CheckUpdatedAssets(void) {
} }
} }
} }
++updated_count; if (found_processor)
++updated_count;
} }
rtUnlockRead(&_asset_db.lock);
} }
return updated_count; return updated_count;
} }
@ -285,7 +330,7 @@ static void ProcessorThreadEntry(void *param) {
while (_keep_running && (_processing_queue.tail == _processing_queue.head)) while (_keep_running && (_processing_queue.tail == _processing_queue.head))
rtWaitOnConditionVar(_processing_queue.lock); rtWaitOnConditionVar(_processing_queue.lock);
bool got_entry = false; bool got_entry = false;
rt_processing_queue_entry entry = {0}; rt_processing_queue_entry entry = {0};
if (_processing_queue.tail != _processing_queue.head) { if (_processing_queue.tail != _processing_queue.head) {
entry = _processing_queue.entries[_processing_queue.head]; entry = _processing_queue.entries[_processing_queue.head];
@ -299,24 +344,47 @@ static void ProcessorThreadEntry(void *param) {
if (!got_entry) if (!got_entry)
continue; continue;
rtLockWrite(&_asset_db.lock);
_asset_db.data[entry.db_index].in_processing = true;
rtUnlockWrite(&_asset_db.lock);
const char *path = rtGetFilePath(entry.fid); const char *path = rtGetFilePath(entry.fid);
rtLog("AC", "Processing %s", path); rtLog("AC", "Processing %s", path);
rtArenaClear(&arena); rtArenaClear(&arena);
rt_result res = _processors[entry.processor_index].proc(entry.fid, &arena); rt_resource_id existing_resources[RT_MAX_RESOURCES_PER_ASSET];
unsigned int existing_resource_count;
rtLockRead(&_asset_db.lock);
memcpy(existing_resources,
_asset_db.data[entry.db_index].resources,
sizeof(existing_resources));
existing_resource_count = _asset_db.data[entry.db_index].resource_count;
rtUnlockRead(&_asset_db.lock);
rt_resource_id new_resources[RT_MAX_RESOURCES_PER_ASSET];
memset(&new_resources, 0, sizeof(new_resources));
unsigned int new_resource_count = 0;
rt_result res = _processors[entry.processor_index].proc(entry.fid,
existing_resource_count,
existing_resources,
&new_resource_count,
new_resources,
&arena);
if (res != RT_SUCCESS) { if (res != RT_SUCCESS) {
rtLog("AC", "Failed to process %s: %u", path, res); rtLog("AC", "Failed to process %s: %u", path, res);
} }
rtLockWrite(&_asset_db.lock);
_asset_db.data[entry.db_index].last_processed = rtGetCurrentTimestamp(); _asset_db.data[entry.db_index].last_processed = rtGetCurrentTimestamp();
_asset_db.data[entry.db_index].in_processing = false;
memcpy(_asset_db.data[entry.db_index].resources, new_resources, sizeof(new_resources));
_asset_db.data[entry.db_index].resource_count = new_resource_count;
rtUnlockWrite(&_asset_db.lock);
} }
} }
/* Utilities for asset processors*/ /* Utilities for asset processors*/
#include "aio.h" #include "aio.h"
#include "asset_compiler.h"
rt_loaded_asset LoadAsset(rt_file_id file) rt_loaded_asset LoadAsset(rt_file_id file) {
{
const char *path = rtGetFilePath(file); const char *path = rtGetFilePath(file);
size_t file_size = rtGetFileSize(path); size_t file_size = rtGetFileSize(path);

View File

@ -6,6 +6,8 @@
#endif #endif
#include "file_tab.h" #include "file_tab.h"
#include "resources.h"
#include "mem_arena.h"
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
@ -15,6 +17,22 @@ enum {
RT_ASSET_PROCESSING_FAILED = RT_CUSTOM_ERROR_START, RT_ASSET_PROCESSING_FAILED = RT_CUSTOM_ERROR_START,
}; };
#define RT_MAX_RESOURCES_PER_ASSET 32
/* Asset processor prototype.
*
* The new resources will replace the associated resources completely.
*/
#define RT_ASSET_PROCESSOR_FN(_Name) \
rt_result _Name(rt_file_id file, \
unsigned int existing_resource_count, \
const rt_resource_id *existing_resources, \
unsigned int *new_resource_count, \
rt_resource_id *new_resources, \
rt_arena *arena)
/* A Asset processor function */
typedef RT_ASSET_PROCESSOR_FN(rt_asset_processor_fn);
/* Allocated from the buffer manager */ /* Allocated from the buffer manager */
typedef struct { typedef struct {
void *buffer; void *buffer;

View File

@ -1,6 +0,0 @@
#ifndef RT_ASSETS_H
#define RT_ASSETS_H
/* Asset system interface */
#endif

View File

@ -9,16 +9,6 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#if 0
typedef struct rt_buffer_region_s {
void *memory;
int16_t *refcounts; // One per block
uint32_t *bitmap;
size_t block_count;
rt_mutex *guard;
} rt_buffer_region;
#endif
/* Count leading zeroes. /* Count leading zeroes.
* Note that the return value of __builtin_clz(0) is undefined. */ * Note that the return value of __builtin_clz(0) is undefined. */
#ifdef _MSC_VER #ifdef _MSC_VER
@ -51,222 +41,8 @@ static __forceinline bool IsLZCNTSupported(void) {
#endif #endif
#if 0
/* NOTE(Kevin): Keep these sorted! */
static size_t _block_sizes[] = {RT_KB(512), RT_MB(1), RT_MB(4), RT_MB(8)};
#define NUM_BLOCK_SIZES (sizeof(_block_sizes) / sizeof(_block_sizes[0]))
static rt_buffer_region _regions[NUM_BLOCK_SIZES];
RT_CVAR_SZ(rt_BufferManagerMemory,
"Total number of bytes allocated for the buffer manager. Default: 1GB",
RT_GB(1));
rt_result InitBufferManager(void) {
if ((rt_BufferManagerMemory.sz % NUM_BLOCK_SIZES) != 0)
rtLog("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)
rtLog("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 = rtCreateMutex();
if (!_regions[i].guard) {
rtReportError("BUFFERMGR", "Failed to create guard mutex %u", i);
return RT_BUFFER_MGR_MUTEX_CREATION_FAILED;
}
_regions[i].memory = malloc(mem_per_size);
if (!_regions[i].memory) {
rtDestroyMutex(_regions[i].guard);
rtReportError("BUFFERMGR", "Failed to allocate memory.", i);
return RT_BUFFER_MGR_OUT_OF_MEMORY;
}
_regions[i].bitmap = calloc((block_count + 31) / 32, sizeof(uint32_t));
if (!_regions[i].bitmap) {
rtDestroyMutex(_regions[i].guard);
free(_regions[i].memory);
rtReportError("BUFFERMGR", "Failed to allocate memory.", i);
return RT_BUFFER_MGR_OUT_OF_MEMORY;
}
_regions[i].refcounts = calloc(block_count, sizeof(uint16_t));
if (!_regions[i].refcounts) {
rtDestroyMutex(_regions[i].guard);
free(_regions[i].memory);
free(_regions[i].bitmap);
rtReportError("BUFFERMGR", "Failed to allocate memory.", i);
return RT_BUFFER_MGR_OUT_OF_MEMORY;
}
}
return RT_SUCCESS;
}
void ShutdownBufferManager(void) {
for (unsigned int i = 0; i < NUM_BLOCK_SIZES; ++i) {
rtDestroyMutex(_regions[i].guard);
free(_regions[i].memory);
free(_regions[i].bitmap);
free(_regions[i].refcounts);
}
}
RT_DLLEXPORT void *rtAllocBuffer(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;
rt_buffer_region *region = &_regions[best_fit];
rtLockMutex(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;
}
}
} else {
for (size_t i = 0; i < dword_count; ++i) {
if (region->bitmap[i] == UINT32_MAX) {
continue;
}
/* Check if we can start the allocation here */
}
}
rtUnlockMutex(region->guard);
return result;
}
RT_DLLEXPORT void rtReleaseBuffer(const void *begin, size_t size) {
if (!begin)
return;
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];
rtLockMutex(_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);
}
rtUnlockMutex(_regions[i].guard);
return;
}
}
rtLog("BUFFERMGR", "Tried to release an invalid buffer");
}
RT_DLLEXPORT void rtIncreaseBufferRefCount(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];
rtLockMutex(_regions[i].guard);
for (size_t j = 0; j < block_count; ++j) {
++_regions[i].refcounts[first_block + j];
}
rtUnlockMutex(_regions[i].guard);
return;
}
}
rtLog("BUFFERMGR", "Tried to increase the refcount of an invalid buffer");
}
#endif
#define BLOCK_SIZE 4096u #define BLOCK_SIZE 4096u
static uint32_t *_refcounts;
static uint32_t *_bitmap; static uint32_t *_bitmap;
static char *_memory; static char *_memory;
static rt_mutex *_guard; static rt_mutex *_guard;
@ -298,14 +74,12 @@ extern rt_result InitBufferManager(void) {
size_t dword_count = (block_count + 31) / 32; size_t dword_count = (block_count + 31) / 32;
_block_count = block_count; _block_count = block_count;
_memory = malloc(budget + dword_count * sizeof(uint32_t) + block_count * sizeof(uint32_t)); _memory = malloc(budget + dword_count * sizeof(uint32_t));
if (!_memory) { if (!_memory) {
return RT_OUT_OF_MEMORY; return RT_OUT_OF_MEMORY;
} }
_bitmap = (uint32_t*)(_memory + budget); _bitmap = (uint32_t*)(_memory + budget);
memset(_bitmap, 0, sizeof(uint32_t) * dword_count); memset(_bitmap, 0, sizeof(uint32_t) * dword_count);
_refcounts = _bitmap + dword_count;
memset(_refcounts, 0, sizeof(uint32_t) * block_count);
return RT_SUCCESS; return RT_SUCCESS;
} }
@ -381,9 +155,6 @@ RT_DLLEXPORT void *rtAllocBuffer(size_t size) {
} }
} }
for (size_t i = first_block; i < first_block + alloc_blocks; ++i)
_refcounts[i] = 1;
rtUnlockMutex(_guard); rtUnlockMutex(_guard);
rtLog("BUFFERMGR", "Result ptr %llx", (uintptr_t)result); rtLog("BUFFERMGR", "Result ptr %llx", (uintptr_t)result);
return result; return result;
@ -395,22 +166,9 @@ RT_DLLEXPORT void rtReleaseBuffer(const void *begin, size_t size) {
uintptr_t first_block = off / BLOCK_SIZE; uintptr_t first_block = off / BLOCK_SIZE;
rtLockMutex(_guard); rtLockMutex(_guard);
for (size_t i = first_block; i < first_block + alloc_blocks; ++i) { for (size_t i = first_block; i < first_block + alloc_blocks; ++i) {
if (--_refcounts[i] == 0) { size_t dword = i / 32;
size_t dword = i / 32; size_t bit = i % 32;
size_t bit = i % 32; _bitmap[dword] &= ~(1u << bit);
_bitmap[dword] &= ~(1u << bit);
}
}
rtUnlockMutex(_guard);
}
RT_DLLEXPORT void rtIncreaseBufferRefCount(const void *begin, size_t size) {
size_t alloc_blocks = (size + BLOCK_SIZE - 1) / BLOCK_SIZE;
uintptr_t off = (uintptr_t)begin - (uintptr_t)_memory;
uintptr_t first_block = off / BLOCK_SIZE;
rtLockMutex(_guard);
for (size_t i = first_block; i < first_block + alloc_blocks; ++i) {
++_refcounts[i];
} }
rtUnlockMutex(_guard); rtUnlockMutex(_guard);
} }

View File

@ -16,8 +16,6 @@ RT_DLLEXPORT void *rtAllocBuffer(size_t size);
RT_DLLEXPORT void rtReleaseBuffer(const void *begin, size_t size); RT_DLLEXPORT void rtReleaseBuffer(const void *begin, size_t size);
RT_DLLEXPORT void rtIncreaseBufferRefCount(const void *begin, size_t size);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

59
src/runtime/ds.h Normal file
View File

@ -0,0 +1,59 @@
#ifndef RT_DS_H
#define RT_DS_H
/* Datastructure Library */
#include "runtime.h"
#ifdef __cplusplus
extern "C" {
#endif
/* A minheap.
*
* The memory pointed to by keys and values is not owned by the minheap
* and instead provided by the caller. */
typedef struct {
int *keys;
void *values;
size_t value_size;
size_t capacity;
int size;
} rt_minheap;
/* Comparison function for rtMinheapUpdate.
* The function should return 0 if *a and *b are considered equal,
* and a different (non-zero) value if they are non-equal.
*
* Note that memcmp fits this requirement.
*/
typedef int rt_minheap_cmp_fn(const void *a, const void *b, size_t n);
/* Takes the arrays and re-orders the values to create a minheap. */
RT_DLLEXPORT rt_minheap
rtCreateMinheap(int *keys, void *values, size_t value_size, size_t capacity, int initial_size);
/* Copies the value with the smallest key to min_value */
RT_DLLEXPORT void rtMinheapPeek(const rt_minheap *minheap, void *min_value);
/* Copies the value with the smallest key to min_value and removes it from the heap */
RT_DLLEXPORT void rtMinheapPop(rt_minheap *minheap, void *min_value);
/* Pushes a new value into the minheap */
RT_DLLEXPORT void rtMinheapPush(rt_minheap *minheap, int key, const void *value);
/* Changes the key of an existing value, or inserts it, if it's not found.
*
* Uses memcmp if cmp is NULL. */
RT_DLLEXPORT void
rtMinheapUpdate(rt_minheap *minheap, const void *value, int new_key, rt_minheap_cmp_fn *cmp);
static RT_INLINE int rtMinheapIsEmpty(rt_minheap *minheap) {
return minheap->size == 0;
}
#ifdef __cplusplus
}
#endif
#endif

138
src/runtime/ds_minheap.c Normal file
View File

@ -0,0 +1,138 @@
#include "ds.h"
#include <assert.h>
extern int memcmp(const void *ptr1, const void *ptr2, size_t num);
extern void *memcpy(void *destination, const void *source, size_t num);
/* Utilities for index calculation */
static RT_INLINE int Parent(int i) {
return (i - 1) / 2;
}
static RT_INLINE int Left(int i) {
return 2 * i + 1;
}
static RT_INLINE int Right(int i) {
return 2 * i + 2;
}
static void Swap(rt_minheap *minheap, int i, int j, void *tmpv) {
void *vi = (char *)minheap->values + (size_t)i * minheap->value_size;
void *vj = (char *)minheap->values + (size_t)j * minheap->value_size;
int tk = minheap->keys[i];
memcpy(tmpv, vi, minheap->value_size);
minheap->keys[i] = minheap->keys[j];
memcpy(vi, vj, minheap->value_size);
minheap->keys[j] = tk;
memcpy(vj, tmpv, minheap->value_size);
}
static void Heapify(rt_minheap *minheap, int start) {
int i = start;
int size = (int)minheap->capacity;
/* FIXME: Allocate on temp-arena */
char tmpv[256];
assert(minheap->value_size < sizeof(tmpv) && "tmpv should be allocated on a temporary arena.");
while (1) {
int min = i;
if (Left(i) < size && minheap->keys[Left(i)] < minheap->keys[min])
min = Left(i);
if (Right(i) < size && minheap->keys[Right(i)] < minheap->keys[min])
min = Right(i);
if (min == i)
break;
Swap(minheap, i, min, tmpv);
}
}
static void Decrease(rt_minheap *minheap, int i, int newkey) {
minheap->keys[i] = newkey;
/* FIXME: Allocate on temp-arena */
char tmpv[256];
assert(minheap->value_size < sizeof(tmpv) && "tmpv should be allocated on a temporary arena.");
while (i > 0 && minheap->keys[i] < minheap->keys[Parent(i)]) {
Swap(minheap, i, Parent(i), tmpv);
i = Parent(i);
}
}
static void Remove(rt_minheap *minheap, int i) {
int last = minheap->size - 1;
if (last < 0)
return;
/* FIXME: Allocate on temp-arena */
char tmpv[256];
assert(minheap->value_size < sizeof(tmpv) && "tmpv should be allocated on a temporary arena.");
Swap(minheap, i, last, tmpv);
minheap->size = last;
if (i != last) {
if (i == 0 || minheap->keys[i] > minheap->keys[Parent(i)])
Heapify(minheap, i);
else
Decrease(minheap, i, minheap->keys[i]);
}
}
/* Takes the arrays and re-orders the values to create a minheap. */
RT_DLLEXPORT rt_minheap
rtCreateMinheap(int *keys, void *values, size_t value_size, size_t capacity, int initial_size) {
rt_minheap minheap = {
.keys = keys,
.values = values,
.value_size = value_size,
.capacity = capacity,
.size = initial_size,
};
/* Start at the last non-leaf element */
for (int i = initial_size / 2 - 1; i >= 0; --i)
Heapify(&minheap, i);
return minheap;
}
/* Copies the value with the smallest key to min_value */
RT_DLLEXPORT void rtMinheapPeek(const rt_minheap *minheap, void *min_value) {
memcpy(min_value, minheap->values, minheap->value_size);
}
/* Copies the value with the smallest key to min_value and removes it from the heap */
RT_DLLEXPORT void rtMinheapPop(rt_minheap *minheap, void *min_value) {
rtMinheapPeek(minheap, min_value);
Remove(minheap, 0);
}
/* Pushes a new value into the minheap */
RT_DLLEXPORT void rtMinheapPush(rt_minheap *minheap, int key, const void *value) {
int at = minheap->size;
void *v = (char *)minheap->values + (size_t)at * minheap->value_size;
minheap->keys[at] = key;
memcpy(v, value, minheap->value_size);
++minheap->size;
Decrease(minheap, at, key);
}
/* Changes the key of an existing value, or inserts it, if it's not found.
*
* Uses memcmp if cmp is NULL. */
RT_DLLEXPORT void
rtMinheapUpdate(rt_minheap *minheap, const void *value, int new_key, rt_minheap_cmp_fn *cmp) {
if (!cmp)
cmp = memcmp;
for (int i = 0; i < minheap->size; ++i) {
void *v = (char *)minheap->values + (size_t)i * minheap->value_size;
if (cmp(v, value, minheap->value_size) == 0) {
if (minheap->keys[i] > new_key) {
Decrease(minheap, i, new_key);
} else if (minheap->keys[i] < new_key) {
Remove(minheap, i);
rtMinheapPush(minheap, new_key, value);
}
return;
}
}
rtMinheapPush(minheap, new_key, value);
}

View File

@ -22,7 +22,7 @@ extern "C" rt_shader_bytecode CompileVulkanShader(rt_shader_stage stage,
// Check if this is what we want. // Check if this is what we want.
// For example: 6_2 is what allows the usage of 16 bit types // For example: 6_2 is what allows the usage of 16 bit types
LPCWSTR target_profile = nullptr; LPCWSTR target_profile = nullptr;
LPWSTR entry = nullptr; LPCWSTR entry = nullptr;
switch (stage) { switch (stage) {
case RT_SHADER_STAGE_VERTEX: case RT_SHADER_STAGE_VERTEX:
target_profile = L"vs_6_1"; target_profile = L"vs_6_1";
@ -41,7 +41,7 @@ extern "C" rt_shader_bytecode CompileVulkanShader(rt_shader_stage stage,
return bc; return bc;
} }
LPWSTR optimization_arg = nullptr; LPCWSTR optimization_arg = nullptr;
switch (optimization) { switch (optimization) {
case RT_SHADER_OPTIMIZATION_NONE: case RT_SHADER_OPTIMIZATION_NONE:
optimization_arg = L"-Od"; optimization_arg = L"-Od";
@ -81,7 +81,7 @@ extern "C" rt_shader_bytecode CompileVulkanShader(rt_shader_stage stage,
utils->Release(); utils->Release();
compiler->Release(); compiler->Release();
library->Release(); library->Release();
rtReportError("AC", "Failed to init the DXC ínclude handler."); rtReportError("AC", "Failed to init the DXC include handler.");
return bc; return bc;
} }
@ -118,8 +118,9 @@ extern "C" rt_shader_bytecode CompileVulkanShader(rt_shader_stage stage,
// Error occured // Error occured
IDxcBlobEncoding *error_blob; IDxcBlobEncoding *error_blob;
hr = result->GetErrorBuffer(&error_blob); hr = result->GetErrorBuffer(&error_blob);
if (SUCCEEDED(hr) && error_blob) { if (SUCCEEDED(hr) && error_blob && error_blob->GetBufferSize() > 0) {
rtLog("AC", "Shader %s compilation failed: %s", (const char *)error_blob->GetBufferPointer()); const char *msg = (const char *)error_blob->GetBufferPointer();
rtLog("AC", "Shader %s compilation failed: %s", file_path, msg);
error_blob->Release(); error_blob->Release();
} else { } else {
rtLog("AC", "Shader %s compilation failed. No error information available!"); rtLog("AC", "Shader %s compilation failed. No error information available!");

View File

@ -57,12 +57,12 @@ static void LogOut(const char *text) {
RT_DLLEXPORT void rtReportError(const char *subsystem, const char *fmt, ...) { RT_DLLEXPORT void rtReportError(const char *subsystem, const char *fmt, ...) {
char buf[256]; char buf[256];
int at = snprintf(buf, RT_ARRAY_COUNT(buf) - 1, "[%s] ", subsystem); int at = rtSPrint(buf, RT_ARRAY_COUNT(buf) - 1, "[%s] ", subsystem);
va_list ap; va_list ap;
va_start(ap, fmt); va_start(ap, fmt);
at += vsnprintf(&buf[at], RT_ARRAY_COUNT(buf) - at - 1, fmt, ap); at += rtVSPrint(&buf[at], RT_ARRAY_COUNT(buf) - at - 1, fmt, ap);
va_end(ap); va_end(ap);
at += snprintf(&buf[at], RT_ARRAY_COUNT(buf) - at - 1, "\n"); at += rtSPrint(&buf[at], RT_ARRAY_COUNT(buf) - at - 1, "\n");
LogOut(buf); LogOut(buf);
if (DisplayErrorBox(buf)) { if (DisplayErrorBox(buf)) {
@ -73,12 +73,12 @@ RT_DLLEXPORT void rtReportError(const char *subsystem, const char *fmt, ...) {
RT_DLLEXPORT void rtLog(const char *subsystem, const char *fmt, ...) { RT_DLLEXPORT void rtLog(const char *subsystem, const char *fmt, ...) {
char buf[256]; char buf[256];
int at = snprintf(buf, RT_ARRAY_COUNT(buf) - 1, "[%s] ", subsystem); int at = rtSPrint(buf, RT_ARRAY_COUNT(buf), "[%s] ", subsystem);
va_list ap; va_list ap;
va_start(ap, fmt); va_start(ap, fmt);
at += vsnprintf(&buf[at], RT_ARRAY_COUNT(buf) - at - 1, fmt, ap); at += rtVSPrint(&buf[at], RT_ARRAY_COUNT(buf) - at, fmt, ap);
va_end(ap); va_end(ap);
at += snprintf(&buf[at], RT_ARRAY_COUNT(buf) - at - 1, "\n"); at += rtSPrint(&buf[at], RT_ARRAY_COUNT(buf) - at, "\n");
LogOut(buf); LogOut(buf);
} }

View File

@ -84,6 +84,12 @@ RT_DLLEXPORT void rtCloseDirectory(rt_scandir_handle *dir) {
dir->handle = NULL; dir->handle = NULL;
} }
RT_DLLEXPORT bool rtCreateDirectory(const char *path) {
WCHAR wpath[MAX_PATH];
MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, path, -1, wpath, MAX_PATH);
return CreateDirectoryW(wpath, NULL);
}
RT_DLLEXPORT size_t rtGetFileSize(const char *path) { RT_DLLEXPORT size_t rtGetFileSize(const char *path) {
WCHAR wpath[MAX_PATH]; WCHAR wpath[MAX_PATH];
MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, path, -1, wpath, MAX_PATH); MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, path, -1, wpath, MAX_PATH);

View File

@ -29,6 +29,8 @@ RT_DLLEXPORT rt_dirent rtNextDirectoryEntry(rt_scandir_handle *dir);
RT_DLLEXPORT void rtCloseDirectory(rt_scandir_handle *dir); RT_DLLEXPORT void rtCloseDirectory(rt_scandir_handle *dir);
RT_DLLEXPORT bool rtCreateDirectory(const char *path);
RT_DLLEXPORT size_t rtGetFileSize(const char *path); RT_DLLEXPORT size_t rtGetFileSize(const char *path);
RT_DLLEXPORT uint64_t rtGetFileModificationTimestamp(const char *path); RT_DLLEXPORT uint64_t rtGetFileModificationTimestamp(const char *path);

View File

@ -11,6 +11,9 @@ extern rt_cvar rt_WindowHeight;
extern rt_cvar rt_BufferMemoryBudget; extern rt_cvar rt_BufferMemoryBudget;
extern rt_cvar rt_FileTabCapacity; extern rt_cvar rt_FileTabCapacity;
extern rt_cvar rt_MaxConcurrentAsyncIO; extern rt_cvar rt_MaxConcurrentAsyncIO;
extern rt_cvar rt_ResourceDirectory;
extern rt_cvar rt_ResourceCacheSize;
extern rt_cvar rt_ResourceNamespaceSize;
#ifdef RT_BUILD_ASSET_COMPILER #ifdef RT_BUILD_ASSET_COMPILER
extern rt_cvar rt_AssetDirectory; extern rt_cvar rt_AssetDirectory;
@ -24,6 +27,9 @@ void RegisterRuntimeCVars(void) {
rtRegisterCVAR(&rt_BufferMemoryBudget); rtRegisterCVAR(&rt_BufferMemoryBudget);
rtRegisterCVAR(&rt_FileTabCapacity); rtRegisterCVAR(&rt_FileTabCapacity);
rtRegisterCVAR(&rt_MaxConcurrentAsyncIO); rtRegisterCVAR(&rt_MaxConcurrentAsyncIO);
rtRegisterCVAR(&rt_ResourceDirectory);
rtRegisterCVAR(&rt_ResourceCacheSize);
rtRegisterCVAR(&rt_ResourceNamespaceSize);
#ifdef RT_BUILD_ASSET_COMPILER #ifdef RT_BUILD_ASSET_COMPILER
rtRegisterCVAR(&rt_AssetDirectory); rtRegisterCVAR(&rt_AssetDirectory);
#endif #endif
@ -37,6 +43,8 @@ extern rt_result InitFileTab(void);
extern void ShutdownFileTab(void); extern void ShutdownFileTab(void);
extern rt_result InitAIO(void); extern rt_result InitAIO(void);
extern void ShutdownAIO(void); extern void ShutdownAIO(void);
extern rt_result InitResourceManager(void);
extern void ShutdownResourceManager(void);
#ifdef RT_BUILD_ASSET_COMPILER #ifdef RT_BUILD_ASSET_COMPILER
extern rt_result InitAssetCompiler(void); extern rt_result InitAssetCompiler(void);
@ -63,6 +71,11 @@ RT_DLLEXPORT rt_result rtInitRuntime(void) {
return res; return res;
} }
if ((res = InitResourceManager()) != RT_SUCCESS) {
rtReportError("RESMGR", "Init failed.");
return res;
}
#ifdef RT_BUILD_ASSET_COMPILER #ifdef RT_BUILD_ASSET_COMPILER
if ((res = InitAssetCompiler()) != RT_SUCCESS) { if ((res = InitAssetCompiler()) != RT_SUCCESS) {
rtReportError("AC", "Init failed."); rtReportError("AC", "Init failed.");
@ -77,6 +90,7 @@ RT_DLLEXPORT void rtShutdownRuntime(void) {
#ifdef RT_BUILD_ASSET_COMPILER #ifdef RT_BUILD_ASSET_COMPILER
ShutdownAssetCompiler(); ShutdownAssetCompiler();
#endif #endif
ShutdownResourceManager();
ShutdownAIO(); ShutdownAIO();
ShutdownFileTab(); ShutdownFileTab();
ShutdownBufferManager(); ShutdownBufferManager();

View File

@ -1,14 +1,14 @@
#include "runtime.h"
#include "mem_arena.h"
#include "description_parser.h"
#include "buffer_manager.h"
#include "asset_compiler.h" #include "asset_compiler.h"
#include "shader_compiler.h" #include "buffer_manager.h"
#include "gfx.h"
#include "config.h" #include "config.h"
#include "description_parser.h"
#include "gfx.h"
#include "mem_arena.h"
#include "runtime.h"
#include "shader_compiler.h"
#include <limits.h>
#include <assert.h> #include <assert.h>
#include <limits.h>
#include <string.h> #include <string.h>
typedef struct { typedef struct {
@ -16,9 +16,13 @@ typedef struct {
rt_attribute_binding *storage_bindings; rt_attribute_binding *storage_bindings;
rt_attribute_binding *texture_bindings; rt_attribute_binding *texture_bindings;
rt_shader_bytecode vertex_shader; rt_resource shaders[3];
rt_shader_bytecode fragment_shader; char *shader_names[3];
rt_shader_bytecode compute_shader; unsigned int shader_count;
unsigned int vertex_shader;
unsigned int fragment_shader;
unsigned int compute_shader;
/* TODO Fixed function settings */ /* TODO Fixed function settings */
@ -144,6 +148,58 @@ static bool ParseBindings(rt_parse_state *state,
} }
} }
static char *GenerateShaderName(rt_shader_type type,
rt_shader_stage stage,
rt_shader_optimization_level optimization,
const char *file_name,
rt_arena *arena) {
size_t name_len = strlen(file_name) + 5 /* type */
+ 5 /* stage */
+ 3 /* optimization */
+ 1; /* '\0' */
char *res_name = rtArenaPush(arena, name_len);
if (!res_name)
return NULL;
const char *type_str = NULL;
switch (type) {
case RT_SHADER_TYPE_VULKAN:
type_str = ":vk";
break;
default:
return NULL;
}
const char *stage_str = NULL;
switch (stage) {
case RT_SHADER_STAGE_VERTEX:
stage_str = ":vert";
break;
case RT_SHADER_STAGE_FRAGMENT:
stage_str = ":frag";
break;
case RT_SHADER_STAGE_COMPUTE:
stage_str = ":comp";
break;
default:
return NULL;
}
const char *optim_str = NULL;
switch (optimization) {
case RT_SHADER_OPTIMIZATION_NONE:
optim_str = ":O0";
break;
case RT_SHADER_OPTIMIZATION_SIZE:
optim_str = ":Os";
break;
case RT_SHADER_OPTIMIZATION_SPEED:
optim_str = ":Ox";
break;
default:
return NULL;
}
rtSPrint(res_name, name_len, "%s%s%s%s", file_name, type_str, stage_str, optim_str);
return res_name;
}
static rt_result ParseShader(rt_parse_state *state, static rt_result ParseShader(rt_parse_state *state,
unsigned int root_list, unsigned int root_list,
const char *name, const char *name,
@ -151,7 +207,8 @@ static rt_result ParseShader(rt_parse_state *state,
rt_shader_type type, rt_shader_type type,
rt_shader_stage stage, rt_shader_stage stage,
rt_shader_optimization_level optimization, rt_shader_optimization_level optimization,
rt_shader_bytecode *p_shader_bytecode, rt_resource *p_resource,
char **p_resource_name,
rt_arena *arena) { rt_arena *arena) {
const rt_parsed_stmt *stmt = rtFindStatement(state, root_list, name); const rt_parsed_stmt *stmt = rtFindStatement(state, root_list, name);
if (stmt) { if (stmt) {
@ -195,10 +252,32 @@ static rt_result ParseShader(rt_parse_state *state,
if (in_file_type == type) { if (in_file_type == type) {
if (shader->form == RT_STMT_FORM_BLOCK) { if (shader->form == RT_STMT_FORM_BLOCK) {
/* Inline code */ /* Inline code */
*p_shader_bytecode = rt_shader_bytecode bytecode =
CompileShader(type, stage, optimization, shader->block, file_path, arena); CompileShader(type, stage, optimization, shader->block, file_path, arena);
if (!p_shader_bytecode->bytes) if (!bytecode.bytes)
return RT_ASSET_PROCESSING_FAILED; return RT_ASSET_PROCESSING_FAILED;
*p_resource_name =
GenerateShaderName(type, stage, optimization, file_path, arena);
if (!*p_resource_name)
return RT_ASSET_PROCESSING_FAILED;
rt_resource resource;
resource.type = RT_RESOURCE_SHADER;
resource.dependency_count = 0;
resource.subresource_count = 0;
resource.data = rtArenaPush(arena, sizeof(rt_shader_info) + bytecode.len);
if (!resource.data)
return RT_ASSET_PROCESSING_FAILED;
rt_shader_info *shader_info = resource.data;
uint8_t *shader_bytecode = (uint8_t *)(shader_info + 1);
shader_info->stage = stage;
shader_info->type = type;
shader_info->bytecode_length = bytecode.len;
rtSetRelptr(&shader_info->bytecode, shader_bytecode);
memcpy(shader_bytecode, bytecode.bytes, bytecode.len);
memcpy(p_resource, &resource, sizeof(resource));
break; break;
} else if (shader->form != RT_STMT_FORM_VALUE) { } else if (shader->form != RT_STMT_FORM_VALUE) {
/* A filename */ /* A filename */
@ -262,7 +341,8 @@ static rt_result ParsePipelineFile(rt_file_id fid,
} }
/* We allow the pipeline file to overwrite the optimization level */ /* We allow the pipeline file to overwrite the optimization level */
rt_shader_optimization_level optimization = ParseOptimizationLevel(&state, root_list, file_path); rt_shader_optimization_level optimization =
ParseOptimizationLevel(&state, root_list, file_path);
rt_shader_type type = RT_SHADER_TYPE_INVALID; rt_shader_type type = RT_SHADER_TYPE_INVALID;
if (strcmp(rt_Renderer.s, "vk") == 0) if (strcmp(rt_Renderer.s, "vk") == 0)
@ -275,42 +355,65 @@ static rt_result ParsePipelineFile(rt_file_id fid,
} }
/* Process shader stages */ /* Process shader stages */
if (ParseShader(&state, result = ParseShader(&state,
root_list, root_list,
"vertex", "vertex",
file_path, file_path,
type, type,
RT_SHADER_STAGE_VERTEX, RT_SHADER_STAGE_VERTEX,
optimization, optimization,
&pipeline->vertex_shader, &pipeline->shaders[pipeline->shader_count],
arena) == RT_ASSET_PROCESSING_FAILED) { &pipeline->shader_names[pipeline->shader_count],
result = RT_ASSET_PROCESSING_FAILED; arena);
if (result == RT_SUCCESS) {
pipeline->vertex_shader = pipeline->shader_count;
++pipeline->shader_count;
} else if (result == RT_SHADER_NOT_PRESENT) {
pipeline->vertex_shader = UINT_MAX;
} else {
goto out; goto out;
} }
if (ParseShader(&state, result = RT_SUCCESS;
root_list,
"fragment", result = ParseShader(&state,
file_path, root_list,
type, "fragment",
RT_SHADER_STAGE_FRAGMENT, file_path,
optimization, type,
&pipeline->fragment_shader, RT_SHADER_STAGE_FRAGMENT,
arena) == RT_ASSET_PROCESSING_FAILED) { optimization,
result = RT_ASSET_PROCESSING_FAILED; &pipeline->shaders[pipeline->shader_count],
&pipeline->shader_names[pipeline->shader_count],
arena);
if (result == RT_SUCCESS) {
pipeline->fragment_shader = pipeline->shader_count;
++pipeline->shader_count;
} else if (result == RT_SHADER_NOT_PRESENT) {
pipeline->fragment_shader = UINT_MAX;
} else {
goto out; goto out;
} }
if (ParseShader(&state, result = RT_SUCCESS;
root_list,
"compute", result = ParseShader(&state,
file_path, root_list,
type, "compute",
RT_SHADER_STAGE_COMPUTE, file_path,
optimization, type,
&pipeline->compute_shader, RT_SHADER_STAGE_COMPUTE,
arena) == RT_ASSET_PROCESSING_FAILED) { optimization,
result = RT_ASSET_PROCESSING_FAILED; &pipeline->shaders[pipeline->shader_count],
&pipeline->shader_names[pipeline->shader_count],
arena);
if (result == RT_SUCCESS) {
pipeline->compute_shader = pipeline->shader_count;
++pipeline->shader_count;
} else if (result == RT_SHADER_NOT_PRESENT) {
pipeline->compute_shader = UINT_MAX;
} else {
goto out; goto out;
} }
result = RT_SUCCESS;
/* Process bindings */ /* Process bindings */
pipeline->texture_bindings = NULL; pipeline->texture_bindings = NULL;
@ -354,18 +457,74 @@ out:
return result; return result;
} }
rt_result PipelineProcessor(rt_file_id file, rt_arena *arena) { RT_ASSET_PROCESSOR_FN(PipelineProcessor) {
rt_loaded_asset asset = LoadAsset(file); rt_loaded_asset asset = LoadAsset(file);
if (!asset.buffer) if (!asset.buffer)
return RT_UNKNOWN_ERROR; return RT_UNKNOWN_ERROR;
rt_parsed_pipeline_data pipeline; rt_parsed_pipeline_data pipeline;
memset(&pipeline, 0, sizeof(pipeline));
rt_result result = ParsePipelineFile(file, asset.buffer, asset.size, &pipeline, arena); rt_result result = ParsePipelineFile(file, asset.buffer, asset.size, &pipeline, arena);
if (result != RT_SUCCESS) if (result != RT_SUCCESS)
goto out; goto out;
rt_resource_id shader_resources[3] = {0};
result = rtCreateResources(pipeline.shader_count,
pipeline.shader_names,
pipeline.shaders,
shader_resources);
if (result != RT_SUCCESS)
goto out;
rt_resource pipeline_resource = {0};
pipeline_resource.type = RT_RESOURCE_PIPELINE;
pipeline_resource.dependency_count = pipeline.shader_count;
memcpy(pipeline_resource.dependencies, shader_resources, sizeof(shader_resources));
pipeline_resource.subresource_count = 0;
size_t data_size =
sizeof(rt_pipeline_info) + sizeof(rt_attribute_binding) * (pipeline.texture_binding_count +
pipeline.uniform_binding_count +
pipeline.storage_binding_count);
pipeline_resource.data = rtArenaPush(arena, data_size);
if (!pipeline_resource.data) {
result = RT_OUT_OF_MEMORY;
goto out;
}
rt_pipeline_info *info = pipeline_resource.data;
memset(info, 0, sizeof(*info));
info->vertex_shader = (pipeline.vertex_shader != UINT_MAX)
? shader_resources[pipeline.vertex_shader]
: RT_INVALID_RESOURCE_ID;
info->fragment_shader = (pipeline.fragment_shader != UINT_MAX)
? shader_resources[pipeline.fragment_shader]
: RT_INVALID_RESOURCE_ID;
info->compute_shader = (pipeline.compute_shader != UINT_MAX)
? shader_resources[pipeline.compute_shader]
: RT_INVALID_RESOURCE_ID;
rt_attribute_binding *uniform_bindings = (rt_attribute_binding *)(info + 1);
if (pipeline.uniform_binding_count > 0) {
memcpy(uniform_bindings, pipeline.uniform_bindings, pipeline.uniform_binding_count);
rtSetRelptr(&info->uniform_bindings, uniform_bindings);
}
rt_attribute_binding *texture_bindings = (uniform_bindings + pipeline.uniform_binding_count);
if (pipeline.texture_binding_count > 0) {
memcpy(texture_bindings, pipeline.texture_bindings, pipeline.texture_binding_count);
rtSetRelptr(&info->texture_bindings, texture_bindings);
}
rt_attribute_binding *storage_bindings = (texture_bindings + pipeline.texture_binding_count);
if (pipeline.texture_binding_count > 0) {
memcpy(storage_bindings, pipeline.storage_bindings, pipeline.storage_binding_count);
rtSetRelptr(&info->storage_bindings, storage_bindings);
}
rt_resource_id pipeline_id;
const char *name = rtGetFilePath(file);
result = rtCreateResources(1, &name, &pipeline_resource, &pipeline_id);
if (result == RT_SUCCESS) {
new_resources[0] = pipeline_id;
memcpy(&new_resources[1], shader_resources, sizeof(shader_resources));
*new_resource_count = 1 + pipeline.shader_count;
}
out: out:
rtReleaseBuffer(asset.buffer, asset.size); rtReleaseBuffer(asset.buffer, asset.size);
return result; return result;

View File

@ -7,7 +7,7 @@
#include "gfx.h" #include "gfx.h"
#include "runtime.h" #include "runtime.h"
#include "assets.h" #include "resources.h"
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
@ -31,10 +31,9 @@ struct rt_renderer_init_info_s {
}; };
typedef struct { typedef struct {
/* rt_uid vertex_shader; rt_resource_id vertex_shader;
rt_uid fragment_shader; rt_resource_id fragment_shader;
rt_uid compute_shader; rt_resource_id compute_shader;
*/
rt_relptr texture_bindings; rt_relptr texture_bindings;
rt_relptr uniform_bindings; rt_relptr uniform_bindings;
@ -45,6 +44,26 @@ typedef struct {
uint16_t storage_binding_count; uint16_t storage_binding_count;
} rt_pipeline_info; } rt_pipeline_info;
typedef enum {
RT_SHADER_TYPE_INVALID,
RT_SHADER_TYPE_VULKAN,
RT_SHADER_TYPE_COUNT,
} rt_shader_type;
typedef enum {
RT_SHADER_STAGE_VERTEX,
RT_SHADER_STAGE_FRAGMENT,
RT_SHADER_STAGE_COMPUTE,
} rt_shader_stage;
typedef struct {
rt_shader_type type;
rt_shader_stage stage;
rt_relptr bytecode;
size_t bytecode_length;
} rt_shader_info;
typedef void rt_register_renderer_cvars_fn(void); typedef void rt_register_renderer_cvars_fn(void);
typedef rt_result rt_init_renderer_fn(const rt_renderer_init_info *info); typedef rt_result rt_init_renderer_fn(const rt_renderer_init_info *info);
typedef void rt_shutdown_renderer_fn(void); typedef void rt_shutdown_renderer_fn(void);

View File

@ -0,0 +1,444 @@
#include "aio.h"
#include "buffer_manager.h"
#include "config.h"
#include "ds.h"
#include "file_tab.h"
#include "fsutils.h"
#include "hashing.h"
#include "renderer_api.h"
#include "resources.h"
#include "threading.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
RT_CVAR_S(rt_ResourceDirectory, "The directory used for storing resources. Default: res", "res");
RT_CVAR_I(rt_ResourceCacheSize,
"The maximum amount of memory used for caching resources. Default: 512MB",
RT_MB(512));
RT_CVAR_I(rt_MaxCachedResources,
"The maximum number of simultaneously cached resources. Default: 1024",
1024);
RT_CVAR_I(rt_ResourceNamespaceSize,
"The maximum number of resources that can exist. Default: 1.048.576",
1048576);
#define RT_TOMBSTONE_ID 1
typedef struct {
void *buffer;
size_t size;
int next_free;
int usage_counter;
} rt_cached_resource;
typedef struct {
unsigned int index;
rt_resource_id id;
} rt_cached_resource_ref;
typedef struct {
void *mem;
rt_minheap reclaim_heap;
/* Used to lookup cached resources by id */
rt_resource_id *resource_ids;
unsigned int *resource_indices;
rt_cached_resource *resources;
unsigned int first_free;
size_t current_size;
rt_rwlock lock;
} rt_resource_cache;
typedef struct {
rt_file_id file;
size_t offset;
size_t size;
} rt_resource_ref;
typedef struct {
rt_resource_id *ids;
rt_resource_ref *refs;
rt_rwlock lock;
} rt_resource_namespace;
/* ~~~ Utilities ~~~ */
static size_t GetResourceDataSize(const rt_resource *resource) {
switch (resource->type) {
case RT_RESOURCE_PIPELINE:
return sizeof(rt_pipeline_info);
case RT_RESOURCE_SHADER: {
/* Sizeof metadata + bytecode */
const rt_shader_info *info = resource->data;
return sizeof(rt_shader_info) + (info) ? info->bytecode_length : 0;
} break;
default:
rtLog("RESMGR", "Tried to get size of an invalid resource type %u", resource->type);
}
return 0;
}
static void CopyResourceData(const rt_resource *resource, void *dest) {
switch (resource->type) {
case RT_RESOURCE_PIPELINE:
memcpy(dest, resource->data, sizeof(rt_pipeline_info));
break;
case RT_RESOURCE_SHADER: {
/* Sizeof metadata + bytecode */
const rt_shader_info *info = resource->data;
rt_shader_info *dest_info = dest;
memcpy(dest_info, info, sizeof(*info));
memcpy(dest_info + 1, rtResolveConstRelptr(&info->bytecode), info->bytecode_length);
rtSetRelptr(&dest_info->bytecode, (void *)(dest_info + 1));
} break;
default:
rtLog("RESMGR", "Tried to get copy a resource of invalid type %u", resource->type);
}
}
/* ~~~ Cache ~~~ */
static rt_resource_cache _cache;
static rt_result InitResourceCache(void) {
int count = rt_MaxCachedResources.i;
if (count == 0) {
rtReportError("RESMGR", "rt_MaxCachedResources must be greater than 0.");
return RT_INVALID_VALUE;
}
size_t required_mem = (size_t)count * (sizeof(rt_cached_resource_ref) + sizeof(int) +
sizeof(rt_cached_resource)) +
2 * (size_t)count * (sizeof(rt_resource_id) + sizeof(unsigned int));
void *mem = malloc(required_mem);
if (!mem)
return RT_OUT_OF_MEMORY;
rt_create_rwlock_result lock_create = rtCreateRWLock();
if (!lock_create.ok) {
free(mem);
return RT_UNKNOWN_ERROR;
}
memset(mem, 0, required_mem);
_cache.mem = mem;
int *reclaim_keys = mem;
rt_cached_resource_ref *reclaim_refs = (rt_cached_resource_ref *)reclaim_keys + count;
_cache.reclaim_heap = rtCreateMinheap(reclaim_keys,
reclaim_refs,
sizeof(rt_cached_resource_ref),
(size_t)count,
0);
_cache.current_size = 0;
_cache.resources = (rt_cached_resource *)(reclaim_keys + count);
_cache.lock = lock_create.lock;
for (int i = 0; i < count; ++i) {
_cache.resources[i].next_free = (i < count - 1) ? i + 1 : UINT_MAX;
}
_cache.first_free = 0;
_cache.resource_ids = (rt_resource_id *)(_cache.resources + count);
_cache.resource_indices = (unsigned int *)(_cache.resource_ids + 2 * count);
return RT_SUCCESS;
}
static void ShutdownResourceCache(void) {
free(_cache.mem);
rtDestroyRWLock(&_cache.lock);
memset(&_cache, 0, sizeof(_cache));
}
static bool FreeCacheSpace(size_t space) {
size_t total_freed = 0;
while (total_freed < space && !rtMinheapIsEmpty(&_cache.reclaim_heap)) {
rt_cached_resource_ref ref;
rtMinheapPop(&_cache.reclaim_heap, &ref);
rt_cached_resource *res = &_cache.resources[ref.index];
rtReleaseBuffer(res->buffer, res->size);
total_freed += res->size;
res->next_free = _cache.first_free;
_cache.first_free = ref.index;
res->usage_counter = 0;
res->buffer = NULL;
res->size = 0;
/* Remove from lookup table */
size_t ht_size = (size_t)rt_MaxCachedResources.i * 2;
for (size_t off = 0; off < ht_size; ++off) {
size_t slot = (ref.id + off) % ht_size;
if (_cache.resource_ids[slot] == ref.id) {
_cache.resource_ids[slot] = RT_TOMBSTONE_ID;
break;
} else if (_cache.resource_ids[slot] == RT_INVALID_RESOURCE_ID) {
break;
}
}
}
return total_freed >= space;
}
static unsigned int FindCachedResource(rt_resource_id id) {
size_t ht_size = (size_t)rt_MaxCachedResources.i * 2;
for (size_t off = 0; off < ht_size; ++off) {
size_t slot = (id + off) % ht_size;
if (_cache.resource_ids[slot] == id)
return _cache.resource_indices[slot];
else if (_cache.resource_ids[slot] == RT_INVALID_RESOURCE_ID)
return UINT_MAX;
}
return UINT_MAX;
}
static rt_resource *CacheResource(rt_resource_id id, const rt_resource *res) {
rt_resource *cached = NULL;
rtLockWrite(&_cache.lock);
unsigned int index = FindCachedResource(id);
if (index != UINT_MAX) {
rt_cached_resource_ref ref = {.id = id, .index = index};
rt_cached_resource *cache_entry = &_cache.resources[index];
++cache_entry->usage_counter;
rtMinheapUpdate(&_cache.reclaim_heap, &ref, cache_entry->usage_counter, NULL);
cached = cache_entry->buffer;
} else {
/* Insert into cache */
size_t total_size = sizeof(rt_resource) + GetResourceDataSize(res);
if (_cache.current_size + total_size >= (size_t)rt_ResourceCacheSize.i) {
if (!FreeCacheSpace(total_size)) {
rtLog("RESMGR",
"Unable to reclaim %zu kB from the resource cache.",
total_size / 1024);
rtUnlockWrite(&_cache.lock);
return NULL;
}
RT_ASSERT(_cache.first_free != UINT_MAX,
"There must be a free cache entry after space was freed.");
}
void *buffer = rtAllocBuffer(total_size);
if (!buffer) {
rtLog("RESMG", "Unable to allocate %zu kB for the new resource.", total_size / 1024);
rtUnlockWrite(&_cache.lock);
return NULL;
}
memcpy(buffer, res, sizeof(rt_resource));
cached = buffer;
cached->data = (void *)(cached + 1);
CopyResourceData(res, cached->data);
index = _cache.first_free;
_cache.first_free = _cache.resources[index].next_free;
_cache.resources[index].buffer = buffer;
_cache.resources[index].usage_counter = 1;
_cache.resources[index].size = total_size;
_cache.resources[index].next_free = UINT_MAX;
rt_cached_resource_ref reclaim_ref = {
.id = id,
.index = index,
};
rtMinheapPush(&_cache.reclaim_heap, 1, &reclaim_ref);
/* Insert into lookup table */
bool inserted = false;
size_t ht_size = (size_t)rt_MaxCachedResources.i * 2;
for (size_t off = 0; off < ht_size; ++off) {
size_t slot = (id + off) % ht_size;
if (_cache.resource_ids[slot] == RT_INVALID_RESOURCE_ID ||
_cache.resource_ids[slot] == RT_TOMBSTONE_ID || _cache.resource_ids[slot] == id) {
_cache.resource_indices[slot] = index;
inserted = true;
break;
}
}
if (!inserted) {
rtReportError("RESMGR",
"Failed to insert created resource into the resource lookup table.");
}
}
rtUnlockWrite(&_cache.lock);
return cached;
}
/* ~~~ Resource Namespace ~~~ */
static rt_resource_namespace _namespace;
static rt_result InitResourceNamespace(void) {
size_t size = (size_t)rt_ResourceNamespaceSize.i;
if (size == 0) {
rtReportError("RESMGR", "rt_ResourceNamespaceSize must be greater than 0.");
return RT_INVALID_VALUE;
}
void *mem = calloc(size, sizeof(rt_resource_id) + sizeof(rt_resource_ref));
if (!mem)
return RT_OUT_OF_MEMORY;
rt_create_rwlock_result lock_create = rtCreateRWLock();
if (!lock_create.ok) {
free(mem);
return RT_UNKNOWN_ERROR;
}
_namespace.lock = lock_create.lock;
_namespace.ids = mem;
_namespace.refs = (rt_resource_ref *)(_namespace.ids + size);
return RT_SUCCESS;
}
static void ShutdownNamespace(void) {
rtDestroyRWLock(&_namespace.lock);
free(_namespace.ids);
memset(&_namespace, 0, sizeof(_namespace));
}
#if 0
static rt_resource_ref *GetResourceRefPtr(rt_resource_id id) {
rt_resource_ref *ref = NULL;
rtLockRead(&_namespace.lock);
size_t ns_size = (size_t)rt_ResourceNamespaceSize.i;
for (size_t j = 0; j < ns_size; ++j) {
size_t at = (id + j) % ns_size;
if (_namespace.ids[at] == RT_INVALID_RESOURCE_ID) {
break;
} else if (_namespace.ids[at] == id) {
ref = &_namespace.refs[at];
break;
}
}
rtUnlockRead(&_namespace.lock);
return ref;
}
#endif
/* Fills the passed write struct with the necessary information to save the resource to a file */
static void
PrepareResourceFlushToFile(rt_resource_id id, const rt_resource *resource, rt_file_write *write) {
/* A file write needs one contiguous buffer */
RT_ASSERT(((uintptr_t)resource->data == (uintptr_t)resource + sizeof(*resource)),
"The resource must reside in the cache, to ensure the correct memory layout");
char file_path[260];
rtSPrint(file_path, 260, "%s/%llx.bin", rt_ResourceDirectory.s, id);
write->file = rtAddFile(file_path);
write->buffer = resource;
write->offset = 0;
write->num_bytes = sizeof(rt_resource) + GetResourceDataSize(resource);
}
/* ~~~ Public API ~~~ */
rt_result InitResourceManager(void) {
if (!rtCreateDirectory(rt_ResourceDirectory.s))
rtLog("RESMGR", "CreateDirectory(%s) failed.", rt_ResourceDirectory.s);
rt_result res;
if ((res = InitResourceCache()) != RT_SUCCESS)
return res;
if ((res = InitResourceNamespace()) != RT_SUCCESS) {
ShutdownResourceCache();
return res;
}
return RT_SUCCESS;
}
void ShutdownResourceManager(void) {
ShutdownResourceCache();
ShutdownNamespace();
}
RT_DLLEXPORT rt_result rtCreateResources(uint32_t count,
const char **names,
const rt_resource *resources,
rt_resource_id *ids) {
rt_result result = RT_SUCCESS;
size_t ns_size = (size_t)rt_ResourceNamespaceSize.i;
rt_write_batch writes = {.num_writes = 0};
rt_aio_handle write_handles[RT_WRITE_BATCH_MAX_SIZE];
uint32_t outstanding_writes = 0;
rtLockWrite(&_namespace.lock);
for (uint32_t i = 0; i < count; ++i) {
size_t name_len = strlen(names[i]);
rt_resource_id id = (rt_resource_id)rtHashBytes(names[i], name_len);
if (id == RT_INVALID_RESOURCE_ID || id == RT_TOMBSTONE_ID)
id = ~id;
bool inserted = false;
for (size_t j = 0; j < ns_size; ++j) {
size_t at = (id + j) % ns_size;
if (_namespace.ids[at] == RT_INVALID_RESOURCE_ID) {
inserted = true;
ids[i] = id;
const rt_resource *cached_resource = CacheResource(id, &resources[i]);
PrepareResourceFlushToFile(id, cached_resource, &writes.writes[writes.num_writes]);
_namespace.ids[at] = id;
_namespace.refs[at].offset = writes.writes[writes.num_writes].offset;
_namespace.refs[at].size = writes.writes[writes.num_writes].num_bytes;
_namespace.refs[at].file = writes.writes[writes.num_writes].file;
++writes.num_writes;
break;
} else if (_namespace.ids[at] == id) {
rtReportError("RESMGR",
"Resource ID collision occured with resource %s.\nID: %llx",
names[i],
id);
result = RT_INVALID_FILE_ID;
goto out;
}
}
if (!inserted) {
result = RT_OUT_OF_MEMORY;
goto out;
}
if (writes.num_writes == RT_WRITE_BATCH_MAX_SIZE ||
(i == count - 1 && writes.num_writes > 0)) {
if (outstanding_writes > 0) {
/* Wait until the previous batch is finished */
for (uint32_t k = 0; k < outstanding_writes; ++k) {
if (rtWaitForAIOCompletion(write_handles[k]) != RT_AIO_STATE_FINISHED) {
rtReportError("RESMGR", "Resource write failed.");
result = RT_UNKNOWN_ERROR;
goto out;
}
rtReleaseAIO(write_handles[k]);
}
}
outstanding_writes = writes.num_writes;
if (rtSubmitWriteBatch(&writes, write_handles) != RT_SUCCESS) {
rtReportError("RESMGR", "Failed to submit resource writes.");
result = RT_UNKNOWN_ERROR;
goto out;
}
}
}
if (outstanding_writes > 0) {
/* Wait until the last batch is finished */
for (uint32_t i = 0; i < outstanding_writes; ++i) {
if (rtWaitForAIOCompletion(write_handles[i]) != RT_AIO_STATE_FINISHED) {
rtReportError("RESMGR", "Resource write failed.");
result = RT_UNKNOWN_ERROR;
}
rtReleaseAIO(write_handles[i]);
}
}
out:
rtUnlockWrite(&_namespace.lock);
return result;
}

72
src/runtime/resources.h Normal file
View File

@ -0,0 +1,72 @@
#ifndef RT_RESOURCES_H
#define RT_RESOURCES_H
/* Resource system interface
*
* To differentiate the two ideas, we called processed assets "resources"
* and the source files "assets".
*
* For example a .pipeline file is an asset, while a compiled pipeline in
* a binary file is a resource.
*
* Furthermore, a single asset file might contain multiple resources,
* i.e. a single texture file might be turned into multiple resources for the different mip-levels.
*/
#include <stdint.h>
#include "runtime.h"
#ifdef __cplusplus
extern "C" {
#endif
/* Identifies a single resource
*
* This is a hash of the resource name. */
typedef uint64_t rt_resource_id;
#define RT_INVALID_RESOURCE_ID 0u
typedef enum {
/* Compiled shader code */
RT_RESOURCE_SHADER,
/* A pipeline state object */
RT_RESOURCE_PIPELINE,
} rt_resource_type;
#define RT_MAX_SUBRESOURCES 32
#define RT_MAX_RESOURCE_DEPENDENCIES 32
typedef struct {
/* Points to the resource data. The size of which is determined by the type. */
void *data;
rt_resource_type type;
/* Subresources are necessary to complete the resource.
* For example, a texture might contain different mip-levels as sub-resources. */
uint32_t subresource_count;
rt_resource_id subresources[RT_MAX_SUBRESOURCES];
/* Dependencies reference distinct resources that are necessary to use this resource.
* For example, a model file might depend on its materials */
uint32_t dependency_count;
rt_resource_id dependencies[RT_MAX_RESOURCE_DEPENDENCIES];
} rt_resource;
/* Registers resources with the resource manager, making them available to the system.
*
* The runtime will create a standalone file for each resource in the resource directory.
* To package them, you will need to use a separate tool.
*/
RT_DLLEXPORT rt_result rtCreateResources(uint32_t count,
const char **names,
const rt_resource *resources,
rt_resource_id *ids);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -3,6 +3,7 @@
/* basic types and macros */ /* basic types and macros */
#include <stdarg.h>
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
@ -22,7 +23,6 @@ extern "C" {
#define RT_INLINE inline __attribute__((always_inline)) #define RT_INLINE inline __attribute__((always_inline))
#endif #endif
#define RT_UNUSED(x) ((void)sizeof((x))) #define RT_UNUSED(x) ((void)sizeof((x)))
#define RT_ARRAY_COUNT(x) (sizeof((x)) / sizeof((x)[0])) #define RT_ARRAY_COUNT(x) (sizeof((x)) / sizeof((x)[0]))
@ -34,8 +34,9 @@ typedef unsigned int rt_result;
/* Default result codes */ /* Default result codes */
enum { enum {
RT_SUCCESS = 0, RT_SUCCESS = 0,
RT_OUT_OF_MEMORY = 1, RT_OUT_OF_MEMORY = 1,
RT_INVALID_VALUE = 2,
RT_CUSTOM_ERROR_START, RT_CUSTOM_ERROR_START,
@ -47,6 +48,13 @@ typedef struct {
unsigned int length; unsigned int length;
} rt_text_span; } rt_text_span;
/* snprintf replacement.
* Always returns a zero terminated string.
*/
RT_DLLEXPORT int rtSPrint(char *dest, size_t n, const char *fmt, ...);
RT_DLLEXPORT int rtVSPrint(char *dest, size_t n, const char *fmt, va_list ap);
/* Returns results like strcmp(): /* Returns results like strcmp():
* - If the first differing character is smaller in span than in cmp: < 0 * - If the first differing character is smaller in span than in cmp: < 0
* - If span and cmp are equal: 0 * - If span and cmp are equal: 0
@ -60,8 +68,30 @@ RT_DLLEXPORT void rtReportError(const char *subsystem, const char *fmt, ...);
RT_DLLEXPORT void rtLog(const char *subsystem, const char *fmt, ...); RT_DLLEXPORT void rtLog(const char *subsystem, const char *fmt, ...);
#ifndef NDEBUG
#ifdef _MSC_VER
#define RT_DEBUGBREAK __debugbreak()
#elif defined(__clang__) && __has_builtin(__bultin_debugtrap)
#define RT_DEBUGBREAK __builtin_debugtrap()
#elif defined(__GNUC__)
#define RT_DEBUGBREAK __builtin_trap()
#endif
RT_DLLEXPORT int rtAssertHandler(const char *expr, const char *msg, const char *file, int line);
#define RT_ASSERT(x, msg) \
do { \
if (!(x)) { \
if (rtAssertHandler(#x, (msg), __FILE__, __LINE__) == 0) { \
RT_DEBUGBREAK; \
} \
} \
} while (0)
#else
#define RT_ASSERT(x, msg) RT_UNUSED(x)
#endif
enum { enum {
RT_INVALID_UNICODE = RT_CUSTOM_ERROR_START, RT_INVALID_UNICODE = RT_CUSTOM_ERROR_START,
RT_INSUFFICIENT_BUFFER, RT_INSUFFICIENT_BUFFER,
}; };
@ -89,6 +119,15 @@ static RT_INLINE void *rtResolveRelptr(rt_relptr *ptr) {
} }
} }
static RT_INLINE const void *rtResolveConstRelptr(const rt_relptr *ptr) {
if (ptr->off != 0) {
const char *p = (const char *)ptr;
return (const void *)(p + (ptrdiff_t)ptr->off);
} else {
return NULL;
}
}
static RT_INLINE void rtSetRelptr(rt_relptr *ptr, void *target) { static RT_INLINE void rtSetRelptr(rt_relptr *ptr, void *target) {
if (target) { if (target) {
char *p = (char *)ptr, *t = (char *)target; char *p = (char *)ptr, *t = (char *)target;

View File

@ -4,23 +4,12 @@
#include "mem_arena.h" #include "mem_arena.h"
#include "runtime.h" #include "runtime.h"
#include "renderer_api.h"
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
typedef enum {
RT_SHADER_TYPE_INVALID,
RT_SHADER_TYPE_VULKAN,
RT_SHADER_TYPE_COUNT,
} rt_shader_type;
typedef enum {
RT_SHADER_STAGE_VERTEX,
RT_SHADER_STAGE_FRAGMENT,
RT_SHADER_STAGE_COMPUTE,
} rt_shader_stage;
typedef enum { typedef enum {
RT_SHADER_OPTIMIZATION_NONE, RT_SHADER_OPTIMIZATION_NONE,
RT_SHADER_OPTIMIZATION_SPEED, RT_SHADER_OPTIMIZATION_SPEED,

47
src/runtime/sprint.c Normal file
View File

@ -0,0 +1,47 @@
#include "runtime.h"
#include <stdarg.h>
#ifdef __SANITIZE_ADDRESS__
/* stb_sprintf has issues with ASAN under msvc */
#define ENABLE_STB_SPRINTF 0
#else
#define ENABLE_STB_SPRINTF 1
#endif
#if ENABLE_STB_SPRINTF
#pragma warning(push, 0)
#define STB_SPRINTF_IMPLEMENTATION
#define STB_SPRINTF_STATIC
#include <stb_sprintf.h>
#pragma warning(pop)
#else
#include <stdio.h>
#endif
RT_DLLEXPORT int rtSPrint(char *dest, size_t n, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
#if ENABLE_STB_SPRINTF
int r = stbsp_vsnprintf(dest, (int)n, fmt, ap);
#else
int r = vsnprintf(dest, n, fmt, ap);
if (r >= (int)n)
dest[n - 1] = '\0';
#endif
va_end(ap);
return r;
}
RT_DLLEXPORT int rtVSPrint(char *dest, size_t n, const char *fmt, va_list ap)
{
#if ENABLE_STB_SPRINTF
return stbsp_vsnprintf(dest, (int)n, fmt, ap);
#else
int r = vsnprintf(dest, n, fmt, ap);
if (r >= (int)n)
dest[n - 1] = '\0';
return r;
#endif
}