Write the first compiled resources to files
This commit is contained in:
parent
9670844bb2
commit
3a9f9d4986
1910
contrib/stb_sprintf.h
Normal file
1910
contrib/stb_sprintf.h
Normal file
File diff suppressed because it is too large
Load Diff
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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
45
src/runtime/assert.c
Normal 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;
|
||||||
|
}
|
@ -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;
|
||||||
@ -136,7 +147,7 @@ static int DiscoverAssets(void) {
|
|||||||
: MAX_FILENAME_LEN);
|
: MAX_FILENAME_LEN);
|
||||||
directory_stack[0][MAX_FILENAME_LEN - 1] = '\0';
|
directory_stack[0][MAX_FILENAME_LEN - 1] = '\0';
|
||||||
++top;
|
++top;
|
||||||
|
|
||||||
int discovery_count = 0;
|
int discovery_count = 0;
|
||||||
|
|
||||||
while (top > 0) {
|
while (top > 0) {
|
||||||
@ -154,7 +165,7 @@ static int DiscoverAssets(void) {
|
|||||||
rt_dirent entry;
|
rt_dirent entry;
|
||||||
do {
|
do {
|
||||||
entry = rtNextDirectoryEntry(scan);
|
entry = rtNextDirectoryEntry(scan);
|
||||||
|
|
||||||
if (entry.name[0] == '.')
|
if (entry.name[0] == '.')
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@ -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,24 +234,47 @@ 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;
|
||||||
entry.fid = _asset_db.files[i];
|
entry.fid = _asset_db.files[i];
|
||||||
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);
|
||||||
|
|
||||||
|
@ -2,10 +2,12 @@
|
|||||||
#define RT_ASSET_COMPILER_H
|
#define RT_ASSET_COMPILER_H
|
||||||
|
|
||||||
#ifndef RT_BUILD_ASSET_COMPILER
|
#ifndef RT_BUILD_ASSET_COMPILER
|
||||||
#error This file should only be included if RT_BUILD_ASSET_COMPILER is defined.
|
#error This file should only be included if RT_BUILD_ASSET_COMPILER is defined.
|
||||||
#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;
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
#ifndef RT_ASSETS_H
|
|
||||||
#define RT_ASSETS_H
|
|
||||||
|
|
||||||
/* Asset system interface */
|
|
||||||
|
|
||||||
#endif
|
|
@ -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);
|
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);
|
|
||||||
}
|
|
@ -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
59
src/runtime/ds.h
Normal 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
138
src/runtime/ds_minheap.c
Normal 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);
|
||||||
|
}
|
@ -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!");
|
||||||
|
@ -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);
|
||||||
}
|
}
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
@ -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();
|
||||||
|
@ -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,8 +341,9 @@ 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)
|
||||||
type = RT_SHADER_TYPE_VULKAN;
|
type = RT_SHADER_TYPE_VULKAN;
|
||||||
@ -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;
|
||||||
|
@ -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);
|
||||||
|
444
src/runtime/resource_manager.c
Normal file
444
src/runtime/resource_manager.c
Normal 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
72
src/runtime/resources.h
Normal 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
|
@ -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,13 +68,35 @@ 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,
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Returns RT_SUCCESS if the string was successfully converted,
|
/* Returns RT_SUCCESS if the string was successfully converted,
|
||||||
* RT_INVALID_UNICODE if invalid unicode characters were encountered or
|
* RT_INVALID_UNICODE if invalid unicode characters were encountered or
|
||||||
* RT_INSUFFICIENT_BUFFER if the provided output buffer is too small */
|
* RT_INSUFFICIENT_BUFFER if the provided output buffer is too small */
|
||||||
RT_DLLEXPORT rt_result rtUTF8ToWStr(const char *utf8, wchar_t *wstr, size_t len);
|
RT_DLLEXPORT rt_result rtUTF8ToWStr(const char *utf8, wchar_t *wstr, size_t len);
|
||||||
|
|
||||||
@ -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;
|
||||||
|
@ -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
47
src/runtime/sprint.c
Normal 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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user