232 lines
9.4 KiB
C
232 lines
9.4 KiB
C
#include "twn_draw_c.h"
|
|
#include "twn_draw.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 *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 const **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 const *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;
|
|
}
|
|
|
|
struct ModelCacheItem const *item;
|
|
|
|
/* if model is missing, queue it up for loading */
|
|
SDL_LockMutex(model_load_mutex);
|
|
if (!(item = shgetp_null(model_cache, model))) {
|
|
model = SDL_strdup(model);
|
|
shput(model_cache, model, (struct ModelCacheItemValue){0});
|
|
arrpush(model_load_queue, model);
|
|
SDL_SemPost(workers_job_semaphore);
|
|
} else
|
|
model = item->key;
|
|
SDL_UnlockMutex(model_load_mutex);
|
|
|
|
struct ModelDrawCommand const command = {
|
|
.model = (char *)model,
|
|
.position = position,
|
|
.rotation = rotation,
|
|
.scale = scale
|
|
};
|
|
arrpush(model_draw_commands, command);
|
|
}
|
|
|
|
|
|
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 */
|
|
unsigned int const material_index = mesh->face_materials[group->index_offset + f];
|
|
fastObjMaterial const *const material = mesh->materials ? &mesh->materials[material_index] : NULL;
|
|
if (vertices == 4) {
|
|
fastObjIndex const i0 = mesh->indices[group->index_offset + idx + 0];
|
|
fastObjIndex const i1 = mesh->indices[group->index_offset + idx + 1];
|
|
fastObjIndex const i2 = mesh->indices[group->index_offset + idx + 2];
|
|
fastObjIndex const i3 = mesh->indices[group->index_offset + idx + 3];
|
|
draw_quad(
|
|
material ? mesh->textures[material->map_Kd].name : NULL,
|
|
(Vec3) { mesh->positions[3 * i0.p + 0] * command->scale.x, mesh->positions[3 * i0.p + 1] * command->scale.y, mesh->positions[3 * i0.p + 2] * command->scale.z },
|
|
(Vec3) { mesh->positions[3 * i1.p + 0] * command->scale.x, mesh->positions[3 * i1.p + 1] * command->scale.y, mesh->positions[3 * i1.p + 2] * command->scale.z },
|
|
(Vec3) { mesh->positions[3 * i2.p + 0] * command->scale.x, mesh->positions[3 * i2.p + 1] * command->scale.y, mesh->positions[3 * i2.p + 2] * command->scale.z },
|
|
(Vec3) { mesh->positions[3 * i3.p + 0] * command->scale.x, mesh->positions[3 * i3.p + 1] * command->scale.y, mesh->positions[3 * i3.p + 2] * command->scale.z },
|
|
(Rect) { .w = 64, .h = 64 },
|
|
(Color) { 255, 255, 255, 255 }
|
|
);
|
|
} else if (vertices == 3) {
|
|
fastObjIndex const i0 = mesh->indices[group->index_offset + idx + 0];
|
|
fastObjIndex const i1 = mesh->indices[group->index_offset + idx + 1];
|
|
fastObjIndex const i2 = mesh->indices[group->index_offset + idx + 2];
|
|
draw_triangle(
|
|
material ? mesh->textures[material->map_Kd].name : NULL,
|
|
(Vec3) { mesh->positions[3 * i0.p + 0] * command->scale.x, mesh->positions[3 * i0.p + 1] * command->scale.y, mesh->positions[3 * i0.p + 2] * command->scale.z },
|
|
(Vec3) { mesh->positions[3 * i1.p + 0] * command->scale.x, mesh->positions[3 * i1.p + 1] * command->scale.y, mesh->positions[3 * i1.p + 2] * command->scale.z },
|
|
(Vec3) { mesh->positions[3 * i2.p + 0] * command->scale.x, mesh->positions[3 * i2.p + 1] * command->scale.y, mesh->positions[3 * i2.p + 2] * command->scale.z },
|
|
(Vec2) {0,0},
|
|
(Vec2) {0,0},
|
|
(Vec2) {0,0},
|
|
(Color){255, 255, 255, 255},
|
|
(Color){255, 255, 255, 255},
|
|
(Color){255, 255, 255, 255}
|
|
);
|
|
} else {
|
|
fastObjIndex const i0 = mesh->indices[group->index_offset + idx];
|
|
for (unsigned int z = 0; z < vertices - 2; ++z) {
|
|
fastObjIndex const i1 = mesh->indices[group->index_offset + idx + 1 + z];
|
|
fastObjIndex const i2 = mesh->indices[group->index_offset + idx + 2 + z];
|
|
draw_triangle(
|
|
material ? mesh->textures[material->map_Kd].name : NULL,
|
|
(Vec3) { mesh->positions[3 * i0.p + 0] * command->scale.x, mesh->positions[3 * i0.p + 1] * command->scale.y, mesh->positions[3 * i0.p + 2] * command->scale.z },
|
|
(Vec3) { mesh->positions[3 * i1.p + 0] * command->scale.x, mesh->positions[3 * i1.p + 1] * command->scale.y, mesh->positions[3 * i1.p + 2] * command->scale.z },
|
|
(Vec3) { mesh->positions[3 * i2.p + 0] * command->scale.x, mesh->positions[3 * i2.p + 1] * command->scale.y, mesh->positions[3 * i2.p + 2] * command->scale.z },
|
|
(Vec2) {0,0},
|
|
(Vec2) {0,0},
|
|
(Vec2) {0,0},
|
|
(Color){255, 255, 255, 255},
|
|
(Color){255, 255, 255, 255},
|
|
(Color){255, 255, 255, 255}
|
|
);
|
|
}
|
|
}
|
|
idx += vertices;
|
|
}
|
|
}
|
|
}
|
|
|
|
arrsetlen(model_draw_commands, 0);
|
|
}
|
|
|
|
|
|
/* drop model caches */
|
|
void free_model_cache(void) {
|
|
while (!model_load_workers_finished()) {
|
|
(void)0;
|
|
}
|
|
|
|
for (size_t i = 0; i < shlenu(model_cache); ++i) {
|
|
fast_obj_destroy(model_cache[i].value.mesh);
|
|
SDL_free(model_cache[i].key);
|
|
}
|
|
|
|
shfree(model_cache);
|
|
}
|
|
|
|
|
|
void model_state_deinit(void) {
|
|
if (!model_load_initialized)
|
|
return;
|
|
free_model_cache();
|
|
arrfree(model_load_queue);
|
|
SDL_DestroyMutex(model_load_mutex);
|
|
model_load_initialized = false;
|
|
}
|