470 lines
16 KiB
C
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;
|
|
}
|