While an
interrupt happens every 52 lines, we CAN trick the interrupt
into happening earlier using a trick called RVI (Rupture:
Vertical, invisible) This combines multiple lines into one, and makes the interrupt happen faster, but it's VERY complex to get working, and we can't look at it yet. |
![]() |
Reg Num | Abbrev | Name | Range | Bits | Default Value | 256x192 | 384x272(26k) | Details | CRTC Limitations |
&00 | HTOT | Horizontal Total | 0-255 | DDDDDDDD | 63 (&3F) | 63 | 63 | Physical width of screen -1 � Leave alone! | |
&01 | HDISP | Horizontal Displayed | 0-255 | DDDDDDDD | 40 (&28) | 32 | 48 | Logical width in Chars (8 pixels in mode 1) | |
&02 | HSYNC | Horizontal Sync Position | 0-255 | DDDDDDDD | 46 (&2E) | 42 | 51 | Logical Xpos | 2: HsncPos+HsyncWid must be < Htot |
&03 | V/HWID | Horiz. and Vert. Sync Widths | 0-15,0-15 | VVVVHHHH | 142 (&8E) | 134 (8,6) | 142 (8,14) | Hsync / Vsync area size � Leave alone! | 1+2: Vhei fixed to 16 |
&04 | VTOT | Vertical Total | 0-127 | -DDDDDDD | 38 (&26) | 38 | 38 | Physical height of screen -1 � Leave alone! | |
&05 | VADJ | Vertical Total Adjust | 0-31 | ---DDDDD | 0 | 0 | 0 | Scanline Offset | |
&06 | VDISP | Vertical Displayed | 0-127 | -DDDDDDD | 25 | 24 | 34 | Logical Height in Chars (8 Pixels) | |
&07 | VSYNC | Vertical Sync position | 0-127 | -DDDDDDD | 30 | 31 | 35 | Logical Ypos of screen | |
&08 | Interlace and Skew | 0-3 | ------DD | 0 | 0 | 0 | 0/2=off 1/3=on � Leave alone! | ||
&09 | MR | Maximum Raster Address | 0-31 | ---DDDDD | 7 | 7 | 7 | Scanlines per Char row � Leave alone! | |
&0A | Cursor Start Raster | 0-127 | -DDDDDDD | 0 | 0 | 0 | unused | ||
&0B | Cursor End Raster | 0-31 | ---DDDDD | 0 | 0 | 0 | unused | ||
&0C | DISPH | Display Start Address | 0-63 | xxPPSSOO | 00 /
16 / 32 / 48 |
00 /
16 / 32 / 48 |
12+1
/ 28 / 44+1 / 60 |
PP=Screen
Page (11=C000) S=Size(11=32k else 16k) O=Offset |
|
&0D | DISPL | Display Start Address | 0-255 | OOOOOOOO | 0 | 0 | 0 | O=Offset | |
&0E | CURH | Cursor Address | 0-63 | --DDDDDD | 0 | 0 | 0 | unused | |
&0F | CURL | Cursor Address | 0-255 | DDDDDDDD | 0 | 0 | 0 | unused | |
&10 | LPH | Light Pen Address | 0-63 | --DDDDDD | 0 | 0 | 0 | Read Only | |
&11 | LPL | Light Pen Address | 0-255 | DDDDDDDD | 0 | 0 | 0 | Read Only | |
internal | VCC | Vertical Character Count | Ypos of drawn line | ||||||
internal | R52 | Raster 52 interrupt timer | Time since last interrupt | ||||||
internal | HDC | Horizontal Display Counter | Current Hpos | ||||||
internal | HCC | Horizontal Character Count | Xpos of drawn line | ||||||
internal | VMA | Video Memory Address | Address of current screenpos | ||||||
internal | VLC | Vertical Line Count | Line of character block being drawn | ||||||
internal | VSC | Vertical Sync Counter | Winape emulator function � Time synce vsync | ||||||
internal | VTAC | Vertical Total Adjust Counter | Time since end of frame | ||||||
internal | HSC | Horizontal Sync Counter | Cycles during Hsync | ||||||
internal | VDUR | VDU Raster | Winape Emulator function � offset within display |
We can see the internal registers
within Winape Select Registers from the Debug menu We can also turn on ROW HIGHLIGHT - this will show a line on the screen when the emulator is paused, which shows what the beam is currently drawing |
![]() |
![]() |
![]() |
Remember,
the Memory address and screen size are only read in at the
start of a new screen, and some tricks (like mode changes and
RVI) need to be synchronized to the end of the line We'll need to keep an eye on HCC and VCC to do this, and ROW HIGHLIGHT will help make things more clear. |
The position of the screen, the
borders, and the Vblank locations are all controlled by the
registers, The current location of the beam is stored in the internal registers... The physical size of the screen is fixed, and there are limits to what the CRTC can do within the screen... but there are some tricks we can do to exploit the CRTC and get it to do things it usually wouldn't! |
![]() |
If
we mess with Hblank or Vblank areas, or VSYNC there's a very
good chance we're going to end up with an unstable screen! This can also happen if our screen width (HTOTAL) is too big, This is just part of the difficulty of these CRTC tricks, we're going to have to do some of the work of the CRTC ourselves if we want to do this kind of thing. |
![]() |
We can change colors at any
point during the screen, to get more than 4 colors on a Mode 1
screen (Like in ChibiAkumas) We saw last week that EGX switches the screen mode every line to simulate a new screen mode... But what if we want to have two areas of the screen that hardware scroll independently? we'd probably want to change registers R12 and R13 to set the display base... but unfortunately the CRTC only reads in this address when it starts drawing the screen... But we can Cheat!... we define a short screen... then as soon as it finishes, define a new screen.... this will trick the CRTC into starting again... re reading in all the registers, and starting a whole new screen... this is known as Rupture (also known as Horizontal Rupture) Unfortunately it's not so easy, we're going to have to trick the CRTC, and do a lot of the work of laying out the screen for it... so that we get the screen we want! Lets take a look! |
![]() |
![]() |
Now We've
done the first interrupt, we've covered all the complex
stuff... the other 5 interrupts are just using the same kind
of commands to repeat the procedure for another 4/5
'screens'... Having so many screens is probably excessive, you'd probably only want 2 in reality... one for your status, and one for the game - or one for player 1, and one for player 2! |
Each interrupt sets the screen settings for the FOLLOWING interrupt block, so Interrupt 1 sets 2's mode and address... so if you want to reprogram things you'll need to bear this in mind... | ![]() |
Different
CRTC versions have different limitations, and some don't like
things other's are OK with... This example should work ok on
all of them... |
![]() |
![]() |
Because we
can only set the start of the screen to a limited number of
memory locations, it can be difficult to get the screen to do
exactly what we want with R12 and R13... There is a solution... RVI (Rupture, Vertical - Invisible)... this creates a mini hidden screen at the start of a line a few pixels wide... the visible screen then starts on the next line - effectively manipluating the 3 bits that connot be set with R12+R13 |
We're going to set the Hsync
width as small as we can - this reduces the 'black areas' of the
off-screen parts - and will help stop 'black areas' when we have
an excessive offset |
![]()
|
||
We need to ensure interrupts are controlled, when an interrupt
occurs we're just going to immediately return, This program is very time sensitive, so our interrupt handler just returns as quickly as possible |
![]() |
||
We need to sync exactly with the screen, we're going to ensure we're consistently aligned to the interrupts by checking port &F5 and waiting for a new screen to start. | ![]() |
When our loop starts, we need to wait again for the first Vsync
of a new screen... When the new screen starts, we set the Vsync point to 255 - this is impossible, effectively ensuring that a Vsync cannot occur during our drawing of the screen |
![]() |
We need to wait a little while for the end of the Vsync, We will then start defining our physical screen |
![]() |
We are going to define some rather odd screens! Each 'character block' is just one line tall (defined by setting Reg 09 to &00)... Each 'Screen' is just 4 character blocks tall (defined by setting Reg 04 to &03)... This has the effect of making each screen just 4 pixels tall, and allows us to change screen address every 4 lines!... because we disabled Vsync, we can start a new screen after each one of these tiny screens! |
![]() |
We're going to use a 'Command list' for the main loop,this list
contains entries, one for every 4 pixel tall screen (so 280 lines
in total) The command list contains 4 bytes... The first two are the address of the 4 pixel tall screen (loaded into Reg 12 and 13)... the next is the Hsync position (Reg 2) - effectively this moves the horizontal position of the 4 pixel strip onscreen The final byte is a screen mode - we can change screen mode every 4 pixels if we wish! |
![]() ![]() |
During the main loop, we load each
of the 3 screen parameters, and OUT them to the video registers... If we 're changing the screen mode, we then Out this to the gate array... Once this is done, we perform a delay until the next 4 pixel screen starts... we define a large number of NOP commands (Byte &00) with a DS command. We repeat this whole procedure for every 4 pixel screen. |
![]() |
Once the screen has finished
drawing, we need to reset the screen settings so we can cause a
Vsync. Once we've done this, a vsync will occur, and when it does we can start the procedure again! |
![]() |
This
may seem pretty logical, but the trouble is the timing of the 4
pixel tall screen creation has to be EXACT, and it takes all the
CPU time. If we were making a game, we'd effectively have to fit the entire game-play code into the NOP sections |
![]() |
![]() |
Lesson
P43-
ULANext on the Spectrum NEXT The ULA (Uncommitted logic array) is responsible for the color graphics on the spectrum, giving a choice of 7 colors in 2 brightness for foreground and background, it gives reasonable color for limited memory... Unfortunately the high contrast palette makes the color clash a bit severe!... however the Spectrum NEXT offers an alternative... ULA NEXT! |
![]() |
ZXN_ULAplus.asm
|
![]() |
![]() |
In this
example we'll look at the options of ULA NEXT, this allows the
bits of the color attributes (&5800-&5AFF) to be
reconfigured... alternatively we can just alter NEXT colors
0-31 to change the default colors the regular ULA gives. |
Here's our Chibiakumas default screen... this is using the
classic spectrum coloring options! |
![]() |
ULA NEXT Recolor... 3 bit Fore / 4 bit Back (actually 5bit - 1 unused) | ULANEXT Alt ColorMap... 4 bit Fore / 4 bit Back |
![]() |
![]() |
This version has been recolored! 3 bits have been used for the
foreground color, and 4 bits for the background... We can now select any color from the 256 color Spectrum Next palette the result in this case is nicer colors, but the 'dark' colors have been lost, as we're only using 3 bits for the background... but the bitmap data is unchanged, so we can easily support ULA+ and classic spectrum with the same bitmap file |
This version has been totally recolored... 4 bits used for
foreground and background - meaning the color information is NOT
the same as the classic spectrum... We can now use 16 colors for the foreground - and if we want, a totally different 16 for the background... Note: This screen was a crude test... a far better screen could be made with more time and work! |
Number |
R/W |
Bits | Default |
Description |
&14 | R W | RRRGGGBB |
&E3 | Sets the "transparent" colour for Layer 2, ULA and LoRes pixel data. (in RGB) |
&40 | R W | PPPPPPPP | &00 | Palette select (0-7=inks , 8-15 Bright, 16-23: paper, 24-31 Bright paper) |
&41 | R W | RRRGGGBB | &FF | Change Palette RGB |
&42 | R W | MMMMMMMM | &0F | ULA Next Mode mask (Applied to color attrib... result is INK entry (umasked bits +128 for PAPER) |
&43 | R W | IPPPSLUN | &10 | Palette option P=palette for rw S=sprite L=layer 2 U=ula N=ulanext |
&44 | R W | RRRGGGBB -------B |
&00 | Define palette in 9 bits / P=Priority in layer 2 |
![]() |
We're going
to use a command called NextReg a lot in this example... it's
a special Z80 opcode added to the processor, we've created a
macro to allow our assembler to support it. It compiles to db &ED,&91,\reg,\val |
In this example, we're going to use the ChibiAkumas screen
shown above, it's been converted to a binary SCR file 7K in
size... We're also going to use an alternative set of Color Attributes - 768 bytes in size these fill the &5800-&5AFF... and will replace the classic color attributes with a special one - allocating 4 bits to foreground colors, and 4 bits to background - for a 16 color palette... These files were created with my free open source AkuSprite editor |
![]() |
Standard Spectrum Screen: First we're going to copy the Screen bitmap over the visible screen (showing the image) We'll set the border to black, then we'll pause so the user can see the image. ![]() |
![]() |
ULANext Relcolored Standard Spectrum Screen: OK, we're going to make some changes - firstly we'll change the Transparent color with NextReg $14- normally it's bright magenta, but we may want to use magenta in our palette, so we'll change it to a different color we won't need Next we need to enable ULA Next... we do this with bit 0 of NextReg $43 Now we need to tell the hardware how to use the Color attribute map... we'll set the Low 3 bits to the Foreground - which defines the remaining bits as the background |
![]() |
We've allocated 3 bits to our foreground - so we need 8 colors
for our foreground palette (at palette entry 0) We've allocated 5 bits to our foreground - our picture uses 8 background colors plus the Bright bit (flashing bit is unused... 4 used bits) - so we need 16 colors for our background palette (at palette entry 16) We select the palette entry with NextReg $40 and use a DefinePalettes function to set the colors |
![]() |
The enhanced version will have nicer color, but we have no
dark foreground colors due to the 3 bit foreground palette![]() |
![]() |
We've defined a 1 byte per color palette in the format %RRRGGGBB... we define it by writing color bytes to NextReg $41 | ![]() |
ULANext 4 bit per color Dedicated ColorMap
Spectrum Screen: We're going improve the colors using a special ULAnext set of color attributes... we copy the new ULA color info over the normal one in screen memory This will result in a total mess on an classic spectrum! ![]() |
![]() |
Lets fix that mess! we need to reprogram NextReg $42 with the new rules!.... 4 of the bits are our foreground definition... 4 are the background We need to write the new palette... it's 16 colors, and we've used the same colors for the foreground and the background... we use NextReg $40 to select Palette entry 0 for the foreground, and Palette entry 128 for the background This time we're using 9 bits per color %RRRGGGBB B so we use an alternative Palette definition routine (DefinePalettes9bit) |
![]() |
When we use 9 bit palettes, we write two bytes per color to
NextReg $44... the 9th bit - the low blue bit - is in Bit 0 of
the second byte. The finished screen can be seen here! ![]() |
![]() |
Having
completely configurable 16 colors for foreground and
background adds a lot of potential, but we're still stuck with
the design limitations of 2 colors per 8x8 block. Still, if we want to support classic Spectrums in the same code, it's an easy way of giving the game a little improvement without too much extra work. |
![]() |
![]() |
Lesson
P44-
Enhancements
to the Classic ULA and Low Res Mode (Radasjimian) Last time we looked at the new ULA NEXT option on the Spectrum NEXT, and used it to try some alternate Color Attribute configurations. We do have an alternate option... Recolor the classic ULA palette by changing the first 32 Spectrum NEXT colors... the NEXT also adds a 'Low Res Mode' - allowing 256 colors at 128*96 |
![]() |
ZXN_LowRes.asm
ZXN_ULArecolor.asm |
![]() |
Classic ULA | Recolored ULA |
![]() |
![]() |
The Classic ULA Colors as seen on the Standard Spectrum | We've used an alternate 16 color palette for the Dark &
Bright colors... We could have used a different palette for the foreground and background, but in this example we've used the same palette for both! |
![]() |
Recoloring
the ULA in this way works far better, and is far easier than
the ULA Next option we looked at last time... In fact, after seeing this effect, there's not a lot of benefit to the ULANext option - especially considering the loss of backwards compatibility |
on the NEXT The Classic ULA will use the first colors of the
Next Palette Colors 0-15 will be the Dark and Bright Foreground Colors.... Colors 16-31 are the Dark and Bright background colors... we select the first color to change with NextReg &40 If we don't want Magenta to be the transparent color, we probably want to set that to a different color with Reg &14 |
![]() |
Our palette setting code is the same as last time... if we're using 1 byte per color, we write to NextReg &41 if we're using 2 bytes per color (9 bit), we write to NextReg &44 |
![]() |
We can hide parts of the ULA with NextReg &1A... four
writes to this address will define a window, hiding the rest of
the ULA According to the documentation, we should be able to use NextReg &32 and &33 to define an 'Offset' allowing the ULA to scroll... it works with LowRes mode, but not the ULA... so maybe there's a bug in the Emulator? ![]() |
![]() |
The ULA
offset didn't work as expected when the Author of these
tutorials tried to make it work... maybe he's to dumb to
understand it, or maybe it's an emulator bug or mistake in the
documentation... But fear not!... When things become clearer, this documentation will be updated! |
![]() |
Low Res mode is a linear format,
each line is 128 bytes wide (128 pixels)... and each line is
128... each pixel is 256 color and takes a single
byte... so to move down a line we just add 128
The Screen memory is split into
two separate areas of standard Ram:
To the right you can see a re-scaled version of the MSX
chibiakumas screen - though it's only 16 color.
|
![]() |
Using Lo Res mode is really easy! All we do is set the top bit
of NextReg &15 - this enables Lo Res Mode... It uses two &1800 byte large banks of ram at &4000 and &6000... so to copy our bitmap we do two LDIR commands |
![]() |
We set up colors for the LowRes Mode in the same way as usual on the Next.... in the case of the sample screen, it only uses 16 colors | ![]() |
We can use Next register &1A to define a 'window' - this
will hide parts of the lowres screen We can see the result below: ![]() ![]() |
![]() |
We can scroll the window with the registers &32 and
&33, these set the offset of the layer![]() |
![]() |
When we want to write data to the screen, we can use the
GetScreenPos function... We pass a (X,Y) co-ordinate in BC, and
it returns an address in HL Because the screen is 128 bytes wide, our formula is: Address= ScreenBase+Ypos*128+Xpos if the Ypos is <48 then the Screenbase is &4000, if it's 48 or over then the screenbase is &6000... we move to the &6000 area by adding &8 to the top byte |
![]() |
If we want to move down a screen line, all we do is add 128 to the current memory position. | ![]() |
Lets create a PrintChar command - to show the Accumulator from
our bitmap font! We'll use the 1bpp font we used in the main tutorials... it has no characters below character 32, and uses 8 bytes per character... We subtract 32 from A, and multiply it by 8 to calculate the offset in our font of the character we want... Next we load the X,Y pos of the 'cursor' from address CursorX and CursorY... each character is 8x8 pixels, so we multiply both by 8, and use GetScreenPos to calculate the address the character should be printed. |
![]() |
We need to convert our 1bpp font into 8bpp screen bytes... we
do this by reading each byte of our font from DE, Our Screen address is in HL... we back this up with a PUSH HL We shift a bit off the left of the byte, and put it into a screen byte... we repeat this 8 times (with B as a counter)... we've done one line... Now we need to do the next line... we restore the old address with POP HL... and call our GetNextLine function we wrote We now use IXL as a counter for the 8 lines of our font. |
![]() |
We've printed the character, but we want to increase CursorX
so the next character will be printed to the right of the
last... we also need to check if we've reached the end of the line, and move down a line with the NewLine function |
![]() |
The font is crazy big, but you can see the result here! for practical purposes, you'd probably want a 5x5 font or something like that. |
![]() |
Lowres mode is... well, Low res! but it gives
an extra 256 color layer - so you could use it for a
parallax effect with Layer 2... We'll look at options for combining layers in a later episode! |
![]() |
![]() |
Lesson
P45
- 256 color mode on the Elan Enterprise The Enterprise mimics Mode 1 and 0 of the Amstrad CPC... these provide 320x200 with 4 colors or 160x200 at 16 colors... But the Enterprise offers an extra mode... 80x200 at 256 color! |
![]() |
![]() |
![]() |
![]() |
To change the screen mode we need to alter bits 5,6 of the 2nd byte of the LPT... we need to set these to '3' which will define the 256 color mode. | ![]() |
||||||||||||||||
In this mode the 'palette' in the LPT has no effect, and every byte of the screen ram IS a color definition of the pixel - in the same format as the palette used. | Pixel bytes:
|
||||||||||||||||
We can see the results to the right! We can export a valid bitmap with the Akusprite Editor, just change the view mode to "ENT 80x200 256 color" and use the Save Raw Bitmap option ![]() |
![]() |
![]() |
We're going
to learn how to use a 4 pixel wide 'half nibble' font on the
Enterprise.. This tutorial is based on the 'Simple Hello World' Episode, and we won't cover again the bits that are unchanged... if you've not seen that episode, please see it Here |
Because our screen is pretty narrow, our normal 8 pixel wide
font would just allow 10 characters per line of the screen!. Instead we'll use just one nibble (4 bits) per character, halving the total size of our font, down to just 384 bytes for our 96 character font! |
![]() ![]() |
We call our PrintChar routine to draw a character to the
screen... Our font has two characters per byte... so when we work out the byte address in our font for the character, we first ignore the low bit... we then shift A two times to the left, effectively multiplying it by 4 We then add the base address of our Btimap font... the result is DE now contains the address of our character in the font. |
![]() |
We need to calculate the screen address... we use CursorX and
CursorY bytes to store the next character position... We multiply CursorX by 4 - as our font is 4 pixels wide... and CursorY |
![]() |
OK, we need to draw our character... We use IXL as a counter for the 8 lines of the font... we load a line from the font into C from DE Next we need to get back the character number we wanted to print - and see if it's odd or even... If it's Even, we print the left most 4 bits (the high nibble %XXXX----) If it's Odd, we print the right most 4 bits (the low nibble %----XXXX)... we do this by bit shifting left 4 times with 4x SLL C before each line... To convert 1bpp to 8bpp, we shift one bit out of C into A, and show A to the screen, we repeat this 4 times to show the 4 pixel wide font... We use GetNextLine to move our destination HL address in screen memory down a line, and repeat for the next line of the font. |
![]() |
Once we've printed the character, we move the CursorX for the next character print. | ![]() |
We've used the font here for a half width effect, but if you're really hard up for ram on a system like the Camputers Lynx, you could double up the bits in the font, for a 'chunky' 8x8 font, and save 384 bytes of font memory. | ![]() |
![]() |
Lesson
P46-
Tilemap on the Spectrum NEXT The Spectrum Next isn't just capable of Bitmap layers, it can also do Tilemaps... This allows us to create fast scrolling graphics using the ULA layer (still allowing Layer 2 for other purposes!)... Lets make the layer work! |
![]() |
ZXN_ULAplus.asm
|
![]() |
First we need to set up the palette... We select the tilemap palette... then use the DefinePalettes function we wrote before. | ![]() |
We need to turn the Tilemap on with bit 7 of reg &6B - we
can also specify the size of the tilemap with bit 6 - we're
setting it to 40x32 Bit 5 controls Attributes - when this is 0 each tile is defined by two bytes (the first is tilenumber, and the second being flip and palette option)... when it's 1 each tile is just one byte - the tilenumber... we'll allow attributes! |
![]() |
We need to define the address of the tilemap with &6D, and
tile pattern data with &6E Each uses 6 of the 8 bits - the top two are ignored - the Tilemap is always in page 5 of ram. We're defining the tilemap at &4000, and the pattern data at &5000 |
![]() |
Ok! we need to copy our Tilepattern data into the correct
memory address... We do this with a LDIR The tile pattern data is included from file. |
![]() ![]() |
You can export the correct Tile data with my Akusprite Editor
- it's free and open source. Select "Save Raw 4bpp 8x8 Tiles" From the Z80->SpecNext Menu |
![]() |
We define the 'TileMap' which will select the tile number to
show in each position on the screen. We're going to need to copy this to the correct Ram area in screen. With Attributes enabled, Entries in the tilemap take 2 bytes in the format below:
N = Tile Number / U = ULA over Tilemap (or N bit 9) / R = Rotate Tile / Y = Y-Flip / X = X-Flip / P = Palette |
![]() |
|||||||||||||||||||||||||||
When it come to filling the tilemap, we need to read from our
'Tilemap' label (shown above), and copy the bytes to &4000+ Our tilemap is 40x32, so we set BC to 1280 (&500) We use IXL to set the second attribute tile for each byte... really this is just so we can test attributes in this example.. After writing each tilenumber we write IXL - we can filip some of the bits of IXL after each write if we want to test the attributes! |
![]() |
|||||||||||||||||||||||||||
The tilemap will be visible onscreen. |
![]() |
Bit 0 of
the attribute can either be 'ULA over tilemap' (Transparency)
or bit 8 of a tilenumber (for 512 tile)... it just depends on
the setting of NextReg &6B Remember the Tilemap can only be in Page 5 (with the regular screen) - so if we using the Tilemap AND ULA, we're going to have too little memory to worry about losing the extra 256 tiles! |
![]() |
Like with other layers, we
can define a 'Window' to change the visible area of the
tilemap... By default the Tilemap is bigger than the classic 256x192 ULA, but we can resize it to match the normal speccy screen |
![]() |
We can also scroll the tilemap with Nextreg's &2F,&30 and &31 | ![]() |
We can scroll and scale the tilemap | ![]() |
![]() |
The Tilemap
allows us to have good color graphics, and change and redraw
things more quickly than a bitmap layer - so is a great choice
for background graphics in our game - with sprites for our
characters... We can also use Layer 2 as... well a Layer 2(!) for parallax effect. |
![]() |
Lesson
P47-
Using 16 color Mode 0 to simulate 2x 4 color Layers Some games from the 80's were known to use a trick where the 4 bits of color depth in Mode 0 were split into pairs... effectively giving 2 layers of 4 colors... By ANDing and ORing new data in - sprites could change, without removing the background behind!... Lets check it out! |
![]() |
CPC_Bitmap_Multilayer.asm | ![]() |
![]() |
We're looking
at today's example on the CPC, but the exact same technique will
work fine on any system with 16 colors, and fully configurable
palette. The Enterprise has 16 colors, however as the 2nd 8 are copies of the first 8, it's unlikely this trick would work on that system. |
This concept seems a little odd, and it's probably not the first
one you want to consider for having sprites over background
graphics, but it's at least worth knowing about! This technique was used in Ghosts n Goblins and Bubble Bobble back in the 80's We're splitting the color bits in 2 - so the result is 4 background colors and 4 (possibly different) foreground colors. Our base Colors for Layer 1 will be Colors 0,1,2,3.... For Layer 2 they will be Colors 0,4,8,12 Colors where the foreground and background overlap (Colors 5,6,7,9,10,11,13,14,15) will need to be set to the same color as the foreground layer color (Though we can effect transparency by using a different color! See Later!)
|
![]() ![]() |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ScreenMode 0 uses a weird combination of bits, Each byte has two
pixels - the 8 bits split into two pairs - 4 for the left
pixel, and 4 for the right... we'll need to split them into a
'Layer 1' and a 'Layer 2':
|
We're going to need a pair of sprite drawing routines - one for
each layer. Here's the version for Layer 1 (the background layer using colors 0-3) Before writing data to the screen We're going to need to mask the current screen data, keeping bits %00110011 (the bits for layer 2) - And then OR in new new sprite data for layer 1... we AND the sprite data with %11001100 to make sure there's no extra data in the sprite... you can skip this if your sprite only has 4 colors. The code here uses the firmware for screen bitmaps functions. |
![]() |
Thre's an alternate version for the foreground layer - the bitmask for the background has been changed to %11001100 (Keep Layer 1) We have to bitshift the sprite data 2 bits to the right, to convert the bitmap for the Layer 2 colors (0,4,8,12)... we mask the bits with %00110011 to remove any unwanted colors from the sprite. |
![]() |
We can use these functions to draw our sprite to the screen... we set DE to the Xpos, HL to the Ypos and BC to the sprite data we want to show. | ![]() |
When the two sprites we draw overlap - with color 0 transparent,
the 2nd layer (using colors 0,4,8,12) will take priority,
because of the color palette we've used If we clear the layer 2 sprite (by writing a blank sprite) ... the layer 1 sprite will be intact! The same is true for layer 1 |
![]() ![]() |
If we draw a sprite to Layer 2 and it overlaps an existing
sprite, the layer 2 sprite will be erased! The same is true for layer 1 |
![]() |
if we set the Overlap Colors 5,6,7,9,10,11,13,14,15 (The colors
made when the two layers overlap) to a different color - we can
effectively make the layers transparent! In this example, we've used a palette that will make an 'Anaglyph 3d effect' (Red-Cyan glasses) Layer 1 is Red shades... Layer 2 is Blue/Cyan shades... The Overlap colors are Greys and White (Cyan=Green+Blue.... Red+Green+Blue=White) |
![]() |
Losing half your screen resolution is a high
price to pay, but if you want nice sprites and backgrounds,
and don't want to spare memory on any kind of screen buffers,
it can give you an 'easy' way of moving sprites round the
screen, without having to worry about redrawing your
background. And it's a bit of fun, don't you think? |
![]() |
![]() |
Lesson
P48
- All MSX2 Bitmap Commands - Part 1/2 The MSX V9938 VDP has a wide range of 'Commands'... these will make the VDP perform a variety of graphics task... depending on the command, we need to set different registers, and in some cases we need to check the status registers too... Lets try each command and learn how they work |
![]() |
MSX2_Bitmap_AllFunctions.asm | ![]() |
Accessing memory directly allows us to write or read bytes
consecutively into VRAM... We're going to use the 'SetReadAddress' and 'SetWriteAddress' to select a VRAM memory address. The address is specified by registers AHL for addresses from &00000-&1FFFF... 128k We have to do some bitshifting, and set bit 6 of byte 3 to either 1 or 0, for Read or Write... we write the result the the control port (&99) |
![]() |
Once we've selected an address, we can read or write to ram from
port &98 (depending on bit 6 of the third byte we sent to the
control port) We can use this to define a read or write function to transfer bytes from consecutive addresses in VRAM |
![]() |
In this example, we'll transfer part of our game code to VRAM, then transfer it back to &D000 as a test | ![]() |
The results can be seen here our code will appear as corruption
(we can see our green sprite in the middle) If we dump &D000 to screen, we can see it contains the same data as &803A |
![]() ![]() |
You can use
direct RAM access while the VDP is drawing other things...
ChibiAkumas used this to draw the gradient background while the
VDP was clearing the blank areas. You can even use VRAM to bank out bits of your program code, then bank them back in later if you have spare VRAM but too little RAM... for example you could page out the disk settings and firmwaer ram at &E000-&FFFF to VRAM during your game, and bring it back when you load the next lever. |
![]() |
We're going to need some ports to work with the VDP, and we'll define them with symbols to make our code easier | ![]() |
Many of our functions can take a 'direction'... We're generally best going left->right,top->bottom, but in cases where there is an overlap (when we're copying a range to itself - eg a scroll effect) the order we copy will affect the result... suppose we copy (x,y)->(x+1,y)... if we work left to right - each line will be filled with the first pixel... if we reverse the direction - working right to left, the screen will be scrolled. | ![]() |
Some of our commands allow 'Logical' functions... These will allow for operations like XOR, or for 'Transparency'... when a pixel is color 0 Logical commands are significantly slower, so should only be used when needed |
![]() |
We're going to need to check TR (Transmit Ready - ok to
send/read data) and CE (Command Executing - vdp busy) To do this we need to Select the status register number with 'normal' Register R15... then we read from the status port &99 to check the status of the register. |
![]() |
Command | HMMC | YMMM | HMMM | HMMV | LMMC | LMCM | LMMM | LMMV | LINE | SRCH | PSET | POINT | STOP | ||
Register | Purpose | Fill Vram From CPU | Fast Copy (Y axis) | Fast Copy | Fill | Logical Fill Vram From CPU | Logical transfer
data to CPU |
Logical Vram to Vram | Logical Fill | Line | Search (Xaxis) | Pset | Read Point | Stop | |
R32 | SX-L | Src X |
X | X | X | X | X | ||||||||
R33 | SX-H | Src X | X | X | X | X | X | ||||||||
R34 | SY-L | Src Y | X | X | X | X | X | X | |||||||
R35 | SY-H | Src Y | X | X | X | X | X | X | |||||||
R36 | DX-L | Dest X | X | X | X | X | X | X | X | X | X | ||||
R37 | DX-H | Dest X | X | X | X | X | X | X | X | X | X | ||||
R38 | DY-L | Dest Y | X | X | X | X | X | X | X | X | X | ||||
R39 | DY-H | Dest Y | X | X | X | X | X | X | X | X | X | ||||
R40 | NX-L | Num X | X | X | X | X | X | X | X | X | |||||
R41 | NX-H | Num X | X | X | X | X | X | X | X | X | |||||
R42 | NY-L | Num Y | X | X | X | X | X | X | X | X | X | ||||
R43 | NY-H | Num Y | X | X | X | X | X | X | X | X | X | ||||
R44 | CLR | Color | X (first pixel) | X (Color) | X (Color) | X (Color) | X (Color) | X (Color) | X (Color) | ||||||
R45 | ARG | Argument | X (Direction) | X (Direction) | X (Direction) | X (Direction) | X (Direction) | X (Direction) | X (Direction) | X (Direction) | X (Direction) | X (Direction) | X (Dest) | X (Dest) | |
R46 | CMD/ LOP | Command/ Logical Op (x) |
X (&F0) | X (&E0) | X (&D0) | X (&C0) | X (&Bx) | X (&A0) | X (&9x) | X (&80) | X (&70) | X (&60) | &(5x) | &(40) | &00 |
Readable Registers | S7= Pixel Data | S2=Status S8/9 = X |
S7=Color |
In this example, we'll transfer data from the Z80 (a sprite in
this case) to VRAM This will probably be the best way to get sprites from our disk/cartridge to the CPU We need to transfer the first pixel with the command... then send the rest to the VDP data port &99. We'll select an (X,Y) position, a Width & Height to define an area onscreen the data will go into. In theory, We should wait for the TR Flag, but in practice, we don't need to! |
![]() |
We need to send data to registers 36-46... we're going to write
11 registers... We push all the values for these registers on the stack, then use OUTI commands to send the data to the VDP We need to set the indirect register to the first register we want to change (36) before we start our OUTIs... we do this by writing 36 to the control port then 128+17.... the '128+' tells the hardware to set the register number 17... the indirect register will autoinc, so we'll write to 36,37, 38 and onwards This will start the command... Next we need to select register 44... but we want to stop AutoINC this time... so we add 128. |
![]() |
Our sprite will be transferred from RAM to the screen | ![]() |
We're going to define a range to copy... Notice we define height
and Ysource... but not Width or Xpos... Xpos is the same as Xdest... and the width is to the edge of the screen... this of course means the direction we copy is quite important! Because the function is more limited, it's slightly faster than the normal version. |
![]() |
For this command We need to set Registers 34+ Like before, we push all the registers onto the stack, then execute a series of OUTI's to send the data... With this command the VDP will start and do all the work itself, so we don't need to do anything after the command starts... the Z80 can get on with other work... But if we want to tell the VDP to start another job we'd have to check the CE flag to make sure the task has finished |
![]() |
The result can be seen here! | ![]() |
The
part of our code which does the OUTI from reg 34 (VDP_YMMM)
doesn't have anything specific to the YMMM command There will be other commands that also write from Reg34, but we'll keep separate versions to keep it clear in this example.. If you were using the code in your own program, some optimization would be a good idea! |
![]() |
We're going to copy a sprite from VRAM to a VRAM... During our game this will do most of our sprite copying as it's fast... however it will delete any background areas - so doesn't allow transparency (See LMMM) |
![]() |
Were going to need to fill all the registers from 32-46... We're going to use the shadow registers for DestX/Y as we don't have enough registers for the regular parameters |
![]() |
The sprite will be copied to a second position onscreen. | ![]() |
The tile is misleading, but this command effectively allows us
to fill areas with a single byte... this is the fastest way to
clear the screen (CLS) We specify an XY pos for the fill, a width and height and a color... |
![]() |
The HMMV command needs to fill registers 36+... Like most commands it does not need any action after the fill starts. | ![]() |
The requested areas will be filled. | ![]() |
![]() |
If the nibbles
of the color bit do not match (in this example &CD) the
filled area will have vertical lines... It would be nice to have a 'checkerboard' effect... where the nibbles swapped every other line, but the VDP cannot do this, so you probably want to just use solid colors (like &CC or &DD) |
![]() |
Lesson
P49
- All MSX2 Bitmap Commands - Part 2/2 We looked at many fast commands last time... this time we'll look at the 'Logical' commands and a few other strange commands! While slower, Logical commands allow for transparency! |
![]() |
MSX2_Bitmap_AllFunctions.asm | ![]() |
Like HMMC, LMMC transfers data from Z80 ram to the screen, but this 'Logical' Version allows an operation to be passed in the C register... we're going to test with an XOR (invert background with pixels) or NOT (Invert pixels) Logical commands work in pixels, not bytes... so we need to pass our colors in the low nibble of bytes... this means two writes to the SendByteData port (&9B) for each byte in HL |
![]() |
Like HMMC we need to send the values to Reg36 onwards... Once we've prepare the registers, we need to select Register 44 so the VDP can recieve the bit data |
![]() |
The two versions of the sprite will be shown... The XOR one will be transparent (but background pixels will be inverted) The NOT one will be a color negative. |
![]() |
![]() |
Logical
operations allow us to perform transparency... but this isn't
really the best way of doing transparency for games... LMMM is
faster as it's VRAM->Vram... we'll take a look at it soon! |
LMCM is the opposite of LMMC... it transfers data out of vram
into the CPU... BUT... because it's a Logical operation, it copy each pixel - not each byte... We can specify an XY start pixel, a Width and a Height... We can then read in color pixels from Status Register 7... in this example we'll store them in ram. |
![]() |
For this command We're going to need to populate registers 32+ We read in the colors from Status Register 7. |
![]() |
The Sprite was drawn to the screen, and read back to $D000 The data in $D000 will be the same as the sprite (originally at $8332) except the two nibbles of each byte will be split up... this is because we read back in PIXELS, not BYTES. |
![]() |
We're going to try out every single Logical operation on our
sprite, to see how the pixel data is effected. We'll draw a strip of 20 pixels, and specify an operation with our predefined symbols... the operation will be specified in C The LOG_Txx options use color 0 as a transparent color. |
![]() |
Pretty much like before, we need to fill registers R32
onwards... This time however, we need to change the bottom nibble of the 'Command byte' as it's the 'Logical Operation'... we OR in the value in C to do this |
![]() |
The result can be seen below - along
with the Logical operation and it's bits that are set in the low
nibble of the Command byte (Reg 46) ![]() |
Logical fill is slower than the high speed version, but it can
be used for effects that may be useful. In this example, we'll fill areas of the screen with the standard Implicit and XOR options. |
![]() |
Like the others, we need to set Registers 36 onwards | ![]() |
The results will be shown to the screen. | ![]() |
The VDP can draw lines for us... The definition is slightly odd... Usually NX is width - but for this command it's the length of the Long Side Usually NY is height - but for this command it's the length of the Short Side We specify a Direction as we often do, but in this case Bit 0 is the 'MAJ' bit... setting this flips the Long side. We also specify the typical XY position, and a color for the line |
![]() |
We need to set Regsiters 36 onwards... We also need to set the Color byte, apart from that the rest of the code is typical. |
![]() |
The Three lines will be shown to the screen... The middle one (light green) is flipped because of the MAJ bit. | ![]() |
This function may be useful for a graphics package, for example
a fill routine. It searches the current X line - either Left or Right - for a color... we can find the color itself, or the first pixel that isn't the color. |
![]() ![]() |
When we want to search, we fill registers 32 onwards as usual. When the command completes however, we need to do some special things! First we need to check the CE flag, to see the command really finished.. Next we check the BD flag (bit 4) of Status Reg 2 ... if it's 1 we found the pixel... if it's zero it was never found. If we wound the pixel, we need to read in the low byte of the Xpos fro Status Reg 8... and the high byte from Status Reg 9... note unused bits (9+) are set to 1 |
![]() |
In this example we found a pixel at 120 (we placed it there!) | ![]() |
We can use PSET to set pixels - we can even use Logical
operations In this example, we XOR one of the pixels - effectively making it flash every other draw |
![]() |
We transfer our data to Register 36 onwards | ![]() |
The pixels will be drawn to the screen. | ![]() |
We can read a point from the screen, and return the color of
that pixel in A We're going to define a function that will return the color for us. |
![]() |
The basics are the same as any other of our commands which use
registers 32+ Most of the registers actually aren't needed (NX/Y DX/Y)... but it's easier just to give them dummy data. Once we've run the command, we read from Status Register 7... it will contain the color of the pixel |
![]() |
Our colors will be read from this screen... in this example one
pixel was color 15, the other color 2 (from the PSET example) |
![]() |
The VDP can only do one job at a time, but we can stop it and
change the job! In this test, we'll set the VDP to clear the screen white... wait a bit, then abort the job! |
![]() |
To abort the job, we just send a 0 to the command register R46 | ![]() |
The Fill command only managed a few lines before we aborted it! | ![]() |
Phew! We've looked at all the commands!... The
VDP is pretty powerful, and gives up lots of functionality... Remember it's a second processor... the Z80 can do other things while it's drawing, but we'll have to check the CE flag (and wait if needed) before issuing another command! |
![]() |
Command | HMMC | YMMM | HMMM | HMMV | LMMC | LMCM | LMMM | LMMV | LINE | SRCH | PSET | POINT | STOP | ||
Register | Purpose | Fill Vram From CPU | Fast Copy (Y axis) | Fast Copy | Fill | Logical Fill Vram From CPU | Logical transfer
data to CPU |
Logical Vram to Vram | Logical Fill | Line | Search (Xaxis) | Pset | Read Point | Stop | |
R32 | SX-L | Src X |
X | X | X | X | X | ||||||||
R33 | SX-H | Src X | X | X | X | X | X | ||||||||
R34 | SY-L | Src Y | X | X | X | X | X | X | |||||||
R35 | SY-H | Src Y | X | X | X | X | X | X | |||||||
R36 | DX-L | Dest X | X | X | X | X | X | X | X | X | X | ||||
R37 | DX-H | Dest X | X | X | X | X | X | X | X | X | X | ||||
R38 | DY-L | Dest Y | X | X | X | X | X | X | X | X | X | ||||
R39 | DY-H | Dest Y | X | X | X | X | X | X | X | X | X | ||||
R40 | NX-L | Num X | X | X | X | X | X | X | X | X | |||||
R41 | NX-H | Num X | X | X | X | X | X | X | X | X | |||||
R42 | NY-L | Num Y | X | X | X | X | X | X | X | X | X | ||||
R43 | NY-H | Num Y | X | X | X | X | X | X | X | X | X | ||||
R44 | CLR | Color | X (first pixel) | X (Color) | X (Color) | X (Color) | X (Color) | X (Color) | X (Color) | ||||||
R45 | ARG | Argument | X (Direction) | X (Direction) | X (Direction) | X (Direction) | X (Direction) | X (Direction) | X (Direction) | X (Direction) | X (Direction) | X (Direction) | X (Dest) | X (Dest) | |
R46 | CMD/ LOP | Command/ Logical Op (x) |
X (&F0) | X (&E0) | X (&D0) | X (&C0) | X (&Bx) | X (&A0) | X (&9x) | X (&80) | X (&70) | X (&60) | &(5x) | &(40) | &00 |
Readable Registers | S7= Pixel Data | S2=Status S8/9 = X |
S7=Color |