//////////////////////////////// //~ Tag Struct(S_Tag) { u64 hash; String path; }; //////////////////////////////// //~ Texture Struct(S_Texture) { b32 loaded; b32 valid; GPU_Resource *gp_texture; u32 width; u32 height; }; //////////////////////////////// //~ Sheet types Struct(S_Frame) { u32 index; f64 duration; ClipRect clip; }; Struct(S_Span) { String name; u32 start; u32 end; }; Struct(S_Slice) { /* If 1, this slice was not copied over from another frame in the sprite sheet */ b32 original; /* If 1, the slice has a corresponding '.ray' slice affecting the 'dir' fields */ b32 has_ray; /* Values are in the range -0.5 (top / left edge) -> +0.5 (bottom / right edge) */ Rect rect; Vec2 center; Vec2 dir; /* '_px' values retain the original sprite pixel dimensions */ Rect rect_px; Vec2 center_px; Vec2 dir_px; }; Struct(S_SliceArray) { u64 count; S_Slice *slices; }; Struct(S_SheetSliceGroup) { String name; u64 per_frame_count; /* 2d array of slices with length (num frames) * (num slices per frame). * Index with [(frame index * num slices per frame) + slice index in frame] */ S_Slice *frame_slices; }; Struct(S_Sheet) { b32 loaded; b32 valid; Vec2 image_size; Vec2 frame_size; u32 frames_count; S_Frame *frames; u32 spans_count; S_Span *spans; Dict *spans_dict; u32 slice_groups_count; S_SheetSliceGroup *slice_groups; Dict *slice_groups_dict; }; //////////////////////////////// //~ Cache types typedef i32 S_CacheEntryKind; enum { S_CacheEntryKind_Texture, S_CacheEntryKind_Sheet, S_CacheEntryKind_Count }; typedef i32 S_CacheEntryState; enum { S_CacheEntryState_None, S_CacheEntryState_Queued, S_CacheEntryState_Working, S_CacheEntryState_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) { Mutex mutex; S_CacheEntry *first; S_CacheEntry *last; }; Struct(S_Cache) { Atomic64Padded memory_usage; Arena *arena; S_CacheEntryBin *bins; 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_CacheEntryRef) { S_CacheEntry *e; }; //////////////////////////////// //~ Scope /* A cache reference whose lifetime is bound to the scope it was retrieved from */ Struct(S_ScopeCacheEntryRef) { S_CacheEntryRef ref; S_ScopeCacheEntryRef *next_in_bin; }; Struct(S_Scope) { S_ScopeCacheEntryRef **ref_node_bins; S_ScopeCacheEntryRef *ref_node_pool; u64 num_references; S_Scope *next_free; }; //////////////////////////////// //~ Evictor Struct(S_EvictorNode) { i32 last_ref_cycle; S_CacheEntry *cache_entry; S_CacheEntryBin *cache_bin; S_EvictorNode *next_evicted; }; //////////////////////////////// //~ Cache constants /* 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_CacheBinsCount 1024 #define S_MaxScopeReferences 1024 #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) /* 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); //////////////////////////////// //~ 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; /* Scopes */ Mutex scopes_mutex; Arena *scopes_arena; S_Scope *first_free_scope; /* Evictor */ Atomic32Padded evictor_cycle; Counter shutdown_counter; b32 evictor_scheduler_shutdown; Mutex evictor_scheduler_mutex; Cv evictor_scheduler_shutdown_cv; }; extern S_SharedState S_shared_state; //////////////////////////////// //~ Startup Struct(S_StartupReceipt) { i32 _; }; S_StartupReceipt S_Startup(void); //////////////////////////////// //~ Shutdown P_ExitFuncDef(S_Shutdown); //////////////////////////////// //~ Purple-black image u32 *S_GeneratePurpleBlackImage(Arena *arena, u32 width, u32 height); //////////////////////////////// //~ Tag operations S_Tag S_TagFromPath(String path); b32 S_IsTagNil(S_Tag tag); b32 S_EqTag(S_Tag t1, S_Tag t2); S_Hash S_CacheEntryFromTagHash(u64 tag_hash, S_CacheEntryKind kind); //////////////////////////////// //~ Sheet init S_Sheet S_SheetFromAseResult(Arena *arena, ASE_DecodedSheet ase); //////////////////////////////// //~ Load job void S_PushLoadJob(S_CacheEntryRef ref, S_Tag tag); JobDecl(S_LoadSpriteJob, { S_Scope *scope; S_CacheEntryRef ref; S_Tag tag; }); //////////////////////////////// //~ Cache load operations void S_LoadCacheEntryTexture(S_CacheEntryRef ref, S_Tag tag); void S_LoadCacheEntrySheet(S_CacheEntryRef ref, S_Tag tag); //////////////////////////////// //~ Ref operations void S_AddRef(S_CacheEntry *e, i32 amount); S_ScopeCacheEntryRef *S_EnsureRefUnsafely(S_Scope *scope, S_CacheEntry *e); S_ScopeCacheEntryRef *S_EnsureRefFromEntryLocked(S_Scope *scope, S_CacheEntry *e, Lock *bin_lock); S_ScopeCacheEntryRef *S_EnsureRefFromRef(S_Scope *scope, S_CacheEntryRef ref); //////////////////////////////// //~ Scope operations S_Scope *S_BeginScope(void); void S_EndScope(S_Scope *scope); //////////////////////////////// //~ Cache lookup operations S_ScopeCacheEntryRef *S_EntryFromHashLocked(S_Scope *scope, S_Hash hash, Lock *bin_lock); S_ScopeCacheEntryRef *S_EntryFromTag(S_Scope *scope, S_Tag tag, S_CacheEntryKind kind, b32 force_new); void *S_DataFromTag(S_Scope *scope, S_Tag tag, S_CacheEntryKind kind, b32 await); //////////////////////////////// //~ Texture retrieval operations S_Texture *S_TextureFromTagAwait(S_Scope *scope, S_Tag tag); S_Texture *S_TextureFromTagAsync(S_Scope *scope, S_Tag tag); void S_PrefetchTextureFromTag(S_Scope *scope, S_Tag tag); //////////////////////////////// //~ Sheet retrieval operations S_Sheet *S_SheetFromTagAwait(S_Scope *scope, S_Tag tag); S_Sheet *S_SheetFromTagAsync(S_Scope *scope, S_Tag tag); void S_PrefetchSheetFromTag(S_Scope *scope, S_Tag tag); //////////////////////////////// //~ Sheet data operations S_Frame S_FrameFromIndex(S_Sheet *sheet, u32 index); S_Span S_SpanFromName(S_Sheet *sheet, String name); S_Slice S_SliceFromNameIndex(S_Sheet *sheet, String name, u32 frame_index); S_SliceArray S_SlicesFromNameIndex(S_Sheet *sheet, String name, u32 frame_index); //////////////////////////////// //~ Resource reload #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); JobDecl(S_EvictorJob, EmptySig);