power_play/src/sprite/sprite_core.h
2025-08-05 10:26:13 -05:00

352 lines
8.2 KiB
C

////////////////////////////////
//~ 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);