Tiles in Assembly II - LINE Interrupt for Parallax Scrolling
More information about the use of IRQ interrupts triggered by VSYNC check the post about Sprite Collisions here. Let’s tackle the use of LINE interrupts.
LINE interrupt can be set to trigger when VERA is drawing a certain line on the screen, hence the name. On Commodore 64 we had a functionality to read in what line the “beam” redrawing the screen was at any time, which can be handy but requires some kind of loop to continuously check the value in that register. On Commander X16 we have the capability to instruct VERA to trigger an interrupt at line of our interest. What is even more interesting for programming advanced effects is that we can change that setting during processing of it during each screen refresh. Commander X16 is fast enough to do that continuously on every frame.
Note that VERA always redraws the screen at VGA resolution which is 640x480 pixels so it doesn’t matter what is the scaling of the screen, the LINE interrupts always have to be set up with 480 lines in mind.
In the world of LCD screens it is somewhat archaic to think in terms of scan lines being displayed. That is how old CRT TVs and monitors worked. The image was redrawn 60 times per second (NTSC) or 50 times per second (PAL). It was also redrawn from top left one horizontal line at a time. That is pretty fast for our eyes because we can’t notice the redrawing but for our CPU that is still pretty slow and provides us enough time to manipulate the displayed image during each redraw many times.
Scroll registers
Scrolling on Commander X16 is very simple to achieve and doesn’t really require a dedicated tutorial. VERA interface includes 4 scrolling registers for each of the available Layers. Two bytes for horizontal and two bytes for vertical scroll.
To make a Layer shift by a certain number of pixels we have to write that shift value to those two registers. First one for bits 0-7 and second for bits 8-11. And that is pretty much it, addresses for your references:
Register | Address | Name | Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
---|---|---|---|---|---|---|---|---|---|---|
16 | $9F30 | Layer 0 - H Scroll | Horizontal Scroll Bits 0 - 7 | |||||||
17 | $9F31 | Layer 0 H Scroll | - | Horizontal Scroll Bits 8 - 11 | ||||||
18 | $9F32 | Layer 0 - V Scroll | Vertical Scroll Bits 0 - 7 | |||||||
19 | $9F33 | Layer 0 - V Scroll | - | Vertical Scroll Bits 8 - 11 | ||||||
23 | $9F37 | Layer 1 - H Scroll | Horizontal Scroll Bits 0 - 7 | |||||||
24 | $9F38 | Layer 1 H Scroll | - | Horizontal Scroll Bits 8 - 11 | ||||||
25 | $9F39 | Layer 1 - V Scroll | Vertical Scroll Bits 0 - 7 | |||||||
26 | $9F3A | Layer 1 - V Scroll | - | Vertical Scroll Bits 8 - 11 |
Parallax effect
There are a ton of resources available on the net that describe what Parallax effect is and why we experience it in the real world. In games it provides a nice visual appearance of depth.
From the programming point of view the Parallax is really just scrolling the objects closer to us, the observer, faster than objects that are further away.
We can achieve the Parallax effect in several ways.
- Use Layers moving at different speed
- Use Sprites to represent objects that are closer or further away. E.g. clouds all the way in the back could move much slower than main playing field
- Considering that Commander X16 only supports two layers and if we want to have more different speeds on the screen we have to use LINE interrupts to create a parallax scrolling effect using one single layer.
- For most complex scenes we can combine two layers split into multiple regions and use sprites for additional layers that can overlap
VERA provides hardware supported scrolling but we only have one scrolling register for each vertical scroll and horizontal scroll per layer. To change the scrolling value more than once per screen we can set LINE interrupt to change it as many times per frame as we want. For our demo let’s split the screen to 6 sections each 80 pixels high.
To better understand what we are trying to achieve let’s illustrate with a picture:
Now, if we imagine that picture is drawn from top to bottom let’s run through the sequence of events:
- we first set up a line interrupt to trigger in Line 0. We increment the scrolling for the topmost section and set the horizontal scroll register. Then we immediately reconfigure LINE interrupt so it will trigger again when reaching line 80. VERA will draw lines 0-79 at the current position.
- When line 80 is reached, interrupt is triggered again. Now we update the scroll value for the second screen areas from top. We reconfigure LINE interrupt again to the line 160. VERA will continue to draw display from line 80 to 159 at the second setting we wrote into scroll registers and will not touch lines 0-79 because they have been already drawn in this frame.
- When reaching line 160 the same sequence is done again and then for lines 240, 320 and 400. When processing line 400 we reset the counter and set LINE interrupt to Line 0 again which will be triggered when VERA starts drawing the next frame and so we go sixty times per second.
Variables
We need to keep track of 6 sections of the screen and some values need to be 16 bits so we will create the following variables organized in such a way to be easily accessible using indexed addressing mode:
The meaning should be pretty self explanatory but let’s summarize it anyway.
Obviously Index will keep track of what section of the screen we currently are. We start with 0 and increment during each LINE interrupt and after 5 we go back to 0.
LineLO and LineHI are 16 bit values of the lines where the LINE interrupt will be set. When testing the program on Emulator version R43 I determined that I have to trigger Line interrupt two lines earlier that the effect will show up. In example above, to see the effect of different scroll value in line 80, I have to trigger the interrupt in line 78 and make change to Scroll registers.
Increment is the value that will be added during each LINE interrupt call. Because each section of the screen is scrolled at a different rate they have different values.
ScrollLO and ScrollHI are values that will be incremented by Increment value and written to VERA horizontal scroll registers. Note that to slow down scrolling by 50% (first section will be scrolling 0.5 pixels per frame) we will divide these values by 2 before writing them to VERA.
Set up code
During the setup we only need to few things and code is really short:
lda LineLO,x
sta IRQ_LINE
lda #%00000011 ; LINE and VSYNC are active
sta IEN
As we see we read the first Line Interrupt value, which happens to be 78 and write it to IRQ_LINE address $9F28.
Then we turn on the LINE IRQ flag and also VSYNC which is the interrupt used by Commander X16 Kernal to handle keyboard, cursor blinking and BASIC interpreter among other "regular" processes in BASIC mode. We want our demo to keep that running in the background. In game we would most likely replace it with our own handler. When computer is in VSYNC phase (means nothing is being drawn to the screen) is perfect time to prepare changes for display.
Next step is to insert our Interrupt handler before the default system IRQ handler but since we have done this few times before in Sprite tutorials we will not repeat it here again.
Lastly, it is good practice to clear IRQ flags and turn on the IRQ again.
Interrupt Handler
Interrupt handler has to be pretty fast so we have to process following sequence of actions in quick succession:
Check what called the interrupt. If not LINE then go to the default handler.
lda ISR
and #%00000010
bne LINE_handler
jmp (OLD_IRQ_HANDLER) ; Process VSYNC in original Interrupt handler
Get current Index
ldx Index
Increment Scroll value of current section
lda ScrollLO,x
adc Increment,x
sta ScrollLO,x
bcc :+
inc ScrollHI,x
Divide by 2 to slow it down and write to VERA :
lsr ; slow down scrolling by half
sta $9F38 ; and write to VERA Horizontal Scroll registers
lda ScrollLO,x
ror ; slow down the low byte
sta $9F37
Increment Index and if reached 6 go back to 0
cpx #6
bne :+
ldx #0
: stx Index
Set up the next LINE interrupt, note the bit 8 of the Line interrupt which is located at bit 7 of the IEN register.
sta IRQ_LINE
lda LineHI,x
beq :+
lda #%10000011 ; Don’t forget the IRQ Bit 8
sta IEN
jmp LINE_exit
: lda #%00000011
sta IEN
Clear LINE flag
lda #%00000010
sta ISR ; Clear LINE flag
Since we are not calling default handler we have to restore registers from stack
plx
pla
Exit
The rest of the source code is only to support the demo like custom characters and filling the display with an interesting pattern to demonstrate the parallax scrolling.
The result
Short video of the end result
Complete Source source is located here.
Binary can be downloaded here.
Comments
Post a Comment