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!
Friday, 29 July 2011
Sunday, 24 July 2011
ZX Spectrum - Screen memory layout
The Sinclair 48K Spectrum was just about the cheapest home computer you could buy in 1982 that had colour graphics.
There was a reason...
Just about every corner was cut in producing the ZX Spectrum: it had no custom hardware of any kind, short of a very simple ULA chip that did as little as it could get away with in order to have a working computer system, and the 16K ROM chip, with BASIC in it.
One consequence of this cost-cutting exercise was the strange, non-linear screen memory layout...
The Spectrum provided 256 pixels along the x-axis, and 192 lines along the y-axis.
Each byte in screen memory contained eight pixels, with no colour information. The colour was set in the attributes, a sequence of single bytes that were placed in memory immediately after the pixel information, and which specified the foreground and background colours for each 8x8 pixel cell.
Though the attributes are placed linearly, the pixels themselves are not. You can see this quite clearly when Spectrum games load their title screens:
The loader here is simply reading all the bytes from $4000 (16384) to the end of screen RAM at $5AFF, in order. As you can see, the pixels are arranged in a very "interesting" fashion...
This makes accessing screen memory a little bit more involved than simply adding Y lots of TotalXBytes to find the line.
Finding screen line addresses from a Y Coordinate
Given a Y coordinate stored in an 8-bit register:
Y Coordinate bit layout (MSB to LSB):
zzxx xnnn
we can produce a 16-bit address in a register pair by placing these bits as follows:
Register pair bit layout (R are the bits for the x-axis offset, 010 provides the base address $4000):
010z znnn xxxR RRRR
Who knows why on earth the engineers designing the Spectrum decided to swap the position of the nnn and xxx bits... but they did.
(I'm sure it was the same kind of reasoning as providing a console with half an altivec unit... ahem.)
Geek Moment - Some code to use this stuff
Here is some Z80 code that makes use of this knowledge, to clear the screen - not in the order as shown in the loader above, but sequentially (from the user's point of view).
This code ignores the attribute memory for convenience's sake (that is linear, and would be a straight blat anyway... though it might be nice to do it in sync with each group of 8 lines... ;-))
There was a reason...
Just about every corner was cut in producing the ZX Spectrum: it had no custom hardware of any kind, short of a very simple ULA chip that did as little as it could get away with in order to have a working computer system, and the 16K ROM chip, with BASIC in it.
One consequence of this cost-cutting exercise was the strange, non-linear screen memory layout...
The Spectrum provided 256 pixels along the x-axis, and 192 lines along the y-axis.
Each byte in screen memory contained eight pixels, with no colour information. The colour was set in the attributes, a sequence of single bytes that were placed in memory immediately after the pixel information, and which specified the foreground and background colours for each 8x8 pixel cell.
Though the attributes are placed linearly, the pixels themselves are not. You can see this quite clearly when Spectrum games load their title screens:
The loader here is simply reading all the bytes from $4000 (16384) to the end of screen RAM at $5AFF, in order. As you can see, the pixels are arranged in a very "interesting" fashion...
This makes accessing screen memory a little bit more involved than simply adding Y lots of TotalXBytes to find the line.
Finding screen line addresses from a Y Coordinate
Given a Y coordinate stored in an 8-bit register:
Y Coordinate bit layout (MSB to LSB):
zzxx xnnn
we can produce a 16-bit address in a register pair by placing these bits as follows:
Register pair bit layout (R are the bits for the x-axis offset, 010 provides the base address $4000):
010z znnn xxxR RRRR
Who knows why on earth the engineers designing the Spectrum decided to swap the position of the nnn and xxx bits... but they did.
(I'm sure it was the same kind of reasoning as providing a console with half an altivec unit... ahem.)
Geek Moment - Some code to use this stuff
Here is some Z80 code that makes use of this knowledge, to clear the screen - not in the order as shown in the loader above, but sequentially (from the user's point of view).
; Clear the screen in an ordered, top to bottom fashion
;
; Entry: None
; Exit: A, BC, D, HL all trashed
; (push on stack if needed)
; (push on stack if needed)
;
; Notes:
;
; This is obviously slower than simply blatting from
; $4000 to $5800, but that would reveal the three sectors
; of the screen AND the alternate line pattern
; (both seen when loading screens)
; (both seen when loading screens)
; This is an example method to show how to address screen
; RAM using a Y coordinate instead, so we can move from
; top to bottom, line by line.
; RAM using a Y coordinate instead, so we can move from
; top to bottom, line by line.
;
clearscreen_ordered:
LD B,192 ; num y lines
XOR A ; clear the accumulator
; (MUCH faster than LD A,$00)
; (MUCH faster than LD A,$00)
LD C,A ; c == y coord
; Here, we create the screen line pointer in HL,
; based upon the given y-coordinate in C.
; based upon the given y-coordinate in C.
; The y coordinate needs to be shuffled about a bit to be
; in the correct format for the Spectrum's bizarre
; hardware.
; in the correct format for the Spectrum's bizarre
; hardware.
ylinesloop:
LD A,C
AND $7 ; get first three bits,
; in the same position in H
; in the same position in H
LD H,A
LD A,C
AND $38 ; next three bits need to be
; shifted left twice,
; and placed in L
; shifted left twice,
; and placed in L
RLA
RLA
LD L,A
LD A,C
AND $C0 ; last two bits need to be
; shifted right three times,
; and placed in H
; shifted right three times,
; and placed in H
RRA
RRA
RRA
OR H
OR $40 ; also include the base address
; (screen mem starts at $4000)
; (screen mem starts at $4000)
LD H,A ; HL = ptr to line
; Individual lines are thankfully arranged in a linear
; fashion in memory. We can simply increase the
; pointer by one each time to clear a single line.
; fashion in memory. We can simply increase the
; pointer by one each time to clear a single line.
LD D,32 ; bytes per line (256 pixels)
xbytesloop:
LD (HL),$00 ; all pixels cleared
INC HL
DEC D
JP NZ,xbytesloop
INC C
DEC B
JP NZ,ylinesloop
ret
This code ignores the attribute memory for convenience's sake (that is linear, and would be a straight blat anyway... though it might be nice to do it in sync with each group of 8 lines... ;-))
ZX Spectrum Platformer Project - Misson Statement
Still coming up with that... but here is an overview of what I intend to cover!!
BASIC vs Machine Code
- Show simple screen wipe in BASIC
- Show simple screen wipe in assembly
- Explain screen mem insanity
- Show ordered screen wipe
- Show function to calc location y line in HL, x byte and rotation for x coord
- Show Sprite copying routine
- Interrupts - and why they don't suck
- Tiled level drawing
- Collision between non-evil platforms and enemies & Evil Platforms
- Random discussion on horizontal scrolling (and why vertical sucks by comparison)
- Sound synth (two channel!) for 1-bit beeper shit (no, I repeat NO 128K 8912 chip)
Game
- Keyboard!
- Joystick!! (as if modern Speccy users care… um, fuck the oxymoron there)
- C# Spectrum graphic editor (by me!)
- Level designing
- Pulling it all together (for a 1-level proper mad game!!)
Future ideas:
- Scrolling shoot-em-up?
- DEFINITELY: C64 platformer, maybe ShootEmUp - because… I never did!! XD
- Atari ST - next? (for same reason as C64)
Argument:
I see this project's purpose as two-fold:
- Show people how gameplay always triumphs, even with IMMENSELY limited resources
- Give back Da Respekt to the hardware, dudes!!! You can't get any crapper than the Spectrum for this, yet it was the platform of CHOICE for gameplay in its day (even though the C64 was far superior in terms of hardware, etc.)- how was this done?
But there's also:
- Something is wrong with the games industry… (oh this could go on for a bit, but… rather than be a bore, I thought showing WHY via the game would be the best way)
Subscribe to:
Posts (Atom)