Writing direct to screen memory

By writing direct to screen memory we are bypassing the general purpose ROM routines in favour of lean output routines written with performance in mind.

ProsCons
Complete flexibilityNeed to understand the complex Spectrum screen memory layout
Can fine tune the routines for better performance – only write the code to do the bare minimumMore work to make feature rich, i.e. add colouring, control codes, etc
Takes up a little more RAM to store the extra code

You can find out more about how the Spectrum screen memory is laid out here. These functions are all variants from my library output.asm, simplified a little for sake of example.

As we are bypassing the ZX Spectrum ROM (effectively its OS) and are writing directly to the screen, we do not need to set the channel. This code is sufficient to write a single character (A) to the screen.

			LD A, 65		; Character to print
			LD D, 1			; Y position
			LD E, 15		; X position
			CALL Print_Char		; Print the character
			RET
;
; Print a single character out to a screen address
;  A: Character to print
;  D: Character Y position
;  E: Character X position
;
Print_Char:		LD HL, 0x3C00		; Character set bitmap data in ROM
			LD B,0			; BC = character code
			LD C, A
			SLA C			; Multiply by 8 by shifting
			RL B
			SLA C
			RL B
			SLA C
			RL B
			ADD HL, BC 		; And add to HL to get first byte of character
			CALL Get_Char_Address	; Get screen position in DE
			LD B,8			; Loop counter - 8 bytes per character
Print_Char_L1:		LD A,(HL)		; Get the byte from the ROM into A
			LD (DE),A		; Stick A onto the screen
			INC HL			; Goto next byte of character
			INC D			; Goto next line on screen
			DJNZ Print_Char_L1	; Loop around whilst it is Not Zero (NZ)
			RET

; Get screen address from a character (X,Y) coordinate
; D = Y character position (0-23)
; E = X character position (0-31)
; Returns screen address in DE
;
Get_Char_Address:	LD A,D
			AND %00000111
			RRA
			RRA
			RRA
			RRA
			OR E
			LD E,A
			LD A,D
			AND %00011000
			OR %01000000
			LD D,A
			RET				; Returns screen address in DE

We can build upon the previous example to print a string by adding the following function that calls Print_Char.

			LD D, 3
			LD E, 2
			LD HL, TEXT2
			CALL Print_String
			RET
;
; Text for my print routine that uses zero-terminated strings 
;
TEXT2:			DB AT, 12, 2, INK, 1, PAPER, 6, BRIGHT, 1, "Hello World!", 0

;
; My print routine
; HL: Address of the string
;  D: Character Y position
;  E: Character X position
;
Print_String:		LD A, (HL)		; Get the character
			CP 0			; CP with 0
			RET Z			; Ret if it is zero
			INC HL			; Skip to next character in string
			CP 32			; CP with 32 (space character)
			JR C, Print_String	; If < 32, then don't ouput
			PUSH DE			; Save screen coordinates
			PUSH HL			; And pointer to text string
			CALL Print_Char		; Print the character
			POP HL			; Pop pointer to text string
			POP DE			; Pop screen coordinates
			INC E			; Inc to the next character position on screen
			JR Print_String		; Loop

Note that we are using the same text string as in the ROM call variant. However, as this is a fairly rudimentary routine, it will ignore any screen control codes and does not change the screen attributes.

This is the nub with rolling your own code; you can tailor it for speed, but usually at the expense of rich functionality.

Note that the above routines could be further optimised, for example by only calculating the screen position once when printing a string and using INC E to move to the next character position along in the screen memory map.