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; }