Programming:Hardware scrolling

28,210 bytes removed, 08:08, 25 May 2014
Undo revision 89836 by [[Special:Contributions/Gryzor|Gryzor]] ([[User talk:Gryzor|talk]])
**The following was written by [[User:Arnoldemu|Arnoldemu]] and taken from this forum thread: [ Uridium with Hardware Scrolling]** Hardware scrolling==================The display is hardware scrolled by changing CRTC register 12 and 13.To understand how the hardware scrolling works we need to understand how the CRTC's outputs are connectedwithin the CPC.This is how the CRTC outputs are connected in the CPC:Z80 6845A15 MA13A14 MA12A13 RA2A12 RA1A11 RA0A10 MA9A9 MA8A8 MA7A7 MA6A6 MA5A5 MA4A4 MA3A3 MA2A2 MA1A1 MA0A0 CCLKMA0-MA13 and RA0-RA2 are outputs from the CRTC. CCLK is an output from the Gate-Array.Within the CRTC there is an MA counter and a RA counter.MA0-MA13 represents the current value of the MA counter.RA0-RA2 represents the current value of the RA counter.The MA counter is reloaded at the start of the frame from the values in CRTC register 12 and 13 and incremented for each char. We therefore have control over this.The RA counter can't be set through the CRTC registers. We can only control it's maximum value through CRTC register 9.Therefore we can control the start address of the display and with this we can perform hardware scrolling.From the assignments above we can see bits 5 and 4 of CRTC register 12 define Z80 A15 and A14 and are effectively the 16KB page in the base 64K of RAM where the screen will start.bit 5, bit 4:0 0: &0000-&3fff0 1: &4000-&7fff1 0: &8000-&bfff1 1: &c000-&ffffNot all of the CRTC's MA output is connected in the CPC. This causes the display to repeat/wrap around to thestart. (note 2)If MA=&3ff and is then incremented, internally the CRTC has the value &400, but because of the assignments above, MA9-MA0 outputs are zero and the graphics appear to wrap around.However, if MA=&fff is incremented, then MA12 and MA13 changes and we have the basis for 32KB overscan screen.So to ensure the screen wraps for hardware scrolling bits 3 and 2 of register 12 should not be both 1.We then use bits 1 and 0 of register 12 and bits 7..0 of register 13 to define the offset for the scroll.The offset range then becomes 0-&3ff, we can use any offset value in this range, combined with the page to set the start of the screen and perform hardware scrolling.Scrolling uses the entire offset range. This means we are sweeping through the entire 16KB page.If R9 is 7 or greater then we can't use any of the 16KB page to store graphics or code. (note 1)The MA/RA outputs are connected to RAM, they are used both for RAM refresh, and also to tell the Gate-Array where to fetch the pixels for the display.For each CRTC character the CPC video hardware fetches 2 bytes (CCLK changes between 0 and 1). Therefore the minimum by that can be scrolled horizontally by changing only R12/R13 is 2 bytes at a time. In Mode 0 with 2 pixels per byte, this is a minimum of 4 pixels.In Mode 1 with 4 pixels per byte, this is a minimum of 8 pixels.In Mode 2 with 8 pixels per byte, this is a minimum of 16 pixels.CCLK is not under our control it is generated by the Gate-Array, therefore we could not use it tooffset the screen for example.The width of a CRTC character is therefore 2 bytes.The vertical height of a CRTC character is defined by R9+1 (R9 = Max Raster). For each character line, RA is reset, and incremented for each scanline of the character row.Normally R9 is set to 7, which corresponds to a height of 8 lines. We can change the value in R9 and directly the height of the visible display, but we can't use it to make smoother vertical scroll.So the normal minimum scroll changing only R12 and R13 is 2 bytes horizontally and 8 lines vertically. By changing the start address of the screen we simulate scrolling without moving any pixel data.If width*height of the visible display is greater than &400, the screen will repeat.So another condition for hardware scrolling is that the width (defined by CRTC R1 and the height defined by CRTC R6 must be less than or equal to &400).It is worth noting that in the CPC, the CRTC only has access to the base 64K of RAM. note 1: If R9 is less than 7, then RA0-RA2 doesn't cover it's entire range, we can therefore usesame areas of screen ram to store code/data at the expensive of a shorter screen.Scrolling on the plus=====================Scrolling on the plus can be achieved in the same way, but on the Plus we have additional hardware thathelps:1. Soft horizontal scroll2. Soft vertical scroll3. Masking the left side of the screenThe screen may also be scrolled on the plus by using the split screen register and setting that with the same values as we would for R12/R13.You still need to update the scroll and draw it in the same way, but the additional helper hardwaremakes it much easier to achieve a smoother scroll.Screen split example: the offset====================== To make it easier in code, we treat the offset and the page seperatly, and program it like so: . . . ld hl,(offset) ;; lock offset to range &0000-&03ff. ld a,h and &3 ld h,a ;; combine base with bits 2 of offset ld a,(base) or h ld bc,&bc0c out (c),c ;; select register 12 inc b out (c),a ;; write register 12 ld bc,&bc0d out (c),c ;; select register 13 inc b out (c),l ;; write offset lower 8 bits . . . offset: defw 0base: defb &30 ;; corresponds to &c000-&ffff. Basis of Scrolling horizontally===============================Scrolling horizontally involves incrementing or decrementing our offset.e.g.ld hl,(offset)inc hlld (offset),hlorld hl,(offset)dec hlld (offset),hlIf we increment we have this scenario: before: ABCDEFGH IJKLMNOP QRSTUVWX after: BCDEFGHI JKLMNOPQ RSTUVWXA The CRTC characters on the left appear on the right. If we decrement we have this scenario: before: ABCDEFGH IJKLMNOP QRSTUVWX after: XABCDEFG HIJKLMNO PQRSTUVW The CRTC characters on the right appear on the left.To scroll an image we need to draw 1 column (with the height of the screen) with new graphics. If we didn't do thatthe same graphics would wrap around when scrolling. Basis of Scrolling vertically=============================The number of characters displayed on each line is defined by R1 of the CRTC. To scroll vertically we need to add or subtract this number from our offset so that it moves up/down by an entire line.For a normal screen of 40 characters (R1=40):e.g.ld hl,(offset)ld bc,40add hl,bcld (offset),hlld hl,(offset)ld bc,40or asbc hl,bcld (offset),hlIf we add we have this scenario: before: ABCDEFGH IJKLMNOP QRSTUVWX after: IJKLMNOP QRSTUVWX ABCDEFGH The CRTC characters on the top appear on the bottom. If we subtract we have this scenario: before: ABCDEFGH IJKLMNOP QRSTUVWX after: QRSTUVWX ABCDEFGH IJKLMNOP The CRTC characters on the bottom appear at the top. To scroll an image we need to draw 1 row with new graphics. Scrolling vertically and horizontally at the same time======================================================Scrolling diagonally involves a scroll in the horizontal and vertical. And in this case we need to draw 1 row and 1 column to scroll diagonally.To achieve a perfect 45 degree diagonal scroll in mode 0, we need to scroll 1 byte in horizontal and 2 scanlines vertically.Or 2 bytes horizontally and 4 scanlines vertically.Example code that shows changing the offset to make a horizontal and vertical scroll: columns/rows====================When you draw a column/row to the screen to update the scroll you will notice it can flickers. Thishappens when it takes time to draw the column/row.When scrolling vertically this can be avoided if the screen is made shorter. Consider this (the -- line) indicates where the border ends: before: ABCDEFGH -------- IJKLMNOP QRSTUVWX after: IJKLMNOP -------- QRSTUVWX ABCDEFGHIn this example the screen is a single row tall. We could draw the line IJKLMNOP safely to update the scroll, then set R12/R13 after to reveal it.The line would never flicker.However the same is not true with horizontal because the way the screen is setup:This is how it is setup:0 -> R1-1R1 -> R1*2-1If we try and draw to R1 it's already visible. We can't make the screen shorter or wider.If we made it shorter the problem just moves. So it's not possible with horizontal scroll.To avoid the flicker we could have faster drawing functions or use double buffer.NOTE: On the Plus we can hide the left column by extending the border into it. This makes the screen narrower but gives us the advantage that we can draw a new column here without flickering and in additionif we're using Plus hardware sprites, we don't need to use double buffer and the extra complicationsit can add.Drawing note============All drawing is done in units of CRTC chars. Therefore you can optimise the drawing code to take thisinto account.You don't need to worry about the bad region horizontally, and vertically you can even use SET/RESinstead of SCR_NEXT_LINE if you re-order your code. If you are drawing a tile from a tile map, split it into smaller chunks or make your tiles the same size as the crtc characters.e.g.draw_tile:ld a,(de) ;; read tile gfxld (hl),a ;; write to screeninc l ;; INC L here because memory address will be even, incrementing it will make it ;; odd but not overflow past de ;; we could make this inc e if the tile graphics were aligned to 16 bytes.ld a,(de) ;; read tile gfxld (hl),a ;; write to screendec l ;; DEC L here to go back to the previous byteinc de ;; we could make this inc e if the tile graphics were aligned to 16 bytesset 3,h ;; move to next lineld a,(de) ;; read tile gfxld (hl),a ;; write to screeninc l ;; INC L here because memory address will be even, incrementing it will make it ;; odd but not overflow past de ;; we could make this inc e if the tile graphics were aligned to 16 bytes.ld a,(de) ;; read tile gfxld (hl),a ;; write to screendec l ;; DEC L here to go back to the previous byteinc de ;; we could make this inc e if the tile graphics were aligned to 16 bytesset 4,h;; other lines cut, but you get the idea ;);;etc...Calculating screen address==========================A screen address is a RAM memory address corresponding to a position on the display.The scroll offset and base define the top-left of the display area. The following code assumes we are calculating the address of the new column following updating the offsetwith the appropiate scroll value.We can calculate the screen address of this simply:ld hl,(offset) ;; offset in crtc charsadd hl,hl ;; x2 (because 2 bytes per crtc char)ld a,h ;; enforce range 0-&3ff.and &3ld h,ald a,(base)add a,a ;; x2add a,a ;; x4 (to move bits into bit 7,6)or h ;; combine with offsetld h,a;; HL = screen address of top-leftWe can use this fact for drawing a column on the left, or a row at the top.To calculate the top-right side of the screen for drawing a column on the right:ld hl,(offset)ld bc,40-1 ;; width of screen in chars (R1-1)add hl,bcld a,hand &3ld h,ald a,(base)add a,aadd a,aor hld h,a;; HL = screen addressTo calculate the bottom-left for drawing a row at the bottom:;; perform the following (height-1)*widthld b,25-1 ;; height of screen in chars (R6-1)ld hl,0ld de,40 ;; width of screen in chars (R1-1)mull: add hl,dedjnz mul1;; HL = offset from the top-left to bottom-right ignoring scroll offsetld de,(offset)add hl,de ;; add on the offsetld a,hand &3ld h,ald a,(base)add a,aadd a,aor hld h,a;;HL = screen addressNOTE: Because the screen scrolls in fixed units, the calculation is made simple.NOTE: In the above code we could avoid the multiply if we never change the width and height of the scroll area, the value therefore becomes a constant we can add.Screen addresses in a hardware scrolling screen===============================================When we are drawing to a hardware scrolling screen we need different functions for moving to the next line (SCR_NEXT_LINE), moving to the previous line (SCR_PREV_LINE), moving to the previousbyte (SCR_PREV_BYTE) and moving to the next byte (SCR_NEXT_BYTE) from our current position. These functions need to work with the wrapping of the screen.e.g.A normal sprite drawing routine looks like this:;; HL = memory address;; DE = sprite pixel datald c,sprite_widthld b,sprite_heightloop_height:push hlloop_width:ld a,(de) ;; read sprite pixelsld (hl),a ;; write to screeninc hl ;; increment screen position to move to next byte to the rightinc de ;; increment sprite pixel positionsdec cjr nz,loop_widthpop hlcall SCR_NEXT_LINEpop bcdjnz loop_heightIt is worth noting that the assignment of CRTC's MA to memory address which automatically wraps the screen causes additional problems for us.These problems do not exist if the screen is static or software scrolled.Under BASIC press RETURN until the screen scrolls.Type the following:poke &c7ff,&ffpoke &c000,&ffYou will see that &c000 is to the right of &c7ff. A simple increment makes &c800 from &c7ff and not &c000.The same happens at &cfff,&d7ff,&dfff,&e7ff,&efff,&f7ff and &ffff. These are byte offsets of:&7ff,&fff,&17ff, &1fff, &27ff, &2fff,&37ff and &3fff.These byte offsets represent the point at which the screen wraps.We need something a bit different:;; HL = memory address;; DE = sprite pixel datald c,sprite_widthld b,sprite_heightloop_height:push hlloop_width:ld a,(de) ;; read sprite pixelsld (hl),a ;; write to screencall SCR_NEXT_BYTEinc de ;; increment sprite pixel positionsdec cjr nz,loop_widthpop hlcall SCR_NEXT_LINEpop bcdjnz loop_heightAs you can see additional work is done, so drawing to a hardware scrolling screen can be slower.Look at the functions in the OS to see implementations that work correctly for hardware scrolling screens.It is worth noting that because the hardware fetches two bytes for each CRTC char we could have2 sprite drawing functions:This would draw a sprite which has a screen address which is even:;; HL = memory address;; DE = sprite pixel datald c,sprite_width/2ld b,sprite_heightloop_height:push hlloop_width:ld a,(de) ;; read sprite pixelsld (hl),a ;; write to screeninc hl ;; increment to odd byteinc deld a,(de)ld (hl),acall SCR_NEXT_BYTEinc de ;; increment sprite pixel positionsdec cjr nz,loop_widthpop hlcall SCR_NEXT_LINEpop bcdjnz loop_heightThis would draw a sprite that has a screen address that is odd:;; HL = memory address;; DE = sprite pixel datald c,sprite_width/2ld b,sprite_heightloop_height:push hlloop_width:ld a,(de) ;; read sprite pixelsld (hl),a ;; write to screencall SCR_NEXT_BYTEinc deld a,(de)ld (hl),ainc hlinc de ;; increment sprite pixel positionsdec cjr nz,loop_widthpop hlcall SCR_NEXT_LINEpop bcdjnz loop_heightNOTE: That this special code is only needed to cope with the problem region. If a sprite doesn'toverlap this region then we can use normal INC to move to the next byte to the right.NOTE: When we draw a column to the screen we need the special SCR_NEXT_LINE but we never need the special SCR_NEXT_BYTE.NOTE: When we draw a row we may need to use the special SCR_NEXT_BYTE in addition to the SCR_NEXT_LINEdepending on how we draw it.We can avoid the problem region:* If we are scrolling vertically only:If we ensure the problem area always sits on the left or always sits on the right side of the screen we don't need to worry about it.To do this set the screen width so that it divides into &400 exactly:e.g. &400/width has a zero remainder. A good value is 32.By doing this when we scroll it will always remain on the left or right side and never move.A width of 30 would not divide exactly and the problem area would appear on the screen eventually.* If we are scrolling horizontally, or horizontally and vertically:We can limit the amount we scroll.The maximum scroll offset is defined as follows:&400-(R1*R6)This means at maximum offset the problem is in the bottom right and never reached.NOTE: If we are using hardware sprites ONLY on the Plus we don't need to worry about this problem area. The hardware sprites don't have this problem.Hardware scrolling and double buffering=======================================If we change our base value between 2 values we can do double buffering and hardware scrolling.It is worth noting that if you have scrolled the screen and drawn a column or row, you will need to redraw/copy the column onto the other screen so that the graphics don't flicker when you swapbetween them.Hardware scrolling and panels=============================It is possible to have a panel on a hardware scrolling screen, however when you scroll you must redraw the panel.This is done in Rick Dangerous and Sly Spy for example. Example: TODO.NOTE: On the PLUS we can use the split screen feature to show the panel avoiding the need to redraw. Calculating screen address==========================If we are to draw sprites, we need to convert between our sprite coordinates and a memory addressfor drawing sprites.If we consider the top-left to be 0,0 and we have coordinates where X increases going rightand Y increases going down. X is in byte coordinates, Y is in scan-lines.First we must calculate which char line we are on.Char_line = y/8Now we can do this:offset from start = char_line*R1we then add the scroll offset:total offset = offset from start + scroll offsetwe then work out the offset from the start of line:offset_from_start_of_line = x/2then we add this:total offset = total offset + offset_from_start_of_linethis gives the total offset.we range fix it:total offset = total offset and &3ffthen multiply by 2 to get byte offset:total_byte_offset = total_offset * 2This gives us the screen position of the crtc char of our coordinate.the scan line offset is the scan line within the char:scan_line = y & 7.We now need to add the scan-line offset:total_byte_offset = total_byte_offset + (scan_line * 0x0800)finally, if x was odd, we incremnent the value giving the final address.We can simplify this down a bit by using look-up tables. Smoother Horizontal scrolling using R3======================================We can make the horizontal scrolling slower so that it scrolls at the rate of 1 byte comparedto 2 bytes.This type of scrolling relies on the monitor's response to the horizontal sync output from the CPC and the way the CPC outputs the sync. It always works on CPC monitors but doesn't work on some televisions or modulators.R3 is used to define the vertical and horizontal sync widths. The sync is passed from the CRTC through the Gate-Array and out to the monitor.For programmed widths greater than 6, the gate-array will output a sync of width 6 to the monitor.For programmed widths less than 6, the gate-array will output a sync of that width to the monitor (e.g. 1,2,3,4 or 5). NOTE: a sync width of 0 is a special case. On some CRTC it means no horizontal sync, for others it means a horizontal sync of width 16.If the sync width is programmed to 5,4,3,2,1 the screen will increasingly move horizontally on the monitor by the width of a byte. Smaller sync widths are not advised because this leads to unstable display (a display which is distorted on some monitors). Larger sync widths are fixed at a width of 6. The sweet spot is changing between 5 and 6.Now to make the horizontal scroll smoother, we use this fact.We follow this sequence:1. R3 = 6, scroll offset = 02. R3 = 5, scroll offset = 03. R3 = 6, scroll offset = 14. R3 = 5, scroll offset = 1etc.NOTE: Doing this will give the illusion we are scrolling at the rate of 1 byte horizontally, howeverthe left and right edges of the display will flicker.One way to avoid this is to expand the width of the display horizontally so this is hidden, e.g. by setting the width to 48.But, if we expand the width, we also have to reduce the height otherwise the graphics will repeat for the same reasons we need them to for the hardware scroll to work.To avoid repetition of graphics, width * height must not be greater than &400 (the maximum scroll offset).So if the width is set to 48, the height can be a maximum of 12 lines. (&400/48 = 21)Legend of Kage uses this method.Example code showing this: If you have a panel on the screen you will need to change R3 back to 6 so the panel doesn't also move.However, the monitor doesn't respond immediately, it can take up to 16 scanlines for it to do so.Games such as Prehistorik 2 have a gap between the scroll and the panel for this reason.Example: TODO.NOTE: On the Plus we don't need to use R3, we can set the smooth horizontal scroll value instead of R3 and in addition we would not get the flickering sides. Vertical scrolling using R5====================================R5 is used to add additional scan-lines to the frame. There are 2 ways to use R5 to achieve line by line vertical scrolling.Method 1: Without "rupture": If we add additional lines the display will move vertically in a similar way to using R3 to move the screen horizontally. We add between 0 and R9-1 lines (i.e. between 0 and 7).e.g.1. R5 = 0, scroll offset = 02. R5 = 1, scroll offset = 03. R5 = 2, scroll offset = 04. R5 = 3, scroll offset = 05. R5 = 3, scroll offset = 06. R5 = 4, scroll offset = 07. R5 = 5, scroll offset = 08. R5 = 6, scroll offset = 09. R5 = 7, scroll offset = 010. R5 = 0, scroll offset = 4011. R5 = 1, scroll offset = 4012. R5 = 2, scroll offset = 40etcNOTE: Using this method there will be flickering above and below the screen.NOTE: This method effectively shortens and lengthens the duration of a frame, and in addition theposition of the vertical sync. The frame is no longer fixed at 50hz. The CPC monitor is generally quite tolerant and will accept that, but televisions or modulators may not.Legend of Kage uses this method.Example code showing this: 2: Using "rupture".This method is more technical and involves good timing.For this method we need to split the screen into 3 sections. A top section which is static, a middle section which scrolls vertically (and can be horizontal too), a bottom section which is static.The top/bottom sections would normally show graphics but we turn the border on to cover them (R6=0).The middle section has graphics and we set the height to be the size we want.In the top section we set R5 to add additional lines to that section. This moves the next (middle) section up/down by one or more scanlines depending on the value we set.At the end of the scrolling section, we set R5 to compensate for this movement. We set R5 to 7 minus the value in the first section. This means that the amount we add in the first section and the amount we add in the third section is always the same, and normally it adds up to 8.In the last section we set R5=0. This ensures that the screen remains a constant duration AND the vsync doesn't move.This method works great but is harder to implement because you need good understanding of the CRTC,understand the "rupture"/"splitting" method. This method requires the display be actively maintained each frame and testing on multiple CRTCs to ensure it works on all.For games we use the CPC raster interrupts to perform these actions leaving the remaining timefree for the game to use.The rupture method works on all monitors and televisions.Mission Genocide uses this method.Example: TODO.NOTE: On the Plus we don't need to use either of these techniques. We can set the Plus soft verticalscroll. This will not cause flickering top/bottom and will not affect the display timing. scrolling without R3/R5================================ There are other ways to achieve smoother scrolling on the CPC without using R3/R5 at the expenseof more RAM or by reducing the screen area.If you are scrolling continuously (e.g. in a horizontal shoot em up), you can use two pages of 16KB.One page is offset by 1 byte compared to the other.By swapping between these and hardware scrolling you can scroll at a smoother rate.To achieve pixel-by-pixel scrolling in mode 0 at normal resolutions, you would need 4 x 16KB (all the RAM available to the CRTC). By combining this method with R3, you can achieve pixel by pixel hardware scrolling.The reason you need 4 screens is that normal scrolling is at a rate of 2 bytes which is 4 mode 0 pixels.If the screen height was reduced (e.g. to a half using R6), you could effectively have 2 screens in each 16KB. Each screen would be seperated by height*width chars. Now you can have pixel by pixel scrollingin 2 * 16KB. NOTE: You can't use R9 to reduce the height of the screen with this method because youwould need to be able to set the screen start using RA2-RA0 too which is not possible.Example: TODO.Scrolling a tilemap===================A tilemap consists of a rectangular 2 dimensional array of numbers (a tilemap has a width and a height). Each number in the tilemap is the index of the tile to display in that position.e.g.A tilemap of:0,0,0,1,1,11,1,1,2,2,20,1,0,1,0,0this uses 3 tiles. With indexs 0,1,and 2.We would have three tiles in our tileset.Each tile in the tileset has the same width and height.The tilemap is wider or taller (or both) than the screen.We only see a region of the tilemap at a time.When we scroll a tilemap we are moving through the tilemap, changing the location of the regionwe are looking at.So to scroll a tilemap we need to know the x,y position of this region. The width and height are fixedand defined by the size of our visible area.If the tile dimensions are the same as the size of a crtc char it is much easier for us. Otherwisewe need to have a idea of how far "into" the tile we are both horizontally and vertically.We update our position "within" the tile, then when we have moved a complete tile width we updatethe x position of our region. Similarly if we have moved a complete tile height we update the y position of our region.To display it, we initially draw all tiles in the region we can see.When we scroll, we need to then draw new pixels from the tiles we now see.If the tile is larger than a a crtc char then we draw part of the tile which is the same size as a crtc character.As we continue to scroll we draw another part of the tile and eventually the whole tile is scrolledinto view.For the cpc, we need to therefore calculate what parts of the tilemap to draw for the rows/columnswe scroll.Example: TODO.Sprites on a hardware scrolling screen======================================Like in other CPC games we will need to clip our sprites to the sides of the visible area if they aremeant to go outside the screen. If we don't do this they will wrap onto the other sides of the screen. e.g. if we move left then they will wrap onto the right side of the screen.Example: TODO.Erasing sprites===============Like in any other cpc game we must erase the sprites in their old position before drawing them in theirnew position.Like other games there are a few approaches that work.1. We could reserve another 16KB for a background screen. This screen doesn't have sprites but it does have the background. We erase the sprites by copying the background to the screen. If we are doublebuffering this means 3 * 16KB is used. This could work well in a 128KB game, where the background screencould be in the extra RAM.Example: extension to this is to only store a region equal to the size of the sprite:Example: We could identify which tiles are under the sprite. Erasing the sprites now involves redrawing those tiles but it's furthur complicated by the fact that on the left, top, right and bottom edges we could havepartial tiles (if the tiles are larger than a crtc character). This is slower but good wherewe have limited RAM.3. We could use a method used in Mission Genocide. If we sacrifice some colours, we can setup the paletteso we can use OR to draw a sprite and AND to remove it. This would be quick and is used in many hardwarescrolling games. Good for limited RAM, fast, but less colourful. Careful choice of colours meansthe background and foreground sprites look nice. Example: