Tiles in Assembly II - LINE Interrupt for Parallax Scrolling
We covered the use of IRQ interrupts triggered by VSYNC and Sprite Collisions already. Let’s tackle the next one.
LINE interrupt can be set to trigger when VERA is drawing a certain line on the screen. On Commodore 64 we had a functionality to read in what line the “beam” redrawing the screen was at any time. On Commander X16 we don’t have that capability but we can instruct VERA to trigger an interrupt at line of our interest. What is even more interesting for programming interesting effects is that we can change that setting during processing of it.
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.
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:
Index: .byte 0
LineLO: .byte 0, 80, 160, 240, 64, 144
LineHI: .byte 0, 0, 0, 0, 1, 1
Increment: .byte 1, 2, 3, 4, 5, 6
ScrollLO: .byte 0, 0, 0, 0, 0, 0
ScrollHI: .byte 0, 0, 0, 0, 0, 0
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.
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 0 and write it to IRQ_LINE address $9F28.
Then we turn on the LINE IRQ flag and als VSYNC which is the interrupt used by Commander X16 Kernal to handle keyboard, cursor blinking and BASIC interpreter. We want our demo to keep that running in the background. In game we would most likely want to disable it or more likely replace it with our own.
Last thing 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.
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) ; pass through
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
sta $9F38 ; and write to VERA Scroll registers
lda ScrollLO,x
ror
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