update graphic compression documentation
This commit is contained in:
parent
3483be127a
commit
6099316fbc
|
@ -370,7 +370,7 @@ addresses in NES like Object Attribute Memory (OAM).
|
|||
|
||||
# Palette
|
||||
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
|
||||
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 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
|
||||
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
|
||||
_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
|
||||
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,
|
||||
instead of
|
||||
* #$00 #$00 #$00 #$00 #$00 #$00 #$00 #$00 #$00
|
||||
It can be represented like
|
||||
* #$89 #$00
|
||||
can be encoded so that the number of repetitions is specified. In addition,
|
||||
_Contra_'s algorithm specifies when a run of bytes isn't the same. Below is
|
||||
an example that can help clarify.
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
|
@ -474,15 +491,77 @@ starting at PPU address $0680 (pattern table).
|
|||
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
|
||||
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
|
||||
* #$7f - tells the algorithm to change PPU write address to the next 2 bytes
|
||||
specified.
|
||||
* bit 7 set - any byte larger than #$7f, or alternatively, any byte that has
|
||||
its most significant bit set to 1 is treated as a RLE command to write the
|
||||
next byte of data multiple times to the PPU. This excludes #$ff, which
|
||||
always means end of graphic data
|
||||
* #$7f - command byte specifies to change PPU write address to the next 2
|
||||
bytes specified.
|
||||
* less than #$7f - or alternatively, any command byte that has its most
|
||||
significant bit clear is treated as a RLE command to write the subsequent
|
||||
byte to the PPU multiple times. The number of times to repeatedly write the
|
||||
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_1_supertiles_screen_ptr_table`
|
||||
|
|
|
@ -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
|
||||
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
|
||||
tay ; store number of repetitions in Y
|
||||
bpl write_graphic_byte_a_times ; if byte is < #$7f, write the following byte multiple times
|
||||
and #$7f ; byte is < #$7f, clear bit 7
|
||||
tay ; store command code byte in y
|
||||
bpl write_graphic_byte_a_times ; branch if byte is < #$7f to write the next byte multiple times (RLE-command)
|
||||
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
|
||||
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
|
||||
; * $02 - the number of bytes to write
|
||||
; * $02 - the number of bytes to write (n)
|
||||
; * y - the graphic data read offset
|
||||
write_next_n_sequence_bytes:
|
||||
lda ($00),y ; read graphic byte
|
||||
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
|
||||
|
||||
; writes value of a to the PPU
|
||||
; then determines if done with n bytes of graphic data
|
||||
write_repetition_sequence_byte:
|
||||
; then determines if done with the n-length byte sequence of graphic data
|
||||
@write_byte_to_ppu:
|
||||
sta PPUDATA ; write graphic byte to PPU
|
||||
cpy $02 ; see if written all n repetitions
|
||||
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
|
||||
; where n is the value in $02 + #$1
|
||||
; the #$01 is necessary to skip passed the command byte
|
||||
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
|
||||
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:
|
||||
ldx #$00 ; specifies that the 2-byte graphic read address is located at $00
|
||||
|
|
Loading…
Reference in New Issue