The only bit the games care about is pingpong. The other bits are extracted and stored in memory (by some sort of common firmware library code that exists in each game) but never accessed.
If you have a moment you should take a look at my posts about Fighting Vipers 2 in the dev forum's timing thread. The first thing all the games do is count how many cycles it takes for pingpong to flip. They load up some very basic scene graph that includes a culling node referencing a VROM address (I haven't checked whether it is a model or some other kind of data -- maybe it is a special reference that is interpreted as a command?) and then try to assess frame timing.
Here is the subroutine translated to C code (read_tbl() reads the PowerPC's high precision clock register):
- Code: Select all
//
// This routine is executed once during startup. Calibrates the DEC value.
// If the DEC value is too low (i.e., the status bit flipped too quickly),
// the game will hang a little further on in suba0e8() in a decrementer
// loop that loops until the decrementer turns positive (which happens in
// the VBL handler when DEC is reloaded with this calibrated value).
//
void sub40c0()
{
// Measure the duration of one whole frame
wait_for_vbl();
uint32_t start_of_frame = read_tbl();
wait_for_vbl();
uint32_t end_of_frame = read_tbl();
uint32_t frame_duration = end_of_frame - start_of_frame;
// Issue a flush command
real3d_flush(2); // 0x88000000 = 0xdeaddead
// Wait for status bit to flip
read_real3d_status();
uint8_t old_status_bit = _real3d_status_bits[2];
do
{
read_real3d_status();
} while (_real3d_status_bits[2] == old_status_bit);
// Measure duration until next VBL
uint32_t start = read_tbl();
wait_for_vbl();
read_real3d_status();
uint32_t end = read_tbl();
uint32_t duration = end - start;
if (duration < 0x20)
duration = 0x20;
// Compute the time that the flush and subsequent status bit flip took. This
// is the only place in the code that this value is loaded, and it is used to
// reload the decrementer.
//
// What puzzles me is why they don't just measure the time directly by
// taking (start - end_of_frame) ?
_dec_reload_on_vbl = frame_duration - duration;
}
And here are the contents of the Real3D memory when this is executed:
- Code: Select all
The data written initially to Real3D RAM appears to be a minimalistic scene
graph consisting of one single viewport and one single culling node that
references an address in VROM which may or may not be a model. It has not yet
been analyzed in detail.
Viewport:
000000: 00000000 0.000000
000001: 01000000 0.000000
000002: 04800500 0.000000 <-- display list at 0x500
000003: 43fb4fc7 502.623260
000004: 00000000 0.000000
000005: bf64f92e -0.894427
000006: 3ee4f92e 0.447214
000007: 3f19999a 0.600000
000008: 3fb5fdbb 1.421806
000009: 3feed9e1 1.866024
00000a: 3f000000 0.500000
00000b: 3f000000 0.500000
00000c: 3ea9db1b 0.331750
00000d: 3f718087 0.943367
00000e: 3e8483f4 0.258819
00000f: 3f7746e9 0.965926
000010: 3ea9db1b 0.331750
000011: bf718087 -0.943367
000012: 3e8483f4 0.258819
000013: bf7746e9 -0.965926
000014: 060007bf 0.000000
000015: 00000000 0.000000
000016: 00802000 0.000000
000017: 00800100 0.000000
000018: 00000000 0.000000
000019: 00000000 0.000000
00001a: 00000000 0.000000
00001b: 00000003 0.000000
00001c: 00000000 0.000000
00001d: 00200600 0.000000
00001e: 002007c0 0.000000
00001f: 00000000 0.000000
000020: 00000000 0.000000
000021: 4e6e6b28 1000000000.000000
000022: 00000000 0.000000
000023: 3727c5ac 0.000010
000024: 00004c00 0.000000
000025: 00ffffab 0.000000
Display list:
000500: 02040000 0.000000 <-- culling node at 0x40000
000501: 00000000 0.000000
...
Culling node:
040000: 80000412 -0.000000
040001: 00000000 0.000000
040002: 00000000 0.000000
040003: 20000000 0.000000
040004: 00000000 0.000000
040005: 00000000 0.000000
040006: 00000000 0.000000
040007: 017fff00 0.000000 <-- VROM model at 0x7fff00 (0x1fffc00 in bytes)
040008: 01000000 0.000000
040009: eab8eab8 -111775389383496413841719296.000000
VROM:
01fffc00h: 9E 28 FD A3 9E 37 08 3A 18 49 94 02 04 BF 39 08
01fffc10h: 2B 8E 02 99 84 77 81 81 0A 49 FF BF F7 3A 39 6C
01fffc20h: 07 3C 13 6C B7 77 05 08 00 86 F8 78 C7 A7 11 74
01fffc30h: 00 64 0D 63 08 6E 0F D3 B6 20 E7 06 6F 5A 11 AE
01fffc40h: 83 52 92 CC 64 19 FF 08 35 F9 2A 4C 74 69 01 B1
01fffc50h: F6 21 A6 70 F6 B5 EC 7C 80 61 F8 85 6A 05 8E CF
01fffc60h: 67 20 DE 7D CF DF 59 64 64 99 E8 3A 23 4E E3 8C
01fffc70h: 4D 81 27 00 53 4F AB 50 61 75 BD A1 7E 26 80 C7
01fffc80h: 51 A8 2A E0 67 38 0A 28 54 88 90 08 8C BF 1D 28
01fffc90h: DC 28 40 C8 0B D9 89 05 A3 E9 B7 37 56 C2 D1 C4
01fffca0h: 71 C8 DC F3 FA 94 25 82 10 1A D8 04 40 5F 33 68
01fffcb0h: 8D 49 70 68 00 CD A3 6A B1 FA 9B 0C F9 29 44 9F
01fffcc0h: 43 50 AA EB E2 77 4C 41 4A F9 10 D2 2B F6 46 08
01fffcd0h: 84 38 80 A8 24 22 7F 32 15 13 7F 08 28 48 12 1D
01fffce0h: 84 37 E5 6F F3 BC 32 D8 0B 9C 26 14 00 BE 57 4C
01fffcf0h: CE 60 05 30 8C 09 A6 0A 04 84 AD 08 FB 07 58 E8
I know the location of fvipers2's main game loop and have even identified the functions where all Real3D transfers are performed before moving on to updating game logic. I don't have a complete handle on what it's trying to do but I've found a couple of timing routines. Basically, the games are looking at VBlank interrupts as well as counting cycles. The timing value computed at the beginning is loaded into the PowerPC's decrementer register, which decrements at the same rate as the high precision clock, TBL. When it hits zero, it triggers an interrupt and the DEC register is reloaded. It uses this information to determine when to advance to the next game frame.
To me, it appears the games are computing how long the Real3D spends rendering a frame. This only makes sense if the Real3D has a hard timeout for frame rendering and uses that to maintain a precise 57.5Hz rate regardless of scene complexity. Alternatively, they may simply be measuring how long of a timeout is required to flip the buffers (which presumably takes a while because it also cancels the current frame and initiates a new one).