From ff7e0b216740595a1e2b8d31020942d7a4987a71 Mon Sep 17 00:00:00 2001 From: jacob Date: Thu, 29 Jan 2026 21:10:17 -0600 Subject: [PATCH] ase refactor wip --- src/ase/ase.c | 1936 ++++++++++++++++----------------- src/ase/ase.h | 507 +++++---- src/pp/pp_res/sprite/bla3.ase | 2 +- src/sprite/sprite.c | 800 ++++++-------- src/sprite/sprite.h | 122 ++- 5 files changed, 1678 insertions(+), 1689 deletions(-) diff --git a/src/ase/ase.c b/src/ase/ase.c index f3be049f..0af781f4 100644 --- a/src/ase/ase.c +++ b/src/ase/ase.c @@ -1,968 +1,968 @@ -// DEFLATE decoder based on Handmade Hero's png parser - -//////////////////////////////////////////////////////////// -//~ Shared constants - -Global Readonly u32 ASE_HuffHclenOrder[] = { - 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 -}; - -Global Readonly ASE_HuffEntry ASE_HuffLenTable[] = { - {3, 0}, // 257 - {4, 0}, // 258 - {5, 0}, // 259 - {6, 0}, // 260 - {7, 0}, // 261 - {8, 0}, // 262 - {9, 0}, // 263 - {10, 0}, // 264 - {11, 1}, // 265 - {13, 1}, // 266 - {15, 1}, // 267 - {17, 1}, // 268 - {19, 2}, // 269 - {23, 2}, // 270 - {27, 2}, // 271 - {31, 2}, // 272 - {35, 3}, // 273 - {43, 3}, // 274 - {51, 3}, // 275 - {59, 3}, // 276 - {67, 4}, // 277 - {83, 4}, // 278 - {99, 4}, // 279 - {115, 4}, // 280 - {131, 5}, // 281 - {163, 5}, // 282 - {195, 5}, // 283 - {227, 5}, // 284 - {258, 0}, // 285 -}; - -Global Readonly ASE_HuffEntry ASE_HuffDistTable[] = { - {1, 0}, // 0 - {2, 0}, // 1 - {3, 0}, // 2 - {4, 0}, // 3 - {5, 1}, // 4 - {7, 1}, // 5 - {9, 2}, // 6 - {13, 2}, // 7 - {17, 3}, // 8 - {25, 3}, // 9 - {33, 4}, // 10 - {49, 4}, // 11 - {65, 5}, // 12 - {97, 5}, // 13 - {129, 6}, // 14 - {193, 6}, // 15 - {257, 7}, // 16 - {385, 7}, // 17 - {513, 8}, // 18 - {769, 8}, // 19 - {1025, 9}, // 20 - {1537, 9}, // 21 - {2049, 10}, // 22 - {3073, 10}, // 23 - {4097, 11}, // 24 - {6145, 11}, // 25 - {8193, 12}, // 26 - {12289, 12}, // 27 - {16385, 13}, // 28 - {24577, 13}, // 29 -}; - -Global Readonly u32 ASE_HuffBlCounts[][2] = { - {143, 8}, - {255, 9}, - {279, 7}, - {287, 8}, - {319, 5}, -}; - -//////////////////////////////////////////////////////////// -//~ Ase bitbuff - -u32 ASE_PeekBits(ASE_Bitbuff *bb, u32 nbits) -{ - Assert(nbits <= 32); - - u64 cur_byte = bb->cur_bit >> 3; - u8 bit_index = bb->cur_bit % 8; - u64 nbytes = (nbits + bit_index + 7) >> 3; - - u64 val64 = 0; - CopyBytes(&val64, &bb->data[cur_byte], nbytes); - u32 val32 = (u32)(val64 >> bit_index); - val32 &= U32Max >> (32 - nbits); - - return val32; -} - -u32 ASE_ConsumeBits(ASE_Bitbuff *bb, u32 nbits) -{ - u32 val = ASE_PeekBits(bb, nbits); - bb->cur_bit += nbits; - return val; -} - -void ASE_SkipBits(ASE_Bitbuff *bb, u32 nbits) -{ - bb->cur_bit += nbits; -} - -//////////////////////////////////////////////////////////// -//~ Inflate - -u32 ASE_ReverseBits(u32 v, u32 bit_count) -{ - // 7 & 15 seem to be the most common bit_counts, so a - // more optimal path is laid out for them. - if (bit_count == 15) - { - u32 b1 = v & 0xFF; - b1 = (b1 & 0xF0) >> 4 | (b1 & 0x0F) << 4; - b1 = (b1 & 0xCC) >> 2 | (b1 & 0x33) << 2; - b1 = (b1 & 0xAA) >> 1 | (b1 & 0x55) << 1; - - u32 b2 = (v & 0xFF00) >> 8; - b2 = (b2 & 0xF0) >> 4 | (b2 & 0x0F) << 4; - b2 = (b2 & 0xCC) >> 2 | (b2 & 0x33) << 2; - b2 = (b2 & 0xAA) >> 1 | (b2 & 0x55) << 1; - b2 >>= 1; - - return (b1 << 7) | b2; - } - else if (bit_count == 7) - { - v = (v & 0xF0) >> 4 | (v & 0x0F) << 4; - v = (v & 0xCC) >> 2 | (v & 0x33) << 2; - v = (v & 0xAA) >> 1 | (v & 0x55) << 1; - return v >> 1; - } - else - { - u32 result = 0; - for (u32 i = 0; i <= (bit_count / 2); ++i) - { - u32 inv = (bit_count - (i + 1)); - result |= ((v >> i) & 0x1) << inv; - result |= ((v >> inv) & 0x1) << i; - } - return result; - } -} - -ASE_HuffDict ASE_InitHuffDict(Arena *arena, u32 max_code_bits, u32 *bl_counts, u32 bl_counts_count) -{ - ASE_HuffDict result = Zi; - result.max_code_bits = max_code_bits; - result.entries_count = (1 << max_code_bits); - result.entries = PushStructsNoZero(arena, ASE_HuffEntry, result.entries_count); - - u32 code_length_hist[ASE_HuffBitCount] = Zi; - for (u32 bl_count_index = 0; bl_count_index < bl_counts_count; ++bl_count_index) - { - u32 count = bl_counts[bl_count_index]; - Assert(count <= countof(code_length_hist)); - ++code_length_hist[count]; - } - - u32 next_code[ASE_HuffBitCount] = Zi; - next_code[0] = 0; - code_length_hist[0] = 0; - for (u32 code_index = 1; code_index < countof(next_code); ++code_index) - { - next_code[code_index] = ((next_code[code_index - 1] + code_length_hist[code_index - 1]) << 1); - } - - for (u32 bl_count_index = 0; bl_count_index < bl_counts_count; ++bl_count_index) - { - u32 code_bits = bl_counts[bl_count_index]; - if (code_bits) - { - Assert(code_bits < countof(next_code)); - u32 code = next_code[code_bits]++; - u32 arbitrary_bits = result.max_code_bits - code_bits; - u32 entry_count = (1 << arbitrary_bits); - // TODO: Optimize this. It's bloating load times. - for (u32 entry_index = 0; entry_index < entry_count; ++entry_index) - { - u32 base_index = (code << arbitrary_bits) | entry_index; - u32 index = ASE_ReverseBits(base_index, result.max_code_bits); - ASE_HuffEntry *entry = &result.entries[index]; - entry->symbol = (u16)bl_count_index; - entry->bits_used = (u16)code_bits; - } - } - } - - return result; -} - -u16 ASE_DecodeHuffDict(ASE_HuffDict *huffman, ASE_Bitbuff *bb) -{ - u32 index = ASE_PeekBits(bb, huffman->max_code_bits); - Assert(index < huffman->entries_count); - - ASE_HuffEntry *entry = &huffman->entries[index]; - u16 result = entry->symbol; - ASE_SkipBits(bb, entry->bits_used); - Assert(entry->bits_used > 0); - return result; -} - -void ASE_Inflate(u8 *dst, u8 *encoded) -{ - TempArena scratch = BeginScratchNoConflict(); - - ASE_Bitbuff bb = { .data = encoded }; - - // ZLIB header - u32 cm = ASE_ConsumeBits(&bb, 4); - u32 cinfo = ASE_ConsumeBits(&bb, 4); - Assert(cm == 8); - Assert(cinfo == 7); - - u32 fcheck = ASE_ConsumeBits(&bb, 5); - u32 fdict = ASE_ConsumeBits(&bb, 1); - u32 flevl = ASE_ConsumeBits(&bb, 2); - Assert(fdict == 0); - - u8 cmf = (u8)(cm | (cinfo << 4)); - u8 flg = fcheck | (fdict << 5) | (flevl << 6); - Assert(((cmf * 256) + flg) % 31 == 0); - - u8 bfinal = 0; - while (!bfinal) - { - bfinal = ASE_ConsumeBits(&bb, 1); - u8 btype = ASE_ConsumeBits(&bb, 2); - switch (btype) - { - case ASE_BlockType_Uncompressed: - { - ASE_SkipBits(&bb, (8 - (bb.cur_bit % 8)) % 8); - i16 len = ASE_ConsumeBits(&bb, 16); - i16 nlen = ASE_ConsumeBits(&bb, 16); - Assert(len == ~nlen); // Validation - while (len-- > 0) - { - *dst++ = ASE_ConsumeBits(&bb, 8); - } - } break; - - case ASE_BlockType_CompressedFixed: - case ASE_BlockType_CompressedDynamic: - { - TempArena temp = BeginTempArena(scratch.arena); - u32 lit_len_dist_table[512] = Zi; - u32 hlit; - u32 hdist; - - if (btype == ASE_BlockType_CompressedDynamic) - { - // Dynamic table - - // Read huffman table - hlit = ASE_ConsumeBits(&bb, 5) + 257; - hdist = ASE_ConsumeBits(&bb, 5) + 1; - u32 hclen = ASE_ConsumeBits(&bb, 4) + 4; - - // Init dict huffman (hclen) - u32 hclen_bl_counts[19] = Zi; - for (u32 i = 0; i < hclen; ++i) - { - u32 code = ASE_HuffHclenOrder[i]; - hclen_bl_counts[code] = ASE_ConsumeBits(&bb, 3); - } - ASE_HuffDict dict_huffman = ASE_InitHuffDict(temp.arena, 7, hclen_bl_counts, countof(hclen_bl_counts)); - - // Decode dict huffman - u32 lit_len_count = 0; - u32 len_count = hlit + hdist; - Assert(len_count <= countof(lit_len_dist_table)); - while (lit_len_count < len_count) - { - u32 rep_count = 1; - u32 rep_val = 0; - u32 encoded_len = ASE_DecodeHuffDict(&dict_huffman, &bb); - if (encoded_len <= 15) - { - rep_val = encoded_len; - } - else if (encoded_len == 16) - { - rep_count = 3 + ASE_ConsumeBits(&bb, 2); - Assert(lit_len_count > 0); - rep_val = lit_len_dist_table[lit_len_count - 1]; - } - else if (encoded_len == 17) - { - rep_count = 3 + ASE_ConsumeBits(&bb, 3); - } - else if (encoded_len == 18) - { - rep_count = 11 + ASE_ConsumeBits(&bb, 7); - } - else - { - // Invalid len - Assert(0); - } - - while (rep_count--) - { - lit_len_dist_table[lit_len_count++] = rep_val; - } - } - Assert(lit_len_count == len_count); - } - else - { - // Fixed table - hlit = 288; - hdist = 32; - u32 index = 0; - for (u32 i = 0; i < countof(ASE_HuffBlCounts); ++i) - { - u32 bit_count = ASE_HuffBlCounts[i][1]; - u32 last_valuie = ASE_HuffBlCounts[i][0]; - while (index <= last_valuie) - { - lit_len_dist_table[index++] = bit_count; - } - } - } - - // Decode - ASE_HuffDict lit_len_huffman = ASE_InitHuffDict(temp.arena, 15, lit_len_dist_table, hlit); - ASE_HuffDict dist_huffman = ASE_InitHuffDict(temp.arena, 15, lit_len_dist_table + hlit, hdist); - for (;;) - { - u32 lit_len = ASE_DecodeHuffDict(&lit_len_huffman, &bb); - if (lit_len <= 255) - { - *dst++ = lit_len & 0xFF; - } - else if (lit_len >= 257) - { - u32 length_index = (lit_len - 257); - ASE_HuffEntry length_entry = ASE_HuffLenTable[length_index]; - u32 length = length_entry.symbol; - if (length_entry.bits_used > 0) - { - u32 extra_bits = ASE_ConsumeBits(&bb, length_entry.bits_used); - length += extra_bits; - } - - u32 dist_index = ASE_DecodeHuffDict(&dist_huffman, &bb); - ASE_HuffEntry dist_entry = ASE_HuffDistTable[dist_index]; - u32 distance = dist_entry.symbol; - if (dist_entry.bits_used > 0) - { - u32 extra_bits = ASE_ConsumeBits(&bb, dist_entry.bits_used); - distance += extra_bits; - } - u8 *src = dst - distance; - while (length--) - { - *dst++ = *src++; - } - } - else - { - break; - } - } - - EndTempArena(temp); - } break; - - case ASE_BlockType_Reserved: - { - // TODO - Assert(0); - } break; - } - } - - EndScratch(scratch); -} - -//////////////////////////////////////////////////////////// -//~ Error helpers - -void ASE_PushError(Arena *arena, ASE_ErrorList *list, String msg_src) -{ - ASE_Error *e = PushStruct(arena, ASE_Error); - e->msg = PushString(arena, msg_src); - if (!list->first) - { - list->first = e; - } - else - { - list->last->next = e; - } - list->last = e; - ++list->count; -} - -//////////////////////////////////////////////////////////// -//~ Decode helpers - -u32 ASE_Blend(u32 src, u32 dst, u8 opacity) -{ - u32 dst_r = (dst & 0xff); - u32 dst_g = (dst >> 8) & 0xff; - u32 dst_b = (dst >> 16) & 0xff; - u32 dst_a = (dst >> 24) & 0xff; - - u32 src_r = (src & 0xff); - u32 src_g = (src >> 8) & 0xff; - u32 src_b = (src >> 16) & 0xff; - u32 src_a = (src >> 24) & 0xff; - - src_a = (u8)MulNormalizedU8(src_a, opacity); - u32 a = src_a + dst_a - MulNormalizedU8(src_a, dst_a); - - u32 r = 0; - u32 g = 0; - u32 b = 0; - if (a != 0) - { - r = dst_r + (src_r - dst_r) * src_a / a; - g = dst_g + (src_g - dst_g) * src_a / a; - b = dst_b + (src_b - dst_b) * src_a / a; - } - - return (r << 0) | (g << 8) | (b << 16) | (a << 24); -} - -void ASE_MakeDimensionsSquareish(ASE_Header *header, u32 *frames_x, u32 *frames_y, u64 *image_width, u64 *image_height) -{ - // Try and get image resolution into as much of a square as possible by - // separating frames into multiple rows. - while (*frames_x > 1) - { - u64 new_frames_x = *frames_x - 1; - u64 new_frames_y = ((header->frames - 1) / new_frames_x) + 1; - u64 new_image_width = header->width * new_frames_x; - u64 new_image_height = header->height * new_frames_y; - if (new_image_width >= new_image_height) - { - *frames_x = new_frames_x; - *frames_y = new_frames_y; - *image_width = new_image_width; - *image_height = new_image_height; - } - else - { - break; - } - } -} - -//////////////////////////////////////////////////////////// -//~ Decode image - -ASE_DecodedImage ASE_DecodeImage(Arena *arena, String encoded) -{ - TempArena scratch = BeginScratch(arena); - ASE_DecodedImage result = Zi; - - BB_Buff bb = BB_BuffFromString(encoded); - BB_Reader bbr = BB_ReaderFromBuffNoDebug(&bb); - ASE_Header ase_header; - BB_ReadBytes(&bbr, StringFromStruct(&ase_header)); - - if (ase_header.magic != 0xA5E0) - { - ASE_PushError(arena, &result.errors, Lit("Not a valid aseprite file")); - goto abort; - } - - if (ase_header.color_depth != 32) - { - String msg = StringF( - scratch.arena, - "Only 32 bit rgba color mode is supported (got %F)", - FmtUint(ase_header.color_depth) - ); - ASE_PushError(arena, &result.errors, msg); - goto abort; - } - - u64 frame_width = ase_header.width; - u64 frame_height = ase_header.height; - - u32 frames_x = ase_header.frames; - u32 frames_y = 1; - u64 image_width = frame_width * frames_x; - u64 image_height = frame_height * frames_y; - ASE_MakeDimensionsSquareish(&ase_header, &frames_x, &frames_y, &image_width, &image_height); - - result.width = image_width; - result.height = image_height; - // TODO: Optimize this. Naive memzero is bloating the decode time for large images. - result.pixels = PushStructs(arena, u32, image_width * image_height); - - u32 layers_count = 0; - ASE_Layer *layer_head = 0; - - ASE_Cel *cel_head = 0; - ASE_Cel *cel_tail = 0; - - ////////////////////////////// - //- Iterate frames - - u32 frames_count = 0; - for (u16 i = 0; i < ase_header.frames; ++i) - { - ASE_FrameHeader frame_header; - BB_ReadBytes(&bbr, StringFromStruct(&frame_header)); - - u32 chunks_count = frame_header.chunks_new; - if (chunks_count == 0) - { - Assert(frame_header.chunks_old != 0xFFFF); - chunks_count = frame_header.chunks_old; - } - - ////////////////////////////// - //- Iterate chunks - - for (u32 j = 0; j < chunks_count; ++j) - { - u32 chunk_size = BB_ReadUBits(&bbr, 32); - ASE_ChunkKind chunk_type = BB_ReadUBits(&bbr, 16); - - // Chunk size includes size & type - Assert(chunk_size >= 6); - chunk_size -= 6; - - u64 chunk_end_pos = BB_GetCurrentReaderByte(&bbr) + chunk_size; - - switch (chunk_type) - { - default: - { - BB_ReadSeekToByte(&bbr, chunk_end_pos); - } break; - - ////////////////////////////// - //- Decode layer - - case ASE_ChunkKind_Layer: - { - ASE_Layer *layer = PushStruct(scratch.arena, ASE_Layer); - layer->next = layer_head; - layer_head = layer; - - layer->flags = BB_ReadUBits(&bbr, 16); - layer->type = BB_ReadUBits(&bbr, 16); - layer->child_level = BB_ReadUBits(&bbr, 16); - - // Ignoring layer default width & height - BB_ReadSeekBytes(&bbr, sizeof(u16) * 2); - - layer->blend_mode = BB_ReadUBits(&bbr, 16); - if (layer->blend_mode != 0) - { - ASE_PushError( - arena, - &result.errors, - Lit("Layer has unsupported blend mode (only 'Normal' mode is supported). Tip: Try using 'merge down' to create a normal layer as a workaround") - ); - goto abort; - } - - layer->opacity = BB_ReadUBits(&bbr, 8); - if (!(ase_header.flags & 1)) - { - layer->opacity = 255; - } - - BB_ReadSeekBytes(&bbr, sizeof(u8) * 3); - - u16 str_len = BB_ReadUBits(&bbr, 16); - layer->name = (String) { str_len, PushStructsNoZero(scratch.arena, u8, str_len) }; - BB_ReadBytes(&bbr, layer->name); - - if (layer->type == 2) - { - layer->tileset_index = BB_ReadUBits(&bbr, 32); - } - - layer->index = layers_count++; - } break; - - ////////////////////////////// - //- Decode cel - - case ASE_ChunkKind_Cel: - { - ASE_Cel *cel = PushStruct(scratch.arena, ASE_Cel); - if (cel_tail) - { - cel_tail->next = cel; - } - else - { - cel_head = cel; - } - cel_tail = cel; - - cel->layer_index = BB_ReadUBits(&bbr, 16); - cel->x_pos = BB_ReadIBits(&bbr, 16); - cel->y_pos = BB_ReadIBits(&bbr, 16); - cel->opacity = BB_ReadUBits(&bbr, 8); - cel->type = BB_ReadUBits(&bbr, 16); - cel->z_index = BB_ReadIBits(&bbr, 16); - BB_ReadSeekBytes(&bbr, sizeof(u8) * 5); - - cel->frame_index = frames_count; - - switch (cel->type) - { - case ASE_CelKind_RawImage: - { - // Unsupported - BB_ReadSeekToByte(&bbr, chunk_end_pos); - } break; - - case ASE_CelKind_Linked: - { - cel->frame_pos = BB_ReadUBits(&bbr, 16); - // Actual linking happens later after iteration - } break; - - case ASE_CelKind_CompressedImage: - { - cel->width = BB_ReadUBits(&bbr, 16); - cel->height = BB_ReadUBits(&bbr, 16); - - cel->pixels = PushStructsNoZero(scratch.arena, u32, cel->width * cel->height); - u8 *huffman_encoded = BB_ReadBytesRaw(&bbr, chunk_end_pos - BB_GetCurrentReaderByte(&bbr)); - if (huffman_encoded) - { - ASE_Inflate((u8 *)cel->pixels, huffman_encoded); - } - } break; - - case ASE_CelKind_CompressedTilemap: - { - // Unsupported - ASE_PushError(arena, &result.errors, Lit("Tilemaps are not supported")); - goto abort; - } break; - } - } break; - } - } - ++frames_count; - } - - ////////////////////////////// - //- Create ordered layers array - - ASE_Layer **layers_ordered = PushStructsNoZero(scratch.arena, ASE_Layer *, layers_count); - for (ASE_Layer *layer = layer_head; layer; layer = layer->next) - { - layers_ordered[layer->index] = layer; - } - - ////////////////////////////// - //- Link cels - - ASE_Cel **cels_ordered = PushStructsNoZero(scratch.arena, ASE_Cel *, frames_count * layers_count); - for (ASE_Cel *cel = cel_head; cel; cel = cel->next) - { - cels_ordered[(cel->frame_index * layers_count) + cel->layer_index] = cel; - if (cel->type == ASE_CelKind_Linked) - { - ASE_Cel *ref_cel = cels_ordered[(cel->frame_pos * layers_count) + cel->layer_index]; - cel->width = ref_cel->width; - cel->height = ref_cel->height; - cel->pixels = ref_cel->pixels; - } - } - - ////////////////////////////// - //- Assemble image from cels - - { - for (ASE_Cel *cel = cel_head; cel; cel = cel->next) - { - ASE_Layer *layer = layers_ordered[cel->layer_index]; - // Only draw visible layers - if (layer->flags & 1) - { - u8 opacity = (cel->opacity / 255.0f) * (layer->opacity / 255.0f) * 255.0f; - - i32 cel_width = cel->width; - i32 cel_height = cel->height; - - i32 cel_left = 0; - i32 cel_right = cel_width; - i32 cel_top = 0; - i32 cel_bottom = cel_height; - - i32 frame_left = cel->x_pos; - i32 frame_top = cel->y_pos; - - i32 image_left = frame_left + ((cel->frame_index % frames_x) * frame_width); - i32 image_top = frame_top + ((cel->frame_index / frames_x) * frame_height); - - // Adjust bounds to ensure pixels outside of frame boundaries - // aren't processed (aseprite keeps chunks outside of frame - // around in project file). - { - i32 frame_right = cel_width + frame_left; - i32 frame_bottom = frame_top + cel_height; - if (frame_left < 0) - { - cel_left += -frame_left; - frame_left = 0; - } - if (frame_top < 0) - { - cel_top += -frame_top; - frame_top = 0; - } - if (frame_right > (i32)frame_width) - { - cel_right -= (frame_right - frame_width); - frame_right = frame_width; - } - if (frame_bottom > (i32)frame_height) - { - cel_bottom -= (frame_bottom - frame_height); - frame_bottom = frame_height; - } - } - - for (i32 cel_y = cel_top; cel_y < cel_bottom; ++cel_y) - { - i32 image_y = image_top + cel_y; - i32 cel_stride = cel_y * cel_width; - i32 image_stride = image_y * image_width; - for (i32 cel_x = cel_left; cel_x < cel_right; ++cel_x) - { - i32 image_x = image_left + cel_x; - u32 cel_pixel = cel->pixels[cel_x + cel_stride]; - u32 *image_pixel = &result.pixels[image_x + image_stride]; - *image_pixel = ASE_Blend(cel_pixel, *image_pixel, opacity); - } - } - } - } - } - - // Sanity check to ensure all data was read - Assert(BB_NumBytesRemaining(&bbr) == 0); - - abort: - - if (result.errors.count <= 0) - { - result.ok = 1; - } - - EndScratch(scratch); - return result; -} - -//////////////////////////////////////////////////////////// -//~ Decode sheet - -ASE_DecodedSheet ASE_DecodeSheet(Arena *arena, String encoded) -{ - ASE_DecodedSheet result = Zi; - - BB_Buff bb = BB_BuffFromString(encoded); - BB_Reader bbr = BB_ReaderFromBuffNoDebug(&bb); - ASE_Header ase_header; - BB_ReadBytes(&bbr, StringFromStruct(&ase_header)); - - if (ase_header.magic != 0xA5E0) - { - ASE_PushError(arena, &result.errors, Lit("Not a valid aseprite file")); - goto abort; - } - - u64 frame_width = ase_header.width; - u64 frame_height = ase_header.height; - - u32 frames_x = ase_header.frames; - u32 frames_y = 1; - u64 image_width = frame_width * frames_x; - u64 image_height = frame_height * frames_y; - ASE_MakeDimensionsSquareish(&ase_header, &frames_x, &frames_y, &image_width, &image_height); - - u32 frames_count = 0; - ASE_Frame *first_frame = 0; - - u32 spans_count = 0; - ASE_Span *first_span = 0; - - u32 slice_keys_count = 0; - ASE_SliceKey *first_slice_key = 0; - - ////////////////////////////// - //- Iterate frames - - for (u16 i = 0; i < ase_header.frames; ++i) - { - ASE_FrameHeader frame_header; - BB_ReadBytes(&bbr, StringFromStruct(&frame_header)); - - u32 chunks_count = frame_header.chunks_new; - if (chunks_count == 0) - { - Assert(frame_header.chunks_old != 0xFFFF); - chunks_count = frame_header.chunks_old; - } - - ASE_Frame *frame = PushStruct(arena, ASE_Frame); - frame->next = first_frame; - first_frame = frame; - - frame->index = i; - frame->duration = frame_header.frame_duration_ms / 1000.0; - - u32 frame_tile_x = i % frames_x; - u32 frame_tile_y = i / frames_x; - - Vec2I32 frame_p0 = VEC2I32(frame_tile_x * frame_width, frame_tile_y * frame_height); - Vec2I32 frame_p1 = AddVec2I32(frame_p0, VEC2I32(frame_width, frame_height)); - frame->rect.p0 = frame_p0; - frame->rect.p1 = frame_p1; - - frame->index = i; - frame->duration = frame_header.frame_duration_ms / 1000.0; - - ////////////////////////////// - //- Iterate chunks - - for (u32 j = 0; j < chunks_count; ++j) - { - u32 chunk_size = BB_ReadUBits(&bbr, 32); - ASE_ChunkKind chunk_type = BB_ReadUBits(&bbr, 16); - - // Chunk size includes size & type - Assert(chunk_size >= 6); - chunk_size -= 6; - - u64 chunk_end_pos = BB_GetCurrentReaderByte(&bbr) + chunk_size; - - switch (chunk_type) - { - default: - { - BB_ReadSeekToByte(&bbr, chunk_end_pos); - } break; - - //- Decode tags - case ASE_ChunkKind_Tags: - { - u16 frame_span_count = BB_ReadUBits(&bbr, 16); - BB_ReadSeekBytes(&bbr, 8); - - for (u16 k = 0; k < frame_span_count; ++k) - { - ASE_Span *span = PushStruct(arena, ASE_Span); - span->next = first_span; - first_span = span; - - span->start = BB_ReadUBits(&bbr, 16); - span->end = BB_ReadUBits(&bbr, 16); - BB_ReadSeekBytes(&bbr, 13); - - u16 str_len = BB_ReadUBits(&bbr, 16); - span->name = (String) { str_len, PushStructsNoZero(arena, u8, str_len) }; - BB_ReadBytes(&bbr, span->name); - - ++spans_count; - } - - } break; - - ////////////////////////////// - //- Decode slice - - case ASE_ChunkKind_Slice: - { - ASE_SliceKey *slice_key = PushStruct(arena, ASE_SliceKey); - slice_key->next = first_slice_key; - first_slice_key = slice_key; - - u32 slices_count = BB_ReadUBits(&bbr, 32); - slice_key->slices_count = slices_count; - - u32 flags = BB_ReadUBits(&bbr, 32); - BB_ReadSeekBytes(&bbr, 4); - - u16 str_len = BB_ReadUBits(&bbr, 16); - slice_key->name = (String) { str_len, PushStructsNoZero(arena, u8, str_len) }; - BB_ReadBytes(&bbr, slice_key->name); - - for (u32 k = 0; k < slices_count; ++k) - { - ASE_Slice *slice = PushStruct(arena, ASE_Slice); - slice->next = slice_key->first_slice; - slice_key->first_slice = slice; - - u32 start = BB_ReadUBits(&bbr, 32); - i32 x = BB_ReadIBits(&bbr, 32); - i32 y = BB_ReadIBits(&bbr, 32); - u32 width = BB_ReadUBits(&bbr, 32); - u32 height = BB_ReadUBits(&bbr, 32); - if (flags & 0x01) - { - // Skip 9-patches info - BB_ReadSeekBytes(&bbr, 128); - } - if (flags & 0x02) - { - // Skip pivot info - BB_ReadSeekBytes(&bbr, 64); - } - - slice->start = start; - slice->rect.p0 = VEC2I32(x, y); - slice->rect.p1 = VEC2I32(x + width, y + width); - } - - ++slice_keys_count; - } break; - - // TODO - //case ASE_ChunkKind_Userdata - } - } - ++frames_count; - } - - // Assert all data was read - Assert(BB_NumBytesRemaining(&bbr) == 0); - - result.image_size = VEC2(image_width, image_height); - result.frame_size = VEC2(frame_width, frame_height); - result.frames_count = frames_count; - result.spans_count = spans_count; - result.slice_keys_count = slice_keys_count; - result.first_frame = first_frame; - result.first_span = first_span; - result.first_slice_key = first_slice_key; - - abort: - - if (result.errors.count <= 0) - { - result.ok = 1; - } - - return result; -} +// // DEFLATE decoder based on Handmade Hero's png parser + +// //////////////////////////////////////////////////////////// +// //~ Shared constants + +// Global Readonly u32 ASE_HuffHclenOrder[] = { +// 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 +// }; + +// Global Readonly ASE_HuffEntry ASE_HuffLenTable[] = { +// {3, 0}, // 257 +// {4, 0}, // 258 +// {5, 0}, // 259 +// {6, 0}, // 260 +// {7, 0}, // 261 +// {8, 0}, // 262 +// {9, 0}, // 263 +// {10, 0}, // 264 +// {11, 1}, // 265 +// {13, 1}, // 266 +// {15, 1}, // 267 +// {17, 1}, // 268 +// {19, 2}, // 269 +// {23, 2}, // 270 +// {27, 2}, // 271 +// {31, 2}, // 272 +// {35, 3}, // 273 +// {43, 3}, // 274 +// {51, 3}, // 275 +// {59, 3}, // 276 +// {67, 4}, // 277 +// {83, 4}, // 278 +// {99, 4}, // 279 +// {115, 4}, // 280 +// {131, 5}, // 281 +// {163, 5}, // 282 +// {195, 5}, // 283 +// {227, 5}, // 284 +// {258, 0}, // 285 +// }; + +// Global Readonly ASE_HuffEntry ASE_HuffDistTable[] = { +// {1, 0}, // 0 +// {2, 0}, // 1 +// {3, 0}, // 2 +// {4, 0}, // 3 +// {5, 1}, // 4 +// {7, 1}, // 5 +// {9, 2}, // 6 +// {13, 2}, // 7 +// {17, 3}, // 8 +// {25, 3}, // 9 +// {33, 4}, // 10 +// {49, 4}, // 11 +// {65, 5}, // 12 +// {97, 5}, // 13 +// {129, 6}, // 14 +// {193, 6}, // 15 +// {257, 7}, // 16 +// {385, 7}, // 17 +// {513, 8}, // 18 +// {769, 8}, // 19 +// {1025, 9}, // 20 +// {1537, 9}, // 21 +// {2049, 10}, // 22 +// {3073, 10}, // 23 +// {4097, 11}, // 24 +// {6145, 11}, // 25 +// {8193, 12}, // 26 +// {12289, 12}, // 27 +// {16385, 13}, // 28 +// {24577, 13}, // 29 +// }; + +// Global Readonly u32 ASE_HuffBlCounts[][2] = { +// {143, 8}, +// {255, 9}, +// {279, 7}, +// {287, 8}, +// {319, 5}, +// }; + +// //////////////////////////////////////////////////////////// +// //~ Ase bitbuff + +// u32 ASE_PeekBits(ASE_Bitbuff *bb, u32 nbits) +// { +// Assert(nbits <= 32); + +// u64 cur_byte = bb->cur_bit >> 3; +// u8 bit_index = bb->cur_bit % 8; +// u64 nbytes = (nbits + bit_index + 7) >> 3; + +// u64 val64 = 0; +// CopyBytes(&val64, &bb->data[cur_byte], nbytes); +// u32 val32 = (u32)(val64 >> bit_index); +// val32 &= U32Max >> (32 - nbits); + +// return val32; +// } + +// u32 ASE_ConsumeBits(ASE_Bitbuff *bb, u32 nbits) +// { +// u32 val = ASE_PeekBits(bb, nbits); +// bb->cur_bit += nbits; +// return val; +// } + +// void ASE_SkipBits(ASE_Bitbuff *bb, u32 nbits) +// { +// bb->cur_bit += nbits; +// } + +// //////////////////////////////////////////////////////////// +// //~ Inflate + +// u32 ASE_ReverseBits(u32 v, u32 bit_count) +// { +// // 7 & 15 seem to be the most common bit_counts, so a +// // more optimal path is laid out for them. +// if (bit_count == 15) +// { +// u32 b1 = v & 0xFF; +// b1 = (b1 & 0xF0) >> 4 | (b1 & 0x0F) << 4; +// b1 = (b1 & 0xCC) >> 2 | (b1 & 0x33) << 2; +// b1 = (b1 & 0xAA) >> 1 | (b1 & 0x55) << 1; + +// u32 b2 = (v & 0xFF00) >> 8; +// b2 = (b2 & 0xF0) >> 4 | (b2 & 0x0F) << 4; +// b2 = (b2 & 0xCC) >> 2 | (b2 & 0x33) << 2; +// b2 = (b2 & 0xAA) >> 1 | (b2 & 0x55) << 1; +// b2 >>= 1; + +// return (b1 << 7) | b2; +// } +// else if (bit_count == 7) +// { +// v = (v & 0xF0) >> 4 | (v & 0x0F) << 4; +// v = (v & 0xCC) >> 2 | (v & 0x33) << 2; +// v = (v & 0xAA) >> 1 | (v & 0x55) << 1; +// return v >> 1; +// } +// else +// { +// u32 result = 0; +// for (u32 i = 0; i <= (bit_count / 2); ++i) +// { +// u32 inv = (bit_count - (i + 1)); +// result |= ((v >> i) & 0x1) << inv; +// result |= ((v >> inv) & 0x1) << i; +// } +// return result; +// } +// } + +// ASE_HuffDict ASE_InitHuffDict(Arena *arena, u32 max_code_bits, u32 *bl_counts, u32 bl_counts_count) +// { +// ASE_HuffDict result = Zi; +// result.max_code_bits = max_code_bits; +// result.entries_count = (1 << max_code_bits); +// result.entries = PushStructsNoZero(arena, ASE_HuffEntry, result.entries_count); + +// u32 code_length_hist[ASE_HuffBitCount] = Zi; +// for (u32 bl_count_index = 0; bl_count_index < bl_counts_count; ++bl_count_index) +// { +// u32 count = bl_counts[bl_count_index]; +// Assert(count <= countof(code_length_hist)); +// ++code_length_hist[count]; +// } + +// u32 next_code[ASE_HuffBitCount] = Zi; +// next_code[0] = 0; +// code_length_hist[0] = 0; +// for (u32 code_index = 1; code_index < countof(next_code); ++code_index) +// { +// next_code[code_index] = ((next_code[code_index - 1] + code_length_hist[code_index - 1]) << 1); +// } + +// for (u32 bl_count_index = 0; bl_count_index < bl_counts_count; ++bl_count_index) +// { +// u32 code_bits = bl_counts[bl_count_index]; +// if (code_bits) +// { +// Assert(code_bits < countof(next_code)); +// u32 code = next_code[code_bits]++; +// u32 arbitrary_bits = result.max_code_bits - code_bits; +// u32 entry_count = (1 << arbitrary_bits); +// // TODO: Optimize this. It's bloating load times. +// for (u32 entry_index = 0; entry_index < entry_count; ++entry_index) +// { +// u32 base_index = (code << arbitrary_bits) | entry_index; +// u32 index = ASE_ReverseBits(base_index, result.max_code_bits); +// ASE_HuffEntry *entry = &result.entries[index]; +// entry->symbol = (u16)bl_count_index; +// entry->bits_used = (u16)code_bits; +// } +// } +// } + +// return result; +// } + +// u16 ASE_DecodeHuffDict(ASE_HuffDict *huffman, ASE_Bitbuff *bb) +// { +// u32 index = ASE_PeekBits(bb, huffman->max_code_bits); +// Assert(index < huffman->entries_count); + +// ASE_HuffEntry *entry = &huffman->entries[index]; +// u16 result = entry->symbol; +// ASE_SkipBits(bb, entry->bits_used); +// Assert(entry->bits_used > 0); +// return result; +// } + +// void ASE_Inflate(u8 *dst, u8 *encoded) +// { +// TempArena scratch = BeginScratchNoConflict(); + +// ASE_Bitbuff bb = { .data = encoded }; + +// // ZLIB header +// u32 cm = ASE_ConsumeBits(&bb, 4); +// u32 cinfo = ASE_ConsumeBits(&bb, 4); +// Assert(cm == 8); +// Assert(cinfo == 7); + +// u32 fcheck = ASE_ConsumeBits(&bb, 5); +// u32 fdict = ASE_ConsumeBits(&bb, 1); +// u32 flevl = ASE_ConsumeBits(&bb, 2); +// Assert(fdict == 0); + +// u8 cmf = (u8)(cm | (cinfo << 4)); +// u8 flg = fcheck | (fdict << 5) | (flevl << 6); +// Assert(((cmf * 256) + flg) % 31 == 0); + +// u8 bfinal = 0; +// while (!bfinal) +// { +// bfinal = ASE_ConsumeBits(&bb, 1); +// u8 btype = ASE_ConsumeBits(&bb, 2); +// switch (btype) +// { +// case ASE_BlockType_Uncompressed: +// { +// ASE_SkipBits(&bb, (8 - (bb.cur_bit % 8)) % 8); +// i16 len = ASE_ConsumeBits(&bb, 16); +// i16 nlen = ASE_ConsumeBits(&bb, 16); +// Assert(len == ~nlen); // Validation +// while (len-- > 0) +// { +// *dst++ = ASE_ConsumeBits(&bb, 8); +// } +// } break; + +// case ASE_BlockType_CompressedFixed: +// case ASE_BlockType_CompressedDynamic: +// { +// TempArena temp = BeginTempArena(scratch.arena); +// u32 lit_len_dist_table[512] = Zi; +// u32 hlit; +// u32 hdist; + +// if (btype == ASE_BlockType_CompressedDynamic) +// { +// // Dynamic table + +// // Read huffman table +// hlit = ASE_ConsumeBits(&bb, 5) + 257; +// hdist = ASE_ConsumeBits(&bb, 5) + 1; +// u32 hclen = ASE_ConsumeBits(&bb, 4) + 4; + +// // Init dict huffman (hclen) +// u32 hclen_bl_counts[19] = Zi; +// for (u32 i = 0; i < hclen; ++i) +// { +// u32 code = ASE_HuffHclenOrder[i]; +// hclen_bl_counts[code] = ASE_ConsumeBits(&bb, 3); +// } +// ASE_HuffDict dict_huffman = ASE_InitHuffDict(temp.arena, 7, hclen_bl_counts, countof(hclen_bl_counts)); + +// // Decode dict huffman +// u32 lit_len_count = 0; +// u32 len_count = hlit + hdist; +// Assert(len_count <= countof(lit_len_dist_table)); +// while (lit_len_count < len_count) +// { +// u32 rep_count = 1; +// u32 rep_val = 0; +// u32 encoded_len = ASE_DecodeHuffDict(&dict_huffman, &bb); +// if (encoded_len <= 15) +// { +// rep_val = encoded_len; +// } +// else if (encoded_len == 16) +// { +// rep_count = 3 + ASE_ConsumeBits(&bb, 2); +// Assert(lit_len_count > 0); +// rep_val = lit_len_dist_table[lit_len_count - 1]; +// } +// else if (encoded_len == 17) +// { +// rep_count = 3 + ASE_ConsumeBits(&bb, 3); +// } +// else if (encoded_len == 18) +// { +// rep_count = 11 + ASE_ConsumeBits(&bb, 7); +// } +// else +// { +// // Invalid len +// Assert(0); +// } + +// while (rep_count--) +// { +// lit_len_dist_table[lit_len_count++] = rep_val; +// } +// } +// Assert(lit_len_count == len_count); +// } +// else +// { +// // Fixed table +// hlit = 288; +// hdist = 32; +// u32 index = 0; +// for (u32 i = 0; i < countof(ASE_HuffBlCounts); ++i) +// { +// u32 bit_count = ASE_HuffBlCounts[i][1]; +// u32 last_valuie = ASE_HuffBlCounts[i][0]; +// while (index <= last_valuie) +// { +// lit_len_dist_table[index++] = bit_count; +// } +// } +// } + +// // Decode +// ASE_HuffDict lit_len_huffman = ASE_InitHuffDict(temp.arena, 15, lit_len_dist_table, hlit); +// ASE_HuffDict dist_huffman = ASE_InitHuffDict(temp.arena, 15, lit_len_dist_table + hlit, hdist); +// for (;;) +// { +// u32 lit_len = ASE_DecodeHuffDict(&lit_len_huffman, &bb); +// if (lit_len <= 255) +// { +// *dst++ = lit_len & 0xFF; +// } +// else if (lit_len >= 257) +// { +// u32 length_index = (lit_len - 257); +// ASE_HuffEntry length_entry = ASE_HuffLenTable[length_index]; +// u32 length = length_entry.symbol; +// if (length_entry.bits_used > 0) +// { +// u32 extra_bits = ASE_ConsumeBits(&bb, length_entry.bits_used); +// length += extra_bits; +// } + +// u32 dist_index = ASE_DecodeHuffDict(&dist_huffman, &bb); +// ASE_HuffEntry dist_entry = ASE_HuffDistTable[dist_index]; +// u32 distance = dist_entry.symbol; +// if (dist_entry.bits_used > 0) +// { +// u32 extra_bits = ASE_ConsumeBits(&bb, dist_entry.bits_used); +// distance += extra_bits; +// } +// u8 *src = dst - distance; +// while (length--) +// { +// *dst++ = *src++; +// } +// } +// else +// { +// break; +// } +// } + +// EndTempArena(temp); +// } break; + +// case ASE_BlockType_Reserved: +// { +// // TODO +// Assert(0); +// } break; +// } +// } + +// EndScratch(scratch); +// } + +// //////////////////////////////////////////////////////////// +// //~ Error helpers + +// void ASE_PushError(Arena *arena, ASE_ErrorList *list, String msg_src) +// { +// ASE_Error *e = PushStruct(arena, ASE_Error); +// e->msg = PushString(arena, msg_src); +// if (!list->first) +// { +// list->first = e; +// } +// else +// { +// list->last->next = e; +// } +// list->last = e; +// ++list->count; +// } + +// //////////////////////////////////////////////////////////// +// //~ Decode helpers + +// u32 ASE_Blend(u32 src, u32 dst, u8 opacity) +// { +// u32 dst_r = (dst & 0xff); +// u32 dst_g = (dst >> 8) & 0xff; +// u32 dst_b = (dst >> 16) & 0xff; +// u32 dst_a = (dst >> 24) & 0xff; + +// u32 src_r = (src & 0xff); +// u32 src_g = (src >> 8) & 0xff; +// u32 src_b = (src >> 16) & 0xff; +// u32 src_a = (src >> 24) & 0xff; + +// src_a = (u8)MulNormalizedU8(src_a, opacity); +// u32 a = src_a + dst_a - MulNormalizedU8(src_a, dst_a); + +// u32 r = 0; +// u32 g = 0; +// u32 b = 0; +// if (a != 0) +// { +// r = dst_r + (src_r - dst_r) * src_a / a; +// g = dst_g + (src_g - dst_g) * src_a / a; +// b = dst_b + (src_b - dst_b) * src_a / a; +// } + +// return (r << 0) | (g << 8) | (b << 16) | (a << 24); +// } + +// void ASE_MakeDimensionsSquareish(ASE_Header *header, u32 *frames_x, u32 *frames_y, u64 *image_width, u64 *image_height) +// { +// // Try and get image resolution into as much of a square as possible by +// // separating frames into multiple rows. +// while (*frames_x > 1) +// { +// u64 new_frames_x = *frames_x - 1; +// u64 new_frames_y = ((header->frames - 1) / new_frames_x) + 1; +// u64 new_image_width = header->width * new_frames_x; +// u64 new_image_height = header->height * new_frames_y; +// if (new_image_width >= new_image_height) +// { +// *frames_x = new_frames_x; +// *frames_y = new_frames_y; +// *image_width = new_image_width; +// *image_height = new_image_height; +// } +// else +// { +// break; +// } +// } +// } + +// //////////////////////////////////////////////////////////// +// //~ Decode image + +// ASE_DecodedImage ASE_DecodeImage(Arena *arena, String encoded) +// { +// TempArena scratch = BeginScratch(arena); +// ASE_DecodedImage result = Zi; + +// BB_Buff bb = BB_BuffFromString(encoded); +// BB_Reader bbr = BB_ReaderFromBuffNoDebug(&bb); +// ASE_Header ase_header; +// BB_ReadBytes(&bbr, StringFromStruct(&ase_header)); + +// if (ase_header.magic != 0xA5E0) +// { +// ASE_PushError(arena, &result.errors, Lit("Not a valid aseprite file")); +// goto abort; +// } + +// if (ase_header.color_depth != 32) +// { +// String msg = StringF( +// scratch.arena, +// "Only 32 bit rgba color mode is supported (got %F)", +// FmtUint(ase_header.color_depth) +// ); +// ASE_PushError(arena, &result.errors, msg); +// goto abort; +// } + +// u64 frame_width = ase_header.width; +// u64 frame_height = ase_header.height; + +// u32 frames_x = ase_header.frames; +// u32 frames_y = 1; +// u64 image_width = frame_width * frames_x; +// u64 image_height = frame_height * frames_y; +// ASE_MakeDimensionsSquareish(&ase_header, &frames_x, &frames_y, &image_width, &image_height); + +// result.width = image_width; +// result.height = image_height; +// // TODO: Optimize this. Naive memzero is bloating the decode time for large images. +// result.pixels = PushStructs(arena, u32, image_width * image_height); + +// u32 layers_count = 0; +// ASE_Layer *layer_head = 0; + +// ASE_Cel *cel_head = 0; +// ASE_Cel *cel_tail = 0; + +// ////////////////////////////// +// //- Iterate frames + +// u32 frames_count = 0; +// for (u16 i = 0; i < ase_header.frames; ++i) +// { +// ASE_FrameHeader frame_header; +// BB_ReadBytes(&bbr, StringFromStruct(&frame_header)); + +// u32 chunks_count = frame_header.chunks_new; +// if (chunks_count == 0) +// { +// Assert(frame_header.chunks_old != 0xFFFF); +// chunks_count = frame_header.chunks_old; +// } + +// ////////////////////////////// +// //- Iterate chunks + +// for (u32 j = 0; j < chunks_count; ++j) +// { +// u32 chunk_size = BB_ReadUBits(&bbr, 32); +// ASE_ChunkKind chunk_type = BB_ReadUBits(&bbr, 16); + +// // Chunk size includes size & type +// Assert(chunk_size >= 6); +// chunk_size -= 6; + +// u64 chunk_end_pos = BB_GetCurrentReaderByte(&bbr) + chunk_size; + +// switch (chunk_type) +// { +// default: +// { +// BB_ReadSeekToByte(&bbr, chunk_end_pos); +// } break; + +// ////////////////////////////// +// //- Decode layer + +// case ASE_ChunkKind_Layer: +// { +// ASE_Layer *layer = PushStruct(scratch.arena, ASE_Layer); +// layer->next = layer_head; +// layer_head = layer; + +// layer->flags = BB_ReadUBits(&bbr, 16); +// layer->type = BB_ReadUBits(&bbr, 16); +// layer->child_level = BB_ReadUBits(&bbr, 16); + +// // Ignoring layer default width & height +// BB_ReadSeekBytes(&bbr, sizeof(u16) * 2); + +// layer->blend_mode = BB_ReadUBits(&bbr, 16); +// if (layer->blend_mode != 0) +// { +// ASE_PushError( +// arena, +// &result.errors, +// Lit("Layer has unsupported blend mode (only 'Normal' mode is supported). Tip: Try using 'merge down' to create a normal layer as a workaround") +// ); +// goto abort; +// } + +// layer->opacity = BB_ReadUBits(&bbr, 8); +// if (!(ase_header.flags & 1)) +// { +// layer->opacity = 255; +// } + +// BB_ReadSeekBytes(&bbr, sizeof(u8) * 3); + +// u16 str_len = BB_ReadUBits(&bbr, 16); +// layer->name = (String) { str_len, PushStructsNoZero(scratch.arena, u8, str_len) }; +// BB_ReadBytes(&bbr, layer->name); + +// if (layer->type == 2) +// { +// layer->tileset_index = BB_ReadUBits(&bbr, 32); +// } + +// layer->index = layers_count++; +// } break; + +// ////////////////////////////// +// //- Decode cel + +// case ASE_ChunkKind_Cel: +// { +// ASE_Cel *cel = PushStruct(scratch.arena, ASE_Cel); +// if (cel_tail) +// { +// cel_tail->next = cel; +// } +// else +// { +// cel_head = cel; +// } +// cel_tail = cel; + +// cel->layer_index = BB_ReadUBits(&bbr, 16); +// cel->x_pos = BB_ReadIBits(&bbr, 16); +// cel->y_pos = BB_ReadIBits(&bbr, 16); +// cel->opacity = BB_ReadUBits(&bbr, 8); +// cel->type = BB_ReadUBits(&bbr, 16); +// cel->z_index = BB_ReadIBits(&bbr, 16); +// BB_ReadSeekBytes(&bbr, sizeof(u8) * 5); + +// cel->frame_index = frames_count; + +// switch (cel->type) +// { +// case ASE_CelKind_RawImage: +// { +// // Unsupported +// BB_ReadSeekToByte(&bbr, chunk_end_pos); +// } break; + +// case ASE_CelKind_Linked: +// { +// cel->frame_pos = BB_ReadUBits(&bbr, 16); +// // Actual linking happens later after iteration +// } break; + +// case ASE_CelKind_CompressedImage: +// { +// cel->width = BB_ReadUBits(&bbr, 16); +// cel->height = BB_ReadUBits(&bbr, 16); + +// cel->pixels = PushStructsNoZero(scratch.arena, u32, cel->width * cel->height); +// u8 *huffman_encoded = BB_ReadBytesRaw(&bbr, chunk_end_pos - BB_GetCurrentReaderByte(&bbr)); +// if (huffman_encoded) +// { +// ASE_Inflate((u8 *)cel->pixels, huffman_encoded); +// } +// } break; + +// case ASE_CelKind_CompressedTilemap: +// { +// // Unsupported +// ASE_PushError(arena, &result.errors, Lit("Tilemaps are not supported")); +// goto abort; +// } break; +// } +// } break; +// } +// } +// ++frames_count; +// } + +// ////////////////////////////// +// //- Create ordered layers array + +// ASE_Layer **layers_ordered = PushStructsNoZero(scratch.arena, ASE_Layer *, layers_count); +// for (ASE_Layer *layer = layer_head; layer; layer = layer->next) +// { +// layers_ordered[layer->index] = layer; +// } + +// ////////////////////////////// +// //- Link cels + +// ASE_Cel **cels_ordered = PushStructsNoZero(scratch.arena, ASE_Cel *, frames_count * layers_count); +// for (ASE_Cel *cel = cel_head; cel; cel = cel->next) +// { +// cels_ordered[(cel->frame_index * layers_count) + cel->layer_index] = cel; +// if (cel->type == ASE_CelKind_Linked) +// { +// ASE_Cel *ref_cel = cels_ordered[(cel->frame_pos * layers_count) + cel->layer_index]; +// cel->width = ref_cel->width; +// cel->height = ref_cel->height; +// cel->pixels = ref_cel->pixels; +// } +// } + +// ////////////////////////////// +// //- Assemble image from cels + +// { +// for (ASE_Cel *cel = cel_head; cel; cel = cel->next) +// { +// ASE_Layer *layer = layers_ordered[cel->layer_index]; +// // Only draw visible layers +// if (layer->flags & 1) +// { +// u8 opacity = (cel->opacity / 255.0f) * (layer->opacity / 255.0f) * 255.0f; + +// i32 cel_width = cel->width; +// i32 cel_height = cel->height; + +// i32 cel_left = 0; +// i32 cel_right = cel_width; +// i32 cel_top = 0; +// i32 cel_bottom = cel_height; + +// i32 frame_left = cel->x_pos; +// i32 frame_top = cel->y_pos; + +// i32 image_left = frame_left + ((cel->frame_index % frames_x) * frame_width); +// i32 image_top = frame_top + ((cel->frame_index / frames_x) * frame_height); + +// // Adjust bounds to ensure pixels outside of frame boundaries +// // aren't processed (aseprite keeps chunks outside of frame +// // around in project file). +// { +// i32 frame_right = cel_width + frame_left; +// i32 frame_bottom = frame_top + cel_height; +// if (frame_left < 0) +// { +// cel_left += -frame_left; +// frame_left = 0; +// } +// if (frame_top < 0) +// { +// cel_top += -frame_top; +// frame_top = 0; +// } +// if (frame_right > (i32)frame_width) +// { +// cel_right -= (frame_right - frame_width); +// frame_right = frame_width; +// } +// if (frame_bottom > (i32)frame_height) +// { +// cel_bottom -= (frame_bottom - frame_height); +// frame_bottom = frame_height; +// } +// } + +// for (i32 cel_y = cel_top; cel_y < cel_bottom; ++cel_y) +// { +// i32 image_y = image_top + cel_y; +// i32 cel_stride = cel_y * cel_width; +// i32 image_stride = image_y * image_width; +// for (i32 cel_x = cel_left; cel_x < cel_right; ++cel_x) +// { +// i32 image_x = image_left + cel_x; +// u32 cel_pixel = cel->pixels[cel_x + cel_stride]; +// u32 *image_pixel = &result.pixels[image_x + image_stride]; +// *image_pixel = ASE_Blend(cel_pixel, *image_pixel, opacity); +// } +// } +// } +// } +// } + +// // Sanity check to ensure all data was read +// Assert(BB_NumBytesRemaining(&bbr) == 0); + +// abort: + +// if (result.errors.count <= 0) +// { +// result.ok = 1; +// } + +// EndScratch(scratch); +// return result; +// } + +// //////////////////////////////////////////////////////////// +// //~ Decode sheet + +// ASE_DecodedSheet ASE_DecodeSheet(Arena *arena, String encoded) +// { +// ASE_DecodedSheet result = Zi; + +// BB_Buff bb = BB_BuffFromString(encoded); +// BB_Reader bbr = BB_ReaderFromBuffNoDebug(&bb); +// ASE_Header ase_header; +// BB_ReadBytes(&bbr, StringFromStruct(&ase_header)); + +// if (ase_header.magic != 0xA5E0) +// { +// ASE_PushError(arena, &result.errors, Lit("Not a valid aseprite file")); +// goto abort; +// } + +// u64 frame_width = ase_header.width; +// u64 frame_height = ase_header.height; + +// u32 frames_x = ase_header.frames; +// u32 frames_y = 1; +// u64 image_width = frame_width * frames_x; +// u64 image_height = frame_height * frames_y; +// ASE_MakeDimensionsSquareish(&ase_header, &frames_x, &frames_y, &image_width, &image_height); + +// u32 frames_count = 0; +// ASE_Frame *first_frame = 0; + +// u32 spans_count = 0; +// ASE_Span *first_span = 0; + +// u32 slice_keys_count = 0; +// ASE_SliceKey *first_slice_key = 0; + +// ////////////////////////////// +// //- Iterate frames + +// for (u16 i = 0; i < ase_header.frames; ++i) +// { +// ASE_FrameHeader frame_header; +// BB_ReadBytes(&bbr, StringFromStruct(&frame_header)); + +// u32 chunks_count = frame_header.chunks_new; +// if (chunks_count == 0) +// { +// Assert(frame_header.chunks_old != 0xFFFF); +// chunks_count = frame_header.chunks_old; +// } + +// ASE_Frame *frame = PushStruct(arena, ASE_Frame); +// frame->next = first_frame; +// first_frame = frame; + +// frame->index = i; +// frame->duration = frame_header.frame_duration_ms / 1000.0; + +// u32 frame_tile_x = i % frames_x; +// u32 frame_tile_y = i / frames_x; + +// Vec2I32 frame_p0 = VEC2I32(frame_tile_x * frame_width, frame_tile_y * frame_height); +// Vec2I32 frame_p1 = AddVec2I32(frame_p0, VEC2I32(frame_width, frame_height)); +// frame->rect.p0 = frame_p0; +// frame->rect.p1 = frame_p1; + +// frame->index = i; +// frame->duration = frame_header.frame_duration_ms / 1000.0; + +// ////////////////////////////// +// //- Iterate chunks + +// for (u32 j = 0; j < chunks_count; ++j) +// { +// u32 chunk_size = BB_ReadUBits(&bbr, 32); +// ASE_ChunkKind chunk_type = BB_ReadUBits(&bbr, 16); + +// // Chunk size includes size & type +// Assert(chunk_size >= 6); +// chunk_size -= 6; + +// u64 chunk_end_pos = BB_GetCurrentReaderByte(&bbr) + chunk_size; + +// switch (chunk_type) +// { +// default: +// { +// BB_ReadSeekToByte(&bbr, chunk_end_pos); +// } break; + +// //- Decode tags +// case ASE_ChunkKind_Tags: +// { +// u16 frame_span_count = BB_ReadUBits(&bbr, 16); +// BB_ReadSeekBytes(&bbr, 8); + +// for (u16 k = 0; k < frame_span_count; ++k) +// { +// ASE_Span *span = PushStruct(arena, ASE_Span); +// span->next = first_span; +// first_span = span; + +// span->start = BB_ReadUBits(&bbr, 16); +// span->end = BB_ReadUBits(&bbr, 16); +// BB_ReadSeekBytes(&bbr, 13); + +// u16 str_len = BB_ReadUBits(&bbr, 16); +// span->name = (String) { str_len, PushStructsNoZero(arena, u8, str_len) }; +// BB_ReadBytes(&bbr, span->name); + +// ++spans_count; +// } + +// } break; + +// ////////////////////////////// +// //- Decode slice + +// case ASE_ChunkKind_Slice: +// { +// ASE_SliceKey *slice_key = PushStruct(arena, ASE_SliceKey); +// slice_key->next = first_slice_key; +// first_slice_key = slice_key; + +// u32 slices_count = BB_ReadUBits(&bbr, 32); +// slice_key->slices_count = slices_count; + +// u32 flags = BB_ReadUBits(&bbr, 32); +// BB_ReadSeekBytes(&bbr, 4); + +// u16 str_len = BB_ReadUBits(&bbr, 16); +// slice_key->name = (String) { str_len, PushStructsNoZero(arena, u8, str_len) }; +// BB_ReadBytes(&bbr, slice_key->name); + +// for (u32 k = 0; k < slices_count; ++k) +// { +// ASE_Slice *slice = PushStruct(arena, ASE_Slice); +// slice->next = slice_key->first_slice; +// slice_key->first_slice = slice; + +// u32 start = BB_ReadUBits(&bbr, 32); +// i32 x = BB_ReadIBits(&bbr, 32); +// i32 y = BB_ReadIBits(&bbr, 32); +// u32 width = BB_ReadUBits(&bbr, 32); +// u32 height = BB_ReadUBits(&bbr, 32); +// if (flags & 0x01) +// { +// // Skip 9-patches info +// BB_ReadSeekBytes(&bbr, 128); +// } +// if (flags & 0x02) +// { +// // Skip pivot info +// BB_ReadSeekBytes(&bbr, 64); +// } + +// slice->start = start; +// slice->rect.p0 = VEC2I32(x, y); +// slice->rect.p1 = VEC2I32(x + width, y + width); +// } + +// ++slice_keys_count; +// } break; + +// // TODO +// //case ASE_ChunkKind_Userdata +// } +// } +// ++frames_count; +// } + +// // Assert all data was read +// Assert(BB_NumBytesRemaining(&bbr) == 0); + +// result.image_size = VEC2(image_width, image_height); +// result.frame_size = VEC2(frame_width, frame_height); +// result.frames_count = frames_count; +// result.spans_count = spans_count; +// result.slice_keys_count = slice_keys_count; +// result.first_frame = first_frame; +// result.first_span = first_span; +// result.first_slice_key = first_slice_key; + +// abort: + +// if (result.errors.count <= 0) +// { +// result.ok = 1; +// } + +// return result; +// } diff --git a/src/ase/ase.h b/src/ase/ase.h index a61211ab..35e9c9e9 100644 --- a/src/ase/ase.h +++ b/src/ase/ase.h @@ -1,241 +1,328 @@ //////////////////////////////////////////////////////////// -//~ Sheet types +//~ Meta types -Struct(ASE_Slice) +Struct (ASE_Cel) { - u32 start; - Rng2I32 rect; - ASE_Slice *next; -}; - -Struct(ASE_Span) -{ - String name; - u32 start; - u32 end; - ASE_Span *next; -}; - -Struct(ASE_Frame) -{ - u32 index; - Rng2I32 rect; - f64 duration; - ASE_Frame *next; -}; - -Struct(ASE_SliceKey) -{ - String name; - u32 slices_count; - ASE_Slice *first_slice; - ASE_SliceKey *next; -}; - -//////////////////////////////////////////////////////////// -//~ Decoder result types - -Struct(ASE_Error) -{ - String msg; - ASE_Error *next; -}; - -Struct(ASE_ErrorList) -{ - u64 count; - ASE_Error *first; - ASE_Error *last; -}; - -Struct(ASE_DecodedImage) -{ - u32 width; - u32 height; - u32 *pixels; // Array of [width * height] pixels - ASE_ErrorList errors; - b32 ok; -}; - -Struct(ASE_DecodedSheet) -{ - Vec2 image_size; - Vec2 frame_size; - u32 frames_count; - u32 spans_count; - u32 slice_keys_count; - ASE_Frame *first_frame; - ASE_Span *first_span; - ASE_SliceKey *first_slice_key; - ASE_ErrorList errors; - b32 ok; -}; - -//////////////////////////////////////////////////////////// -//~ Inflator types - -#define ASE_HuffBitCount 16 - -Struct(ASE_Bitbuff) -{ - u8 *data; - u64 cur_bit; -}; - -Enum(ASE_BlockType) -{ - ASE_BlockType_Uncompressed = 0, - ASE_BlockType_CompressedFixed = 1, - ASE_BlockType_CompressedDynamic = 2, - ASE_BlockType_Reserved = 3 -}; - -Struct(ASE_HuffEntry) -{ - u16 symbol; - u16 bits_used; -}; - -Struct(ASE_HuffDict) -{ - u32 max_code_bits; - u32 entries_count; - ASE_HuffEntry *entries; -}; - -//////////////////////////////////////////////////////////// -//~ Header types - -Packed(Struct(ASE_Header) -{ - u32 file_size; - u16 magic; - u16 frames; - u16 width; - u16 height; - u16 color_depth; - u32 flags; - u16 speed; - u32 _1; - u32 _2; - u8 palette_entry; - u8 _3[3]; - u16 colors_count; - u8 pixel_width; - u8 pixel_height; - i16 grid_x; - i16 grid_y; - u16 grid_width; - u16 grid_height; - u8 _4[84]; -}); - -Packed(Struct(ASE_FrameHeader) -{ - u32 bytes; - u16 magic; - u16 chunks_old; - u16 frame_duration_ms; - u8 _[2]; - u32 chunks_new; -}); - -//////////////////////////////////////////////////////////// -//~ Image decoder types - -Enum(ASE_ChunkKind) -{ - ASE_ChunkKind_OldPalette1 = 0x0004, - ASE_ChunkKind_OldPalette2 = 0x0011, - ASE_ChunkKind_Layer = 0x2004, - ASE_ChunkKind_Cel = 0x2005, - ASE_ChunkKind_CelExtra = 0x2006, - ASE_ChunkKind_ColorProfile = 0x2007, - ASE_ChunkKind_ExternalFiles = 0x2008, - ASE_ChunkKind_Mask = 0x2016, - ASE_ChunkKind_Path = 0x2017, - ASE_ChunkKind_Tags = 0x2018, - ASE_ChunkKind_Palette = 0x2019, - ASE_ChunkKind_Userdata = 0x2020, - ASE_ChunkKind_Slice = 0x2022, - ASE_ChunkKind_Tileset = 0x2023 -}; - -Enum(ASE_CelKind) -{ - ASE_CelKind_RawImage = 0, - ASE_CelKind_Linked = 1, - ASE_CelKind_CompressedImage = 2, - ASE_CelKind_CompressedTilemap = 3 + i32 _; }; Struct(ASE_Layer) { - u16 flags; - u16 type; - u16 child_level; - u16 blend_mode; - u8 opacity; - String name; - u32 tileset_index; - - u32 index; ASE_Layer *next; + ASE_Layer *prev; + + String name; + i64 cels_count; + ASE_Cel *cels; }; -Struct(ASE_Cel) +Struct(ASE_Span) { - u16 layer_index; - i16 x_pos; - i16 y_pos; - u8 opacity; - ASE_CelKind type; - i16 z_index; + ASE_Span *next; + ASE_Span *prev; - // Linked cel - u16 frame_pos; + String name; + i64 start; + i64 end; +}; - // Compressed image - u32 width; - u32 height; - u32 *pixels; +Struct(ASE_Meta) +{ + i64 frames_count; - u16 frame_index; - ASE_Cel *next; + i64 spans_count; + ASE_Span *first_span; + ASE_Span *last_span; + + i64 layers_count; + ASE_Layer *first_layer; + ASE_Layer *last_layer; }; //////////////////////////////////////////////////////////// -//~ Ase bitbuff helpers +//~ Query types -u32 ASE_PeekBits(ASE_Bitbuff *bb, u32 nbits); -u32 ASE_ConsumeBits(ASE_Bitbuff *bb, u32 nbits); -void ASE_SkipBits(ASE_Bitbuff *bb, u32 nbits); +Struct(ASE_SinglePixelResult) +{ + Vec2 pos; + u32 v; +}; //////////////////////////////////////////////////////////// -//~ Inflate +//~ Meta -u32 ASE_ReverseBits(u32 v, u32 bit_count); -ASE_HuffDict ASE_InitHuffDict(Arena *arena, u32 max_code_bits, u32 *bl_counts, u32 bl_counts_count); -u16 ASE_DecodeHuffDict(ASE_HuffDict *huffman, ASE_Bitbuff *bb); -void ASE_Inflate(u8 *dst, u8 *encoded); +ASE_Meta ASE_DecodeMeta(Arena *arena, String encoded); //////////////////////////////////////////////////////////// -//~ Error helpers +//~ Query -void ASE_PushError(Arena *arena, ASE_ErrorList *list, String msg_src); +ASE_SinglePixelResult ASE_SinglePixelFromCel(ASE_Cel *cel); //////////////////////////////////////////////////////////// -//~ Decode helpers +//~ Rasterize -u32 ASE_Blend(u32 src, u32 dst, u8 opacity); -void ASE_MakeDimensionsSquareish(ASE_Header *header, u32 *frames_x, u32 *frames_y, u64 *image_width, u64 *image_height); +void ASE_RasterizeCel(ASE_Cel *cel, u32 *dst_pixels, Vec2 dst_dims); -//////////////////////////////////////////////////////////// -//~ Decode image -ASE_DecodedImage ASE_DecodeImage(Arena *arena, String encoded); -//////////////////////////////////////////////////////////// -//~ Decode sheet -ASE_DecodedSheet ASE_DecodeSheet(Arena *arena, String encoded); + + + + + + + + + + + + + + + + + + + +// //////////////////////////////////////////////////////////// +// //~ Sheet types + +// Struct(ASE_Slice) +// { +// u32 start; +// Rng2I32 rect; +// ASE_Slice *next; +// }; + +// Struct(ASE_Span) +// { +// String name; +// u32 start; +// u32 end; +// ASE_Span *next; +// }; + +// Struct(ASE_Frame) +// { +// u32 index; +// Rng2I32 rect; +// f64 duration; +// ASE_Frame *next; +// }; + +// Struct(ASE_SliceKey) +// { +// String name; +// u32 slices_count; +// ASE_Slice *first_slice; +// ASE_SliceKey *next; +// }; + +// //////////////////////////////////////////////////////////// +// //~ Decoder result types + +// Struct(ASE_Error) +// { +// String msg; +// ASE_Error *next; +// }; + +// Struct(ASE_ErrorList) +// { +// u64 count; +// ASE_Error *first; +// ASE_Error *last; +// }; + +// Struct(ASE_DecodedImage) +// { +// u32 width; +// u32 height; +// u32 *pixels; // Array of [width * height] pixels +// ASE_ErrorList errors; +// b32 ok; +// }; + +// Struct(ASE_DecodedSheet) +// { +// Vec2 image_size; +// Vec2 frame_size; +// u32 frames_count; +// u32 spans_count; +// u32 slice_keys_count; +// ASE_Frame *first_frame; +// ASE_Span *first_span; +// ASE_SliceKey *first_slice_key; +// ASE_ErrorList errors; +// b32 ok; +// }; + +// //////////////////////////////////////////////////////////// +// //~ Inflator types + +// #define ASE_HuffBitCount 16 + +// Struct(ASE_Bitbuff) +// { +// u8 *data; +// u64 cur_bit; +// }; + +// Enum(ASE_BlockType) +// { +// ASE_BlockType_Uncompressed = 0, +// ASE_BlockType_CompressedFixed = 1, +// ASE_BlockType_CompressedDynamic = 2, +// ASE_BlockType_Reserved = 3 +// }; + +// Struct(ASE_HuffEntry) +// { +// u16 symbol; +// u16 bits_used; +// }; + +// Struct(ASE_HuffDict) +// { +// u32 max_code_bits; +// u32 entries_count; +// ASE_HuffEntry *entries; +// }; + +// //////////////////////////////////////////////////////////// +// //~ Header types + +// Packed(Struct(ASE_Header) +// { +// u32 file_size; +// u16 magic; +// u16 frames; +// u16 width; +// u16 height; +// u16 color_depth; +// u32 flags; +// u16 speed; +// u32 _1; +// u32 _2; +// u8 palette_entry; +// u8 _3[3]; +// u16 colors_count; +// u8 pixel_width; +// u8 pixel_height; +// i16 grid_x; +// i16 grid_y; +// u16 grid_width; +// u16 grid_height; +// u8 _4[84]; +// }); + +// Packed(Struct(ASE_FrameHeader) +// { +// u32 bytes; +// u16 magic; +// u16 chunks_old; +// u16 frame_duration_ms; +// u8 _[2]; +// u32 chunks_new; +// }); + +// //////////////////////////////////////////////////////////// +// //~ Image decoder types + +// Enum(ASE_ChunkKind) +// { +// ASE_ChunkKind_OldPalette1 = 0x0004, +// ASE_ChunkKind_OldPalette2 = 0x0011, +// ASE_ChunkKind_Layer = 0x2004, +// ASE_ChunkKind_Cel = 0x2005, +// ASE_ChunkKind_CelExtra = 0x2006, +// ASE_ChunkKind_ColorProfile = 0x2007, +// ASE_ChunkKind_ExternalFiles = 0x2008, +// ASE_ChunkKind_Mask = 0x2016, +// ASE_ChunkKind_Path = 0x2017, +// ASE_ChunkKind_Tags = 0x2018, +// ASE_ChunkKind_Palette = 0x2019, +// ASE_ChunkKind_Userdata = 0x2020, +// ASE_ChunkKind_Slice = 0x2022, +// ASE_ChunkKind_Tileset = 0x2023 +// }; + +// Enum(ASE_CelKind) +// { +// ASE_CelKind_RawImage = 0, +// ASE_CelKind_Linked = 1, +// ASE_CelKind_CompressedImage = 2, +// ASE_CelKind_CompressedTilemap = 3 +// }; + +// Struct(ASE_Layer) +// { +// u16 flags; +// u16 type; +// u16 child_level; +// u16 blend_mode; +// u8 opacity; +// String name; +// u32 tileset_index; + +// u32 index; +// ASE_Layer *next; +// }; + +// Struct(ASE_Cel) +// { +// u16 layer_index; +// i16 x_pos; +// i16 y_pos; +// u8 opacity; +// ASE_CelKind type; +// i16 z_index; + +// // Linked cel +// u16 frame_pos; + +// // Compressed image +// u32 width; +// u32 height; +// u32 *pixels; + +// u16 frame_index; +// ASE_Cel *next; +// }; + +// //////////////////////////////////////////////////////////// +// //~ Ase bitbuff helpers + +// u32 ASE_PeekBits(ASE_Bitbuff *bb, u32 nbits); +// u32 ASE_ConsumeBits(ASE_Bitbuff *bb, u32 nbits); +// void ASE_SkipBits(ASE_Bitbuff *bb, u32 nbits); + +// //////////////////////////////////////////////////////////// +// //~ Inflate + +// u32 ASE_ReverseBits(u32 v, u32 bit_count); +// ASE_HuffDict ASE_InitHuffDict(Arena *arena, u32 max_code_bits, u32 *bl_counts, u32 bl_counts_count); +// u16 ASE_DecodeHuffDict(ASE_HuffDict *huffman, ASE_Bitbuff *bb); +// void ASE_Inflate(u8 *dst, u8 *encoded); + +// //////////////////////////////////////////////////////////// +// //~ Error helpers + +// void ASE_PushError(Arena *arena, ASE_ErrorList *list, String msg_src); + +// //////////////////////////////////////////////////////////// +// //~ Decode helpers + +// u32 ASE_Blend(u32 src, u32 dst, u8 opacity); +// void ASE_MakeDimensionsSquareish(ASE_Header *header, u32 *frames_x, u32 *frames_y, u64 *image_width, u64 *image_height); + +// //////////////////////////////////////////////////////////// +// //~ Decode image + +// ASE_DecodedImage ASE_DecodeImage(Arena *arena, String encoded); + +// //////////////////////////////////////////////////////////// +// //~ Decode sheet + +// ASE_DecodedSheet ASE_DecodeSheet(Arena *arena, String encoded); diff --git a/src/pp/pp_res/sprite/bla3.ase b/src/pp/pp_res/sprite/bla3.ase index da66208e..b2b983dd 100644 --- a/src/pp/pp_res/sprite/bla3.ase +++ b/src/pp/pp_res/sprite/bla3.ase @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6fbdcd64e7ac8230452fe6438fb84944c88533a8384f8108c873dc175b1ea7c3 +oid sha256:cf51d5fb9a66ee01c0edcecf71b59285ad09c7bffdfe6d9cc1636e78e3afbac4 size 3059 diff --git a/src/sprite/sprite.c b/src/sprite/sprite.c index cc40d243..d18e9480 100644 --- a/src/sprite/sprite.c +++ b/src/sprite/sprite.c @@ -9,7 +9,7 @@ void SPR_Bootstrap(void) } //////////////////////////////////////////////////////////// -//~ Key helpers +//~ Helpers SPR_SheetKey SPR_SheetKeyFromResource(ResourceKey resource) { @@ -25,228 +25,255 @@ SPR_SpanKey SPR_SpanKeyFromName(String name) return result; } -SPR_MarkKey SPR_MarkKeyFromName(String name) +String SPR_NameFromRayKind(SPR_RayKind kind) { - SPR_MarkKey result = Zi; - result.v = HashString(name); + PERSIST Readonly String names[SPR_RayKind_COUNT] = { + #define X(kind_name, layer_name, ...) [SPR_RayKind_##kind_name] = CompLit(#layer_name), + SPR_RayKindXMacro(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_SheetEntry *SPR_SheetFromKey(SPR_SheetKey key) -{ - SPR_SheetEntry *result = 0; - return result; -} - SPR_Slice SPR_SliceFromSheet(SPR_SheetKey sheet_key, SPR_SpanKey span_key, i64 frame_seq) { - SPR_Slice result = Zi; + ////////////////////////////// + //- Fetch sheet - // FIXME: Default nil result - - // i64 completion = G_CompletionValueFromQueue(G_QueueKind_AsyncCopy); - // if (completion < Atomic64Fetch(&sheet->atlas_copy_completion_target)) - - SPR_SpanEntry *span = SPR_SpanEntryFromSheet(sheet_key, span_key); - if (span && span->slices_count > 0) + SPR_SheetEntry *sheet = 0; { - SPR_SpanStatus status = Atomic32Fetch(&span->status); - if (status >= SPR_SpanStatus_Rasterized) + SPR_SheetBin *sheet_bin = &SPR.sheet_bins[sheet_key.r.v % countof(SPR.sheet_bins)]; + Lock sheet_bin_lock = LockS(&sheet_bin->mutex); { - i64 frame_idx = frame_seq % span->slices_count; - SPR_SliceEntry *slice_entry = &span->slices[frame_idx]; - result.tex = slice_entry->atlas->tex; - result.tex_rect_uv = slice_entry->atlas_rect_uv; - result.dims = slice_entry->dims; - result.ready = 1; - } - result.exists = 1; - } - - - // SPR_SheetEntry *sheet = SPR_SheetFromKey(sheet_key, 1); - // { - // SPR_SpanEntry *span = 0; - // { - // SPR_SpanBin *span_bin = &sheet->span_bins[span_key.v % sheet->span_bins_count]; - // span = span_bin->first; - // for (; span; span = span->next_in_bin) - // { - // if (span->key.v == span_key.v) - // { - // break; - // } - // } - // } - // if (span && span->slices_count > 0) - // { - // i64 completion = G_CompletionValueFromQueue(G_QueueKind_AsyncCopy); - // if (completion < Atomic64Fetch(&sheet->atlas_copy_completion_target)) - // { - // i64 frame_idx = frame_seq % span->slices_count; - // SPR_SliceEntry *slice_entry = &span->slices[frame_idx]; - // result.tex = slice_entry->atlas->tex; - // result.tex_rect_uv = slice_entry->atlas_rect_uv; - // result.dims = slice_entry->dims; - // result.ready = 1; - // } - // result.exists = 1; - // } - // } - - return result; -} - -SPR_Mark SPR_MarkFromSheet(SPR_SheetKey sheet_key, SPR_SpanKey span_key, SPR_MarkKey mark_key, i64 frame_seq) -{ - SPR_Mark result = Zi; - - // SPR_SheetEntry *sheet = SPR_SheetFromKey(sheet_key, 0); - - SPR_SpanEntry *span = SPR_SpanEntryFromSheet(sheet_key, span_key); - if (span && span->slices_count > 0) - { - SPR_SpanStatus status = Atomic32Fetch(&span->status); - if (status >= SPR_SpanStatus_Processed) - { - i64 frame_idx = frame_seq % span->slices_count; - - SPR_MarkEntry *mark_entry = 0 + for (sheet = sheet_bin->first; sheet; sheet = sheet->next_in_bin) { - SPR_MarkBin *mark_bin = &span->mark_bins[mark_key.v % span->mark_bins_count]; - mark_entry = mark_bin->first; - for (; mark_entry; mark_entry = mark_entry->next_in_bin) + if (sheet->key.r.v == sheet_key.r.v) { - if (mark_entry->key.v == mark_key.v) - { - break; - } + break; } } - if (mark_entry) + } + 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]; + for (SPR_RayKind ray_kind = 0; ray_kind < SPR_RayKind_COUNT; ++ray_kind) + { + slice->rays[ray_kind] = XformIdentity; + } + } + + //- Push async rasterization commands + if (sheet->meta.frames_count > 0) + { + Lock cmds_lock = LockE(&SPR.submit.mutex); + { + for (i64 slice_idx = 0; slice_idx < sheet->meta.frames_count; ++slice_idx) + { + 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); + ++SPR.submit.count; + } + Atomic32FetchSet(&SPR.new_cmds_present, 1); + SignalAsyncTick(); + } + Unlock(&cmds_lock); + } + + //- Compute rays + 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_SinglePixelResult ray_pix = ASE_SinglePixelFromCel(ase_cel); + u32 alpha = (ray_pix.v >> 24) & 0xFF; + if (alpha > 0) + { + // TODO: Different quantization so that 128 equals 0, instead of approximately 0 + f32 rot_x = (((f32)((ray_pix.v >> 0) & 0xFF) / 255.0) * 2.0) - 1; + f32 rot_y = (((f32)((ray_pix.v >> 8) & 0xFF) / 255.0) * 2.0) - 1; + Vec2 rot = NormVec2(VEC2(rot_x, rot_y)); + slice->rays[ray_kind].r = rot; + slice->rays[ray_kind].t = ray_pix.pos; + } + } + } + } + } + + //- Init spans + sheet->span_bins_count = 256; + 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->start = ase_span->start; + span->end = ase_span->end; + } + } + + // TODO: Proper validation + sheet->ok = 1; + } + } + Unlock(&sheet_bin_lock); + } + + ////////////////////////////// + //- Fetch span + + SPR_SpanEntry *span = 0; + b32 span_matched = 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) + { + // FIXME: Ensure first span always exists in sheet + span = sheet->first_span; + } + } + + ////////////////////////////// + //- 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_Slice result = Zi; + { + result.exists = span_matched; + if (result.exists) + { + i64 completion = G_CompletionValueFromQueue(G_QueueKind_AsyncCopy); + if (completion >= Atomic64Fetch(&slice->atlas_copy_completion_target)) { - SPR_SliceEntry *slice_entry = &span->slices[frame_idx]; - result.tex = slice_entry->atlas->tex; - result.tex_rect_uv = slice_entry->atlas_rect_uv; - result.dims = slice_entry->dims; result.ready = 1; } - } - result.exists = 1; + // Fill tex info + if (result.ready) + { + result.tex = slice->atlas->tex_ref; + result.tex_rect_uv = slice->atlas_rect_uv; + result.dims = slice->dims; + } + else + { + result.tex = SPR.unready_tex; + result.tex_rect_uv = RNG2(VEC2(0, 0), VEC2(1, 1)); + result.dims = SPR.unready_tex_dims; + } + // Fill rays + StaticAssert(countof(result.rays) == countof(slice->rays)); + CopyStructs(result.rays, slice->rays, countof(result.rays)); } return result; } - - - - -// SPR_SliceEntry SPR_SliceFromSheet(SPR_SheetKey sheet, SPR_SpanKey span, i64 frame_seq) -// { -// // TODO: Ability to specify desired alpha modes (Straight, Premultiplied, Opaque) -// SPR_SliceEntry 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->atlas_copy_completion_target)) -// { -// result = entry->slice_entry; -// } -// 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->atlas_copy_completion_target)) -// { -// result = entry->slice_entry; -// } -// found = 1; -// } -// else -// { -// Arena *perm = PermArena(); - -// entry = PushStruct(perm, SPR_SliceEntry); -// entry->hash = hash; -// Atomic64FetchSet(&entry->atlas_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 @@ -291,306 +318,109 @@ void SPR_TickAsync(WaveLaneCtx *lane, AsyncFrameLaneCtx *base_async_lane_frame) { SPR_Cmd *cmd = &cmds[cmd_idx]; SPR_SheetEntry *sheet = cmd->sheet; - ASE_Meta *meta = &sheet->meta; + 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)); + // 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)); - for (ASE_Span *ase_span = meta.first_span; ase_span; ase_span = ase_span->next) + SPR_SliceEntry *slice = &sheet->slices[cmd->slice_idx]; + + ////////////////////////////// + //- Rasterize + + u32 *pixels = PushStructs(frame_arena, u32, slice->dims.x * slice->dims.y); + for (ASE_Layer *ase_layer = sheet->meta.last_layer; ase_layer; ase_layer = ase_layer->prev) { - u64 span_hash = HashString(ase_span->name); - SPR_SpanBin *span_bin = &sheet->span_bins[span_hash % sheet->span_bins_count]; - SPR_SpanEntry *span = span_bin->first; - for (; span; span = span->next_in_bin) + SPR_LayerKind kind = SPR_LayerKindFromName(ase_layer->name); + if (kind == SPR_LayerKind_Visual) { - if (span->key.v == span_hash) - { - break; - } - } - if (span) - { - i64 slice_idx = 0; - for (ASE_Frame *ase_frame = span->first_frame; ase_frame && slice_idx < span->slices_count; ase_frame = ase_frame->next) - { - SPR_SliceEntry *slice_entry = &span->slice_entry[slice_idx]; - - // // Determine slice_entry bounds - // Rng2 frame_bounds = Rng2Empty; - // for (ASE_Layer *ase_layer = ase_frame->first_layer; ase_layer; ase_layer = ase_layer->next) - // { - // if (!StringBeginsWith(ase_layer->name, Lit("."))) - // { - // frame_bounds = UnionRng2(frame_bounds); - // } - // } - // Vec2 dims = DimsFromRng2(frame_bounds); - - if (!IsRng2Empty(frame_bounds)) - { - // Rasterize slice_entry - u32 *pixels = PushStructs(frame_arena, u32, slice_entry->dims.x * slice_entry->dims.y); - for (ASE_Layer *ase_layer = ase_frame->first_layer; ase_layer; ase_layer = ase_layer->next) - { - SPR_LayerKind kind = SPR_LayerKindFromName(ase_layer->name); - if (kind == SPR_LayerKind_Standard) - { - ASE_RasterizeLayer(ase_layer, pixels, slice_entry->dims); - } - } - - // Allocate atlas rect - // TODO: Use a more efficient atlas packing algorithm for less wasted space - SPR_Atlas *atlas = SPR.first_atlas; - b32 can_use_atlas = 0; - Vec2I32 pos_in_atlas = Zi; - while (can_use_atlas == 0) - { - // Create atlas - if (!atlas) - { - Arena *perm = PermArena(); - atlas = PushStruct(perm, SPR_Atlas); - i32 atlas_size = MaxI32(1024, NextPow2U64(MaxI32(slice_entry->dims.x, slice_entry->dims.y))); - atlas->dims = VEC2I32(atlas_size, atlas_size); - { - G_ArenaHandle gpu_perm = G_PermArena(); - atlas->tex = G_PushTexture2D( - gpu_perm, cl, - G_Format_R8G8B8A8_Unorm_Srgb, - atlas->dims, - G_Layout_AnyQueue_ShaderRead_CopyRead_CopyWrite_Present, - ); - atlas->tex_ref = G_PushTexture2DRef(gpu_perm, atlas->tex); - } - SllStackPush(SPR.first_atlas, atlas); - ++SPR.atlases_count; - } - // Determine pos in atlas - pos_in_atlas = atlas->cur_pos; - atlas->cur_row_height = MaxI32(atlas->cur_row_height, slice_entry->dims.y); - if (pos_in_atlas.x + slice_entry->dims.x > atlas->dims.x); - { - atlas->cur_pos.x = 0; - atlas->cur_pos.y += atlas->cur_row_height; - atlas->cur_row_height = slice_entry->dims.y; - } - atlas->cur_pos.x += slice_entry->dims.x; - if (atlas->cur_pos.x < atlas->dims.x && atlas->cur_pos.y < atlas->dims.y) - { - can_use_atlas = 1; - } - else - { - atlas = 0; - } - } - - // Fill slice_entry atlas info - slice_entry->atlas = atlas; - slice_entry->atlas_rect = RNG2I32(pos_in_atlas, AddVec2I32(pos_in_atlas, slice_entry->dims)); - slice_entry->atlas_rect_uv.p0.x = (f32)slice_entry->atlas_rect.p0.x / (f32)atlas->dims.x; - slice_entry->atlas_rect_uv.p0.y = (f32)slice_entry->atlas_rect.p0.y / (f32)atlas->dims.x; - slice_entry->atlas_rect_uv.p1.x = (f32)slice_entry->atlas_rect.p1.x / (f32)atlas->dims.x; - slice_entry->atlas_rect_uv.p1.y = (f32)slice_entry->atlas_rect.p1.y / (f32)atlas->dims.x; - - // Copy to atlas - G_CopyCpuToTexture( - cl, - atlas->tex, VEC3I32(pos_in_atlas.x, pos_in_atlas.y, 0), - pixels, VEC3I32(slice_entry->dims.x, slice_entry->dims.y, 1), - RNG3I32( - VEC3I32(0, 0, 0), - VEC3I32(slice_entry->dims.x, slice_entry->dims.y, 1) - ) - ); - } - ++slice_idx; - } + ASE_Cel *cel = &ase_layer->cels[cmd->slice_idx]; + ASE_RasterizeCel(cel, pixels, slice->dims); } } + + ////////////////////////////// + //- 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 pos_in_atlas = 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 = G_PushTexture2D( + gpu_perm, cl, + G_Format_R8G8B8A8_Unorm_Srgb, + atlas->dims, + G_Layout_AnyQueue_ShaderRead_CopyRead_CopyWrite_Present, + ); + atlas->tex_ref = G_PushTexture2DRef(gpu_perm, atlas->tex); + } + SllStackPush(SPR.first_atlas, atlas); + ++SPR.atlases_count; + } + // Determine pos in atlas + pos_in_atlas = atlas->cur_pos; + atlas->cur_row_height = MaxI32(atlas->cur_row_height, slice->dims.y); + if (pos_in_atlas.x + slice->dims.x > atlas->dims.x); + { + atlas->cur_pos.x = 0; + atlas->cur_pos.y += atlas->cur_row_height; + atlas->cur_row_height = slice->dims.y; + } + atlas->cur_pos.x += slice->dims.x; + if (atlas->cur_pos.x < atlas->dims.x && atlas->cur_pos.y < atlas->dims.y) + { + can_use_atlas = 1; + } + else + { + atlas = 0; + } + } + + // Fill slice_entry atlas info + { + Rng2I32 atlas_rect = RNG2I32(pos_in_atlas, AddVec2I32(pos_in_atlas, Vec2I32FromVec(slice->dims))); + slice->atlas = atlas; + slice->atlas_rect_uv.p0.x = (f32)atlas_rect.p0.x / (f32)atlas->dims.x; + slice->atlas_rect_uv.p0.y = (f32)atlas_rect.p0.y / (f32)atlas->dims.x; + slice->atlas_rect_uv.p1.x = (f32)atlas_rect.p1.x / (f32)atlas->dims.x; + slice->atlas_rect_uv.p1.y = (f32)atlas_rect.p1.y / (f32)atlas->dims.x; + } + + // Copy to atlas + G_CopyCpuToTexture( + cl, + atlas->tex, VEC3I32(pos_in_atlas.x, pos_in_atlas.y, 0), + pixels, VEC3I32(slice->dims.x, slice->dims.y, 1), + RNG3I32( + VEC3I32(0, 0, 0), + VEC3I32(slice->dims.x, slice->dims.y, 1) + ) + ); } i64 completion_target = G_CommitCommandList(cl); - // Update completion targets + ////////////////////////////// + //- Update completion targets + for (i64 cmd_idx = 0; cmd_idx < cmds_count; ++cmd_idx) { SPR_Cmd *cmd = &cmds[cmd_idx]; - SPR_SheetEntry *sheet = cmd->sheet; - Atomic64Set(&sheet->atlas_copy_completion_target, completion_target); + SPR_SliceEntry *slice = &cmd->sheet->slices[cmd->slice_idx]; + Atomic64Set(&slice->atlas_copy_completion_target, completion_target); } - - // 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->atlas_copy_completion_target = completion_target; - // sheet->tex = G_PushTexture2DRef(gpu_perm, gpu_resource); - // // LogDebugF("Decoded with ref: %F", FmtUint(slice_entry->slice_entry.tex.v)); - // } - // else - // { - // // TODO: Use 'missing' texture - // sheet->tex = G_BlankTexture2D(); - // sheet->atlas_copy_completion_target = 0; - // } - // } - // slice_entry->slice_entry.tex = sheet->tex; - // // FIXME: Real uv - // slice_entry->slice_entry.uv_rect.p0 = VEC2(0, 0); - // slice_entry->slice_entry.uv_rect.p1= VEC2(1, 1); - // Atomic64Set(&slice_entry->atlas_copy_completion_target, sheet->atlas_copy_completion_target); + } } } } - - - - - - - -// 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->atlas_copy_completion_target = completion_target; -// sheet->tex = G_PushTexture2DRef(gpu_perm, gpu_resource); -// // LogDebugF("Decoded with ref: %F", FmtUint(slice_entry->slice_entry.tex.v)); -// } -// else -// { -// // TODO: Use 'missing' texture -// sheet->tex = G_BlankTexture2D(); -// sheet->atlas_copy_completion_target = 0; -// } -// } -// slice_entry->slice_entry.tex = sheet->tex; -// // FIXME: Real uv -// slice_entry->slice_entry.uv_rect.p0 = VEC2(0, 0); -// slice_entry->slice_entry.uv_rect.p1= VEC2(1, 1); -// Atomic64Set(&slice_entry->atlas_copy_completion_target, sheet->atlas_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); -// } -// } -// } -// } diff --git a/src/sprite/sprite.h b/src/sprite/sprite.h index e250e54d..90c9837f 100644 --- a/src/sprite/sprite.h +++ b/src/sprite/sprite.h @@ -3,56 +3,120 @@ Struct(SPR_SheetKey) { ResourceKey r; }; Struct(SPR_SpanKey) { u64 v; }; -Struct(SPR_MarkKey) { u64 v; }; #define SPR_NilSheetKey ((SPR_SheetKey) { 0 }) #define SPR_NilSpanKey ((SPR_SpanKey) { 0 }) -#define SPR_NilRayKey ((SPR_MarkKey) { 0 }) //////////////////////////////////////////////////////////// -//~ Slice types +//~ Atlas types + +Struct(SPR_Atlas) +{ + SPR_Atlas *next; + + Vec2I32 dims; + G_ResourceHandle tex; + G_Texture2DRef tex_ref; + Vec2I32 cur_pos; + i32 cur_row_height; +}; + +//////////////////////////////////////////////////////////// +//~ Ray types + +#define SPR_RayKindXMacro(X) \ + X(Origin, .origin) \ + X(Ap, .ap) \ +/* ----------------------------- */ + +Enum(SPR_RayKind) +{ + #define X(kind_name, ...) SPR_RayKind_##kind_name, + SPR_RayKindXMacro(X) + #undef X + SPR_RayKind_COUNT +}; + +//////////////////////////////////////////////////////////// +//~ Layer types + +Enum(SPR_LayerKind) +{ + SPR_LayerKind_Visual, + SPR_LayerKind_Hidden, +}; + +//////////////////////////////////////////////////////////// +//~ Lookup result types Struct(SPR_Slice) { + Xform rays[SPR_RayKind_COUNT]; + G_Texture2DRef tex; - Rng2 uv_rect; + Rng2 tex_rect_uv; Vec2 dims; + b32 exists; b32 ready; }; //////////////////////////////////////////////////////////// //~ Cache types +//- Slice + Struct(SPR_SliceEntry) { - SPR_SliceEntry *next; + SPR_SliceEntry *next_in_bin; - SPR_SheetKey sheet; - u64 hash; + Xform rays[SPR_RayKind_COUNT]; + + SPR_Atlas *atlas; + Vec2 dims; + Rng2 atlas_rect_uv; Atomic64 atlas_copy_completion_target; - - String slice_name; - - SPR_Slice slice; }; -Struct(SPR_SliceBin) +//- Span + +Struct(SPR_SpanEntry) { - Mutex mutex; - SPR_SliceEntry *first; + SPR_SpanEntry *next; + SPR_SpanEntry *next_in_bin; + SPR_SpanKey key; + + i64 start; + i64 end; }; +Struct(SPR_SpanBin) +{ + SPR_SpanEntry *first; +}; + +//- Sheet + Struct(SPR_SheetEntry) { - SPR_SheetEntry *next; + SPR_SheetEntry *next_in_bin; SPR_SheetKey key; - i64 atlas_copy_completion_target; - G_Texture2DRef tex; + + i64 slices_count; + SPR_SliceEntry *slices; + + i64 span_bins_count; + SPR_SpanBin *span_bins; + SPR_SpanEntry *first_span; + SPR_SpanEntry *last_span; + + ASE_Meta meta; + b32 ok; }; Struct(SPR_SheetBin) { + Mutex mutex; SPR_SheetEntry *first; }; @@ -61,7 +125,8 @@ Struct(SPR_SheetBin) Struct(SPR_Cmd) { - SPR_SliceEntry *entry; + SPR_SheetEntry *sheet; + i64 slice_idx; }; Struct(SPR_CmdNode) @@ -75,12 +140,19 @@ Struct(SPR_CmdNode) Struct(SPR_AsyncCtx) { - SPR_SheetBin sheet_bins[Kibi(16)]; + i32 _; }; Struct(SPR_Ctx) { - SPR_SliceBin slice_bins[Kibi(16)]; + SPR_SheetBin sheet_bins[Kibi(16)]; + + // FIXME: Initialize this + G_Texture2DRef unready_tex; + Vec2 unready_tex_dims; + + i64 atlases_count; + SPR_Atlas *first_atlas; Atomic32 new_cmds_present; struct @@ -103,18 +175,18 @@ extern SPR_Ctx SPR; void SPR_Bootstrap(void); //////////////////////////////////////////////////////////// -//~ Key helpers +//~ Helpers SPR_SheetKey SPR_SheetKeyFromResource(ResourceKey resource); SPR_SpanKey SPR_SpanKeyFromName(String name); -SPR_MarkKey SPR_MarkKeyFromName(String name); + +String SPR_NameFromRayKind(SPR_RayKind kind); +SPR_LayerKind SPR_LayerKindFromName(String name); //////////////////////////////////////////////////////////// -//~ Lookup helpers +//~ Lookup -SPR_SpanEntry *SPR_SpanEntryFromSheet(SPR_SheetKey sheet_key, SPR_SpanKey span_key); SPR_Slice SPR_SliceFromSheet(SPR_SheetKey sheet_key, SPR_SpanKey span_key, i64 frame_seq); -SPR_Mark SPR_MarkFromSheet(SPR_SheetKey sheet_key, SPR_SpanKey span_key, SPR_MarkKey ray_key, i64 frame_seq); //////////////////////////////////////////////////////////// //~ Async