1K hires coding explained (and source analyzed)

Any discussions related to the creation of new hardware or software for the ZX80 or ZX81
Post Reply
dr beep
Posts: 2079
Joined: Thu Jun 16, 2011 8:35 am
Location: Boxmeer

1K hires coding explained (and source analyzed)

Post by dr beep »

Easy TRUE Hires coding on the ZX81.



INTRO
After releasing the ZX81 it was stated that it was a textonly computer with a predefined characterset in the ROM. Soon followed by the first games that used pseudohires graphics that gave some big eyes to everyone who saw that for the first time.

Pseudohires is bounded by the same rules as the normal characterset. Bit 6 is used in a tricky way by the ROM to find the end of a line. Bit 7 of a character is used to invert the data of that character.
Pseudohires can therefore not access all possible combinations of data and it is bound by the variations of data stored in the ROM. Richard Taylor's book “HIGH RESOLUTION” handles pseudohires and its capabilities and restrictions.

Pseudohires and the default characterset use an indirect method to display a character.
During the display the value of the character is read and the data of that character is displayed.
The ROM and the ULA keep track of the data that must be read. What if you could make the displayroutine point to your own defined data and display that? The displayroutine was analyzed further and true hires was possible.


TRUE HIRES
True hires, unlike pseudohires, needs a simple adjustment in the RAM-pack. The refreshmethod needs to be altered to make a truehires displaymethod access the RAM-data. Modern RAM-pack are equipped with this change and can do true hires. Be sure that your RAM-pack is capable of truehires.

There are more ways to activate a truehiresdisplay. The most common method is WRX-graphics, named after the person who optimized the displaymethod over the years: Wilf Rigter.

In 2011 I decided to make a port of my 1K game SHOGUN on the ZX Spectrum to the ZX81.
I was aware of truehiresgraphics on the ZX81 and I needed those since the ZX Spectrumversion used user defined graphics and true hires would make the use of user defined graphics possible.

Wilf Rigter's webpage (1) gave me (too much) information how to make a ZX81 version of the game. Too much information since the whole technical method how the ZX81 makes the display is mentioned and how that led to the displaymethod. If you want to code a game you only need the displaymethod. On the other hand information was not mentioned why a displabuffer always had to end on a 32 bytes boundary.

When I coded my ZX81 emulator for the ZX81 around 1997 I couldn't figure out why the display of the screen made a jump to memory above 48K. I altered the ROM a bit and made my own displaymethod for the emulator. To fully understand hires on the ZX81 you need to know that the display of the screen is “executed” in the memory above 48K.
The ZX81 is a clever designed device. With minimal electronics it was possible to make a screen.
The ZX81 knew when data had to be sent to the screen when a displayline above 32K was called.
The line itself remaines 32K lower so the ZX81 knows what to do.





The optimized hires routine from Wilf Rigter is displayed here:
Explanation follows.

Code: Select all

START 	LD 	IX,HR ;simple start of the hrs mode 
      		RET 

HR     		LD 	B,7 			; delay 
HR0 		DJNZ 	HR0 			; delay 
		NOP				; delay

      		DEC 	B 			; reset possible Z flag 
      		LD 	HL,HSCRN		; your hiresscreen location
      		LD 	DE,20 			;32 bytes per line 
      		LD 	B,#C0 			;192 lines per hires screen

HR1 		LD 	A,H 			;get HFILE address MSB 
      		LD 	I,A 			;load MSB into I register 
      		LD 	A,L 			;get HFILE address LSB  
      		CALL	LBUF + #8000
      		ADD 	HL,DE 		;next line 
      		DEC 	B 			;dec line counter 
      		JP 	NZ HR1 		;last line 

HR2          	CALL 	#292 			;return to application program 
      		CALL 	#220 			;extra register PUSH and VSYNC 

BREAK ;this code segment is optional 
      		CALL 	#F46 			; check break key 
      		LD 	A,#1E 			; restore pattern table pointer 
      		LD 	I,A 
      		JR 	NC STOP 		; skip the HR vector load if BREAK 

      		LD 	IX,HR 			; load the HR vector
STOP 		JP 	#2A4 			; return to application program 

LBUF 		LD 	R,A 			;load HFILE address LSB 
      		DB	0,0,0,0 		;32 NOPs = 256 pixels 
      		DB	0,0,0,0 
      		DB	0,0,0,0 
      		DB	0,0,0,0 
      		DB	0,0,0,0 
      		DB	0,0,0,0 
      		DB	0,0,0,0 
      		DB	0,0,0,0 
      		RET 	NZ 			; always returns 



		ORG	#6000			; example destination
HSCRN 	BLOCK 6144,0 		; 6K of screen
To activate the hiresdisplay you only need to set IX to your displayroutine.
That is what is done at start. The RET will go back to BASIC, but when your code is in machinecode the rest of your code will follow.

The hardware of the ZX81 will now take over the display.
In the lowresdisplay IX would now jump to the routine to start the normal display.
Now it will jump to the routine indicated by HR.

The first part is a small delay to make the display start at the visible part of the screen.
After the delay one time initialization is done.
First we need the Z-flag reset so a DEC B is done. From the loop above B is 0 so it becomes 255 and Z-flag is reset. Is is needed later on. HL is set to the start of the screen, DE is set with the length of a line and B is set with the number of lines needed, 192 screenlines.

Next we enter a loop that is 192 times executed. For each line the display is done.
You can sent data to the screen when you make the I-register and the R-register form the address
where the data is stored. In the loop that address is held by HL so the highbyte H is copied to the I-register. The setting of the R-register is done in upper memory so first A is loaded with the lowbyte L. Tha call to the displayline is made. In uppermemory, like the lowresdisplay, opcodes with bit 6 set are executed normally. LD R,A is such an opcode, so after this code I and R form the data-address. Now 32 NOP's will only increase the R register so each data is sent to the screen. After a full line a RET NZ is executed (also bit 6 set) to return to the loop. A RET NZ is needed over a RET. The timing of the loop is exactly 207 tstates, the time needed to display the line. 1 tstate was missing so RET NZ is used to get the final tstate. During display the Z-flag is reset.
The next thing to do is to make HL point to the next line and repeat this until all lines are done.
For timing DEC B JP NZ,HR1 is used over DJNZ HR1.

After the display we need to end the display in a proper way. Also the keyboard is read during intrupt. A test for a break key is added when you want to disable HR-display and go back to lowres. The breaktest can be skipped/deleted when you want to disable breaking in.

The only thing not mentioned now is why the screen must have a 32 bytes boundary.
On each display the R-register is increased 32 times. However... the R-register can't handle the change of bit 7. The R-register must therefore during display not change from 127 to 128 (this will be 0) or from 255 to 0 (this will remain 128). The R-register will then point to the wrong data. This is why the screen must have a 32 byte boundary so R will go from 0 to 31 within that line.
It is not forbidden to set the screen on a not 32 byte boundary if you can prevent R going to 128 or 0. In 1K hires the boundary is often set to a not matching boundary.


1K HIRES
Wilf Rigter even made a routine that allowed a small hires screen on a 1K machine.
This routine became the base for my 1K ZX81 hires games, although I cut off a lot of checks.

The 1K hirescode from Wilf Rigter is hard to understand. It has a lot of checks and the filling
of the final part of the screen is done in a difficult manner. Still from this routine I was able to create my first 1K hires game




Code: Select all

; IX is set in code
		LD	IX,HR
		

; the HR routine
hr         	LD   	B,3                                             
hr0        	DJNZ 	hr0                                             
                                                                
           		LD   	HL,screen                                       
           		LD   	E,bytcol                                        
topline    	LD   	BC,#8000+lines                                  
           		INC  	B                 ; always 1 topline            
           		LD   	A,192-lines                                     
           		SUB  	B                                               
           		LD   	(notend+1),A                                    
xmove      	CALL 	delay                                           
hr1        	CALL 	lbuf2                                           
           		DJNZ 	hr1                                             
notend     	LD   	B,0                                             
                                                                
hr2        	LD   	A,H                                             
           		LD   	I,A                                             
           		LD   	A,L                                             
           		CALL 	lbuf+#8000                                      
           		ADD  	HL,DE                                           
           		DEC  	C                                               
           		JP   	NZ,hr2                                          
                                                                
hr3        	CALL 	lbuf2                                           
           		DJNZ 	hr3                                             
                                                                
           		CALL 	#292                                            
           		CALL 	#220                                                                                                            
           		LD   	IX,hr                                           
           		JP   	#2A4                                            

lbuf       	LD   	R,A                                             
           		DEFB 	00,00,00,00,00,64,64,64                         
           		DEFB 	64,64,64,64,64,64,64,64                         
           		DEFB 	64,64,64,64,64,64,64,64                         
           		DEFB 	64,64,64,64,64,64,64,64                         
delay      	RET  	NC                                              

lbuf2      	LD   	D,3                                             
lb2        	EX   	(SP),HL                                         
           		EX   	(SP),HL                                         
           		DEC  	D                                               
           		JP   	NZ,lb2                                          
           		NOP                                                  
           		RET                                                  

This routine makes a 5x5 screen that can move over the screen.
Depending on your location in the maze the small screen moves over the full screen.

The 1K routine from Wilf Rigter makes a 6x8 characterscreen. The routine is written to that purpose as is my routine to write a 5x5 block to the screen. And that illustrates the problem with a 1K hires game. Each game needs its own routine to do the display. When you have a game in mind you need to out think the displaymethod before you can code a game.

LOWRES and HIRES combined
I added a combined hires- and lowresdisplay in my second game BLOCKY.
After many games I made some kind of a model that starts with a lowresdisplay followed by the hiresdisplay.

Code: Select all

; some lowres, HR must start AFTER #403F, but before #4070      
hr      	LD   	HL,lowres+#8000   	; the lowres display          
        	LD   	BC,#311           	; minimum needed #11          
        	LD   	A,#1E                                           
        	LD   	I,A                                             
        	LD   	A,#Fb                                           
        	CALL 	#2B5              		; show lowres screen          
                                                                
        	LD   	B,3	       		; match hires display with lowres
hr00    DJNZ 	hr00                                            
                                                                
; the hr displayroutine                                                   
; user defined

; fixed end of HR-routine
	CALL #292              		; back from intrupt           	
	CALL #220                                            
	LD   IX,hr                                           
	JP   #2A4                                            
This model will start with 17 blanc lines then 2 line lowres screen followed by the hiresscreen.
The lowresscreen is 17+2*8=33 lines. A full screen has 193 lines in lowres, first 1 line then another 192 for 32x24 characters. To make a full screen your hiresscreen must fill 193-33 = 160 lines to make a stable screen.

When your game holds less lines you can alter the C-register and add a number of blanc lines on top
without losing any bytes to code. This explains why many 1K hires games have the screen at the bottom. The top is filled by setting C higher.

1K MEMORY
1K of memory also means that the hiresscreen can't be too big when it is 1 block of screen
like Wilf's demo or my first game.If you want to give the illusion of a full sized game
(like Blocky, Bowling, Qbert, The Edge, Asteroidbelt, Itsy Bitsy Spider and many more)
you need to write a routine that can unpack a screen while doing the display.
This also counts for smaller games that do not make a full screen but still write a larger screen then the data that is stored (Ghostbusters, Monkeyboard, Make a Shit).



In short I have 4 ways of building a screen.
1) A fixed sized screen like Wilf's demo (First game Wiwo Dido, W-rotator, Sir Clive)
2) Counting screenlined and display on right screenline (Blocky, Bowling, Carrace)
3) Double display or mirroring screenlines (3D random maze, Sokob1, 1Karat)
4) Change UDG per 8 lines and multiple linebuffers (Ghostbusters, Make a Shit, Bomber)

There are more methods but mostly it is an alteration of one of the above or a combination.
The first 3 methods do not alter data in the line to display. The screen is fully built outside the hiresroutine and the hiresroutine is only capable of displaying defined data outside the routine.

Th 4th method has only a fixed number of lines (1 to 8) to display. The lines are changed just before the display is done. Each line alters a few bytes and the bytes are repaired after display to make allow each possible screen. Most games with bytewise movement have this method of displaying.

Changing bytes cost time. The more bytes you want to change the less time you have for a display. After many games I was able to change 2 User Defined Graphics over 16 fixed graphics that
can be set on/off or even inverted. This comes at another cost. The graphics can only be 7 lines in size. The 8th line will always be an empty line. This line is used to show nothing on screen so it can repair the changes made. The screen is 7*16 bytes in memory, so it will have the same
highbyte all the time. Settin I for the right display cam be done with just 1 time setting, saving 13 tstates in the loop to do other things needed. The screen is best kept on a size of max 256 bytes.
The only reason to make the screen shorter could be that the game needs more byte.
The routine of APPLEPIE can do 16 columns display but the game was too large so 2 columns had to be taken off.

To get the most out of 1K memory you need to use compressed data on loading and routines that can be placed over the systemvariables after loading. After loading the routines are copied and the
needed buffers are formed out of the first buffer. The stack must also be kept small, a 32 bytes stack is best to use. A smaller stack can be a problem. After loading you need to start the game.
This is just 1 BASIC-line of RANDOMISE USR start. This line can be placed fully over
the systemvariables. A model to get most out of 1K is available.

48K BUG
I already mentioned that during display opcodes with bit 6 set are executed. You can use this trick
to disable the display of a character by setting LD B,B in the buffer. You can also use a (conditional)
JP to go to a routine in lower memory. This can save time in your loops and it might make your code work. Most commands with bit 6 set are just 1 byte (RET, JP (HL) ) or 2 bytes that form a shifted command (LD R,A), Some commands use more bytes. A 48K ZX81 would read the real address when a command like CP N or JP NN is executed in uppermemory. The second (or 3rd) byte is read from the real address. A JP would then end in a reset of the game. To make 1K hires games run on a 48K ZX81 a fix was needed. This 48K bug can be solved with a full copy of the game exactly 32K higher in memory. This is also solved in the model for coding 1K hires games.

Conclusion
There is no 1 fix solves all for 1K hires games, but I am willing to explain a 1K source to the bottom when mentioned in this thread.




(1)
https://8bit-museum.de/heimcomputer-2/s ... ay-system/
dr beep
Posts: 2079
Joined: Thu Jun 16, 2011 8:35 am
Location: Boxmeer

Re: 1K hires coding explained (and source analyzed)

Post by dr beep »

BLOCKY

BLOCKY is the second game in 1K using hires graphics.
Each game has itsd own hires driver. This document explains how the hiresdriver
from BLOCKY is working.

During the display the ZX81 sents 192 lines of data to the screen in lowres.
The size of the screen can be more lines. Blocky sents 24 lines more to the screen,
a total of 192+24=216 lines.

This was the first game that did a hires and lowres display on 1 screen.
First a 192 line hires screen is displayed, followed by 16 lines of lowres screen
with 8 lines visible for score, name and lives.

BLOCKY uses a full screen but only has 256 bytes of data for the screen.
The screen follows a counter from 192 to 0. When a line is met where data
must be drawn that data will be displayed. In all other cases nothing will be displayed.

This game uses a screen that looks like this

linenumber,31 bytes of data
linenumber,31 bytes of data
linenumber,31 bytes of data
linenumber,31 bytes of data
linenumber,31 bytes of data
linenumber,31 bytes of data
linenumber,31 bytes of data
linenumber,31 bytes of data



First of all the hires routine is activated in the game with

Code: Select all

	LD	IX,hr

When an intrupt occurs the following driver is started

Code: Select all

; first synchronize the display with the screen
hr 	DI 
 	LD 	B,6 
hr0 	DJNZ 	hr0

Code: Select all

; now start the check what to display 
 	LD 	HL,screen 		; make HL point to screen as above
 	LD 	DE,31 			; stepcounter to next line
 	LD 	IX,hr2			; the return after a display 
 	LD 	B,192 			; we check 192 lines

hr1 	LD 	A,H			; we set I-reg  
 	LD 	I,A 

 	LD 	A,(HL) 			; we get current line to display
 	INC 	L			; step to next field (size is 256 INC L is enough) 
 	CP 	B			; test if line is reached 
 	LD 	A,L			; preset A if display is ok 
 	JP 	Z,lbuf+#8000 		; Do display or not
At this moment the display can go 2 ways. Either a display by the buffer
or no display. The timing of both loops must be the same so by no display
time is wasted to synchronize both loops

Code: Select all

; delays when no display
 	DEC 	HL 
 	LD 	C,8 
nohi 	DEC 	C 
 	JR 	NZ,nohi  
 	LD 	A,I 
; end of delay
However when we have passed time we must keep the same line for next test
we skip the addition by hiding the ADD HL,DE in a LD A,N command waisting
the final 7 tstates.

Code: Select all

 	DEFB 	62 
hr2 	ADD 	HL,DE 

; check all lines
 	DJNZ 	hr1 
Now we have done the hires display.
We sync the display with delay to allow lowres display

Code: Select all

 	EX 	(SP),HL 
 	EX 	(SP),HL 
 	NOP 
 	NOP 
 	DEC 	HL 
 	INC 	HL 
 	LD 	A,L 

; get dfile from sysvar (later games will point directly to dfile)
 	LD 	HL,(#400C) 

; make it as display (above 32K)
 	SET 	7,H

; set character to lowres 
 	LD 	A,#1E 
 	LD 	I,A 

 	LD 	A,#F5 

; BC holds the nr of lines to display
 	LD 	BC,#208 
; do lowres display
 	CALL 	#2B5 

And we end the hires with this fixed end

Code: Select all

 	CALL 	#292 
 	CALL 	#220 
 	LD 	IX,hr 
 	JP 	#2A4 

Code: Select all

; the lowresscreen
dfile 	DEFB 118 
score 	DEFB 28,28,28,28,28,28,0 
lifecnt DEFB 28,0 
hisc 	DEFB 28,28,31,28,36,29,0,0 
 	DEFB 0,16,40,17,0,30,28,29,29,0,62,55,56 
 	DEFB 118,118 
Now the hires display must be done

Code: Select all

; the displaybuffer, with an extra trick

; this part looks like a normal buffer
lbuf 	LD R,A 
 	DEFW 0 
This part of the buffer is double used code.
in uppermemory only opcodes with bit 6 set are executed.
This part has bit 6 low and done as NOP, but in normal memory
the code is used to reset score and set lives. The JP (IX) is skipped
by hiding that in a LD DE,NN.
In hires the routine exits with the JP to HR2.
At HR2 the next line is calculated for the next display.

Code: Select all

reset 	LD A,28 
 	LD (BC),A 
 	INC BC 
 	LD (BC),A 
 	INC BC 
 	LD (BC),A 
 	INC BC 
 	LD (BC),A 
 	INC BC 
 	LD (BC),A 
 	INC BC 
 	LD (BC),A 
 	INC BC 
 	INC BC 
 	LD A,lives 
 	LD (BC),A 
 	LD A,0 
 	LD (DE),A 
 	INC DE 
 	LD (DE),A 
add5 	INC HL 
add4 	INC HL 
 	INC HL 
 	INC HL 
 	INC HL 
 	DEFB 17 
 	JP (IX) 
 	RET
The game itself will create the next screen that needs to be displayed.

As for the next game to be explained: first game mentioned will be next.
User avatar
thewiz
Posts: 58
Joined: Sun Aug 16, 2009 8:36 pm
Location: Crewe

Re: 1K hires coding explained (and source analyzed)

Post by thewiz »

Really interesting. Thanks for sharing.
Memotech rules
einar
Posts: 1
Joined: Tue Feb 20, 2018 3:49 am

Re: 1K hires coding explained (and source analyzed)

Post by einar »

This is interesting!

Does your 1K hires method work with the original built-in ZX81 RAM, or does it also required a modern RAM-pack?
User avatar
Paul
Posts: 1517
Joined: Thu May 27, 2010 8:15 am
Location: Germanys west end

Re: 1K hires coding explained (and source analyzed)

Post by Paul »

The ZX81 without expansion is hires capable, no addon or modification is needed.
Thats what makes his games so adorable.
In theory, there is no difference between theory and practice. But, in practice, there is.
dr beep
Posts: 2079
Joined: Thu Jun 16, 2011 8:35 am
Location: Boxmeer

Re: 1K hires coding explained (and source analyzed)

Post by dr beep »

einar wrote: Thu May 18, 2023 2:31 am This is interesting!

Does your 1K hires method work with the original built-in ZX81 RAM, or does it also required a modern RAM-pack?
With RAM-pack: your RAM-paxk must be hires capable
Without RAM-pack: the games should work.
Post Reply