Painting pixels: Introduction to video memory

Pincha aquí para verlo en español

As we saw in the previous tutorial Filling the screen with colors in c and assembler (Pasmo, z88dk and sdcc), Amstrad CPC has three video modes: "Mode 0" 160 × 200 pixels with 16 colors, "Mode 1" 320 × 200 pixels with 4 colors and "Mode 2" 640 × 200 pixels with 2 colors. The video memory is located between the addresses C000 to FFFF, ie has a size of 3FFF (16,383) bytes. Depending on the mode, each byte represents 2, 4 or 8 pixels of the screen. But the video memory is linear? ie, the first byte is the upper left corner of the screen and the last byte is the bottom right?. I'm afraid not.  On this subject, Amstrad CPC design left a huge surprise, which makes complicated (and fun) graphics programming for cpc .

To see how video memory is structured, nothing easier than writing a program that fills it, to see how the lines are distributed across the screen. Source code for sdcc (at the end of tutorial you can download a zip) :

///////////////////////////////////////////////////////////////////////
// pixel01.c
// Fillin the screen with random colors
// Mochilote - www.cpcmania.com
////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>

void main()
{
  unsigned char *pScreen = (unsigned char *)0xC000;
  unsigned int nByte = 0;

  //SCR_SET_MODE 0
  __asm
    ld  a, #0
    call  #0xBC0E
  __endasm;

  for(nByte = 0; nByte < 0x3FFF; nByte++)
    pScreen[nByte] = (unsigned char)(rand() % 256);
  
  //KM_WAIT_CHAR  
  __asm
    call #0xBB06
  __endasm; 
}

Compile and generate dsk with the following commands (pixel01.bat):

sdcc -mz80 --code-loc 0x6038 --data-loc 0 --no-std-crt0 crt0_cpc.rel pixel01.c
hex2bin pixel01.ihx
CPCDiskXP -File pixel01.bin -AddAmsdosHeader 6000 -AddToNewDsk pixel01.dsk

Run in the emulator and get this:

fill

This sequence of lines is very familiar to us if we remember the load of the display screen of many games (especially tape). As we see the lines are filled 'linearly' from left to right, but at the end of the line, instead of going to the next lower skips 8...

In this table (taken from Amstrad CPC Firmware Manual) we see the organization of video memory:

LINE R0W0 R0W1 R0W2 R0W3 R0W4 R0W5 R0W6 R0W7
1 C000 C800 D000 D800 E000 E800 F000 F800
2 C050 C850 D050 D850 E050 E850 F050 F850
3 C0A0 C8A0 D0A0 D8A0 E0A0 E8A0 F0A0 F8A0
4 C0F0 C8F0 D0F0 D8F0 E0F0 E8F0 F0F0 F8F0
5 C140 C940 D140 D940 E140 E940 F140 F940
6 C190 C990 D190 D990 E190 E990 F190 F990
7 C1E0 C9E0 D1E0 D9E0 E1E0 E9E0 F1E0 F9E0
8 C230 CA30 D230 DA30 E230 EA30 F230 FA30
9 C280 CA80 D280 DA80 E280 EA80 F280 FA80
10 C2D0 CAD0 D2D0 DAD0 E2D0 EAD0 F2D0 FAD0
11 C320 CB20 D320 DB20 E320 EB20 F320 FB20
12 C370 CB70 D370 DB70 E370 EB70 F370 FB70
13 C3C0 CBC0 D3C0 DBC0 E3C0 EBC0 F3C0 FBC0
14 C410 CC10 D410 DC10 E410 EC10 F410 FC10
15 C460 CC60 D460 DC60 E460 EC60 F460 FC60
16 C4B0 CCB0 D4B0 DCB0 E4B0 ECB0 F4B0 FCB0
17 C500 CD00 D500 DD00 E500 ED00 F500 FD00
18 C550 CD50 D550 DD50 E550 ED50 F550 FD50
19 C5A0 CDA0 D5A0 DDA0 E5A0 EDA0 F5A0 FDA0
20 C5F0 CDF0 D5F0 DDF0 E5F0 ED50 F550 FD50
21 C640 CE40 D640 DE40 E640 EE40 F640 FE40
22 C690 CE90 D690 DE90 E690 EE90 F690 FE90
23 C6E0 CEE0 D6E0 DEE0 E6E0 EEE0 F6E0 FEE0
24 C730 CF30 D730 DF30 E730 EF30 F730 FF30
25 C780 CF80 D780 DF80 E780 EF80 F780 FF80
spare start C7D0 CFD0 D7D0 DFD0 E7D0 EFD0 F7D0 FFD0
spare end C7FF CFFF D7FF DFFF E7FF EFFF F7FF FFFF

The three video modes have 200 lines in height, so that these addresses are fixed for all three modes. Each of these 200 lines, is 80 bytes in size, representing 160, 320 or 640 pixels in width depending on the mode. As we said before, each byte represents 2, 4 or 8 pixels of the display depending on the mode, but to further complicate the programming, the bits of each pixel are arranged in the byte as follows:

  • Mode 2, 640×200, 2 colors (each byte of video memory represents 8 pixels):
    bit 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0
    pixel 0 pixel 1 pixel 2 pixel 3 pixel 4 pixel 5 pixel 6 pixel 7

  • Mode 1, 320×200, 4 colors (each byte of video memory represents 4 pixels):
    bit 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0
    pixel 0 (bit 1) pixel 1 (bit 1) pixel 2 (bit 1) pixel 3 (bit 1) pixel 0 (bit 0) pixel 1(bit 0) pixel 2 (bit 0) pixel 3 (bit 0)

  • Mode 0, 160×200, 16 colors (each byte of video memory represents 2 pixels):
    bit 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0
    pixel 0 (bit 0) pixel 1 (bit 0) pixel 0 (bit 2) pixel 1 (bit 2) pixel 0 (bit 1) pixel 1 (bit 1) pixel 0 (bit 3) pixel 1 (bit 3)

Seeing all this information and this disorder one can only think that the person who designed this was not quite in their right mind...

Practical examples:

  • In Mode 2, set pixel x=81, y=7 to color 1: First look at the table line 7 (start counting from 0) and the address is F800, as in mode 2 each byte is 8 pixels, to move to the byte where the 81 pixel horizontal is, we increase the direction 81/8 = 10 and we have 1 of rest , add 10 to the direction and gives us F80A, now in that direction to write the pixel 1 we have set to 1 bit 6.
  • In Mode 1, set pixel x=50, y=22 to color 3: The direction of line 22 in the table is F0A0, to move to the pixel 50, add to the address 50/4 = 12 and we left over 2, the sum gives F0AC, and put the color 3 to the pixel 2 of the direction, we must set to 1 bits 5 and 1.

If you look in the address table will see that there is a relationship between them, grouped in 8 of 8 lines, with breaks between them of 2048 bytes and 80 bytes for the group of 8 below. Through this relationship we can easily obtain the address of any line with the following rule:

Address = 0xC000 + ((Line / 8) * 80) + ((Line % 8) * 2048)

Let's apply what we just learned on the previous program, to fill the screen from top to bottom. Source code for sdcc (at the end of tutorial you can download a zip):

////////////////////////////////////////////////////////////////////////
// pixel02.c
// Calculating the addresses of the display
// Mochilote - www.cpcmania.com
////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>


unsigned char *GetLineAddress(unsigned char nLine)
{
  return (unsigned char *)0xC000 + ((nLine / 8) * 80) + ((nLine % 8) * 2048);
}


void main()
{
  unsigned char *pScreen = (unsigned char *)0xC000;
  unsigned int nByte = 0;
  unsigned int nLine = 0;

  //SCR_SET_MODE 0
  __asm
    ld  a, #0
    call  #0xBC0E
  __endasm;

  for(nLine = 0; nLine < 200; nLine++)
  {
    pScreen = GetLineAddress(nLine);

    for(nByte = 0; nByte < 80; nByte++)
      pScreen[nByte] = (unsigned char)(rand() % 256);
  }

  //KM_WAIT_CHAR  
  __asm
    call #0xBB06
  __endasm; 
}

Compile and generate dsk with the following commands (pixel02.bat):

sdcc -mz80 --code-loc 0x6038 --data-loc 0 --no-std-crt0 crt0_cpc.rel pixel02.c
hex2bin pixel02.ihx
CPCDiskXP -File pixel02.bin -AddAmsdosHeader 6000 -AddToNewDsk pixel02.dsk

Run in the emulator and get this:

fill

And now more difficult still, let's fill the screen pixel by pixel columns from left to right and to do so, you have to code a function to fill the corresponding bits for each pixel and color. Source code for sdcc (at the end of tutorial you can download a zip):

////////////////////////////////////////////////////////////////////////
// pixel03.c
// Filling the screen from left to right
// Mochilote - www.cpcmania.com
////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>


unsigned char *GetLineAddress(unsigned char nLine)
{
  return (unsigned char *)0xC000 + ((nLine / 8) * 80) + ((nLine % 8) * 2048);
}

void SetMode0PixelColor(unsigned char *pByteAddress, unsigned char nColor, unsigned char nPixel)
{
  unsigned char nByte = *pByteAddress;

  if(nPixel == 0)
  {
    nByte &= 85;

    if(nColor & 1)
      nByte |= 128;

    if(nColor & 2)
      nByte |= 8;

    if(nColor & 4)
      nByte |= 32;

    if(nColor & 8)
      nByte |= 2;
  }
  else
  {
    nByte &= 170;

    if(nColor & 1)
      nByte |= 64;

    if(nColor & 2)
      nByte |= 4;

    if(nColor & 4)
      nByte |= 16;

    if(nColor & 8)
      nByte |= 1;
  }

  *pByteAddress = nByte;
}

void main()
{
  unsigned char *pScreen = (unsigned char *)0xC000;
  unsigned int nLine = 0;
  unsigned int nColumn = 0;
  unsigned char nColor = 0;
  unsigned char nPixel = 0;

  //SCR_SET_MODE 0
  __asm
    ld  a, #0
    call  #0xBC0E
  __endasm;

  for(nColumn = 0; nColumn < 160; nColumn++)
  {
    nColor = (nColor + 1) % 16;
    nPixel = nColumn % 2;

    for(nLine = 0; nLine < 200; nLine++)
    {
      pScreen = GetLineAddress(nLine) + nColumn / 2;
      SetMode0PixelColor(pScreen, nColor, nPixel);
    }
  }

  //KM_WAIT_CHAR  
  __asm
    call #0xBB06
  __endasm; 
}

Compile and generate dsk with the following commands (pixel03.bat):

sdcc -mz80 --code-loc 0x6038 --data-loc 0 --no-std-crt0 crt0_cpc.rel pixel03.c
hex2bin pixel03.ihx
CPCDiskXP -File pixel03.bin -AddAmsdosHeader 6000 -AddToNewDsk pixel03.dsk

Run in the emulator and get this:

fill

Finally, let's see an example, a function PutPixelMode0 complete with a bouncing pixel across the screen. Source code for sdcc (at the end of tutorial you can download a zip):

////////////////////////////////////////////////////////////////////////
// pixel04.c
// Bouncing pixel
// Mochilote - www.cpcmania.com
////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>

void SetMode0PixelColor(unsigned char *pByteAddress, unsigned char nColor, unsigned char nPixel)
{
  unsigned char nByte = *pByteAddress;

  if(nPixel == 0)
  {
    nByte &= 85;

    if(nColor & 1)
      nByte |= 128;

    if(nColor & 2)
      nByte |= 8;

    if(nColor & 4)
      nByte |= 32;

    if(nColor & 8)
      nByte |= 2;
  }
  else
  {
    nByte &= 170;

    if(nColor & 1)
      nByte |= 64;

    if(nColor & 2)
      nByte |= 4;

    if(nColor & 4)
      nByte |= 16;

    if(nColor & 8)
      nByte |= 1;
  }

  *pByteAddress = nByte;
}

void PutPixelMode0(unsigned char nX, unsigned char nY, unsigned char nColor)
{
  unsigned char nPixel = 0;
  unsigned int nAddress = 0xC000 + ((nY / 8) * 80) + ((nY % 8) * 2048) + (nX / 2);
  nPixel = nX % 2;

  SetMode0PixelColor((unsigned char *)nAddress, nColor, nPixel);
}


void main()
{
  unsigned char *pScreen = (unsigned char *)0xC000;
  int nX = 81;
  int nY = 101;
  char nYDir = 1;
  char nXDir = 1;

  //SCR_SET_MODE 0
  __asm
    ld a, #0
    call #0xBC0E
  __endasm;

  while(1)
  {
    nX += nXDir;
    nY += nYDir;

    if(nX < 0)
    {
      nX = 0;
      nXDir = 1;
    }

    if(nX >= 160)
    {
      nX = 159;
      nXDir = -1;
    }

    if(nY < 0)
    {
      nY = 0;
      nYDir = 1;
    }

    if(nY >= 200)
    {
      nY = 199;
      nYDir = -1;
    }

    PutPixelMode0(nX, nY, rand() % 16);
  }
}
////////////////////////////////////////////////////////////////////////

Compile and generate dsk with the following commands (pixel04.bat):

sdcc -mz80 --code-loc 0x6038 --data-loc 0 --no-std-crt0 crt0_cpc.rel pixel04.c
hex2bin pixel04.ihx
CPCDiskXP -File pixel04.bin -AddAmsdosHeader 6000 -AddToNewDsk pixel04.dsk

Run in the emulator and get this:

fill

 

You could download a zip with all files (source code, bat to compile, binary and dsk's) here: Painting_pixels_introduction_to_video_memory.zip

 

www.CPCMania.com 2012