real3d end frame

Technical discussion for those interested in Supermodel development and Model 3 reverse engineering. Prospective contributors welcome.
Forum rules
Keep it classy!

  • No ROM requests or links.
  • Do not ask to be a play tester.
  • Do not ask about release dates.
  • No drama!

real3d end frame

Postby Ian » Sun Dec 17, 2017 9:00 am

Gonna write this here before I forget about it. I think I finally figured out what triggers a real3d frame draw (at least for the 3d).
It's simply a flush or 0x88 after writing to the high culling memory.

So you can have any combination of writes to

WriteLowCullingRAM
WritePolygonRAM
WriteTextureFIFO
etc ..
then once this happens
WriteHighCullingRAM
0x88 <- frame should drawn

You can even have this

WriteLowCullingRAM
0x88
WritePolygonRAM
0x88
WriteTextureFIFO
0x88
WriteHighCullingRAM
0x88 <- draw now

If WriteHighCullingRAM followed by 0x88 is never written to, we don't need to draw anything. This happens extensively in loading sections. In these sections the 2d layer is normally the only thing active. In games like virtua fighter we are filling the memory and textures with total junk as stuff is loading.

ie this is what happens if you disable the 2d layers during loading

Image

Image
Last edited by Ian on Sun Dec 17, 2017 9:49 am, edited 1 time in total.
Ian
 
Posts: 2044
Joined: Tue Feb 23, 2016 9:23 am

Re: real3d end frame

Postby HarryTuttle » Sun Dec 17, 2017 9:37 am

Great! :)

Can't wait to test it. Meanwhile I'll try to port into my frame sync mod/patch.
User avatar
HarryTuttle
 
Posts: 646
Joined: Thu Mar 09, 2017 8:57 am

Re: real3d end frame

Postby Ian » Sun Dec 17, 2017 9:43 am

We still need to understand better how timing works to fix this. Because currently in many games we have frame updates straddling 2 frames.
If you disable GPU threading, it should be possible to trigger a draw directly after:

WriteHighCullingRAM
0x88
Ian
 
Posts: 2044
Joined: Tue Feb 23, 2016 9:23 am

Re: real3d end frame

Postby HarryTuttle » Sun Dec 17, 2017 10:00 am

Ian wrote:Because currently in many games we have frame updates straddling 2 frames.

You mean like Dirt Devils?
User avatar
HarryTuttle
 
Posts: 646
Joined: Thu Mar 09, 2017 8:57 am

Re: real3d end frame

Postby Bart » Sun Dec 17, 2017 10:05 am

I agree this memory region needs to be set up correctly before drawing a frame. But do you really think the hardware is detecting writes to high culling RAM, setting some internal status bit, AND waiting for the flush command? Seems more likely that the operation is independent. I think the games' main loops are written in such a way as to guarantee that this memory region is written *before* the frame begins, and I think 0x88000000 indicates whether or not the Real3D should swap the ping pong memory once it is ready to draw the next fame.

If you take a look at the init sequence I posted a while back, the game is trying to initialize two different memory buffers that reside at the same address, using 0x88000000 and waiting an entire frame to guarantee that they have been swapped out.

I think this is consistent with your observations unless I'm forgetting something (been a while since I've looked, but I actually wanted to resume my reverse engineering of Virtual On's startup sequence on a long plane flight I have coming up this Tuesday). I believe if the timing is correct, the games *will* write to all regions and then flush.

I believe you were right that the Real3D itself is operating in some sort of frame locked mode that allows it to somehow abort processing when too much time has elapsed. All of the games use the decrementer to precisely count cycles, and I believe what they are doing is waiting for some particular scanline at which point some sort of sync mechanism kicks in on the Real3D. Some games are more sensitive than others. It's quite possible that this value is loaded up via JTAG or some other register. Or, it could be constant. Also, the presence of *two* frame interrupts is probably related to this. The games only use one of them. I'm actually wondering whether instead of spending time on VON2's init sequence it would be more useful to write a Model 3 test program that measures at which point in the frame these two interrupts occur?

I'm not going to be able to develop a test program that probes the Real3D in the short time I have. I'm flying home for Christmas on Tuesday but am only staying a week and have a ton of work to finish up on my two HoloLens projects. The EPROM procedure is tedious and damaging to the board. But, I can augment the test program I've already developed and see what's going on. I can print out the difference in cycle count between one interrupt and the other, which would give us their relative positions (although still not their absolute positions within a frame). It's quite likely that one of them is VBL and the other is either start of frame, end of VBL, or maybe somewhere mid-frame and related to the Real3D.
User avatar
Bart
Site Admin
 
Posts: 3086
Joined: Thu Sep 01, 2011 2:13 pm
Location: Reno, Nevada

Re: real3d end frame

Postby Ian » Sun Dec 17, 2017 10:16 am

I agree this memory region needs to be set up correctly before drawing a frame. But do you really think the hardware is detecting writes to high culling RAM, setting some internal status bit, AND waiting for the flush command?


Yes. 0x88 is used frequently after updating any of the memory regions, texture, polyram etc. I think it marks the transfer as complete. High culling memory is also the entry point to the database, so once that is updated it can draw a new frame. The high culling memory always seems to be written last in every frame. The other memory regions will update more or less randomly.
Ian
 
Posts: 2044
Joined: Tue Feb 23, 2016 9:23 am

Re: real3d end frame

Postby Ian » Sun Dec 17, 2017 10:22 am

This is the code I used for testing

Code: Select all
static int lastVal = 0;

void CModel3::Write32(UINT32 addr, UINT32 data)
{
  if ((addr&3))
  {
    Write16(addr+0,data>>16);
    Write16(addr+2,data);
    return;
  }

  // RAM (most frequently accessed)
  if (addr<0x00800000)
  {
    *(UINT32 *) &ram[addr] = data;
    return;
  }

  // Other
  switch ((addr>>24))
  {
  // Real3D trigger
  case 0x88:  // 88000000
    GPU.Flush();
   if (lastVal != 0x88) {
      printf("flush\n");
   }
   lastVal = 0x88;
    break;
 
  // Real3D low culling RAM
  case 0x8C:  // 8C000000-8C400000
    GPU.WriteLowCullingRAM(addr&0x3FFFFF,FLIPENDIAN32(data));
   if (lastVal != 0x8C) {
      printf("low culling RAM\n");
   }
   lastVal = 0x8c;
    break;

  // Real3D high culling RAM
  case 0x8E:  // 8E000000-8E100000
    GPU.WriteHighCullingRAM(addr&0xFFFFF,FLIPENDIAN32(data));
    if (addr == 0x8E000000) {
      printf("<---- updated\n");
    }
   if (lastVal != 0x8E) {
    printf("high culling RAM %x\n", addr);
   }
   lastVal = 0x8e;
    break;

  // Real3D texture port
  case 0x90:  // 90000000-90??????
    GPU.WriteTexturePort(addr&0xFF,FLIPENDIAN32(data));
   if (lastVal != 0x90) {
      printf("texture port\n");
   }
   lastVal = 0x90;
    break;

  // Real3D texture FIFO
  case 0x94:  // 94000000-94100000
    GPU.WriteTextureFIFO(FLIPENDIAN32(data));
   if (lastVal != 0x94) {
      printf("texture FIFO\n");
   }
   lastVal = 0x94;
    break;

  // Real3D polygon RAM
  case 0x98:  // 98000000-98400000
    GPU.WritePolygonRAM(addr&0x3FFFFF,FLIPENDIAN32(data));
   if (lastVal != 0x98) {
      printf("polygon RAM\n");
   }
   lastVal = 0x98;
    break;


You can see exactly how the writes are happening without spamming too much data that. It's quite clear the high culling memory updates are always written last during a frame, or not at all during some loading sections (when nothing should be drawn anyway).

Should probably add, its useful to print a swapbuffers() to see where we are drawing relative to the updates.
Ian
 
Posts: 2044
Joined: Tue Feb 23, 2016 9:23 am

Re: real3d end frame

Postby Bart » Sun Dec 17, 2017 10:37 am

But it is definitely the case that there is a double buffering mechanism on one of the culling RAM regions (I forget which one, though). The SDK confirms it. The rendering process, however it is triggered, swaps the buffers first and then renders. And I don't think all of the memory regions are double buffered this way, only one of the two culling RAM regions. If they were all double buffered, an end-of-transfer signal makes sense, but if only one of them is, I don't know why the games are writing it and my guess is that it appears to be a misunderstanding on the part of the programmers (which is not at all unusual).

Have you tried testing this method with my timing changes? As far as I remember, the games do set up the transfers so that they all happen one after another, and they should not be split up between frames. But *when* the transfers start was somehow determined by IRQ timing.
User avatar
Bart
Site Admin
 
Posts: 3086
Joined: Thu Sep 01, 2011 2:13 pm
Location: Reno, Nevada

Re: real3d end frame

Postby Ian » Sun Dec 17, 2017 10:58 am

I haven't tried it with your timing changes
Only briefly tested that code. Some games like daytona battle on the edge and fighting vipers looked miles better, but games like vf3 the polys were having large gaps between them
Ian
 
Posts: 2044
Joined: Tue Feb 23, 2016 9:23 am

Re: real3d end frame

Postby Bart » Sun Dec 17, 2017 11:12 am

Timing definitely affects VF3 in that way. My timing changes also broke it.

I need to think about this some more tomorrow. My FV2 disassembly is actually on a work machine. The SDK refers to "culling RAM" and "ping pong RAM". We refer to these as culling RAM high/low. I think only one of these is swapped. I had once convinced myself that the games are performing a double-swap at the beginning, when initializing memory, so that they "know" exactly what is in which buffer when they start up. But reviewing the previous thread, I see that FV2 writes some data to 0x8cxxxxxx, flushes twice, and then writes more data there. This is during init only. Doesn't make a whole lot of sense anymore. I need to see all that code in context again.

Another test I could potentially do on the real hardware is write to 0x88000000 and see how long it takes the bit to flip. The code I've looked at, which measures the bit flip time, actually reads the "old" value immediately after the flush. Under my old hypothesis, that it was flipped at some specific point in the frame, this is fine. But if the flush actually triggers a swap immediately, the games should be saving this bit right before the flush.

Actually, come to think of it, maybe I should just test the calibration routine? I assume it will still work with garbage data in culling RAM. I could even probably duplicate the first few transfers that FV2 performs, just to set up the memory in a sensible fashion (really, just nulling out the viewports). This won't really confirm exactly what's going on but having some insight into the timing of all this would be super useful.

I wish we had time to create a test 3D scene that consisted only of two triangles, untextured, in poly RAM: one green, one red. I could then run some experiments, alternating frames to see when one gets rendered vs. the other... If someone could generate the necessary data (viewport, display list, culling node, transform matrix) in the next couple of days, I can try to write a program to do this...
User avatar
Bart
Site Admin
 
Posts: 3086
Joined: Thu Sep 01, 2011 2:13 pm
Location: Reno, Nevada

Next

Return to The Dark Room

Who is online

Users browsing this forum: No registered users and 1 guest