resource: partial support for resource reloading

This commit is contained in:
Andrei Alexeyev 2021-11-24 09:31:40 +02:00
parent 1886e95a66
commit b39c9ba78e
No known key found for this signature in database
GPG key ID: 72D26128040B9690
21 changed files with 511 additions and 56 deletions

View file

@ -58,6 +58,7 @@
CONFIGDEF_KEYBINDING(KEY_RESTART, "key_restart", SDL_SCANCODE_F2) \
CONFIGDEF_KEYBINDING(KEY_HITAREAS, "key_hitareas", SDL_SCANCODE_H) \
CONFIGDEF_KEYBINDING(KEY_TOGGLE_AUDIO, "key_toggle_audio", SDL_SCANCODE_M) \
CONFIGDEF_KEYBINDING(KEY_RELOAD_RESOURCES, "key_reload_resources", SDL_SCANCODE_F5) \
#define GPKEYDEFS \

View file

@ -526,5 +526,10 @@ static bool events_handler_hotkeys(SDL_Event *event, void *arg) {
return true;
}
if(scan == config_get_int(CONFIG_KEY_RELOAD_RESOURCES)) {
reload_all_resources();
return true;
}
return false;
}

View file

@ -356,6 +356,10 @@ const char* r_shader_object_get_debug_label(ShaderObject *shobj) {
return B.shader_object_get_debug_label(shobj);
}
bool r_shader_object_transfer(ShaderObject *dst, ShaderObject *src) {
return B.shader_object_transfer(dst, src);
}
ShaderProgram* r_shader_program_link(uint num_objects, ShaderObject *shobjs[num_objects]) {
return B.shader_program_link(num_objects, shobjs);
}
@ -372,6 +376,10 @@ const char* r_shader_program_get_debug_label(ShaderProgram *prog) {
return B.shader_program_get_debug_label(prog);
}
bool r_shader_program_transfer(ShaderProgram *dst, ShaderProgram *src) {
return B.shader_program_transfer(dst, src);
}
void r_shader_ptr(ShaderProgram *prog) {
_r_state_touch_shader();
B.shader(prog);
@ -462,6 +470,10 @@ void r_texture_destroy(Texture *tex) {
B.texture_destroy(tex);
}
bool r_texture_transfer(Texture *dst, Texture *src) {
return B.texture_transfer(dst, src);
}
bool r_texture_type_query(TextureType type, TextureFlags flags, PixmapFormat pxfmt, PixmapOrigin pxorigin, TextureTypeQueryResult *result) {
return B.texture_type_query(type, flags, pxfmt, pxorigin, result);
}

View file

@ -539,11 +539,13 @@ ShaderObject* r_shader_object_compile(ShaderSource *source) attr_nonnull(1);
void r_shader_object_destroy(ShaderObject *shobj) attr_nonnull(1);
void r_shader_object_set_debug_label(ShaderObject *shobj, const char *label) attr_nonnull(1);
const char* r_shader_object_get_debug_label(ShaderObject *shobj) attr_nonnull(1);
bool r_shader_object_transfer(ShaderObject *dst, ShaderObject *src) attr_nonnull_all;
ShaderProgram* r_shader_program_link(uint num_objects, ShaderObject *shobjs[num_objects]) attr_nonnull(2);
void r_shader_program_destroy(ShaderProgram *prog);
void r_shader_program_set_debug_label(ShaderProgram *prog, const char *label) attr_nonnull(1);
const char* r_shader_program_get_debug_label(ShaderProgram *prog) attr_nonnull(1);
bool r_shader_program_transfer(ShaderProgram *dst, ShaderProgram *src) attr_nonnull_all;
void r_shader_ptr(ShaderProgram *prog) attr_nonnull(1);
ShaderProgram* r_shader_current(void) attr_returns_nonnull;
@ -737,6 +739,7 @@ bool r_texture_dump(Texture *tex, uint mipmap, uint layer, Pixmap *dst) attr_non
void r_texture_invalidate(Texture *tex) attr_nonnull(1);
void r_texture_clear(Texture *tex, const Color *clr) attr_nonnull(1, 2);
void r_texture_destroy(Texture *tex) attr_nonnull(1);
bool r_texture_transfer(Texture *dst, Texture *src) attr_nonnull(1);
bool r_texture_type_query(TextureType type, TextureFlags flags, PixmapFormat pxfmt, PixmapOrigin pxorigin, TextureTypeQueryResult *result) attr_nodiscard;
const char *r_texture_type_name(TextureType type);

View file

@ -49,11 +49,13 @@ typedef struct RendererFuncs {
void (*shader_object_destroy)(ShaderObject *shobj);
void (*shader_object_set_debug_label)(ShaderObject *shobj, const char *label);
const char* (*shader_object_get_debug_label)(ShaderObject *shobj);
bool (*shader_object_transfer)(ShaderObject *dst, ShaderObject *src);
ShaderProgram* (*shader_program_link)(uint num_objects, ShaderObject *shobjs[num_objects]);
void (*shader_program_destroy)(ShaderProgram *prog);
void (*shader_program_set_debug_label)(ShaderProgram *prog, const char *label);
const char* (*shader_program_get_debug_label)(ShaderProgram *prog);
bool (*shader_program_transfer)(ShaderProgram *dst, ShaderProgram *src);
void (*shader)(ShaderProgram *prog);
ShaderProgram* (*shader_current)(void);
@ -76,6 +78,7 @@ typedef struct RendererFuncs {
bool (*texture_dump)(Texture *tex, uint mipmap, uint layer, Pixmap *dst);
void (*texture_clear)(Texture *tex, const Color *clr);
bool (*texture_type_query)(TextureType type, TextureFlags flags, PixmapFormat pxfmt, PixmapOrigin pxorigin, TextureTypeQueryResult *result);
bool (*texture_transfer)(Texture *dst, Texture *src);
Framebuffer* (*framebuffer_create)(void);
const char* (*framebuffer_get_debug_label)(Framebuffer *framebuffer);

View file

@ -1035,6 +1035,21 @@ void gl33_texture_deleted(Texture *tex) {
}
}
void gl33_texture_pointer_renamed(Texture *pold, Texture *pnew) {
_r_sprite_batch_texture_deleted(pold);
gl33_uniforms_handle_texture_pointer_renamed(pold, pnew);
for(TextureUnit *unit = R.texunits.array; unit < R.texunits.array + R.texunits.limit; ++unit) {
if(unit->pending == pold) {
unit->pending = pnew;
}
if(unit->active == pold) {
unit->active = pnew;
}
}
}
void gl33_framebuffer_deleted(Framebuffer *fb) {
if(R.framebuffer.pending == fb) {
R.framebuffer.pending = NULL;
@ -1390,10 +1405,12 @@ RendererBackend _r_backend_gl33 = {
.shader_object_destroy = gl33_shader_object_destroy,
.shader_object_set_debug_label = gl33_shader_object_set_debug_label,
.shader_object_get_debug_label = gl33_shader_object_get_debug_label,
.shader_object_transfer = gl33_shader_object_transfer,
.shader_program_link = gl33_shader_program_link,
.shader_program_destroy = gl33_shader_program_destroy,
.shader_program_set_debug_label = gl33_shader_program_set_debug_label,
.shader_program_get_debug_label = gl33_shader_program_get_debug_label,
.shader_program_transfer = gl33_shader_program_transfer,
.shader = gl33_shader,
.shader_current = gl33_shader_current,
.shader_uniform = gl33_shader_uniform,
@ -1413,6 +1430,7 @@ RendererBackend _r_backend_gl33 = {
.texture_clear = gl33_texture_clear,
.texture_type_query = gl33_texture_type_query,
.texture_dump = gl33_texture_dump,
.texture_transfer = gl33_texture_transfer,
.framebuffer_create = gl33_framebuffer_create,
.framebuffer_destroy = gl33_framebuffer_destroy,
.framebuffer_attach = gl33_framebuffer_attach,

View file

@ -83,4 +83,6 @@ void gl33_texture_deleted(Texture *tex);
void gl33_framebuffer_deleted(Framebuffer *fb);
void gl33_shader_deleted(ShaderProgram *prog);
void gl33_texture_pointer_renamed(Texture *pold, Texture *pnew);
extern RendererBackend _r_backend_gl33;

View file

@ -58,7 +58,7 @@ static void print_info_log(GLuint shader) {
}
}
ShaderObject* gl33_shader_object_compile(ShaderSource *source) {
ShaderObject *gl33_shader_object_compile(ShaderSource *source) {
assert(r_shader_language_supported(&source->lang, NULL));
GLuint gl_handle = glCreateShader(
@ -100,12 +100,16 @@ ShaderObject* gl33_shader_object_compile(ShaderSource *source) {
if(status) {
uint nattribs = source->meta.glsl.num_attributes;
shobj = calloc(1, sizeof(*shobj) + sizeof(GLSLAttribute) * nattribs);
shobj = calloc(1, sizeof(*shobj));
shobj->gl_handle = gl_handle;
shobj->stage = source->stage;
shobj->num_attribs = nattribs;
snprintf(shobj->debug_label, sizeof(shobj->debug_label), "Shader object #%i", gl_handle);
if(nattribs > 0) {
shobj->attribs = calloc(nattribs, sizeof(*shobj->attribs));
}
for(uint i = 0; i < nattribs; ++i) {
GLSLAttribute *a = source->meta.glsl.attributes + i;
shobj->attribs[i].name = strdup(a->name);
@ -127,6 +131,7 @@ void gl33_shader_object_destroy(ShaderObject *shobj) {
free(shobj->attribs[i].name);
}
free(shobj->attribs);
free(shobj);
}
@ -134,6 +139,27 @@ void gl33_shader_object_set_debug_label(ShaderObject *shobj, const char *label)
glcommon_set_debug_label(shobj->debug_label, "Shader object", GL_SHADER, shobj->gl_handle, label);
}
const char* gl33_shader_object_get_debug_label(ShaderObject *shobj) {
const char *gl33_shader_object_get_debug_label(ShaderObject *shobj) {
return shobj->debug_label;
}
bool gl33_shader_object_transfer(ShaderObject *dst, ShaderObject *src) {
if(UNLIKELY(dst->stage != src->stage)) {
log_error("Shader object changed stage");
return false;
}
glDeleteShader(dst->gl_handle);
uint nattribs = dst->num_attribs;
for(uint i = 0; i < nattribs; ++i) {
free(dst->attribs[i].name);
}
free(dst->attribs);
*dst = *src;
free(src);
return true;
}

View file

@ -17,12 +17,13 @@ struct ShaderObject {
ShaderStage stage;
char debug_label[R_DEBUG_LABEL_SIZE];
uint num_attribs;
GLSLAttribute attribs[];
GLSLAttribute *attribs;
};
bool gl33_shader_language_supported(const ShaderLangInfo *lang, ShaderLangInfo *out_alternative);
ShaderObject* gl33_shader_object_compile(ShaderSource *source);
ShaderObject *gl33_shader_object_compile(ShaderSource *source);
void gl33_shader_object_destroy(ShaderObject *shobj);
void gl33_shader_object_set_debug_label(ShaderObject *shobj, const char *label);
const char* gl33_shader_object_get_debug_label(ShaderObject *shobj);
const char *gl33_shader_object_get_debug_label(ShaderObject *shobj);
bool gl33_shader_object_transfer(ShaderObject *dst, ShaderObject *src);

View file

@ -468,9 +468,13 @@ static bool cache_uniforms(ShaderProgram *prog) {
void gl33_unref_texture_from_samplers(Texture *tex) {
for(Uniform *u = sampler_uniforms; u; u = u->next) {
assert(UNIFORM_TYPE_IS_SAMPLER(u->type));
assert(u->textures != NULL);
if(!u->textures) {
continue;
}
for(Texture **slot = u->textures; slot < u->textures + u->array_size; ++slot) {
assert(slot != NULL);
if(*slot == tex) {
*slot = NULL;
}
@ -478,6 +482,19 @@ void gl33_unref_texture_from_samplers(Texture *tex) {
}
}
void gl33_uniforms_handle_texture_pointer_renamed(Texture *pold, Texture *pnew) {
for(Uniform *u = sampler_uniforms; u; u = u->next) {
assert(UNIFORM_TYPE_IS_SAMPLER(u->type));
for(Texture **slot = u->textures; slot < u->textures + u->array_size; ++slot) {
assert(slot != NULL);
if(*slot == pold) {
*slot = pnew;
}
}
}
}
static void print_info_log(GLuint prog) {
GLint len = 0, alen = 0;
glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &len);
@ -561,3 +578,113 @@ void gl33_shader_program_set_debug_label(ShaderProgram *prog, const char *label)
const char *gl33_shader_program_get_debug_label(ShaderProgram *prog) {
return prog->debug_label;
}
bool gl33_shader_program_transfer(ShaderProgram *dst, ShaderProgram *src) {
ht_ptr2ptr_t old_new_map; // bidirectional
ht_create(&old_new_map);
ht_str2ptr_iter_t iter;
ht_iter_begin(&src->uniforms, &iter);
bool fail = false;
for(; iter.has_data; ht_iter_next(&iter)) {
Uniform *unew = NOT_NULL(iter.value);
Uniform *uold = ht_get(&dst->uniforms, iter.key, NULL);
if(!uold) {
continue;
}
if(unew->type != uold->type || unew->elem_size != uold->elem_size) {
log_error(
"Can't update shader program '%s': uniform %s changed type",
dst->debug_label, iter.key
);
fail = true;
break;
}
ht_set(&old_new_map, uold, unew);
ht_set(&old_new_map, unew, uold);
}
ht_iter_end(&iter);
if(fail) {
ht_destroy(&old_new_map);
gl33_shader_program_destroy(src);
return false;
}
// Update existing uniforms
ht_iter_begin(&dst->uniforms, &iter);
for(; iter.has_data; ht_iter_next(&iter)) {
Uniform *uold = NOT_NULL(iter.value);
Uniform *unew = ht_get(&old_new_map, uold, NULL);
free(uold->textures);
free(uold->cache.pending);
free(uold->cache.commited);
if(unew) {
uold->textures = unew->textures;
assert(uold->elem_size == unew->elem_size);
uold->array_size = unew->array_size;
uold->location = unew->location;
assert(uold->type == unew->type);
uold->size_uniform = ht_get(&old_new_map, unew->size_uniform, unew->size_uniform);
uold->cache = unew->cache;
if(UNIFORM_TYPE_IS_SAMPLER(unew->type)) {
list_unlink(&sampler_uniforms, unew);
}
ht_unset(&src->uniforms, iter.key);
free(unew);
} else {
// Deactivate, but keep the object around, because user code may be referencing it.
// We also need to keep type information, in case the uniform gets re-introduced.
uold->location = INVALID_UNIFORM_LOCATION;
uold->size_uniform = NULL;
uold->array_size = 0;
uold->textures = NULL;
uold->cache.pending = NULL;
uold->cache.commited = NULL;
}
}
ht_iter_end(&iter);
// Add new uniforms
ht_iter_begin(&src->uniforms, &iter);
for(; iter.has_data; ht_iter_next(&iter)) {
Uniform *unew = NOT_NULL(iter.value);
assert(ht_get(&old_new_map, unew, NULL) == NULL);
assert(ht_get(&dst->uniforms, iter.key, NULL) == NULL);
unew->prog = dst;
unew->size_uniform = ht_get(&old_new_map, unew->size_uniform, unew->size_uniform);
ht_set(&dst->uniforms, iter.key, unew);
}
ht_iter_end(&iter);
dst->gl_handle = src->gl_handle;
memcpy(dst->debug_label, src->debug_label, sizeof(dst->debug_label));
for(int i = 0; i < ARRAY_SIZE(dst->magic_uniforms); ++i) {
Uniform *unew = src->magic_uniforms[i];
dst->magic_uniforms[i] = ht_get(&old_new_map, unew, unew);
}
ht_destroy(&old_new_map);
ht_destroy(&src->uniforms);
free(src);
return true;
}

View file

@ -36,6 +36,8 @@ struct ShaderProgram {
char debug_label[R_DEBUG_LABEL_SIZE];
};
#define INVALID_UNIFORM_LOCATION 0xffffffff
struct Uniform {
// these are for sampler uniforms
LIST_INTERFACE(Uniform);
@ -71,3 +73,5 @@ Uniform *gl33_shader_uniform(ShaderProgram *prog, const char *uniform_name, hash
UniformType gl33_uniform_type(Uniform *uniform);
void gl33_uniform(Uniform *uniform, uint offset, uint count, const void *data);
void gl33_unref_texture_from_samplers(Texture *tex);
void gl33_uniforms_handle_texture_pointer_renamed(Texture *pold, Texture *pnew);
bool gl33_shader_program_transfer(ShaderProgram *dst, ShaderProgram *src);

View file

@ -622,3 +622,12 @@ bool gl33_texture_dump(Texture *tex, uint mipmap, uint layer, Pixmap *dst) {
return true;
#endif
}
bool gl33_texture_transfer(Texture *dst, Texture *src) {
gl33_texture_deleted(dst);
glDeleteTextures(1, &dst->gl_handle);
*dst = *src;
gl33_texture_pointer_renamed(src, dst);
free(src);
return true;
}

View file

@ -44,3 +44,4 @@ void gl33_texture_destroy(Texture *tex);
bool gl33_texture_type_query(TextureType type, TextureFlags flags, PixmapFormat pxfmt, PixmapOrigin pxorigin, TextureTypeQueryResult *result);
bool gl33_texture_sampler_compatible(Texture *tex, UniformType sampler_type) attr_nonnull(1);
bool gl33_texture_dump(Texture *tex, uint mipmap, uint layer, Pixmap *dst);
bool gl33_texture_transfer(Texture *dst, Texture *src);

View file

@ -46,11 +46,13 @@ static ShaderObject* null_shader_object_compile(ShaderSource *source) { return (
static void null_shader_object_destroy(ShaderObject *shobj) { }
static void null_shader_object_set_debug_label(ShaderObject *shobj, const char *label) { }
static const char* null_shader_object_get_debug_label(ShaderObject *shobj) { return "Null shader object"; }
static bool null_shader_object_transfer(ShaderObject *dst, ShaderObject *src) { return true; }
static ShaderProgram* null_shader_program_link(uint num_objects, ShaderObject *shobjs[num_objects]) { return (void*)&placeholder; }
static void null_shader_program_destroy(ShaderProgram *prog) { }
static void null_shader_program_set_debug_label(ShaderProgram *prog, const char *label) { }
static const char* null_shader_program_get_debug_label(ShaderProgram *prog) { return "Null shader program"; }
static bool null_shader_program_transfer(ShaderProgram *dst, ShaderProgram *src) { return true; }
static void null_shader(ShaderProgram *prog) { }
static ShaderProgram* null_shader_current(void) { return (void*)&placeholder; }
@ -101,6 +103,7 @@ static bool null_texture_type_query(TextureType type, TextureFlags flags, Pixmap
return true;
}
static bool null_texture_transfer(Texture *dst, Texture *src) { return true; }
static FloatRect default_fb_viewport = { 0, 0, 800, 600 };
@ -208,10 +211,12 @@ RendererBackend _r_backend_null = {
.shader_object_destroy = null_shader_object_destroy,
.shader_object_set_debug_label = null_shader_object_set_debug_label,
.shader_object_get_debug_label = null_shader_object_get_debug_label,
.shader_object_transfer = null_shader_object_transfer,
.shader_program_link = null_shader_program_link,
.shader_program_destroy = null_shader_program_destroy,
.shader_program_set_debug_label = null_shader_program_set_debug_label,
.shader_program_get_debug_label = null_shader_program_get_debug_label,
.shader_program_transfer = null_shader_program_transfer,
.shader = null_shader,
.shader_current = null_shader_current,
.shader_uniform = null_shader_uniform,
@ -231,6 +236,7 @@ RendererBackend _r_backend_null = {
.texture_dump = null_texture_dump,
.texture_clear = null_texture_clear,
.texture_type_query = null_texture_type_query,
.texture_transfer = null_texture_transfer,
.framebuffer_create = null_framebuffer_create,
.framebuffer_get_debug_label = null_framebuffer_get_debug_label,
.framebuffer_set_debug_label = null_framebuffer_set_debug_label,

View file

@ -13,6 +13,7 @@
static char *material_path(const char *basename);
static bool material_check_path(const char *path);
static void material_load_stage1(ResourceLoadState *st);
static bool material_transfer(void *dst, void *src);
ResourceHandler material_res_handler = {
.type = RES_MATERIAL,
@ -23,6 +24,7 @@ ResourceHandler material_res_handler = {
.find = material_path,
.check = material_check_path,
.load = material_load_stage1,
.transfer = material_transfer,
.unload = free,
},
};
@ -104,7 +106,8 @@ static void material_load_stage1(ResourceLoadState *st) {
#define LOADMAP(_map_) do { \
if(ld->_map_##_map) { \
ld->mat->_map_##_map = get_resource_data(RES_TEXTURE, ld->_map_##_map, st->flags); \
ld->mat->_map_##_map = get_resource_data( \
RES_TEXTURE, ld->_map_##_map, st->flags & ~RESF_RELOAD); \
if(UNLIKELY(ld->mat->_map_##_map == NULL)) { \
log_error("%s: failed to load " #_map_ " map '%s'", st->name, ld->_map_##_map); \
free_mat_load_data(ld); \
@ -127,3 +130,11 @@ static void material_load_stage2(ResourceLoadState *st) {
res_load_finished(st, ld->mat);
free_mat_load_data(ld);
}
static bool material_transfer(void *dst, void *src) {
PBRMaterial *mdst = dst;
PBRMaterial *msrc = src;
*mdst = *msrc;
free(msrc);
return true;
}

View file

@ -65,7 +65,11 @@ struct InternalResource {
SDL_mutex *mutex;
SDL_cond *cond;
InternalResLoadState *load;
char *name;
ResourceStatus status;
bool is_transient_reloader;
InternalResource *reload_buddy;
};
struct InternalResLoadState {
@ -89,14 +93,72 @@ static struct {
uchar no_unload : 1;
uchar preload_required : 1;
} env;
SDL_SpinLock ires_freelist_lock;
InternalResource *ires_freelist;
} res_gstate;
INLINE ResourceHandler *get_handler(ResourceType type) {
return _handlers[type];
}
INLINE ResourceHandler *get_ires_handler(InternalResource *ires) {
return get_handler(ires->res.type);
}
static inline InternalResLoadState *loadstate_internal(ResourceLoadState *st) {
return UNION_CAST(ResourceLoadState*, InternalResLoadState*, st);
}
static InternalResource *preload_resource_internal(ResourceType type, const char *name, ResourceFlags flags);
attr_returns_nonnull
static InternalResource *ires_alloc(ResourceType rtype) {
SDL_AtomicLock(&res_gstate.ires_freelist_lock);
InternalResource *ires = res_gstate.ires_freelist;
if(ires) {
res_gstate.ires_freelist = ires->reload_buddy;
}
SDL_AtomicUnlock(&res_gstate.ires_freelist_lock);
if(!ires) {
ires = calloc(1, sizeof(*ires));
ires->mutex = SDL_CreateMutex();
ires->cond = SDL_CreateCond();
}
ires->res.type = rtype;
return ires;
}
attr_nonnull_all
static void ires_release(InternalResource *ires) {
*ires = (InternalResource) {
.mutex = ires->mutex,
.cond = ires->cond,
};
SDL_AtomicLock(&res_gstate.ires_freelist_lock);
ires->reload_buddy = res_gstate.ires_freelist;
res_gstate.ires_freelist = ires;
SDL_AtomicUnlock(&res_gstate.ires_freelist_lock);
}
attr_nonnull_all
static void ires_free(InternalResource *ires) {
SDL_DestroyMutex(ires->mutex);
SDL_DestroyCond(ires->cond);
free(ires);
}
attr_nonnull_all attr_returns_nonnull
static InternalResource *ires_get_persistent(InternalResource *ires) {
if(ires->is_transient_reloader) {
return NOT_NULL(ires->reload_buddy);
}
return ires;
}
void res_load_failed(ResourceLoadState *st) {
InternalResLoadState *ist = loadstate_internal(st);
ist->status = LOAD_FAILED;
@ -131,20 +193,14 @@ void res_load_dependency(ResourceLoadState *st, ResourceType type, const char *n
SDL_UnlockMutex(ires->mutex);
}
static inline ResourceHandler *get_handler(ResourceType type) {
return *(_handlers + type);
}
static inline ResourceHandler *get_ires_handler(InternalResource *ires) {
return get_handler(ires->res.type);
}
static void alloc_handler(ResourceHandler *h) {
INLINE void alloc_handler(ResourceHandler *h) {
assert(h != NULL);
ht_create(&h->private.mapping);
}
static const char *type_name(ResourceType type) {
INLINE const char *type_name(ResourceType type) {
return get_handler(type)->typename;
}
@ -154,14 +210,7 @@ struct valfunc_arg {
static void *valfunc_begin_load_resource(void* arg) {
ResourceType type = ((struct valfunc_arg*)arg)->type;
InternalResource *ires = calloc(1, sizeof(InternalResource));
ires->res.type = type;
ires->status = RES_STATUS_LOADING;
ires->mutex = SDL_CreateMutex();
ires->cond = SDL_CreateCond();
return ires;
return ires_alloc(type);
}
static bool try_begin_load_resource(ResourceType type, const char *name, hash_t hash, InternalResource **out_ires) {
@ -193,7 +242,7 @@ static ResourceStatus pump_or_wait_for_resource_load(InternalResource *ires, uin
Task *task = load_state->async_task;
if(task) {
// If there's an async load task for this resource, wait for it for complete.
// If there's an async load task for this resource, wait for it to complete.
// If it's not yet running, it will be offloaded to this thread instead.
load_state->async_task = NULL;
@ -213,15 +262,23 @@ static ResourceStatus pump_or_wait_for_resource_load(InternalResource *ires, uin
if(load_state) {
ResourceStatus dep_status = pump_dependencies(load_state);
if(load_state->ready_to_finalize && is_main_thread()) {
// Resource has finished async load, but needs to be finalized on the main thread.
// Since we are the main thread, we can do it here.
if(!pump_only && is_main_thread()) {
if(dep_status == RES_STATUS_LOADING) {
dep_status = wait_for_dependencies(load_state);
wait_for_dependencies(load_state);
}
while(!load_state->ready_to_finalize) {
SDL_CondWait(ires->cond, ires->mutex);
}
// May have been finalized while we were sleeping
// Can happen if load fails
load_state = ires->load;
if(load_state) {
load_resource_finish(load_state);
}
load_resource_finish(load_state);
assert(ires->status != RES_STATUS_LOADING);
}
}
@ -237,13 +294,19 @@ static ResourceStatus pump_or_wait_for_resource_load(InternalResource *ires, uin
ResourceStatus status = ires->status;
if(status == RES_STATUS_LOADED) {
uint32_t missing_flags = want_flags & ~ires->res.flags;
uint32_t missing_flags = (want_flags & ~ires->res.flags) & RESF_PROMOTABLE_FLAGS;
if(missing_flags) {
uint32_t new_flags = ires->res.flags | want_flags;
log_debug("Flags for %s at %p promoted from 0x%08x to 0x%08x", type_name(ires->res.type), (void*)ires, ires->res.flags, new_flags);
ires->res.flags = new_flags;
}
if((want_flags & RESF_RELOAD) && ires->reload_buddy && !ires->is_transient_reloader) {
InternalResource *r = ires->reload_buddy;
SDL_UnlockMutex(ires->mutex);
return pump_or_wait_for_resource_load(r, want_flags & ~RESF_RELOAD, pump_only);
}
}
SDL_UnlockMutex(ires->mutex);
@ -284,16 +347,22 @@ static ResourceStatus pump_or_wait_for_dependencies(InternalResLoadState *st, bo
return dep_status;
}
#define PROTECT_FLAGS(ist, ...) do { \
attr_unused InternalResLoadState *_ist = (ist); \
attr_unused ResourceFlags _orig_flags = _ist->st.flags; \
{ __VA_ARGS__; } \
assert(_ist->st.flags == _orig_flags); \
} while(0)
static void *load_resource_async_task(void *vdata) {
InternalResLoadState *st = vdata;
InternalResource *ires = st->ires;
assume(st == ires->load);
SDL_LockMutex(ires->mutex);
ResourceHandler *h = get_ires_handler(ires);
st->status = LOAD_NONE;
h->procs.load(&st->st);
PROTECT_FLAGS(st, h->procs.load(&st->st));
retry:
switch(st->status) {
@ -308,7 +377,7 @@ retry:
}
st->status = LOAD_NONE;
st->continuation(&st->st);
PROTECT_FLAGS(st, st->continuation(&st->st));
goto retry;
} else {
dep_status = pump_dependencies(st);
@ -316,11 +385,12 @@ retry:
if(dep_status == RES_STATUS_LOADING) {
st->status = LOAD_CONT_ON_MAIN;
st->ready_to_finalize = true;
SDL_CondBroadcast(ires->cond);
events_emit(TE_RESOURCE_ASYNC_LOADED, 0, ires, NULL);
break;
} else {
st->status = LOAD_NONE;
st->continuation(&st->st);
PROTECT_FLAGS(st, st->continuation(&st->st));
goto retry;
}
}
@ -331,14 +401,17 @@ retry:
case LOAD_CONT_ON_MAIN:
if(pump_dependencies(st) == RES_STATUS_LOADING || !is_main_thread()) {
st->ready_to_finalize = true;
SDL_CondBroadcast(ires->cond);
events_emit(TE_RESOURCE_ASYNC_LOADED, 0, ires, NULL);
break;
}
// fallthrough
case LOAD_OK:
case LOAD_FAILED:
SDL_LockMutex(ires->mutex);
st->ready_to_finalize = true;
load_resource_finish(st);
SDL_UnlockMutex(ires->mutex);
st = NULL;
break;
@ -347,7 +420,6 @@ retry:
}
assume(ires->load == st);
SDL_UnlockMutex(ires->mutex);
return st;
}
@ -365,9 +437,8 @@ static void unload_resource(InternalResource *ires) {
SDL_PumpEvents();
SDL_FilterEvents(filter_asyncload_event, ires);
SDL_DestroyCond(ires->cond);
SDL_DestroyMutex(ires->mutex);
free(ires);
free(ires->name);
ires_release(ires);
}
static char *get_name_from_path(ResourceHandler *handler, const char *path) {
@ -378,7 +449,11 @@ static char *get_name_from_path(ResourceHandler *handler, const char *path) {
return resource_util_basename(handler->subdir, path);
}
static bool should_defer_load(void) {
static bool should_defer_load(InternalResLoadState *st) {
if(st->st.flags & RESF_RELOAD) {
return false;
}
FrameTimes ft = eventloop_get_frame_times();
if(ft.next != res_gstate.frame_threshold) {
@ -409,19 +484,20 @@ static bool resource_asyncload_handler(SDL_Event *evt, void *arg) {
return true;
}
#if 1
if(should_defer_load()) {
if(should_defer_load(st)) {
events_defer(evt);
return true;
}
#endif
SDL_LockMutex(ires->mutex);
ResourceStatus dep_status = pump_dependencies(st);
if(dep_status == RES_STATUS_LOADING) {
log_debug("Deferring %s '%s' because some dependencies are not satisfied", type_name(ires->res.type), st->st.name);
// log_debug("Deferring %s '%s' because some dependencies are not satisfied", type_name(ires->res.type), st->st.name);
// FIXME: Make this less braindead.
// This will retry every frame until dependencies are satisfied.
SDL_UnlockMutex(ires->mutex);
events_defer(evt);
return true;
@ -492,6 +568,13 @@ static void load_resource(InternalResource *ires, const char *name, ResourceFlag
flags |= RESF_OPTIONAL;
}
if(ires->name == NULL) {
ires->name = strdup(name);
} else {
name = ires->name;
assume(flags & RESF_RELOAD);
}
path = handler->procs.find(name);
if(path) {
@ -523,7 +606,7 @@ static void load_resource(InternalResource *ires, const char *name, ResourceFlag
load_resource_async(&st);
} else {
st.status = LOAD_NONE;
handler->procs.load(&st.st);
PROTECT_FLAGS(&st, handler->procs.load(&st.st));
retry: switch(st.status) {
case LOAD_OK:
@ -535,13 +618,55 @@ static void load_resource(InternalResource *ires, const char *name, ResourceFlag
case LOAD_CONT_ON_MAIN:
wait_for_dependencies(&st);
st.status = LOAD_NONE;
st.continuation(&st.st);
PROTECT_FLAGS(&st, st.continuation(&st.st));
goto retry;
default: UNREACHABLE;
}
}
}
static bool reload_resource(InternalResource *ires, ResourceFlags flags, bool async) {
ResourceHandler *handler = get_ires_handler(ires);
const char *typename = type_name(handler->type);
if(!handler->procs.transfer) {
log_debug("Can't reload %s '%s'", typename, ires->name);
return false;
}
flags |= RESF_RELOAD | RESF_OPTIONAL;
SDL_LockMutex(ires->mutex);
assert(!ires->is_transient_reloader);
if(ires->status != RES_STATUS_LOADED) {
SDL_UnlockMutex(ires->mutex);
log_warn("Tried to reload %s '%s' that is not loaded", typename, ires->name);
return false;
}
if(ires->reload_buddy) {
SDL_UnlockMutex(ires->mutex);
log_warn("Tried to reload %s '%s' that is currently reloading", typename, ires->name);
return false;
}
log_info("Reloading %s '%s'", typename, ires->name);
InternalResource *transient = ires_alloc(ires->res.type);
transient->name = ires->name;
transient->status = RES_STATUS_LOADING;
transient->reload_buddy = ires;
transient->is_transient_reloader = true;
ires->reload_buddy = transient;
load_resource(transient, ires->name, flags, async);
SDL_UnlockMutex(ires->mutex);
return true;
}
static void load_resource_finish(InternalResLoadState *st) {
void *raw = NULL;
InternalResource *ires = st->ires;
@ -615,6 +740,34 @@ static void load_resource_finish(InternalResLoadState *st) {
SDL_CondBroadcast(ires->cond);
assert(ires->status != RES_STATUS_LOADING);
// handle reload
InternalResource *persistent = ires_get_persistent(ires);
if(persistent == ires) {
return;
}
ResourceHandler *handler = get_ires_handler(ires);
assert(handler->procs.transfer != NULL);
SDL_LockMutex(persistent->mutex);
if(
!ires->res.data ||
!handler->procs.transfer(NOT_NULL(persistent->res.data), ires->res.data)
) {
log_error("Failed to reload %s '%s'", typename, persistent->name);
}
persistent->reload_buddy = NULL;
persistent->res.flags |= ires->res.flags & ~(RESF_RELOAD | RESF_OPTIONAL);
assert(persistent->status == RES_STATUS_LOADED);
SDL_CondBroadcast(persistent->cond);
SDL_UnlockMutex(persistent->mutex);
ires_release(ires);
}
Resource *_get_resource(ResourceType type, const char *name, hash_t hash, ResourceFlags flags) {
@ -622,6 +775,8 @@ Resource *_get_resource(ResourceType type, const char *name, hash_t hash, Resour
Resource *res;
if(try_begin_load_resource(type, name, hash, &ires)) {
flags &= ~RESF_RELOAD;
SDL_LockMutex(ires->mutex);
if(!(flags & RESF_PRELOAD)) {
@ -646,8 +801,11 @@ Resource *_get_resource(ResourceType type, const char *name, hash_t hash, Resour
SDL_UnlockMutex(ires->mutex);
return res;
} else {
uint32_t promotion_flags = flags & RESF_PERMANENT;
ResourceStatus status = wait_for_resource_load(ires, promotion_flags);
if(flags & RESF_RELOAD) {
reload_resource(ires, flags, false);
}
ResourceStatus status = wait_for_resource_load(ires, flags);
if(status == RES_STATUS_FAILED) {
return NULL;
@ -677,6 +835,8 @@ static InternalResource *preload_resource_internal(ResourceType type, const char
SDL_LockMutex(ires->mutex);
load_resource(ires, name, flags, !res_gstate.env.no_async_load);
SDL_UnlockMutex(ires->mutex);
} else if(flags & RESF_RELOAD) {
reload_resource(ires, flags, !res_gstate.env.no_async_load);
}
return ires;
@ -699,6 +859,29 @@ void preload_resources(ResourceType type, ResourceFlags flags, const char *first
va_end(args);
}
static void reload_resources(ResourceHandler *h) {
if(!h->procs.transfer) {
return;
}
ht_str2ptr_ts_iter_t iter;
ht_iter_begin(&h->private.mapping, &iter);
for(; iter.has_data; ht_iter_next(&iter)) {
reload_resource(iter.value, 0, !res_gstate.env.no_async_load);
}
ht_iter_end(&iter);
}
void reload_all_resources(void) {
for(uint i = 0; i < RES_NUMTYPES; ++i) {
ResourceHandler *h = get_handler(i);
assert(h != NULL);
reload_resources(h);
}
}
void init_resources(void) {
res_gstate.env.no_async_load = env_get("TAISEI_NOASYNC", false);
res_gstate.env.no_preload = env_get("TAISEI_NOPRELOAD", false);
@ -897,6 +1080,14 @@ void free_resources(bool all) {
return;
}
for(InternalResource *ires = res_gstate.ires_freelist; ires;) {
InternalResource *next = ires->reload_buddy;
ires_free(ires);
ires = next;
}
res_gstate.ires_freelist = NULL;
if(!res_gstate.env.no_async_load) {
events_unregister_handler(resource_asyncload_handler);
}

View file

@ -28,10 +28,12 @@ typedef enum ResourceType {
typedef enum ResourceFlags {
RESF_OPTIONAL = 1,
RESF_PERMANENT = 2,
RESF_PERMANENT = 2, // TODO get rid of this cancer
RESF_PRELOAD = 4,
RESF_RELOAD = 8,
RESF_DEFAULT = 0,
RESF_PROMOTABLE_FLAGS = RESF_PERMANENT, // TODO get rid of this cancer
} ResourceFlags;
typedef struct ResourceLoadState ResourceLoadState;
@ -47,7 +49,7 @@ struct ResourceLoadState {
// The path may not actually exist or be usable. The load function (see below) shall deal with such cases.
// The returned path must be free()'d.
// May return NULL on failure, but does not have to.
typedef char* (*ResourceFindProc)(const char *name);
typedef char *(*ResourceFindProc)(const char *name);
// Tells whether the resource handler should attempt to load a file, specified by a vfs path.
typedef bool (*ResourceCheckProc)(const char *path);
@ -56,6 +58,15 @@ typedef bool (*ResourceCheckProc)(const char *path);
// Must call one of the following res_load_* functions before returning to indicate status.
typedef void (*ResourceLoadProc)(ResourceLoadState *st);
// Makes `dst` refer to the resource represented by `src`.
// `src` may no longer be a valid reference to the resource after this operation.
// Resource previously represented by `dst` is destroyed in the process.
// Called on the main thread, after a resource reload successfully completes.
//
// Returns true if the operation succeeds.
// In case of failure, `dst` must not be modified, and `src` must be destroyed.
typedef bool (*ResourceTransferProc)(void *dst, void *src);
void res_load_failed(ResourceLoadState *st) attr_nonnull(1);
void res_load_finished(ResourceLoadState *st, void *res) attr_nonnull(1, 2);
@ -96,6 +107,7 @@ typedef struct ResourceHandler {
ResourceCheckProc check;
ResourceLoadProc load;
ResourceUnloadProc unload;
ResourceTransferProc transfer;
ResourceInitProc init;
ResourcePostInitProc post_init;
ResourceShutdownProc shutdown;
@ -115,6 +127,7 @@ typedef struct Resource {
void init_resources(void);
void load_resources(void);
void free_resources(bool all);
void reload_all_resources(void);
Resource *_get_resource(ResourceType type, const char *name, hash_t hash, ResourceFlags flags) attr_nonnull_all;
void *_get_resource_data(ResourceType type, const char *name, hash_t hash, ResourceFlags flags) attr_nonnull_all;

View file

@ -33,7 +33,7 @@ static struct shobj_type shobj_type_table[] = {
{ NULL }
};
static struct shobj_type* get_shobj_type(const char *name) {
static struct shobj_type *get_shobj_type(const char *name) {
for(struct shobj_type *type = shobj_type_table; type->ext; ++type) {
if(strendswith(name, type->ext)) {
return type;
@ -43,7 +43,7 @@ static struct shobj_type* get_shobj_type(const char *name) {
return NULL;
}
static char* shader_object_path(const char *name) {
static char *shader_object_path(const char *name) {
char *path = NULL;
for(const char *const *ext = shobj_exts; *ext; ++ext) {
@ -163,6 +163,10 @@ static void unload_shader_object(void *vsha) {
r_shader_object_destroy(vsha);
}
static bool transfer_shader_object(void *dst, void *src) {
return r_shader_object_transfer(dst, src);
}
ResourceHandler shader_object_res_handler = {
.type = RES_SHADER_OBJECT,
.typename = "shader object",
@ -175,5 +179,6 @@ ResourceHandler shader_object_res_handler = {
.check = check_shader_object_path,
.load = load_shader_object_stage1,
.unload = unload_shader_object,
.transfer = transfer_shader_object,
},
};

View file

@ -78,7 +78,7 @@ static void load_shader_program_stage2(ResourceLoadState *st) {
char *objname = ldata.objlist;
for(int i = 0; i < ldata.num_objects; ++i) {
if(!(objs[i] = get_resource_data(RES_SHADER_OBJECT, objname, st->flags))) {
if(!(objs[i] = get_resource_data(RES_SHADER_OBJECT, objname, st->flags & ~RESF_RELOAD))) {
log_error("%s: couldn't load shader object '%s'", st->path, objname);
free(ldata.objlist);
res_load_failed(st);
@ -104,6 +104,10 @@ static void unload_shader_program(void *vprog) {
r_shader_program_destroy(vprog);
}
static bool transfer_shader_program(void *dst, void *src) {
return r_shader_program_transfer(dst, src);
}
ResourceHandler shader_program_res_handler = {
.type = RES_SHADER_PROGRAM,
.typename = "shader program",
@ -114,5 +118,6 @@ ResourceHandler shader_program_res_handler = {
.check = check_shader_program_path,
.load = load_shader_program_stage1,
.unload = unload_shader_program,
.transfer = transfer_shader_program,
},
};

View file

@ -45,7 +45,6 @@ static void load_sprite_stage1(ResourceLoadState *st) {
if(texture_res_handler.procs.check(st->path)) {
state->texture_name = strdup(st->name);
// preload_resource(RES_TEXTURE, state->texture_name, st->flags);
res_load_dependency(st, RES_TEXTURE, state->texture_name);
res_load_continue_after_dependencies(st, load_sprite_stage2, state);
return;
@ -97,7 +96,7 @@ static void load_sprite_stage2(ResourceLoadState *st) {
struct sprite_load_state *state = NOT_NULL(st->opaque);
Sprite *spr = NOT_NULL(state->spr);
spr->tex = get_resource_data(RES_TEXTURE, state->texture_name, st->flags);
spr->tex = get_resource_data(RES_TEXTURE, state->texture_name, st->flags & ~RESF_RELOAD);
free(state->texture_name);
free(state);
@ -200,6 +199,12 @@ void sprite_set_denormalized_tex_coords(Sprite *restrict spr, FloatRect tc) {
spr->tex_area.h = tc.h / tex_h;
}
static bool transfer_sprite(void *dst, void *src) {
*(Sprite*)dst = *(Sprite*)src;
free(src);
return true;
}
ResourceHandler sprite_res_handler = {
.type = RES_SPRITE,
.typename = "sprite",
@ -210,5 +215,6 @@ ResourceHandler sprite_res_handler = {
.check = check_sprite_path,
.load = load_sprite_stage1,
.unload = free,
.transfer = transfer_sprite,
},
};

View file

@ -12,6 +12,11 @@
#include "global.h"
#include "video.h"
#include "renderer/api.h"
static bool texture_transfer(void *dst, void *src) {
return r_texture_transfer(dst, src);
}
ResourceHandler texture_res_handler = {
.type = RES_TEXTURE,
@ -23,6 +28,7 @@ ResourceHandler texture_res_handler = {
.check = texture_loader_check_path,
.load = texture_loader_stage1,
.unload = texture_loader_unload,
.transfer = texture_transfer,
},
};