# 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:

- How to calculate a screen memory address from an (x,y) coordinate,
- how to manipulate an existing screen memory address from an existing screen memory address.

# 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:

- Easier to understand compared to using screen address directly.
- Easier to manipulate.
- Great to use double-buffer screens.
- Easier to calculate collisions with other sprites.

Disadvantages of using coordinates:

- We must convert the coordinates into a screen address before we can plot to the screen.

The standard convention for defining coordinates:

- 'x' is used to define the horizontal position and 'y' to define the vertical position.
- a coordinate is defined by both x and y. i.e. (x,y) defines a position.

Coordinates are always defined relative to an origin point.

The standard convention:

- The origin with coordinate (0,0) is the top-left of the screen.
- The horizontal position increases from left to right.
- The vertical position increases from top to bottom.

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

- The origin is (0,0) (top-left of the screen area)
- Top-right is at (159,0)
- Bottom-left is at (0,199)
- Bottom-right is at (159,199)

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 Signal | Signal Source | Signal name
--- | --- | ---
A15 | 6845 | MA13
A14 | 6845 | MA12
A13 | 6845 | RA2
A12 | 6845 | RA1
A11 | 6845 | RA0
A10 | 6845 | MA9
A9 | 6845 | MA8
A8 | 6845 | MA7
A7 | 6845 | MA6
A6 | 6845 | MA5
A5 | 6845 | MA4
A4 | 6845 | MA3
A3 | 6845 | MA2
A2 | 6845 | MA1
A1 | 6845 | MA0
A0 | Gate-Array | CCLK

Notes

- CRTC register 9 defines the number of scanlines per CRTC character.
- CRTC register 1 defines the width of the visible area in CRTC characters. The CPC display hardware fetches two bytes per CRTC character. Therefore the length of a CRTC scanline in bytes is (R1*2).
- CRTC register 6 defines the height of the visible area in CRTC character lines. Therefore the total height of the visible area in CRTC scanlines is (R9+1)*R6
- CRTC register 12 and 13 defines both the start of the screen in RAM, and one method for overscan.

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 Name | Description
--- | ---
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:

- The functions are stored in ROM and they are accessed via the firmware jumpblock which is in RAM. Executing a function requires additional CPU time compared to using your own function in RAM, or even to 'inline' these functions where you are using them.
- These functions assume the screen is 80 CRTC characters wide. If you want to use a different sized screen (e.g. overscan or Spectrum sized), then you must use your own implementation.
- These functions are designed so they will work even if the screen has been scrolled (using the firmware functions), so are not optimal in all cases.
- You must use the firmware jumpblock (for compatibility with all CPC versions) and therefore must also have the firmware structures in RAM (low jumpblock, high jumpblock and some kernel functions). These jumpblocks and structures reduce the total amount of free RAM you can use for your programs. If you take control of the computer and use your own functions you can 'turn off' the firmware and then use these RAM areas for your own needs.

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:

- The following code should be inlined into your code for the fastest results.
- Some values will need to be changed depending on the values you have used for CRTC register 9 and CRTC register 1, these values are marked by comments in the code.
- This code will work for any 16K screen with any base address.
- This code will work for both a static and a hardware scrolled screen

Parameters:

- HL = current memory address
- B = current scan line within current CRTC character line. At the start this is initialised with the initial scan 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