Very brilliant work, Firefox. Many thanks for this one. It's running very well when overclocked to 280 mhz but even at 240 mhz it's still impressive.
I hope you have nothing against this quote from the yakyak.org forum, which I think is very interesting to read:
Firefox said:
Original Linux Port
This was based on SDL, and the GP2X runs Linux and comes with SDL libraries. When I got my GP2X I'd recently ported T2K to Linux and realised that it'd be simple to recompile it for the GP2X. I couldn't get the rendering code to work properly so I just called SDL's filled rectangle API function to draw a 1x1 rectangle for every pixel on the screen.
This version really did look like a slideshow when it was running! It wasn't a serious attempt to port T2K to the GP2X, just something to quickly try out the toolchain. I posted some pictures of it on YakYak for a laugh (noting how slowly it ran).
SDL Jettisoned
As I began to learn a bit about the GP2X I found out how to draw directly onto the framebuffers, and tried it out on my GP2X build of T2K to see what sort of speedup I'd get. Surprisingly it wobbled along at about six frames per second without any more change than that.
Dual Core Version
The big system-on-a-chip that the GP2X is built around contains two ARM processor cores. The main one (an ARM920T) has big 16kB caches and proper memory management units, and Linux runs on it.
The second one (an ARM940T) is very similar to the first except with smaller 4kB caches and a memory protection unit that you can use to carve memory up into cached and uncached regions. You can run whatever you like on that one, but you have to write it straight onto the metal without an operating system between you and the hardware (not an entirely bad thing). I wrote some boot code for it, and tested it with some little C routines that blinked the GP2X's battery light on and off and drew coloured lines on the framebuffer.
The Jaguar has a part of its graphic chip called the Object Processor (OP) which renders each frame using a sort of scene graph.
By switching the compiler's optimiser to optimise-for-space I was able to get the chunk of Dio's code that emulates the OP to just about fit into the ARM940T's instruction cache, which means it can render frames much like the Jag's real silicon OP without stealing too much memory bandwidth from the main ARM920T.
(Big long gap of more than a year, in which I was snowed under with very heavy-duty work that left me with little time or inclination to work on this. A few half-hearted and unsuccessful attempts to fit the Cyclone 68000 emulation core to the build. Lots of jibes from Leekfiend over when it's going to be done...)
Cyclone Version
Prior to this I was using the Musashi 68000 emulation core. It's very accurate but written in C, so it can be a bit slow. Our very own fdave had developed a 68000 emulation core called Cyclone for use in his PDA Megadrive emulator which is written in ARM assembler (actually it's a C program that generates ARM assembler) and so is very fast. So it seemed an obvious next step to swap Cyclone for Musashi and thus gain more speed.
A month or so ago a version of the UAE Amiga emulator was released for the GP2X using an improved version of the Cyclone 68000 emulation core developed by a bright Lithuanian chap called Notaz. I thought, "If that thing runs an Amiga it must be complete enough to run a Jaguar!" So I downloaded the code for the Amiga emulator, fished out the Cyclone core, and tried fitting it to T2K. Didn't work, and I was busy that weekend so didn't have much time to look at it.
A few weekends later I had more time and had another go. I posted some questions on the GP2X developer forum and Notaz pointed me to a proper release of his latest version of Cyclone.
It took quite a bit of head-scratching and lots of stepping through 68000 code (in the excellent integrated debugger in Dio's PC version of T2K), and comparing what that was doing against logs of 68000 activity from runs of the Cyclone-based build on the GP2X. But eventually I worked out what was wrong and got it to work.
It was quite nostalgic stepping through 68000 code again for what must be the first time in the thick end of a decade.
Fast Memory Version
After I'd got Cyclone up and running I looked for something else I could do to squeeze more speed out of T2K, and soon spotted the emulated Jag's memory system.
The Jaguar has a fairly complicated memory map with several different areas of RAM, the big ROM in the cartridge, memory mapped registers in the various custom chips, etc etc.
T2K ordinarily runs through some logic that works out which part of the memory map each memory access is actually referring to, and then does the right thing based on that. That's not a burden to a modern PC's x86 processor running at a couple of gigahertz, but it's a lot of overhead on an ARM running at a few hundred megahertz.
After thinking about it for a bit I came up with a scheme that would be written in ARM assembler that would do the same job in very few ARM instructions. But before I started I decided I'd do a quick prototype in C to check that it would work.
So, it's very simple: I divide the Jaguar's memory map into little regular blocks that are small enough that each block in definitely inside only one of the Jag's many different memory areas.
Then I create several tables of function pointers for different types of memory access (read, write, different widths) and fill these tables with pointers to tiny little functions that do the right thing for the corresponding little block of memory. Read from one of the RAM areas, write to the registers of one of the custom chips, etc. The entries for writes to the cartridge's ROM just point to empty functions, so the ROM is effectively write protected.
With a couple of ARM instructions an address in the Jaguar's memory map can be converted to a function call, and each function will only usually be a few instructions with no stack operations. So each Jaguar memory access only takes a handful of ARM instructions unless there's actually some real emulation-related work to be done.
It took about three hours to put the prototype together, and by tweaking the way I'd phrased my code and examining the compiler output it turned out to be within a few ARM instructions per emulated memory access to what I'd been planning to code by hand, so I left it at that.
This *really* sped the whole emulator up! Pretty much everything in the emulator (even Cyclone to a large extent) has to go through the memory subsystem, so speeding that up sped everything else up. And it only took three hours' work! Bargain!
So the moral of the story is: make sure there isn't an algorithm that's a better fit for what you're trying to do, and speak nicely to your compiler and it'll probably do the l33t assembler coding for you.
Replacing Tom's RISC Core
The next obvious target for efficiency improvements was the custom RISC processor at the heart of Tom, the Jaguar's graphics processor.
I've done lots of low-level ARM-related work in the last few years, and a lot of ARM assembly-language programming in the last couple of months. So I'm currently at one with how the ARM works.
I'd picked some instructions out of the Tom RISC processor's instruction set and sketched out how they could be emulated by the ARM, and it turned out to be a pretty efficient fit. So writing a Jaguar RISC core in ARM assembler seemed to be the way ahead.
That's going to take me a while though, so Dio suggested I could probably get a nice speedup for not too much effort if I wrote a code generator to convert his RISC emulation functions into lots and lots of functions with much of the calculations done ahead of time (and thus able to be optimised by the compiler) to save on the amount of work that would have to be done at runtime.
That took me about a day in total, spread over the bank holiday weekend. It was somewhat tedious and mechanical work, and easy to lose concentration. I'd convert Dio's RISC emulator code function-by-function, adding each one to the code generator, diverting emulation for that instruction away from the normal handler to the generated code, run the code generator, wait an increasing amount of time for its output to compile, test the build on the GP2X, add another instruction... ZZzzzz...
But it was a mechanical process, and I eventually got to the end. I pulled the diverter out and just let all RISC instructions be handled by the generated code. It all still worked, and was indeed a fair bit faster.
Original can be found
here.
Regards,
Stephan