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:

; Variables
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:

    ldx Index                  ; Initialize Line IRQ to first value
    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.

ProcessIRQ:
    lda ISR
    and #%00000010
    bne LINE_handler
    jmp (OLD_IRQ_HANDLER)         ; pass through


Get current Index

LINE_handler:
    ldx Index


Increment Scroll value of current section

    clc                           ; Add Increment to Scroll value
    lda ScrollLO,x
    adc Increment,x
    sta ScrollLO,x
    bcc :+
    inc ScrollHI,x


Divide by 2 to slow it down and write to VERA :

    lda ScrollHI,X                ; slow down scrolling by half
    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

    inx                          ; go to next Index and wrap around
    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.

    lda LineLO,x                 ; set Line IRQ to next value
    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

LINE_exit:
    lda #%00000010
    sta ISR                      ; Clear LINE flag


Since we are not calling default handler we have to restore registers from stack

    ply                          ; Restore CPU Registers
    plx
    pla


Exit

    rti                          ; Don't bother going to default IRQ handler

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

Popular posts from this blog

Commander X16 Premium Keyboard

Hello VERA (BASIC vs C vs Assembly)

Direct VERA Access