Friday, October 22, 2021

Manipulating Draw Order for Better Performance Through Amortisation

I'll give a quick update and call today's entry a day because frankly, I'm not feeling too good in the head. Too many dark thoughts recently.

Anyway, I talked about getting parallax background movement to work yesterday and said that the speed was acceptable.

Well, it was acceptable in the stochastic sense. In reality, there were big dips in the processing rate ever so often even while using the PCOPY caching technique that came from the increase in workload needed to dump a whole bunch of large (relative to the screen) bitmaps at once whenever the background had to be shifted by a pixel due to the parallaxed movement. This was unacceptable for the simple reason that it was affecting the output animation in a very obvious way.

20 years ago me would have raised my hands and go ``well I don't know how to solve it''. The me of today just shrugged at 1am and said ``meh, let's use another screen page (we have 6 spare not counting the two that we are using for the page-flipping animation technique) to incrementally dump the 21 vertical slices of the background for the next pixel shift, and then just copy it over to the original page for the full background, filling in the missing slices that we didn't manage to do before''. And that was what I did.

It wasn't too hard to implement it---I just added a new variable amortisedXtile that kept track of which of the 21 slices I have drawn so far. Then, I shoved some variant of a conditional on amortisedXtile that would do the relevant PUT as needed. So, instead of paying 21×C all at once to draw the entire background, I just pay C slowly over 21 different rendering cycles to spread the cost around. It worked superlatively well in reducing those dips in processing rate.

One last thing I did to further amortise the cost of the parallax background update was to apply the same set up towards the shifting of the vertical slices by one tile when the left-most had fallen out of the screen. In a modern set-up, this would be trivial---I just needed to maintain [say] a singly-linked list of pointers to the bitmaps in memory, and just append a new one to the end while dropping off the first. Except in QuickBASIC, there isn't such a thing as ``pointers''. No, I can't even make use of the 20-bit physical address thing I mentioned before because I was using the built-in GET/PUT statements for these. Those are faster than whatever equivalent that I did in QuickBASIC, unless I decided to go down the Dark Arts Path of using pure assembly (recall that I've disallowed myself the use of that). If I wanted the array representing slice 1 to now represent slice 2, I would have to copy the contents of slice 2 back into slice 1. Sadly, the GET graphics statement works an order of magnitude faster than a FOR-loop that I could code in QuickBASIC.

So with all that amortised things added in, the parallax background renderer is much more efficient than before. All these are predicated on having more than 21 frames of activity before the time for pixel shift though, which is something I can control somewhat through my choice of the overall width of the background ``skybox'' image and the overall width of the entire level.

I also got the explosion rendering pipeline to work correctly, and wrote a basic pixel-collision function between the masks of two sprites. That is not sufficient for true pixel-perfect collision detection though---we need to take into account the intermediate motion between the start and end of the trajectory of the two sprites as well. Otherwise we will have the age-old problem where a sprite moves so fast that it completely bypasses collision altogether. That's a different math problem for a different day, but it will get done.

The final thing that I did was to add a basic gauge-bar renderer. The gauge-bar renderer is similar to say the life bar of some modern games, where if the number reduces, the difference between the original number and the current number is shown fleetingly with a mini-bar of a different colour. The renderer can do that now, but it cannot do it in a ``smoothly animating manner'' just yet. I'm not even sure if I want to do it, since it will require more sprite elements to keep track of, but we'll see.

I'm also thinking about incorporating hit-boxes in lieu of just using the mask to define collision. This would allow ``boss'' sprites to be more challenging to tackle. But we'll see.

Head isn't doing too well after being rudely woken up this morning by some incessant banging. Till the next update.

No comments: