An object-oriented Game Boy Advance 3D soft engine concept
Go to file
wgroeneveld ec49c37f1b demo 3 textures bootstrap 2020-07-16 16:14:46 +02:00
demos demo 3 textures bootstrap 2020-07-16 16:14:46 +02:00
engine demo 3 textures bootstrap 2020-07-16 16:14:46 +02:00
img back-face culling performance boost! 2020-07-15 12:03:50 +02:00
test rename sprite-engine subdir 2020-07-15 10:52:13 +02:00
.gitignore initial commit, stripped-down version of gba-sprite-engine 2020-07-08 09:05:02 +02:00
CMakeLists.txt demo 3 textures bootstrap 2020-07-16 16:14:46 +02:00
LICENSE Initial commit 2020-07-08 08:28:45 +02:00
README.md back-face culling performance boost! 2020-07-15 12:03:50 +02:00

README.md

A high-level object-oriented Game Boy Advance bitmap 3D software engine

What's this? A GBA MODE4 3D Software Engine - built from scratch.

This is (very) loosely based on David's 3D soft engine in C#/JS: https://www.davrous.com/2013/06/13/tutorial-series-learning-how-to-write-a-3d-soft-engine-from-scratch-in-c-typescript-or-javascript/

Engine blueprint: a stripped-down version of https://github.com/wgroeneveld/gba-sprite-engine/ combined with more tonc library functions.

Show me the money!

Sure thing.

Demo 1: without wires

design

Camera rotates 2/256 units on x and y each render cycle. You 'should' see a cube forming based on 8 vertices. It's a simple example to showcase what the Mesh class is about, and how GBAEngine handles projections.

60 FPS. Yay.

Demo 1b: with wires

design

A lot less smooth. 20 FPS. I implemented Bresenham somewhat similar to David's approach, without any good results. MODE4's weird byte write problems are causing trouble. Time to consult tonc and fix this:

design

30 FPS, winning 10 frames using bmp8_line().

Demo 2: load actual vertex content

From Blender/Babylon based on this.

A JS script has been provided that generates the needed C++ code. It's not great, and not very performant.

design

2 FPS. Ouch!! This thing has 507 meshes and 968 faces. GLHF! The GBA CPU does not seem to be very happy with that. Like everyone indeed says:

GBA Bitmap mode is not for gaming!

Use the gba-sprite-engine instead.

I am aware of countless optimization opportunities but even thinking about that makes me sleepy. This is a high-level C++ engine, meaning redundant stack objects could also cause problems. And I'm okay with that - it's a proof-of-concept!

Demo 3: rasterization

The RasterizerRenderer class draws triangles as 'fast' as possible, using horizontal scanlines. There is a fast way to lines into VRAM. I tried implementing Z-buffering, but the buffer was too big and too slow as z-coords also had to be interpolated...

design

At this point, I do not think it's that interesting to go on to texture mapping other than the fun of it. Even with a lot of haxx and tricks, the colored monkey won't ever spin at 30FPS...

Demo 3b: with back-face culling

It did improve performance. I exported a few Babylon meshes, and the octahedron with 8 faces does run at 20FPS compared to 11FPS when back-face culling was implemented (that omits rendering certain faces if z < 0)

design

Changing colors indicate certain triangles were not drawn (into the background). It does not help a lot with our monkey, alas. Too many vertices...

More examples of meshes (box, cylinder, octahedron, sphere, torus) included.

GBA-Specific problems

Fixed-point math sums up things nicely.

Numbers should be .8f, so lots of shifting is needed. Problem with Sqrt() BIOS functions is that they output .8f but require .16f? So, calculating the length of a vector:

inline FIXED length() const {
    FIXED toRoot = fxmul(v.x, v.x) + fxmul(v.y, v.y) + fxmul(v.z, v.z);
    return Sqrt(toRoot << 8);
}

Second problem, sin/cos are expensive so we use tonc's lookup tables - but they also come with weird requirements.

For instance, input is normally in radians. But we have fixed-point radians. And the lookup table is filled in [1-512] slices but actually requires input in [1-FFFFh]:

INLINE FIXED fxrad2lut(FIXED rad) {
    int scale = fx2float(rad) / (2*M_PI / 512);
    return (scale << 6) * 2;
}

INLINE FIXED fxsin(FIXED fxrad) {
    if(fxrad == 0) return 0;
    FIXED theta = fxrad2lut(fxrad);
    FIXED sin = lu_sin(theta);
    return fx12Tofx8(sin);
}

Divisions are a bit of a mess, but converting them to fixed-point and going back is not going to help much. More details in math.h.

In any case, lots of rounding errors occur. It is luckily not a problem due to GBA's limited screen dimensions.

Limited iWRAM size is another problem. Use const arrays as much as possible!