⭅ Previous (Cartridge Loading) Next (Graphics Palette) ⭆

NES CHR Graphics

Now that we have some cartridge data loaded, lets see how the CHR graphic data is used.

Also available on Youtube.

For these posts on graphics, we’ll be building up our knowledge “bottom up”, starting with primitive pieces and then seeing how they tie into the big picture.

The alternative would be to present this “top down”, starting with an outline and filling in the pieces. I’ve decided to use the “bottom up” approach so that each piece can be appreciated fully.

Graphics Primer

Before we jump right into the CHR data, lets do a quick primer on how computers represent images. Feel free to skip this section if you’re already familiar with computer graphics.

When we looked at memory, we saw that computers essentially remember information as long lists of numbers. The handling of computer graphics is no different. This applies to “raster” graphics. There is also “vector” graphics, which behave slightly differently.

A computer image is a collection of “pixels” or picture elements. Each of these pixels has a color. An image is a grid of pixels, and when these pixels are all placed together it looks like an image.

One way of representing colors is by mixing different amounts of red, green, and blue. The “color depth” refers to how many different possibilities there are for each of these color components. Colors on the web, for example, use one byte for each of red, green, and blue. This “24 bit” color depth allows 256256256 = 16,777,216 possible different colors, or about 16 million colors.

If you search for “color picker” you’ll find a tool that will let you explore this. The “RGB” number underneath shows the byte-per-component representation, with 0 being none of a color and 255 being the maximum possible for each component.

And with that, we have enough context to jump into the CHR data.

CHR data

For this article, I’ll be using examples from the nestest rom. We’ve been using it without graphics, but if you run it on a system with graphics support, this application actually has a simple menu system which looks like this:

nestest-menu

(image generated by our online nes emulator)

Now lets take a look at the CHR rom data and see how these images are represented. Just like with the ines header, lets look at a hexdump:

We had a 16384 byte PRG rom, plus a 16 byte header. hex(16384 + 10) = 0x4010, so 4010 is the start position of the CHR data in the nestest.nes file.

In the dump below, I’ve also included the last line of the PRG rom, which is interesting because you can see the reset vectors placed right at the end.

00004000: 0000 0000 0000 0000 0000 afc5 04c0 f4c5  ................
00004010: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00004020: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00004030: 8080 ff80 8000 0000 8080 ff80 8000 0000  ................
00004040: 0000 ff00 0000 0000 0000 ff00 0000 0000  ................
00004050: 0101 ff01 0100 0000 0101 ff01 0100 0000  ................
00004060: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00004070: 7cfe 00c0 c0fe 7c00 7cfe 00c0 c0fe 7c00  |.....|.|.....|.
00004080: fefe 00f0 c0fe fe00 fefe 00f0 c0fe fe00  ................
00004090: c6c6 02fe c6c6 c600 c6c6 02fe c6c6 c600  ................
000040a0: ccd8 00f0 d8cc c600 ccd8 00f0 d8cc c600  ................
000040b0: c6ee 02d6 c6c6 c600 c6ee 02d6 c6c6 c600  ................
000040c0: c6c6 02d6 cec6 c600 c6c6 02d6 cec6 c600  ................

0x4010 is the first byte of chr data. But it starts with all zeros, so lets skip ahead a little bit.

A byte position into the CHR data can be intrepeted according to:

dcba98 76543210  (bit number)
0TNNNN NNNNPyyy  (significance)
T: 0 or 1 for table selector
N: tile id, 0 - FF
P: plane, 0 or 1
y: y or row #

CHR data holds pixel data grouped into tiles. We’ll see this clearly quite soon. Since the 4 least significant(closest to position 0) bits are used within a tile, we have 0b1111 before the next tile. So each tile starts on a multiple of 0b10000, or 16 bytes.

We’ll skip ahead a bit and start decoding a tile that isn’t zero. Lets jump ahead 163 = 48 tiles, or 4816 = 0x300 bytes. Starting at 0x4310 we have:

00004310: 3e63 676b 7363 3e00 3e63 676b 7363 3e00  >cgksc>.>cgksc>.
00004320: 0c1c 0c0c 0c0c 3f00 0c1c 0c0c 0c0c 3f00  ......?.......?.
00004330: 3e63 630e 3863 7f00 3e63 630e 3863 7f00  >cc.8c..>cc.8c..
00004340: 3e63 630e 6363 3e00 3e63 630e 6363 3e00  >cc.cc>.>cc.cc>.

CHR Tile Decoding Example

Now lets decode a tile! CHR has two bits per pixel, and these bits are spli up into what are called two “planes”. For a single tile, all of the 0 position bits come first, then all of the 1 position bits.

This will be clearer with an example. To decode one tile starting at position 0x4310, we need all the rows for plane 0, and all the rows for plane 1. So 8 bytes for plane 0, and 8 bytes for plane 1.

Plane 0: (1st half of line 0x4310 in the hexdump)
3e63 676b 7363 3e00

Plane 1: (2nd half)
3e63 676b 7363 3e00

Lucky for us, in this example they’re the same. So we can just decode one plane by hand, and know that the other is identical.

Tile data represents an 8x8 image. Each byte represents one row, and each bit within that byte represents a dot from left to right.

Lets look at the binary representation of these bytes, so we can start to see pixels:

0x3e:0b0111110
0x63:0b1100011
0x67:0b1100111
0x6b:0b1101011
0x73:0b1110011
0x63:0b1100011
0x3e:0b1111100
0x00:0b0000000

Laid out this way, you might be able to see the image already. Here’s what it looks like when replacing the zeros with spaces, to make it even more clear: the zero.

0x3e:  11111 
0x63: 11   11
0x67: 11  111
0x6b: 11 1 11
0x73: 111  11
0x63: 11   11
0x3e: 11111  
0x00:        

And just to show that this works for each tile, lets decode the next one too.

Plane 0: 0c1c 0c0c 0c0c 3f00
Plane 1: 0c1c 0c0c 0c0c 3f00 (the same, again)
0x0c:0b00001100'
0x1c:0b00011100'
0x0c:0b00001100'
0x0c:0b00001100'
0x0c:0b00001100'
0x0c:0b00001100'
0x3f:0b00111111'
0x00:0b00000000'

Cleaned up:
0x0c:     11  
0x1c:    111  
0x0c:     11  
0x0c:     11  
0x0c:     11  
0x0c:     11  
0x3f:   111111
0x00:         

Now that we can see the pattern, lets talk a bit about how this actually works. Each pixel in a tile is represented with a 2 bit number, composed of bits from plane 0 and plane 1. Since the two tiles we looked at were the same in planes 0 and 1, this means each value is either 0b00 or 0b11.

These two bit numbers refer to positions in what is called a palette. Each time one of these tiles is drawn a palette will be selected which indiciates which colors should be used.

CHR Table visualization

Since these tiles do not have colors themselves, another way to visualize the data is to use a sort of greyscale palette. Using grey correctly indicates that the data is without color. And yet by using different shades of grey, it makes it possible to see the patterns in the data.

Here’s what interpretting these values with a greyscale “palette” might look like. We’ll see palettes in more detail soon, this is just to help make sense of what we’ve seen so far.

greyscale-palette

The debugger I am building alongside this series makes it possible to visualize the CHR tile data in this way. Visualizing the entire CHR rom from nestest, we have:

nestest-chr-visualization

The nes treats the CHR rom as two different tables of tiles. Often one side is used for background, and the other for characters. Since nestest only needs to draw text, only one of the tables is used, and it isn’t even fully utilized.

CHR sizes

Perhaps you’re wondering why chr data is organized this way. Splitting into two separate planes makes decoding a little simpler, since this needs to be performed by hardware and not software.

As for why only two bits per pixel, this is a space saving optimization which helped reduce the cost of storage needed.

Remember that every cartridge is going to need to store character data. The 8k NROM chr data allows storage of 2*16*16 tiles, each 8x8 pixels.

If modern image formats were used, each pixel would be 3 bytes. This would take 2*16*16*8*8*3 bytes, or 98304 bytes. More than 10 times the size.

And these chips were a significant portion of the cost of the cartridge.

We’ll see soon that using palettes also makes certain types of animation possible that would be challenging to do otherwise on this system.

Next up, colors and the palette!

And thats all for the CHR data! Graphic data without color wouldn’t be very exciting, so next up we’ll see how colors get applied to this image data.

⭅ Previous (Cartridge Loading) Next (Graphics Palette) ⭆

We publish about 1 post a week discussing emulation and retro systems. Join our email list to get notified when a new post is available. You can unsubscribe at any time.