#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 #include #include #include 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; }