I'm trying my hand at compressing metasprite data and I would be very interested in your thoughts on it.
I tried finding other threads about this issue but could only find Bregalad's old thread on his/her custom format https://forums.nesdev.com/viewtopic.php?f=2&t=9724 which seemed a bit too complex for me to dare try implementing.
My take is to cut down the data from 4bytes/tile entry to 3 by taking away a big range from the x and y positions and scattering the bits for the tile character in with the other values like this:
X = x position
Y = y position
T = tile character
A = attributes
Uncompressed data:
Compressed data step 1:
And for unpacking this data in my drawing routine, here's a simplified version of that:
Here's an example of compressed metasprite data:
So by doing this I use 75% of the space I used before to store the metasprite data (apart from the extra byte for the offset in the header), but of course I'm adding a lot of new instructions to do the unpacking for each tile.
I've implemented all this in my engine now and have it sort of working apart from some issues I have on existing metasprites with values going outside of the range that my compression allows.
I would be very interested in hearing if my take on this makes sense and if there's anything I can do to speed up the decompression of the tile character. My idea with compressing the data this way is that it will not be too hard to understand the decompression code as well as being compatible with metasprites with a maximum width of 63x63 (actually (63+8) * (63+8), given the width and height of an individual tile).
Sorry if this post is hard to follow. I omitted most of my drawing routine to put the focus on the unpacking. Obviously the real code does a lot more in terms of flipping sprites and offsetting positions before finally storing them..
Cheers!
I tried finding other threads about this issue but could only find Bregalad's old thread on his/her custom format https://forums.nesdev.com/viewtopic.php?f=2&t=9724 which seemed a bit too complex for me to dare try implementing.
My take is to cut down the data from 4bytes/tile entry to 3 by taking away a big range from the x and y positions and scattering the bits for the tile character in with the other values like this:
X = x position
Y = y position
T = tile character
A = attributes
Uncompressed data:
Code:
1: XXXX xxxx
2: YYYY yyyy
3: TTTT tttt
4: AAAA aaaa
2: YYYY yyyy
3: TTTT tttt
4: AAAA aaaa
Compressed data step 1:
Code:
1: **XX xxxx
2: **YY yyyy
3: *TTT tttt
4: AAA* **aa
So what I do is I sacrifice bits I think I can work my way around not having. This means I can only store values 0-63 ($00-$7F) for x and y position. Same goes for tile character but I make sure to put an offset in the header for the metasprite to be able to display tiles beyond that range. As for the attributes, I only take away the unimplemented bits. When that's done I rearrange the data like this:2: **YY yyyy
3: *TTT tttt
4: AAA* **aa
Code:
1: TTXX xxxx
2: TtYY yyyy
3: AAAt ttaa
2: TtYY yyyy
3: AAAt ttaa
And for unpacking this data in my drawing routine, here's a simplified version of that:
Code:
; ZP stuff
OAM_sprite_ptr_lo .db 1
OAM_sprite_ptr_hi .db 1
temp_var_1 .db 1
temp_var_2 .db 1
temp_var_3 .db 1
temp_var_4 .db 1
tile_offset .db 1
; drawing routine
DrawMetaSprite:
ldy #0
lda (OAM_sprite_ptr_lo), y
sta OAM_currSpriteByteSize ; this is a value I use unrelated to the compression.
iny
lda (OAM_sprite_ptr_lo), y
sta tile_offset
dey
ldx #0
@drawing_loop:
@temp_x = temp_var_1
@temp_y = temp_var_2
@temp_tile = temp_var_3
@temp_attr = temp_var_4
lda (OAM_sprite_ptr_lo), y
sta @temp_x ; we'll save this for later to do offsets to the x-position.
and #%11000000 ; shave off all but the tile char bits (bit 6-5).
sta @temp_tile ; store them for later.
iny
lda (OAM_sprite_ptr_lo), y
sta @temp_y ; save y-position for later just like the x-position.
and #%11000000 ; shave off all but the tile char bits (bit 4-3).
lsr
lsr ; shift the bits.
ora @temp_tile ; merge with bits 6-5.
sta @temp_tile
; TTTt 0000
iny
lda (OAM_sprite_ptr_lo), y
sta @temp_attr ; save for later.
and #%00011100 ; shave off all but the tile char bits (bit 2-0).
lsr
ora @temp_tile ; shift and merge with the other bits
; TTTt ttt0
lsr ; shift the result once more to get all bits in thei proper place.
; 0TTT tttt
adc tile_offset ; add the offset stored in the header for the metasprite data, incase we're using tiles within $80-$FF.
sta $0201, x ; store the final uncompressed tile char byte.
;;;; Do position offsets, attribute manipulations and so on...
rts
OAM_sprite_ptr_lo .db 1
OAM_sprite_ptr_hi .db 1
temp_var_1 .db 1
temp_var_2 .db 1
temp_var_3 .db 1
temp_var_4 .db 1
tile_offset .db 1
; drawing routine
DrawMetaSprite:
ldy #0
lda (OAM_sprite_ptr_lo), y
sta OAM_currSpriteByteSize ; this is a value I use unrelated to the compression.
iny
lda (OAM_sprite_ptr_lo), y
sta tile_offset
dey
ldx #0
@drawing_loop:
@temp_x = temp_var_1
@temp_y = temp_var_2
@temp_tile = temp_var_3
@temp_attr = temp_var_4
lda (OAM_sprite_ptr_lo), y
sta @temp_x ; we'll save this for later to do offsets to the x-position.
and #%11000000 ; shave off all but the tile char bits (bit 6-5).
sta @temp_tile ; store them for later.
iny
lda (OAM_sprite_ptr_lo), y
sta @temp_y ; save y-position for later just like the x-position.
and #%11000000 ; shave off all but the tile char bits (bit 4-3).
lsr
lsr ; shift the bits.
ora @temp_tile ; merge with bits 6-5.
sta @temp_tile
; TTTt 0000
iny
lda (OAM_sprite_ptr_lo), y
sta @temp_attr ; save for later.
and #%00011100 ; shave off all but the tile char bits (bit 2-0).
lsr
ora @temp_tile ; shift and merge with the other bits
; TTTt ttt0
lsr ; shift the result once more to get all bits in thei proper place.
; 0TTT tttt
adc tile_offset ; add the offset stored in the header for the metasprite data, incase we're using tiles within $80-$FF.
sta $0201, x ; store the final uncompressed tile char byte.
;;;; Do position offsets, attribute manipulations and so on...
rts
Here's an example of compressed metasprite data:
Code:
test_metasprite_offset:
.db -16, -16 ; y, x
test_metasprite_0:
.db 16 ; header: amount of unpacked bytes in this sprite
.db 128 ; ; tile char offset value
.db $88, $08, $1f ;;;; .db $08, $08, $47, $03
.db $90, $48, $03 ;;;; .db $10, $08, $48, $03
.db $88, $90, $1f ;;;; .db $08, $10, $57, $03
.db $90, $d0, $03 ;;;; .db $10, $10, $58, $03
.db -16, -16 ; y, x
test_metasprite_0:
.db 16 ; header: amount of unpacked bytes in this sprite
.db 128 ; ; tile char offset value
.db $88, $08, $1f ;;;; .db $08, $08, $47, $03
.db $90, $48, $03 ;;;; .db $10, $08, $48, $03
.db $88, $90, $1f ;;;; .db $08, $10, $57, $03
.db $90, $d0, $03 ;;;; .db $10, $10, $58, $03
So by doing this I use 75% of the space I used before to store the metasprite data (apart from the extra byte for the offset in the header), but of course I'm adding a lot of new instructions to do the unpacking for each tile.
I've implemented all this in my engine now and have it sort of working apart from some issues I have on existing metasprites with values going outside of the range that my compression allows.
I would be very interested in hearing if my take on this makes sense and if there's anything I can do to speed up the decompression of the tile character. My idea with compressing the data this way is that it will not be too hard to understand the decompression code as well as being compatible with metasprites with a maximum width of 63x63 (actually (63+8) * (63+8), given the width and height of an individual tile).
Sorry if this post is hard to follow. I omitted most of my drawing routine to put the focus on the unpacking. Obviously the real code does a lot more in terms of flipping sprites and offsetting positions before finally storing them..
Cheers!