Screen memory addressess

A 'screen memory address' is the memory address of a byte which is converted into pixels and shown on the display.

A screen address is required if we want to plot pixels or draw images on the screen.

This document describes:

Coordinates

A coordinate is used to define a position. For example, we can use a coordinate to define the position of a sprite on the screen.

Advantages of using coordinates:

Disadvantages of using coordinates:

The standard convention for defining coordinates:

Coordinates are always defined relative to an origin point.

The standard convention:

So using the standard conventions with a screen which is 160 pixels wide and 200 pixels tall:

In our game code we convert each coordinate into a screen address before we can plot a sprite.

The screen display

The CPC display hardware generates a memory address by using the CRTC MA (MA0-MA13) and RA (RA0-RA5) outputs in the following way:

Memory address SignalSignal SourceSignal name
A156845MA13
A146845MA12
A136845RA2
A126845RA1
A116845RA0
A106845MA9
A96845MA8
A86845MA7
A76845MA6
A66845MA5
A56845MA4
A46845MA3
A36845MA2
A26845MA1
A16845MA0
A0Gate-ArrayCCLK

Notes

Calculating a screen address therefore depends on the values we have defined for CRTC register 1,6,9,12 and 13.

We can think of the screen as being composed of a number of CRTC character lines, each character line is composed of a number of scan lines.

Calculating a screen address

The Amstrad CPC has a strange screen layout, the screen address of the start of the next line does not follow on from the screen address of the end of the previous line, so in order to speed up the conversion of coordinates to screen address we use a look-up table.

The look-up table holds the result of an intermediate calculation which we use to generate the final screen address, this is much easier than calculating the screen address completely each time.

The following code will generate a look-up table. Each element of the table is the memory address for the start of each screen line. This table can be used if the screen base is static (the screen is not scrolling using the hardware), the screen base is at &C000, and the screen is 200 pixels tall.

.make_screen_addr_table
ld b,200						;; number of lines
ld ix,screen_addr_table			;; start of table
ld hl,&c000						;; base memory address of screen
.mst1
;; HL = current memory address for the start of the scan line

ld (ix+0),l						;; write to table
ld (ix+1),h

inc ix							;; update position in table (ready for next entry)
inc ix

call scr_next_line				;; calculate memory address
djnz mst1						;; loop
ret

;; Storage space for look-up table used to convert a Y coordinate into a screen memory address
.screen_addr_table
defs 200*2						;; the table stores the memory address for 200 lines. Each
								;; memory address is a 16-bit value (2 bytes per value).

To convert from a coordinate to a screen address we use the following code:

;; input conditions:
;; H = x byte coordinate (0-79)
;; L = y coordinate (0-199)
;; output conditions:
;; HL = screen address

.get_screen_address
push bc
ld c,h				;; store H coordinate for later

ld h,0				;; H used to hold X coordinate, need to zero this out
					;; because we want HL to contain the Y coordinate

add hl,hl				;; each element of the look-up table is 2 bytes
					;; convert y position to a byte offset from the start
					;; of the look up table
ld de,screen_addr_table
add hl,de				;; add start of lookup table to get address of element
					;; in lookup table

ld a,(hl)
inc hl
ld h,(hl)
ld l,a				;; read element from lookup table (memory address of the start
					;; of the line defined by the y coordinate)

ld b,0
add hl,bc				;; add on X byte coordinate

;; HL = final memory address

pop bc
ret

Moving around the screen using screen addressess

Once you have calculated the memory address of a byte on the screen, you may need to use this to calculate the memory address of other bytes which are immediately to the left, right, above or below the current byte on the screen. i.e. these bytes are to the left, right, above or below as they are viewed on the display.

Knowing how to calculate these memory addressess is required to draw software sprites or images on the screen.

The firmware provides four functions to do this and these are listed, with a brief description in the table below:

Function NameDescription
SCR PREV BYTECompute the memory address of the byte immediatly to the left of the current byte
SCR NEXT BYTECompute the memory address of the byte immediatly to the right of the current byte
SCR PREV LINECompute the memory address of the byte immediatly above the current byte
SCR NEXT LINECompute the memory address of the byte immediatly above the current byte

NOTE: For more information about these functions please consult the firmware guide.

The firmware functions work well but have the following disadvantages compared to your own implementation:

In general it is best to use your own implementation where possible and this section describes how you can do this, the different methods available and the possible problems which you might want to avoid.

Thankfully we can also study the firmware's implementation of these functions to learn about the Amstrad's screen as the dissassembly of the firmware and operating system are available.

Moving around the screen using screen addressess with a hardware scrolling screen

SCR PREV BYTE, SCR NEXT BYTE

Problem addressess when implementing your own SCR PREV BYTE and SCR NEXT BYTE

When using a 32K screen, or the screen is hardware scrolled, there are 8 'problem' memory addressess, where the memory address of the next byte is not equal to the current memory address plus 1.

These memory addressess, given here as offsets from the start of the screen are:

Offset before (HEX)Required offset after SCR NEXT BYTE (HEX)
&07ff&0000
&0fff&0800
&17ff&1000
&1fff&1800
&27ff&2000
&2fff&2800
&37ff&3000
&3fff&3800

If the screen is static (not scrolled using the hardware), then we can adjust the screen start so that these problem addressess occur on the leftmost or rightmost column of the screen, now the problem has been eliminated and we can use the fastest methods.

If the screen is scrolled by the hardware then you have two choices:

  1. Implement one type of 'push scroll'.

    The screen is only scrolled when the main character approaches the left or right side of the screen, at which point all sprites are turned off while the new section of the map is quickly scrolled into view. Sprites are then enabled so the character can explore the new area.

  2. Use two sprite plotting functions. The plot function is chosen depending on the location of the sprite on the screen and if, when plotted, would cover these problem memory addressess. The first function would use the fastest implementations of SCR NEXT BYTE/SCR PREV BYTE and the second would use the slowest implementations of SCR NEXT BYTE/SCR PREV BYTE which can handle this special case.

A version of SCR NEXT BYTE which handles this special case is:

inc hl		
ld a,h		;; has it overflowed &800?
and &7
ret nz
or l
ret nz
;; if HL was &c7ff before, it will now be &c800
;; add on &38 to get &0000
ld a,h
add &38		;; skip to next page
ld h,a

SCR NEXT LINE

Optimisations

If we think of the screen as being constructed from more than one character line, with each of these constructed from one or more scanlines, then we can use the following optimisation.

We use the y coordinate to calculate the initial character line and the initial scan line within that line.

We initialise a 'current scan line' count to the initial scan line. This is decremented after each line plotted until it reaches 0. When it reaches 0, we must then calculate the memory address of the next character line, and then reset it's value to the number of lines in a character line.

NOTE:

Parameters:

The Z80 assembler code:

ld a,h
add a,8			;; calculate address of next scanline within character line
ld h,a

dec b			;; decrement scanline count (when this reaches 0 we have calculated the last
				;; scan line within the current CRTC character line)
jr nz,nl1

;; calculated the memory address for all scanlines within the current CRTC character line
;; Now need to calculate the memory address of the first scanline of the next CRTC character line.

ld b,8			;; reload scanline count (this is equivalent to the value of CRTC register 9 + 1)


ld a,l
add a,80		;; this is equivalent to (CRTC register 1 * 2)
ld l,a
ld a,h
adc a,&C0
ld h,a
.nl1