1 \ ******************************************************************
\r
5 \ * Simple demonstration of how to write self-relocating code
\r
6 \ * in BeebAsm, using the new 'reload address' feature of SAVE.
\r
8 \ * This uses the 'star globe' demo as a base.
\r
10 \ ******************************************************************
\r
15 NATIVE_ADDR = &300 ; address at which code will run
\r
16 RELOAD_ADDR = &1100 ; address at which code will load
\r
18 OFFSET = RELOAD_ADDR - NATIVE_ADDR
\r
25 timerlength = 64*8*26
\r
26 debugrasters = FALSE
\r
29 \\ Define some zp locations
\r
45 \\ Set start address
\r
50 \ ******************************************************************
\r
51 \ * The start of the demo 'proper', after it has been relocated
\r
52 \ ******************************************************************
\r
69 \\ Enable interrupts, ready to start the main loop
\r
73 \\ First we wait for 'vsync' so we are synchronised
\r
76 LDA vsync:BEQ initialwait:LDA #0:STA vsync
\r
78 \\ Enable the screen
\r
80 LDA #6:STA &FE00:LDA #32:STA &FE01
\r
82 \\ This is the main loop!
\r
86 \\ Plot every dot on the screen
\r
92 ; setup y pos ready for plot routine
\r
98 CLC:LDA dotx,X:ADC angle+1:TAY
\r
99 CLC:ADC #64:STA behindflag
\r
101 ; get colour from sin index
\r
103 LDA coltable,Y:STA colour
\r
105 ; perform sin(x) * radius
\r
106 ; discussion of the multiplication method below in the table setup
\r
108 SEC:LDA sintable,Y:STA temp:SBC dotr,X
\r
109 BCS noneg:EOR #&FF:ADC #1:.noneg
\r
110 CPY #128:TAY:BCS negativesine
\r
112 CLC:LDA dotr,X:ADC temp:TAX
\r
113 BCS morethan256:SEC
\r
114 LDA multtab1,X:SBC multtab1,Y:JMP donemult
\r
116 LDA multtab2,X:SBC multtab1,Y:JMP donemult
\r
119 CLC:LDA dotr,X:ADC temp:TAX
\r
120 BCS morethan256b:SEC
\r
121 LDA multtab1,Y:SBC multtab1,X:JMP donemult
\r
123 LDA multtab1,Y:SBC multtab2,X
\r
126 CLC:ADC #64:STA xpos
\r
128 ; routine to plot a dot
\r
129 ; also we remember the calculated screen address in the dot tables
\r
131 LDA ypos:LSR A:LSR A:AND #&FE
\r
133 LDA xpos:AND #&FE:ASL A:ASL A
\r
135 LDY counter:STA olddotaddrlo,Y
\r
136 TXA:ADC #&40:STA write+1:STA olddotaddrhi,Y
\r
137 LDA ypos:AND #7:STA olddotaddry,Y:TAY
\r
138 LDA xpos:LSR A:LDA colour:ROL A:TAX
\r
142 BIT behindflag:BMI behind
\r
144 ; if the dot is in front, we double its size
\r
146 DEY:BPL samescreenrow
\r
147 DEC write+1:DEC write+1:LDY #7:.samescreenrow
\r
153 ; loop to the next dot
\r
160 \\ Wait for VSync here
\r
164 LDA #&00 + PAL_magenta:STA &FE21
\r
167 LDA vsync:BEQ waitingforvsync
\r
168 CMP #2:BCS exit ; insist that it runs in a frame!
\r
171 \\ Now delete all the old dots.
\r
172 \\ We actually do this when the screen is still rasterising down..!
\r
176 LDY olddotaddrlo,X:STY write
\r
177 LDY olddotaddrhi,X:STY write+1
\r
180 DEY:BPL erasesamerow
\r
181 DEC write+1:DEC write+1:LDY #7:.erasesamerow
\r
187 LDA #&00 + PAL_red:STA &FE21
\r
192 CLC:LDA angle:ADC speed:STA angle
\r
193 LDA angle+1:ADC speed+1:STA angle+1
\r
195 \\ Check keypresses
\r
197 LDA #66:STA &FE4F:LDA &FE4F:BPL notx
\r
198 CLC:LDA speed:ADC #16:STA speed:BCC notx:INC speed+1:.notx
\r
199 LDA #97:STA &FE4F:LDA &FE4F:BPL notz
\r
200 SEC:LDA speed:SBC #16:STA speed:BCS notz:DEC speed+1:.notz
\r
201 LDA #112:STA &FE4F:LDA &FE4F:BMI exit
\r
205 \\ Exit - in the least graceful way possible :)
\r
212 \ ******************************************************************
\r
214 \ ******************************************************************
\r
217 LDA &FE4D:AND #2:BNE irqvsync
\r
219 LDA #&40:STA &FE4D:INC vsync
\r
221 LDA #&00 + PAL_blue:STA &FE21
\r
227 LDA #LO(timerlength):STA &FE44
\r
228 LDA #HI(timerlength):STA &FE45
\r
230 LDA #&00 + PAL_black:STA &FE21
\r
237 \ ******************************************************************
\r
238 \ * Colour table used by the plot code
\r
239 \ ******************************************************************
\r
242 EQUB &00, &00 ; black pixels
\r
243 EQUB &02, &01 ; blue pixels
\r
244 EQUB &08, &04 ; red pixels
\r
245 EQUB &0A, &05 ; magenta pixels
\r
246 EQUB &20, &10 ; green pixels
\r
247 EQUB &22, &11 ; cyan pixels
\r
248 EQUB &28, &14 ; yellow pixels
\r
249 EQUB &2A, &15 ; white pixels
\r
252 \ ******************************************************************
\r
254 \ ******************************************************************
\r
256 ; contains ABS sine values
\r
257 ; we don't store the sign as it confuses the multiplication.
\r
258 ; we can tell the sign very easily from whether the index is >128
\r
260 ALIGN &100 ; so we don't incur page-crossed penalties
\r
263 EQUB ABS(SIN(n/128*PI)) * 255
\r
267 \ ******************************************************************
\r
269 \ ******************************************************************
\r
274 EQUB (SIN(n/128*PI) + 1) / 2.0001 * 7 + 1
\r
278 \ ******************************************************************
\r
279 \ * multiplication tables
\r
280 \ ******************************************************************
\r
282 ; This is a very quick way to do multiplies, based on the fact that:
\r
284 ; (a+b)^2 = a^2 + b^2 + 2ab (I)
\r
285 ; (a-b)^2 = a^2 + b^2 - 2ab (II)
\r
287 ; (I) minus (II) yields: (a+b)^2 - (a-b)^2 = 4ab
\r
289 ; or, rewritten: ab = f(a+b) - f(a-b),
\r
290 ; where f(x) = x^2 / 4
\r
292 ; We build a table of f(x) here with x=0..511, and then can perform
\r
293 ; 8-bit * 8-bit by 4 table lookups and a 16-bit subtract.
\r
295 ; In this case, we will discard the low byte of the result, so we
\r
296 ; only need the high bytes, and can do just 2 table lookups and a
\r
297 ; simple 8-bit subtract.
\r
310 \ ******************************************************************
\r
312 \ ******************************************************************
\r
314 ; contains the phase of this dot
\r
318 FOR n, 0, numdots-1
\r
323 ; contains the y position of the dot
\r
324 ; the dots are sorted by y positions, highest on screen first - this means we can do
\r
325 ; 'raster chasing'!
\r
326 ; the y positions are also biased so there are fewer at the poles, and more at the equator!
\r
330 FOR n, 0, numdots-1
\r
331 x = (n - numdots/2 + 0.5) / (numdots/2)
\r
332 y = (x - SIN(x*PI) * 0.1) * radius
\r
337 ; contains the radius of the ball at this y position
\r
341 FOR n, 0, numdots-1
\r
342 x = (n - numdots/2 + 0.5) / (numdots/2)
\r
343 y = (x - SIN(x*PI) * 0.1) * radius
\r
344 r = SQR(radius*radius - y*y) / 2
\r
349 \ ******************************************************************
\r
350 \ * This is the end of the main native block of code
\r
351 \ ******************************************************************
\r
355 \ ******************************************************************
\r
356 \ * The entry point of the demo
\r
357 \ * This relocates the code to its 'real' address, and can also
\r
358 \ * do one-time initialisation, i.e. code we can chuck away afterwards.
\r
360 \ * Since this is the relocation code, it has to go at the very end of
\r
361 \ * the executable.
\r
363 \ * This code will be running at its assemble address + OFFSET,
\r
364 \ * so we have to patch up any absolute address references accordingly.
\r
365 \ ******************************************************************
\r
370 \\ Set up hardware state and interrupts
\r
373 LDX #&FF:TXS ; reset stack
\r
374 STX &FE44:STX &FE45
\r
375 LDA #&7F:STA &FE4E ; disable all interrupts
\r
376 STA &FE43 ; set keyboard data direction
\r
377 LDA #&C2:STA &FE4E ; enable VSync and timer interrupt
\r
378 LDA #&0F:STA &FE42 ; set addressable latch for writing
\r
379 LDA #3:STA &FE40 ; keyboard write enable
\r
380 LDA #0:STA &FE4B ; timer 1 one shot mode
\r
381 LDA #LO(irq):STA &204
\r
382 LDA #HI(irq):STA &205 ; set interrupt handler
\r
384 \\ Set up CRTC for MODE 2
\r
389 LDA crtcregs + OFFSET,X ; PATCHED ADDRESS
\r
394 \\ Set up video ULA for MODE 2
\r
399 \\ Set up palette for MODE 2
\r
403 LDA paldata + OFFSET,X ; PATCHED ADDRESS
\r
412 LDA #0:STA angle:STA angle+1
\r
419 LDX #HI(RELOC_START-START)
\r
426 INC relocloop+OFFSET+2 ; PATCHED ADDRESS
\r
427 INC relocloop+OFFSET+5 ; PATCHED ADDRESS
\r
434 \ ******************************************************************
\r
435 \ * Values of CRTC regs for MODE 2
\r
436 \ ******************************************************************
\r
439 EQUB 127 ; R0 horizontal total
\r
440 EQUB 64 ; R1 horizontal displayed - shrunk a little
\r
441 EQUB 91 ; R2 horizontal position
\r
442 EQUB 40 ; R3 sync width
\r
443 EQUB 38 ; R4 vertical total
\r
444 EQUB 0 ; R5 vertical total adjust
\r
445 EQUB 0 ; R6 vertical displayed
\r
446 EQUB 34 ; R7 vertical position
\r
447 EQUB 0 ; R8 interlace
\r
448 EQUB 7 ; R9 scanlines per row
\r
449 EQUB 32 ; R10 cursor start
\r
450 EQUB 8 ; R11 cursor end
\r
451 EQUB HI(&4000/8) ; R12 screen start address, high
\r
452 EQUB LO(&4000/8) ; R13 screen start address, low
\r
455 \ ******************************************************************
\r
456 \ * Values of palette regs for MODE 2
\r
457 \ ******************************************************************
\r
459 PAL_black = (0 EOR 7)
\r
460 PAL_blue = (4 EOR 7)
\r
461 PAL_red = (1 EOR 7)
\r
462 PAL_magenta = (5 EOR 7)
\r
463 PAL_green = (2 EOR 7)
\r
464 PAL_cyan = (6 EOR 7)
\r
465 PAL_yellow = (3 EOR 7)
\r
466 PAL_white = (7 EOR 7)
\r
469 EQUB &00 + PAL_black
\r
470 EQUB &10 + PAL_blue
\r
472 EQUB &30 + PAL_magenta
\r
473 EQUB &40 + PAL_green
\r
474 EQUB &50 + PAL_cyan
\r
475 EQUB &60 + PAL_yellow
\r
476 EQUB &70 + PAL_white
\r
481 \ ******************************************************************
\r
482 \ * End address to be saved
\r
483 \ ******************************************************************
\r
487 \ ******************************************************************
\r
488 \ * Save the code, before the following data overlay clears it again
\r
489 \ ******************************************************************
\r
491 SAVE "Code", START, RELOC_END, RELOC_START+OFFSET, RELOAD_ADDR
\r
496 \ ******************************************************************
\r
497 \ * Start a new overlay:
\r
499 \ * This is overlapped with the relocation code above, because it
\r
500 \ * will already have been thrown away by the time these tables are
\r
503 \ * These tables are filled at run-time, hence we just define their
\r
504 \ * addresses, we don't need to save anything.
\r
505 \ ******************************************************************
\r
507 CLEAR END, RELOC_END
\r
510 ; these store the screen address of the last dot
\r
511 ; at the end of the frame, we go through these tables, storing zeroes to
\r
512 ; all these addresses in order to delete the last frame
\r
515 .olddotaddrlo SKIP numdots
\r
518 .olddotaddrhi SKIP numdots
\r
521 .olddotaddry SKIP numdots
\r