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:
parent
8692a1a05c
commit
ae8194ae78
36 changed files with 922 additions and 593 deletions
|
@ -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);
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
12
src/events.c
12
src/events.c
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|