Parallax Home Games MSX Misc. Contact Us

Core Dump


Core Dump Work in progress The Gallery Technical information





Overview


Core Dump - Work In Progress


Episode 72

August 3, 1998 : VDP coding trick




Aleph map size

The game is looking rather cool at the moment. There's more enemies to blast than there were in Black Cyclon, even.

I did an "estimate size" analysis on the maps. I'm rather surprised at the outcome. I just counted the maps (block location data), minus the headers, and got this number of blocks that are in the total map. Then I divided that number by (32*24), which is the number of blocks that would fill a 256 x 192 pixel screen. So that ends me up with the total number of screens, right?

But that would imply I've got far over 500 of screens' worth of playing area at the moment. Consider that you will see all of these screens more than once, and you've got a lot of walking to do! The only screens you'll not be able to visit all the time are those outside of Aleph, so you can imagine this space station is quite big.

But then I've got to add that because of the speed of the thing it doesn't actually feel like that much screens. Oh well.

A coding trick

As I've noticed that many people are happily coding away at games, I will explain a trick that speeds up games that have many seperate copies, like Core Dump.

For such a game that uses small blocks for the background and the sprites, extremely large amounts of copies are needed for a single frame. So one of the first priorities is to make sure your copy commands are as quick as they can be.

First, the usual tricks. I suspect everybody knows these by now:

  1. Disable screen - not much use in a scrolling game, though.
  2. Disable sprites - useful, as I didn't need any sprites.
  3. Use High-speed (Byte) copies whenever possible - obviously I need TIMP copies as well, but the Core Dump engine decides for itself when to use high-speed or not.
  4. Use multiple OUTI commands instead of one OUTIR for copying the command data to the VDP, and use the indirect register write method with automatic increment.

So, the commands ends up like this (in pseudo-code, and assuming your interrupt routine doesn't mess up the VDP status too much)

VDPcommand:
   di
   ld a,32                        ; first command argument in this case
   out (099h),a
   ld a,IndirectRegister+128
   out (099h),a
   ei
VDPcommLoop:
   ld a,2
   call ReadStatusRegister
   bit [the_bit_for_CommandExecute],a
   jr c,VDPcommLoop

   ld c,09bh                      ; indirect register write
   di
   outi
   outi
   ...
   ..
   outi                           ; fifteen, usually
   ei
   ret

(Yes, I am rather sloppy here, but I assume you can write a copy like that with your head in a large bag.)
Now for something new. You see, with a lot of copies, you end up with many calls to that "ReadStatusRegister" procedure. What does it look like?

ReadStatusRegister:
   di
   out (099h),a
   ld a,08fh
   out (099h),a
   in a,(099h)
   push af
   xor a
   out (099h),a
   ld a,08fh
   ei
   out (099h),a
   pop af
   ret

Yuk! What an ugly bit of code that is. But you see, it has to be done like that because of the interrupt handlers.

This is a bit of a historical design decision. On an MSX1 there is only one statusregister. You could get the value of it by doing just in a,(099h). An MSX2 has multiple registers. This is solved by writing the number of the status register to register 15. Then, reading port #99 gives the correct result. (Now comes the ugly bit) MSX1-like software interrupts (including the one in your MSX2-bios) just read port #99. They simply assume that statusregister 0 is selected. So, you can only read other statusregister with the interrupts disabled, and you then have to set register 15 back to 0 afterwards. That's what the code did.

So... just build a different interrupt, and use statusregister 2 as the default one!

Interrupt:
   di
   push af
   xor a
   out (099h),a
   ld a,08fh
   out (099h),a
   in a,(099h)        ; is required in any such interrupt
   ld a,2
   out (099h),a
   ld a,08fh
   out (099h),a
   push bc
   push de 
   push hl
   ...
   [more stuff]

Now reading a statusregister becomes:

ReadStatusRegister:
   di
   out (099h),a
   ld a,08fh
   out (099h),a
   in a,(099h)
   push af
   ld a,2         ; this has changed!
   out (099h),a
   ld a,08fh
   ei
   out (099h),a
   pop af
   ret

But the time-gain is in the copy routine, because it changes

   ld a,2
   call ReadStatusRegister
into the much faster
   in a,(099h)

This technique is used in Core Dump. Actually, I alternate two interrupt modes... but that is a different story.

Room for improvement

Of course there is also the alternative of a "correct" interrupt, i.e. one that enables the coder to select the default status register. It's actually bloody easy, come to think of it, and we only need one variable, DefaultReg.

Interrupt:
   di
   push af
   xor a
   out (099h),a
   ld a,08fh
   out (099h),a
   in a,(099h)
   ld a,(DefaultReg)
   out (099h),a
   ld a,08fh
   out (099h),a
   push bc
   ...
   ... bla bla

Then, we need a procedure to set a new default register:

SetDefault:
   di
   ld (DefaultReg),a
   out (099h),a
   ld a,08fh
   ei
   out (099h),a
   ret

Now, any register can be selected by the procedure, and after that in a,(099h) can be used to read it. Funny, I should think, because it's very quick indeed.

Beware

You'll have to beware, though, because you have to adjust the 0038h interrupt vector with ram in page 0. You can't use the bios from that point. To go the the bios, you'll have to make register 0 the default one again, and then you can put the bios back into page 0. Other than that it works like a charm.


Go on...



Parallax Home Games MSX Misc. Contact Us