poll workers for non-threaded support, emscripten main loop, remove twn_model.c

This commit is contained in:
veclavtalica
2025-02-21 21:16:15 +03:00
parent dc6b298532
commit d76ea06470
12 changed files with 105 additions and 289 deletions

View File

@ -5,7 +5,7 @@
#include "twn_types.h"
#include "twn_deferred_commands.h"
#ifdef EMSCRIPTEN
#ifdef __EMSCRIPTEN__
#define GL_GLEXT_PROTOTYPES
#include <GL/gl.h>
#include <GL/glext.h>
@ -41,7 +41,7 @@ static void APIENTRY opengl_log(GLenum source,
bool render_init(void) {
#ifndef EMSCRIPTEN
#ifndef __EMSCRIPTEN__
if (gladLoadGLLoader(&SDL_GL_GetProcAddress) == 0) {
CRY("Init", "GLAD failed");
return false;
@ -50,7 +50,7 @@ bool render_init(void) {
log_info("OpenGL context: %s\n", glGetString(GL_VERSION));
#ifndef EMSCRIPTEN
#ifndef __EMSCRIPTEN__
glHint(GL_TEXTURE_COMPRESSION_HINT, GL_NICEST);
glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
glHint(GL_FOG_HINT, GL_FASTEST);
@ -98,7 +98,7 @@ static void finally_use_space_pipeline(void) {
depth_range_high = 1.0;
depth_range_low = 0.0;
#ifndef EMSCRIPTEN
#ifndef __EMSCRIPTEN__
static GLuint list = 0;
if (!list) {
list = glGenLists(1);
@ -109,7 +109,7 @@ static void finally_use_space_pipeline(void) {
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
glShadeModel(GL_FLAT);
#ifndef EMSCRIPTEN
#ifndef __EMSCRIPTEN__
if (GLAD_GL_ARB_depth_clamp)
glDisable(GL_DEPTH_CLAMP);
#endif
@ -125,7 +125,7 @@ static void finally_use_space_pipeline(void) {
/* solid white, no modulation */
glColor4ub(255, 255, 255, 255);
#ifndef EMSCRIPTEN
#ifndef __EMSCRIPTEN__
} glEndList();
glCallList(list);
@ -152,7 +152,7 @@ static void finally_use_2d_pipeline(void) {
if (pipeline_last_used == PIPELINE_2D)
return;
#ifndef EMSCRIPTEN
#ifndef __EMSCRIPTEN__
static GLuint list = 0;
if (!list) {
list = glGenLists(1);
@ -161,7 +161,7 @@ static void finally_use_2d_pipeline(void) {
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
glShadeModel(GL_FLAT);
#ifndef EMSCRIPTEN
#ifndef __EMSCRIPTEN__
/* removes near/far plane comparison and discard */
if (GLAD_GL_ARB_depth_clamp)
glEnable(GL_DEPTH_CLAMP);
@ -171,7 +171,7 @@ static void finally_use_2d_pipeline(void) {
glActiveTexture(GL_TEXTURE0);
glDisable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
#ifndef EMSCRIPTEN
#ifndef __EMSCRIPTEN__
} glEndList();
} glCallList(list);
@ -201,7 +201,7 @@ static void finally_use_texture_mode(TextureMode mode) {
if (texture_mode_last_used == mode)
return;
#ifndef EMSCRIPTEN
#ifndef __EMSCRIPTEN__
static GLuint lists = 0;
if (!lists) {
lists = glGenLists(3);
@ -214,7 +214,7 @@ static void finally_use_texture_mode(TextureMode mode) {
glDepthFunc(GL_LESS);
glDepthMask(GL_FALSE);
glDisable(GL_ALPHA_TEST);
#ifndef EMSCRIPTEN
#ifndef __EMSCRIPTEN__
} glEndList();
/* seethrough */
@ -225,7 +225,7 @@ static void finally_use_texture_mode(TextureMode mode) {
glDepthMask(GL_TRUE);
glEnable(GL_ALPHA_TEST);
glAlphaFunc(GL_EQUAL, 1.0f);
#ifndef EMSCRIPTEN
#ifndef __EMSCRIPTEN__
} glEndList();
/* opaque */
@ -235,7 +235,7 @@ static void finally_use_texture_mode(TextureMode mode) {
glDepthFunc(GL_LESS);
glDepthMask(GL_TRUE);
glDisable(GL_ALPHA_TEST);
#ifndef EMSCRIPTEN
#ifndef __EMSCRIPTEN__
} glEndList();
}
@ -257,7 +257,7 @@ VertexBufferBuilder build_vertex_buffer(VertexBuffer buffer, size_t bytes) {
SDL_assert(bytes != 0);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, bytes, NULL, GL_STREAM_DRAW);
#ifndef EMSCRIPTEN
#ifndef __EMSCRIPTEN__
void *mapping = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
#else
void *mapping = SDL_malloc(bytes);
@ -273,7 +273,7 @@ VertexBufferBuilder build_vertex_buffer(VertexBuffer buffer, size_t bytes) {
void finish_vertex_builder(VertexBufferBuilder *builder) {
#ifndef EMSCRIPTEN
#ifndef __EMSCRIPTEN__
if (!glUnmapBuffer(GL_ARRAY_BUFFER))
CRY("finish_vertex_builder", "Error unmapping a vertex array buffer");
#else
@ -372,7 +372,7 @@ void finally_render_skybox(DeferredCommandDrawSkybox command) {
/* TODO: figure out which coordinates to use to not have issues with far z */
/* TODO: recalculate the list if far z requirement changes */
#ifndef EMSCRIPTEN
#ifndef __EMSCRIPTEN__
static GLuint list = 0;
if (!list) {
list = glGenLists(1);
@ -384,7 +384,7 @@ void finally_render_skybox(DeferredCommandDrawSkybox command) {
glDisable(GL_ALPHA_TEST);
glDepthMask(GL_FALSE);
#ifndef EMSCRIPTEN
#ifndef __EMSCRIPTEN__
/* removes near/far plane comparison and discard */
if (GLAD_GL_ARB_depth_clamp)
glEnable(GL_DEPTH_CLAMP);
@ -452,7 +452,7 @@ void finally_render_skybox(DeferredCommandDrawSkybox command) {
glVertex3f(-50.f, 50.f, -50.f);
} glEnd();
#ifndef EMSCRIPTEN
#ifndef __EMSCRIPTEN__
if (GLAD_GL_ARB_depth_clamp)
glDisable(GL_DEPTH_CLAMP);
#endif
@ -460,7 +460,7 @@ void finally_render_skybox(DeferredCommandDrawSkybox command) {
glDepthMask(GL_TRUE);
glDisable(GL_TEXTURE_CUBE_MAP);
#ifndef EMSCRIPTEN
#ifndef __EMSCRIPTEN__
} glEndList();
glCallList(list);
#endif

View File

@ -4,7 +4,7 @@
#include <stb_ds.h>
#ifdef EMSCRIPTEN
#ifdef __EMSCRIPTEN__
#define GL_GLEXT_PROTOTYPES
#include <GL/gl.h>
#include <GL/glext.h>

View File

@ -1,235 +0,0 @@
#include "twn_draw_c.h"
#include "twn_draw.h"
#include "twn_workers_c.h"
#include "twn_textures_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 size_t model_load_queued;
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 */
/* it's safe to access everything without lock after this returns true and no public api is possible to call */
static bool model_load_workers_finished(void) {
bool result;
SDL_LockMutex(model_load_mutex);
result = model_load_queued == 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
};
fastObjMesh *const 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;
model_load_queued--;
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;
/* TODO: make it lockless */
/* 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);
model_load_queued++;
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())
SDL_Delay(1);
/* 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;
SDL_assert(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())
SDL_Delay(1);
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;
}

View File

@ -162,7 +162,7 @@ void draw_model(const char *model,
};
arrpush(model_load_queue, request);
SDL_UnlockMutex(model_load_mutex);
SDL_SemPost(workers_job_semaphore);
workers_add_job();
} else
modelcopy = item->key;