- Four screen-sized buffers, each containing the same image, but pixel-shifted different amounts.
- A pointer that indicates the position of the top-left corner of the display within the buffers.
- A tile-based map that provides the graphics.
- Routines that read the map and draw new data into the buffers in the right places to appear at the screen edges as they scroll into view.
- A fast copy routine that moves the data from one of the buffers to the screen.
I was fairly sure this would work, but I would need to start coding to test the idea. I decided to develop it one small step at a time and start with a single buffer, scrolling whole bytes instead of pixels. This would test that I was drawing into the right places in the buffer.
One problem with talking about scrolling is that the direction of scrolling is not clear. If I say 'scroll left', which way should the screen move? There's a sort of consensus on 'scrolling down' meaning the screen contents moving up, so that is the system I shall use. i.e. whatever direction we scroll in, the screen moves in the opposite direction.
This represents a buffer. I have arranged it as an 8 x 8 square to correspond with a hypothetical 8 x 8 display. The real thing would be much larger, but this will serve to illustrate.
The numbers represent the memory addresses of the 64 locations making up the buffer and the highlighted square represents a pointer to one of those addresses. When we copy the buffer to the display, we use the pointer as the start position for the copy. In this case the display would end up looking exactly like the buffer:
Things get more interesting when the pointer starts moving around as this will make the screen contents appear to move. What happens if we add one to the pointer before copying?
The numbers illustrate where in the buffer the data came from. Compared to the previous display, everything has moved one byte to the left i.e. we've scrolled right. I've highlighted the right hand column to indicate where we expect new graphics to appear. These are the locations in the buffer where we should have written new information before copying the buffer to the screen, the start address given by pointer + width - 1.
Note the behaviour is that of a circular buffer. When the copy operation reaches the end of the buffer it continues from the start of the buffer. It will be the same with writing into the buffer. The addresses must be 'wrapped' to always stay in the buffer.
Let's now add the buffer width of eight to the pointer and copy to the display:
This time the display has moved up compared to the previous display and the new data needed to be written starting at pointer - width. Or, as I have just noticed, the old value of the pointer. (Which saves a calculation and a boundary check)
To scroll left we need to subtract one from the pointer and the new data needs to be written starting at the pointer:
That leaves scrolling up by subtracting eight from the pointer. Again the new data will start at the pointer:
That covers the four main directions. Moving diagonally can be lazily achieved by performing separate horizontal and vertical scroll operations before copying. It's lazy because the data in a corner would get written twice where the horizontal and vertical sections overlap, but it should be possible to selectively draw in the corners depending on the situation.
These concepts can be tested fairly easily with some simple code. Four routines are required to handle each of the four scroll directions. These could be in response to key presses, and each routine could draw it's own colour or pattern to verify that data is being written to the right places.
The vertical scroll routines need to draw a horizontal stripe, with data at consecutive addresses. If the stripe reaches the end of the buffer before the full width has been drawn then it needs to continue from the start of the buffer.
The horizontal scroll routines need to draw a vertical stripe, with the address advancing by an amount equal to the buffer width. If the address falls outside of the buffer, then the buffer size needs to be subtracted to put the address back inside the buffer.
The copy routine can be a simple loop that transfers one byte per loop, checking for the end of the buffer and wrapping if necessary. It will be slow but it will work, or at least easy to debug.
It's a fairly gentle start, but I find all of this useful in understanding the problem properly, which will hopefully help with debugging later!
You make it sound easy. I spent way too much time just trying to figure out how to scroll text on the 32 column VDG screen for some blog posts I did recently!
ReplyDeleteDon't feel bad, I've spent way too much time on it too. The first 10 million years were the worst... ;-)
Delete