501 lines
15 KiB
C
501 lines
15 KiB
C
SPR_Ctx SPR = Zi;
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ Bootstrap
|
|
|
|
void SPR_Bootstrap(void)
|
|
{
|
|
// FIXME: Initialize nil/unready sprite texture here
|
|
|
|
OnAsyncTick(SPR_TickAsync);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ 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;
|
|
result.v = HashString(name);
|
|
return result;
|
|
}
|
|
|
|
b32 SPR_IsSheetKeyNil(SPR_SheetKey key)
|
|
{
|
|
return key.r.v == 0;
|
|
}
|
|
|
|
b32 SPR_IsSpanKeyNil(SPR_SpanKey key)
|
|
{
|
|
return key.v == 0;
|
|
}
|
|
|
|
String SPR_NameFromRayKind(SPR_RayKind kind)
|
|
{
|
|
PERSIST Readonly String names[SPR_RayKind_COUNT] = {
|
|
#define X(kind_name, layer_name, ...) [SPR_RayKind_##kind_name] = CompLit(#layer_name),
|
|
SPR_RayKindXList(X)
|
|
#undef X
|
|
};
|
|
String result = Zi;
|
|
if (kind >= 0 && kind < countof(names))
|
|
{
|
|
result = names[kind];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
SPR_LayerKind SPR_LayerKindFromName(String name)
|
|
{
|
|
SPR_LayerKind result = SPR_LayerKind_Visual;
|
|
if (StringBeginsWith(name, Lit(".")) || StringBeginsWith(name, Lit("#")))
|
|
{
|
|
result = SPR_LayerKind_Hidden;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ Lookup
|
|
|
|
SPR_Sprite SPR_SpriteFromSheet(SPR_SheetKey sheet_key, SPR_SpanKey span_key, i64 frame_seq)
|
|
{
|
|
//////////////////////////////
|
|
//- Fetch sheet
|
|
|
|
SPR_SheetEntry *sheet = 0;
|
|
{
|
|
SPR_SheetBin *sheet_bin = &SPR.sheet_bins[sheet_key.r.v % countof(SPR.sheet_bins)];
|
|
Lock sheet_bin_lock = LockS(&sheet_bin->mutex);
|
|
{
|
|
for (sheet = sheet_bin->first; sheet; sheet = sheet->next_in_bin)
|
|
{
|
|
if (sheet->key.r.v == sheet_key.r.v)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
Unlock(&sheet_bin_lock);
|
|
}
|
|
|
|
//////////////////////////////
|
|
//- Decode sheet
|
|
|
|
if (!sheet)
|
|
{
|
|
SPR_SheetBin *sheet_bin = &SPR.sheet_bins[sheet_key.r.v % countof(SPR.sheet_bins)];
|
|
Lock sheet_bin_lock = LockE(&sheet_bin->mutex);
|
|
{
|
|
for (sheet = sheet_bin->first; sheet; sheet = sheet->next_in_bin)
|
|
{
|
|
if (sheet->key.r.v == sheet_key.r.v)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (!sheet)
|
|
{
|
|
Arena *perm = PermArena();
|
|
sheet = PushStruct(perm, SPR_SheetEntry);
|
|
SllStackPushN(sheet_bin->first, sheet, next_in_bin);
|
|
sheet->key = sheet_key;
|
|
|
|
String sheet_data = DataFromResource(sheet->key.r);
|
|
String sheet_name = NameFromResource(sheet->key.r);
|
|
LogInfoF("Decoding sprite sheet %F \"%F\" (%F bytes)", FmtHandle(sheet->key.r.v), FmtString(sheet_name), FmtUint(sheet_data.len));
|
|
sheet->meta = ASE_DecodeMeta(perm, sheet_data);
|
|
|
|
//- Init slices
|
|
sheet->slices_count = MaxI64(sheet->meta.frames_count, 1);
|
|
sheet->slices = PushStructs(perm, SPR_SliceEntry, sheet->slices_count);
|
|
for (i64 slice_idx = 0; slice_idx < sheet->slices_count; ++slice_idx)
|
|
{
|
|
SPR_SliceEntry *slice = &sheet->slices[slice_idx];
|
|
slice->canvas_rect = Rng2Empty;
|
|
Atomic64Set(&slice->atlas_copy_completion_target, I64Max);
|
|
for (SPR_RayKind ray_kind = 0; ray_kind < SPR_RayKind_COUNT; ++ray_kind)
|
|
{
|
|
slice->rays[ray_kind].dir.x = 1;
|
|
}
|
|
}
|
|
|
|
//- Compute slice rect
|
|
for (ASE_Layer *ase_layer = sheet->meta.first_layer; ase_layer; ase_layer = ase_layer->next)
|
|
{
|
|
SPR_LayerKind kind = SPR_LayerKindFromName(ase_layer->name);
|
|
if (kind == SPR_LayerKind_Visual)
|
|
{
|
|
for (i64 slice_idx = 0; slice_idx < sheet->meta.frames_count; ++slice_idx)
|
|
{
|
|
ASE_Cel *cel = &ase_layer->cels[slice_idx];
|
|
SPR_SliceEntry *slice = &sheet->slices[slice_idx];
|
|
slice->canvas_rect = UnionRng2(slice->canvas_rect, cel->bounds);
|
|
}
|
|
}
|
|
}
|
|
|
|
//- Push async rasterization commands
|
|
if (sheet->meta.frames_count > 0)
|
|
{
|
|
Lock cmds_lock = LockE(&SPR.submit.mutex);
|
|
{
|
|
i64 submit_count = 0;
|
|
for (i64 slice_idx = 0; slice_idx < sheet->meta.frames_count; ++slice_idx)
|
|
{
|
|
SPR_SliceEntry *slice = &sheet->slices[slice_idx];
|
|
if (!IsRng2Empty(slice->canvas_rect))
|
|
{
|
|
SPR_CmdNode *cmd_node = SPR.submit.first_free;
|
|
if (cmd_node)
|
|
{
|
|
SllStackPop(SPR.submit.first_free);
|
|
ZeroStruct(cmd_node);
|
|
}
|
|
else
|
|
{
|
|
cmd_node = PushStruct(perm, SPR_CmdNode);
|
|
}
|
|
cmd_node->cmd.sheet = sheet;
|
|
cmd_node->cmd.slice_idx = slice_idx;
|
|
SllQueuePush(SPR.submit.first, SPR.submit.last, cmd_node);
|
|
submit_count += 1;
|
|
}
|
|
}
|
|
if (submit_count > 0)
|
|
{
|
|
SPR.submit.count += submit_count;
|
|
SignalAsyncTick();
|
|
}
|
|
}
|
|
Unlock(&cmds_lock);
|
|
}
|
|
|
|
//- Compute rays
|
|
{
|
|
TempArena scratch = BeginScratchNoConflict();
|
|
for (SPR_RayKind ray_kind = 0; ray_kind < SPR_RayKind_COUNT; ++ray_kind)
|
|
{
|
|
String ray_name = SPR_NameFromRayKind(ray_kind);
|
|
b32 match = 0;
|
|
for (ASE_Layer *ase_layer = sheet->meta.last_layer; ase_layer && !match; ase_layer = ase_layer->prev)
|
|
{
|
|
if (MatchString(ray_name, ase_layer->name))
|
|
{
|
|
match = 1;
|
|
for (i64 slice_idx = 0; slice_idx < sheet->meta.frames_count; ++slice_idx)
|
|
{
|
|
ASE_Cel *ase_cel = &ase_layer->cels[slice_idx];
|
|
SPR_SliceEntry *slice = &sheet->slices[slice_idx];
|
|
ASE_Image image = ASE_DecompressImageFromCel(scratch.arena, ase_cel);
|
|
if (!IsRng2Empty(image.bounds))
|
|
{
|
|
u32 ray_pix = image.pixels[0];
|
|
u32 alpha = (ray_pix >> 24) & 0xFF;
|
|
if (alpha > 0)
|
|
{
|
|
// TODO: Different quantization so that 128 equals 0, instead of approximately 0
|
|
f32 dir_x = (((f32)((ray_pix >> 0) & 0xFF) / 255.0) * 2.0) - 1;
|
|
f32 dir_y = (((f32)((ray_pix >> 8) & 0xFF) / 255.0) * 2.0) - 1;
|
|
Vec2 dir = NormVec2(VEC2(dir_x, dir_y));
|
|
slice->rays[ray_kind].pos = AddVec2(SubVec2(ase_cel->bounds.p0, slice->canvas_rect.p0), VEC2(0.5, 0.5));
|
|
slice->rays[ray_kind].dir = dir;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
EndScratch(scratch);
|
|
}
|
|
|
|
//- Init spans
|
|
{
|
|
sheet->span_bins_count = MaxI64(1, NextPow2U64(sheet->meta.spans_count * 4));
|
|
sheet->span_bins = PushStructs(perm, SPR_SpanBin, sheet->span_bins_count);
|
|
for (ASE_Span *ase_span = sheet->meta.first_span; ase_span; ase_span = ase_span->next)
|
|
{
|
|
SPR_SpanKey new_span_key = { .v = HashString(ase_span->name) };
|
|
SPR_SpanBin *span_bin = &sheet->span_bins[new_span_key.v % sheet->span_bins_count];
|
|
SPR_SpanEntry *span = 0;
|
|
for (span = span_bin->first; span; span = span->next_in_bin)
|
|
{
|
|
if (span->key.v == new_span_key.v)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (!span)
|
|
{
|
|
span = PushStruct(perm, SPR_SpanEntry);
|
|
SllQueuePush(sheet->first_span, sheet->last_span, span);
|
|
SllStackPushN(span_bin->first, span, next_in_bin);
|
|
span->key = new_span_key;
|
|
span->from = ase_span->from;
|
|
span->to = ase_span->to;
|
|
}
|
|
}
|
|
|
|
// Insert nil span
|
|
{
|
|
SPR_SpanBin *span_bin = &sheet->span_bins[0];
|
|
SPR_SpanEntry *span = PushStruct(perm, SPR_SpanEntry);
|
|
SllQueuePush(sheet->first_span, sheet->last_span, span);
|
|
SllStackPushN(span_bin->first, span, next_in_bin);
|
|
span->key = SPR_NilSpanKey;
|
|
span->from = 0;
|
|
span->to = sheet->slices_count;
|
|
}
|
|
}
|
|
|
|
// TODO: More rigorous validation
|
|
sheet->ok = sheet->meta.ok;
|
|
}
|
|
}
|
|
Unlock(&sheet_bin_lock);
|
|
}
|
|
|
|
//////////////////////////////
|
|
//- Fetch span
|
|
|
|
// FIXME: Ensure slices_count always > 0
|
|
i64 span_start = 0;
|
|
i64 span_end = sheet->slices_count;
|
|
b32 span_matched = 0;
|
|
{
|
|
SPR_SpanEntry *span = 0;
|
|
if (sheet->ok)
|
|
{
|
|
SPR_SpanBin *span_bin = &sheet->span_bins[span_key.v % sheet->span_bins_count];
|
|
for (span = span_bin->first; span; span = span->next_in_bin)
|
|
{
|
|
if (span->key.v == span_key.v)
|
|
{
|
|
span_matched = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!span)
|
|
{
|
|
span = sheet->first_span;
|
|
}
|
|
if (span)
|
|
{
|
|
span_start = span->from;
|
|
span_end = span->to;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////
|
|
//- Fetch slice
|
|
|
|
SPR_SliceEntry *slice = 0;
|
|
{
|
|
// FIXME: Ensure span->end is never <= span->start
|
|
i64 slice_idx = span_start + (frame_seq % (span_end - span_start));
|
|
slice = &sheet->slices[slice_idx];
|
|
}
|
|
|
|
//////////////////////////////
|
|
//- Compute result
|
|
|
|
SPR_Sprite result = Zi;
|
|
{
|
|
b32 slice_ready = 0;
|
|
if (sheet->ok)
|
|
{
|
|
i64 completion = G_CompletionValueFromQueue(G_QueueKind_AsyncCopy);
|
|
if (completion >= Atomic64Fetch(&slice->atlas_copy_completion_target))
|
|
{
|
|
slice_ready = 1;
|
|
}
|
|
}
|
|
|
|
// Fill tex info
|
|
if (slice_ready)
|
|
{
|
|
result.tex_dims = Vec2FromVec(slice->atlas->dims);
|
|
result.tex = slice->atlas->tex;
|
|
result.tex_rect = RNG2(slice->atlas_pos, AddVec2(slice->atlas_pos, DimsFromRng2(slice->canvas_rect)));
|
|
}
|
|
else
|
|
{
|
|
result.tex_dims = SPR.unready_tex_dims;
|
|
result.tex = SPR.unready_tex;
|
|
result.tex_rect = RNG2(VEC2(0, 0), SPR.unready_tex_dims);
|
|
}
|
|
|
|
// Fill rays
|
|
StaticAssert(countof(result.rays) == countof(slice->rays));
|
|
CopyStructs(result.rays, slice->rays, countof(result.rays));
|
|
|
|
result.matched = span_matched;
|
|
result.ready = slice_ready;
|
|
}
|
|
|
|
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: Distribute rasterization accross wave
|
|
if (lane->idx == 0)
|
|
{
|
|
i64 cmds_count = 0;
|
|
SPR_Cmd *cmds = 0;
|
|
{
|
|
Lock lock = LockE(&SPR.submit.mutex);
|
|
if (SPR.submit.count > 0)
|
|
{
|
|
cmds_count = SPR.submit.count;
|
|
cmds = PushStructsNoZero(frame_arena, SPR_Cmd, cmds_count);
|
|
i64 cmd_idx = 0;
|
|
for (SPR_CmdNode *n = SPR.submit.first; n; n = n->next)
|
|
{
|
|
cmds[cmd_idx] = n->cmd;
|
|
++cmd_idx;
|
|
}
|
|
// Reset submission queue
|
|
SPR.submit.first_free = SPR.submit.first;
|
|
SPR.submit.first = 0;
|
|
SPR.submit.last = 0;
|
|
SPR.submit.count = 0;
|
|
}
|
|
Unlock(&lock);
|
|
}
|
|
|
|
if (cmds_count > 0)
|
|
{
|
|
G_CommandListHandle cl = G_PrepareCommandList(G_QueueKind_AsyncCopy);
|
|
for (i64 cmd_idx = 0; cmd_idx < cmds_count; ++cmd_idx)
|
|
{
|
|
TempArena scratch = BeginScratchNoConflict();
|
|
SPR_Cmd *cmd = &cmds[cmd_idx];
|
|
SPR_SheetEntry *sheet = cmd->sheet;
|
|
ASE_Meta meta = sheet->meta;
|
|
|
|
// String encoded = DataFromResource(sheet->key.r);
|
|
// String name = NameFromResource(sheet->key.r);
|
|
// LogInfoF("Rasterizing sprite sheet %F \"%F\" (%F bytes)", FmtHandle(sheet->key.r), FmtString(name), FmtUint(encoded.len));
|
|
|
|
SPR_SliceEntry *slice = &sheet->slices[cmd->slice_idx];
|
|
Vec2 slice_dims = DimsFromRng2(slice->canvas_rect);
|
|
|
|
//////////////////////////////
|
|
//- Composite
|
|
|
|
ASE_Image composite = ASE_PushBlankImage(frame_arena, slice->canvas_rect);
|
|
for (ASE_Layer *ase_layer = sheet->meta.first_layer; ase_layer; ase_layer = ase_layer->next)
|
|
{
|
|
SPR_LayerKind kind = SPR_LayerKindFromName(ase_layer->name);
|
|
if (kind == SPR_LayerKind_Visual)
|
|
{
|
|
ASE_Cel *cel = &ase_layer->cels[cmd->slice_idx];
|
|
{
|
|
TempArena temp = BeginTempArena(scratch.arena);
|
|
{
|
|
// TODO: Reuse decompressed images for linked cels
|
|
ASE_Image image = ASE_DecompressImageFromCel(temp.arena, cel);
|
|
ASE_BlendImage(image, composite);
|
|
}
|
|
EndTempArena(temp);
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////
|
|
//- Write atlas
|
|
|
|
// TODO: Use a more efficient atlas packing algorithm for less wasted space
|
|
SPR_Atlas *atlas = SPR.first_atlas;
|
|
b32 can_use_atlas = 0;
|
|
Vec2I32 atlas_pos = Zi;
|
|
while (can_use_atlas == 0)
|
|
{
|
|
// Create atlas
|
|
if (!atlas)
|
|
{
|
|
atlas = PushStruct(perm, SPR_Atlas);
|
|
i32 atlas_size = MaxI32(1024, NextPow2U64(MaxI32(slice_dims.x, slice_dims.y)));
|
|
atlas->dims = VEC2I32(atlas_size, atlas_size);
|
|
{
|
|
G_ArenaHandle gpu_perm = G_PermArena();
|
|
atlas->tex_res = G_PushTexture2D(
|
|
gpu_perm, cl,
|
|
G_Format_R8G8B8A8_Unorm_Srgb,
|
|
atlas->dims,
|
|
.name = Lit("Sprite atlas")
|
|
);
|
|
atlas->tex = G_PushTexture2DRef(gpu_perm, atlas->tex_res);
|
|
}
|
|
SllStackPush(SPR.first_atlas, atlas);
|
|
++SPR.atlases_count;
|
|
}
|
|
if (atlas->cur_pos.x + slice_dims.x > atlas->dims.x)
|
|
{
|
|
atlas->cur_pos.x = 0;
|
|
atlas->cur_pos.y += atlas->cur_row_height;
|
|
atlas->cur_row_height = 0;
|
|
}
|
|
if (atlas->cur_pos.x + slice_dims.x < atlas->dims.x && atlas->cur_pos.y + slice_dims.y < atlas->dims.y)
|
|
{
|
|
atlas_pos = atlas->cur_pos;
|
|
atlas->cur_row_height = MaxI32(atlas->cur_row_height, slice_dims.y);
|
|
atlas->cur_pos.x += slice_dims.x;
|
|
can_use_atlas = 1;
|
|
}
|
|
else
|
|
{
|
|
// New atlas required
|
|
atlas = 0;
|
|
}
|
|
}
|
|
|
|
// Fill slice_entry atlas info
|
|
{
|
|
slice->atlas = atlas;
|
|
slice->atlas_pos = Vec2FromVec(atlas_pos);
|
|
}
|
|
|
|
// Copy to atlas
|
|
G_CopyCpuToTexture(
|
|
cl,
|
|
atlas->tex_res, VEC3I32(atlas_pos.x, atlas_pos.y, 0),
|
|
composite.pixels, VEC3I32(slice_dims.x, slice_dims.y, 1),
|
|
RNG3I32(
|
|
VEC3I32(0, 0, 0),
|
|
VEC3I32(slice_dims.x, slice_dims.y, 1)
|
|
)
|
|
);
|
|
EndScratch(scratch);
|
|
}
|
|
i64 completion_target = G_CommitCommandList(cl);
|
|
|
|
//////////////////////////////
|
|
//- Update completion targets
|
|
|
|
for (i64 cmd_idx = 0; cmd_idx < cmds_count; ++cmd_idx)
|
|
{
|
|
SPR_Cmd *cmd = &cmds[cmd_idx];
|
|
SPR_SliceEntry *slice = &cmd->sheet->slices[cmd->slice_idx];
|
|
Atomic64Set(&slice->atlas_copy_completion_target, completion_target);
|
|
}
|
|
}
|
|
}
|
|
}
|