update graphic compression documentation

This commit is contained in:
Michael Miceli 2023-07-16 23:30:30 -04:00
parent 3483be127a
commit 6099316fbc
2 changed files with 111 additions and 29 deletions

View File

@ -370,7 +370,7 @@ addresses in NES like Object Attribute Memory (OAM).
# Palette # Palette
The NES supports 4 palettes for the nametables (background) and 4 palettes for The NES supports 4 palettes for the nametables (background) and 4 palettes for
sprites. A palette is a set of 3 colors. For nametables, one palette is shared sprites. A palette is a set of 4 colors. For nametables, one palette is shared
for each square 16x16 set of 4 pattern table tiles. For sprites, each tile in a for each square 16x16 set of 4 pattern table tiles. For sprites, each tile in a
sprite has its own palette. sprite has its own palette.
@ -388,7 +388,8 @@ level header starting at byte offset 15 (see `LEVEL_PALETTE_INDEX`). Each of
the next 8 bytes specify an entry into `game_palettes`. The first 4 bytes are the next 8 bytes specify an entry into `game_palettes`. The first 4 bytes are
the nametable palette colors, and the next 4 bytes are the sprite palette the nametable palette colors, and the next 4 bytes are the sprite palette
colors. Each entry in `game_palettes` specifies the 3 colors that make up the colors. Each entry in `game_palettes` specifies the 3 colors that make up the
palette. palette. Each palette always starts with a black color #$0f. Combined this
is how the 4 colors of each palette are determined.
## Fade-In Effect ## Fade-In Effect
_Contra_ supports a fade in effect for nametable palettes. This effect is used _Contra_ supports a fade in effect for nametable palettes. This effect is used
@ -448,17 +449,33 @@ compression algorithm known as
The idea of this algorithm is that the graphics data will have long "runs" of The idea of this algorithm is that the graphics data will have long "runs" of
repeated information. So instead of specifying the same byte over and over, it repeated information. So instead of specifying the same byte over and over, it
can be encoded so that the number of repetitions is specified. Essentially, can be encoded so that the number of repetitions is specified. In addition,
instead of _Contra_'s algorithm specifies when a run of bytes isn't the same. Below is
* #$00 #$00 #$00 #$00 #$00 #$00 #$00 #$00 #$00 an example that can help clarify.
It can be represented like
* #$89 #$00 Below is an example of un-compressed graphics data
```
#$00 #$00 #$00 #00 #$00 #$00 #$0e #$1f #$07 #$04 #$c0
```
When compressed, it becomes
```
#$06 #$00 #$85 #$0e #$1f #$07 #$0f #$c0 #$ff
```
The special codes in this sequence are #$06, #$85, and #$ff
#$06 means the next byte #$00 is repeated 6 times. Since the next code has bit
7 set, #$85 means to write the next 5 bytes to the PPU. Finally, #$ff means the
sequence is complete.
Interestingly, this algorithm is implemented twice in _Contra_: once for pattern Interestingly, this algorithm is implemented twice in _Contra_: once for pattern
table tiles (`write_graphic_data_to_ppu` and once again for super-tile table tiles (`write_graphic_data_to_ppu` and once again for super-tile
screen indexes (`load_supertiles_screen_indexes`). screen indexes (`load_supertiles_screen_indexes`). Each implementation is
slightly different.
## Pattern Table Decompression ## graphic_data_xx Decompression
The graphic data is loaded from ROM directly to the PPU. Not only is the The graphic data is loaded from ROM directly to the PPU. Not only is the
graphic data compressed, it also includes command bytes that specify where in graphic data compressed, it also includes command bytes that specify where in
the PPU to write the data to. This section outlines how the data is read and the PPU to write the data to. This section outlines how the data is read and
@ -474,15 +491,77 @@ starting at PPU address $0680 (pattern table).
After the first two bytes are read, the following algorithm reads the graphics After the first two bytes are read, the following algorithm reads the graphics
data section. When reading the graphic data, if the most significant bit of the data section. When reading the graphic data, if the most significant bit of the
graphic data byte is set, i.e 1, then the byte is treated as a command byte. graphic data byte is set, i.e 1, then the byte is treated as a command byte.
There are 3 command byte types There are 4 command byte types, evaluated in the following order
* #$ff - specifies the end of graphic data * #$ff - specifies the end of graphic data
* #$7f - tells the algorithm to change PPU write address to the next 2 bytes * #$7f - command byte specifies to change PPU write address to the next 2
specified. bytes specified.
* bit 7 set - any byte larger than #$7f, or alternatively, any byte that has * less than #$7f - or alternatively, any command byte that has its most
its most significant bit set to 1 is treated as a RLE command to write the significant bit clear is treated as a RLE command to write the subsequent
next byte of data multiple times to the PPU. This excludes #$ff, which byte to the PPU multiple times. The number of times to repeatedly write the
always means end of graphic data next byte is specified by the command byte value.
* greater than #$7f - or alternatively, any command byte that has its most
significant bit set is (but not #$ff) is interpreted as a command to write
the next string of bytes to the PPU. The number of bytes to write to the
PPU is specified by bits 0-6 of the command byte.
Below is some pseudocode for decompressing a graphics section and writing it to
the PPU. Note that due to the implementation, you can only flip graphics data
horizontally when that data only writes to a single PPU address location.
```
parse_ppu_address() {
byte b = read_next_byte();
write_to_PPUADDR(b);
b = read_next_byte();
write_to_PPUADDR(b);
if(flip_horizontally) {
read_next_byte();
read_next_byte();
}
}
while(true) {
parse_ppu_address();
byte b = read_next_byte();
if(b == 0xff) {
// finished decompressing graphics data
return;
}
while(true) {
if(b == 0x7f) {
// finished reading one section of compressed graphic
// next bytes are new PPU address
break;
}
if(b < 0x7f) {
// write same byte multiple times
byte numberOfRepetitions = b;
for (byte i = 0; i < numberOfRepetitions; i++) {
if(flip_horizontally) {
b = flip_horizontally(b);
}
write_to_PPUDATA(b);
}
} else if(b > 0x7f) {
// write next numberOfBytesToWrite bytes directly to PPU
byte numberOfBytesToWrite = b & 0x7f;
for (byte i = 0; i < numberOfBytesToWrite; i++) {
byte d = read_next_byte();
if(flip_horizontally) {
d = flip_horizontally(d);
}
write_to_PPUDATA(d);
}
}
}
}
```
Level section data not encoded exactly the same way Level section data not encoded exactly the same way
`level_1_supertiles_screen_ptr_table` `level_1_supertiles_screen_ptr_table`

View File

@ -2293,25 +2293,26 @@ write_graphic_data_sequences_to_ppu:
beq end_graphic_code ; loaded entire graphics data, restore previously loaded bank, re-init PPU beq end_graphic_code ; loaded entire graphics data, restore previously loaded bank, re-init PPU
cmp #$7f ; used to specify the PPU write address should change to the address specified in the next 2 bytes) cmp #$7f ; used to specify the PPU write address should change to the address specified in the next 2 bytes)
beq change_ppu_write_address beq change_ppu_write_address
tay ; store number of repetitions in Y tay ; store command code byte in y
bpl write_graphic_byte_a_times ; if byte is < #$7f, write the following byte multiple times bpl write_graphic_byte_a_times ; branch if byte is < #$7f to write the next byte multiple times (RLE-command)
and #$7f ; byte is < #$7f, clear bit 7 and #$7f ; byte has bit 7 set (negative), code is writing a string of bytes
; clear bit 7 to get number of bytes to write from compressed data
sta $02 ; store positive portion in $02, this is the number of bytes to write to PPU sta $02 ; store positive portion in $02, this is the number of bytes to write to PPU
ldy #$01 ; skip past size byte, prepare to read next n graphic bytes ldy #$01 ; skip past size byte, prepare to read next n graphic bytes
; writes the next n bytes of the graphic compression sequence to the PPU, starting at offset Y ; writes the next n bytes of the compressed graphic data to the PPU, starting at offset Y
; input ; input
; * $02 - the number of bytes to write ; * $02 - the number of bytes to write (n)
; * y - the graphic data read offset ; * y - the graphic data read offset
write_next_n_sequence_bytes: write_next_n_sequence_bytes:
lda ($00),y ; read graphic byte lda ($00),y ; read graphic byte
ldx $04 ; load graphic data horizontal flip bit value ldx $04 ; load graphic data horizontal flip bit value
bpl write_repetition_sequence_byte ; branch if not flipping graphic byte bpl @write_byte_to_ppu ; branch if not flipping graphic byte
jsr horizontal_flip_graphic_byte ; flipping horizontally, flip data before writing to PPU jsr horizontal_flip_graphic_byte ; flipping horizontally, flip data before writing to PPU
; writes value of a to the PPU ; writes value of a to the PPU
; then determines if done with n bytes of graphic data ; then determines if done with the n-length byte sequence of graphic data
write_repetition_sequence_byte: @write_byte_to_ppu:
sta PPUDATA ; write graphic byte to PPU sta PPUDATA ; write graphic byte to PPU
cpy $02 ; see if written all n repetitions cpy $02 ; see if written all n repetitions
beq advance_graphic_read_addr_n_bytes ; written all n bytes, update base graphic read address beq advance_graphic_read_addr_n_bytes ; written all n bytes, update base graphic read address
@ -2320,10 +2321,12 @@ write_repetition_sequence_byte:
; advances the address of the current graphic byte offset by n bytes ; advances the address of the current graphic byte offset by n bytes
; where n is the value in $02 + #$1 ; where n is the value in $02 + #$1
; the #$01 is necessary to skip passed the command byte
advance_graphic_read_addr_n_bytes: advance_graphic_read_addr_n_bytes:
lda #$01 lda #$01 ; advancing graphic byte read address by 1 (size of repetition string)
clc ; clear carry in preparation for addition clc ; clear carry in preparation for addition
adc $02 ; set A to the number of bytes to skip (they've already been written to the PPU) adc $02 ; skip over the bytes just written the PPU
; a now has the number of bytes to skip
advance_ppu_write_addr: advance_ppu_write_addr:
ldx #$00 ; specifies that the 2-byte graphic read address is located at $00 ldx #$00 ; specifies that the 2-byte graphic read address is located at $00