Jump to content
  • entries
    39
  • comments
    0
  • views
    834

Screen Compression


Piledriver

22 views

I want to go into more detail on my screen encoding (done in a C# app) and loading (done in the game).  Today I'll discuss screen loading.

 

This image is half of the 32KB ROM.  This is 4 banks of data containing the 1024 screens in the game and the loading code is duplicated in each bank.

638520643815303003-20240523-AutoFastFixb.thumb.png.15ff52707a4810147199a5e69e979f69.png

 

Each bank is divided into 4 subbanks.  The loading code is in the first subbank cutting into the available screen data a bit.

Bank7.thumb.png.c66cd94f29ea466dfe6b6cd04c7ff894.png

 

An individual subbank holds 64 screens.  There are two main components to a subbank - the screens and the chunk palette.  A screen is a list of 2-15 one byte indexes to chunks.  The screen data grows from near the top of the subbank as more screens are added and the chunk palette grows from the bottom towards the top.  As I designed the screens I had to make sure the screen data and chunk palette didn't overlap or there would be corrupted data.  You can see the buffer between these two regions as series of solid bytes.

Bank7-2.thumb.png.e0e02db28bd0e270e2a86dbcec8c6920.png

 

Here's a snippet of the chunk palette:

    .byte #%00000111    ; Line 47 (250)
    .byte #%00111111
    .byte #%00010001    ; rle
    .byte #%00001111    ; Line 48 (251)
    .byte #%01111111
 
    .byte #%00000000    ; Line 49 (252)
    .byte #%00000111
    .byte #%00100001    ; rle
    .byte #%00000000    ; Line 50 (253)
    .byte #%00000010
 
    .byte #%00000000    ; Line 51 (254)
    .byte #%11100000
    .byte #%00010001    ; rle
    .byte #%00001000    ; Line 52 (255)
    .byte #%00000000

 

A chunk is a run length encoded piece of a screen.  A chunk consists of 2 bytes of playfield data (PF1 and PF2) and a nibble of run length encoding data allowing a chunk to be up to 15 rows tall.  I didn't want to waste a full byte on these nibbles so I combine two chunks into 5 bytes.  This makes loading a tad more complicated but I'm not wasting any space.

 

Here's 7 screens:

zScreen000:    .byte #5, #255, #5, #255, #4, #254, #5
zScreen001:    .byte #253, #252, #251, #250, #249, #12, #248, #249, #247, #246
zScreen002:    .byte #5, #255, #5, #254, #4, #255, #5
zScreen003:    .byte #5, #254, #5, #254, #4, #255, #5
zScreen004:    .byte #5, #254, #10, #255, #5
zScreen005:    .byte #5, #245, #10, #245, #5
zScreen006:    .byte #10, #245, #11

 

Each screen is a list of chunk indexes.  Notice how in this example the index of the chunks is either a large number (200+) or a small number (< 16)?  This is from an optimization I made.  A chunk is 2.5 bytes.  This adds up quickly as you add more chunks.  I came up with an optimization for empty spaces.  Instead of making all these chunks of empty space of various numbers of rows... if the chunk index is under 16 I just use the index as the run length value and the chunk playfield data is just empty space.  This was not a huge optimization, but still this allowed me to make a handful of complex screens in each of the 16 subbanks that I wouldn't have been able to do otherwise.  Every bit helps eh?

 

So now we mostly know how the screen data is laid out... mostly.

 

So how do I actually load a screen from a subbank into RAM??

 

This is pseudocode, but it starts with something like this:

LOADSCREEN(bank=X, subbank=Y, screennumber=Z)                      ;where bank is 4,5,6 or 7, subbank is 0,1,2, or 3, and screennumber is between 0 and 63.

 

I'm going to gloss over a bit of detail, but lets just jump to the subbank we want.  So from there how do I get a pointer to the screen we want?  The screens are of variable size so I cant just do something like address = screennumber*16.  I could have an array of pointers one for each screen.  That would require 2 bytes of extra storage per screen.  At 1024 screens that's 2KB just for screen pointers!  That's the size of the whole Combat game right there!

 

This is where I got creative to save rom.  What I did was for each subbank theres 64 screens but I only store addresses for every 8th screen.  So I set my screen pointer close to where it should be (within 8 screens away).  I need a way to get to the screen I want.  I have another table which stores the size of every screen in a nibble.  So with the sizes of all the screens between my current screen and destination I can loop through those sizes offsetting the screen pointer by those sizes until I reach the desired screen.  Pretty weird eh?  Lets do some analysis on this on a per subbank basis...

 

One pointer per screen method of getting desired screen pointer

-requires 128 bytes of pointer table!

-very fast to get a pointer to desired screen

 

One pointer per 8th screen method of getting desired screen pointer

-requires 16 bytes of pointer table and 32 bytes of screen size data = 48 bytes total overhead!

-plus extra overhead for the code required to get to the desired screen

-very slow to get a pointer to the desired screen

 

This was an interesting tradeoff.  It didn't really matter that locating a screen is very slow - I have an entire frame to load and process a screen, so it's worth it to save a few dozen bytes per subbank.  These small gains helped a lot to give me enough bytes to get creative with the screen design and not just have a handful of chunky blocks on each screen.

 

Getting the screen pointer was the hard part.  The next step is just looping through all the chunk indexes of a screen, loading the corresponding chunks, and expanding each chunk into the section of RAM reserved for the screen.

 

There is a variant of the screen load function that just loads a single row into RAM.  I found a few bytes to spare for the screen pointer, so I have that cached!  When I need to load a single row for scrolling purposes I don't need to go through the entire screen searching process again.  I still need to jump to the correct bank though.

0 Comments


Recommended Comments

There are no comments to display.

Guest
Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...