Friday 29 July 2011

Matthew Smith has left the building

The Original Plan: Do as Manic Miner...




The sprite routines (and general screen updating code) was going to be awesome.

I was going to copy the great Spectrum coding legend, Matthew Smith, and write directly to the Speccy's screen memory every frame, 50 frames a second, using interrupts.

You have around 14000 T-states (CPU cycles, roughly) to play with at the start if the interrupt routine, before the first byte of screen data is drawn. Surely this would be plenty to be getting on with? I'd have my sprites drawn, edge tiles drawn, scrolling done, music playing - the works. And I'd always be ahead of the vertical image trace. Yeahmon!

Except... and it breaks my heart to realize this... but, basically, the Spectrum... the thing about the Spectrum... the one thing you really need to know about the Spectrum is...

The Spectrum SUCKS.

Okay. It's an old machine (to say the least - that was after all the point of the exercise) - but I have discovered in my experiments that even a straight screen wipe - yes, a start-at-beginning-move-to-end-of-framebuffer screen wipe, is IMPOSSIBLE** in the time you have without the beam catching up.

Yes, I even tried moving to the second 32k of memory (uninterrupted by the ULA), for screen wiping, and for sprites. True, I could probably just about optimise the screen wipe to work... But even if so, I wouldn't be able to do anything else at all in that frame.

So, how did Matthew Smith do it?

Well, first of all, he would never have cleared the screen at all - except when the level was first drawn. He'd have written directly into screen memory, XORing data as he went (you can see this when Miner Willy walks past the scenery, in fact).

Additionally, and crucially, he most likely worked out all the line-based operations in advance, and then - instead of rendering one sprite at a time - drew individual vertical lines that shared Y coordinates between sprites, and did so in order.

This is fine, and I had planned to rewrite my sprite routines to do this (even if it's fiddly, it would almost certainly let me render approximately eight sprites smoothly).

Except I realised I wasn't simply trying to rewrite Manic Miner or Jet Set Willy: games with largely blank backgrounds, that draw most of each room in one pass and then leave it alone, and with only handfuls of sprites to worry about.

I want to make games that scroll, have fancy effects, maybe have massive boss sprites, etc.

I conceded at this point: there is no way to do all that at 50 frames a second on a Spectrum, no matter how well you optimise (well, unless you do some pretty specific cheats... which will hardly work for a variety of games as I describe).

So, I have grudgingly decided that for my project to go ahead, I will have to use... double buffering.

Double Buffering: Man's best friend... right?



Now of course there will be some of you now screaming at the screen: "Of COURSE you should double buffer, you moron! Why didn't you do that in the first place?"

Now hold on, and re-read the first part of this article. It is IMPOSSIBLE to do even a simple screen wipe in a single frame.

A screen wipe is considerably simpler and faster than copying a damn buffer into the frame memory.

That was why: I wanted 50 frames a second gameplay. Sadly, for my purposes, this is not to be.

There is another consideration for double buffering: memory. We are working with 48K of RAM, and not all of that is available (the screen itself takes up around 7K of it, and then we have the stack at the top of memory, the interrupt vector, our at-the-moment very simple code, sprite data, etc.)

Add to this that only the top 32K is uncontended with the CPU (and therefore runs without halting interruptions from the ULA chip). We would ideally want our buffer here, but of course we'd also want all our intensive calculation code here, too.

So, there are many reasons I would like to avoid double buffering on the no-hardware Spectrum. But there are a few advantages, too...

Advantage One: I can draw into a linear buffer!

Yes indeed, I can determine my buffer to be in whatever form I like. If I was crazy (and I really would have to be), I could create a chunky bitmap buffer with colours per pixel! I wouldn't be able to display all those colours, but hey, maybe I could fake it..?

Of course, reality crashes into our fantasy world once again: there are limits to what you can do speed-wise. But in fact, having a linear buffer (with a cached 192 line framebuffer ptr array) would in fact make things easier for timings with the beam. So win!

However...

14000 T states (slightly more, but not much). We have to get the bulk done here in the first part of the interrupt routine. Then the beam will actually be drawing the pixels and colours, and it will be a race to get done before the beam catches us up.

In fact, this situation is worse than I've described. We won't get all of those 14000 T states (or even each lines worth of 224 T states), because some of the cycles will be lost to interruptions by the ULA (it takes precedence over the CPU, locking it out).

So, quite simply we will have to accept that, for all the reasons given, the very best framerate we can hope for with double buffering is 25 fps. (Or 30 fps on US models).

Double poo sandwhich :-(

The Upside

The big win of all this, though, is that all kinds of game styles can emerge from it. Our platformers (the original game style choice for this project) can have much more detailed backdrops if we want (and balance for framerate). We can do scrolling shooters, Gauntlet-style multi-directional games, pseudo-3D stuff like Dungeon Master (well, an imitation), and even perhaps proper wireframe - or even Freescape-style filled! - 3D graphics.

Having said all of that, I'm still grumpy. I wanted to prove myself a God of the Matthew Smith order, and then some. But I know that without severely limiting what I can do in terms of game design, I just can't do that.

It does, however, make me even more awestruck by that strange, drug-addled genius that gave the world its first proper ZX Spectrum arcade game that didn't suck: Manic Miner.

Matthew Smith, I salute you!


Footnote:

Some people might quite rightly point out that you don't need to in fact wipe or update the entire screen each frame. This is true (and very few games on the Spectrum even come close to doing so). But even given that, the limitations on what you can have where on the screen in order to have no flickering at 50 frames a second is just far too restrictive for what I want to do. Boo, Spectrum. But also... *hugs*

** I'd love someone to prove me wrong here, but I tried every imaginable optmisation. The screen wipe was tried with both the LDIR method, and a single XOR A followed by a LD (HL), A loop. It no workey :-( Well, not in a single frame, anyway.


EDIT: I think this may be the answer! So, Project Spectrum may well be back on!

http://zxspectrumdev.blogspot.co.uk/

More about it here:

http://www.worldofspectrum.org/forums/showthread.php?t=24788

So psyched - my original aim is now back within realistic bounds... A proper flicker-free game!

5 comments:

  1. Necropost ftw! Pretty sure Manic Miner wasn't updating at 50 fps. Looking at the code, Matthew had two buffers, one for the room, and one for the room + sprites, and copied the second buffer to the screen with one LDIR, nothing fancy at all. Double buffering is fine on the Speccy, just forget about 50Hz, 25Hz is just fine.

    ReplyDelete
    Replies
    1. Cool, I think I will have to take a look at that :)

      Delete
    2. Apparently double buffering is something the 128k speccy does well

      Delete
  2. You can also get an extra bit of performance by abusing the stack pointer to blit rows of pixels. Use push and pop to very quickly move words from your buffer to the screen, setting the SP and restoring it accordingly. Remember to turn off interrupts though.

    ReplyDelete
    Replies
    1. Yeah, I read that recently in an old interview with some Speccy game coders from the time - a great technique I'd never thought of!

      Delete