Thursday 10 November 2022

To what end?

I've implemented, but not tested, the routines that update the shadow sprite registers and copy them to sprite hardware.

In the original code, the shadow registers (as the name implies) are RAM-based copies of the hardware registers and have the same format. There is a SUB CPU routine that periodically updates the shadow registers from the object table, with the necessary bit-twiddling to get data into the right format. And another routine elsewhere that simpy block-copies the shadow registers to the hardware.

Somewhat stupidly, my initial implementation was to preserve the original format of the shadow registers, and then convert on-the-fly to the Neo Geo in place of the block copy. In fact I had gone as far as coding the latter routine, but then realised how pointless this was when coding the routine that filled the shadow registers from the object table. A double bit-twiddling nightmare for no good reason.

Instead, I moved the shadow registers into the OSD (Neo Geo) layer together with the routine that fills them from the object table. And then simply replaced my previous bit-twiddling routine to update the Neo Geo sprite registers with a number of what are esentially block byte copies. Too easy.

In order to test any of this though, I need to transcode some routines that use the object table. As I mentioned in an earlier post, the first instance of that is the sparkle chaser around the Xevious logo.

I spent some time thinking about the most efficient way on the 68K to organise and access the object tables. On the original arcade game, they comprise a number of 2-byte-per-object tables on 256-byte boundaries; so they can be accessed for a single object by simply updating the H register (L=0/1), for example. I've opted for a single 32-byte-per-object table, accessed using either Address Register Indirect with Displacement or Address Register Indirect with Index addressing mode, depending on specific circumstances.

It really does result in some nice, compact code - compared with the Z80 - but there is one stick-in-the-mud... endianity.

The X and Y sprite coordinate values are the only 16-bit tables; the rest are pairs of 8-bit values. It is very advantageous to store the X & Y with the natural endianity of the host, that's a no-brainer. But given the Z80 and 68K have opposing endianity, this causes an issue when pairs of single-byte value are accessed via 16-bit register pairs on the Z80.

An example; in the so-called 'state' table, byte0 is the state, and byte1 is the attribute. Ordinarily they are manipulated using byte accesses, but there are a few instances where they are initialised using the HL register pair. eg:

ld  hl, #0xF30
ld  (obj_code_tbl+0x1E), hl

This initialises the code for object 0x1E (byte0) to 0x30, and the attribute (byte1) to 0x0F.

When I naively transcode that, I'd end up with something like:

move.w  #0xF30,(0,a5)

...which of course puts 0x0F in byte0, and 0x30 in byte1 - ie. swapped around!

So this gives me two options; either swap the order of bytes in the immediate operand of the move instruction, or swap the byte order in the table. I was leaning toward the latter, mainly because it's easier to forget to swap the bytes manually, but ultimately decided on the former - since it's not done in a lot of places, and I like the idea of preserving the original table order.

I should note that I'm using .equ values for all the displacement values, which not only negates the need for me to remember them for hundreds of instructions in the transcode, but they're also self-commenting!

move.w  #0x300F,(_CODE,a5)

Now back to sprites, which should be working, but they're not. Again, par for the course...

No comments:

Post a Comment