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:
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 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 Signal||Signal Source||Signal name|
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.
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
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:
|SCR PREV BYTE||Compute the memory address of the byte immediatly to the left of the current byte|
|SCR NEXT BYTE||Compute the memory address of the byte immediatly to the right of the current byte|
|SCR PREV LINE||Compute the memory address of the byte immediatly above the current byte|
|SCR NEXT LINE||Compute 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.
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)|
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:
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.
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
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.
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