BFLI - New graphics modes 2 by Pasi 'Albert' Ojala One day I was watching some demos that used linecrunch routines for whole-screen multicolor-graphics upscrollers. I already had my theories about how and why linecrunch worked, but because I had not used it anywhere, the details were a bit vague. In fact, I have many times accidentally created linecrunch effects when trying to do something else with $D011. Probably every demo coder has. But you learn by doing. I had the idea of using linecrunch for FLI instead of a simple multicolor picture as it always seemed to be used. However, this has probably been done before and because I don't like to do things that have been done before, I decided to use linecrunch to show a two-screen-tall FLI picture. _Linecrunch Basics_ For those not familiar with linecrunch routines: linecrunch is used to scroll the screen UPWARDS by convincing VIC-II that it has already showed more character rows than it in reality has shown. Surprisingly (or then, maybe not :) this consists of fiddling with $D011. The timing is critical as always. Linecrunch works by setting $D011 equal the line before the current line and VIC-II will happily think that it is time to move on to the next character row - add 40 to the video matrix counter, 320 to the graphics memory counter and be ready to start a bad line. Or, maybe 'NOT to go back to the current row' would be a more suitable description. (Programming VIC-II is slowly becoming a science.) The required timing also does not cause bad lines so that you can skip another line immediately on the successive line. In addition, lines can be skipped only after the first character row and half of the second character row have been displayed. This has something to do with the way VIC-II decides when there is a bad line. Because linecrunch causes VIC-II to skip rows, it will run out of video matrix and color memory (and graphics memory) before reaching the end of the screen. However, VIC-II does not stop displaying the graphics nor does it reset the internal counters. The counters keep on running and wrap around instead. Normally, when VIC-II is displaying the last character row, it is showing the memory from offsets $3c0 to $3e7. If VIC-II has skipped one character row, it is displaying from $3e8 to $40f instead. But, there are only 10 bits for the video matrix counter (0..1023), so it wraps around to zero after $3ff. This means that the beginning of the video matrix is displayed at the bottom of the screen. The character rows become shifted by 24 character positions to the right because there were originally 24 unused memory locations at the end of the memory (1000..1023). (To be honest, sprite image pointers are not unused memory, but they are not used with normal FLI.) ____________________ ____________________ |abcdefghijklmnopqrst| |abcdefghijklmnopqrst| | | |--------------------| <- Skipped row : : : : : : : : : : : : | | |normally last line | |normally last line | |XXXXXXXXZZZZabcdefgh| `--------------------' `--------------------' X = unused mem (1000..1015) Z = sprite pointers (1016..1023) Figure 1: Linecrunch The same thing happens for color memory because it uses the same counter for addressing the memory (in fact, color memory access and character data access are performed simultaneosly, 12 bits at a time). The graphics memory behaves the same way, except that the counter has three bits more and it counts at eight times the speed, so that it wraps at the exact same time as the other counter. The first character row can't be used for linecrunch and the second one is also lost in the process. The first usable line to display is the third character row. However, those two lost rows can still be used as an extension at the end of the first screen. You must notice, however, that the alignment has been changed. After these two rows have been displayed, the video bank is switched to get new fresh data on the screen. _Back to BFLI_ Wrapped data is nothing difficult to work with. It is just the matter of writing the right conversion program. Also, the normal FLI routine can be used, we just have to make sure VIC always has the right bank visible - simple LDA bank,x:sta $DD00 can accomplish that. The more difficult aspect is to make the display freely locatable. We have 32 kilobytes of graphics data, this is the main reason we can't even think about using copying. Linecrunch combined with the bad line delaying technique will do the job much more nicely. Figure 2 shows the principles. To make things simpler I have chosen location 0 to mean that the top of the picture is visible, 1 means that the picture is scrolled one line upwards and so on. We can see that linecrunch is not used at all for the location 0. To make the picture start at the same point whether linecrunch has crunched lines or not we compensate the non-lost raster lines by delaying the next bad line. When the location is n*8 (n=0,1,2..), the sum of the linecrunched and delayed lines is constant - the graphics display always starts at the same point. Then how do we deal with the location values that are not evenly dividable by eight ? Now, lets assume that the location is L, and we have C, which is the location divided by eight (C = L/8), and R, which is the remainder (R = L%8). To make the picture scroll to the right position, we need to delay the bad line less than before - R lines less for location L than for location C*8. E.g. for location 2 we delay the bad line two lines less than for location 0. This also shows that we need 7 lines more than is needed for to compensate for the linecrunch. Determining the number of linecrunch lines is a recursive process, because when you use more linecrunch lines, that decreases the number of lines you have available for the display and you need bigger range for the location value. The linecrunch can be started after 12 lines, and we need at least 7 lines to use the soft y-scroll. This makes 181 lines available for the display originally. Because we need to show 400 lines of graphics, we would need (400-181)/8=28 linecrunch lines. However, this in turn reduces the number of lines we have for graphics to 181-28=153 and we need (400-153)/8=31 linecrunch lines. Again, 181-31 is 150. We get (400-150)/8=32 and there it finally converges and we have 149 lines for graphics, which makes location values 0..251 valid. Location 0 1 2 .. 8 9 .. 251 ___________________.. ___________.. ________ ___________________.. ___________.. ________ Linecrunch -------------------.. ___________.. ^ ^ ^ | | | ^ ^ | | | | | Bad line delayed| | | | | | | | | | ======== | | v | | 244 | v ___.. | v : v ________0 v ___.. : Gfx Enabled ________0_______1__.. ________8__.. 250_____ 0 1 2 8 9 251 1 2 3 9 10 252 2 3 4 10 11 253 3 4 5 11 12 254 4 5 6 12 13 255 5 6 7 13 14 256 6 7 8 14 15 257 7 8 9 15 16 258 : : : : : : : : : : : : 148 149 150.. 156 157.. 399 Figure 2: Linecrunch and DMA delay in BFLI (Graphics lines not in scale) _Clipping added_ Now we can scroll the picture to any location we want, but the top of the picture is not clipped and it is very annoying to watch. We need to enable the graphics at the same point regardless of the y-scroll value. The answer is in the extended color mode (ECM). When both ECM and multicolor mode (MCM) are selected, VIC-II will turn the display to black. This is because there is a conflicting situation and it just can't decide which color scheme to use. The video accesses will continue to happen just like before, the data is just not displayed. When the ECM bit is cleared again, the normal multicolor graphics is shown. So, we set the ECM bit and start to display the first eight lines of the FLI. Because the FLI routine already writes to $D011, we just make sure the ECM bit is set in the first R number of writes to $D011 and zero in all other. The viewer is now 'complete'. You can take a look at the code below or you can get C64Gfx1_4.lha and see it in action yourself and not just rely on my word. The package includes converter programs for BFLI, FLI and Koala (ANSI-C), couple of example pictures and viewers for PAL and NTSC machines. -Pasi 'Albert' Ojala albert@cs.tut.fi -------------------------------------------------------------------------- BFLI viewer program for PAL machines UPOS = $C00 ; temporary area for tables BANK = $D00 ; UPOS for linecrunch, BANK for FLI bank select RASTER = 29 ; where to position the sprite -> IRQ 20 lines later DUMMY = $FFF ; dummy location for timing purposes FLISZ = 19-1 ; visible FLI size in character rows - 1 *= $810 SEI LDA #$7F:STA $DC0D ; IRQ setup LDA #1:STA $D01A STA $D015:STA KEYW+1 LDA #IRQ:STA $315 LDA #RASTER:STA $D001:CLC:ADC #20:STA $D012 LDA #0:STA $D017 LDA #0:STA 2 JSR NEWPOS ; Init the FLI routines LDA #$A:STA $D011 ; Blank screen LDX #23 ; Init tables BLOOP LDA #$94:STA BANK,X LDA #$96:STA BANK+24,X DEX:BPL BLOOP LDX #15 LOOP0 LDA YINIT,X:AND #$77 ; Change to $37 to better see the STA UPOS,X ; workings of the routines STA UPOS+16,X STA UPOS+32,X DEX:BPL LOOP0 LDA #$34:STA 1 ; Copy to the last video bank LDA #$80:STA SRC+2 ; from $8000-$BFFF to $C000-$FFFF LDA #$C0:STA DST+2 LDX #0:LDY #$40 SRC LDA $8000,X DST STA $C000,X INX:BNE SRC INC SRC+2:INC DST+2 DEY:BNE SRC LDA #$37:STA 1 LDX #0 ; Init color memory LP LDA $3C00,X:STA $D800,X ; All 1024 bytes are used LDA $3D00,X:STA $D900,X ; - some even twice! LDA $3E00,X:STA $DA00,X LDA $3F00,X:STA $DB00,X INX:BNE LP LDA $DC0D:CLI KEYW LDX #0:BNE KEYW ; Wait for space to be pressed SEI ; System to normal LDA #$37:STA 1 JSR $FDA3 LDA #$97:STA $DD00 JSR $E5A0 LDY #3 IRQL LDA $FD30,Y:STA $314,Y DEY:BPL IRQL LDX #0:LDA #1 ; Clear color memory CLL STA $D800,X:STA $D900,X STA $DA00,X:STA $DB00,X INX:BNE CLL CLI:RTS YINIT BYT $78,$79,$7A,$7B,$7C,$7D,$7E,$7F BYT $78,$79,$7A,$7B,$7C,$7D,$7E,$7F *=*-<*+256 IRQ LDA #$18:STA $D016:LDX #0:LDA #$5A INC DUMMY:DEC DUMMY ; Synchronization STX $D020:STX $D021:STA $D011 LDA #$15:STA $D018 LDA #$97:STA $DD00 LDX #44 ; Wait for the 4th line LL DEX:BPL LL:NOP LDX #0 LOOP3 NOP ; Linecrunch-part routine LDA UPOS+6,X:INC DUMMY:STA $D011 NOP:NOP:INC DUMMY NOP:NOP:NOP:NOP:NOP NOP:NOP:NOP:NOP:NOP NOP:NOP:NOP:NOP:NOP INX E1 CPX #$10:BNE LOOP3 ; Skip that many character rows-4 BIT $EA LOOP4 LDA UPOS,X:INC DUMMY:STA $D011 NOP:NOP:NOP:INC DUMMY NOP:NOP:NOP:NOP:NOP NOP:NOP:NOP:NOP:NOP NOP:NOP:NOP:NOP:LDA #0 INX E2 CPX #$1F:BNE LOOP4 ; Delay DMA until we are at the ; 'same place' each time LDA #0:STA $D020 ; Now wait for the bad line and start FLI BIT $EA:NOP NOP:NOP:NOP:NOP NOP:NOP:NOP:NOP NOP:NOP:NOP:NOP B0 LDA #$92:STA $DD00:NOP ; The right video bank ; Wait for 0-7 lines to set the ECM mode off ; (makes the graphics visible) F0 LDA #0:STA $D011:LDA #$08:STA $D018:NOP:NOP:NOP:NOP:BIT $EA F1 LDA #0:STA $D011:LDA #$18:STA $D018:NOP:NOP:NOP:NOP:BIT $EA F2 LDA #0:STA $D011:LDA #$28:STA $D018:NOP:NOP:NOP:NOP:BIT $EA F3 LDA #0:STA $D011:LDA #$38:STA $D018:NOP:NOP:NOP:NOP:BIT $EA F4 LDA #0:STA $D011:LDA #$48:STA $D018:NOP:NOP:NOP:NOP:BIT $EA F5 LDA #0:STA $D011:LDA #$58:STA $D018:NOP:NOP:NOP:NOP:BIT $EA F6 LDA #0:STA $D011:LDA #$68:STA $D018:NOP:NOP:NOP:NOP:BIT $EA F7 LDA #0:STA $D011:LDA #$78:STA $D018 LDX #FLISZ:NOP:NOP:NOP:BIT $EA ; Do FLI 18 more character rows F8 LDA #0:STA $D011:LDA #$08:STA $D018 B1 LDA BANK,X:STA $DD00:BIT $EA F9 LDA #0:STA $D011:LDA #$18:STA $D018:NOP:NOP:NOP:NOP:BIT $EA FA LDA #0:STA $D011:LDA #$28:STA $D018:NOP:NOP:NOP:NOP:BIT $EA FB LDA #0:STA $D011:LDA #$38:STA $D018:NOP:NOP:NOP:NOP:BIT $EA FC LDA #0:STA $D011:LDA #$48:STA $D018:NOP:NOP:NOP:NOP:BIT $EA FD LDA #0:STA $D011:LDA #$58:STA $D018:NOP:NOP:NOP:NOP:BIT $EA FE LDA #0:STA $D011:LDA #$68:STA $D018:NOP:NOP:NOP:NOP:BIT $EA FF LDA #0:STA $D011:LDA #$78:STA $D018:NOP:NOP:DEX:BMI EFLI:JMP F8 EFLI NOP LDA #$FC WL CMP $D012:BNE WL INC DUMMY:INC DUMMY:INC DUMMY:INC DUMMY INC DUMMY:INC DUMMY:INC DUMMY:INC DUMMY INC DUMMY:INC DUMMY:STA $D020 JSR NEWPOS ; Update the location JSR CHPOS ; Change to a new location LDA $DC01:AND #$10:BNE OV3 ; Check for the space bar LDA #0:STA KEYW+1 OV3 LDX #$53:STX $D011:INC $D019:JMP $EA81 NEWPOS LDA #0 ; Init the IRQ routine for this position LSR:LSR:LSR:CLC:ADC #4:STA E1+1 LDA #7:SEC:SBC NEWPOS+1:AND #7:TAX:TAY:CLC:ADC #35:STA E2+1 LDA UPOS+3+7,Y:DEX:BMI J0:AND #$3F J0 STA F7+1:AND #$3F:STA FF+1 LDA UPOS+3+6,Y:DEX:BMI J1:AND #$3F J1 STA F6+1:AND #$3F:STA FE+1 LDA UPOS+3+5,Y:DEX:BMI J2:AND #$3F J2 STA F5+1:AND #$3F:STA FD+1 LDA UPOS+3+4,Y:DEX:BMI J3:AND #$3F J3 STA F4+1:AND #$3F:STA FC+1 LDA UPOS+3+3,Y:DEX:BMI J4:AND #$3F J4 STA F3+1:AND #$3F:STA FB+1 LDA UPOS+3+2,Y:DEX:BMI J5:AND #$3F J5 STA F2+1:AND #$3F:STA FA+1 LDA UPOS+3+1,Y:DEX:BMI J6:AND #$3F J6 STA F1+1:AND #$3F:STA F9+1 LDA UPOS+3+0,Y:DEX:BMI J7:AND #$3F J7 STA F0+1:AND #$3F:STA F8+1 LDA #$96:STA B0+1:LDA #199:SEC:SBC NEWPOS+1:BCC OV2 LSR:LSR:LSR:CLC:ADC #5:STA B1+1 RTS OV2 LDA #0:STA B1+1:LDX #$94:STX B0+1:RTS CHPOS LDX NEWPOS+1 LDA $DC00:TAY ; Get joystick AND #$10:BNE DIR ; If no button pressed TYA:AND #1:BEQ UP ; If joy up TYA:AND #2:BEQ DOWN ; If joy down RTS DIR LDA #0:BEQ UP DOWN DEX:CPX #$FF:BNE DOK LDX #0:STX DIR+1 ; Change direction DOK STX NEWPOS+1:RTS UP INX:CPX #$FD:BCC UOK ; 251(locations)+149(visible)=400 LDX #$FC:STX DIR+1 ; Change direction UOK STX NEWPOS+1:RTS -------------------------------------------------------------------------- The BFLI file format: File BFLI Display Lines Offset Offset Lines Size Colors 0-1.3 0..55 944..999 22.7-24 56 I 1.3-2 56..79 - 24 2-24 80..999 0..919 0-22 920 24-24.7 1000..1023 920..943 22-22.7 24 II 0-1.3 0..55 1968..2024 49.3-50.6 56 1.3-24.7 56..1023 1000..1967 24-49.3 968 Gfx 0-1.3 0..447 7552..7999 22.7-24 448 I 1.3-2 448..639 - 192 2-24 640..7999 0..7359 0-22 7360 24-24.7 8000..8191 7360..7551 22-22.7 192 II 0-1.3 0..447 15744..16192 49.3-50.6 448 1.3-24.7 448..8191 8000..15743 24-49.3 7744