
**********************************************************************
files: Display.TXT, Display.TAP
       http://www.users.globalnet.co.uk/~jg27paw4/type-ins/zx-comp/elemgrph.zip
       http://www.users.globalnet.co.uk/~jg27paw4/pourri/elemg2_4.gif
       screenslice.asm, screenslice.bas
**********************************************************************


** Display file addresssing; Screen slicing
   ========================================

Read Toni Baker's [url=http://www.users.globalnet.co.uk/~jg27paw4/type-ins/zx-comp/elemgrph.zip]Elementary Graphics[/url] series from ZX Computing; part 2 covers the display file layout. I would draw particular attention to this illustration from Elementary Graphics, part 2:
[center][img]http://www.users.globalnet.co.uk/~jg27paw4/pourri/elemg2_4.gif[/img][/center]
which simply, concisely and clearly analyses the Spectrum's display file addressing, and with the description in the article shows the cleverness in the design of the screen layout and how easy it is to reference any particular display element by selectively modifying the relevant part of the address.

   ========================================

While working on this Compendium I'm updating or rewriting a lot of stuff. One thing I eventually came to appreciate was the simple & clever way in which the Spectrum's display file addressing was designed. So I've done a little screen slicing program in both assembler:

screenslice.asm [code]
;  10 CLEAR 65535-24*32*8-24*32: LET df=FN p(23730)+1:
;     DEF FN p(a)=PEEK a+256*PEEK (a+1)

; 100 INPUT "First row: ";first,"Last row: ";last:
;     GO SUB 2100: PAUSE 0:
;     RANDOMIZE 1: LET bc=first=last+USR 23296: LET ram=FN p(23670)
; 141 SAVE "sliceASM" CODE df,ram-df

;1100 LET diff=last-first: INPUT "First row: ";first: LET last=first+diff:
;     GO SUB 2100: LOAD "sliceASM" CODE df,ram-df: CLS:
;     RANDOMIZE 2: LET bc=first=last+USR 23296: LET ram=FN p(23670)
;1101 INPUT a
;1199 STOP

;2100 LET first=ABS INT (first AND first>=0 AND first<=23):
;     LET last=ABS INT (last-(last-first AND (last<first OR last>23))):
;     RETURN

; *****

SLICE:  ld   ix,(23653)      ;IX=(STKEND)
        ld   a,(ix-3)        ;A =last
        sub  (ix-8)
        inc  a
        ld   b,a             ;B =last-first+1; ie. #rows
        push bc

;display file addressing (HL):
;H =010ssppp, ss=segment 0-2, ppp=pixrow 0-7;
;L =rrrccccc, rrr=char.row 0-7, ccccc=char.col 0-31

;starting values:
;segment = INT(row/8)
;pixrow  = 0
;charrow = row-INT(row/8)*8
;charcol = 0

;starting address:
;H = 64 + segment*8
;L = charrow*32
;HL= H*256 + L

        ld   a,64
        add  a,b
        and  %11111000
        ld   h,a            ;H =010ss000
        ld   a,(ix-8)
        rrca
        rrca
        rrca
        and  %11100000
        ld   l,a             ;L =rrr00000
        ld   de,(23730)      ;DE=(RAMTOP)
        inc  de              ;DE=RAM copy pointer

CHAROW: push bc
        push hl
        ld   b,8
PIXROW: push hl
        push bc
        ld   bc,32
        ld   a,(23670)       ;A =1,2 =save,restore
        cp   1
        jr   z,COPY1
        ex   de,hl           ;swap to,from if restore
        ldir
        ex   de,hl
        jr   COPY2
COPY1:  ldir
COPY2:  pop  bc
        pop  hl
        inc  h
        djnz PIXROW          ;repeat inner loop for all 8 pixrows in line
        pop  hl
        srl  h
        srl  h
        srl  h               ;shift segment# to lo-end of H
        ld   bc,%00100000    ;then increment char.row in L
        add  hl,bc           ;with Carry into H
        sla  h
        sla  h
        sla  h               ;restore alignment in H
        pop  bc
        djnz CHAROW          ;repeat outer loop for all char.rows in slice

        ld   h,0             ;copy attributes for slice
        ld   l,(ix-8)        ;HL=first
        add  hl,hl
        add  hl,hl
        add  hl,hl
        add  hl,hl
        add  hl,hl           ;HL=first*32
        pop  bc              ;B =last-first+1; ie. #rows
        push de
        ld   de,22528
        add  hl,de
        ex   de,hl           ;DE=attribute segment start
        ld   h,0
        ld   l,b
        add  hl,hl
        add  hl,hl
        add  hl,hl
        add  hl,hl
        add  hl,hl           ;HL=#rows*32
        ld   b,h
        ld   c,l             ;BC=attribute segment size
        pop  hl              ;HL=RAM copy pointer

        ld   a,(23670)
        cp   2
        jr   z,COPY2A
        ex   de,hl           ;swap to,from if save
        ldir
        ex   de,hl
        jr   COPY1A
COPY2A: ldir
COPY1A: ld   (23670),hl      ;return RAM copy pointer in SEED
        ret                  ;as function returns 0 or 1
[/code]

and BASIC:

screenslice.bas [code]
  10 CLEAR 65535-24*32*8-24*32: LET df=FN p(23730)+1: LET ram=df:
     DEF FN p(a)=PEEK a+256*PEEK (a+1):
     DEF FN l(v)=v-256*FN h(v): DEF FN h(v)=INT (v/256):
     REM find FN q() from DATADD
  20 DEF FN q(r,c$,s,d$,l)=33: RESTORE 20: LET fnq=FN p(23639)+8:
     LET qlo=FN l(fnq): LET qhi=FN h(fnq):
     POKE fnq+15,0: POKE fnq+32,0: GO TO 100:
     REM zero hi-bytes of length parameters

  33 POKE 23563,qlo: POKE 23564,qhi
  34 FOR a=fnq TO fnq+17 STEP 17:
       LET loc=VAL (CHR$ PEEK (a+os)): LET os=-os:
       POKE a+12,FN l(loc): POKE a+13,FN h(loc): POKE a+14,l
  35   POKE a+11,0:
     NEXT a: LET c$=d$: POKE 23564,0: RETURN

 100 INPUT "First row: ";first,"Last row: ";last:
     GO SUB 2100: PAUSE 0: LET os=0: GO SUB 2109
 141 SAVE "sliceBAS" CODE df,ram-df

1100 LET diff=last-first: INPUT "First row: ";first: LET last=first+diff:
     GO SUB 2100: LOAD "sliceBAS" CODE df,ram-df: CLS: LET ram=df:
     LET os=17: GO SUB 2109
1199 STOP

2100 LET first=ABS INT (first AND first>=0 AND first<=23):
     LET last=ABS INT (last-(last-first AND (last<first OR last>23))):
     LET H=256*(64+INT(first/8)*8): LET L=(first-INT (first/8)*8)*32
2101 LET p7=7*256: LET p8=p7+256: RETURN

2109 REM picture
2110 FOR r=first TO last
2120   FOR p=0 TO p7 STEP 256:
         GO SUB FN q(ram,"",H+p+L,"",32): LET ram=ram+32:
       NEXT p
2130   LET L=L+32: IF L>255 THEN LET L=0: LET H=H+p8
2135 NEXT r
2139 REM attributes
2140 LET BC=(last-first+1)*32: GO SUB FN q(ram,"",22528+first*32,"",BC):
     LET ram=ram+BC: RETURN
[/code]

just to show how easy it is to place & move things on the screen once the structure of the display file address is appreciated. The BASIC version is not unreasonably slow, and the actual slicing  bit is only 10 statements in four lines (2110,2120,2130,2135), the rest being either setup or performance enhancements. The two versions work in more or less the same way; the output files are interchangeable. I did the pair to compare timings, and also because it's a bit easier to understand the program structure with the BASIC version.

The important bit to understand is this:

display file addressing bit patterns (using the HL register pair):
H =010ssppp, ss=segment 0-2, ppp=pixline 0-7;
L =rrrccccc, rrr=char.row 0-7, ccccc=char.col 0-31

That's the kernel of it, but to illustrate some of the details:

The screen is divided into 3 segments numbered 0-2 (bits ss in H);
each segment has 8 rows of characters numbered 0-7 (bits rrr in L);
each row of characters has 8 lines of pixels numbered 0-7 (bits ppp in H);
each line of pixels has 32 column bytes numbered 0-31 (bits ccccc in L).

To move along the columns, increment L. This will automatically calculate the correct address for the next row and reset the column when ccccc carries over from 31. When rrr carries over from 7 the segment ss can be updated by either rotating H 3 bits right to receive the HL carry, or adding 8 to H (if using BASIC).
To move down the rows add 32 to L.
To move down the segments add 8 to H.
To move down the pixlines within a row, increment H. This will automatically calculate the correct address for the corresponding row in the next segment and reset the pixline when ppp carries over from 7.
All of the address elements can be handled almost as easily (although rather more sedately) from BASIC as from assembler.

The routine starts off like this:

STARTING VALUES:
segment = INT(row/8)
pixline = 0
charrow = row-INT(row/8)*8
charcol = 0

STARTING ADDRESS:
H = 64 + segment*8
L = charrow*32
HL= H*256 + L

I wrote a quicker BASIC version which minimised the number of function calls by setting the block write length for each segment independently to the maximum pixline length for each segment's slice, but this meant that the slice could only be restored at the same relative segment position; ie. if it was saved from row 3 it could only be restored to rows 3,11,19. The version posted here uses fixed 32 byte blocks so the slice can be restored to any row. Blocks are concatenated in RAM and saved as a single CODE block.

   ========================================

If BC=yx coordinates, where y range is 0-191 and x range is 0-255, giving a range for BC of $000-$BFFF; ie. 48k points for a full screen of 24 lines x 32 characters comprising 49152 pixels, then the plot position for any point can be determined with some simple arithmetic.

The address of the byte at which those coordinates are located is:
	LET adr = 16384 + INT (BC/8)
The bit position within that byte is:
	LET pos = C - INT (C/8)*8
To check that point:
	LET byte=PEEK adr:
    LET bit=INT (byte/2^pos)-INT (byte/2^(pos+1))*2
To set that point:
	IF NOT bit THEN POKE adr,byte+2^pos
To reset (clear, unplot) that point:
	IF bit THEN POKE adr,byte-2^pos

In assembler the point can be plotted with:
        ld a,c : and 7 : inc a : ld e,a : xor a : scf
RotateLeft:                  ;rotate bit to correct position in A
        rla : dec e : jr nz,RotateLeft
        srl b : rr c : srl b : rr c : srl b : rr c ;BC=BC/8
        ld hl,$4000 : add hl,bc : or (hl) : ld (hl),a

   ========================================

As a small addendum to this topic I thought that I'd add routines in both BASIC & assembler to calculate the display file address from the row & column number. (The BASIC could be written more concisely, but here I wanted to keep a 1:1 BASIC:assembler equivalence.) Afterwards I thought to look in David Webb's "Advanced ..." book to see how he'd done it, which was very similar; both versions add up to 14 bytes and 53 T, so I guess that's the least in which it can be done. Using binary notation makes the calculations clearer when referring back to the address bit patterns mentioned earlier, emphasising that what at first appears to be a confusingly complicated screen layout is in fact eminently sensible & simple.

[code]
1000 FOR b=0 TO 21: FOR c=0 TO 31
1001 LET a=64:            REM  ld   a,%01000000     ;calc char address in display file
1002 LET a=a+b:           REM  or   b               ;B =row
1003 LET a=INT (a/8)*8:   REM  and  %11111000
1004 LET h=a:             REM  ld   h,a             ;H =010ssppp
1005 LET a=b:             REM  ld   a,b
1006 LET a=a-INT (a/8)*8: REM  and  %00000111
1007                     REM { rrca
1008 LET a=a*32:         REM { rrca
1009                     REM { rrca
1010 LET a=a+c:           REM  or   c               ;C =column
1011 LET l=a:             REM  ld   l,a             ;L =rrrccccc
1012 LET hl=h*256+l: POKE hl,255
1013 NEXT c: NEXT b: STOP
[/code]


**********************************************************************


** Addressing Bottom 4 Lines of Display
   ====================================

That would be 32 (*32 byte) pixel rows for the bottom 4 lines, plus one 128-byte row for the attributes. Although due to the display file layout there are actually only 8 (*128 byte) distinct pixel rows in the bottom four print lines. In BASIC I would have done a "PRINT AT 20,0;" and then read DF_CC to get the start address, then run a loop from there with a STEP of 256 to get each of the eight 128-byte rows, then start at 22528+20*32 to get the 128 attribute bytes. The nine address ranges are:
[code]20608 -> 20735
20864 -> 20991
21120 -> 21247
21376 -> 21503
21632 -> 21759
21888 -> 22015
22144 -> 22271
22400 -> 22527
23168 -> 23295[/code]

In m/code the display file layout makes it possible to save the nine rows as one continuous row in a very few lines of code, as the first eight addresses are $5080 to $5780, the attribute address is $5A80, and each row is $80 bytes, so it could be done with:

[code]
;     SAVE                           ;     RESTORE
      ld   a,8                             ld   a,8
      ld   h,$50                           ld   hl,$fb00   ;save address
      ld   de,$fb00   ;save address        ld   d,$50
loops:ld   bc,$80                    loopr:ld   bc,$80
      ld   l,c                             ld   e,c
      ldir                                 ldir
      dec  a                               dec  a
      ret  m                               ret  m
      jr   nz,loops                        jr   nz,loopr
      inc  h                               inc  d
      inc  h                               inc  d
      jr   loops                           jr   loopr
[/code]

That's assuming there's graphic data in those four lines on the screen; if there's just ASCII text then 896 bytes could be saved by just saving the character codes.


**********************************************************************