;; This example shows how to make a vertical split/rupture.
;;
;; A vertical split/rupture is used to divide the display (in the vertical dimension)
;; into more than one block. Each block spans the entire width of the display,
;; can have a programmable height and it's own start address.
;; Therefore parts of the screen can be scrolled while other parts remain static.
;; The split is another method to make overscan, but the split must be refreshed
;; every frame for the effect to be maintained. The split can also require exact
;; timing.
;;
;; When making a split, be aware of the differences in the CRTC models used by Amstrad.
;; (HD6845S/UM6845 (type 0), UM6845R (type 1), MC6845 (type 2), CPC+ CRTC inside ASIC
;; (type 3), CPC CRTC inside Pre-ASIC in cost-down CPCs (type 4)).
;; You will certainly notice the difference between these CRTCs when you program splits.
;; 
;; The split is not a new effect, it has been used in some games (i.e. Mission Genocide, Octoplex
;; Prehistorik 2, Super Cauldron and Snowstrike, to name a few), and many demos.
;; This effect was made popular by demo groups such as Logon System.
;; 
;; This method is technical and requires good understanding of the operation
;; of the CRTC.
;;
;; Extensive comments have been included to explain the operation of the split
;; and the reasons for every CRTC register write.
;;
;; Abreviations used:
;;
;; VADJC = internal Vertical Adjust Count register of the CRTC
;; VC = internal Vertical Count register of the CRTC
;; RC = internal Raster Count register of the CRTC
;; HC = internal Horizontal Count register of the CRTC
;; HTOT = Horizontal total register of CRTC (register 0)
;; HDISP = Horizontal displayed register of CRTC (register 1)
;; VTOT = Vertical total register of CRTC (register 4)
;; VSYNCPOS = Vertical sync position register of CRTC (register 7)
;; VSYNC = The vertical sync signal. This can be monitored through PPI port B.
;; HSYNCWIDTH = Horizontal sync width (programmed by register 3 of the CRTC)
;; HSYNCPOS = Horizontal sync position register of CRTC (register 2)
;; HDISP = Horizontal displayed register of CRTC (register 1)
;; VADJ = Vertical adjust register of CRTC (register 5)
;; MR = Maximum raster register of CRTC (register 9)
;;
;; (c) Kevin Thacker, 2002
;;
;; This code has been released under the GNU Public License V2.

org &4000
nolist

;;------------------------------------------------
;; install a interrupt handler

di						;; disable interrupts
im 1					;; interrupt mode 1 (CPU will jump to &0038 when a interrupt occrs)
ld hl,&c9fb				;; C9 FB are the bytes for the Z80 opcodes EI:RET
ld (&0038),hl			;; setup interrupt handler

;; the interrupt handler we have setup is minimal:
;;
;; EI
;; RET
;; 
;; This will re-enable interrupts and then return to program control.
;; 
;; We know the CPU time taken for the interrupt handler because we have defined
;; the interrupt handler and the timing of the instructions is known.
;;

ei						;; re-enable interrupts
;;--------------------------------------------------------------------------


;;------------------------------------------------
;; define the horizontal and vertical sync widths
;; 
;; the vertical sync width is programmable on CRTC type 0, type 3 and type 4.
;; the vertical sync width is fixed on CRTC type 1 and type 2.
;;
;; the horizontal sync width is programmable on all CRTC types.
;;

ld bc,&bc03							;; select vertical and horizontal sync register of the CRTC
out (c),c
ld bc,&bd00+8						;; set vertical sync width = 16, horizontal sync width = 8
out (c),c

;;------------------------------------------------
;; define the horizontal sync position
;; 
;; With the vertical split, this is used to position the screen horizontally
;; within the display. Increasing this value will move the screen to the left
;; decreasing this value will move the screen to the right.
;; 
;; Note that on CRTC type 2, (HSYNCPOS+HSYNCWIDTH)<HTOT otherwise interrupts are not 
;; generated, and the split will not work!
;;
;; This is setup here once as the horizontal position will be the same
;; for all blocks.

ld bc,&bc02							;; select horizontal sync position register of the CRTC
out (c),c
ld bc,&bd00+48						;; set horizontal sync to 48
out (c),c

;;------------------------------------------------
;; define the horizontal displayed
;; 
;; The horizontal displayed defines the number of CRTC characters to display
;; on each CRTC scan line. (Each CRTC character is 2 bytes)
;; 
;; In this example this is setup here once as the horizontal displayed will 
;; be the same for all blocks.

ld bc,&bc01							;; select horizontal displayed register of the CRTC
out (c),c
ld bc,&bd00+48						;; set horizontal sync to 48
out (c),c

;;------------------------------------------------
;; Setup the vertical displayed so that it is larger than 
;; the vertical height of the tallest split block.
;;
;; In this example, this is setup here once as the horizontal displayed 
;; will be the same for all blocks.
;;
;; The vertical and horizontal displayed signals define when
;; the border is shown:
;;
;; - If VDISP<VTOT of any block then border colour will be shown 
;; between the block (where VDISP<VTOT) and the start of the next block.
;;
;; - If HDISP<HTOT of any block then border colour will be shown
;; on the sides of that block (where HDISP<HTOT).

ld bc,&bc06
out (c),c
ld bc,&bdff
out (c),c

;;------------------------------------------------
;; Setup the maximum raster so that it is the same for every split block.
;;
;; The following registers define the height of a split block:
;; - maximum raster register
;; - vertical total register
;; - vertical adjust register
;;
;; The height is computed as ((MR+1))*(VTOT+1))+VADJ  HTOT times.
;;
;; The time for a split block is defined as:
;; (((MR*HTOT)+1)*(VTOT+1))+(VADJ*HTOT)
;;
;; If HTOT is 64, then RC will increment once for every monitor scan-line.
;;
;; VERTICAL ADJUST:
;;
;; The vertical adjust, if defined to be a value other than 0, is activated
;; at the end of the split block it is defined for. The VADJC will increment
;; until it's value matches the programmed vertical adjust. Therefore the split block
;; is extended by (HTOT*VADJ) time.
;;
;; In this example where every HTOT is the duration of a monitor scan-line,
;; vertical adjust will count additional monitor scan-lines.
;;
;; MAXIMUM RASTER ADDRESS:
;; 
;; The maximum raster adjust defines the number of times RC must increment
;; for each CRTC character row.
;;
;; In this example where every HTOT is the duration of a monitor scan-line,
;; the maximum raster address defines the height of the CRTC character row in
;; monitor scan-lines.
 
ld bc,&bc05									;; select vertical adjust register of CRTC
out (c),c
ld bc,&bd00									;; 0 scan-lines of vertical adjust
out (c),c

ld bc,&bc09									;; select maximum raster register of CRTC
out (c),c
ld bc,&bd07									;; 8 scan-lines per CRTC character row
out (c),c

;;----------------------------------------------------
;; this main loop is executed every frame to maintain
;; the split. The timing required to maintain this split is not 
;; too important because the split is simple. If we are attempting
;; a more complex split, then the timing is often critical.


.main_loop

;;----------------------------------------------------
;; wait for the start of the vsync
;;
;; the position of the vsync for the first time this code is executed,
;; and then subsequent executions will be different.
;;
;; The position of the first vsync is defined by the initial CRTC values
;; when this code is first entered.
;; 
;; The position of subsequent vsyncs is determined by our vertical split code.
;;
;; (Note: This code will also recognise when a VSYNC is already in progress.
;; It will only see the start of a VSYNC if the VSYNC was inactive before this
;; checking loop is started. 
;;
;; If the test is entered when the VSYNC has already started then our split
;; could be unstable as we may not always be synchronised to the CRTC at the same
;; point each frame. If the code requires the timing to be accurate, and we miss the 
;; start of the VSYNC, then the CRTC registers we program (to create the split) could
;; be setup at the wrong time and the split will break).

ld b,&f5				;; B = I/O address of PPI port B
.vsync
in a,(c)				;; read PPI port B input
rra						;; transfer bit 0 into carry
jr nc,vsync				;; if carry=0 then vsync= 0 (inactive),
						;; if carry=1 then vsync=1 (active).

;;--------------------------------------------------
;; at this point we have seen the start of a vsync,
;; and are synchronised (but not exactly) to
;; the start of it.
;;
;;
;; (If we detected the start of the VSYNC, then we also know when the next interrupt
;; will occur.
;; 
;; The interrupt counter is updated every HSYNC.
;; The interrupt counter reset is synchronised to the start of the VSYNC.
;; A interrupt request is issued when the interrupt counter reaches 52.
;; 
;; The next interrupt could occur in two HSYNC times, assuming that
;; the previous interrupt was not serviced less than 32 lines ago.
;;
;; Otherwise the next interrupt will occur in 52+2 HSYNC times.
;;
;; A perfect split relies on predicting the position of the start of the VSYNC
;; and the position of the interrupts, as these are the signals we use to
;; synchronise with the display, and this means that we can setup the next split
;; block at the correct position).
;;
;; in our split example, the VSYNC is programmed to occur when 
;; VC = 0, RC = 0. (This trigger point is setup at the end of the main loop).
;; We know that when we have seen the start of the VSYNC the CRTC
;; will be processing VC = 0, RC=0, and HC will be somewhere on this line. 
;; (The value of HC will vary as the VSYNC testing check is accurate to about 8us).
;; 

;;-----------------------------------------------------------------
;; To stop the VSYNC from triggering again, until we want it
;; to, program the vsync position so that it is larger than 
;; the vertical height of the tallest split block.
;;
;; Now Vsync will not be triggered, because VC can never equal the 
;; vertical sync position we have programmed.
;;
;; The CRTC will continue to display the split blocks until we set
;; a valid VSYNC position which can be reached by VC. (e.g. VSYNCPOS<VTOT)

ld bc,&bc07				;; select vertical sync position register of the CRTC
out (c),c
ld bc,&bdff				;; set vertical sync position (255)
out (c),c

;;--------------------------------------------------------------
;; The height of each split block is defined by:
;;
;; - the vertical total register of the CRTC. The register must be reprogrammed for each
;; block.
;; - the maximum raster register of the CRTC.
;; - the vertical adjust register of the CRTC. 
;;
;;
;; In the case where HTOT is 64, the height of the split block is therefore
;; defined in complete monitor scan-lines.
;;
;; For compatibility reasons, the vertical total register should be written
;; when VC<new_VTOT, otherwise the CRTC may finish the current split block and
;; start a new one, and this may not be the effect you want.
;;
;; For a steady split, when HTOT is 64 the frame should use 312 scan-lines.
;; (64 microseconds * 312 crtc scan lines)=19968us.
;;
;; 1/50 second = 0.02 seconds
;; 1/1000000 second = 1 microsecond
;; 
;; 1 microsecond*19968 = 0.019968 seconds = approx 0.02 seconds
;;
;; If more scan-lines are used then the whole display may roll, or the display
;; will flicker.

;;---------------------------------------------------------------
;; set height of first split block. At this point VC<new_VTOT, so first split
;; block will become 5 char lines tall. The split block will
;; be 40 (5*8) scanlines.

ld bc,&bc04				;; select vertical total register of the CRTC
out (c),c
ld bc,&bd00+10-1		;; set height (height-1) of the first split block
out (c),c

;;----------------------------------------------------------------------------
;; HALT = wait for next interrupt to trigger, execute the interrupt handler
;; and continue with program execution. Assumes interrupts are ENABLED!
;;
;; NOTE: we use the interrupts for two purposes:
;; - to sync exactly with the CRTC to an exact point on the screen
;; (This position is determined by the HSYNCPOS, the CPU time required to acknowledge
;; the interrupt and the CPU time required to execute the interrupt handler)
;;
;; - to waste some time, so that we can wait until the appropiate position
;; to setup the split. When using the standard Amstrad CPC interrupts
;; the HALT provides a coarse positioning method.
;;
;; A HALT instruction will execute the equivalent of NOP instructions until
;; a interrupt request occurs. A NOP is one of the fastest instructions taking
;; 1us for each cycle. Therefore, when a interrupt request occurs, there will
;; always be the same time between interrupt request occuring and interrupt
;; request being acknowledge and serviced. This instruction works the best
;; when there is no interrupt request outstanding when the HALT is first executed.)

halt
;; At this point we are synchronised exactly to a predictable position on the screen.
;; We can now use software loops to delay to the exact position we want.

;;-------------------------------------------------------
;; In this example, the previous interrupt was processed greater than 32 lines
;; ago. 
;;
;; If IRQ_ACK is the time between the interrupt being triggered and then acknowledged
;; by the CPU, and IRQ_FUNC is the time for the interrupt handler to execute and
;; return control back to the program, then the interrupt will occur at time:
;;
;; HTOT+HSYNCPOS+IRQ_ACK+IRQ_FUNC
;; 
;; (This assumes that the HSYNCPOS and HTOT remain constant).
;; 
;; When VSYNCPOS=0 then the CRTC should be processing VC=0, RC=1 at the time the
;; interrupt is requested, and by the time we receive control the CRTC should
;; be processing VC=0, RC=2.

;; delay until VC=1.
;; Delay required = 8 scanlines = 8 * 64 us = 512 us
;; 
;; for DJNZ: if b-1==0, instruction will execute in 3us
;; if b-1<>0, instruction will execute in 4us.
;;

ld b,127						;; [2]
.wait1 
djnz wait1						;; [3/4]

;; for this loop:
;; 
;; (14*4)+3+2 = 513 us

;;-----------------------------------------------------------------------------------
;; set the start address of the *next* split block
;;
;; - for CRTC type 0,2,3 and 4, this start address will take effect at the start
;; of the next split block.
;; - for CRTC type 1, it is possible to reprogram the start address of the current
;; split block if VC=0, otherwise this start address will take effect at the start
;; of the next split block.
;; 
;; For compatibility with other CRTC types, attempt to change the start position
;; when VC>0 and VC<(VTOT-1).
;;
;; NOTE: the start address of the first split block is defined at the end of the loop


ld hl,&1000							;; start address in CRTC form (&4000 - &7fff in RAM)
ld bc,&bc0c							;; start address high register of CRTC
out (c),c							;; select start address high register of CRTC
inc b								;; B = &BD
out (c),h							;; write data to start address low register

ld bc,&bc0d							;; select start address low register of CRTC
out (c),c							;; select start address low register of CRTC
inc b								;; B = &BD
out (c),l							;; write data to start address low register

;-----------------------------------------------------------------------------

halt
;; in this example, the CRTC has completed 2 + 52 monitor scanlines
;; since the start of the VSYNC.
;;
;; In this example HTOT and HSYNC remain constant, therefore the amount of time
;; that has passed is:
;;
;; (HTOT+HSYNCPOS)+(HTOT-HSYNCPOS)+(51*HTOT)+HSYNCPOS
;;
;; where (HTOT+HSYNCPOS) is the time to the first interrupt request,
;; (HTOT-HSYNCPOS) is the time from the first interrupt request to the first HTOT
;; after the interrupt request,
;; (51*HTOT)+HSYNCPOS is the time to the second interrupt request.
;;
;; approx 6.75 scanlines 

ld b,15
.wait2 djnz wait2

ld bc,&bc04
out (c),c
ld bc,&bd00+24             
out (c),c

;------------------------------------------------------------------------------
;blk3
halt

;------------------------------------------------------------------------------

halt
ld b,15
.wait4 djnz wait4
ld bc,&bc0c
out (c),c
ld bc,&bd00
out (c),c
ld bc,&bc0d
out (c),c
ld bc,&bd00
out (c),c

halt

halt
ld b,15
.wait6 djnz wait6

ld bc,&bc04
out (c),c
ld bc,&bd00+5 ;5+25+6=36 (nearly 39!)
out (c),c

ld bc,&bc0c
out (c),c
ld bc,&bd00+%00010000 ;top section of screen
out (c),c
ld bc,&bc0d
out (c),c
ld bc,&bd00
out (c),c


;;-------------------------------------------------------------
;; To maintain a steady split we must do the following: 
;; - ensure the register writes to the CRTC occur at the same position every frame
;; - force a VSYNC to be generated once per frame, once per 312 complete (64us) scan-lines
;;  (once every 19968us)
;;
;; If we want a steady split that will work on every CRTC type:
;; - ensure the register writes to the CRTC occur at a time that is compatible with
;; every CRTC

;; reprogram a new vertical sync position to force a VSYNC to be triggered.
;; 
;; We want our VSYNC to start on VC=0, RC=0, HC=0.
;;
;; - If VC<>0 then the VSYNC will be triggered at the start of the next split (VC=0, RC=0, HC=0).
;; - If VC==0, then on some CRTC's a VSYNC will be triggered immediatly (VC=0, RC!=0, HC=??), this
;; could cause a bad split if our split requires that the VSYNC must occur when VC=0, RC=0, HC=0)!
;;

ld bc,&bc07				;; vertical sync position register of the CRTC
out (c),c				;; select vertical sync position of the CRTC
ld bc,&bd00				;; vertical sync position = 0
out (c),c				;; set vertical sync position

;;----------------------------------------
;; continue to loop so that the split is maintained

jp