Various fixes & improvements for concurrent loading (#235)

- RESF_UNSAFE is removed.
- Resources that don't have to be finalized on the main thread can load
completely asynchronously.
- A thread waiting for a concurrent task to complete can start executing
that task itself if it hasn't started yet.
- Refactor the resource loading interface, add support for load-time
dependencies.
- Main-thread finalization of asynchronously loaded resources is now
spread out across multiple frames to mitigate frametime spikes.
- Remove some archaisms from the resource management code.
- Fix potential hashtable synchronization issue.
- Fix some deadlock edge cases.
- Don't spawn more worker threads than there are CPU cores (degrades
performance).
- Add TAISEI_AGGRESSIVE_PRELOAD env variable to attempt to aggressively
discover and preload every possible resource.
- Make r_texture_fill{,_region} expect optimal pixmaps, so that it's
never forced to convert them on the main thread. The optimal format may
be queried with the new r_texture_optimal_pixmap_format_for_type API.
These functions will also no longer needlessly copy the entire image
into a staging buffer - previously they did this even if no conversion
was needed.
- Other random changes to facilitate the stuff above.

The overall effect is somewhat faster load times.

Of course it's still all terrible and full of lock contention because I
suck at concurrent programming, but it's not worse than it was.
Probably.
This commit is contained in:
Andrei Alexeyev 2020-06-09 03:01:53 +03:00 committed by GitHub
parent 8692a1a05c
commit ae8194ae78
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 922 additions and 593 deletions

View file

@ -52,6 +52,10 @@ void eventloop_leave(void) {
}
}
FrameTimes eventloop_get_frame_times(void) {
return evloop.frame_times;
}
LogicFrameAction run_logic_frame(LoopFrame *frame) {
assert(frame == evloop.stack_ptr);

View file

@ -11,6 +11,8 @@
#include "taisei.h"
#include "hirestime.h"
typedef enum RenderFrameAction {
RFRAME_SWAP,
RFRAME_DROP,
@ -26,6 +28,12 @@ typedef LogicFrameAction (*LogicFrameFunc)(void *context);
typedef RenderFrameAction (*RenderFrameFunc)(void *context);
typedef void (*PostLoopFunc)(void *context);
typedef struct FrameTimes {
hrtime_t target;
hrtime_t start;
hrtime_t next;
} FrameTimes;
#ifdef DEBUG_CALLCHAIN
#include "util/debug.h"
#include "log.h"
@ -64,6 +72,8 @@ void eventloop_enter(
void eventloop_run(void);
FrameTimes eventloop_get_frame_times(void);
#ifdef DEBUG_CALLCHAIN
INLINE attr_nonnull(1) void run_call_chain(CallChain *cc, void *result, DebugInfo caller_dbg) {
if(cc->callback != NULL) {

View file

@ -30,14 +30,9 @@ struct LoopFrame {
extern struct evloop_s {
LoopFrame stack[EVLOOP_STACK_SIZE];
LoopFrame *stack_ptr;
FrameTimes frame_times;
} evloop;
typedef struct FrameTimes {
hrtime_t target;
hrtime_t start;
hrtime_t next;
} FrameTimes;
void eventloop_leave(void);
LogicFrameAction run_logic_frame(LoopFrame *frame);

View file

@ -14,7 +14,6 @@
#include <emscripten.h>
static FrameTimes frame_times;
static uint frame_num;
static bool em_handle_resize_event(SDL_Event *event, void *arg);
@ -28,23 +27,23 @@ static void em_loop_callback(void) {
return;
}
if(time_get() < frame_times.next) {
if(time_get() < evloop.frame_times.next) {
return;
}
frame_times.start = time_get();
frame_times.target = frame->frametime;
evloop.frame_times.start = time_get();
evloop.frame_times.target = frame->frametime;
frame_times.next += frame_times.target;
hrtime_t min_next_time = frame_times.start - 2 * frame_times.target;
evloop.frame_times.next += evloop.frame_times.target;
hrtime_t min_next_time = evloop.frame_times.start - 2 * evloop.frame_times.target;
if(min_next_time > frame_times.next) {
frame_times.next = min_next_time;
if(min_next_time > evloop.frame_times.next) {
evloop.frame_times.next = min_next_time;
}
global.fps.busy.last_update_time = frame_times.start;
global.fps.busy.last_update_time = evloop.frame_times.start;
LogicFrameAction lframe_action = handle_logic(&frame, &frame_times);
LogicFrameAction lframe_action = handle_logic(&frame, &evloop.frame_times);
if(!frame || lframe_action == LFRAME_STOP) {
return;
@ -72,7 +71,7 @@ static bool em_handle_resize_event(SDL_Event *event, void *arg) {
}
void eventloop_run(void) {
frame_times.next = time_get();
evloop.frame_times.next = time_get();
emscripten_set_main_loop(em_loop_callback, 0, false);
update_vsync();

View file

@ -21,10 +21,9 @@ void eventloop_run(void) {
}
LoopFrame *frame = evloop.stack_ptr;
FrameTimes frame_times;
frame_times.target = frame->frametime;
frame_times.start = time_get();
frame_times.next = frame_times.start + frame_times.target;
evloop.frame_times.target = frame->frametime;
evloop.frame_times.start = time_get();
evloop.frame_times.next = evloop.frame_times.start + evloop.frame_times.target;
int32_t sleep = env_get("TAISEI_FRAMELIMITER_SLEEP", 3);
bool compensate = env_get("TAISEI_FRAMELIMITER_COMPENSATE", 1);
bool uncapped_rendering_env, uncapped_rendering;
@ -47,11 +46,11 @@ begin_main_loop:
}
#endif
frame_times.start = time_get();
evloop.frame_times.start = time_get();
begin_frame:
global.fps.busy.last_update_time = time_get();
frame_times.target = frame->frametime;
evloop.frame_times.target = frame->frametime;
++frame_num;
LogicFrameAction lframe_action = LFRAME_WAIT;
@ -59,21 +58,21 @@ begin_frame:
if(uncapped_rendering) {
attr_unused uint32_t logic_frames = 0;
while(lframe_action != LFRAME_STOP && frame_times.next < frame_times.start) {
lframe_action = handle_logic(&frame, &frame_times);
while(lframe_action != LFRAME_STOP && evloop.frame_times.next < evloop.frame_times.start) {
lframe_action = handle_logic(&frame, &evloop.frame_times);
if(!frame || lframe_action == LFRAME_STOP) {
goto begin_main_loop;
}
++logic_frames;
hrtime_t total = time_get() - frame_times.start;
hrtime_t total = time_get() - evloop.frame_times.start;
if(total > frame_times.target) {
frame_times.next = frame_times.start;
if(total > evloop.frame_times.target) {
evloop.frame_times.next = evloop.frame_times.start;
log_debug("Executing logic took too long (%"PRIuTIME"), giving up", total);
} else {
frame_times.next += frame_times.target;
evloop.frame_times.next += evloop.frame_times.target;
}
}
@ -86,7 +85,7 @@ begin_frame:
);
}
} else {
lframe_action = handle_logic(&frame, &frame_times);
lframe_action = handle_logic(&frame, &evloop.frame_times);
if(!frame || lframe_action == LFRAME_STOP) {
goto begin_main_loop;
@ -109,31 +108,31 @@ begin_frame:
}
#endif
frame_times.next = frame_times.start + frame_times.target;
evloop.frame_times.next = evloop.frame_times.start + evloop.frame_times.target;
if(compensate) {
hrtime_t rt = time_get();
if(rt > frame_times.next) {
if(rt > evloop.frame_times.next) {
// frame took too long...
// try to compensate in the next frame to avoid slowdown
frame_times.start = rt - imin(rt - frame_times.next, frame_times.target);
evloop.frame_times.start = rt - imin(rt - evloop.frame_times.next, evloop.frame_times.target);
goto begin_frame;
}
}
if(sleep > 0) {
// CAUTION: All of these casts are important!
while((shrtime_t)frame_times.next - (shrtime_t)time_get() > (shrtime_t)frame_times.target / sleep) {
while((shrtime_t)evloop.frame_times.next - (shrtime_t)time_get() > (shrtime_t)evloop.frame_times.target / sleep) {
uint32_t nap_multiplier = 1;
uint32_t nap_divisor = 3;
hrtime_t nap_raw = imax(0, (shrtime_t)frame_times.next - (shrtime_t)time_get());
hrtime_t nap_raw = imax(0, (shrtime_t)evloop.frame_times.next - (shrtime_t)time_get());
uint32_t nap_sdl = (nap_multiplier * nap_raw * 1000) / (HRTIME_RESOLUTION * nap_divisor);
nap_sdl = imax(nap_sdl, 1);
SDL_Delay(nap_sdl);
}
}
while(time_get() < frame_times.next);
while(time_get() < evloop.frame_times.next);
}
}

View file

@ -24,6 +24,7 @@ typedef LIST_ANCHOR(EventHandlerContainer) EventHandlerList;
static hrtime_t keyrepeat_paused_until;
static EventHandlerList global_handlers;
static int global_handlers_lock = 0;
static DYNAMIC_ARRAY(SDL_Event) deferred_events;
uint32_t sdl_first_user_event;
@ -51,6 +52,7 @@ void events_init(void) {
void events_shutdown(void) {
events_unregister_default_handlers();
dynarray_free_data(&deferred_events);
#ifdef DEBUG
if(global_handlers.first) {
@ -284,6 +286,12 @@ void events_poll(EventHandler *handlers, EventFlags flags) {
while(SDL_PollEvent(&event)) {
events_invoke_handlers(&event, global_handlers.first, handlers);
}
dynarray_foreach_elem(&deferred_events, SDL_Event *evt, {
SDL_PushEvent(evt);
});
deferred_events.num_elements = 0;
}
void events_emit(TaiseiEvent type, int32_t code, void *data1, void *data2) {
@ -304,6 +312,10 @@ void events_emit(TaiseiEvent type, int32_t code, void *data1, void *data2) {
SDL_PushEvent(&event);
}
void events_defer(SDL_Event *evt) {
*dynarray_append(&deferred_events) = *evt;
}
void events_pause_keyrepeat(void) {
// workaround for SDL bug
// https://bugzilla.libsdl.org/show_bug.cgi?id=3287

View file

@ -111,5 +111,6 @@ void events_register_handler(EventHandler *handler);
void events_unregister_handler(EventHandlerProc proc);
void events_poll(EventHandler *handlers, EventFlags flags);
void events_emit(TaiseiEvent type, int32_t code, void *data1, void *data2);
void events_defer(SDL_Event *evt);
#endif // IGUARD_events_h

View file

@ -720,7 +720,7 @@ HT_DECLARE_PRIV_FUNC(void, end_read, (HT_BASETYPE *ht)) {
SDL_LockMutex(ht->sync.mutex);
if(!--ht->sync.readers) {
SDL_CondSignal(ht->sync.cond);
SDL_CondBroadcast(ht->sync.cond);
}
SDL_UnlockMutex(ht->sync.mutex);

View file

@ -119,7 +119,7 @@ void log_set_gui_error_appendix(const char *message);
#if defined(DEBUG) && !defined(__EMSCRIPTEN__)
#define log_debug(...) log_custom(LOG_DEBUG, __VA_ARGS__)
#undef UNREACHABLE
#define UNREACHABLE log_fatal("This code should never be reached")
#define UNREACHABLE log_fatal("This code should never be reached (%s:%i)", __FILE__, __LINE__)
#else
#define log_debug(...)
#define LOG_NO_FILENAMES

View file

@ -31,8 +31,6 @@ attr_unused
static void taisei_shutdown(void) {
log_info("Shutting down");
taskmgr_global_shutdown();
if(!global.is_replay_verification) {
config_save();
progress_save();
@ -42,6 +40,7 @@ static void taisei_shutdown(void) {
free_all_refs();
free_resources(true);
taskmgr_global_shutdown();
audio_shutdown();
video_shutdown();
gamepad_shutdown();

View file

@ -381,6 +381,7 @@ void preload_char_menu(void) {
for(int i = 0; i < NUM_CHARACTERS; ++i) {
PlayerCharacter *pchar = plrchar_get(i);
portrait_preload_base_sprite(pchar->lower_name, NULL, RESF_PERMANENT);
preload_resource(RES_TEXTURE, pchar->menu_texture_name, RESF_PERMANENT);
}
char *p = (char*)facedefs;

View file

@ -450,6 +450,10 @@ void r_texture_destroy(Texture *tex) {
B.texture_destroy(tex);
}
PixmapFormat r_texture_optimal_pixmap_format_for_type(TextureType type, PixmapFormat src_format) {
return B.texture_optimal_pixmap_format_for_type(type, src_format);
}
Framebuffer* r_framebuffer_create(void) {
return B.framebuffer_create();
}

View file

@ -665,6 +665,7 @@ void r_texture_fill_region(Texture *tex, uint mipmap, uint x, uint y, const Pixm
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);
PixmapFormat r_texture_optimal_pixmap_format_for_type(TextureType type, PixmapFormat src_format);
Framebuffer* r_framebuffer_create(void);
const char* r_framebuffer_get_debug_label(Framebuffer *fb) attr_nonnull(1);
@ -811,17 +812,17 @@ void r_disable(RendererCapability cap) {
INLINE
ShaderProgram* r_shader_get(const char *name) {
return get_resource_data(RES_SHADER_PROGRAM, name, RESF_DEFAULT | RESF_UNSAFE);
return get_resource_data(RES_SHADER_PROGRAM, name, RESF_DEFAULT);
}
INLINE
ShaderProgram* r_shader_get_optional(const char *name) {
return get_resource_data(RES_SHADER_PROGRAM, name, RESF_OPTIONAL | RESF_UNSAFE);
return get_resource_data(RES_SHADER_PROGRAM, name, RESF_OPTIONAL);
}
INLINE
Texture* r_texture_get(const char *name) {
return get_resource_data(RES_TEXTURE, name, RESF_DEFAULT | RESF_UNSAFE);
return get_resource_data(RES_TEXTURE, name, RESF_DEFAULT);
}
#pragma GCC diagnostic push
@ -873,12 +874,12 @@ void r_clear(ClearBufferFlags flags, const Color *colorval, float depthval) {
INLINE attr_nonnull(1)
void r_draw_model(const char *model) {
r_draw_model_ptr(get_resource_data(RES_MODEL, model, RESF_UNSAFE), 0, 0);
r_draw_model_ptr(get_resource_data(RES_MODEL, model, RESF_DEFAULT), 0, 0);
}
INLINE attr_nonnull(1)
void r_draw_model_instanced(const char *model, uint instances, uint base_instance) {
r_draw_model_ptr(get_resource_data(RES_MODEL, model, RESF_UNSAFE), instances, base_instance);
r_draw_model_ptr(get_resource_data(RES_MODEL, model, RESF_DEFAULT), instances, base_instance);
}
INLINE

View file

@ -71,6 +71,7 @@ typedef struct RendererFuncs {
void (*texture_fill)(Texture *tex, uint mipmap, const Pixmap *image_data);
void (*texture_fill_region)(Texture *tex, uint mipmap, uint x, uint y, const Pixmap *image_data);
void (*texture_clear)(Texture *tex, const Color *clr);
PixmapFormat (*texture_optimal_pixmap_format_for_type)(TextureType type, PixmapFormat src_format);
Framebuffer* (*framebuffer_create)(void);
const char* (*framebuffer_get_debug_label)(Framebuffer *framebuffer);

View file

@ -1201,6 +1201,7 @@ RendererBackend _r_backend_gl33 = {
.texture_fill = gl33_texture_fill,
.texture_fill_region = gl33_texture_fill_region,
.texture_clear = gl33_texture_clear,
.texture_optimal_pixmap_format_for_type = gl33_texture_optimal_pixmap_format_for_type,
.framebuffer_create = gl33_framebuffer_create,
.framebuffer_destroy = gl33_framebuffer_destroy,
.framebuffer_attach = gl33_framebuffer_attach,

View file

@ -173,20 +173,19 @@ void gl33_texture_get_size(Texture *tex, uint mipmap, uint *width, uint *height)
}
}
static GLTextureFormatTuple *prepare_pixmap(Texture *tex, const Pixmap *px_in, Pixmap *px_out) {
GLTextureFormatTuple *fmt = glcommon_find_best_pixformat(tex->params.type, px_in->format);
pixmap_convert_alloc(px_in, px_out, fmt->px_fmt);
pixmap_flip_to_origin_inplace(px_out, PIXMAP_ORIGIN_BOTTOMLEFT);
return fmt;
PixmapFormat gl33_texture_optimal_pixmap_format_for_type(TextureType type, PixmapFormat src_format) {
return glcommon_find_best_pixformat(type, src_format)->px_fmt;
}
static void gl33_texture_set(Texture *tex, uint mipmap, const Pixmap *image) {
assert(mipmap < tex->params.mipmaps);
assert(image != NULL);
Pixmap pix;
GLTextureFormatTuple *fmt = prepare_pixmap(tex, image, &pix);
void *image_data = pix.data.untyped;
GLTextureFormatTuple *fmt = glcommon_find_best_pixformat(tex->params.type, image->format);
assert(image->origin == PIXMAP_ORIGIN_BOTTOMLEFT);
assert(image->format == fmt->px_fmt);
void *image_data = image->data.untyped;
GLuint prev_pbo = 0;
@ -197,7 +196,7 @@ static void gl33_texture_set(Texture *tex, uint mipmap, const Pixmap *image) {
prev_pbo = gl33_buffer_current(GL33_BUFFER_BINDING_PIXEL_UNPACK);
gl33_bind_buffer(GL33_BUFFER_BINDING_PIXEL_UNPACK, tex->pbo);
gl33_sync_buffer(GL33_BUFFER_BINDING_PIXEL_UNPACK);
glBufferData(GL_PIXEL_UNPACK_BUFFER, pixmap_data_size(&pix), image_data, GL_STREAM_DRAW);
glBufferData(GL_PIXEL_UNPACK_BUFFER, pixmap_data_size(image), image_data, GL_STREAM_DRAW);
image_data = NULL;
}
@ -216,8 +215,6 @@ static void gl33_texture_set(Texture *tex, uint mipmap, const Pixmap *image) {
image_data
);
free(pix.data.untyped);
if(tex->pbo) {
gl33_bind_buffer(GL33_BUFFER_BINDING_PIXEL_UNPACK, prev_pbo);
}
@ -361,18 +358,18 @@ void gl33_texture_fill_region(Texture *tex, uint mipmap, uint x, uint y, const P
gl33_bind_texture(tex, false, -1);
gl33_sync_texunit(tex->binding_unit, false, true);
Pixmap pix;
GLTextureFormatTuple *fmt = prepare_pixmap(tex, image, &pix);
GLTextureFormatTuple *fmt = glcommon_find_best_pixformat(tex->params.type, image->format);
assert(image->origin == PIXMAP_ORIGIN_BOTTOMLEFT);
assert(image->format == fmt->px_fmt);
glTexSubImage2D(
GL_TEXTURE_2D, mipmap,
x, tex->params.height - y - pix.height, pix.width, pix.height,
x, tex->params.height - y - image->height, image->width, image->height,
fmt->gl_fmt,
fmt->gl_type,
pix.data.untyped
image->data.untyped
);
free(pix.data.untyped);
tex->mipmaps_outdated = true;
}

View file

@ -30,7 +30,7 @@ typedef struct Texture {
Texture* gl33_texture_create(const TextureParams *params);
void gl33_texture_get_size(Texture *tex, uint mipmap, uint *width, uint *height);
void gl33_texture_get_params(Texture *tex, TextureParams *params);
const char* gl33_texture_get_debug_label(Texture *tex);
const char *gl33_texture_get_debug_label(Texture *tex);
void gl33_texture_set_debug_label(Texture *tex, const char *label);
void gl33_texture_set_filter(Texture *tex, TextureFilterMode fmin, TextureFilterMode fmag);
void gl33_texture_set_wrap(Texture *tex, TextureWrapMode ws, TextureWrapMode wt);
@ -42,8 +42,9 @@ void gl33_texture_taint(Texture *tex);
void gl44_texture_clear(Texture *tex, const Color *clr);
void gl33_texture_clear(Texture *tex, const Color *clr);
void gl33_texture_destroy(Texture *tex);
PixmapFormat gl33_texture_optimal_pixmap_format_for_type(TextureType type, PixmapFormat src_format);
GLTextureTypeInfo* gl33_texture_type_info(TextureType type);
GLTextureTypeInfo *gl33_texture_type_info(TextureType type);
GLTexFormatCapabilities gl33_texture_format_caps(GLenum internal_fmt);
#endif // IGUARD_renderer_gl33_texture_h

View file

@ -90,6 +90,7 @@ static void null_texture_fill_region(Texture *tex, uint mipmap, uint x, uint y,
static void null_texture_invalidate(Texture *tex) { }
static void null_texture_destroy(Texture *tex) { }
static void null_texture_clear(Texture *tex, const Color *color) { }
static PixmapFormat null_texture_optimal_pixmap_format_for_type(TextureType type, PixmapFormat src_format) { return src_format; }
static FloatRect default_fb_viewport;
@ -207,6 +208,7 @@ RendererBackend _r_backend_null = {
.texture_fill = null_texture_fill,
.texture_fill_region = null_texture_fill_region,
.texture_clear = null_texture_clear,
.texture_optimal_pixmap_format_for_type = null_texture_optimal_pixmap_format_for_type,
.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

@ -14,33 +14,14 @@
#include "list.h"
#include "renderer/api.h"
ResourceHandler animation_res_handler = {
.type = RES_ANIM,
.typename = "animation",
.subdir = ANI_PATH_PREFIX,
.procs = {
.find = animation_path,
.check = check_animation_path,
.begin_load = load_animation_begin,
.end_load = load_animation_end,
.unload = unload_animation,
},
};
char *animation_path(const char *name) {
static char *animation_path(const char *name) {
return strjoin(ANI_PATH_PREFIX, name, ANI_EXTENSION, NULL);
}
bool check_animation_path(const char *path) {
static bool check_animation_path(const char *path) {
return strendswith(path, ANI_EXTENSION);
}
typedef struct AnimationLoadData {
Animation *ani;
char *basename;
} AnimationLoadData;
// See ANIMATION_FORMAT.rst for a documentation of this syntax.
static bool animation_parse_sequence_spec(AniSequence **seq, int seq_capacity, const char *specstr) {
const char *delaytoken = "d";
@ -191,35 +172,38 @@ static void *free_sequence_callback(const char *key, void *data, void *arg) {
return NULL;
}
void *load_animation_begin(const char *filename, uint flags) {
char *basename = resource_util_basename(ANI_PATH_PREFIX, filename);
char name[strlen(basename) + 1];
strcpy(name, basename);
static void load_animation_stage1(ResourceLoadState *st);
static void load_animation_stage2(ResourceLoadState *st);
static void load_animation_stage1(ResourceLoadState *st) {
Animation *ani = calloc(1, sizeof(Animation));
ht_create(&ani->sequences);
if(!parse_keyvalue_file_cb(filename, animation_parse_callback, ani)) {
if(!parse_keyvalue_file_cb(st->path, animation_parse_callback, ani)) {
ht_foreach(&ani->sequences, free_sequence_callback, NULL);
ht_destroy(&ani->sequences);
free(ani);
free(basename);
return NULL;
res_load_failed(st);
return;
}
if(ani->sprite_count <= 0) {
log_error("Animation sprite count of '%s', must be positive integer", name);
log_error("Animation sprite count of '%s', must be positive integer", st->name);
ht_foreach(&ani->sequences, free_sequence_callback, NULL);
ht_destroy(&ani->sequences);
free(ani);
free(basename);
return NULL;
res_load_failed(st);
return;
}
AnimationLoadData *data = malloc(sizeof(AnimationLoadData));
data->ani = ani;
data->basename = basename;
return data;
char buf[strlen(st->name) + sizeof(".frame0000")];
for(int i = 0; i < ani->sprite_count; ++i) {
snprintf(buf, sizeof(buf), "%s.frame%04d", st->name, i);
res_load_dependency(st, RES_SPRITE, buf);
}
res_load_continue_after_dependencies(st, load_animation_stage2, ani);
}
struct anim_remap_state {
@ -298,30 +282,30 @@ static void *remap_sequence_callback(const char *key, void *value, void *varg) {
return NULL;
}
void *load_animation_end(void *opaque, const char *filename, uint flags) {
AnimationLoadData *data = opaque;
static void unload_animation(void *vani) {
Animation *ani = vani;
ht_foreach(&ani->sequences, free_sequence_callback, NULL);
ht_destroy(&ani->sequences);
free(ani->sprites);
free(ani->local_sprites);
free(ani);
}
if(opaque == NULL) {
return NULL;
}
Animation *ani = data->ani;
static void load_animation_stage2(ResourceLoadState *st) {
Animation *ani = NOT_NULL(st->opaque);
ani->sprites = calloc(ani->sprite_count, sizeof(Sprite*));
char buf[strlen(data->basename) + sizeof(".frame0000")];
char buf[strlen(st->name) + sizeof(".frame0000")];
for(int i = 0; i < ani->sprite_count; ++i) {
snprintf(buf, sizeof(buf), "%s.frame%04d", data->basename, i);
Resource *res = get_resource(RES_SPRITE, buf, flags);
snprintf(buf, sizeof(buf), "%s.frame%04d", st->name, i);
if(res == NULL) {
if(!(ani->sprites[i] = get_resource_data(RES_SPRITE, buf, st->flags))) {
log_error("Animation frame '%s' not found but @sprite_count was %d",buf,ani->sprite_count);
unload_animation(ani);
ani = NULL;
goto done;
}
ani->sprites[i] = res->data;
}
struct anim_remap_state remap_state = { 0 };
@ -354,19 +338,11 @@ done:
ht_destroy(&remap_state.flip_map);
}
free(data->basename);
free(data);
return ani;
}
void unload_animation(void *vani) {
Animation *ani = vani;
ht_foreach(&ani->sequences, free_sequence_callback, NULL);
ht_destroy(&ani->sequences);
free(ani->sprites);
free(ani->local_sprites);
free(ani);
if(ani) {
res_load_finished(st, ani);
} else {
res_load_failed(st);
}
}
AniSequence *get_ani_sequence(Animation *ani, const char *seqname) {
@ -384,3 +360,16 @@ Sprite *animation_get_frame(Animation *ani, AniSequence *seq, int seqframe) {
assert(idx < ani->sprite_count);
return ani->sprites[idx];
}
ResourceHandler animation_res_handler = {
.type = RES_ANIM,
.typename = "animation",
.subdir = ANI_PATH_PREFIX,
.procs = {
.find = animation_path,
.check = check_animation_path,
.load = load_animation_stage1,
.unload = unload_animation,
},
};

View file

@ -26,12 +26,6 @@ typedef struct Animation {
int sprite_count;
} Animation;
char *animation_path(const char *name);
bool check_animation_path(const char *path);
void *load_animation_begin(const char *filename, uint flags);
void *load_animation_end(void *opaque, const char *filename, uint flags);
void unload_animation(void *vani);
INLINE Animation *get_ani(const char *name) {
return get_resource(RES_ANIM, name, RESF_DEFAULT)->data;
}

View file

@ -30,34 +30,31 @@ static MusicImpl *load_music(const char *path) {
return _a_backend.funcs.music_load(path);
}
static void *load_bgm_begin(const char *path, uint flags) {
static void load_bgm(ResourceLoadState *st) {
Music *mus = calloc(1, sizeof(Music));
if(strendswith(path, ".bgm")) {
char *basename = resource_util_basename(BGM_PATH_PREFIX, path);
mus->meta = get_resource_data(RES_BGM_METADATA, basename, flags);
free(basename);
if(strendswith(st->path, ".bgm")) {
mus->meta = get_resource_data(RES_BGM_METADATA, st->name, st->flags);
if(mus->meta) {
mus->impl = load_music(mus->meta->loop_path);
}
} else {
mus->impl = load_music(path);
mus->impl = load_music(st->path);
}
if(!mus->impl) {
free(mus);
mus = NULL;
log_error("Failed to load bgm '%s'", path);
} else if(mus->meta->loop_point > 0) {
_a_backend.funcs.music_set_loop_point(mus->impl, mus->meta->loop_point);
log_error("Failed to load bgm '%s'", st->path);
res_load_failed(st);
} else {
if(mus->meta && mus->meta->loop_point > 0) {
_a_backend.funcs.music_set_loop_point(mus->impl, mus->meta->loop_point);
}
res_load_finished(st, mus);
}
return mus;
}
static void *load_bgm_end(void *opaque, const char *path, uint flags) {
return opaque;
}
static void unload_bgm(void *vmus) {
@ -74,8 +71,7 @@ ResourceHandler bgm_res_handler = {
.procs = {
.find = bgm_path,
.check = check_bgm_path,
.begin_load = load_bgm_begin,
.end_load = load_bgm_end,
.load = load_bgm,
.unload = unload_bgm,
},
};

View file

@ -27,10 +27,10 @@ static void free_metadata_fields(MusicMetadata *meta) {
free(meta->title);
}
static void *load_bgm_meta_begin(const char *path, uint flags) {
static void load_bgm_meta(ResourceLoadState *st) {
MusicMetadata meta = { 0 };
if(!parse_keyvalue_file_with_spec(path, (KVSpec[]) {
if(!parse_keyvalue_file_with_spec(st->path, (KVSpec[]) {
{ "artist", .out_str = &meta.artist },
{ "title", .out_str = &meta.title },
{ "comment", .out_str = &meta.comment },
@ -38,20 +38,17 @@ static void *load_bgm_meta_begin(const char *path, uint flags) {
{ "loop_point", .out_double = &meta.loop_point },
{ NULL }
})) {
log_error("Failed to parse BGM metadata '%s'", path);
log_error("Failed to parse BGM metadata '%s'", st->path);
free_metadata_fields(&meta);
return NULL;
res_load_failed(st);
return;
}
if(meta.comment) {
expand_escape_sequences(meta.comment);
}
return memdup(&meta, sizeof(meta));
}
static void *load_bgm_meta_end(void *opaque, const char *path, uint flags) {
return opaque;
res_load_finished(st, memdup(&meta, sizeof(meta)));
}
static void unload_bgm_meta(void *vmus) {
@ -68,8 +65,7 @@ ResourceHandler bgm_metadata_res_handler = {
.procs = {
.find = bgm_meta_path,
.check = check_bgm_meta_path,
.begin_load = load_bgm_meta_begin,
.end_load = load_bgm_meta_end,
.load = load_bgm_meta,
.unload = unload_bgm_meta,
},
};

View file

@ -26,10 +26,9 @@
static void init_fonts(void);
static void post_init_fonts(void);
static void shutdown_fonts(void);
static char* font_path(const char*);
static char *font_path(const char*);
static bool check_font_path(const char*);
static void* load_font_begin(const char*, uint);
static void* load_font_end(void*, const char*, uint);
static void load_font(ResourceLoadState *st);
static void unload_font(void*);
ResourceHandler font_res_handler = {
@ -43,8 +42,7 @@ ResourceHandler font_res_handler = {
.shutdown = shutdown_fonts,
.find = font_path,
.check = check_font_path,
.begin_load = load_font_begin,
.end_load = load_font_end,
.load = load_font,
.unload = unload_font,
},
};
@ -528,7 +526,7 @@ static Glyph* load_glyph(Font *font, FT_UInt gindex, SpriteSheetAnchor *spritesh
}
Pixmap px;
px.origin = PIXMAP_ORIGIN_TOPLEFT;
px.origin = PIXMAP_ORIGIN_BOTTOMLEFT;
px.format = PIXMAP_FORMAT_RGB8;
px.width = imax(g_bm_fill->bitmap.width, imax(g_bm_border->bitmap.width, g_bm_inner->bitmap.width));
px.height = imax(g_bm_fill->bitmap.rows, imax(g_bm_border->bitmap.rows, g_bm_inner->bitmap.rows));
@ -537,7 +535,7 @@ static Glyph* load_glyph(Font *font, FT_UInt gindex, SpriteSheetAnchor *spritesh
int ref_left = g_bm_border->left;
int ref_top = g_bm_border->top;
ssize_t fill_ofs_x = (g_bm_fill->left - ref_left);
ssize_t fill_ofs_x = (g_bm_fill->left - ref_left);
ssize_t fill_ofs_y = -(g_bm_fill->top - ref_top);
ssize_t border_ofs_x = (g_bm_border->left - ref_left);
ssize_t border_ofs_y = -(g_bm_border->top - ref_top);
@ -555,9 +553,9 @@ static Glyph* load_glyph(Font *font, FT_UInt gindex, SpriteSheetAnchor *spritesh
ssize_t inner_coord_x = x - inner_ofs_x;
ssize_t inner_coord_y = y - inner_ofs_y;
ssize_t fill_index = fill_coord_x + fill_coord_y * g_bm_fill->bitmap.pitch;
ssize_t border_index = border_coord_x + border_coord_y * g_bm_border->bitmap.pitch;
ssize_t inner_index = inner_coord_x + inner_coord_y * g_bm_inner->bitmap.pitch;
ssize_t fill_index = fill_coord_x + (g_bm_fill->bitmap.rows - fill_coord_y - 1) * g_bm_fill->bitmap.pitch;
ssize_t border_index = border_coord_x + (g_bm_border->bitmap.rows - border_coord_y - 1) * g_bm_border->bitmap.pitch;
ssize_t inner_index = inner_coord_x + (g_bm_inner->bitmap.rows - inner_coord_y - 1) * g_bm_inner->bitmap.pitch;
if(
fill_coord_x >= 0 && fill_coord_x < g_bm_fill->bitmap.width &&
@ -588,6 +586,13 @@ static Glyph* load_glyph(Font *font, FT_UInt gindex, SpriteSheetAnchor *spritesh
}
}
PixmapFormat optimal_fmt = r_texture_optimal_pixmap_format_for_type(TEX_TYPE_RGB_8, px.format);
pixmap_convert_inplace_realloc(&px, optimal_fmt);
if(!r_supports(RFEAT_TEXTURE_BOTTOMLEFT_ORIGIN)) {
pixmap_flip_to_origin_inplace(&px, PIXMAP_ORIGIN_TOPLEFT);
}
if(!add_glyph_to_spritesheets(glyph, &px, spritesheets)) {
log_warn(
"Glyph %u fill can't fit into any spritesheets (padded bitmap size: %zux%zu; max spritesheet size: %ux%u)",
@ -695,18 +700,19 @@ static void free_font_resources(Font *font) {
dynarray_free_data(&font->glyphs);
}
void* load_font_begin(const char *path, uint flags) {
void load_font(ResourceLoadState *st) {
Font font;
memset(&font, 0, sizeof(font));
if(!parse_keyvalue_file_with_spec(path, (KVSpec[]){
if(!parse_keyvalue_file_with_spec(st->path, (KVSpec[]){
{ "source", .out_str = &font.source_path },
{ "size", .out_int = &font.base_size },
{ "face", .out_long = &font.base_face_idx },
{ NULL }
})) {
log_error("Failed to parse font file '%s'", path);
return NULL;
log_error("Failed to parse font file '%s'", st->path);
res_load_failed(st);
return;
}
ht_create(&font.charcodes_to_glyph_ofs);
@ -714,28 +720,23 @@ void* load_font_begin(const char *path, uint flags) {
if(!(font.face = load_font_face(font.source_path, font.base_face_idx))) {
free_font_resources(&font);
return NULL;
res_load_failed(st);
}
if(set_font_size(&font, font.base_size, global_font_scale())) {
free_font_resources(&font);
return NULL;
res_load_failed(st);
return;
}
dynarray_ensure_capacity(&font.glyphs, 32);
#ifdef DEBUG
char *basename = resource_util_basename(FONT_PATH_PREFIX, path);
strlcpy(font.debug_label, basename, sizeof(font.debug_label));
free(basename);
strlcpy(font.debug_label, st->name, sizeof(font.debug_label));
#endif
font_set_kerning_enabled(&font, true);
return memdup(&font, sizeof(font));
}
void* load_font_end(void *opaque, const char *path, uint flags) {
return opaque;
res_load_finished(st, memdup(&font, sizeof(font)));
}
void unload_font(void *vfont) {

View file

@ -333,12 +333,17 @@ static bool range_is_valid(uint32_t total, uint32_t first, uint32_t num) {
return true;
}
static void *load_model_begin(const char *path, uint flags) {
static void load_model_stage1(ResourceLoadState *st);
static void load_model_stage2(ResourceLoadState *st);
static void load_model_stage1(ResourceLoadState *st) {
const char *path = st->path;
SDL_RWops *rw = vfs_open(path, VFS_MODE_READ | VFS_MODE_SEEKABLE);
if(!rw) {
log_error("VFS error: %s", vfs_get_error());
return NULL;
res_load_failed(st);
return;
}
ModelLoadData *ldata = NULL;
@ -429,22 +434,25 @@ cleanup:
free(meshes);
free(vert_arrays);
SDL_RWclose(rw);
return ldata;
if(ldata) {
res_load_continue_on_main(st, load_model_stage2, ldata);
} else {
res_load_failed(st);
}
return;
fail:
free(vertices);
free(indices);
free(ldata);
ldata = NULL;
goto cleanup;
}
static void *load_model_end(void *opaque, const char *path, uint flags) {
ModelLoadData *ldata = opaque;
if(ldata == NULL) {
return NULL;
}
static void load_model_stage2(ResourceLoadState *st) {
ModelLoadData *ldata = NOT_NULL(st->opaque);
Model *mdl = calloc(1, sizeof(*mdl));
r_model_add_static(
@ -460,7 +468,7 @@ static void *load_model_end(void *opaque, const char *path, uint flags) {
free(ldata->indices);
free(ldata);
return mdl;
res_load_finished(st, mdl);
}
static char *model_path(const char *name) {
@ -483,8 +491,7 @@ ResourceHandler model_res_handler = {
.procs = {
.find = model_path,
.check = check_model_path,
.begin_load = load_model_begin,
.end_load = load_model_end,
.load = load_model_stage1,
.unload = unload_model,
},
};

View file

@ -15,19 +15,8 @@
#include "resource.h"
#include "renderer/api.h"
ResourceHandler postprocess_res_handler = {
.type = RES_POSTPROCESS,
.typename = "postprocessing pipeline",
.subdir = PP_PATH_PREFIX,
.procs = {
.find = postprocess_path,
.check = check_postprocess_path,
.begin_load = load_postprocess_begin,
.end_load = load_postprocess_end,
.unload = unload_postprocess,
},
};
#define PP_PATH_PREFIX SHPROG_PATH_PREFIX
#define PP_EXTENSION ".pp"
typedef struct PostprocessLoadData {
PostprocessShader *list;
@ -242,22 +231,41 @@ void postprocess(PostprocessShader *ppshaders, FBPair *fbos, PostprocessPrepareF
* Glue for resources api
*/
char* postprocess_path(const char *name) {
static char *postprocess_path(const char *name) {
return strjoin(PP_PATH_PREFIX, name, PP_EXTENSION, NULL);
}
bool check_postprocess_path(const char *path) {
static bool check_postprocess_path(const char *path) {
return strendswith(path, PP_EXTENSION);
}
void* load_postprocess_begin(const char *path, uint flags) {
return (void*)true;
static void load_postprocess_stage2(ResourceLoadState *st) {
PostprocessShader *pp = postprocess_load(st->path, st->flags);
if(pp) {
res_load_finished(st, pp);
} else {
res_load_failed(st);
}
}
void* load_postprocess_end(void *opaque, const char *path, uint flags) {
return postprocess_load(path, flags);
static void load_postprocess_stage1(ResourceLoadState *st) {
res_load_continue_on_main(st, load_postprocess_stage2, NULL);
}
void unload_postprocess(void *vlist) {
static void unload_postprocess(void *vlist) {
postprocess_unload((PostprocessShader**)&vlist);
}
ResourceHandler postprocess_res_handler = {
.type = RES_POSTPROCESS,
.typename = "postprocessing pipeline",
.subdir = PP_PATH_PREFIX,
.procs = {
.find = postprocess_path,
.check = check_postprocess_path,
.load = load_postprocess_stage1,
.unload = unload_postprocess,
},
};

View file

@ -48,25 +48,10 @@ struct PostprocessShaderUniform {
typedef void (*PostprocessDrawFuncPtr)(Framebuffer *fb, double w, double h);
typedef void (*PostprocessPrepareFuncPtr)(Framebuffer *fb, ShaderProgram *prog, void *arg);
char* postprocess_path(const char *path);
PostprocessShader* postprocess_load(const char *path, uint flags);
void postprocess_unload(PostprocessShader **list);
void postprocess(PostprocessShader *ppshaders, FBPair *fbos, PostprocessPrepareFuncPtr prepare, PostprocessDrawFuncPtr draw, double width, double height, void *arg);
/*
* Glue for resources api
*/
char* postprocess_path(const char *name);
bool check_postprocess_path(const char *path);
void* load_postprocess_begin(const char *path, uint flags);
void* load_postprocess_end(void *opaque, const char *path, uint flags);
void unload_postprocess(void*);
extern ResourceHandler postprocess_res_handler;
#define PP_PATH_PREFIX SHPROG_PATH_PREFIX
#define PP_EXTENSION ".pp"
#endif // IGUARD_resource_postprocess_h

View file

@ -49,27 +49,93 @@ typedef enum ResourceStatus {
RES_STATUS_FAILED,
} ResourceStatus;
typedef struct InternalResource {
typedef enum LoadStatus {
LOAD_NONE,
LOAD_OK,
LOAD_FAILED,
LOAD_CONT_ON_MAIN,
LOAD_CONT,
} LoadStatus;
typedef struct InternalResource InternalResource;
typedef struct InternalResLoadState InternalResLoadState;
struct InternalResource {
Resource res;
ResourceStatus status;
SDL_mutex *mutex;
SDL_cond *cond;
Task *async_task;
} InternalResource;
InternalResLoadState *load;
ResourceStatus status;
};
typedef struct ResourceAsyncLoadData {
struct InternalResLoadState {
ResourceLoadState st;
InternalResource *ires;
char *path;
char *name;
ResourceFlags flags;
void *opaque;
} ResourceAsyncLoadData;
Task *async_task;
char *allocated_name;
char *allocated_path;
ResourceLoadProc continuation;
DYNAMIC_ARRAY(InternalResource*) dependencies;
LoadStatus status;
bool ready_to_finalize;
};
static inline ResourceHandler* get_handler(ResourceType type) {
static struct {
hrtime_t frame_threshold;
uchar loaded_this_frame : 1;
struct {
uchar no_async_load : 1;
uchar no_preload : 1;
uchar no_unload : 1;
uchar preload_required : 1;
} env;
} res_gstate;
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);
void res_load_failed(ResourceLoadState *st) {
InternalResLoadState *ist = loadstate_internal(st);
ist->status = LOAD_FAILED;
}
void res_load_finished(ResourceLoadState *st, void *res) {
InternalResLoadState *ist = loadstate_internal(st);
ist->status = LOAD_OK;
ist->st.opaque = res;
}
void res_load_continue_on_main(ResourceLoadState *st, ResourceLoadProc callback, void *opaque) {
InternalResLoadState *ist = loadstate_internal(st);
ist->status = LOAD_CONT_ON_MAIN;
ist->st.opaque = opaque;
ist->continuation = callback;
}
void res_load_continue_after_dependencies(ResourceLoadState *st, ResourceLoadProc callback, void *opaque) {
InternalResLoadState *ist = loadstate_internal(st);
ist->status = LOAD_CONT;
ist->st.opaque = opaque;
ist->continuation = callback;
}
void res_load_dependency(ResourceLoadState *st, ResourceType type, const char *name) {
InternalResLoadState *ist = loadstate_internal(st);
InternalResource *dep = preload_resource_internal(type, name, st->flags);
InternalResource *ires = ist->ires;
SDL_LockMutex(ires->mutex);
*dynarray_append(&ist->dependencies) = dep;
SDL_UnlockMutex(ires->mutex);
}
static inline ResourceHandler *get_handler(ResourceType type) {
return *(_handlers + type);
}
static inline ResourceHandler* get_ires_handler(InternalResource *ires) {
static inline ResourceHandler *get_ires_handler(InternalResource *ires) {
return get_handler(ires->res.type);
}