wip model loading + workers
This commit is contained in:
parent
cb88b4bcc5
commit
7040d6f218
@ -97,6 +97,7 @@ set(TWN_NONOPT_SOURCE_FILES
|
||||
src/twn_filewatch.c src/twn_filewatch_c.h
|
||||
src/twn_filewatch.c src/twn_filewatch_c.h
|
||||
src/twn_timer.c src/twn_timer_c.h
|
||||
src/twn_workers.c src/twn_workers_c.h
|
||||
|
||||
src/rendering/twn_draw.c src/rendering/twn_draw_c.h
|
||||
src/rendering/twn_quads.c
|
||||
@ -107,6 +108,7 @@ set(TWN_NONOPT_SOURCE_FILES
|
||||
src/rendering/twn_billboards.c
|
||||
src/rendering/twn_circles.c
|
||||
src/rendering/twn_skybox.c
|
||||
src/rendering/twn_model.c
|
||||
)
|
||||
|
||||
set(TWN_SOURCE_FILES
|
||||
@ -253,6 +255,7 @@ function(include_deps target)
|
||||
third-party/stb
|
||||
third-party/dmon
|
||||
third-party/tomlc99
|
||||
third-party/fast_obj
|
||||
$<$<NOT:$<BOOL:${EMSCRIPTEN}>>:third-party/glad/include>)
|
||||
|
||||
list(TRANSFORM THIRD_PARTY_INCLUDES PREPEND ${TWN_ROOT_DIR}/)
|
||||
|
@ -197,6 +197,8 @@ static void ingame_tick(State *state) {
|
||||
input_action("mouse_capture_toggle", "ESCAPE");
|
||||
input_action("toggle_camera_mode", "C");
|
||||
|
||||
draw_model("models/test.obj", (Vec3){0}, (Vec3){0,0,1}, (Vec3){1,1,1});
|
||||
|
||||
if (scn->mouse_captured) {
|
||||
const float sensitivity = 0.4f * (float)DEG2RAD; /* TODO: put this in a better place */
|
||||
scn->yaw += (float)ctx.mouse_movement.x * sensitivity;
|
||||
|
@ -111,6 +111,10 @@ draw_camera_from_principal_axes(Vec3 position,
|
||||
/* expects '*' masks that will be expanded to 6 names: 'up', 'down', 'east', 'west', 'north' and 'south' */
|
||||
TWN_API void draw_skybox(const char *textures);
|
||||
|
||||
TWN_API void draw_model(const char *model,
|
||||
Vec3 position, /* optional, default: 0 */
|
||||
Vec3 rotation, /* optional, default: (0, 0, 1) */
|
||||
Vec3 scale); /* optional, default: (1, 1, 1) */
|
||||
|
||||
#ifndef TWN_NOT_C
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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
170
src/rendering/twn_model.c
Normal 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);
|
||||
}
|
@ -11,6 +11,7 @@
|
||||
#include "twn_util.c"
|
||||
#include "twn_filewatch.c"
|
||||
#include "twn_timer.c"
|
||||
#include "twn_workers.c"
|
||||
|
||||
#include "rendering/twn_circles.c"
|
||||
#include "rendering/twn_draw.c"
|
||||
@ -21,3 +22,4 @@
|
||||
#include "rendering/twn_quads.c"
|
||||
#include "rendering/twn_triangles.c"
|
||||
#include "rendering/twn_billboards.c"
|
||||
#include "rendering/twn_model.c"
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "twn_game_object_c.h"
|
||||
#include "twn_textures_c.h"
|
||||
#include "twn_timer_c.h"
|
||||
#include "twn_workers_c.h"
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include <physfs.h>
|
||||
@ -724,7 +725,7 @@ static void clean_up(void) {
|
||||
|
||||
toml_free(ctx.config_table);
|
||||
PHYSFS_deinit();
|
||||
|
||||
workers_deinit();
|
||||
SDL_free(ctx.base_dir);
|
||||
SDL_free(ctx.title);
|
||||
SDL_GL_DeleteContext(ctx.gl_context);
|
||||
@ -861,6 +862,7 @@ int enter_loop(int argc, char **argv) {
|
||||
ctx.game.initialization_needed = true;
|
||||
|
||||
SDL_InitSubSystem(SDL_INIT_EVENTS);
|
||||
workers_init(SDL_GetCPUCount());
|
||||
|
||||
profile_end("startup");
|
||||
|
||||
|
69
src/twn_workers.c
Normal file
69
src/twn_workers.c
Normal file
@ -0,0 +1,69 @@
|
||||
#include "twn_util.h"
|
||||
#include "twn_workers_c.h"
|
||||
#include "rendering/twn_draw_c.h"
|
||||
|
||||
SDL_sem *workers_job_semaphore;
|
||||
|
||||
static SDL_Thread *workers_pool[MAX_WORKERS];
|
||||
static size_t workers_pool_size;
|
||||
static SDL_mutex *workers_mutex;
|
||||
static bool workers_should_exit;
|
||||
|
||||
/* logic is such that when job is posted, worker threads attempt to grab it from any possible entry point */
|
||||
/* if it did something, which is signaled by `true` return, go back to waiting on semaphore, so that it's decremented properly */
|
||||
static int worker_thread(void *udata) {
|
||||
(void)udata;
|
||||
|
||||
while (true) {
|
||||
/* check whether loop should end */
|
||||
SDL_LockMutex(workers_mutex);
|
||||
if (workers_should_exit) {
|
||||
SDL_UnlockMutex(workers_mutex);
|
||||
break;
|
||||
}
|
||||
SDL_UnlockMutex(workers_mutex);
|
||||
|
||||
/* wait and occasionally go back to check whether it all should end */
|
||||
if (SDL_SemWaitTimeout(workers_job_semaphore, 100) == SDL_MUTEX_TIMEDOUT)
|
||||
continue;
|
||||
|
||||
if (model_load_workers_thread())
|
||||
continue;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* TODO: have a path for platforms without thread support? */
|
||||
/* TODO: limit stack size? */
|
||||
bool workers_init(size_t worker_count) {
|
||||
SDL_assert(workers_pool_size == 0);
|
||||
|
||||
if (worker_count > MAX_WORKERS)
|
||||
worker_count = MAX_WORKERS;
|
||||
|
||||
/* spawn a bunch of detached threads without references to them */
|
||||
for (size_t i = 0; i < worker_count; ++i) {
|
||||
SDL_Thread *thread = SDL_CreateThread(worker_thread, "worker", NULL);
|
||||
SDL_assert_always(thread);
|
||||
SDL_DetachThread(thread);
|
||||
}
|
||||
|
||||
workers_pool_size = worker_count;
|
||||
workers_job_semaphore = SDL_CreateSemaphore(0);
|
||||
workers_mutex = SDL_CreateMutex();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void workers_deinit(void) {
|
||||
SDL_LockMutex(workers_mutex);
|
||||
workers_should_exit = true;
|
||||
SDL_UnlockMutex(workers_mutex);
|
||||
|
||||
/* TODO: that's not correct */
|
||||
SDL_DestroyMutex(workers_mutex);
|
||||
SDL_DestroySemaphore(workers_job_semaphore);
|
||||
workers_pool_size = 0;
|
||||
}
|
17
src/twn_workers_c.h
Normal file
17
src/twn_workers_c.h
Normal file
@ -0,0 +1,17 @@
|
||||
#ifndef TWN_WORKERS_C_H
|
||||
#define TWN_WORKERS_C_H
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#define MAX_WORKERS 9
|
||||
|
||||
/* workers are waiting on this, increment this value when some work needs to be done */
|
||||
/* for now every possible job path is hardcoded in twn_workers.c itself */
|
||||
extern SDL_sem *workers_job_semaphore;
|
||||
|
||||
bool workers_init(size_t worker_count);
|
||||
void workers_deinit(void);
|
||||
|
||||
#endif
|
21
third-party/fast_obj/LICENSE
vendored
Normal file
21
third-party/fast_obj/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 thisistherk
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
1583
third-party/fast_obj/fast_obj.h
vendored
Normal file
1583
third-party/fast_obj/fast_obj.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user