Sunday 7 May 2017

Shift work

Would you believe it?!

All this talk about tiles has real world repercussions: The new reality is that when someone has a shower upstairs, water comes out of the cooker hood downstairs.

Now, I'm fairly sure that isn't one of the advertised functions of the cooker hood. I couldn't find any mention of it in the manual. Extract steam? Check. Provide light? Check. Dribble minging second hand shower water into my glorious and most sacred Saturday Morning Fry Up? No. I didn't think so.

So according to 'professional' opinion, it would appear that the tiles around the shower weren't done 'properly', by a person or persons 'unknown', or possibly me and my father-in-law, and now after six years it has started leaking. And I can't just regrout it. Oh no, that would be way too easy. Those tiles have got to come off, and the wall fixed. And new tiles put back, because when my Wife gets involved, stuff 'has' to change colour.

None of this could possibly be my fault. Obviously greater forces are at work and it was talked up as a result of this blog. So please, be more careful in future. Careless talk costs £31 per square metre (inc. VAT).

True story.


Back to my happy place


So far I've described methods for scrolling in any direction, by drawing a relatively small amount of graphics into a buffer, and then copying the contents of the buffer to the display. This has given us pixel by pixel vertical scrolling, but the horizontal scrolling is still byte by byte. i.e. four pixels at a time in the chosen graphics mode. So what do we need to do to make the horizontal scroll work at the pixel level?

Early on in this sorry saga, many hump days ago, I spoke of The Idea. This involved four buffers, each containing the same screen image shifted by different amounts. The only time bit-shifting would be required is when new graphics are drawn into a buffer: Just a thin strip to fill in new background as it appears at the edge of the screen. Once in the buffer, there it will stay until it scrolls out of view again, making room for new graphics as it does so.

I call these four buffers shift buffers, numbered from zero to three. Shift buffer zero contains unshifted graphics. It works in the same way as the single buffer in the byte by byte scroll. The other shift buffers contain the same graphics shifted between one and three pixels to the left.

Each time we want to scroll horizontally, we need to choose one of the buffers into which we will draw new graphics. To keep track of where we are, we need a horizontal pixel counter. This is incremented each time we scroll right one pixel. (i.e. the background moves left one pixel)

The bottom two bits of the horizontal counter can therefore represent the number of the current shift buffer and we can use those two bits to look up the address of one of four drawing routines, each dedicated to drawing into a specific shift buffer.

Scrolling right, the shift buffers will be accessed in the sequence 0-1-2-3-0-1-2-3, and the buffer and map pointers will be incremented by one byte each time we land on buffer zero. (Because we have scrolled a new byte into view)

It's a similar story for scrolling left, except the sequence this time is 3-2-1-0-3-2-1-0, and the pointers need to be decremented each time we land on buffer three.

The buffer pointer now works slightly differently, as it represents the same position in all four buffers. So instead of pointing to an absolute address within one buffer, it is now an offset to be added to the base address of the current buffer.

Fab Four


So what do the four drawing routines look like? The buffer zero routine is the same as the one we already have for byte-by-byte horizontal scrolling. The others have more work to do. They have to shift the graphics data left by an amount that suits each buffer.

Now, when the pixels are shifted left, one or more of them will fall off the left hand edge of each byte. This is no problem because these will already be in the buffer. They were drawn during a previous frame. The problem is what happens on the right hand side of each byte. Where do the new pixels come from to fill up the space left by the other pixels moving left?

We Can Work it Out


The answer is the new pixels come from the half of the tile immediately to the right of the one being pointed to by the map pointer. This might be the right hand half of the same tile, or the left hand half of the next tile, or occasionally the left half of the tile from the left edge of the map thanks to wrapping. In each case, it means we need to load two bytes of tile data to create one byte of shifted data. The first byte is found via the map pointer, the second byte via the (wrapped) map pointer plus one.

If we get the first byte in the A register and the second byte in the B register then we are ready to do some shifting:

Source bytes before shifting

Remembering that each pixel is made up of two bits, the following sequence of instructions will shift left by one pixel and form the basis of the shift buffer one code:

    lda ,u++  ; get 1st byte
    ldb ,s++  ; get 2nd byte
    lslb      ; shift left one pixel..
    rola      ; ..by shifting two bits..
    lslb      ; ..from B..
    rola      ; ..into A
    sta ,x    ; store in buffer
    leax 32,x ; next row in buffer
    ; insert code to check for buffer boundary here
    ; loop above instructions eight times for complete tile

The U and S registers have been set up to point to tile graphics data. The X register is pointing into the shift buffer. The A and B registers look like this after shifting:

Shifted one pixel left

The shift instructions can be doubled up to create the shift buffer two routine:

Shifted two pixels left

And tripled to create the shift buffer three routine:

Shifted three pixels left

Back to the LSR


But shifting three pixels left is not very efficient. We can achieve the same result by shifting one pixel right instead. It just means the result ends up in the B register instead of the A register:

Shifted one pixel right

The four drawing routines all have the same general structure, the same logic behind the pointers, and the same technique for faster drawing using unrolled loops. They are so similar in fact that I have defined them in a macro, reducing the amount of effort in development and debugging. The macro just needs to be given the number of pixels to shift left as a parameter.

Now, finally, I was able to scroll pixel by pixel horizontally, and at a very high speed. That day was a good day. Well, it was a good day until I started scrolling vertically. The graphics were not updating correctly when scrolling horizontally after scrolling vertically. It soon became obvious that the vertical scroll routine needed to draw in all four shift buffers. I added a routine to take the newly drawn vertical scroll data from shift buffer zero, and write suitably shifted versions to the other buffers. Then it worked properly.

Shameless handwaving


I made one major improvement to the horizontal scrolling after that. It comes from the observation that the shift buffers all use the same source data, and that the shift buffer zero routine is relatively fast. The improvement is to calculate tile graphics addresses during the shift buffer zero routine only, and store those same addresses for re-use by the other shift routines. There are a couple of subtleties: Addresses need to be calculated for both left and right hand edges of the screen, so that the correct addresses are available when we change direction. It also becomes necessary to scroll the contents of the address buffers up and down during vertical scrolling, to keep the addresses in sync with the display.

I think that's quite enough rambling on about the scroll engine. Next time, something else!

No comments:

Post a Comment