Saturday, August 19, 2017

CSpect V0.7 released

port $57 and $5B are now mapped as $xx57 and $xx5B
Fixed a couple of sprite flipping and rotation issues
Can now use RST $08 to save (see the layer 2 demo)
Some more additions to SNasm - see docs

Enjoy!

Download: CSpect Emulator


Wednesday, August 16, 2017

Shock! CSpect V0.6

Okay... shock... new version.
HLDE has been renamed to DEHL, and ACC32 has been renamed to A32
I've also added the new TEST $XX opcode
And I've added SAVEBIN "filename",start,len
and I've added SAVESNA "filename",PC [,STACK]
'*' as current drive now working properly

swapnib           ED 23           A bits 7-4 swap with A bits 3-0
   mul               ED 30           multiply HL*DE = HLDE (no flags set)
   add  hl,a         ED 31           Add A to HL (no flags set)
   add  de,a         ED 32           Add A to DE (no flags set)
   add  bc,a         ED 33           Add A to BC (no flags set)
   add  hl,$0000     ED 34 LO HI     Add A to HL (no flags set)
   add  de,$0000     ED 35 LO HI     Add A to DE (no flags set)
   add  bc,$0000     ED 36 LO HI     Add A to BC (no flags set)
   outinb            ED 90           out (c),(hl), hl++
   ldix              ED A4           As LDI,  but if byte==A does not copy
   ldirx             ED B4           As LDIR, but if byte==A does not copy
   lddx              ED AC           As LDD,  but if byte==A does not copy, and DE is incremented
   lddrx             ED BC           As LDDR,  but if byte==A does not copy
   fillde            ED B5           Using A fill from DE for BC bytes
   ld  hl,sp         ED 25           transfer SP to HL
   ld  acc32,hlde    ED 20           transfer HLDE into ACC32
   ld  hlde,acc32    ED 21           transfer ACC32 into HLDE
   ex  acc32,hlde    ED 22           swap ACC32 with HLDE
   inc hlde          ED 37           increment 32bit HLDE
   dec hlde          ED 38           increment 32bit HLDE
   add hlde,a        ED 39           Add A to 32bit HLDE
   add hlde,bc       ED 3A           Add BC to 32bit HLDE
   add hlde,$0000    ED 3B LO HI     Add $0000 to 32bit HLDE
   sub hlde,a        ED 3C           Subtract A from 32bit HLDE
   sub hlde,bc       ED 3D           Subtract BC from 32bit HLDE
   mirror a          ED 24           mirror the bits in A    
   mirror de         ED 26           mirror the bits in DE    
   push $0000        ED 8A LO HI     push 16bit immidiate value
   popx              ED 8B           pop value and disguard
   nextreg reg,val   ED 91 reg,val   Set a NEXT register (like doing out($243b),reg then out($253b),val )
   nextreg reg,a     ED 92 reg       Set a NEXT register using A (like doing out($243b),reg then out($253b),A )
   pixeldn           ED 93           Move down a line on the ULA screen
   pixelad           ED 94           using D,E (as Y,X) calculate the ULA screen address and store in HL
   setae             ED 95           Using the lower 3 bits of E (X coordinate), set the correct bit value in A
   test $00          ED 27           And A with $XX and set all flags. A is not affected.

Download: CSpect Emulator

Tuesday, August 15, 2017

CSpect V0.5

Will just say.... These are all subject to change. Looks like HLDE will swap to DEHL to help compilers, and ACC32 changes to A32. So be aware when using.

Okay, this this (might) be the last one for a little - I really want to carry on with my game, and not get drawn into doing emulators and assemblers all the time!


There are also several new Z80 opcodes to play with! My included assembler will assemble these correctly for use in the emulator. There are a few more to come.....

   swapnib           ED 23           A bits 7-4 swap with A bits 3-0
   mul               ED 30           multiply HL*DE = HLDE (no flags set)
   add  hl,a         ED 31           Add A to HL (no flags set)
   add  de,a         ED 32           Add A to DE (no flags set)
   add  bc,a         ED 33           Add A to BC (no flags set)
   add  hl,$0000     ED 34 LO HI     Add A to HL (no flags set)
   add  de,$0000     ED 35 LO HI     Add A to DE (no flags set)
   add  bc,$0000     ED 36 LO HI     Add A to BC (no flags set)
   outinb            ED 90           out (c),(hl), hl++
   ldix              ED A4           As LDI,  but if byte==A does not copy
   ldirx             ED B4           As LDIR, but if byte==A does not copy
   lddx              ED AC           As LDD,  but if byte==A does not copy, and DE is incremented
   lddrx             ED BC           As LDDR,  but if byte==A does not copy
   fillde            ED B5           Using A fill from DE for BC bytes
   ld  hl,sp         ED 25           transfer SP to HL
   ld  acc32,hlde    ED 20           transfer HLDE into ACC32
   ld  hlde,acc32    ED 21           transfer ACC32 into HLDE
   ex  acc32,hlde    ED 22           swap ACC32 with HLDE
   inc hlde          ED 37           increment 32bit HLDE
   dec hlde          ED 38           increment 32bit HLDE
   add hlde,a        ED 39           Add A to 32bit HLDE
   add hlde,bc       ED 3A           Add BC to 32bit HLDE
   add hlde,$0000    ED 3B LO HI     Add $0000 to 32bit HLDE
   sub hlde,a        ED 3C           Subtract A from 32bit HLDE
   sub hlde,bc       ED 3D           Subtract BC from 32bit HLDE
   mirror a          ED 24           mirror the bits in A     
   mirror de         ED 26           mirror the bits in DE     
   push $0000        ED 8A LO HI     push 16bit immidiate value
   popx              ED 8B           pop value and disguard
   nextreg reg,val   ED 91 reg,val   Set a NEXT register (like doing out($243b),reg then out($253b),val )
   nextreg reg,a     ED 92 reg       Set a NEXT register using A (like doing out($243b),reg then out($253b),A )
   pixeldn           ED 93           Move down a line on the ULA screen
   pixelad           ED 94           using D,E (as Y,X) calculate the ULA screen address and store in HL
   setae             ED 95           Using the lower 3 bits of E (X coordinate), set the correct bit value in A

Download: CSpect Emulator

Thursday, August 10, 2017

Version 0.4 of CSpect

This is a new release of my CSpect emulator with yet more ZX Spectrum Next features.

You can now set sprite and screen order priorities, and the included Layer 2 scrolling demo+source shows how to do this.
Full memory banking has been added, so you can now use the full 1MB of RAM that will come with the next! (well....subject to change, and esxDOS pinching some). Again the Layer 2 demo shows this function in the utils.asm file. The comments also show the general layout of memory, so you should take a look there before using the paging.

Layer 2 double buffering! You can now have 2 Layer 2 buffers and page in either to $0000-$3ffff using the new bit layout in port $123b - see the included read me. for more details. To flip buffers, you just need to swap register 18 and register 19 values around.

There are also several new Z80 opcodes to play with! My included assembler will assemble these correctly for use in the emulator. There are a few more to come.....

   swapnib    ED 23    A bits 7-4 swap with A bits 3-0
   mul        ED 30    multiply HL*DE = HLDE (no flags set)
   add  hl,a  ED 31    Add A to HL (no flags set)
   add  de,a  ED 32    Add A to DE (no flags set)
   add  bc,a  ED 33    Add A to BC (no flags set)
   outinb     ED 90    out (c),(hl), hl++
   ldix       ED A4    As LDI,  but if byte==A does not copy
   ldirx      ED B4    As LDIR, but if byte==A does not copy
   lddx       ED AC    As LDD,  but if byte==A does not copy, and DE is incremented
   lddrx      ED BC    As LDDR,  but if byte==A does not copy


The file system will (should) also now accept '*' or '$' as setting the current drive. (untested)

I have also started to add some basic Audio, so the spectrum beeper now works.

Lastly.... the Sprite shape register has switched from port $55 to port $5B

Download: CSpect Emulator

Saturday, July 15, 2017

New CSpect ZX Spectrum Next emulator

UPDATE:  I've now added register setting, and memory window set/moving. You can also set breakpoints from the input line, and push/pop values.

I've been working away on a new debugger for my CSpect emulator, and it's now ready to use, so here it is along with my assembler (SNasm) and a sample showing how to use it all. Please read the readme and be aware it may go pop at any time. Aside from that -have fun!!

Download: CSpect Emulator



Thursday, July 13, 2017

Z8410 DMA chip for the ZX Spectrum Next

So the ZX Spectrum Next team have added a Z80 DMA chip, the Z8410  chip to be more precise. This was popular add on in Europe ( more info here: http://velesoft.speccy.cz/data-gear.htm ), and has some very cool advantages for future games, especially because it's now a standard part. However information on it is a little thin on the ground, so after some major googling, I've found the info on them.

The DMA port can be 11 or 107, and it can transfer around 865k per second
From the web page above: Max. speed of data transfer on ZX128+ is...

17.3 kB(17727 bytes) / frame = 865.6 kB(886350 bytes) / second.

Or.... 1 byte every 4 T-States.

Now here is the really cool bit, because it's tied to the clock speed, when you speed up the NEXT (as it can go 7Mhz, 14Mhz and 28Mhz), the DMA will also speed up!  This is amazing, as it means you'll be able to transfer 1.73Mb/s, 3.46Mb/s, and an astounding 6.92Mb/s!  At 28Mhz you won't be using Layer 2, and that means you could copy the original Layer 1 screen in just 13.5 scanlines, which is incredible.

The registers are below, and here is the Datasheet



Saturday, July 01, 2017

esxDOS File access

So I was about to start adding some very basic "simulation" support for files into my emulator, and I'd lost track of the API details as they were listed on Facebook. Facebook isn't a great place to have technical discussions as you can't search later to find the stuff you need!

Fortunately I had it saved off, so before I (and everyone else) loses it, I thought I'd put it up here. I'll do a little ASM lib with all this later.
I'll also extend this if/when I discover more commands, as there appears to be very little info about the esxDOS API

;
; NOTE: File paths use the slash character (‘/’) as directory separator (UNIX style)
;

M_GETSETDRV  equ $89
F_OPEN       equ $9a
F_CLOSE      equ $9b
F_READ       equ $9d
F_WRITE      equ $9e
F_SEEK       equ $9f
F_GET_DIR    equ $a8
F_SET_DIR    equ $a9

FA_READ      equ $01
FA_APPEND    equ $06
FA_OVERWRITE equ $0C

; Function: Detect if unit is ready
; Out:      A = default drive (required for all file access)
;           Carry flag will be set if error.
GetSetDrive:
             xor  a               ; A=0, get the default drive
             rst  $08
             db   M_GETSETDRV             
             ld   (DefaultDrive),a
             ret
DefaultDrive db   0



; Function:  Open file
; In:        IX = filename
;            B  = open mode
;            A  = Drive
; Out:       A  = file handle
;            On error: Carry set
;                      A = 5   File not found
;                      A = 7   Name error - not 8.3?
;                      A = 11  Drive not found
;                      
fOpen:
             ld   a, (DefaultDrive)  ; get drive we're on
             ld   b, FA_READ         ; b = open mode
             ld   ix,FileName        ; ix = Pointer to file name (ASCIIZ)
             rst  $08
             db   F_OPEN             ; open read mode
             ret                     ; Returns a file handler in 'A' register.



; Function:  Read bytes from a file
; In:        A  = file handle
;            ix = address to load into
;            bc = number of bytes to read
; Out:       Carry flag is set if read fails.
fRead:
             ld   ix, 16384          ; ix = address where to store what is read
             ld   bc, 6912           ; bc = bytes to read
             ld   a, filehandle      ; a  = the file handler
             rst  $08
             db   F_READ             ; read file
             ret


; Function:  Write bytes to a file
; In:        A  = file handle
;            ix = address to save from 
;            bc = number of bytes to write
; Out:       Carry flag is set if write fails.
fWrite:
             ld   ix, 16384          ; ix = memory address to save from
             ld   bc, 6912           ; bc = bytes to write
             ld   a, handle          ; a  = file handler
             rst  $08
             db   F_WRITE            ; write file
             ret


; Function:  Write bytes to a file
; In:        A  = file handle
; Out:       Carry flag active if error when closing
fClose: 
             ld   a, handle           ; a  = file handler
             rst  $08
             db   F_CLOSE
             ret



; Function:  Seek into file
; In:        A    = file handle
;            L    = mode:  0 - from start of file
;                          1 - forward from current position
;                          2 - back from current position
;            BCDE = bytes to seek
; Out:       BCDE = Current file pointer. (*does not return this yet)
;            
fSeek:   
             ld   a,handle           ; file handle
             or   a                  ; is it zero?
             ret  z                  ; if so return
             ld   l,0
             ld   bc,0
             ld   de,0
             rst  $08           
             db   F_SEEK
             ret



; Function:  SetDirectory
; In:        A    = Drive
;            HL   = pointer to zero terminated path string ("path",0)
; Out:       carry set if error
;            
SetDir:
             ld   a,(DefaultDrive)   ; drive to change directory on
             ld   hl,Path            ; point to "path",0 to set
             rst  $08           
             db   F_SET_DIR  
             ret



; Function:  Get Directory
; In:        A    = Drive
;            HL   = pointer to where to STORE zero terminated path string
; Out:       carry set if error
;            
GetDir:     
             ld   a,(DefaultDrive)   ; drive to get current directory from
             ld   hl,Path            ; location to store path string in
             rst  $08           
             db   F_GET_DIR  
             ret


Sunday, June 04, 2017

ZX Spectrum Next - Bitmap example disassembly

I've been having a poke around in the ZX Spectrum Next Bitmap example. It's mostly clear, but one or two things are....odd.
This is what I have so far.

UPDATE: Banking registers now confirmed by Jim Bagley

; Disassembly of the file "bitmaps\BMPLOAD"
; 
; BMP Port $123b
;     bit 0    -    Write enable. (banks in 16K from $0000-$3fff)
;     bit 1    -    bitmap ON
;     bit 4    -    Layer 2 below spectrum screen
;     bit 6-7  -    Which 16K bank to page into $0000-$3ffff
;
; On entry, HL = arguments
;

;
; Since dot commands run at $2000, and the Layer 2 is paged into $0000-$3fff
; It needs to have some code above the lower 16K to actually copy into layer 2
; So copy up the "copy" routine, and it'll page in/out the layer 2 bank
;
2000 226221    ld      (HL_Save),hl     ; Store HL
2003 216421    ld      hl,UploadCode    ; Src  = 
2006 110060    ld      de,6000h         ; dest = $6000
2009 011f00    ld      bc,001fh         ; size = 31 bytes
200c edb0      ldir    

200e 2a6221    ld      hl,(HL_Save)     ; get HL back
2011 7c        ld      a,h
2012 b5        or      l                ; is hl 0?
2013 2008      jr      nz,201dh         ; if we have an argument, carry on

2015 213121    ld      hl,2131h         ; get message
2018 cd1d21    call    PrintText        ; print the filename
201b 1823      jr      Exit             ; exit

; Load file....?
CopyFilename:
201d 112421    ld      de,FileName      ; "filename.ext" text - space to store filename?
2020 060c      ld      b,0ch            ; b = $0c (max length of allowed filename - no path it seems)

; Copy, and validate characters in filename
2022 7e        ld      a,(hl)           ; first byte of filename
2023 fe3a      cp      3ah              ; is it a ":"?  End of filename
2025 2810      jr      z,EndFilename    ; if so end of filename  
2027 b7        or      a                ; 0?
2028 280d      jr      z,EndFilename    ; if so end of filename
202a fe0d      cp      0dh              ; newline?
202c 2809      jr      z,EndFilename    ; if so end of filename
202e cb7f      bit     7,a              ; over 127?
2030 2005      jr      nz,EndFilename   ; if so end of filename
2032 12        ld      (de),a           ; copy over to filename cache
2033 23        inc     hl               ; next src letter
2034 13        inc     de               ; next dest letter
2035 10eb      djnz    2022h            ; copy all
EndFilename
2037 af        xor     a                ; Mark end of filename
2038 12        ld      (de),a           ; store filename
2039 dd212421  ld      ix,FileName      ; get filename base address
203d cd6220    call    LoadFile:

Exit:
2040 af        xor     a                ; no error
2041 c9        ret                      ; exit


; esxDOS detect if unit is ready
DetectUnit:
2042 af        xor     a                ; Detect if unit is ready
2043 cf        rst     08h              ; call esxDOS
2044 89        db      $89              ; M_GETSETDRV

; Open
; IX= Filename (ASCIIZ)
; B = FA_READ       ($01)
;     FA_APPEND     ($06)
;     FA_OVERWRITE  ($0C)
;
OpenFile:
2045 dde5      push    ix               ; ix = filename
2047 e1        pop     hl               ; If a DOT command (rather than normal memory), uses HL not IX
2048 0601      ld      b,01h            ; b = FA_READ
204a 3e2a      ld      a,2ah            ; a = unknown  (a=0 for open)
204c cf        rst     08h              ; call esxDOS
204d 9a        db      $9a              ; F_Open
204e 325620    ld      (2056h),a        ; Store file handle  (self modify read from file code)
2051 c9        ret                      ;


; esxDOS command - Read from file - command $9d
; IX = address to load into
; BC = number of bytes to load
; A  = file handle
ReadBytes:
2052 dde5      push    ix               ; IX = where to to store data
2054 e1        pop     hl               ; get address to load into
2055 3e00      ld      a,00h            ; $00 is self modified
2057 cf        rst     08h              ; call esxDOS
2058 9d        db      $9d              ; F_Read
2059 c9        ret     

; esxDOS command - Close File - command $9b
; A = file handle
CloseFile:
205a 3a5620    ld      a,(2056h)         ; Get open file handle 
205d b7        or      a                 ; is it 0? (did it open)
205e c8        ret     z                 ; if file handle is 0, return
205f cf        rst     08h               ; Call esxDOS
2060 9b        db      $9b               ; F_Close
2061 c9        ret     

LoadFile:
2062 dde5      push    ix                ; remember filename
2064 cd4220    call    DetectUnit        ; detect unit and open...?!?!?
2067 dde1      pop     ix                ; get filename back
2069 cd4520    call    OpenFile          ; OpenFile - again??
206c dd21e720  ld      ix,BMPHeader      ; Read the BMP file header
2070 013600    ld      bc,0036h          ; read header ($36 bytes)
2073 cd5220    call    ReadBytes
2076 dd218321  ld      ix,BitMap         ; read block into $2183
207a 010004    ld      bc,0400h          ; read palette - 1024 bytes
207d cd5220    call    ReadBytes

;
; Convert the 24bit palette into a simple RRRGGGBB format
;
ConvertBMP:
2080 218321    ld      hl,BitMap         ; Get buffer address ($2183)
2083 11003f    ld      de,3f00h          ; Dest address of converted palette
2086 0600      ld      b,00h

ConvertionLoop:
2088 7e        ld      a,(hl)            ; get BLUE byte
2089 23        inc     hl                ; move on to green
208a c620      add     a,20h             ; brighten up a bit? (blue is always pretty dark??)
208c 3002      jr      nc,SkipSatB       ; overflow? 
208e 3eff      ld      a,0ffh            ; if overflow, then saturate to $FF

SkipSatB:
2090 1f        rra                       ; get top 2 bits only  RRRGGGBB
2091 1f        rra     
2092 1f        rra     
2093 1f        rra     
2094 1f        rra     
2095 1f        rra     
2096 e603      and     03h               ; and store them at the bottom
2098 4f        ld      c,a               ; c holds current byte

2099 7e        ld      a,(hl)            ; get GREEN
209a 23        inc     hl                ; move onto red
209b c610      add     a,10h             ; brighten up a bit as well
209d 3002      jr      nc,SkipSatG       ; if no overflow, skip saturate
209f 3eff      ld      a,0ffh

SkipSatG:
20a1 1f        rra                       ; get 3 bits of green into right place
20a2 1f        rra     
20a3 1f        rra     
20a4 e61c      and     1ch               ; mask off remaining lower bits
20a6 b1        or      c                 ; merge with output byte
20a7 4f        ld      c,a               ; store into output

20a8 7e        ld      a,(hl)            ; get RED
20a9 23        inc     hl                ; move to next byte of colour (assuming alpha)
20aa c610      add     a,10h             ; brighten up a bit
20ac 3002      jr      nc,SkipSatR       ; no overflow?
20ae 3eff      ld      a,0ffh            ; Saturate to $FF

SkipSatR
20b0 e6e0      and     0e0h              ; keep top 3 bits
20b2 b1        or      c                 ; merge with output pixel
20b3 12        ld      (de),a            ; store converted pixel
20b4 13        inc     de                ; move to next ouput pixel
20b5 23        inc     hl                ; move to next BGRA pixel
20b6 10d0      djnz    ConvertionLoop    ; c = pixel....?!?!?!

20b8 06c0      ld      b,0c0h            ; bc=$c000

ConvertUploadLoop:
20ba c5        push    bc
20bb dd21003e  ld      ix,3e00h          ; $3e00 = Destination address
20bf 010001    ld      bc,0100h          ; read 256 bytes of data
20c2 cd5220    call    ReadBytes         ; Read from file

;
; convert 256 value palette index into actual RGB byte pixel using palette lookup
;
20c5 2e00      ld      l,00h             ; l = xx (loop counter)

CopyLoop:
20c7 263e      ld      h,3eh             ; hl = $3exx
20c9 5e        ld      e,(hl)            ; e = Get palette index
20ca 163f      ld      d,3fh             ; d = $3f palette base address - 256 byte aligned
20cc 1a        ld      a,(de)            ; a = palette value (24bit converted downto 8bit)
20cd 265b      ld      h,5bh             ; hl = $5bxx converted 256 byte buffer
20cf 77        ld      (hl),a            ; ($5bxx) = converted colour pixel
20d0 263e      ld      h,3eh             ; hl = $3e00
20d2 2c        inc     l                 ; do 256 bytes of this....
20d3 20f2      jr      nz,CopyLoop

20d5 c1        pop     bc                ; bc = $c000
20d6 c5        push    bc
20d7 cd0060    call    6000h             ; block transfer 256 bytes of the bitmap
20da c1        pop     bc
20db 10dd      djnz    ConvertUploadLoop
20dd 013b12    ld      bc,123bh          ; Bitmap port
20e0 3e02      ld      a,02h             ; 2 = enable and visible
20e2 ed79      out     (c),a             ; switch bitmap layer 2 on
20e4 c35a20    jp      205ah

BMPHeader:     ds   54                   ; $20e7 to $211c

PrintText:
211d 7e        ld      a,(hl)            ; get character
211e 23        inc     hl                ; move to next one
211f b7        or      a                 ; is this 0? 
2120 c8        ret     z                 ; if so... end of string
2121 d7        rst     10h               ; outchr()
2122 18f9      jr      211dh             ; print all characters

;
; Looks like data of some kinds
;
FileName: db  "filename.ext",0
Message:  db  ".picload  to load image to background",$d,$00
HL_Save   dw  0

;
; Copied up to $6000
;
UploadCode:
2164 05        dec     b                ; b = upper byte of memory address
2165 78        ld      a,b              ; get 256 byte page into a
2166 e63f      and     3fh              ; ignore top bits
2168 57        ld      d,a              ; de=$0000-$3ffff 
2169 1e00      ld      e,00h            ; Page bitmap block into lower 16K
216b 78        ld      a,b
216c e6c0      and     0c0h             ; get the 16K bank to page
216e f601      or      01h              ; OR in bank active bit
2170 013b12    ld      bc,123bh         ; bitmap register
2173 ed79      out     (c),a            ; map bank into memory perhaps?
2175 21005b    ld      hl,5b00h         ; $5b00 src image chunk
2178 010001    ld      bc,0100h         ; 256 byte copy (one line)
217b edb0      ldir                     ; copy up  (de=dest)
217d 013b12    ld      bc,123bh         ; bitmap register
2180 ed69      out     (c),l            ; l=0, disable current bank and screen
2182 c9        ret     

BitMap:
2183 00        nop                      ; file is loaded into here....

Friday, April 28, 2017

Hacking ZX Spectrum Manic Miner

So I was playing around with the original Manic Miner game, using a ZX Spectrum .SNA snap shot file (so an actual memory image), and while extracting levels etc. I also extracted graphics - which were obviously included as well.

For the record, I didn't reverse engineer all this, I just used one of the many pages out there describing it.  ( http://www.icemark.com/dataformats/manic/mmformat.htm )

But it also occurred to me that it might be nice to have these graphics readily available for folk to play with, it would certainly have been handy when I was doing my remake a few months ago! So here's what I've extracted....

So, this is the baddies that come with each level, the items (1 per level), the 8 tiles (left to right per level) in colour, and in B/W, and then Miner Willy himself. There are several "special" baddies, and if I extract them I'll add them here as well.

I might see if I can go and grab the Z Spectrum font from the ROM, because I couldn't find it as a simple strip at the original size.

I'll just mention the sprites move through the graphic, so the animation frame and X position is usually....

frame = 3-((x>>1)&3);
draw_xpos = (*x&0xfffffff8)

     














ZX Spectrum 8x8 pixel Font

Sunday, September 25, 2016

ZX Spectrum colour clash on modern hardware

There are always some games that like to do very retro looking games, usually in the style of a Spectrum since you still get "hi-res" from it, rather than the C64 or something where pixels were doubled and couple look a bit nasty, even for today's retro fan club. However, as retro as they look, they never really capture the full spirit of the old machine because of one thing - colour clash.



The ZX Spectrum's screen consisted of a hires (for its day) bitmap of 256x192 pixels, and a colour (or attribute) map. The pixels in the bitmap would get coloured every 8x8 pixels depending on the colour in the attribute map.

So, roll onto modern day. We no longer have separate pixels and attributes so simulating this old mode can be a little tricky.

Roll on further and I suddenly realised a really simple way of achieving this using the hardware we all have in our machines already. So lets say we simulate the way the spectrum drew things, a bitmap screen an a colour map. First, lets draw everything in "white" pixels only - which is much like the spectrum would be, it would look something like this.



If we then drew the colour map, we'd just end up with big blocks of colour going over the top of the screen - like this

And this is where modern hardware struggles. However... if we could somehow use the first image, as a "mask" for the second, then we could get rid of the pixels from the blocky colour image and get the image we wanted. We could generate both these images and then send them through a shader and do - well, whatever we want to, but there's actually a simpler way.

Let me introduce you to destination alpha. DestAlpha is the alpha channel on the screen surface (or render target) that you render to all the time anyway. Whenever you draw a sprite the RGB goes onto the surface from the texture/sprite your drawing, and so does the alpha. When you clear the screen to black with no alpha (or UINT32 of 0x00000000), and then draw a sprite onto it, you're effectively making a copy of a sprite. When you start drawing lots of other sprites it all gets a bit of a mess and is basically useless, but if you control it...it can be very handy

There's also another string for our bow; the colour channel write mask. This lets you switch off and on the different channels of the screen (red,green,blue or alpha). Now it gets very interesting.....

Lets say that black and white image above was also represented in via the alpha channel.
The image shown here has white pixels where there is data, and transparent everywhere else. This is a "mask" of the data we want to draw, and if we put the same data into the alpha channel we can use "destalpha" to actually mask everything.

So, the first thing we need to do is draw the background - whatever we want, however we want. Next we need to clear the alpha channel only - leaving the colour screen intact. We do this by setting the colour channel mask so that only the alpha channel is being written to. We can then render a filled rectangle with alpha of 0. One the alpha channel is cleared, we then set the colour mask back to normal and render everything else.

Once this is done,  the alpha channel will look something like the transparent image above, a lovely alpha mask - now if only we could add this to a sprite it would be like any normal texture....  Step in dest alpha. Normally were use source alpha, inverse source alpha as the blend mode. This would normally take the alpha value from the source i.e. the texture, but now we'll use destalpha, inverse destalpha. This will use the screens alpha channel as the alpha for the blend.

Now when we render the solid rectangles of colour that would be the colour mask, the underlying pixels that crated the alpha channel, will only let colour through were we drew a pixel in the first place.


So, by using the transparent image as the alpha channel of the blocky image, we're left with the image above - which is just like the spectrum.

Now comes the good bit. When we render objects - players, baddies, pickups etc, all we need to then do is change the colour block in the blocky image (which is rendered layer), and then this gives us the same blocky, colour clash feel that the spectrum had

Here's the general order to rendering things....


Render "paper" colour map  (32x24 of 8x8 blocks of colour)

Set colour mask to alpha only

Disable blending and alpha-test
Draw giant flat rectangle of 0 alpha, and 0 colour
Reset colour mask to full ARGB
Enable alpha blend and alpha test

Draw everything

Set colour write mask to RGB - disabling ALPHA

Enable alpha blend and alpha test
Set blend mode to DestAlpha, Inv_DestAlpha

Draw all colour blocks (32x24 of 8x8 blocks of solid colour)

Set blend mode back to SrcAlpha, Inv_SrcAlpha


Once you've done all this.... you'll be using the "pixels" from your sprites, and the colour from the attribute map. Whats better is every bit of hardware out there can do this. It's all standard blend modes stuff - even older hardware. If you've done all this, you should get something like the video below. Make sure ALL your sprites background graphics are transparent, and don't have solid pixel colours (like black) as this will effect the mask.







One final point... It should be noted that the "colour" pixels written at the same time of the mask aren't really needed, but if you colour them as they are meant to be (not just white), then it means you can easily switch the effect on and off without affecting the game. This gives the end user the option to have colour clash or not, which is a nice side effect.