-----Original Message----- From: Simon Cooke <[EMAIL PROTECTED]> To: [EMAIL PROTECTED] <[EMAIL PROTECTED]> Date: 21 February 1999 07:38 Subject: Re: SAM FAQ Version 2
>Unorthodox frame sync code? Pray tell! I can only think of a couple of >ways of doing it... OK. Apologies to everyone because this is a quite long. Also, advanced apologies if it's 'egg sucking' territory to anyone. God, that sounds patronising... Sorry. :-) Anyway, deep breath! Here we go: First, a little description of how SAM Defender works will later highlight my need to use unorthodox frame sync code. Defenders' internals are almost exclusively 'stack based'. All of the graphic routines use the stack for dumping/erasing. The main sprite dumpers are all hard-coded and use an interleaved format. This format removes the need for an ADD 128 or SUB 128 to the screen address when moving to the next screen line, reducing it to a simple INC or DEC of the high-byte with the ADD or SUB required only when switching the dump direction at the top of the sprite. All of the main data structures inside the program are again manipulated using the stack. One of the main reasons for this is the fact that all of the movement within the game is fractional, using 16-bit fixed-point allows easy programming of inertia and gravity effects. Also, the width of the 'world' is some 2048 pixels, so 8-bit just doesn't cut it! These 16-bit data lists are much faster to manipulate using the stack, especially when all of the usual registers are tied up leaving only the fat and lethargic IX or IY pairs, which are to be avoided if performance is the key factor. To simplify, the stack is used almost exclusively because it's so efficient and fast at shunting around word-sized chunks of data. There's an *awful* amount of this being shunted around, especially when you have to dump/erase a 256-pixel wide landscape, manipulate the data lists for as many as 40 sprites, dump/erase any of these sprites that happen to be on the 'window' into the 2048-pixel wide world... All this must happen at a constant (or as close as) 50-fps for the game to be anywhere near the quality of the arcade original. So, the good old stack gives a much needed boost in performance. Note the use of 'as close as' above? This becomes important later. Because data lists are manipulated using the stack, and these lists contain data that is used continuously by the program, any corruption would spell disaster. Corruption would occur if an interrupt occurred, for example. SAM Defender runs with the interrupts in a permanently disabled state to prevent such potential corruption. From a performance point of view it wouldn't be economical to DI, EI all over the place because stack manipulation is occurring almost continuously during the game. So, back to the question of frame syncing. Usually you can frame sync on machines like SAM by having a HALT instruction at the top of your main loop. Trouble is, HALT requires the interrupts to be on. Defender runs in a permanent DI state, as mentioned. OK, so I could have EI, HALT but that would have meant setting up an ISR that simply RETs. Not worth the bother. I'm not complicating the issue here with the potential for other types of interrupt to occur... Originally I was frame syncing by reading the FRAME INT bit on port 249 at the top of the main loop. This worked fine, until the game 'missed' a frame, at which time the main loop will actually catch the next frame, causing a drop down to 25-fps. Everything switching to half-speed for a time, including the sound, then back to full-speed is just plain ugly! If you watch the arcade original you'll notice that it misses frames too. In fact, it misses more frames than my SAM version does! But, there's one crucial difference. It doesn't drop to half-speed but rather jumps/jerks a bit, with very little slowdown. How? I concluded that the programmer was able to detect the fact that the game had missed a frame and simply didn't bother waiting for it when the program got back to the top of the main loop. He simply allowed the program to continue as if it *had* caught the last frame, which technically it did but was a little late! SAM Defender can never miss any more than *one* frame in a given cycle of the main loop. So, knowing you've missed a frame, you can simply continue processing as if you *had* waited for it. In other words, on a missed frame remove the frame wait at the top of the main loop. The benefit here is that the program is allowed to run *full belt* for the next cycle, and for any cycles after that where a frame is missed, until everything falls back in sync. The on-screen effect is identical to the arcade original, a slight jumpiness and a very small slowdown which is proportional to the amount of 'extra' work, beyond a normal frame, that the program has to do. No more 50% speed drop in game motion, and no audio quality loss. Much more pleasing! It also let me allow the program more objects in the world and more sprites on-screen! ;-) How does it know when its missed a frame? Simple, I hear you cry, use the LINE INTs! Yes, that's one way. But I can't allow interrupts, so I can't use the LINE INTs to set a 'missed frame' flag! ;-) I tackled it like this: Crucial to the success of this method of frame syncing is the proper functioning of the 'HPEN' (vertical lightpen register, port 504) when no lightpen or similar device is connected. Defender includes code in its initialisation that detects if you have a lightpen connected. If you have, it informs you that it must be removed for the game to function correctly. Reading the HPEN port produces a value that reflects the current vertical (Y) raster position. It runs 0 - 191 for the main screen area and 192 when the raster is in the border area. The frame sync code keeps two variables: 1. A flag to signal that the last frame was missed. "missed_last" 2. The raster position after processing 50%. "half_way" "half_way" is set thus: After Defender has processed most of its more time consuming tasks, a read is made of HPEN and saved in "half_way". This is approximately 50% of all the processing Defender has to do in one frame. At the start of the main loop I check the "missed_last" flag, if it's false (zero) I simply compare the "half_way" variable to 192. If this compare is true then the program managed to do all of its processing before the raster left the border area at the top of the screen. In this case it simply waits for the HPEN contents to change from 192 to <>192 and then back to 192 again. Similar to a 'normal' vertical blank routine. Note, the raster *cannot* reach the start of the border area at the bottom of the screen after Defender has processed just 50% of its tasks. However, programs with the potential to miss more than one frame in a row might. This routine wouldn't work correctly in these cases. If "missed_last" was true (not zero), signalling a known missed frame, or "missed_last" was false but "half_way" >=0 and <=191, signalling a potential missed frame, the code reads the HPEN port. If HPEN = 192 (back in the border again) then its a missed frame, the "missed_last" variable is set to true and a jump is made into the main game processing, skipping the frame-sync wait. Else, HPEN is compared to "half_way" and if it's value is <= "half_way" the raster has wrapped through the border and is back on its way down the screen to the "half_way" point again, which is a missed frame. In this case, again, "missed_last" is set to true and a jump is made into the main game processing, skipping the frame-sync wait. If HPEN > "half_way" then the program is back on track and the "missed_last" flag is cleared and a 'normal' frame-sync (a wait for HPEN = 192) is performed before entering the main game processing loop. If you throw some test values at the algorithm you'll see how it works. After a missed frame, but the program is back in sync again, it'll take at least the next frame to settle down. This isn't noticeable however! ;-) There are probably other ways to achieve the same result. This way worked great for Defender on a *real* SAM. Simcoupe doesn't seem to like it though! Phew... Lots of writing for a routine that's only 24 lines long!! Hurry with that Win32 port, Si. What Simcoupe desperately needs is quality sound emulation and a 50Hz refresh, if that's possible under Windows???... Chris.

