Wednesday, May 25, 2016

Taking advantage of a device's capabilities

Know thy hardware

In the previous post, I showed you that rendering, in a straighforward way, a background the size of the display is so slow that it's unusable. There are of course much more efficient methods of displaying images, background, foreground, sprites, etc. The first step towards finding a better method is learning more about the hardware, what it can do and how to program it to do it. Today I will try explaining an example of this.

The display shows only a section of what is called the hardbuffer (actually, there are two hardbuffers, but that's for later). In reality the hardbuffer (from now on "HARDBUFF") may be any memory area. It's just that you can tell the calculator "use this memory location as the start of the image you will show on the screen". It's like you give someone an entire book but you tell them "start reading on page five". You set HARDBUFF to be a specific location by writing that address (which is 20 bits wide) into a special memory location by the human-readable name =DISP1CTL (=DISP2CTL is for the other hardbuffer and the equal sign is not very important). There is another special memory location that accepts how long each row of HARDBUFF is, in nibbles, which is called =LINENIBS. There are some other peculiarities about =DISP1CTL and some other special memory locations, but these will do for now.

Suppose we write the background image, as we did in the previous post, starting at address #01000h. That display would appear if we wrote the nibbles 0,0,0,1,0 to the locations =DISP1CTL, =DISP1CTL+1 ... =DISP1CTL+4 (or "write #01000h to =DISP1CTL"). This is how the screen will look if we write #00FDEh (#01000h - 34) instead:
One pixel/row/step up
Essentially, we scrolled the game area up, as if our player character moved down. The last row looks random. It certainly isn't our image, we didn't write that far anyway. What is it? It's memory. It's the memory that happens to (already) exist, just after the area our image's data exists. Doesn't sound very useful. Yet.

We observe that we can change what is shown on the screen without redrawing it. We just wrote a different number in =DISP1CTL and the display changed. Not much, but it changed. Can we use it? Yes. Can we abuse it? Of course!

So by rendering a 136*128 background (instead of the current 136*64) and decrementing/incrementing the value we write to =DISP1CTL by 34, we can move the image down/up (scroll up/down) respectively.

What about horizontal scrolling? Well, things now get tricky: =DISP1CTL can only accept addresses on byte boundaries, so we can only do a big step left/right by 8 pixels. There is, however, a 4bit address, called =BITOFFSET, which sets the pixel offset of what is shown on the screen. Right now it is set to 0. To scroll towards the left, as the player moves right, we would have to increment =BITOFFSET by 1 for each pixel towards the right and when we're about to increase it from 7 to 8, write 0 to it instead and increment =DISP1CTL by 2. That, however, implies the image we are displaying is wider than 136 pixels: if we did this with the image we have shown until now, the screen would become garbled and at the start of each row of pixels, pixels from the ending of the previous row would appear. We need to adjust =LINENIBS, to configure how many nibbles the display controller should skip after reaching the end of each row, by an amount appropriate to the size of the displayed image.

So, we need a larger image. And with a larger image comes heavier memory usage. And longer rendering times. For now however, we can, theoretically, scroll a static image back and forth, up and down, without much cpu usage: just a couple writes. That's a big step from earlier when we didn't have time to render one screen's worth of data in time.

No comments:

Post a Comment