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; } SPR_SpanKey SPR_SpanKeyFromName(String name) { SPR_SpanKey result = Zi; // TODO return result; } SPR_LayerKey SPR_LayerKeyFromName(String name) { SPR_LayerKey result = Zi; // TODO return result; } //////////////////////////////////////////////////////////// //~ Lookup SPR_Slice SPR_SliceFromSheet(SPR_SheetKey sheet, SPR_SpanKey span, i64 frame_seq) { SPR_Slice result = Zi; // TODO return result; } // SPR_Slice SPR_SliceFromSheet(SPR_SheetKey sheet, SPR_SpanKey span, i64 frame_seq) // { // // TODO: Ability to specify desired alpha modes (Straight, Premultiplied, Opaque) // SPR_Slice result = Zi; // // FIXME // 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) // { // SllStackPop(SPR.submit.first_free); // 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; SPR.submit.count = 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); } } } }