power_play/src/sprite/sprite.c
2025-09-18 18:58:55 -05:00

470 lines
16 KiB
C

Readonly S_Texture S_NilTexture = ZI;
Readonly S_Sheet S_NilSheet = ZI;
S_SharedState S_shared_state = ZI;
////////////////////////////////
//~ Load jobs
JobDef(S_LoadTexture, sig, _)
{
TempArena scratch = BeginScratchNoConflict();
S_Entry *entry = sig->entry;
Resource resource = entry->resource;
b32 ok = 1;
S_Texture *texture = &entry->texture;
texture->valid = 1;
String name = NameFromResource(resource);
String data = DataFromResource(resource);
ASE_DecodedImage decoded = ASE_DecodeImage(scratch.arena, data);
ok = decoded.ok;
if (ok)
{
GPU_ResourceDesc desc = ZI;
desc.kind = GPU_ResourceKind_Texture2D;
desc.flags = GPU_ResourceFlag_None;
/* FIXME: Use srgb format */
desc.texture.format = GPU_Format_R8G8B8A8_Unorm_Srgb;
desc.texture.size = VEC3I32(decoded.width, decoded.height, 1);
desc.texture.mip_levels = 1;
texture->gpu_texture = GPU_AcquireResource(desc);
texture->width = decoded.width;
texture->height = decoded.height;
/* Fill upload buffer */
GPU_ResourceDesc upload_desc = ZI;
upload_desc.kind = GPU_ResourceKind_Buffer;
upload_desc.buffer.heap_kind = GPU_HeapKind_Upload;
upload_desc.buffer.size = GPU_GetFootprintSize(texture->gpu_texture);
GPU_Resource *upload = GPU_AcquireResource(upload_desc);
{
GPU_Mapped mapped = GPU_Map(upload);
GPU_CopyBytesToFootprint(mapped.mem, (u8 *)decoded.pixels, texture->gpu_texture);
GPU_Unmap(mapped);
}
GPU_QueueKind copy_queue = GPU_QueueKind_BackgroundCopy;
GPU_QueueKind direct_queue = GPU_QueueKind_Direct;
Fence *direct_queue_fence = GPU_FenceFromQueue(direct_queue);
i64 direct_queue_fence_target = 0;
if (copy_queue == direct_queue)
{
/* Copy & transition GPU resource on direct queue*/
{
GPU_CommandList *cl = GPU_BeginCommandList(direct_queue);
{
GPU_TransitionToCopyDst(cl, texture->gpu_texture);
GPU_CopyResource(cl, texture->gpu_texture, upload);
GPU_TransitionToReadable(cl, texture->gpu_texture);
}
direct_queue_fence_target = GPU_EndCommandList(cl);
}
}
else
{
/* Copy to GPU resource on background copy queue*/
i64 copy_queue_fence_target = 0;
{
GPU_CommandList *cl = GPU_BeginCommandList(copy_queue);
{
GPU_TransitionToCopyDst(cl, texture->gpu_texture);
GPU_CopyResource(cl, texture->gpu_texture, upload);
}
copy_queue_fence_target = GPU_EndCommandList(cl);
}
/* Once copy finishes, transition resource to readable on direct queue */
{
GPU_QueueWait(direct_queue, copy_queue, copy_queue_fence_target);
GPU_CommandList *cl = GPU_BeginCommandList(direct_queue);
{
GPU_TransitionToReadable(cl, texture->gpu_texture);
}
direct_queue_fence_target = GPU_EndCommandList(cl);
}
}
/* Release upload buffer once transition finishes */
YieldOnFence(direct_queue_fence, direct_queue_fence_target);
GPU_ReleaseResource(upload, GPU_ReleaseFlag_None);
}
texture->loaded = 1;
SetFence(&entry->texture_ready_fence, 1);
EndScratch(scratch);
}
JobDef(S_LoadSheet, sig, _)
{
TempArena scratch = BeginScratchNoConflict();
Arena *perm = PermArena();
S_Entry *entry = sig->entry;
Resource resource = entry->resource;
b32 ok = 1;
S_Sheet *sheet = &entry->sheet;
sheet->valid = 1;
String name = NameFromResource(resource);
String data = DataFromResource(resource);
ASE_DecodedSheet decoded = ASE_DecodeSheet(scratch.arena, data);
ok = decoded.ok;
if (ok)
{
Vec2 image_size = decoded.image_size;
Vec2 frame_size = decoded.frame_size;
Vec2 frame_center = MulVec2(decoded.frame_size, 0.5f);
sheet->image_size = image_size;
sheet->frame_size = frame_size;
/* Init frames */
sheet->frames_count = decoded.num_frames;
sheet->frames = PushStructs(perm, S_Frame, sheet->frames_count);
for (ASE_Frame *src = decoded.first_frame; src; src = src->next)
{
S_Frame *dst = &sheet->frames[src->index];
dst->index = src->index;
dst->duration = src->duration;
dst->clip.p0.x = src->x1;
dst->clip.p0.y = src->y1;
dst->clip.p1.x = src->x2;
dst->clip.p1.y = src->y2;
}
/* Init spans */
sheet->spans_count = decoded.num_spans;
sheet->span_bins_count = MaxU32(AlignU64Pow2(sheet->spans_count * 2), 1);
sheet->spans = PushStructs(perm, S_Span, sheet->spans_count);
sheet->span_bins = PushStructs(perm, S_SpanBin, sheet->span_bins_count);
{
i32 span_index = 0;
for (ASE_Span *src = decoded.first_span; src; src = src->next)
{
S_Span *dst = &sheet->spans[span_index];
dst->hash = HashFnv64(Fnv64Basis, src->name);
dst->name = PushString(perm, src->name);
dst->start = src->start;
dst->end = src->end;
/* Insert span into bin */
{
S_SpanBin *bin = &sheet->span_bins[dst->hash % sheet->span_bins_count];
QueuePushN(bin->first, bin->last, dst, next_in_bin);
}
++span_index;
}
}
/* Init slice groups */
sheet->slice_groups_count = decoded.num_slice_keys;
sheet->slice_group_bins_count = MaxU32(AlignU64Pow2(sheet->slice_groups_count * 2), 1);
sheet->slice_groups = PushStructs(perm, S_SliceGroup, sheet->slice_groups_count);
sheet->slice_group_bins = PushStructs(perm, S_SliceGroupBin, sheet->slice_group_bins_count);
{
i32 group_index = 0;
for (ASE_SliceKey *src_group = decoded.first_slice_key; src_group; src_group = src_group->next)
{
S_SliceGroup *dst_group = &sheet->slice_groups[group_index];
dst_group->hash = HashFnv64(Fnv64Basis, src_group->name);
dst_group->name = PushString(perm, src_group->name);
/* Init slices */
dst_group->slices = PushStructs(perm, S_Slice, sheet->frames_count);
{
/* Fill is_original slices */
for (ASE_Slice *src_slice = src_group->first_slice; src_slice; src_slice = src_slice->next)
{
f32 x1_px = src_slice->x1;
f32 y1_px = src_slice->y1;
f32 x2_px = src_slice->x2;
f32 y2_px = src_slice->y2;
f32 width_px = x2_px - x1_px;
f32 height_px = y2_px - y1_px;
f32 x1 = (x1_px - frame_center.x) / frame_size.x;
f32 y1 = (y1_px - frame_center.y) / frame_size.y;
f32 x2 = (x2_px - frame_center.x) / frame_size.x;
f32 y2 = (y2_px - frame_center.y) / frame_size.y;
f32 width = x2 - x1;
f32 height = y2 - y1;
/* Rect */
Rect rect_px = RectFromScalar(x1_px, y1_px, width_px, height_px);
Rect rect = RectFromScalar(x1, y1, width, height);
/* Center */
Vec2 center_px = VEC2(x1_px + (width_px * 0.5f), y1_px + (height_px * 0.5f));
Vec2 center = VEC2(x1 + (width * 0.5f), y1 + (height * 0.5f));
/* Dir */
Vec2 dir_px = VEC2(center_px.x, -1);
Vec2 dir = VEC2(0, -1);
S_Slice *dst_slice = &dst_group->slices[src_slice->start];
dst_slice->is_original = 1;
dst_slice->rect_px = rect_px;
dst_slice->center_px = center_px;
dst_slice->dir_px = dir_px;
dst_slice->rect = rect;
dst_slice->center = center;
dst_slice->dir = dir;
}
/* Copy slices forward into frames without a slice */
{
S_Slice *origin = 0;
for (u32 frame_index = 0; frame_index < sheet->frames_count; ++frame_index)
{
S_Slice *slice = &dst_group->slices[frame_index];
if (slice->is_original)
{
origin = slice;
}
else
{
*slice = *origin;
slice->is_original = 0;
}
}
}
}
/* Insert group into bin */
{
S_SliceGroupBin *bin = &sheet->slice_group_bins[dst_group->hash % sheet->slice_group_bins_count];
QueuePushN(bin->first, bin->last, dst_group, next_in_bin);
}
++group_index;
}
}
/* Init slice ray directions */
{
String ray_suffix = Lit(".ray");
for (u32 slice_group_index = 0; slice_group_index < sheet->slice_groups_count; ++slice_group_index)
{
S_SliceGroup *group = &sheet->slice_groups[slice_group_index];
if (StringEndsWith(group->name, ray_suffix))
{
String point_slice_group_name = group->name;
point_slice_group_name.len -= ray_suffix.len;
u64 point_slice_group_hash = HashFnv64(Fnv64Basis, point_slice_group_name);
S_SliceGroupBin *bin = &sheet->slice_group_bins[point_slice_group_hash % sheet->slice_group_bins_count];
S_SliceGroup *point_slice_group = bin->first;
for (; point_slice_group; point_slice_group = point_slice_group->next_in_bin)
{
if (point_slice_group->hash == point_slice_group_hash)
{
break;
}
}
if (point_slice_group)
{
for (u32 frame_index = 0; frame_index < sheet->frames_count; ++frame_index)
{
S_Slice *point_slice = &point_slice_group->slices[frame_index];
S_Slice *ray_slice = &group->slices[frame_index];
Vec2 ray_end = ray_slice->center_px;
Vec2 ray_end_norm = ray_slice->center;
point_slice->dir_px = SubVec2(ray_end, point_slice->center_px);
point_slice->dir = SubVec2(ray_end_norm, point_slice->center);
point_slice->has_dir = 1;
}
}
}
}
}
}
sheet->loaded = 1;
SetFence(&entry->sheet_ready_fence, 1);
EndScratch(scratch);
}
////////////////////////////////
//~ Cache
/* TODO: Per-fiber L1 cache */
S_Entry *S_FetchEntry(Resource resource, JobPool pool, S_FetchFlag flags)
{
S_SharedState *g = &S_shared_state;
S_Entry *entry = 0;
{
S_EntryBin *bin = &g->entry_bins[resource.hash % S_EntryBinsCount];
/* Search for entry */
entry = bin->first;
{
Lock lock = LockS(&bin->mutex);
{
for (; entry; entry = entry->next_in_bin)
{
if (entry->resource.hash == resource.hash)
{
break;
}
}
}
Unlock(&lock);
}
/* Entry not found: lock, re-search, & create */
if (!entry)
{
Lock lock = LockE(&bin->mutex);
{
/* Re-search */
entry = bin->first;
for (; entry; entry = entry->next_in_bin)
{
if (entry->resource.hash == resource.hash)
{
break;
}
}
/* Create */
if (!entry)
{
Arena *perm = PermArena();
entry = PushStruct(perm, S_Entry);
entry->resource = resource;
QueuePushN(bin->first, bin->last, entry, next_in_bin);
}
}
Unlock(&lock);
}
}
/* Launch load jobs */
if ((flags & S_FetchFlag_Texture)
&& !Atomic32Fetch(&entry->texture_touched)
&& !Atomic32FetchTestSet(&entry->texture_touched, 0, 1))
{
RunJob(S_LoadTexture, .pool = pool, .sig.entry = entry);
}
if ((flags & S_FetchFlag_Sheet)
&& !Atomic32Fetch(&entry->sheet_touched)
&& !Atomic32FetchTestSet(&entry->sheet_touched, 0, 1))
{
RunJob(S_LoadSheet, .pool = pool, .sig.entry = entry);
}
return entry;
}
////////////////////////////////
//~ Sprite data retrieval operations
S_Texture *S_TextureFromResource(Resource resource)
{
S_Entry *entry = S_FetchEntry(resource, JobPool_Inherit, S_FetchFlag_Texture);
YieldOnFence(&entry->texture_ready_fence, 1);
return &entry->texture;
}
S_Texture *S_TextureFromResourceAsync(Resource resource)
{
S_Texture *result = &S_NilTexture;
S_Entry *entry = S_FetchEntry(resource, JobPool_Inherit, S_FetchFlag_Texture);
if (FetchFence(&entry->texture_ready_fence) >= 1)
{
result = &entry->texture;
}
return result;
}
S_Sheet *S_SheetFromResource(Resource resource)
{
S_Entry *entry = S_FetchEntry(resource, JobPool_Inherit, S_FetchFlag_Sheet);
YieldOnFence(&entry->sheet_ready_fence, 1);
return &entry->sheet;
}
S_Sheet *S_SheetFromResourceAsync(Resource resource)
{
S_Sheet *result = &S_NilSheet;
S_Entry *entry = S_FetchEntry(resource, JobPool_Inherit, S_FetchFlag_Sheet);
if (FetchFence(&entry->sheet_ready_fence) >= 1)
{
result = &entry->sheet;
}
return result;
}
////////////////////////////////
//~ Sheet access operations
S_Span S_SpanFromName(S_Sheet *sheet, String name)
{
S_Span result = ZI;
u32 bins_count = sheet->span_bins_count;
if (bins_count > 0)
{
u64 name_hash = HashFnv64(Fnv64Basis, name);
S_SpanBin *bin = &sheet->span_bins[name_hash % bins_count];
S_Span *span = bin->first;
for (; span; span = span->next_in_bin)
{
if (span->hash == name_hash)
{
result = *span;
break;
}
}
}
return result;
}
S_Frame S_FrameFromIndex(S_Sheet *sheet, u64 index)
{
S_Frame result = ZI;
if (sheet->frames_count > 0)
{
result = sheet->frames[index % sheet->frames_count];
}
return result;
}
S_Slice S_SliceFromName(S_Sheet *sheet, String name, u64 frame_index)
{
S_Slice result = ZI;
b32 match = 0;
u32 bins_count = sheet->slice_group_bins_count;
if (bins_count > 0 && sheet->frames_count > 0)
{
u64 name_hash = HashFnv64(Fnv64Basis, name);
S_SliceGroupBin *bin = &sheet->slice_group_bins[name_hash % bins_count];
S_SliceGroup *group = bin->first;
for (; group; group = group->next_in_bin)
{
if (group->hash == name_hash)
{
result = group->slices[frame_index % sheet->frames_count];
match = 1;
break;
}
}
}
/* Return 'pivot' by default */
if (!match)
{
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
{
result = S_SliceFromName(sheet, Lit("pivot"), frame_index);
}
}
return result;
}