From 9d8758b2b40e115582c4354384ae73d8cbb4bbc8 Mon Sep 17 00:00:00 2001 From: jacob Date: Fri, 1 Aug 2025 00:14:57 -0500 Subject: [PATCH] sprite layer refactor progress --- src/sim/sim_step.c | 6 +- src/sprite/sprite_core.c | 865 +++++++++++++++++++-------------------- src/sprite/sprite_core.h | 444 ++++++++++++++++---- src/user/user_core.c | 3 +- 4 files changed, 791 insertions(+), 527 deletions(-) diff --git a/src/sim/sim_step.c b/src/sim/sim_step.c index 8c2e4ab6..a62c7a53 100644 --- a/src/sim/sim_step.c +++ b/src/sim/sim_step.c @@ -405,9 +405,8 @@ internal void test_spawn_tile(Snapshot *world, Vec2 world_pos) -internal MergesortCompareFuncDef(tile_chunk_sort_x, arg_a, arg_b, udata) +internal MergesortCompareFuncDef(tile_chunk_sort_x, arg_a, arg_b, _) { - (UNUSED)udata; Ent *a = *(Ent **)arg_a; Ent *b = *(Ent **)arg_b; i32 a_x = a->tile_chunk_index.x; @@ -418,9 +417,8 @@ internal MergesortCompareFuncDef(tile_chunk_sort_x, arg_a, arg_b, udata) return result; } -internal MergesortCompareFuncDef(tile_chunk_sort_y, arg_a, arg_b, udata) +internal MergesortCompareFuncDef(tile_chunk_sort_y, arg_a, arg_b, _) { - (UNUSED)udata; Ent *a = *(Ent **)arg_a; Ent *b = *(Ent **)arg_b; i32 a_y = a->tile_chunk_index.y; diff --git a/src/sprite/sprite_core.c b/src/sprite/sprite_core.c index d34dbe18..b2191dc4 100644 --- a/src/sprite/sprite_core.c +++ b/src/sprite/sprite_core.c @@ -1,154 +1,10 @@ -/* The evictor will begin evicting once cache usage is > threshold. - * It will entries until the budget has shrunk < target. */ -#define CACHE_MEMORY_BUDGET_THRESHOLD (Mebi(256)) -#define CACHE_MEMORY_BUDGET_TARGET (Mebi(128)) -StaticAssert(CACHE_MEMORY_BUDGET_THRESHOLD >= CACHE_MEMORY_BUDGET_TARGET); - -#define CACHE_BINS_COUNT 1024 - -#define MAX_SCOPE_REFERENCES 1024 - -/* How long between evictor cycles */ -#define EVICTOR_CYCLE_INTERVAL_NS NsFromSeconds(0.500) - -/* How many cycles a cache entry spends unused until it's considered evictable */ -#define EVICTOR_GRACE_PERIOD_CYCLES (NsFromSeconds(10.000) / EVICTOR_CYCLE_INTERVAL_NS) - -/* Texture arena only used to store texture struct at the moment. Actual image data is allocated on GPU. */ -#define TEXTURE_ARENA_RESERVE Mebi(1) - -#define SHEET_ARENA_RESERVE Mebi(64) -#define SHEET_SPAN_LOOKUP_TABLE_BIN_RATIO 2.0 -#define SHEET_SLICE_LOOKUP_TABLE_BIN_RATIO 2.0 - -/* ========================== * - * Cache structs - * ========================== */ - -enum cache_entry_kind { - CACHE_ENTRY_KIND_TEXTURE, - CACHE_ENTRY_KIND_SHEET, - - NUM_CACHE_ENTRY_KINDS -}; - -enum cache_entry_state { - CACHE_ENTRY_STATE_NONE, - CACHE_ENTRY_STATE_QUEUED, - CACHE_ENTRY_STATE_WORKING, - CACHE_ENTRY_STATE_LOADED -}; - -struct cache_refcount { - i32 count; /* Number of scopes currently holding a reference to this entry */ - i32 last_ref_cycle; /* Last evictor cycle that the refcount was modified */ -}; -StaticAssert(sizeof(struct cache_refcount) == 8); /* Must fit into 64 bit atomic */ - -struct cache_entry_hash { - u64 v; -}; - -struct cache_entry { - enum cache_entry_kind kind; - struct cache_entry_hash hash; - Atomic32 state; - Atomic64Padded refcount_struct; /* Cast fetched result to `cache_refcount` */ - - /* Allocated data */ - /* NOTE: This data is finalized once entry state = loaded */ - i64 load_time_ns; - u64 memory_usage; - Arena *arena; - S_Texture *texture; - S_Sheet *sheet; - - /* Hash list */ - struct cache_entry *next_in_bin; - struct cache_entry *prev_in_bin; - - /* Free list */ - struct cache_entry *next_free; - -#if RESOURCE_RELOADING - Atomic32 out_of_date; /* Has the resource changed since this entry was loaded */ -#endif -}; - -struct cache_bin { - P_Mutex mutex; - struct cache_entry *first; - struct cache_entry *last; -}; - -struct cache { - Atomic64Padded memory_usage; - Arena *arena; - struct cache_bin *bins; - P_Mutex entry_pool_mutex; - struct cache_entry *entry_pool_first_free; -}; - -/* Represents a reference that can be used to safely access cache entry without it becoming evicted during the reference's lifetime */ -struct cache_ref { - struct cache_entry *e; -}; - -/* A cache reference whose lifetime is bound to the scope it was retrieved from */ -struct sprite_scope_cache_ref { - struct cache_ref ref; - struct sprite_scope_cache_ref *next_in_bin; -}; - -/* ========================== * - * Load cmd structs - * ========================== */ - -struct load_cmd { - struct load_cmd *next_free; - S_Scope *scope; - struct cache_ref ref; - S_Tag tag; - u8 tag_path_buff[512]; -}; - -/* ========================== * - * Global state - * ========================== */ - -Global struct { - Arena *perm_arena; - S_Texture *nil_texture; - S_Texture *loading_texture; - S_Sheet *nil_sheet; - S_Sheet *loading_sheet; - - /* Cache */ - struct cache cache; - - /* Load cmds */ - P_Mutex load_cmds_mutex; - Arena *load_cmds_arena; - struct load_cmd *first_free_load_cmd; - - /* Scopes */ - P_Mutex scopes_mutex; - Arena *scopes_arena; - S_Scope *first_free_scope; - - /* Evictor */ - Atomic32Padded evictor_cycle; - P_Counter shutdown_counter; - b32 evictor_scheduler_shutdown; - P_Mutex evictor_scheduler_mutex; - P_Cv evictor_scheduler_shutdown_cv; -} G = ZI, DebugAlias(G, G_sprite); +S_SharedState S_shared_state = ZI; /* ========================== * * Purple-black image * ========================== */ -internal u32 *generate_purple_black_image(Arena *arena, u32 width, u32 height) +u32 *generate_purple_black_image(Arena *arena, u32 width, u32 height) { u32 *pixels = PushStructsNoZero(arena, u32, width * height); @@ -156,19 +12,30 @@ internal u32 *generate_purple_black_image(Arena *arena, u32 width, u32 height) u32 color_size = 4; u32 color_1 = 0xFFDC00FF; u32 color_2 = 0xFF000000; - for (u32 x = 0; x < width; ++x) { - for (u32 y = 0; y < height; ++y) { + for (u32 x = 0; x < width; ++x) + { + for (u32 y = 0; y < height; ++y) + { u32 pixel_index = x + width * y; - if ((y / color_size) % 2 == 0) { - if ((x / color_size) % 2 == 0) { + if ((y / color_size) % 2 == 0) + { + if ((x / color_size) % 2 == 0) + { pixels[pixel_index] = color_1; - } else { + } + else + { pixels[pixel_index] = color_2; } - } else { - if ((x / color_size) % 2 == 0) { + } + else + { + if ((x / color_size) % 2 == 0) + { pixels[pixel_index] = color_2; - } else { + } + else + { pixels[pixel_index] = color_1; } } @@ -182,76 +49,70 @@ internal u32 *generate_purple_black_image(Arena *arena, u32 width, u32 height) * Startup * ========================== */ -internal P_ExitFuncDef(sprite_shutdown); -internal P_JobDef(sprite_load_job, arg); -internal P_JobDef(sprite_evictor_job, _); - -#if RESOURCE_RELOADING -internal W_CallbackFuncDef(sprite_watch_callback, info); -#endif - S_StartupReceipt sprite_startup(void) { __prof; - G.perm_arena = AllocArena(Mebi(1)); + S_SharedState *g = &S_shared_state; + g->perm_arena = AllocArena(Mebi(1)); { /* Init loading texture */ - G.loading_texture = PushStruct(G.perm_arena, S_Texture); + g->loading_texture = PushStruct(g->perm_arena, S_Texture); /* Init nil texture */ - G.nil_texture = PushStruct(G.perm_arena, S_Texture); - G.nil_texture->loaded = 1; + g->nil_texture = PushStruct(g->perm_arena, S_Texture); + g->nil_texture->loaded = 1; { TempArena scratch = BeginScratchNoConflict(); u32 width = 64; u32 height = 64; u32 *pixels = generate_purple_black_image(scratch.arena, width, height); - G.nil_texture->gp_texture = GPU_AllocTexture(GP_TEXTURE_FORMAT_R8G8B8A8_UNORM, 0, VEC2I32(width, height), pixels); + g->nil_texture->gp_texture = GPU_AllocTexture(GP_TEXTURE_FORMAT_R8G8B8A8_UNORM, 0, VEC2I32(width, height), pixels); EndScratch(scratch); } /* Init loading sheet */ - G.loading_sheet = PushStruct(G.perm_arena, S_Sheet); - G.loading_sheet->image_size = VEC2(PIXELS_PER_UNIT, PIXELS_PER_UNIT); - G.loading_sheet->frame_size = VEC2(PIXELS_PER_UNIT, PIXELS_PER_UNIT); + g->loading_sheet = PushStruct(g->perm_arena, S_Sheet); + g->loading_sheet->image_size = VEC2(PIXELS_PER_UNIT, PIXELS_PER_UNIT); + g->loading_sheet->frame_size = VEC2(PIXELS_PER_UNIT, PIXELS_PER_UNIT); /* Init nil sheet */ - G.nil_sheet = PushStruct(G.perm_arena, S_Sheet); - G.nil_sheet->image_size = VEC2(PIXELS_PER_UNIT, PIXELS_PER_UNIT); - G.nil_sheet->frame_size = VEC2(PIXELS_PER_UNIT, PIXELS_PER_UNIT); - G.nil_sheet->loaded = 1; + g->nil_sheet = PushStruct(g->perm_arena, S_Sheet); + g->nil_sheet->image_size = VEC2(PIXELS_PER_UNIT, PIXELS_PER_UNIT); + g->nil_sheet->frame_size = VEC2(PIXELS_PER_UNIT, PIXELS_PER_UNIT); + g->nil_sheet->loaded = 1; } - SetArenaReadonly(G.perm_arena); + SetArenaReadonly(g->perm_arena); - G.cache.arena = AllocArena(Gibi(64)); - G.cache.bins = PushStructs(G.cache.arena, struct cache_bin, CACHE_BINS_COUNT); + g->cache.arena = AllocArena(Gibi(64)); + g->cache.bins = PushStructs(g->cache.arena, S_CacheEntryBin, S_CacheBinsCount); - G.load_cmds_arena = AllocArena(Gibi(64)); + g->cmds_arena = AllocArena(Gibi(64)); - G.scopes_arena = AllocArena(Gibi(64)); + g->scopes_arena = AllocArena(Gibi(64)); - P_Run(1, sprite_evictor_job, 0, P_Pool_Background, P_Priority_Low, &G.shutdown_counter); + P_Run(1, S_EvictorJob, 0, P_Pool_Background, P_Priority_Low, &g->shutdown_counter); P_OnExit(&sprite_shutdown); #if RESOURCE_RELOADING - W_RegisterCallback(&sprite_watch_callback); + W_RegisterCallback(&S_WatchSpriteCallback); #endif return (S_StartupReceipt) { 0 }; } -internal P_ExitFuncDef(sprite_shutdown) +P_ExitFuncDef(sprite_shutdown) { __prof; + S_SharedState *g = &S_shared_state; /* Signal evictor shutdown */ { - P_Lock lock = P_LockE(&G.evictor_scheduler_mutex); - G.evictor_scheduler_shutdown = 1; - P_SignalCv(&G.evictor_scheduler_shutdown_cv, I32Max); + P_Lock lock = P_LockE(&g->evictor_scheduler_mutex); + g->evictor_scheduler_shutdown = 1; + P_SignalCv(&g->evictor_scheduler_shutdown_cv, I32Max); P_Unlock(&lock); } /* Wait for evictor shutdown */ - P_WaitOnCounter(&G.shutdown_counter); + P_WaitOnCounter(&g->shutdown_counter); } /* ========================== * @@ -276,26 +137,30 @@ b32 sprite_tag_eq(S_Tag t1, S_Tag t2) return t1.hash == t2.hash; } -internal struct cache_entry_hash cache_entry_hash_from_tag_hash(u64 tag_hash, enum cache_entry_kind kind) +S_Hash cache_entry_hash_from_tag_hash(u64 tag_hash, S_CacheEntryKind kind) { - return (struct cache_entry_hash) { .v = RandU64FromSeed(tag_hash + kind) }; + return (S_Hash) { .v = RandU64FromSeed(tag_hash + kind) }; } /* ========================== * * Load * ========================== */ -internal struct sprite_scope_cache_ref *scope_ensure_ref_from_ref(S_Scope *scope, struct cache_ref ref); -internal void push_load_job(struct cache_ref ref, S_Tag tag) +S_ScopeCacheRef *scope_ensure_ref_from_ref(S_Scope *scope, S_Ref ref); +void push_load_job(S_Ref ref, S_Tag tag) { - struct load_cmd *cmd = 0; + S_SharedState *g = &S_shared_state; + S_Cmd *cmd = 0; { - P_Lock lock = P_LockE(&G.load_cmds_mutex); - if (G.first_free_load_cmd) { - cmd = G.first_free_load_cmd; - G.first_free_load_cmd = cmd->next_free; - } else { - cmd = PushStructNoZero(G.load_cmds_arena, struct load_cmd); + P_Lock lock = P_LockE(&g->cmds_mutex); + if (g->first_free_cmd) + { + cmd = g->first_free_cmd; + g->first_free_cmd = cmd->next_free; + } + else + { + cmd = PushStructNoZero(g->cmds_arena, S_Cmd); } P_Unlock(&lock); } @@ -312,14 +177,15 @@ internal void push_load_job(struct cache_ref ref, S_Tag tag) } /* PushStruct work */ - P_Run(1, sprite_load_job, cmd, P_Pool_Background, P_Priority_Inherit, 0); + P_Run(1, S_SpriteLoadJob, cmd, P_Pool_Background, P_Priority_Inherit, 0); } -internal void cache_entry_load_texture(struct cache_ref ref, S_Tag tag) +void cache_entry_load_texture(S_Ref ref, S_Tag tag) { __prof; + S_SharedState *g = &S_shared_state; TempArena scratch = BeginScratchNoConflict(); - struct cache_entry *e = ref.e; + S_CacheEntry *e = ref.e; Atomic32FetchSet(&e->state, CACHE_ENTRY_STATE_WORKING); String path = tag.path; @@ -333,22 +199,26 @@ internal void cache_entry_load_texture(struct cache_ref ref, S_Tag tag) /* TODO: Replace arena allocs w/ buddy allocator */ /* TODO: Arena probably overkill. Just using it to store texture struct. */ - e->arena = AllocArena(TEXTURE_ARENA_RESERVE); + e->arena = AllocArena(S_TextureArenaReserve); u64 memory_size = 0; { /* Decode */ ASE_DecodedImage decoded = ZI; { RES_Resource texture_rs = RES_OpenResource(path); - if (RES_ResourceExists(&texture_rs)) { + if (RES_ResourceExists(&texture_rs)) + { decoded = ASE_DecodeImage(scratch.arena, RES_GetResourceData(&texture_rs)); - } else { + } + else + { P_LogErrorF("Sprite texture for \"%F\" not found", FmtString(path)); } RES_CloseResource(&texture_rs); } - if (decoded.success) { + if (decoded.success) + { /* Initialize */ e->texture = PushStruct(e->arena, S_Texture); e->texture->width = decoded.width; @@ -363,24 +233,27 @@ internal void cache_entry_load_texture(struct cache_ref ref, S_Tag tag) } SetArenaReadonly(e->arena); e->memory_usage = e->arena->committed + memory_size; - Atomic64FetchAdd(&G.cache.memory_usage.v, e->memory_usage); + Atomic64FetchAdd(&g->cache.memory_usage.v, e->memory_usage); - if (success) { + if (success) + { P_LogSuccessF("Loaded sprite texture [%F] \"%F\" in %F seconds (cache size: %F bytes).", - FmtHex(e->hash.v), - FmtString(path), - FmtFloat(SecondsFromNs(P_TimeNs() - start_ns)), - FmtUint(e->memory_usage)); + FmtHex(e->hash.v), + FmtString(path), + FmtFloat(SecondsFromNs(P_TimeNs() - start_ns)), + FmtUint(e->memory_usage)); } Atomic32FetchSet(&e->state, CACHE_ENTRY_STATE_LOADED); #if RESOURCE_RELOADING - struct cache_bin *bin = &G.cache.bins[e->hash.v % CACHE_BINS_COUNT]; + S_CacheEntryBin *bin = &g->cache.bins[e->hash.v % S_CacheBinsCount]; P_Lock bin_lock = P_LockE(&bin->mutex); { - for (struct cache_entry *old_entry = bin->first; old_entry; old_entry = old_entry->next_in_bin) { - if (old_entry != e && old_entry->hash.v == e->hash.v) { + for (S_CacheEntry *old_entry = bin->first; old_entry; old_entry = old_entry->next_in_bin) + { + if (old_entry != e && old_entry->hash.v == e->hash.v) + { Atomic32FetchSet(&old_entry->out_of_date, 1); } } @@ -392,7 +265,7 @@ internal void cache_entry_load_texture(struct cache_ref ref, S_Tag tag) EndScratch(scratch); } -internal S_Sheet init_sheet_from_ase_result(Arena *arena, ASE_DecodedSheet ase) +S_Sheet init_sheet_from_ase_result(Arena *arena, ASE_DecodedSheet ase) { __prof; S_Sheet sheet = ZI; @@ -409,7 +282,8 @@ internal S_Sheet init_sheet_from_ase_result(Arena *arena, ASE_DecodedSheet ase) sheet.frame_size = ase.frame_size; sheet.frames = PushStructs(arena, S_SheetFrame, ase.num_frames); sheet.frames_count = ase.num_frames; - for (ASE_Frame *ase_frame = ase.frame_head; ase_frame; ase_frame = ase_frame->next) { + for (ASE_Frame *ase_frame = ase.frame_head; ase_frame; ase_frame = ase_frame->next) + { u32 index = ase_frame->index; Vec2 clip_p1 = { (f32)ase_frame->x1 / (f32)ase.image_size.x, (f32)ase_frame->y1 / (f32)ase.image_size.y }; @@ -425,12 +299,14 @@ internal S_Sheet init_sheet_from_ase_result(Arena *arena, ASE_DecodedSheet ase) /* Init spans */ sheet.spans_count = ase.num_spans; - if (ase.num_spans > 0) { + if (ase.num_spans > 0) + { __profn("Init spans"); sheet.spans = PushStructs(arena, S_SheetSpan, sheet.spans_count); - sheet.spans_dict = InitDict(arena, (u64)(ase.num_spans * SHEET_SPAN_LOOKUP_TABLE_BIN_RATIO)); + sheet.spans_dict = InitDict(arena, (u64)(ase.num_spans * S_SheetSpanLookupTableBinRatio)); u64 index = 0; - for (ASE_Span *ase_span = ase.span_head; ase_span; ase_span = ase_span->next) { + for (ASE_Span *ase_span = ase.span_head; ase_span; ase_span = ase_span->next) + { String name = CopyString(arena, ase_span->name); S_SheetSpan *span = &sheet.spans[index]; span->name = name; @@ -443,11 +319,13 @@ internal S_Sheet init_sheet_from_ase_result(Arena *arena, ASE_DecodedSheet ase) } /* Init slices */ - if (ase.num_slice_keys > 0) { + if (ase.num_slice_keys > 0) + { __profn("Init slices"); TempArena scratch = BeginScratch(arena); - struct temp_ase_slice_key_node { + struct temp_ase_slice_key_node + { ASE_SliceKey *key; struct temp_ase_slice_key_node *next; @@ -455,7 +333,8 @@ internal S_Sheet init_sheet_from_ase_result(Arena *arena, ASE_DecodedSheet ase) u32 earliest_frame; }; - struct temp_slice_group_node { + struct temp_slice_group_node + { String name; u64 per_frame_count; struct temp_ase_slice_key_node *temp_ase_slice_key_head; @@ -469,11 +348,13 @@ internal S_Sheet init_sheet_from_ase_result(Arena *arena, ASE_DecodedSheet ase) struct temp_slice_group_node *temp_slice_group_head = 0; { Dict *temp_slice_dict = InitDict(scratch.arena, (u64)(ase.num_slice_keys * 2)); - for (ASE_SliceKey *ase_slice_key = ase.slice_key_head; ase_slice_key; ase_slice_key = ase_slice_key->next) { + for (ASE_SliceKey *ase_slice_key = ase.slice_key_head; ase_slice_key; ase_slice_key = ase_slice_key->next) + { String name = ase_slice_key->name; u64 hash = HashFnv64(Fnv64Basis, name); struct temp_slice_group_node *temp_slice_group_node = (struct temp_slice_group_node *)DictValueFromHash(temp_slice_dict, hash); - if (!temp_slice_group_node) { + if (!temp_slice_group_node) + { temp_slice_group_node = PushStruct(scratch.arena, struct temp_slice_group_node); temp_slice_group_node->name = name; SetDictValue(scratch.arena, temp_slice_dict, hash, (u64)temp_slice_group_node); @@ -497,10 +378,11 @@ internal S_Sheet init_sheet_from_ase_result(Arena *arena, ASE_DecodedSheet ase) /* Allocate slice groups & fill originals in 2d array */ sheet.slice_groups_count = num_temp_slice_group_nodes; sheet.slice_groups = PushStructs(arena, S_SheetSliceGroup, sheet.slice_groups_count); - sheet.slice_groups_dict = InitDict(arena, (u64)(num_temp_slice_group_nodes * SHEET_SLICE_LOOKUP_TABLE_BIN_RATIO)); + sheet.slice_groups_dict = InitDict(arena, (u64)(num_temp_slice_group_nodes * S_SliceLookupTableBinRatio)); u64 index = 0; - for (struct temp_slice_group_node *temp_slice_group_node = temp_slice_group_head; temp_slice_group_node; temp_slice_group_node = temp_slice_group_node->next) { + for (struct temp_slice_group_node *temp_slice_group_node = temp_slice_group_head; temp_slice_group_node; temp_slice_group_node = temp_slice_group_node->next) + { S_SheetSliceGroup *slice_group = &sheet.slice_groups[index]; slice_group->name = CopyString(arena, temp_slice_group_node->name); slice_group->per_frame_count = temp_slice_group_node->per_frame_count; @@ -508,10 +390,12 @@ internal S_Sheet init_sheet_from_ase_result(Arena *arena, ASE_DecodedSheet ase) slice_group->frame_slices = PushStructs(arena, S_SheetSlice, ase.num_frames * slice_group->per_frame_count); u64 index_in_frame = 0; - for (struct temp_ase_slice_key_node *node = temp_slice_group_node->temp_ase_slice_key_head; node; node = node->next) { + for (struct temp_ase_slice_key_node *node = temp_slice_group_node->temp_ase_slice_key_head; node; node = node->next) + { ASE_SliceKey *key = node->key; - for (ASE_Slice *ase_slice = key->slice_head; ase_slice; ase_slice = ase_slice->next) { + for (ASE_Slice *ase_slice = key->slice_head; ase_slice; ase_slice = ase_slice->next) + { u32 start = ase_slice->start; S_SheetSlice *slice = &slice_group->frame_slices[(start * slice_group->per_frame_count) + index_in_frame]; @@ -550,7 +434,8 @@ internal S_Sheet init_sheet_from_ase_result(Arena *arena, ASE_DecodedSheet ase) slice->dir = dir; node->index_in_frame = index_in_frame; - if (start < node->earliest_frame) { + if (start < node->earliest_frame) + { node->earliest_frame = start; } } @@ -565,20 +450,25 @@ internal S_Sheet init_sheet_from_ase_result(Arena *arena, ASE_DecodedSheet ase) } /* Propagate original slices into next frames (and first slices into previous frames) */ - for (struct temp_slice_group_node *temp_slice_group_node = temp_slice_group_head; temp_slice_group_node; temp_slice_group_node = temp_slice_group_node->next) { + for (struct temp_slice_group_node *temp_slice_group_node = temp_slice_group_head; temp_slice_group_node; temp_slice_group_node = temp_slice_group_node->next) + { S_SheetSliceGroup *slice_group = temp_slice_group_node->final_slice_group; - for (struct temp_ase_slice_key_node *node = temp_slice_group_node->temp_ase_slice_key_head; node; node = node->next) { + for (struct temp_ase_slice_key_node *node = temp_slice_group_node->temp_ase_slice_key_head; node; node = node->next) + { ASE_SliceKey *key = node->key; u32 index_in_frame = node->index_in_frame; - for (ASE_Slice *ase_slice = key->slice_head; ase_slice; ase_slice = ase_slice->next) { + for (ASE_Slice *ase_slice = key->slice_head; ase_slice; ase_slice = ase_slice->next) + { u32 start = ase_slice->start; S_SheetSlice *slice = &slice_group->frame_slices[(start * slice_group->per_frame_count) + index_in_frame]; /* Propagate earliest slice to all previous frames */ - if (start == node->earliest_frame && start > 0) { - for (u32 i = start; i-- > 0;) { + if (start == node->earliest_frame && start > 0) + { + for (u32 i = start; i-- > 0;) + { S_SheetSlice *target = &slice_group->frame_slices[(i * slice_group->per_frame_count) + index_in_frame]; *target = *slice; target->original = 0; @@ -586,11 +476,15 @@ internal S_Sheet init_sheet_from_ase_result(Arena *arena, ASE_DecodedSheet ase) } /* Propagate slice to forward frames until original is found */ - for (u32 i = start + 1; i < ase.num_frames; ++i) { + for (u32 i = start + 1; i < ase.num_frames; ++i) + { S_SheetSlice *target = &slice_group->frame_slices[(i * slice_group->per_frame_count) + index_in_frame]; - if (target->original) { + if (target->original) + { break; - } else { + } + else + { *target = *slice; target->original = 0; } @@ -600,27 +494,32 @@ internal S_Sheet init_sheet_from_ase_result(Arena *arena, ASE_DecodedSheet ase) } /* Calculate direction vectors */ - for (struct temp_slice_group_node *temp_slice_group_node = temp_slice_group_head; temp_slice_group_node; temp_slice_group_node = temp_slice_group_node->next) { + for (struct temp_slice_group_node *temp_slice_group_node = temp_slice_group_head; temp_slice_group_node; temp_slice_group_node = temp_slice_group_node->next) + { String ray_suffix = Lit(".ray"); S_SheetSliceGroup *ray_slice_group = temp_slice_group_node->final_slice_group; String ray_slice_name = ray_slice_group->name; - if (StringEndsWith(ray_slice_name, ray_suffix)) { + if (StringEndsWith(ray_slice_name, ray_suffix)) + { String point_slice_name = ray_slice_name; point_slice_name.len -= ray_suffix.len; u64 hash = HashFnv64(Fnv64Basis, point_slice_name); S_SheetSliceGroup *point_slice_group = (S_SheetSliceGroup *)DictValueFromHash(sheet.slice_groups_dict, hash); - if (point_slice_group) { + if (point_slice_group) + { u32 point_slices_per_frame = point_slice_group->per_frame_count; - for (u32 i = 0; i < ase.num_frames; ++i) { + for (u32 i = 0; i < ase.num_frames; ++i) + { /* Use ray slice in ray group */ S_SheetSlice *ray_slice = &ray_slice_group->frame_slices[i * point_slices_per_frame]; Vec2 ray_end = ray_slice->center_px; Vec2 ray_end_norm = ray_slice->center; /* Apply to each point slice in point group */ - for (u32 j = 0; j < point_slices_per_frame; ++j) { + for (u32 j = 0; j < point_slices_per_frame; ++j) + { S_SheetSlice *point_slice = &point_slice_group->frame_slices[(i * point_slices_per_frame) + j]; point_slice->dir_px = SubVec2(ray_end, point_slice->center_px); point_slice->dir = SubVec2(ray_end_norm, point_slice->center); @@ -638,11 +537,12 @@ internal S_Sheet init_sheet_from_ase_result(Arena *arena, ASE_DecodedSheet ase) return sheet; } -internal void cache_entry_load_sheet(struct cache_ref ref, S_Tag tag) +void cache_entry_load_sheet(S_Ref ref, S_Tag tag) { __prof; + S_SharedState *g = &S_shared_state; TempArena scratch = BeginScratchNoConflict(); - struct cache_entry *e = ref.e; + S_CacheEntry *e = ref.e; Atomic32FetchSet(&e->state, CACHE_ENTRY_STATE_WORKING); String path = tag.path; @@ -654,21 +554,25 @@ internal void cache_entry_load_sheet(struct cache_ref ref, S_Tag tag) Assert(e->kind == CACHE_ENTRY_KIND_SHEET); /* TODO: Replace arena allocs w/ buddy allocator */ - e->arena = AllocArena(SHEET_ARENA_RESERVE); + e->arena = AllocArena(S_SheetArenaReserve); { /* Decode */ ASE_DecodedSheet decoded = ZI; { RES_Resource sheet_rs = RES_OpenResource(path); - if (RES_ResourceExists(&sheet_rs)) { + if (RES_ResourceExists(&sheet_rs)) + { decoded = ASE_DecodeSheet(scratch.arena, RES_GetResourceData(&sheet_rs)); - } else { + } + else + { P_LogErrorF("Sprite sheet for \"%F\" not found", FmtString(path)); } RES_CloseResource(&sheet_rs); } - if (decoded.success) { + if (decoded.success) + { RES_Resource sheet_rs = RES_OpenResource(path); decoded = ASE_DecodeSheet(scratch.arena, RES_GetResourceData(&sheet_rs)); RES_CloseResource(&sheet_rs); @@ -684,24 +588,27 @@ internal void cache_entry_load_sheet(struct cache_ref ref, S_Tag tag) } SetArenaReadonly(e->arena); e->memory_usage = e->arena->committed; - Atomic64FetchAdd(&G.cache.memory_usage.v, e->memory_usage); + Atomic64FetchAdd(&g->cache.memory_usage.v, e->memory_usage); - if (success) { + if (success) + { P_LogSuccessF("Loaded sprite sheet [%F] \"%F\" in %F seconds (cache size: %F bytes).", - FmtHex(e->hash.v), - FmtString(path), - FmtFloat(SecondsFromNs(P_TimeNs() - start_ns)), - FmtUint(e->memory_usage)); + FmtHex(e->hash.v), + FmtString(path), + FmtFloat(SecondsFromNs(P_TimeNs() - start_ns)), + FmtUint(e->memory_usage)); } Atomic32FetchSet(&e->state, CACHE_ENTRY_STATE_LOADED); #if RESOURCE_RELOADING - struct cache_bin *bin = &G.cache.bins[e->hash.v % CACHE_BINS_COUNT]; + S_CacheEntryBin *bin = &g->cache.bins[e->hash.v % S_CacheBinsCount]; P_Lock bin_lock = P_LockE(&bin->mutex); { - for (struct cache_entry *old_entry = bin->first; old_entry; old_entry = old_entry->next_in_bin) { - if (old_entry != e && old_entry->hash.v == e->hash.v) { + for (S_CacheEntry *old_entry = bin->first; old_entry; old_entry = old_entry->next_in_bin) + { + if (old_entry != e && old_entry->hash.v == e->hash.v) + { Atomic32FetchSet(&old_entry->out_of_date, 1); } } @@ -717,17 +624,20 @@ internal void cache_entry_load_sheet(struct cache_ref ref, S_Tag tag) * Scope * ========================== */ -internal void refcount_add(struct cache_entry *e, i32 amount) +void refcount_add(S_CacheEntry *e, i32 amount) { - i32 evictor_cycle = Atomic32Fetch(&G.evictor_cycle.v); + S_SharedState *g = &S_shared_state; + i32 evictor_cycle = Atomic32Fetch(&g->evictor_cycle.v); Atomic64 *refcount_atomic = &e->refcount_struct.v; u64 old_refcount_uncast = Atomic64Fetch(refcount_atomic); - for (;;) { - struct cache_refcount new_refcount = *(struct cache_refcount *)&old_refcount_uncast; + for (;;) + { + S_Refcount new_refcount = *(S_Refcount *)&old_refcount_uncast; new_refcount.count += amount; new_refcount.last_ref_cycle = evictor_cycle; u64 v = Atomic64FetchTestSet(refcount_atomic, old_refcount_uncast, *(u64 *)&new_refcount); - if (v == old_refcount_uncast) { + if (v == old_refcount_uncast) + { Assert(new_refcount.count >= 0); break; } @@ -735,22 +645,28 @@ internal void refcount_add(struct cache_entry *e, i32 amount) } } -internal struct sprite_scope_cache_ref *scope_ensure_ref_unsafe(S_Scope *scope, struct cache_entry *e) +S_ScopeCacheRef *scope_ensure_ref_unsafe(S_Scope *scope, S_CacheEntry *e) { - u64 bin_index = e->hash.v % CACHE_BINS_COUNT; + u64 bin_index = e->hash.v % S_CacheBinsCount; - struct sprite_scope_cache_ref **slot = &scope->ref_node_bins[bin_index]; - while (*slot) { - if ((*slot)->ref.e == e) { + S_ScopeCacheRef **slot = &scope->ref_node_bins[bin_index]; + while (*slot) + { + if ((*slot)->ref.e == e) + { /* Found reference in scope */ break; - } else { + } + else + { slot = &(*slot)->next_in_bin; } } - if (*slot == 0) { - if (scope->num_references >= MAX_SCOPE_REFERENCES) { + if (*slot == 0) + { + if (scope->num_references >= S_MaxScopeReferences) + { P_Panic(Lit("Max sprite scope references reached")); } @@ -758,7 +674,7 @@ internal struct sprite_scope_cache_ref *scope_ensure_ref_unsafe(S_Scope *scope, refcount_add(e, 1); /* Grab node from pool */ - struct sprite_scope_cache_ref *scope_ref = &scope->ref_node_pool[scope->num_references++]; + S_ScopeCacheRef *scope_ref = &scope->ref_node_pool[scope->num_references++]; ZeroStruct(scope_ref); scope_ref->ref.e = e; @@ -768,14 +684,15 @@ internal struct sprite_scope_cache_ref *scope_ensure_ref_unsafe(S_Scope *scope, return *slot; } -internal struct sprite_scope_cache_ref *scope_ensure_ref_from_entry(S_Scope *scope, struct cache_entry *e, P_Lock *bin_lock) +S_ScopeCacheRef *scope_ensure_ref_from_entry(S_Scope *scope, S_CacheEntry *e, P_Lock *bin_lock) { + S_SharedState *g = &S_shared_state; /* Guaranteed safe if caller has lock on entry's bin, since entry may not have an existing reference and could otherwise be evicted while ensuring this reference */ - P_AssertLockedES(bin_lock, &G.cache.bins[e->hash.v % CACHE_BINS_COUNT].mutex); + P_AssertLockedES(bin_lock, &g->cache.bins[e->hash.v % S_CacheBinsCount].mutex); return scope_ensure_ref_unsafe(scope, e); } -internal struct sprite_scope_cache_ref *scope_ensure_ref_from_ref(S_Scope *scope, struct cache_ref ref) +S_ScopeCacheRef *scope_ensure_ref_from_ref(S_Scope *scope, S_Ref ref) { /* Safe since caller has ref */ return scope_ensure_ref_unsafe(scope, ref.e); @@ -783,28 +700,32 @@ internal struct sprite_scope_cache_ref *scope_ensure_ref_from_ref(S_Scope *scope S_Scope *sprite_scope_begin(void) { + S_SharedState *g = &S_shared_state; /* Alloc scope */ S_Scope *result = 0; - struct sprite_scope_cache_ref **bins = 0; - struct sprite_scope_cache_ref *pool = 0; + S_ScopeCacheRef **bins = 0; + S_ScopeCacheRef *pool = 0; { - P_Lock lock = P_LockE(&G.scopes_mutex); + P_Lock lock = P_LockE(&g->scopes_mutex); { - if (G.first_free_scope) { - result = G.first_free_scope; - G.first_free_scope = result->next_free; + if (g->first_free_scope) + { + result = g->first_free_scope; + g->first_free_scope = result->next_free; bins = result->ref_node_bins; pool = result->ref_node_pool; - } else { - result = PushStructNoZero(G.scopes_arena, S_Scope); - bins = PushStructsNoZero(G.scopes_arena, struct sprite_scope_cache_ref *, CACHE_BINS_COUNT); - pool = PushStructsNoZero(G.scopes_arena, struct sprite_scope_cache_ref, MAX_SCOPE_REFERENCES); + } + else + { + result = PushStructNoZero(g->scopes_arena, S_Scope); + bins = PushStructsNoZero(g->scopes_arena, S_ScopeCacheRef *, S_CacheBinsCount); + pool = PushStructsNoZero(g->scopes_arena, S_ScopeCacheRef, S_MaxScopeReferences); } } P_Unlock(&lock); } ZeroStruct(result); - ZeroBytes(bins, sizeof(*bins) * CACHE_BINS_COUNT); + ZeroBytes(bins, sizeof(*bins) * S_CacheBinsCount); result->ref_node_bins = bins; result->ref_node_pool = pool; return result; @@ -812,18 +733,20 @@ S_Scope *sprite_scope_begin(void) void sprite_scope_end(S_Scope *scope) { + S_SharedState *g = &S_shared_state; /* Dereference entries */ u64 num_references = scope->num_references; - for (u64 i = 0; i < num_references; ++i) { - struct sprite_scope_cache_ref *n = &scope->ref_node_pool[i]; + for (u64 i = 0; i < num_references; ++i) + { + S_ScopeCacheRef *n = &scope->ref_node_pool[i]; refcount_add(n->ref.e, -1); } /* Release scope */ - P_Lock lock = P_LockE(&G.scopes_mutex); + P_Lock lock = P_LockE(&g->scopes_mutex); { - scope->next_free = G.first_free_scope; - G.first_free_scope = scope; + scope->next_free = g->first_free_scope; + g->first_free_scope = scope; } P_Unlock(&lock); } @@ -832,34 +755,41 @@ void sprite_scope_end(S_Scope *scope) * Cache interface * ========================== */ -internal struct sprite_scope_cache_ref *cache_lookup(S_Scope *scope, struct cache_entry_hash hash, P_Lock *bin_lock) +S_ScopeCacheRef *cache_lookup(S_Scope *scope, S_Hash hash, P_Lock *bin_lock) { - struct sprite_scope_cache_ref *scope_ref = 0; + S_SharedState *g = &S_shared_state; + S_ScopeCacheRef *scope_ref = 0; - struct cache_bin *bin = &G.cache.bins[hash.v % CACHE_BINS_COUNT]; + S_CacheEntryBin *bin = &g->cache.bins[hash.v % S_CacheBinsCount]; P_AssertLockedES(bin_lock, &bin->mutex); /* Lock required for iterating bin */ #if RESOURCE_RELOADING /* If resource reloading is enabled, then we want to find the * newest entry rather than the first one that exists since * there may be more than one matching entry in the cache */ - struct cache_entry *match = 0; - enum cache_entry_state match_state = CACHE_ENTRY_STATE_NONE; - for (struct cache_entry *entry = bin->first; entry; entry = entry->next_in_bin) { - if (entry->hash.v == hash.v) { - enum cache_entry_state entry_state = Atomic32Fetch(&entry->state); - if (!match || entry_state > match_state || (entry_state == CACHE_ENTRY_STATE_LOADED && match_state == CACHE_ENTRY_STATE_LOADED && entry->load_time_ns > match->load_time_ns)) { + S_CacheEntry *match = 0; + S_CacheEntryState match_state = CACHE_ENTRY_STATE_NONE; + for (S_CacheEntry *entry = bin->first; entry; entry = entry->next_in_bin) + { + if (entry->hash.v == hash.v) + { + S_CacheEntryState entry_state = Atomic32Fetch(&entry->state); + if (!match || entry_state > match_state || (entry_state == CACHE_ENTRY_STATE_LOADED && match_state == CACHE_ENTRY_STATE_LOADED && entry->load_time_ns > match->load_time_ns)) + { match = entry; match_state = entry_state; } } } - if (match) { + if (match) + { scope_ref = scope_ensure_ref_from_entry(scope, match, bin_lock); } #else - for (struct cache_entry *entry = bin->first; entry; entry = entry->next_in_bin) { - if (entry->hash.v == hash.v) { + for (S_CacheEntry *entry = bin->first; entry; entry = entry->next_in_bin) + { + if (entry->hash.v == hash.v) + { scope_ref = scope_ensure_ref_from_entry(scope, entry, bin_lock); break; } @@ -869,17 +799,21 @@ internal struct sprite_scope_cache_ref *cache_lookup(S_Scope *scope, struct cach return scope_ref; } -internal struct sprite_scope_cache_ref *cache_entry_from_tag(S_Scope *scope, S_Tag tag, enum cache_entry_kind kind, b32 force_new) +S_ScopeCacheRef *cache_entry_from_tag(S_Scope *scope, S_Tag tag, S_CacheEntryKind kind, b32 force_new) { - struct cache_entry_hash hash = cache_entry_hash_from_tag_hash(tag.hash, kind); - u64 bin_index = hash.v % CACHE_BINS_COUNT; - struct sprite_scope_cache_ref *scope_ref = 0; + S_SharedState *g = &S_shared_state; + S_Hash hash = cache_entry_hash_from_tag_hash(tag.hash, kind); + u64 bin_index = hash.v % S_CacheBinsCount; + S_ScopeCacheRef *scope_ref = 0; /* Search for entry in scope */ - if (!force_new) { + if (!force_new) + { scope_ref = scope->ref_node_bins[bin_index]; - while (scope_ref) { - if (scope_ref->ref.e->hash.v == hash.v) { + while (scope_ref) + { + if (scope_ref->ref.e->hash.v == hash.v) + { break; } scope_ref = scope_ref->next_in_bin; @@ -887,11 +821,13 @@ internal struct sprite_scope_cache_ref *cache_entry_from_tag(S_Scope *scope, S_T } /* If not in scope, search for entry in cache */ - if (!scope_ref) { - struct cache_bin *bin = &G.cache.bins[bin_index]; + if (!scope_ref) + { + S_CacheEntryBin *bin = &g->cache.bins[bin_index]; /* Search in cache */ - if (!force_new) { + if (!force_new) + { P_Lock bin_lock = P_LockS(&bin->mutex); { scope_ref = cache_lookup(scope, hash, &bin_lock); @@ -900,24 +836,30 @@ internal struct sprite_scope_cache_ref *cache_entry_from_tag(S_Scope *scope, S_T } /* If not in cache, allocate new entry */ - if (!scope_ref) { + if (!scope_ref) + { P_Lock bin_lock = P_LockE(&bin->mutex); { /* Search cache one more time in case an entry was allocated between locks */ - if (!force_new) { + if (!force_new) + { scope_ref = cache_lookup(scope, hash, &bin_lock); } - if (!scope_ref) { + if (!scope_ref) + { /* Cache entry still absent, allocate new entry */ - struct cache_entry *entry = 0; + S_CacheEntry *entry = 0; { - P_Lock pool_lock = P_LockE(&G.cache.entry_pool_mutex); - if (G.cache.entry_pool_first_free) { - entry = G.cache.entry_pool_first_free; - G.cache.entry_pool_first_free = entry->next_free; - } else { - entry = PushStructNoZero(G.cache.arena, struct cache_entry); + P_Lock pool_lock = P_LockE(&g->cache.entry_pool_mutex); + if (g->cache.entry_pool_first_free) + { + entry = g->cache.entry_pool_first_free; + g->cache.entry_pool_first_free = entry->next_free; + } + else + { + entry = PushStructNoZero(g->cache.arena, S_CacheEntry); } P_Unlock(&pool_lock); } @@ -925,18 +867,21 @@ internal struct sprite_scope_cache_ref *cache_entry_from_tag(S_Scope *scope, S_T /* Init entry and add to bin */ { - if (bin->last) { + if (bin->last) + { bin->last->next_in_bin = entry; entry->prev_in_bin = bin->last; - } else { + } + else + { bin->first = entry; } bin->last = entry; } entry->hash = cache_entry_hash_from_tag_hash(tag.hash, kind); entry->kind = kind; - entry->texture = G.nil_texture; - entry->sheet = G.nil_sheet; + entry->texture = g->nil_texture; + entry->sheet = g->nil_sheet; scope_ref = scope_ensure_ref_from_entry(scope, entry, &bin_lock); } @@ -948,43 +893,56 @@ internal struct sprite_scope_cache_ref *cache_entry_from_tag(S_Scope *scope, S_T return scope_ref; } -internal void *data_from_tag_internal(S_Scope *scope, S_Tag tag, enum cache_entry_kind kind, b32 await) +void *data_from_tag_internal(S_Scope *scope, S_Tag tag, S_CacheEntryKind kind, b32 await) { + S_SharedState *g = &S_shared_state; /* TODO: Replace switch statements */ void *result = 0; - switch (kind) { - case CACHE_ENTRY_KIND_TEXTURE: { result = G.loading_texture; } break; - case CACHE_ENTRY_KIND_SHEET: { result = G.loading_sheet; } break; + switch (kind) + { + case CACHE_ENTRY_KIND_TEXTURE: { result = g->loading_texture; } break; + case CACHE_ENTRY_KIND_SHEET: { result = g->loading_sheet; } break; default: { P_Panic(Lit("Unknown sprite cache entry kind")); } break; } - struct sprite_scope_cache_ref *scope_ref = cache_entry_from_tag(scope, tag, kind, 0); - struct cache_ref ref = scope_ref->ref; + S_ScopeCacheRef *scope_ref = cache_entry_from_tag(scope, tag, kind, 0); + S_Ref ref = scope_ref->ref; - enum cache_entry_state state = Atomic32Fetch(&ref.e->state); - if (state == CACHE_ENTRY_STATE_LOADED) { - switch (kind) { + S_CacheEntryState state = Atomic32Fetch(&ref.e->state); + if (state == CACHE_ENTRY_STATE_LOADED) + { + switch (kind) + { case CACHE_ENTRY_KIND_TEXTURE: { result = ref.e->texture; } break; case CACHE_ENTRY_KIND_SHEET: { result = ref.e->sheet; } break; default: { P_Panic(Lit("Unknown sprite cache entry kind")); } break; } - } else if (state == CACHE_ENTRY_STATE_NONE) { + } + else if (state == CACHE_ENTRY_STATE_NONE) + { /* If entry is new, load texture */ - if (Atomic32FetchTestSet(&ref.e->state, CACHE_ENTRY_STATE_NONE, CACHE_ENTRY_STATE_QUEUED) == CACHE_ENTRY_STATE_NONE) { + if (Atomic32FetchTestSet(&ref.e->state, CACHE_ENTRY_STATE_NONE, CACHE_ENTRY_STATE_QUEUED) == CACHE_ENTRY_STATE_NONE) + { /* If caller is awaiting result then just load now on the calling thread. Otherwise spawn a work task. */ - if (await) { - switch (kind) { - case CACHE_ENTRY_KIND_TEXTURE: { + if (await) + { + switch (kind) + { + case CACHE_ENTRY_KIND_TEXTURE: + { cache_entry_load_texture(ref, tag); result = ref.e->texture; } break; - case CACHE_ENTRY_KIND_SHEET: { + case CACHE_ENTRY_KIND_SHEET: + { cache_entry_load_sheet(ref, tag); result = ref.e->sheet; } break; default: { P_Panic(Lit("Unknown sprite cache entry kind")); } break; } - } else { + } + else + { /* Allocate cmd */ push_load_job(ref, tag); } @@ -992,8 +950,10 @@ internal void *data_from_tag_internal(S_Scope *scope, S_Tag tag, enum cache_entr } /* Spinlock until result is ready */ - if (await && state != CACHE_ENTRY_STATE_LOADED) { - while (Atomic32Fetch(&ref.e->state) != CACHE_ENTRY_STATE_LOADED) { + if (await && state != CACHE_ENTRY_STATE_LOADED) + { + while (Atomic32Fetch(&ref.e->state) != CACHE_ENTRY_STATE_LOADED) + { IxPause(); } } @@ -1043,7 +1003,8 @@ void sprite_sheet_from_tag_prefetch(S_Scope *scope, S_Tag tag) S_SheetFrame sprite_sheet_get_frame(S_Sheet *sheet, u32 index) { - if (index < sheet->frames_count ) { + if (index < sheet->frames_count) + { return sheet->frames[index]; } S_SheetFrame result = ZI; @@ -1056,48 +1017,59 @@ S_SheetFrame sprite_sheet_get_frame(S_Sheet *sheet, u32 index) S_SheetSpan sprite_sheet_get_span(S_Sheet *sheet, String name) { S_SheetSpan result = ZI; - if (sheet->spans_count > 0) { + if (sheet->spans_count > 0) + { u64 hash = HashFnv64(Fnv64Basis, name); S_SheetSpan *entry = (S_SheetSpan *)DictValueFromHash(sheet->spans_dict, hash); - if (entry) { + if (entry) + { result = *entry; } } return result; } +/* Returns first slice with name in frame */ S_SheetSlice sprite_sheet_get_slice(S_Sheet *sheet, String name, u32 frame_index) { - if (sheet->slice_groups_count > 0) { + if (sheet->slice_groups_count > 0) + { u64 hash = HashFnv64(Fnv64Basis, name); S_SheetSliceGroup *group = (S_SheetSliceGroup *)DictValueFromHash(sheet->slice_groups_dict, hash); - if (group) { + if (group) + { return group->frame_slices[frame_index * group->per_frame_count]; } } /* Return 'pivot' by default */ S_SheetSlice result = ZI; - if (EqString(name, Lit("pivot"))) { + if (EqString(name, Lit("pivot"))) + { /* 'pivot' slice does not exist, return center */ result.center = VEC2(0, 0); result.center_px = MulVec2(sheet->frame_size, 0.5f); result.dir_px = VEC2(result.center_px.x, 0); result.dir = VEC2(0, -0.5); - } else { + } + else + { result = sprite_sheet_get_slice(sheet, Lit("pivot"), frame_index); } return result; } +/* Returns all slices with name in frame */ S_SheetSliceArray sprite_sheet_get_slices(S_Sheet *sheet, String name, u32 frame_index) { S_SheetSliceArray result = ZI; - if (sheet->slice_groups_count > 0) { + if (sheet->slice_groups_count > 0) + { u64 hash = HashFnv64(Fnv64Basis, name); S_SheetSliceGroup *group = (S_SheetSliceGroup *)DictValueFromHash(sheet->slice_groups_dict, hash); - if (group) { + if (group) + { result.count = group->per_frame_count; result.slices = &group->frame_slices[frame_index * group->per_frame_count]; } @@ -1109,28 +1081,32 @@ S_SheetSliceArray sprite_sheet_get_slices(S_Sheet *sheet, String name, u32 frame * Load job * ========================== */ -internal P_JobDef(sprite_load_job, job) +P_JobDef(S_SpriteLoadJob, job) { __prof; - struct load_cmd *cmd = job.sig; - struct cache_ref ref = cmd->ref; + S_SharedState *g = &S_shared_state; + S_Cmd *cmd = job.sig; + S_Ref ref = cmd->ref; - switch (ref.e->kind) { - case CACHE_ENTRY_KIND_TEXTURE: { + switch (ref.e->kind) + { + case CACHE_ENTRY_KIND_TEXTURE: + { cache_entry_load_texture(ref, cmd->tag); } break; - case CACHE_ENTRY_KIND_SHEET: { + case CACHE_ENTRY_KIND_SHEET: + { cache_entry_load_sheet(ref, cmd->tag); } break; default: { P_Panic(Lit("Unknown sprite cache node kind")); } break; } /* Free cmd */ - P_Lock lock = P_LockE(&G.load_cmds_mutex); + P_Lock lock = P_LockE(&g->cmds_mutex); { sprite_scope_end(cmd->scope); - cmd->next_free = G.first_free_load_cmd; - G.first_free_load_cmd = cmd; + cmd->next_free = g->first_free_cmd; + g->first_free_cmd = cmd; } P_Unlock(&lock); } @@ -1141,36 +1117,40 @@ internal P_JobDef(sprite_load_job, job) #if RESOURCE_RELOADING -internal void reload_if_exists(S_Scope *scope, S_Tag tag, enum cache_entry_kind kind) +void S_ReloadSpriteFromTag(S_Scope *scope, S_Tag tag, S_CacheEntryKind kind) { - struct cache_entry_hash hash = cache_entry_hash_from_tag_hash(tag.hash, kind); - struct cache_bin *bin = &G.cache.bins[hash.v % CACHE_BINS_COUNT]; - struct sprite_scope_cache_ref *existing_ref = 0; - P_Lock bin_lock = P_LockS(&bin->mutex); + S_SharedState *g = &S_shared_state; + S_Hash hash = cache_entry_hash_from_tag_hash(tag.hash, kind); + S_CacheEntryBin *bin = &g->cache.bins[hash.v % S_CacheBinsCount]; + S_ScopeCacheRef *existing_ref = 0; + P_Lock bin_lock = P_LockS(&bin->mutex); { existing_ref = cache_lookup(scope, hash, &bin_lock); } P_Unlock(&bin_lock); - if (existing_ref) { + if (existing_ref) + { P_LogInfoF("Sprite resource file \"%F\" has changed for sprite [%F].", FmtString(tag.path), FmtHex(hash.v)); - struct sprite_scope_cache_ref *scope_ref = cache_entry_from_tag(scope, tag, kind, 1); + S_ScopeCacheRef *scope_ref = cache_entry_from_tag(scope, tag, kind, 1); push_load_job(scope_ref->ref, tag); } } -internal W_CallbackFuncDef(sprite_watch_callback, name) +W_CallbackFuncDef(S_WatchSpriteCallback, name) { S_Scope *scope = sprite_scope_begin(); - if (StringStartsWith(name, Lit("res/"))) { + if (StringStartsWith(name, Lit("res/"))) + { name.len -= Lit("res/").len; name.text += Lit("res/").len; } S_Tag tag = sprite_tag_from_path(name); - for (enum cache_entry_kind kind = 0; kind < NUM_CACHE_ENTRY_KINDS; ++kind) { - reload_if_exists(scope, tag, kind); + for (S_CacheEntryKind kind = 0; kind < NUM_CACHE_ENTRY_KINDS; ++kind) + { + S_ReloadSpriteFromTag(scope, tag, kind); } sprite_scope_end(scope); @@ -1178,23 +1158,13 @@ internal W_CallbackFuncDef(sprite_watch_callback, name) #endif -/* ========================== * - * Evictor job - * ========================== */ +//////////////////////////////// +//~ Evictor -struct evict_node { - i32 last_ref_cycle; - struct cache_entry *cache_entry; - struct cache_bin *cache_bin; - - struct evict_node *next_evicted; -}; - -internal MergesortCompareFuncDef(evict_sort, arg_a, arg_b, udata) +MergesortCompareFuncDef(S_EvictorSortCmp, arg_a, arg_b, _) { - (UNUSED)udata; - struct evict_node *a = arg_a; - struct evict_node *b = arg_b; + S_EvictorNode *a = arg_a; + S_EvictorNode *b = arg_b; i32 a_cycle = a->last_ref_cycle; i32 b_cycle = b->last_ref_cycle; return (b_cycle > a_cycle) - (a_cycle > b_cycle); @@ -1210,44 +1180,52 @@ internal MergesortCompareFuncDef(evict_sort, arg_a, arg_b, udata) * - The cache is over its memory budget and the node's last reference is longer ago than the grace period * - Resource reloading is enabled and the node is out of date due to a change to its original resource file */ -internal P_JobDef(sprite_evictor_job, _) +P_JobDef(S_EvictorJob, _) { + S_SharedState *g = &S_shared_state; b32 shutdown = 0; - while (!shutdown) { + while (!shutdown) + { { __profn("Sprite evictor cycle"); TempArena scratch = BeginScratchNoConflict(); u64 evict_array_count = 0; - struct evict_node *evict_array = PushDry(scratch.arena, struct evict_node); + S_EvictorNode *evict_array = PushDry(scratch.arena, S_EvictorNode); { - i32 cur_cycle = Atomic32Fetch(&G.evictor_cycle.v); + i32 cur_cycle = Atomic32Fetch(&g->evictor_cycle.v); /* Scan for evictable nodes */ - b32 cache_over_budget_threshold = Atomic64Fetch(&G.cache.memory_usage.v) > (i64)CACHE_MEMORY_BUDGET_THRESHOLD; - if (cache_over_budget_threshold || RESOURCE_RELOADING) { + b32 cache_over_budget_threshold = Atomic64Fetch(&g->cache.memory_usage.v) > (i64)S_CacheMemoryBudgetThreshold; + if (cache_over_budget_threshold || RESOURCE_RELOADING) + { __profn("Evictor scan"); - for (u64 i = 0; i < CACHE_BINS_COUNT; ++i) { - struct cache_bin *bin = &G.cache.bins[i]; + for (u64 i = 0; i < S_CacheBinsCount; ++i) + { + S_CacheEntryBin *bin = &g->cache.bins[i]; P_Lock bin_lock = P_LockS(&bin->mutex); { - struct cache_entry *n = bin->first; - while (n) { + S_CacheEntry *n = bin->first; + while (n) + { u64 refcount_uncast = Atomic64Fetch(&n->refcount_struct.v); - struct cache_refcount refcount = *(struct cache_refcount *)&refcount_uncast; - if (refcount.count <= 0) { + S_Refcount refcount = *(S_Refcount *)&refcount_uncast; + if (refcount.count <= 0) + { /* Add node to evict list */ #if RESOURCE_RELOADING b32 is_out_of_date = Atomic32Fetch(&n->out_of_date); #else b32 is_out_of_date = 0; #endif - b32 is_old = cache_over_budget_threshold && ((cur_cycle - refcount.last_ref_cycle) > EVICTOR_GRACE_PERIOD_CYCLES); - if (is_old || is_out_of_date) { - struct evict_node *en = PushStruct(scratch.arena, struct evict_node); + b32 is_old = cache_over_budget_threshold && ((cur_cycle - refcount.last_ref_cycle) > S_EvictorGracePeriodCycles); + if (is_old || is_out_of_date) + { + S_EvictorNode *en = PushStruct(scratch.arena, S_EvictorNode); en->cache_entry = n; en->cache_bin = bin; en->last_ref_cycle = refcount.last_ref_cycle; - if (is_out_of_date) { + if (is_out_of_date) + { en->last_ref_cycle = -1; } ++evict_array_count; @@ -1267,47 +1245,59 @@ internal P_JobDef(sprite_evictor_job, _) /* Sort evict nodes */ { __profn("Evictor sort"); - Mergesort(evict_array, evict_array_count, sizeof(*evict_array), evict_sort, 0); + Mergesort(evict_array, evict_array_count, sizeof(*evict_array), S_EvictorSortCmp, 0); } /* Remove evictable nodes from cache until under budget */ - struct evict_node *first_evicted = 0; + S_EvictorNode *first_evicted = 0; { __profn("Evictor cache removal"); b32 stop_evicting = 0; - for (u64 i = 0; i < evict_array_count && !stop_evicting; ++i) { - struct evict_node *en = &evict_array[i]; - struct cache_bin *bin = en->cache_bin; - struct cache_entry *entry = en->cache_entry; + for (u64 i = 0; i < evict_array_count && !stop_evicting; ++i) + { + S_EvictorNode *en = &evict_array[i]; + S_CacheEntryBin *bin = en->cache_bin; + S_CacheEntry *entry = en->cache_entry; i32 last_ref_cycle = en->last_ref_cycle; - b32 cache_over_budget_target = Atomic64Fetch(&G.cache.memory_usage.v) > (i64)CACHE_MEMORY_BUDGET_TARGET; + b32 cache_over_budget_target = Atomic64Fetch(&g->cache.memory_usage.v) > (i64)S_CacheMemoryBudgetTarget; P_Lock bin_lock = P_LockE(&bin->mutex); { u64 refcount_uncast = Atomic64Fetch(&entry->refcount_struct.v); - struct cache_refcount refcount = *(struct cache_refcount *)&refcount_uncast; - if (refcount.count > 0 || (last_ref_cycle >= 0 && refcount.last_ref_cycle != en->last_ref_cycle)) { + S_Refcount refcount = *(S_Refcount *)&refcount_uncast; + if (refcount.count > 0 || (last_ref_cycle >= 0 && refcount.last_ref_cycle != en->last_ref_cycle)) + { /* Cache node has been referenced since scan, skip node. */ - } else if (cache_over_budget_target || last_ref_cycle < 0) { + } + else if (cache_over_budget_target || last_ref_cycle < 0) + { /* Remove from cache bin */ - struct cache_entry *prev = entry->prev_in_bin; - struct cache_entry *next = entry->next_in_bin; - if (prev) { + S_CacheEntry *prev = entry->prev_in_bin; + S_CacheEntry *next = entry->next_in_bin; + if (prev) + { prev->next_in_bin = next; - } else { + } + else + { bin->first = next; } - if (next) { + if (next) + { next->prev_in_bin = prev; - } else { + } + else + { bin->last = prev; } - Atomic64FetchAdd(&G.cache.memory_usage.v, -((i64)entry->memory_usage)); + Atomic64FetchAdd(&g->cache.memory_usage.v, -((i64)entry->memory_usage)); /* Add to evicted list */ en->next_evicted = first_evicted; first_evicted = en; - } else { + } + else + { /* Cache is no longer over budget or force evicting, stop iteration */ stop_evicting = 1; } @@ -1316,13 +1306,16 @@ internal P_JobDef(sprite_evictor_job, _) } } - if (first_evicted) { + if (first_evicted) + { /* Release evicted node memory */ { __profn("Evictor memory release"); - for (struct evict_node *en = first_evicted; en; en = en->next_evicted) { - struct cache_entry *n = en->cache_entry; - if (n->kind == CACHE_ENTRY_KIND_TEXTURE && n->texture->valid) { + for (S_EvictorNode *en = first_evicted; en; en = en->next_evicted) + { + S_CacheEntry *n = en->cache_entry; + if (n->kind == CACHE_ENTRY_KIND_TEXTURE && n->texture->valid) + { GPU_ReleaseResourceFenced(n->texture->gp_texture); } ReleaseArena(n->arena); @@ -1332,28 +1325,30 @@ internal P_JobDef(sprite_evictor_job, _) /* Add evicted nodes to free list */ { __profn("Evictor free list append"); - P_Lock pool_lock = P_LockE(&G.cache.entry_pool_mutex); - for (struct evict_node *en = first_evicted; en; en = en->next_evicted) { - struct cache_entry *n = en->cache_entry; - n->next_free = G.cache.entry_pool_first_free; - G.cache.entry_pool_first_free = n; + P_Lock pool_lock = P_LockE(&g->cache.entry_pool_mutex); + for (S_EvictorNode *en = first_evicted; en; en = en->next_evicted) + { + S_CacheEntry *n = en->cache_entry; + n->next_free = g->cache.entry_pool_first_free; + g->cache.entry_pool_first_free = n; } P_Unlock(&pool_lock); } } } - Atomic32FetchAdd(&G.evictor_cycle.v, 1); + Atomic32FetchAdd(&g->evictor_cycle.v, 1); EndScratch(scratch); } /* Evictor sleep */ { - P_Lock lock = P_LockE(&G.evictor_scheduler_mutex); + P_Lock lock = P_LockE(&g->evictor_scheduler_mutex); { - if (!G.evictor_scheduler_shutdown) { - P_WaitOnCvTime(&G.evictor_scheduler_shutdown_cv, &lock, EVICTOR_CYCLE_INTERVAL_NS); + if (!g->evictor_scheduler_shutdown) + { + P_WaitOnCvTime(&g->evictor_scheduler_shutdown_cv, &lock, S_EvictorCycleIntervalNs); } - shutdown = G.evictor_scheduler_shutdown; + shutdown = g->evictor_scheduler_shutdown; } P_Unlock(&lock); } diff --git a/src/sprite/sprite_core.h b/src/sprite/sprite_core.h index 4a329e93..181778e5 100644 --- a/src/sprite/sprite_core.h +++ b/src/sprite/sprite_core.h @@ -1,48 +1,17 @@ -/* ========================== * - * Startup - * ========================== */ +//////////////////////////////// +//~ Tag -typedef struct S_StartupReceipt S_StartupReceipt; -struct S_StartupReceipt { i32 _; }; -S_StartupReceipt sprite_startup(void); - -/* ========================== * - * Tag - * ========================== */ - -typedef struct S_Tag S_Tag; -struct S_Tag { +Struct(S_Tag) +{ u64 hash; String path; }; -Inline S_Tag sprite_tag_nil(void) { return (S_Tag) { 0 }; } +//////////////////////////////// +//~ Texture -S_Tag sprite_tag_from_path(String path); -b32 sprite_tag_is_nil(S_Tag tag); -b32 sprite_tag_eq(S_Tag t1, S_Tag t2); - -/* ========================== * - * Scope - * ========================== */ - -typedef struct S_Scope S_Scope; -struct S_Scope { - struct sprite_scope_cache_ref **ref_node_bins; - struct sprite_scope_cache_ref *ref_node_pool; - u64 num_references; - S_Scope *next_free; -}; - -S_Scope *sprite_scope_begin(void); -void sprite_scope_end(S_Scope *scope); - -/* ========================== * - * Texture load - * ========================== */ - -typedef struct S_Texture S_Texture; -struct S_Texture { +Struct(S_Texture) +{ b32 loaded; b32 valid; GPU_Resource *gp_texture; @@ -50,60 +19,25 @@ struct S_Texture { u32 height; }; -S_Texture *sprite_texture_from_tag_await(S_Scope *scope, S_Tag tag); -S_Texture *sprite_texture_from_tag_async(S_Scope *scope, S_Tag tag); -void sprite_texture_from_tag_prefetch(S_Scope *scope, S_Tag tag); +//////////////////////////////// +//~ Sheet -/* ========================== * - * Sheet load - * ========================== */ - -typedef struct S_SheetSliceGroup S_SheetSliceGroup; -typedef struct S_SheetSpan S_SheetSpan; -typedef struct S_SheetFrame S_SheetFrame; -typedef struct S_Sheet S_Sheet; -struct S_Sheet { - b32 loaded; - b32 valid; - Vec2 image_size; - Vec2 frame_size; - - u32 frames_count; - S_SheetFrame *frames; - - u32 spans_count; - S_SheetSpan *spans; - Dict *spans_dict; - - u32 slice_groups_count; - S_SheetSliceGroup *slice_groups; - Dict *slice_groups_dict; -}; - -S_Sheet *sprite_sheet_from_tag_await(S_Scope *scope, S_Tag tag); -S_Sheet *sprite_sheet_from_tag_async(S_Scope *scope, S_Tag tag); -void sprite_sheet_from_tag_prefetch(S_Scope *scope, S_Tag tag); - -/* ========================== * - * Sheet query - * ========================== */ - -typedef struct S_SheetFrame S_SheetFrame; -struct S_SheetFrame { +Struct(S_SheetFrame) +{ u32 index; f64 duration; ClipRect clip; }; -typedef struct S_SheetSpan S_SheetSpan; -struct S_SheetSpan { +Struct(S_SheetSpan) +{ String name; u32 start; u32 end; }; -typedef struct S_SheetSlice S_SheetSlice; -struct S_SheetSlice { +Struct(S_SheetSlice) +{ /* If 1, this slice was not copied over from another frame in the sprite sheet */ b32 original; @@ -121,14 +55,14 @@ struct S_SheetSlice { Vec2 dir_px; }; -typedef struct S_SheetSliceArray S_SheetSliceArray; -struct S_SheetSliceArray { +Struct(S_SheetSliceArray) +{ u64 count; S_SheetSlice *slices; }; -typedef struct S_SheetSliceGroup S_SheetSliceGroup; -struct S_SheetSliceGroup { +Struct(S_SheetSliceGroup) +{ String name; u64 per_frame_count; @@ -137,6 +71,321 @@ struct S_SheetSliceGroup { S_SheetSlice *frame_slices; }; +Struct(S_Sheet) +{ + b32 loaded; + b32 valid; + Vec2 image_size; + Vec2 frame_size; + + u32 frames_count; + S_SheetFrame *frames; + + u32 spans_count; + S_SheetSpan *spans; + Dict *spans_dict; + + u32 slice_groups_count; + S_SheetSliceGroup *slice_groups; + Dict *slice_groups_dict; +}; + +//////////////////////////////// +//~ Cache + +typedef i32 S_CacheEntryKind; enum +{ + CACHE_ENTRY_KIND_TEXTURE, + CACHE_ENTRY_KIND_SHEET, + + NUM_CACHE_ENTRY_KINDS +}; + +typedef i32 S_CacheEntryState; enum +{ + CACHE_ENTRY_STATE_NONE, + CACHE_ENTRY_STATE_QUEUED, + CACHE_ENTRY_STATE_WORKING, + CACHE_ENTRY_STATE_LOADED +}; + +Struct(S_Refcount) +{ + i32 count; /* Number of scopes currently holding a reference to this entry */ + i32 last_ref_cycle; /* Last evictor cycle that the refcount was modified */ +}; +StaticAssert(sizeof(S_Refcount) == 8); /* Must fit into 64 bit atomic */ + +Struct(S_Hash) +{ + u64 v; +}; + +Struct(S_CacheEntry) +{ + S_CacheEntryKind kind; + S_Hash hash; + Atomic32 state; + Atomic64Padded refcount_struct; /* Cast fetched result to `cache_refcount` */ + + /* Allocated data */ + /* NOTE: This data is finalized once entry state = loaded */ + i64 load_time_ns; + u64 memory_usage; + Arena *arena; + S_Texture *texture; + S_Sheet *sheet; + + /* Hash list */ + S_CacheEntry *next_in_bin; + S_CacheEntry *prev_in_bin; + + /* Free list */ + S_CacheEntry *next_free; + +#if RESOURCE_RELOADING + Atomic32 out_of_date; /* Has the resource changed since this entry was loaded */ +#endif +}; + +Struct(S_CacheEntryBin) +{ + P_Mutex mutex; + S_CacheEntry *first; + S_CacheEntry *last; +}; + +Struct(S_Cache) +{ + Atomic64Padded memory_usage; + Arena *arena; + S_CacheEntryBin *bins; + P_Mutex entry_pool_mutex; + S_CacheEntry *entry_pool_first_free; +}; + +/* Represents a reference that can be used to safely access cache entry without it becoming evicted during the reference's lifetime */ +Struct(S_Ref) +{ + S_CacheEntry *e; +}; + +//////////////////////////////// +//~ Scope + +/* A cache reference whose lifetime is bound to the scope it was retrieved from */ +Struct(S_ScopeCacheRef) +{ + S_Ref ref; + S_ScopeCacheRef *next_in_bin; +}; + +Struct(S_Scope) +{ + S_ScopeCacheRef **ref_node_bins; + S_ScopeCacheRef *ref_node_pool; + u64 num_references; + S_Scope *next_free; +}; + +//////////////////////////////// +//~ Cmd + +Struct(S_Cmd) +{ + S_Cmd *next_free; + S_Scope *scope; + S_Ref ref; + S_Tag tag; + u8 tag_path_buff[512]; +}; + +//////////////////////////////// +//~ Evictor + +Struct(S_EvictorNode) +{ + i32 last_ref_cycle; + S_CacheEntry *cache_entry; + S_CacheEntryBin *cache_bin; + S_EvictorNode *next_evicted; +}; + +//////////////////////////////// +//~ Cache constants + +/* The evictor will begin evicting once cache usage is > threshold. + * It will entries until the budget has shrunk < target. */ +#define S_CacheMemoryBudgetThreshold (Mebi(256)) +#define S_CacheMemoryBudgetTarget (Mebi(128)) +StaticAssert(S_CacheMemoryBudgetThreshold >= S_CacheMemoryBudgetTarget); + +#define S_CacheBinsCount 1024 + +#define S_MaxScopeReferences 1024 + +/* Texture arena only used to store texture struct at the moment. Actual image data is allocated on GPU. */ +#define S_TextureArenaReserve Mebi(1) + +#define S_SheetArenaReserve Mebi(64) +#define S_SheetSpanLookupTableBinRatio 2.0 +#define S_SliceLookupTableBinRatio 2.0 + +/* How long between evictor cycles */ +#define S_EvictorCycleIntervalNs NsFromSeconds(0.500) + +/* How many cycles a cache entry spends unused until it's considered evictable */ +#define S_EvictorGracePeriodCycles (NsFromSeconds(10.000) / S_EvictorCycleIntervalNs) + +//////////////////////////////// +//~ Shared state + +Struct(S_SharedState) +{ + Arena *perm_arena; + S_Texture *nil_texture; + S_Texture *loading_texture; + S_Sheet *nil_sheet; + S_Sheet *loading_sheet; + + /* Cache */ + S_Cache cache; + + /* Cmds */ + P_Mutex cmds_mutex; + Arena *cmds_arena; + S_Cmd *first_free_cmd; + + /* Scopes */ + P_Mutex scopes_mutex; + Arena *scopes_arena; + S_Scope *first_free_scope; + + /* Evictor */ + Atomic32Padded evictor_cycle; + P_Counter shutdown_counter; + b32 evictor_scheduler_shutdown; + P_Mutex evictor_scheduler_mutex; + P_Cv evictor_scheduler_shutdown_cv; +}; + +extern S_SharedState S_shared_state; + +//////////////////////////////// +//~ Startup + +Struct(S_StartupReceipt) { i32 _; }; +S_StartupReceipt sprite_startup(void); + +//////////////////////////////// +//~ Tag operations + +Inline S_Tag sprite_tag_nil(void) { return (S_Tag) { 0 }; } + +S_Tag sprite_tag_from_path(String path); +b32 sprite_tag_is_nil(S_Tag tag); +b32 sprite_tag_eq(S_Tag t1, S_Tag t2); + +//////////////////////////////// +//~ Scope operations + +S_Scope *sprite_scope_begin(void); +void sprite_scope_end(S_Scope *scope); + +/* ========================== * + * Texture load + * ========================== */ + +/* ========================== * + * Cache structs + * ========================== */ + +/* ========================== * + * Purple-black image + * ========================== */ + +u32 *generate_purple_black_image(Arena *arena, u32 width, u32 height); + +/* ========================== * + * Startup + * ========================== */ + +S_StartupReceipt sprite_startup(void); + +P_ExitFuncDef(sprite_shutdown); + +/* ========================== * + * Tag + * ========================== */ + +S_Tag sprite_tag_from_path(String path); + +b32 sprite_tag_is_nil(S_Tag tag); + +b32 sprite_tag_eq(S_Tag t1, S_Tag t2); + +S_Hash cache_entry_hash_from_tag_hash(u64 tag_hash, S_CacheEntryKind kind); + +/* ========================== * + * Load + * ========================== */ + +S_ScopeCacheRef *scope_ensure_ref_from_ref(S_Scope *scope, S_Ref ref); +void push_load_job(S_Ref ref, S_Tag tag); + +void cache_entry_load_texture(S_Ref ref, S_Tag tag); + +S_Sheet init_sheet_from_ase_result(Arena *arena, ASE_DecodedSheet ase); + +void cache_entry_load_sheet(S_Ref ref, S_Tag tag); + +/* ========================== * + * Scope + * ========================== */ + +void refcount_add(S_CacheEntry *e, i32 amount); + +S_ScopeCacheRef *scope_ensure_ref_unsafe(S_Scope *scope, S_CacheEntry *e); + +S_ScopeCacheRef *scope_ensure_ref_from_entry(S_Scope *scope, S_CacheEntry *e, P_Lock *bin_lock); + +S_ScopeCacheRef *scope_ensure_ref_from_ref(S_Scope *scope, S_Ref ref); + +S_Scope *sprite_scope_begin(void); + +void sprite_scope_end(S_Scope *scope); + +/* ========================== * + * Cache interface + * ========================== */ + +S_ScopeCacheRef *cache_lookup(S_Scope *scope, S_Hash hash, P_Lock *bin_lock); + +S_ScopeCacheRef *cache_entry_from_tag(S_Scope *scope, S_Tag tag, S_CacheEntryKind kind, b32 force_new); + +void *data_from_tag_internal(S_Scope *scope, S_Tag tag, S_CacheEntryKind kind, b32 await); + +/* ========================== * + * Texture + * ========================== */ + +S_Texture *sprite_texture_from_tag_await(S_Scope *scope, S_Tag tag); + +S_Texture *sprite_texture_from_tag_async(S_Scope *scope, S_Tag tag); + +void sprite_texture_from_tag_prefetch(S_Scope *scope, S_Tag tag); + +/* ========================== * + * Sheet + * ========================== */ + +S_Sheet *sprite_sheet_from_tag_await(S_Scope *scope, S_Tag tag); + +S_Sheet *sprite_sheet_from_tag_async(S_Scope *scope, S_Tag tag); + +void sprite_sheet_from_tag_prefetch(S_Scope *scope, S_Tag tag); + + S_SheetFrame sprite_sheet_get_frame(S_Sheet *sheet, u32 index); S_SheetSpan sprite_sheet_get_span(S_Sheet *sheet, String name); @@ -146,3 +395,26 @@ S_SheetSlice sprite_sheet_get_slice(S_Sheet *sheet, String name, u32 frame_index /* Returns all slices with name in frame */ S_SheetSliceArray sprite_sheet_get_slices(S_Sheet *sheet, String name, u32 frame_index); + +/* ========================== * + * Load job + * ========================== */ + +P_JobDef(S_SpriteLoadJob, job); + +//////////////////////////////// +//~ Resource watch + +#if RESOURCE_RELOADING + +void S_ReloadSpriteFromTag(S_Scope *scope, S_Tag tag, S_CacheEntryKind kind); +W_CallbackFuncDef(S_WatchSpriteCallback, name); + +#endif + +//////////////////////////////// +//~ Evictor job + +MergesortCompareFuncDef(S_EvictorSortCmp, arg_a, arg_b, udata); + +P_JobDef(S_EvictorJob, _); diff --git a/src/user/user_core.c b/src/user/user_core.c index fd2407bf..d5de3e5b 100644 --- a/src/user/user_core.c +++ b/src/user/user_core.c @@ -506,9 +506,8 @@ internal void draw_debug_console(i32 level, b32 minimized) * Sort entities * ========================== */ -internal MergesortCompareFuncDef(ent_draw_order_cmp, arg_a, arg_b, udata) +internal MergesortCompareFuncDef(ent_draw_order_cmp, arg_a, arg_b, _) { - (UNUSED)udata; Ent *a = *(Ent **)arg_a; Ent *b = *(Ent **)arg_b;