wip model loading + workers

This commit is contained in:
veclavtalica
2025-02-07 10:19:36 +03:00
parent cb88b4bcc5
commit 7040d6f218
12 changed files with 1881 additions and 2 deletions

View File

@ -393,7 +393,10 @@ static void render_2d(void) {
}
/* TODO: benchmark which order works best for expected cases */
static void render_space(void) {
finally_draw_models();
/* nothing to do, abort */
/* as space pipeline isn't used we can have fewer changes and initialization costs */
if (hmlenu(ctx.uncolored_mesh_batches) != 0 || hmlenu(ctx.billboard_batches) != 0) {

View File

@ -132,7 +132,7 @@ typedef struct MeshBatch {
/* TODO: use atlas id instead */
typedef struct MeshBatchItem {
TextureKey key;
struct TextureKey key;
struct MeshBatch value;
} MeshBatchItem;
@ -339,4 +339,7 @@ void finally_draw_command(DeferredCommandDraw command);
void issue_deferred_draw_commands(void);
bool model_load_workers_thread(void);
void finally_draw_models(void);
#endif

170
src/rendering/twn_model.c Normal file
View File

@ -0,0 +1,170 @@
#include "twn_draw_c.h"
#include "twn_draw.h"
#include "twn_util.h"
#include "twn_workers_c.h"
#define FAST_OBJ_IMPLEMENTATION
#define FAST_OBJ_REALLOC SDL_realloc
#define FAST_OBJ_FREE SDL_free
#include <fast_obj.h>
#include <stb_ds.h>
#include <physfs.h>
#include <physfsrwops.h>
static struct ModelCacheItem {
char const *key;
struct ModelCacheItemValue {
fastObjMesh *mesh;
} value;
} *model_cache;
/* TODO: store index to model cache instead */
static struct ModelDrawCommand {
char *model;
Vec3 position;
Vec3 rotation;
Vec3 scale;
} *model_draw_commands;
/* deferred queue of model files to load from worker threads */
static SDL_mutex *model_load_mutex;
static char **model_load_queue;
static bool model_load_initialized;
/* use streaming via callbacks to reduce memory congestion */
static void model_load_callback_close(void *handle, void *udata) {
(void)udata;
((SDL_RWops *)handle)->close(handle);
}
static void *model_load_callback_open(const char *path, void *udata) {
(void)udata;
return PHYSFSRWOPS_openRead(path);
}
static size_t model_load_callback_read(void *handle, void *dst, size_t bytes, void *udata) {
(void)udata;
return ((SDL_RWops *)handle)->read(handle, dst, 1, bytes);
}
static unsigned long model_load_callback_size(void *handle, void *udata) {
(void)udata;
return ((SDL_RWops *)handle)->size(handle);
}
/* TODO: is there a way to do this nicely while locking main thread? */
/* sleeping over atomic counter might be good enough i guess */
static bool model_load_workers_finished(void) {
bool result;
SDL_LockMutex(model_load_mutex);
result = arrlenu(model_load_queue) == 0;
SDL_UnlockMutex(model_load_mutex);
return result;
}
/* entry point for workers, polled every time a job semaphore is posted */
/* returns false if there was nothing to do */
bool model_load_workers_thread(void) {
/* attempt to grab something to work on */
char *load_request = NULL;
SDL_LockMutex(model_load_mutex);
if (arrlenu(model_load_queue) != 0)
load_request = arrpop(model_load_queue);
SDL_UnlockMutex(model_load_mutex);
/* nothing to do, bail */
if (!load_request)
return false;
fastObjCallbacks const callbacks = {
.file_close = model_load_callback_close,
.file_open = model_load_callback_open,
.file_read = model_load_callback_read,
.file_size = model_load_callback_size
};
/* TODO: immediately create jobs for missing textures */
fastObjMesh *mesh = fast_obj_read_with_callbacks(load_request, &callbacks, NULL);
SDL_LockMutex(model_load_mutex);
struct ModelCacheItem *item = shgetp(model_cache, load_request);
item->value.mesh = mesh;
SDL_UnlockMutex(model_load_mutex);
return true;
}
void draw_model(const char *model,
Vec3 position,
Vec3 rotation,
Vec3 scale)
{
if (!model_load_initialized) {
model_load_mutex = SDL_CreateMutex();
model_load_initialized = true;
}
/* make sure not to reference parameter longer than duration of this function */
char *model_copy = SDL_strdup(model);
model = NULL; /* trap */
struct ModelDrawCommand const command = {
.model = model_copy,
.position = position,
.rotation = rotation,
.scale = scale
};
arrpush(model_draw_commands, command);
/* if model is missing, queue it up for loading */
SDL_LockMutex(model_load_mutex);
if (!(shgetp_null(model_cache, model_copy))) {
shput(model_cache, model_copy, (struct ModelCacheItemValue){0});
arrpush(model_load_queue, model_copy);
SDL_SemPost(workers_job_semaphore);
}
SDL_UnlockMutex(model_load_mutex);
}
void finally_draw_models(void) {
while (!model_load_workers_finished()) {
(void)0;
}
/* TODO: have special path for them, preserving the buffers and potentially using instanced draw */
for (int i = 0; i < arrlen(model_draw_commands); ++i) {
struct ModelDrawCommand const *const command = &model_draw_commands[i];
fastObjMesh const *const mesh = model_cache[shgeti(model_cache, command->model)].value.mesh;
for (unsigned int g = 0; g < mesh->group_count; ++g) {
fastObjGroup const *const group = &mesh->groups[g];
unsigned int idx = 0;
for (unsigned int f = 0; f < group->face_count; ++f) {
unsigned int const vertices = mesh->face_vertices[group->face_offset + f];
// fastObjTexture const *const texture = &mesh->textures[group->face_offset + f];
// log_info("material: %s", material->name);
/* TODO: support arbitrary fans */
SDL_assert(vertices == 4);
fastObjIndex i0 = mesh->indices[group->index_offset + idx + 0];
fastObjIndex i1 = mesh->indices[group->index_offset + idx + 1];
fastObjIndex i2 = mesh->indices[group->index_offset + idx + 2];
fastObjIndex i3 = mesh->indices[group->index_offset + idx + 3];
draw_quad(
"asd",
(Vec3) { mesh->positions[3 * i0.p + 0], mesh->positions[3 * i0.p + 1], mesh->positions[3 * i0.p + 2] },
(Vec3) { mesh->positions[3 * i1.p + 0], mesh->positions[3 * i1.p + 1], mesh->positions[3 * i1.p + 2] },
(Vec3) { mesh->positions[3 * i2.p + 0], mesh->positions[3 * i2.p + 1], mesh->positions[3 * i2.p + 2] },
(Vec3) { mesh->positions[3 * i3.p + 0], mesh->positions[3 * i3.p + 1], mesh->positions[3 * i3.p + 2] },
(Rect) { .w = 64, .h = 64 },
(Color) { 255, 255, 255, 255 }
);
idx += vertices;
}
}
}
arrsetlen(model_draw_commands, 0);
}