SPR_Ctx SPR = Zi; //////////////////////////////////////////////////////////// //~ Bootstrap void SPR_Bootstrap(void) { OnAsyncTick(SPR_TickAsync); } //////////////////////////////////////////////////////////// //~ Key helpers SPR_SheetKey SPR_SheetKeyFromResource(ResourceKey resource) { SPR_SheetKey result = Zi; result.r = resource; return result; } //////////////////////////////////////////////////////////// //~ Lookup SPR_Slice SPR_SliceFromSheet(SPR_SheetKey sheet, String slice_name) { SPR_Slice result = Zi; u64 hash = sheet.r.v; hash = HashStringEx(hash, slice_name); i64 completion = G_CompletionValueFromQueue(G_QueueKind_AsyncCopy); // Search for existing entry b32 found = 0; SPR_SliceBin *bin = &SPR.slice_bins[hash % countof(SPR.slice_bins)]; { Lock bin_lock = LockS(&bin->mutex); { SPR_SliceEntry *entry = bin->first; for (; entry; entry = entry->next) { if (entry->hash == hash) { break; } } if (entry) { if (completion >= Atomic64Fetch(&entry->async_copy_completion_target)) { result = entry->slice; } found = 1; } } Unlock(&bin_lock); } // Push new entry if (!found) { Lock submit_lock = LockE(&SPR.submit.mutex); Lock bin_lock = LockE(&bin->mutex); { SPR_SliceEntry *entry = bin->first; for (; entry; entry = entry->next) { if (entry->hash == hash) { break; } } if (entry) { if (completion >= Atomic64Fetch(&entry->async_copy_completion_target)) { result = entry->slice; } found = 1; } else { Arena *perm = PermArena(); entry = PushStruct(perm, SPR_SliceEntry); entry->hash = hash; Atomic64FetchSet(&entry->async_copy_completion_target, I64Max); entry->sheet = sheet; entry->slice_name = PushString(perm, slice_name); SllStackPush(bin->first, entry); SPR_CmdNode *n = SPR.submit.first_free; if (n) { ZeroStruct(n); } else { n = PushStruct(perm, SPR_CmdNode); } n->cmd.entry = entry; SllQueuePush(SPR.submit.first, SPR.submit.last, n); ++SPR.submit.count; Atomic32FetchSet(&SPR.new_cmds_present, 1); SignalAsyncTick(); } } Unlock(&bin_lock); Unlock(&submit_lock); } if (G_IsRefNil(result.tex)) { result.tex = G_BlankTexture2D(); result.uv_rect.p0 = VEC2(0, 0); result.uv_rect.p1 = VEC2(1, 1); } return result; } //////////////////////////////////////////////////////////// //~ Async void SPR_TickAsync(WaveLaneCtx *lane, AsyncFrameLaneCtx *base_async_lane_frame) { Arena *perm = PermArena(); SPR_AsyncCtx *async = &SPR.async; Arena *frame_arena = base_async_lane_frame->arena; // TODO: Go wide if (lane->idx == 0) { if (Atomic32Fetch(&SPR.new_cmds_present)) { Atomic32Set(&SPR.new_cmds_present, 0); SPR_CmdNode *first_cmd_node = 0; SPR_CmdNode *last_cmd_node = 0; u64 cmds_count = 0; { Lock lock = LockE(&SPR.submit.mutex); { first_cmd_node = SPR.submit.first; last_cmd_node = SPR.submit.last; cmds_count = SPR.submit.count; SPR.submit.first = 0; SPR.submit.last = 0; } Unlock(&lock); } if (cmds_count > 0) { for (SPR_CmdNode *n = first_cmd_node; n; n = n->next) { SPR_Cmd cmd = n->cmd; SPR_SliceEntry *slice_entry = cmd.entry; SPR_SheetBin *sheet_bin = &async->sheet_bins[slice_entry->sheet.r.v % countof(async->sheet_bins)]; SPR_SheetEntry *sheet = sheet_bin->first; for (; sheet; sheet = sheet->next) { if (sheet->key.r.v == slice_entry->sheet.r.v) { break; } } // Decode sheet // TODO: Distribute chunk decoding accross wave // TODO: Use atlas allocator and separate slices into unique textures // TODO: Reuse command list for all uploads if (!sheet) { sheet = PushStruct(perm, SPR_SheetEntry); sheet->key = slice_entry->sheet; SllStackPush(sheet_bin->first, sheet); String encoded = DataFromResource(sheet->key.r); String name = NameFromResource(sheet->key.r); LogInfoF("Decoding sprite sheet \"%F\" (%F bytes)", FmtString(name), FmtUint(encoded.len)); ASE_DecodedImage decoded_image = ASE_DecodeImage(frame_arena, encoded); ASE_DecodedSheet decoded_sheet = ASE_DecodeSheet(frame_arena, encoded); if (decoded_image.ok) { G_ResourceHandle gpu_resource = Zi; G_ArenaHandle gpu_perm = G_PermArena(); G_CommandListHandle cl = G_PrepareCommandList(G_QueueKind_AsyncCopy); { Vec3I32 dims = Zi; dims.x = decoded_image.width; dims.y = decoded_image.height; dims.z = 1; gpu_resource = G_PushTexture2D( gpu_perm, cl, G_Format_R8G8B8A8_Unorm_Srgb, dims, G_Layout_AnyQueue_ShaderRead_CopyRead_CopyWrite_Present ); G_CopyCpuToTexture( cl, gpu_resource, VEC3I32(0, 0, 0), decoded_image.pixels, dims, RNG3I32( VEC3I32(0, 0, 0), dims ) ); } i64 completion_target = G_CommitCommandList(cl); sheet->async_copy_completion_target = completion_target; sheet->tex = G_PushTexture2DRef(gpu_perm, gpu_resource); // LogDebugF("Decoded with ref: %F", FmtUint(slice_entry->slice.tex.v)); } else { // TODO: Use 'missing' texture sheet->tex = G_BlankTexture2D(); sheet->async_copy_completion_target = 0; } } slice_entry->slice.tex = sheet->tex; // FIXME: Real uv slice_entry->slice.uv_rect.p0 = VEC2(0, 0); slice_entry->slice.uv_rect.p1= VEC2(1, 1); Atomic64Set(&slice_entry->async_copy_completion_target, sheet->async_copy_completion_target); } // Free cmds Lock lock = LockE(&SPR.submit.mutex); { last_cmd_node->next = SPR.submit.first_free; SPR.submit.first_free = first_cmd_node; } Unlock(&lock); } } } }