Opening the borders
by Pasi 'Albert' Ojala (po87553@cs.tut.fi or albert@cc.tut.fi)
        Written: 20-Jul-92

       All timings are in PAL, principles will apply to NTSC too.
              Refer to VIC memory map in Hacking Issue 4.

VIC has many features and transparent borders are one of them. You can not
make characters appear in the border, but sprites are displayed in the
border too. "How to do this then?" is the big question.

The screen resolution in C64 has been and will be 320 x 200 pixels. Most
games need to use the whole screen to be efficient or just plain playable.
But there still is that useless border area, and you can put score and
other status information there instead of having them interfere with the
full-screen smooth-scrolling playing area.


_How to disable the vertical borders_

When VIC (Video Interface Controller) has displayed all character rows,
it will start displaying the vertical border area. It will start displaying
the characters again in top of the screen. The row select register sets the
number of character lines on the screen. If we select the 24-row display
when VIC is drawing the last (25th) row, it does not start to draw the
border at all !  VIC will think that it already started to draw the border.

The 25-row display must be selected again in the top of the screen, so that
the border may be opened in the next frame too. The number of displayed rows
can be selected with the bit 3 in $d011. If the bit is set, VIC will display
25 rows and 24 rows otherwise. We have to clear the bit somewhere during the
last row (raster lines $f2-$fa) and set it again in top of the screen or at
least somewhere before the last row (line $f2). This has to be done in every
frame (50 times per second in PAL).


_How to open the sideborders_

The same trick can be applied to sideborders. When VIC is about to start
displaying the sideborder, just select 38-column mode and restore 40-column
mode so that you can do the trick again in the next scan line. If you need to
open the sideborders in the bottom or top border area, you have to open the
vertical borders also, but there shouldn't be any difficulty in doing that.

There is two drawbacks in this. The timing must be precise, one clock cycle
off and the sideborder will not open (the sprites will generally take care of
the timing) and you have to do the opening on each and every line. With
top/bottom borders once in a frame was enough.

Another problem is bad-lines. There is not enough time to open the borders
during a bad line and still have all of the sprites enabled. One solution
is to open the borders only on seven lines and leave the bad lines unopened.
Another way is to use less than eight sprites. You can have six of them
on a bad line and still be able to open the sideborders (PAL). The old and
still good solution is to scroll the bad lines, so that VIC will not start
to draw the screen at all until it is allowed to do so.
[Read more about bad lines from previous C=Hacking Issues]


_Scrolling the screen_

VIC begins to draw the screen from the first bad line. VIC will know what
line is a bad line by comparing its scan line counter to the vertical
scroll register : when they match, the next line is a bad line. If we change
the vertical scroll register ($d011), the first bad line will move also.
If we do this on every line, the line counter in VIC will never match with
it and the drawing never starts (until it is allowed to do so).

When we don't have to worry about bad lines, we have enough time to open the
borders and do some other effects too. It is not necassary to change the
vertical scroll on every line to get rid of the bad lines, just make sure
that it never matches the line counter (or actually the least significant
8 bits).

You can even scroll the bad lines independently and you have FLD - Flexible
Line Distance. You just allow a bad line when it is time to display the next
character row. With this you can bounce the lines or scroll a hires picture
very fast down the screen. But this has not so much to do with borders, so
I will leave it to another article. (Just send requests and I might start
writing about FLD ..)


_Garbage appearing_

When we open the top and bottom borders, some graphics may appear. Even
though VIC has already completed the graphics data fetches for the screen
area, it will still fetch data for every character position in top and bottom
borders. This will not do any harm though, because it does not generate any
bad lines and happens during video fetch cycles [see Missing Cycles article].
VIC reads the data from the last address in the current video bank, which is
normally $3fff and displays this over and over again.

If we change the data in this address in the border area, the change will be
visible right away. And if you synchronize the routine to the beam position,
you can have a different value on each line. If there is nothing else to do
in the border, you can get seven different values on each scan line.

The bad thing about this graphics is that it is impossible to change its
color - it is always black. It is of course possible to use inverted graphics
and change the background color. And if you have different data on each line,
you can as easily have different color(s) on each line too.

If you don't use $3fff for any effects, it is a good idea to set it to zero,
but remember to check that you do not store anything important in that
address. In one demo I just cleared $3fff and it was right in the middle of
another packed demopart. It took some time to find out what was wrong with
the other part.


_Horizontal scrolling_

This new graphics data also obeys the horizontal scroll register ($D016), so
you can do limited tech-tech effects in the border too. You can also use
sprites and open the sideborders. You can see an example of the tech-tech
effect in the first example program. Multicolor mode select has no effect
on this data. You can read more about tech-tech effects in a future article.


_Example routine_

The example program will show how to open the top and bottom borders and how
to use the $3fff-graphics. It is fairly well commented, so just check it for
details. The program uses a sprite to do the synchronization [see Missing
Cycles article] and reads a part of the character ROM to the display data
buffer. To be honest, I might add that this is almost the same routine than
the one in the Missing Cycles article. I have included both PAL and NTSC
versions of the executables.

--------------------------------------------------------------------------
The example program - $3fff-graphics

IMAGE0= $CE00   ; First graphics piece to show
IMAGE1= $CF00   ; Second piece
TECH=   $CD00   ; x-shift
RASTER= $FA     ; Rasterline for the interrupt
DUMMY=  $CFFF   ; Dummy-address for timing (refer to missing_cycles-article)

*= $C000
        SEI             ; Disable interrupts
        LDA #$7F        ; Disable timer interrupts (CIA)
        STA $DC0D
        LDA #$01        ; Enable raster interrupts (VIC)
        STA $D01A
        STA $D015       ; Enable the timing sprite
        LDA #<IRQ
        STA $0314       ; Interrupt vector to our routine
        LDA #>IRQ
        STA $0315
        LDA #RASTER     ; Set the raster compare (9th bit will be set
        STA $D012       ;  inside the raster routine)
        LDA #RASTER-20  ; Sprite is situated 20 lines before the interrupt
        STA $D001

        LDX #111
        LDY #0
        STY $D017       ; Disable y-expand
        LDA #$32
        STA $01         ; Select Character ROM
LOOP0   LDA $D000,X
        STA IMAGE0,Y    ; Copy a part of the charset to be the graphics
        STA IMAGE0+112,Y
        LDA $D800,X
        STA IMAGE1,Y
        STA IMAGE1+112,Y
        INY             ; Until we copied enough
        DEX
        BPL LOOP0
        LDA #$37        ; Char ROM out of the address space
        STA $01

        LDY #15
LOOP1   LDA XPOS,Y      ; Take a half of a sinus and mirror it to make
        STA TECH,Y      ;  a whole cycle and then copy it as many times
        STA TECH+32,Y   ;   as necassary
        LDA #24
        SEC
        SBC XPOS,Y
        STA TECH+16,Y
        STA TECH+48,Y
        DEY
        BPL LOOP1
        LDY #64
LOOP2   LDA TECH,Y
        STA TECH+64,Y
        STA TECH+128,Y
        DEY
        BPL LOOP2
        CLI             ; Enable interrupts
        RTS             ; Return to basic (?)


IRQ     LDA #$13        ; Open the bottom border (top border will open too)
        STA $D011
        NOP
        LDY #111	; Reduce for NTSC ?
        INC DUMMY       ; Do the timing with a sprite
        BIT $EA         ; Wait a bit (add a NOP for NTSC)

LOOP3   LDA TECH,Y      ; Do the x-shift
        STA $D016
FIRST   LDX IMAGE0,Y    ; Load the graphics to registers
SECOND  LDA IMAGE1,Y
        STA $3FFF       ; Alternate the graphics
        STX $3FFF
        STA $3FFF
        STX $3FFF
        STA $3FFF
        STX $3FFF
        STA $3FFF
        STX $3FFF
        STA $3FFF
        STX $3FFF
        LDA #0          ; Throw away 2 cycles (add a NOP for NTSC)
        DEY
        BPL LOOP3

        STA $3FFF       ; Clear the graphics
        LDA #8
        STA $D016       ; x-scroll to normal
        LDA #$1B
        STA $D011       ; Normal screen (be ready to open the border again)
        LDA #111
        DEC FIRST+1     ; Move the graphics by changing the low byte of the
        BPL OVER        ;  load instruction
        STA FIRST+1
OVER    SEC
        SBC FIRST+1
        STA SECOND+1    ; Another graphics goes to opposite direction
        LDA LOOP3+1     ; Move the x-shift also
        SEC
        SBC #2
        AND #31         ; Sinus cycle is 32 bytes
        STA LOOP3+1

        LDA #1
        STA $D019       ; Acknowledge the raster interrupt
        JMP $EA31       ; jump to the normal irq-handler

XPOS    BYT $C,$C,$D,$E,$E,$F,$F,$F,$F,$F,$F,$F,$E,$E,$D,$C
        BYT $C,$B,$A,$9,$9,$8,$8,$8,$8,$8,$8,$8,$9,$9,$A,$B
                        ; half of the sinus

--------------------------------------------------------------------------
Basic loader for the $3fff-program (PAL)

1 S=49152
2 DEFFNH(C)=C-48+7*(C>64)
3 CH=0:READA$,A:PRINTA$:IFA$="END"THENPRINT"<clear>":SYS49152:END
4 FORF=0TO31:Q=FNH(ASC(MID$(A$,F*2+1)))*16+FNH(ASC(MID$(A$,F*2+2)))
5 CH=CH+Q:POKES,Q:S=S+1:NEXT:IFCH=ATHEN3
6 PRINT"CHECKSUM ERROR":END
100 DATA 78A97F8D0DDCA9018D1AD08D15D0A9718D1403A9C08D1503A9FA8D12D0A9E68D,4003
101 DATA 01D0A26FA0008C17D0A9328501BD00D09900CE9970CEBD00D89900CF9970CFC8,4030
102 DATA CA10EAA9378501A00FB9DCC09900CD9920CDA91838F9DCC09910CD9930CD8810,4172
103 DATA E8A040B900CD9940CD9980CD8810F45860A9138D11D0EAA06FEEFFCF24EAB906,4554
104 DATA CD8D16D0BE53CEB91CCF8DFF3F8EFF3F8DFF3F8EFF3F8DFF3F8EFF3F8DFF3F8E,4833
105 DATA FF3F8DFF3F8EFF3FA9008810D18DFF3FA9088D16D0A91B8D11D0A96FCE85C010,4163
106 DATA 038D85C038ED85C08D88C0AD7FC018E901291F8D7FC0EE19D04C31EA0C0C0D0E,3719
107 DATA 0E0F0F0F0F0F0F0F0E0E0D0C0C0B0A09090808080808080809090A0B00000000,318
200 DATA END,0

--------------------------------------------------------------------------
An uuencoded C64 executable $3fff-program (PAL)

begin 644 xFFF.64
M`0@-"`$`4[(T.3$U,@`F"`(`EJ5(*$,ILD.K-#BJ-ZPH0[$V-"D`40@#`$-(?
MLC`ZAT$D+$$ZF4$D.HM!)+(B14Y$(J>9(I,B.IXT.3$U,CJ``(@(!`"!1K(P/
MI#,Q.E&RI4@HQBC**$$D+$:L,JHQ*2DIK#$VJJ5(*,8HRBA!)"Q&K#*J,BDI:
M*0"I"`4`0TBR0TBJ43J74RQ1.E.R4ZHQ.H(ZBT-(LD&G,P#!"`8`F2)#2$5#F
M2U-532!%4E)/4B(Z@``."60`@R`W.$$Y-T8X1#!$1$-!.3`Q.$0Q040P.$0QK
M-40P03DW,3A$,30P,T$Y0S`X1#$U,#-!.49!.$0Q,D0P03E%-CA$+"`T,#`S9
M`%L)90"#(#`Q1#!!,C9&03`P,#A#,3=$,$$Y,S(X-3`Q0D0P,$0P.3DP,$-%Y
M.3DW,$-%0D0P,$0X.3DP,$-&.3DW,$-&0S@L(#0P,S``J`EF`(,@0T$Q,$5!S
M03DS-S@U,#%!,#!&0CE$0T,P.3DP,$-$.3DR,$-$03DQ.#,X1CE$0T,P.3DQL
M,$-$.3DS,$-$.#@Q,"P@-#$W,@#U"6<`@R!%.$$P-#!".3`P0T0Y.30P0T0Y0
M.3@P0T0X.#$P1C0U.#8P03DQ,SA$,3%$,$5!03`V1D5%1D9#1C(T14%".3`V5
M+"`T-34T`$(*:`"#($-$.$0Q-D0P0D4U,T-%0CDQ0T-&.$1&1C-&.$5&1C-&%
M.$1&1C-&.$5&1C-&.$1&1C-&.$5&1C-&.$1&1C-&.$4L(#0X,S,`CPII`(,@'
M1D8S1CA$1D8S1CA%1D8S1D$Y,#`X.#$P1#$X1$9&,T9!.3`X.$0Q-D0P03DQ-
M0CA$,3%$,$$Y-D9#13@U0S`Q,"P@-#$V,P#<"FH`@R`P,SA$.#5#,#,X140X+
M-4,P.$0X.$,P040W1D,P,3A%.3`Q,CDQ1CA$-T9#,$5%,3E$,#1#,S%%03!#.
M,$,P1#!%+"`S-S$Y`"@+:P"#(#!%,$8P1C!&,$8P1C!&,$8P13!%,$0P0S!#P
M,$(P03`Y,#DP.#`X,#@P.#`X,#@P.#`Y,#DP03!",#`P,#`P,#`L(#,Q.``T>
-"\@`@R!%3D0L,````#@P1
``
end
size 823
--------------------------------------------------------------------------
An uuencoded C64 executable $3fff-program (NTSC)

begin 644 xfff-ntsc.64
M`0@-"`$`4[(T.3$U,@`F"`(`EJ5(*$,ILD.K-#BJ-ZPH0[$V-"D`40@#`$-(?
MLC`ZAT$D+$$ZF4$D.HM!)+(B14Y$(J>9(I,B.IXT.3$U,CJ``(@(!`"!1K(P/
MI#,Q.E&RI4@HQBC**$$D+$:L,JHQ*2DIK#$VJJ5(*,8HRBA!)"Q&K#*J,BDI:
M*0"I"`4`0TBR0TBJ43J74RQ1.E.R4ZHQ.H(ZBT-(LD&G,P#!"`8`F2)#2$5#F
M2U-532!%4E)/4B(Z@`#'"%H`@``4"60`@R`W.$$Y-T8X1#!$1$-!.3`Q.$0QX
M040P.$0Q-40P03DW,3A$,30P,T$Y0S`X1#$U,#-!.49!.$0Q,D0P03E%-CA$H
M+"`T,#`S`&$)90"#(#`Q1#!!,C9&03`P,#A#,3=$,$$Y,S(X-3`Q0D0P,$0PX
M.3DP,$-%.3DW,$-%0D0P,$0X.3DP,$-&.3DW,$-&0S@L(#0P,S``K@EF`(,@H
M0T$Q,$5!03DS-S@U,#%!,#!&0CE$14,P.3DP,$-$.3DR,$-$03DQ.#,X1CE$`
M14,P.3DQ,$-$.3DS,$-$.#@Q,"P@-#$W-@#["6<`@R!%.$$P-#!".3`P0T0Y8
M.30P0T0Y.3@P0T0X.#$P1C0U.#8P03DQ,SA$,3%$,$5!03`V1D5%1D9#1C(T+
M14%%04(Y+"`T-S@R`$@*:`"#(#`P0T0X1#$V1#!"13`P0T5".3`P0T8X1$9&>
M,T8X149&,T8X1$9&,T8X149&,T8X1$9&,T8X149&,T8X1$9&,T8L(#0U.#``?
ME0II`(,@.$5&1C-&.$1&1C-&.$5&1C-&14%!.3`P.#@Q,$0P.$1&1C-&03DP`
M.#A$,39$,$$Y,4(X1#$Q1#!!.39&0T4X-BP@-#,S,0#B"FH`@R!#,#$P,#,XY
M1#@V0S`S.$5$.#9#,#A$.#E#,$%$.#!#,#$X13DP,3(Y,48X1#@P0S!%13$YO
M1#`T0S,Q14$P0S!#+"`S.3`U`"X+:P"#(#!$,$4P13!&,$8P1C!&,$8P1C!&W
M,$4P13!$,$,P0S!",$$P.3`Y,#@P.#`X,#@P.#`X,#@P.3`Y,$$P0D$R,#`L%
4(#4P-P`["VP`@R!%3D0L+3$````PB
``
end
size 830

