Frame Timing

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!

Re: Frame Timing

Postby Bart » Mon Jun 27, 2016 10:57 am

Unfortunately not :( I looked at that a while back. Check out Real3D.cpp:889. Only bit 1 (0x2) is ever checked by the games and it doesn't correspond here to anything. My theory is that the data returned back to the PC is a combination of re-packaged status registers and software-defined fields assembled by the PowerPC.

One interesting thing is that the games never seem to check any other bit besides that one, despite reading all the status regs each frame.
User avatar
Bart
Site Admin
 
Posts: 3086
Joined: Thu Sep 01, 2011 2:13 pm
Location: Reno, Nevada

Re: Frame Timing

Postby Ian » Mon Jun 27, 2016 11:20 am

Wonder if the status bit should be set after an actual SwapBuffers() calls, or is this already the current logic
Ian
 
Posts: 2044
Joined: Tue Feb 23, 2016 9:23 am

Re: Frame Timing

Postby Bart » Mon Jun 27, 2016 11:41 am

Ian wrote:Wonder if the status bit should be set after an actual SwapBuffers() calls, or is this already the current logic


Quite possible but the problem is that we never found the swap buffer command. It's almost certainly related to the write to 0x88000000 but this is also used to trigger texture uploads (of which there seemingly can be more than one per frame). Right now, the logic sets this flag based on cycle timing, with the thinking being that perhaps this is a flag indicating the VBlank period is occurring.

Rather, the flag could perhaps indicate that the device is busy rendering, processing textures, or both.

I also wonder whether the device determines whether to render or upload textures by checking the texture FIFO. E.g., if FIFO not empty, upload textures, else render a frame?
User avatar
Bart
Site Admin
 
Posts: 3086
Joined: Thu Sep 01, 2011 2:13 pm
Location: Reno, Nevada

Re: Frame Timing

Postby Ian » Mon Jun 27, 2016 12:01 pm

Quite possible but the problem is that we never found the swap buffer command.


maybe there isn't one? Maybe the hardware contentiously renders the database, except when its actively being written to. The documentation seems to imply that 60fps is almost a hard coded assumption. It even has a mode where it'll stop drawing geometry to hit this rate, which is quite bizarre. Not sure modern hardware has any equivalent to that.
Ian
 
Posts: 2044
Joined: Tue Feb 23, 2016 9:23 am

Re: Frame Timing

Postby Bart » Mon Jun 27, 2016 12:49 pm

Ian wrote:
Quite possible but the problem is that we never found the swap buffer command.


maybe there isn't one? Maybe the hardware contentiously renders the database, except when its actively being written to. The documentation seems to imply that 60fps is almost a hard coded assumption. It even has a mode where it'll stop drawing geometry to hit this rate, which is quite bizarre. Not sure modern hardware has any equivalent to that.


There doesn't seem to be a way to lock RAM, for example. Maybe it could achieve such a frame rate lock by syncing rendering to VBL or something. Not sure exactly how the timing would have to be in such a case. I presume that the games update their logic as the last frame is being output to the display and then, when the active part of the display period is over and VBL begins, the Real3D starts rendering again and has to finish by the time VBL is over. But this would leave very little time for rendering because VBL is typically a very small fraction of the total frame time.

The 60Hz lock could simply have been managed by the PowerPC. Remember that it sits between the WinNT API and the rendering chipset.
User avatar
Bart
Site Admin
 
Posts: 3086
Joined: Thu Sep 01, 2011 2:13 pm
Location: Reno, Nevada

Re: Frame Timing

Postby Ian » Mon Jun 27, 2016 1:22 pm

As far as I can tell rendering is trigged by PRO_Flush();
Ian
 
Posts: 2044
Joined: Tue Feb 23, 2016 9:23 am

Re: Frame Timing

Postby Bart » Mon Jun 27, 2016 1:55 pm

Ian wrote:As far as I can tell rendering is trigged by PRO_Flush();


That's on the PC side, though, which doesn't exist on Model 3. But presumably this translates to some single command, like writing 0xDEADDEAD to 0x88000000 on the PowerPC. Is PRO_Flush() used when uploading textures as well?

And what about ping pong memory? Ping pong seems to imply some sort of back and forth transfer, or buffer swapping, yet there are two other memory areas the PowerPC can write to (culling RAM and polygon RAM -- ping pong memory is either high or low culling RAM in Supermodel's parlance).
User avatar
Bart
Site Admin
 
Posts: 3086
Joined: Thu Sep 01, 2011 2:13 pm
Location: Reno, Nevada

Re: Frame Timing

Postby Ian » Mon Jun 27, 2016 2:07 pm

Well in the example code it calls PRO_DisplayDatabase every frame
Which decompiled looks like
Looks like it syncs the memory, then calls flush to actually draw it.

Code: Select all
signed int PRO_DisplayDatabase()
{
  signed int v0; // esi@1
  signed int v1; // edi@1
  int v2; // ebx@1
  int v3; // ecx@1
  int v4; // eax@3
  int v5; // eax@5
  PRO_Viewport *v6; // ecx@7
  PRO_Dynamic_Update *v7; // ecx@9
  int v8; // eax@12
  int v9; // edi@14
  int v10; // ebx@14
  __int32 v11; // ST00_4@15
  PRO_Vertex *v12; // eax@15

  v0 = 1;
  v1 = 1;
  v2 = *(_DWORD *)(*((_DWORD *)g_API_Data + 3) + 2316);
  v3 = *((_DWORD *)g_API_Data + 4859);
  if ( v3 )
  {
    v1 = *(_DWORD *)(v3 + 8);
    PRO_Dynamic_Update::DoUpdate((PRO_Dynamic_Update *)v3);
  }
  v4 = PRO_API_Data::FormatUserTextures(g_API_Data);
  if ( v4 != 1 )
    v0 = v4;
  v5 = PRO_API_Data::FormatUserMicroTextures(g_API_Data);
  if ( v5 != 1 )
    v0 = v5;
  PRO_SetScroll(0);
  PRO_ProcessAnimations();
  v6 = (PRO_Viewport *)*((_DWORD *)g_API_Data + 47);
  if ( v6 )
    PRO_Viewport::UpdateSiblingPointers(v6);
  PRO_Directional_Light_List::ProcessDirectionalLightList(*((PRO_Directional_Light_List **)g_API_Data + 323));
  PRO_Range_Node_List::ProcessRangeLodNodeList(*((PRO_Range_Node_List **)g_API_Data + 324));
  PRO_Memory_Manager::WriteDataToHardware(*((_DWORD *)g_API_Data + 2));
  PRO_Memory_Manager::WriteDataToHardware(*((_DWORD *)g_API_Data + 1));
  PRO_PingPong_Manager::WriteAllDataToHardware(*(_DWORD *)g_API_Data);
  v7 = (PRO_Dynamic_Update *)*((_DWORD *)g_API_Data + 4859);
  if ( v7 )
    PRO_Dynamic_Update::DoIO(v7);
  PRO_Flush();
  if ( *(_DWORD *)(*((_DWORD *)g_API_Data + 3) + 2316) - v2 > 1 )
  {
    v0 = 4;
    v8 = *((_DWORD *)g_API_Data + 4859);
    if ( v8 )
      ++*(_DWORD *)(v8 + 4 * v1 + 1048);
  }
  v9 = 0;
  v10 = *(_DWORD *)(*((_DWORD *)g_API_Data + 61) + 4);
  if ( v10 > 0 )
  {
    do
    {
      v11 = v9++;
      v12 = (PRO_Vertex *)PRO_Generic_List::GetEntryByIndex(*((PRO_Generic_List **)g_API_Data + 61), v11);
      PRO_Vertex::ResetChangeAttributes(v12);
    }
    while ( v10 > v9 );
  }
  *(_DWORD *)(*((_DWORD *)g_API_Data + 61) + 4) = 0;
  ++*((_DWORD *)g_API_Data + 329);
  ++*((_DWORD *)g_API_Data + 330);
  return v0;
}
Ian
 
Posts: 2044
Joined: Tue Feb 23, 2016 9:23 am

Re: Frame Timing

Postby Bart » Tue Jun 28, 2016 8:16 pm

If those FormatTextures functions end up writing to the texture FIFO, then I guess it draws frames and uploads textures each time it is triggered. But I'm pretty sure that multiple texture uploads can happen per frame. Step 1.x games seem not to upload textures while rendering, so it's quite possible that the scene graph at that point is essentially empty and the flush commands execute very quickly, allowing multiple textures to be sent in the time it would normally take to render a typical in-game scene.

If this is true, then the FMV sequence must simply be out of sync with respect to texture and geometry updates. It looks to me like each time it uploads a texture, it's to a different region of the map, careful so as not to overwrite anything recently uploaded. Can't immediately see why this would be. The FMV sequence relies only on a single IRQ working, so it's not as if two different IRQs are driving it and Supermodel is triggering them at the wrong times relative to each other.

Translating the code by the way is going very slowly. We should probably put together a short to-do list of things to be done before the release. I'd still like to reform the config system and transition to XML for ROM loading. Not sure if we'll be able to fix specular lighting by end of summer. Anything else we should do?

I'm hoping to get this FMV sequence worked out but we'll see...
User avatar
Bart
Site Admin
 
Posts: 3086
Joined: Thu Sep 01, 2011 2:13 pm
Location: Reno, Nevada

Re: Frame Timing

Postby Ian » Wed Jun 29, 2016 1:11 am

From what I recall it uploads to the same texture coordinates, but each frame uploads to an even/odd texture sheet. I guess that's for some sort of tear free double buffering?
Maybe they felt they needed that, not sure

Format textures

Code: Select all
signed int __thiscall PRO_API_Data::FormatUserTextures(int this)
{
  int v1; // esi@1
  signed int v2; // edi@1
  signed int result; // eax@2
  __int32 i; // ebx@5
  __int32 v5; // eax@6
  __int32 v6; // ebp@6
  signed int v7; // eax@6
  int v8; // eax@11
  __int32 v9; // [sp+10h] [bp-4h]@1

  v1 = this;
  v2 = 1;
  v9 = *(_DWORD *)(*(_DWORD *)(this + 236) + 4);
  if ( v9 )
  {
    if ( !*(_DWORD *)(g_API_Data + 224) )
      PRO_InitTextureManager();
    for ( i = 0; i < v9; ++i )
    {
      v5 = PRO_Generic_List::GetEntryByIndex(*(PRO_Generic_List **)(v1 + 236), i);
      v6 = v5;
      v7 = *(_DWORD *)(v5 + 16);
      if ( v7 == 1 )
      {
        if ( *(_BYTE *)(v6 + 64) & 1
          || (PRO_API_Data::AllocateTextureMemory((PRO_API_Data *)v1), *(_BYTE *)(v6 + 64) & 1) )
        {
          v8 = PRO_Texture::FormatImage(v6);
          if ( v8 != 1 )
          {
            v2 = v8;
            *(_DWORD *)(v6 + 16) = v8;
          }
        }
        else
        {
          v2 = 2;
          *(_DWORD *)(v6 + 16) = 2;
        }
      }
      else
      {
        v2 = v7;
      }
    }
    PRO_Generic_List::Reset(*(PRO_Generic_List **)(v1 + 236));
    result = v2;
  }
  else
  {
    result = 1;
  }
  return result;
}


Format image, I think uploads it

Code: Select all
int __thiscall PRO_Texture::FormatImage(int this)
{
  int v1; // esi@1
  int v2; // ecx@1
  __int32 v3; // edi@2
  __int32 v4; // ebx@2
  unsigned __int8 *v5; // eax@6
  int result; // eax@7
  int v7; // ecx@8
  int v8; // ecx@9
  bool v9; // zf@9
  int v10; // eax@13
  unsigned __int8 *v11; // eax@15
  int v12; // ecx@17
  int v13; // ecx@18
  unsigned __int8 *v14; // eax@23
  int v15; // ecx@25
  int v16; // ecx@26
  int v17; // eax@30
  unsigned __int8 *v18; // ecx@31
  unsigned __int8 *v19; // eax@33
  int v20; // ecx@35
  int v21; // ecx@36
  int v22; // edi@45
  unsigned int v23; // ebx@48
  signed int v24; // ecx@48
  int i; // eax@48
  void *v26; // eax@52
  int v27; // ecx@52
  void *v28; // edi@70
  __int32 v29; // [sp-10h] [bp-94h]@46
  char Dest; // [sp+0h] [bp-84h]@41
  char v31; // [sp+50h] [bp-34h]@41
  void *v32; // [sp+70h] [bp-14h]@52
  int v33; // [sp+74h] [bp-10h]@63
  unsigned int v34; // [sp+78h] [bp-Ch]@1
  _UNKNOWN *v35; // [sp+7Ch] [bp-8h]@1
  int v36; // [sp+80h] [bp-4h]@1

  v36 = -1;
  v35 = &_L4284;
  v34 = __readfsdword((signed __int32)&_except_list);
  __writefsdword((signed __int32)&_except_list, (unsigned int)&v34);
  v1 = this;
  v2 = *(_DWORD *)(this + 64);
  if ( v2 & 4 )
  {
    v3 = *(_DWORD *)(v1 + 92);
    v4 = *(_DWORD *)(v1 + 88);
  }
  else
  {
    v3 = *(_DWORD *)(v1 + 88);
    v4 = *(_DWORD *)(v1 + 92);
  }
  switch ( *(_DWORD *)(v1 + 68) )
  {
    case 0:
    case 1:
    case 0x2B:
    case 0x2C:
    case 0x2D:
      if ( v2 & 0x40000 )
        goto LABEL_13;
      v5 = PRO_Texture::ResizeBand(
             (PRO_Texture *)v1,
             *(unsigned __int8 **)(v1 + 44),
             *(_DWORD *)(v1 + 72),
             *(_DWORD *)(v1 + 76),
             v3,
             v4);
      if ( !v5 )
      {
        result = 7;
        goto LABEL_74;
      }
      v7 = *(_DWORD *)(v1 + 64);
      if ( v7 & 0x400000 )
      {
        v8 = v7 | 0x40000;
        *(_DWORD *)(v1 + 64) = v8;
        *(_DWORD *)(v1 + 44) = v5;
        *(_DWORD *)(v1 + 220) = 0;
        v9 = *(_DWORD *)(v1 + 68) == 0;
        *(_DWORD *)(v1 + 64) = v8 ^ (v8 ^ ((unsigned int)v8 >> 4)) & 0x800000;
        if ( !v9 )
        {
          *(_DWORD *)(v1 + 68) = 43;
          result = *(_DWORD *)(v1 + 16);
          goto LABEL_74;
        }
      }
      else if ( *(_DWORD *)(v1 + 68) )
      {
        result = *(_DWORD *)(v1 + 16);
        goto LABEL_74;
      }
LABEL_13:
      v10 = *(_DWORD *)(v1 + 64);
      if ( BYTE1(v10) & 0x20 )
      {
        if ( !(v10 & 0x80000) )
        {
          v11 = PRO_Texture::ResizeBand(
                  (PRO_Texture *)v1,
                  *(unsigned __int8 **)(v1 + 48),
                  *(_DWORD *)(v1 + 72),
                  *(_DWORD *)(v1 + 76),
                  v3,
                  v4);
          if ( !v11 )
          {
            result = 7;
            goto LABEL_74;
          }
          v12 = *(_DWORD *)(v1 + 64);
          if ( v12 & 0x400000 )
          {
            v13 = v12 | 0x80000;
            *(_DWORD *)(v1 + 64) = v13;
            *(_DWORD *)(v1 + 48) = v11;
            *(_DWORD *)(v1 + 220) = 0;
            v9 = *(_DWORD *)(v1 + 68) == 0;
            *(_DWORD *)(v1 + 64) = v13 ^ (v13 ^ ((unsigned int)v13 >> 3)) & 0x1000000;
            if ( !v9 )
            {
              *(_DWORD *)(v1 + 68) = 43;
              result = *(_DWORD *)(v1 + 16);
              goto LABEL_74;
            }
          }
          else if ( *(_DWORD *)(v1 + 68) )
          {
            result = *(_DWORD *)(v1 + 16);
            goto LABEL_74;
          }
        }
        if ( !(*(_BYTE *)(v1 + 66) & 0x10) )
        {
          v14 = PRO_Texture::ResizeBand(
                  (PRO_Texture *)v1,
                  *(unsigned __int8 **)(v1 + 52),
                  *(_DWORD *)(v1 + 72),
                  *(_DWORD *)(v1 + 76),
                  v3,
                  v4);
          if ( !v14 )
          {
            result = 7;
            goto LABEL_74;
          }
          v15 = *(_DWORD *)(v1 + 64);
          if ( v15 & 0x400000 )
          {
            v16 = v15 | 0x100000;
            *(_DWORD *)(v1 + 64) = v16;
            *(_DWORD *)(v1 + 52) = v14;
            *(_DWORD *)(v1 + 220) = 0;
            v9 = *(_DWORD *)(v1 + 68) == 0;
            *(_DWORD *)(v1 + 64) = v16 ^ (v16 ^ ((unsigned int)v16 >> 2)) & 0x2000000;
            if ( !v9 )
            {
              *(_DWORD *)(v1 + 68) = 43;
              result = *(_DWORD *)(v1 + 16);
              goto LABEL_74;
            }
          }
          else if ( *(_DWORD *)(v1 + 68) )
          {
            result = *(_DWORD *)(v1 + 16);
            goto LABEL_74;
          }
        }
      }
      else
      {
        v17 = *(_DWORD *)(v1 + 44);
        *(_DWORD *)(v1 + 48) = v17;
        *(_DWORD *)(v1 + 52) = v17;
      }
      v18 = *(unsigned __int8 **)(v1 + 56);
      if ( !v18 || *(_BYTE *)(v1 + 66) & 0x20 )
        goto LABEL_40;
      v19 = PRO_Texture::ResizeBand((PRO_Texture *)v1, v18, *(_DWORD *)(v1 + 72), *(_DWORD *)(v1 + 76), v3, v4);
      if ( !v19 )
      {
        result = 7;
        goto LABEL_74;
      }
      v20 = *(_DWORD *)(v1 + 64);
      if ( v20 & 0x400000 )
      {
        v21 = v20 | 0x200000;
        *(_DWORD *)(v1 + 64) = v21;
        *(_DWORD *)(v1 + 56) = v19;
        *(_DWORD *)(v1 + 220) = 0;
        v9 = *(_DWORD *)(v1 + 68) == 0;
        *(_DWORD *)(v1 + 64) = v21 ^ (v21 ^ ((unsigned int)v21 >> 1)) & 0x4000000;
        if ( !v9 )
        {
          *(_DWORD *)(v1 + 68) = 1;
          result = *(_DWORD *)(v1 + 16);
          goto LABEL_74;
        }
      }
      else if ( *(_DWORD *)(v1 + 68) )
      {
        result = *(_DWORD *)(v1 + 16);
        goto LABEL_74;
      }
LABEL_40:
      if ( *(_BYTE *)(g_API_Data + 2016) & 2 )
      {
        PRO_GetNameFromPath(*(char **)(v1 + 208), &v31);
        sprintf(&Dest, `string', &v31);
        result = PRO_Texture::writeDmapFile(v1, &Dest);
        *(_DWORD *)(v1 + 64) |= 0x200u;
      }
      else if ( *(_BYTE *)(v1 + 65) & 0x40 && *(_DWORD *)(v1 + 200) == -1 )
      {
        PRO_Texture::freeChannels((PRO_Texture *)v1);
        result = 2;
      }
      else
      {
        v22 = v1 + 96;
        if ( *(_DWORD *)(v1 + 88) >= *(_DWORD *)(v1 + 92) )
          v29 = *(_DWORD *)(v1 + 92);
        else
          v29 = *(_DWORD *)(v1 + 88);
        v23 = 8;
        *(_DWORD *)v22 = PRO_Texture::intlog2((PRO_Texture *)v1, v29);
        v24 = 2 * *(_DWORD *)(v1 + 92) * *(_DWORD *)(v1 + 88);
        for ( i = *(_DWORD *)v22; i; --i )
        {
          v23 += v24;
          v24 >>= 2;
        }
        if ( v23 & 7 )
          v23 = (v23 & 0xFFFFFFF8) + 8;
        v26 = operator new(0x24u);
        v32 = v26;
        v27 = 0;
        v36 = 0;
        if ( v26 )
          v27 = PRO_Polygon_Data_Block::PRO_Polygon_Data_Block(v26, v23);
        v36 = -1;
        *(_DWORD *)(v1 + 224) = v27;
        if ( v27 )
        {
          result = *(_DWORD *)(v27 + 16);
          if ( result == 1 )
          {
            v9 = *(_DWORD *)(v1 + 68) == 0;
            *(_DWORD *)(v1 + 132) = **(_DWORD **)(v27 + 12);
            if ( v9 )
              goto $L3672;
            *(_DWORD *)(v1 + 68) = 46;
            result = *(_DWORD *)(v1 + 16);
          }
        }
        else
        {
          result = 7;
        }
      }
LABEL_74:
      __writefsdword((signed __int32)&_except_list, v34);
      return result;
    case 0x2E:
    case 0x2F:
    case 0x33:
    case 0x34:
$L3672:
      result = PRO_Texture::PackImage(v1);
      if ( result == 1 && !*(_DWORD *)(v1 + 68) )
        goto $L3675;
      goto LABEL_74;
    case 0x30:
$L3675:
      PRO_Texture::freeChannels((PRO_Texture *)v1);
      if ( !*(_DWORD *)(v1 + 68) )
      {
        PRO_Flush();
        goto $L4297;
      }
      *(_DWORD *)(v1 + 68) = 49;
      result = *(_DWORD *)(v1 + 16);
      goto LABEL_74;
    case 0x31:
$L4297:
      v33 = **(_DWORD **)(*(_DWORD *)(v1 + 224) + 12) >> 2;
      PRO_IO(&v33, 1, -1879048192, -1, 1);
      if ( !*(_DWORD *)(v1 + 68) )
      {
        PRO_Flush();
        goto $L3682;
      }
      *(_DWORD *)(v1 + 68) = 50;
      result = *(_DWORD *)(v1 + 16);
      goto LABEL_74;
    case 0x32:
$L3682:
      if ( *(_BYTE *)(v1 + 65) & 0x40 )
      {
        *(_DWORD *)(v1 + 184) |= 0x100000u;
        set_byte_swap(0);
        PRO_IO(v1 + 180, 2, **(_DWORD **)(*(_DWORD *)(v1 + 224) + 12) | 0x98000000, -1, 1);
        if ( !*(_DWORD *)(v1 + 68) )
          PRO_Flush();
        PRO_IO(&v33, 1, -1879048192, -1, 1);
        if ( !*(_DWORD *)(v1 + 68) )
          PRO_Flush();
      }
      *(_BYTE *)(*(_DWORD *)(v1 + 224) + 4) |= 2u;
      v28 = *(void **)(v1 + 224);
      if ( v28 )
      {
        PRO_Polygon_Data_Block::~PRO_Polygon_Data_Block(*(PRO_Polygon_Data_Block **)(v1 + 224));
        operator delete(v28);
        *(_DWORD *)(v1 + 224) = 0;
      }
      *(_DWORD *)(v1 + 64) |= 0x200u;
      goto $L3624;
    default:
$L3624:
      result = 1;
      goto LABEL_74;
  }
}


Notice it also does

PRO_IO(v1 + 180, 2, **(_DWORD **)(*(_DWORD *)(v1 + 224) + 12) | 0x98000000, -1, 1);
if ( !*(_DWORD *)(v1 + 68) )
PRO_Flush();

As for todo list, sure there is a bunch of other stuff to look at, clipping planes, specular, some unimplmented fog/light stuff we aren't handling. But I think we should just release now :) Also I don't have too much time to work on supermodel currently
Ian
 
Posts: 2044
Joined: Tue Feb 23, 2016 9:23 am

PreviousNext

Return to The Dark Room

Who is online

Users browsing this forum: No registered users and 1 guest

cron