Cores/Nds/GPU3D.cs
namespace Emulotl.Nds;
public sealed partial class GPU3D
{
private readonly NDS NDS;
private struct CmdFIFOEntry
{
public byte Command;
public uint Param;
}
private static readonly byte[] CmdNumParams =
[
0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 1, 1, 0, 16, 12, 16, 12, 9, 3, 3,
0, 0, 0,
1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0,
1, 1, 1, 1, 32,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
3, 2, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
private readonly FIFO<CmdFIFOEntry> CmdFIFO = new( 256 );
private readonly FIFO<CmdFIFOEntry> CmdPIPE = new( 4 );
private readonly FIFO<CmdFIFOEntry> CmdStallQueue = new( 64 );
public uint GXStat;
public uint DispCnt;
public uint ZeroDotWLimit;
private readonly int[] ExecParams = new int[32];
private uint ExecParamCount;
private long CycleCount;
public long Timestamp;
private int VertexPipeline, NormalPipeline, PolygonPipeline;
private int VertexSlotCounter;
private uint VertexSlotsFree;
private uint NumPushPopCommands;
private uint NumTestCommands;
private uint NumCommands, CurCommand, ParamCount, TotalParams;
private uint MatrixMode;
private uint ProjMatrixStackPointer;
private uint PosMatrixStackPointer;
private uint TexMatrixStackPointer;
public uint FlushRequest;
private uint FlushAttributes;
public uint NumVertices, NumPolygons;
private bool GeometryEnabled;
private bool RenderingEnabled;
private readonly int[] PosTestResult = new int[4];
private readonly short[] VecTestResult = new short[3];
public GPU3D( NDS nds )
{
NDS = nds;
for ( int i = 0; i < PolygonRAM.Length; i++ )
PolygonRAM[i] = new Polygon();
}
private void ResetRenderingState()
{
RenderNumPolygons = 0;
RenderDispCnt = 0;
RenderAlphaRef = 0;
Array.Clear( RenderEdgeTable );
Array.Clear( RenderToonTable );
RenderFogColor = 0;
RenderFogOffset = 0;
RenderFogShift = 0;
Array.Clear( RenderFogDensityTable );
RenderClearAttr1 = 0x3F000000;
RenderClearAttr2 = 0x00007FFF;
}
public void Reset()
{
CmdFIFO.Clear();
CmdPIPE.Clear();
CmdStallQueue.Clear();
GXStat = 0;
DispCnt = 0;
ZeroDotWLimit = 0xFFFFFF;
Array.Clear( ExecParams );
ExecParamCount = 0;
CycleCount = 0;
Timestamp = 0;
VertexPipeline = NormalPipeline = PolygonPipeline = 0;
VertexSlotCounter = 0;
VertexSlotsFree = 1;
NumPushPopCommands = 0;
NumTestCommands = 0;
MatrixMode = 0;
MatrixLoadIdentity( ProjMatrix );
MatrixLoadIdentity( PosMatrix );
MatrixLoadIdentity( VecMatrix );
MatrixLoadIdentity( TexMatrix );
ClipMatrixDirty = true;
UpdateClipMatrix();
Array.Clear( Viewport );
Array.Clear( ProjMatrixStack );
Array.Clear( PosMatrixStack );
Array.Clear( VecMatrixStack );
Array.Clear( TexMatrixStack );
ProjMatrixStackPointer = 0;
PosMatrixStackPointer = 0;
TexMatrixStackPointer = 0;
NumCommands = CurCommand = ParamCount = TotalParams = 0;
GeometryEnabled = false;
RenderingEnabled = false;
DispCnt = 0;
AlphaRefVal = 0;
AlphaRef = 0;
Array.Clear( ToonTable );
Array.Clear( EdgeTable );
FogOffset = 0;
FogColor = 0;
Array.Clear( FogDensityTable );
ClearAttr1 = 0x3F000000;
ClearAttr2 = 0x00007FFF;
ResetRenderingState();
AbortFrame = false;
PolygonMode = 0;
Array.Clear( CurVertex );
Array.Clear( VertexColor );
Array.Clear( TexCoords );
Array.Clear( RawTexCoords );
Array.Clear( Normal );
Array.Clear( LightDirection );
Array.Clear( LightColor );
Array.Clear( MatDiffuse );
Array.Clear( MatAmbient );
Array.Clear( MatSpecular );
Array.Clear( MatEmission );
UseShininessTable = false;
Array.Clear( ShininessTable );
PolygonAttr = 0;
CurPolygonAttr = 0;
TexParam = 0;
TexPalette = 0;
Array.Clear( PosTestResult );
Array.Clear( VecTestResult );
Array.Clear( TempVertexBuffer );
VertexNum = 0;
VertexNumInPoly = 0;
NumConsecutivePolygons = 0;
LastStripPolygon = -1;
NumOpaquePolygons = 0;
CurVertexBase = 0;
CurPolygonBase = 0;
NumVertices = 0;
NumPolygons = 0;
CurRAMBank = 0;
FlushRequest = 0;
FlushAttributes = 0;
RenderXPos = 0;
}
public void SetEnabled( bool geometry, bool rendering )
{
GeometryEnabled = geometry;
RenderingEnabled = rendering;
if ( !rendering ) ResetRenderingState();
}
public void SetRenderXPos( ushort xpos, ushort mask )
{
if ( !RenderingEnabled ) return;
RenderXPos = (ushort)((RenderXPos & ~mask) | (xpos & mask & 0x01FF));
}
private void AddCycles( int num )
{
CycleCount += num;
if ( VertexPipeline > 0 )
{
if ( VertexPipeline > num ) VertexPipeline -= num;
else VertexPipeline = 0;
}
if ( PolygonPipeline > 0 )
{
if ( PolygonPipeline > num )
{
PolygonPipeline -= num;
VertexSlotCounter += num;
while ( VertexSlotCounter > 9 )
{
VertexSlotCounter -= 9;
VertexSlotsFree >>= 1;
}
}
else
{
PolygonPipeline = 0;
VertexSlotCounter = 0;
VertexSlotsFree = 0x1;
}
}
}
private void CmdFIFOWrite( CmdFIFOEntry entry )
{
if ( CmdFIFO.IsEmpty() && !CmdPIPE.IsFull() )
{
CmdPIPE.Write( entry );
}
else
{
if ( CmdFIFO.IsFull() )
{
CmdStallQueue.Write( entry );
NDS.GXFIFOStall();
return;
}
CmdFIFO.Write( entry );
}
GXStat |= 1u << 27;
if ( entry.Command == 0x11 || entry.Command == 0x12 )
{
GXStat |= 1u << 14;
NumPushPopCommands++;
}
else if ( entry.Command == 0x70 || entry.Command == 0x71 || entry.Command == 0x72 )
{
GXStat |= 1u << 0;
NumTestCommands++;
}
}
private CmdFIFOEntry CmdFIFORead()
{
CmdFIFOEntry ret = CmdPIPE.Read();
if ( CmdPIPE.Level() <= 2 )
{
if ( !CmdFIFO.IsEmpty() )
CmdPIPE.Write( CmdFIFO.Read() );
if ( !CmdFIFO.IsEmpty() )
CmdPIPE.Write( CmdFIFO.Read() );
if ( !CmdStallQueue.IsEmpty() )
{
while ( !CmdStallQueue.IsEmpty() )
{
if ( CmdFIFO.IsFull() ) break;
CmdFIFOEntry entry = CmdStallQueue.Read();
CmdFIFOWrite( entry );
}
if ( CmdStallQueue.IsEmpty() )
NDS.GXFIFOUnstall();
}
CheckFIFODMA();
CheckFIFOIRQ();
}
return ret;
}
public long CyclesToRunFor()
{
if ( CycleCount < 0 ) return 0;
return CycleCount;
}
private void FinishWork( long cycles )
{
AddCycles( (int)cycles );
CycleCount = 0;
if ( VertexPipeline != 0 || NormalPipeline != 0 || PolygonPipeline != 0 )
return;
GXStat &= ~(1u << 27);
}
public void Run()
{
if ( !GeometryEnabled || FlushRequest != 0 ||
(CmdPIPE.IsEmpty() && (GXStat & (1 << 27)) == 0) )
{
Timestamp = NDS.ARM9Timestamp >> 1;
return;
}
long cycles = (NDS.ARM9Timestamp >> 1) - Timestamp;
CycleCount -= cycles;
Timestamp = NDS.ARM9Timestamp >> 1;
if ( CycleCount <= 0 )
{
while ( CycleCount <= 0 && !CmdPIPE.IsEmpty() )
{
if ( NumPushPopCommands == 0 ) GXStat &= ~(1u << 14);
if ( NumTestCommands == 0 ) GXStat &= ~(1u << 0);
ExecuteCommand();
}
}
if ( CycleCount <= 0 && CmdPIPE.IsEmpty() )
{
if ( (GXStat & (1 << 27)) != 0 ) FinishWork( -CycleCount );
else CycleCount = 0;
if ( NumPushPopCommands == 0 ) GXStat &= ~(1u << 14);
if ( NumTestCommands == 0 ) GXStat &= ~(1u << 0);
}
}
public void CheckFIFOIRQ()
{
bool irq = false;
switch ( GXStat >> 30 )
{
case 1: irq = CmdFIFO.Level() < 128; break;
case 2: irq = CmdFIFO.IsEmpty(); break;
}
if ( irq ) NDS.SetIRQ( 0, IRQ.GXFIFO );
else NDS.ClearIRQ( 0, IRQ.GXFIFO );
}
public void CheckFIFODMA()
{
if ( CmdFIFO.Level() < 128 )
NDS.CheckDMAs( 0, 0x07 );
}
public void VBlank()
{
if ( GeometryEnabled )
{
if ( RenderingEnabled )
{
if ( FlushRequest != 0 )
{
if ( NumPolygons != 0 )
{
uint io = 0, it = NumOpaquePolygons;
for ( uint i = 0; i < NumPolygons; i++ )
{
Polygon poly = PolygonRAM[CurPolygonBase + (int)i];
if ( poly.Translucent )
RenderPolygonRAM[it++] = poly;
else
RenderPolygonRAM[io++] = poly;
}
uint sortcount = (FlushAttributes & 0x1) != 0 ? NumOpaquePolygons : NumPolygons;
StableSortPolygons( (int)sortcount );
}
RenderNumPolygons = NumPolygons;
RenderFrameIdentical = false;
}
else
{
RenderFrameIdentical = RenderDispCnt == DispCnt
&& RenderAlphaRef == AlphaRef
&& RenderClearAttr1 == ClearAttr1
&& RenderClearAttr2 == ClearAttr2
&& RenderFogColor == FogColor
&& RenderFogOffset == FogOffset * 0x200
&& TableEqual( RenderEdgeTable, EdgeTable, 8 )
&& FogTableEqual()
&& TableEqual( RenderToonTable, ToonTable, 32 );
}
RenderDispCnt = DispCnt;
RenderAlphaRef = AlphaRef;
Array.Copy( EdgeTable, RenderEdgeTable, 8 );
Array.Copy( ToonTable, RenderToonTable, 32 );
RenderFogColor = FogColor;
RenderFogOffset = FogOffset * 0x200;
RenderFogShift = (RenderDispCnt >> 8) & 0xF;
RenderFogDensityTable[0] = FogDensityTable[0];
Array.Copy( FogDensityTable, 0, RenderFogDensityTable, 1, 32 );
RenderFogDensityTable[33] = FogDensityTable[31];
RenderClearAttr1 = ClearAttr1;
RenderClearAttr2 = ClearAttr2;
}
if ( FlushRequest != 0 )
{
CurRAMBank = CurRAMBank != 0 ? 0u : 1u;
CurVertexBase = CurRAMBank != 0 ? 6144 : 0;
CurPolygonBase = CurRAMBank != 0 ? 2048 : 0;
NumVertices = 0;
NumPolygons = 0;
NumOpaquePolygons = 0;
FlushRequest = 0;
}
}
}
private static bool TableEqual( ushort[] a, ushort[] b, int count )
{
for ( int i = 0; i < count; i++ )
if ( a[i] != b[i] ) return false;
return true;
}
private bool FogTableEqual()
{
for ( int i = 0; i < 32; i++ )
if ( RenderFogDensityTable[i + 1] != FogDensityTable[i] ) return false;
return true;
}
private void StableSortPolygons( int count )
{
for ( int i = 1; i < count; i++ )
{
Polygon key = RenderPolygonRAM[i];
int j = i - 1;
while ( j >= 0 && RenderPolygonRAM[j].SortKey > key.SortKey )
{
RenderPolygonRAM[j + 1] = RenderPolygonRAM[j];
j--;
}
RenderPolygonRAM[j + 1] = key;
}
}
private void WriteToGXFIFO( uint val )
{
if ( NumCommands == 0 )
{
NumCommands = 4;
CurCommand = val;
ParamCount = 0;
TotalParams = CmdNumParams[CurCommand & 0xFF];
if ( TotalParams > 0 ) return;
}
else
{
ParamCount++;
}
for (; ; )
{
if ( (CurCommand & 0xFF) != 0 || (NumCommands == 4 && CurCommand == 0) )
{
CmdFIFOEntry entry;
entry.Command = (byte)(CurCommand & 0xFF);
entry.Param = val;
CmdFIFOWrite( entry );
}
if ( ParamCount >= TotalParams )
{
CurCommand >>= 8;
NumCommands--;
if ( NumCommands == 0 ) break;
ParamCount = 0;
TotalParams = CmdNumParams[CurCommand & 0xFF];
}
if ( ParamCount < TotalParams )
break;
}
}
public byte Read8( uint addr )
{
switch ( addr )
{
case 0x04000600:
Run();
return (byte)(GXStat & 0xFF);
case 0x04000601:
Run();
return (byte)(((GXStat >> 8) & 0xFF) |
(PosMatrixStackPointer & 0x1F) |
((ProjMatrixStackPointer & 0x1) << 5));
case 0x04000602:
Run();
return (byte)(CmdFIFO.Level() & 0xFF);
case 0x04000603:
{
Run();
uint fifolevel = CmdFIFO.Level();
return (byte)(((GXStat >> 24) & 0xFF) |
(fifolevel >> 8) |
(fifolevel < 128 ? (1u << 1) : 0) |
(fifolevel == 0 ? (1u << 2) : 0));
}
}
return 0;
}
public ushort Read16( uint addr )
{
switch ( addr )
{
case 0x04000060:
return (ushort)DispCnt;
case 0x04000320:
return 46;
case 0x04000600:
Run();
return (ushort)((GXStat & 0xFFFF) |
((PosMatrixStackPointer & 0x1F) << 8) |
((ProjMatrixStackPointer & 0x1) << 13));
case 0x04000602:
{
Run();
uint fifolevel = CmdFIFO.Level();
return (ushort)((GXStat >> 16) |
fifolevel |
(fifolevel < 128 ? (1u << 9) : 0) |
(fifolevel == 0 ? (1u << 10) : 0));
}
case 0x04000604: return (ushort)NumPolygons;
case 0x04000606: return (ushort)NumVertices;
case 0x04000630: return (ushort)VecTestResult[0];
case 0x04000632: return (ushort)VecTestResult[1];
case 0x04000634: return (ushort)VecTestResult[2];
}
return 0;
}
public uint Read32( uint addr )
{
switch ( addr )
{
case 0x04000060:
return DispCnt;
case 0x04000320:
return 46;
case 0x04000600:
{
Run();
uint fifolevel = CmdFIFO.Level();
return GXStat |
((PosMatrixStackPointer & 0x1F) << 8) |
((ProjMatrixStackPointer & 0x1) << 13) |
(fifolevel << 16) |
(fifolevel < 128 ? (1u << 25) : 0) |
(fifolevel == 0 ? (1u << 26) : 0);
}
case 0x04000604:
return NumPolygons | (NumVertices << 16);
case 0x04000620: return (uint)PosTestResult[0];
case 0x04000624: return (uint)PosTestResult[1];
case 0x04000628: return (uint)PosTestResult[2];
case 0x0400062C: return (uint)PosTestResult[3];
}
if ( addr >= 0x04000640 && addr < 0x04000680 )
{
Run();
UpdateClipMatrix();
return (uint)ClipMatrix[(addr - 0x04000640) >> 2];
}
if ( addr >= 0x04000680 && addr < 0x040006A4 )
{
Run();
int i = (int)((addr - 0x04000680) >> 2);
i += i >= 6 ? 2 : i >= 3 ? 1 : 0;
return (uint)VecMatrix[i];
}
return 0;
}
public void Write8( uint addr, byte val )
{
if ( !RenderingEnabled && addr >= 0x04000320 && addr < 0x04000400 ) return;
if ( !GeometryEnabled && addr >= 0x04000400 && addr < 0x04000700 ) return;
switch ( addr )
{
case 0x04000340:
AlphaRefVal = (byte)(val & 0x1F);
AlphaRef = (DispCnt & (1 << 2)) != 0 ? AlphaRefVal : (byte)0;
return;
case 0x04000601:
if ( (val & 0x80) != 0 )
{
GXStat &= ~0x8000u;
ProjMatrixStackPointer = 0;
TexMatrixStackPointer = 0;
}
return;
case 0x04000603:
GXStat &= 0x3FFFFFFF;
GXStat |= (uint)(val & 0xC0) << 24;
CheckFIFOIRQ();
return;
}
if ( addr >= 0x04000330 && addr < 0x04000340 )
{
int idx = (int)(addr - 0x04000330);
if ( (idx & 1) == 0 ) EdgeTable[idx >> 1] = (ushort)((EdgeTable[idx >> 1] & 0xFF00) | val);
else EdgeTable[idx >> 1] = (ushort)((EdgeTable[idx >> 1] & 0x00FF) | (val << 8));
return;
}
if ( addr >= 0x04000360 && addr < 0x04000380 )
{
FogDensityTable[addr - 0x04000360] = (byte)(val & 0x7F);
return;
}
if ( addr >= 0x04000380 && addr < 0x040003C0 )
{
int idx = (int)(addr - 0x04000380);
if ( (idx & 1) == 0 ) ToonTable[idx >> 1] = (ushort)((ToonTable[idx >> 1] & 0xFF00) | val);
else ToonTable[idx >> 1] = (ushort)((ToonTable[idx >> 1] & 0x00FF) | (val << 8));
return;
}
}
public void Write16( uint addr, ushort val )
{
if ( !RenderingEnabled && addr >= 0x04000320 && addr < 0x04000400 ) return;
if ( !GeometryEnabled && addr >= 0x04000400 && addr < 0x04000700 ) return;
switch ( addr )
{
case 0x04000060:
DispCnt = ((uint)val & 0x4FFF) | (DispCnt & 0x3000);
if ( (val & (1 << 12)) != 0 ) DispCnt &= ~(1u << 12);
if ( (val & (1 << 13)) != 0 ) DispCnt &= ~(1u << 13);
AlphaRef = (DispCnt & (1 << 2)) != 0 ? AlphaRefVal : (byte)0;
return;
case 0x04000340:
AlphaRefVal = (byte)(val & 0x1F);
AlphaRef = (DispCnt & (1 << 2)) != 0 ? AlphaRefVal : (byte)0;
return;
case 0x04000350: ClearAttr1 = (ClearAttr1 & 0xFFFF0000) | val; return;
case 0x04000352: ClearAttr1 = (ClearAttr1 & 0xFFFF) | ((uint)val << 16); return;
case 0x04000354: ClearAttr2 = (ClearAttr2 & 0xFFFF0000) | val; return;
case 0x04000356: ClearAttr2 = (ClearAttr2 & 0xFFFF) | ((uint)val << 16); return;
case 0x04000358: FogColor = (FogColor & 0xFFFF0000) | val; return;
case 0x0400035A: FogColor = (FogColor & 0xFFFF) | ((uint)val << 16); return;
case 0x0400035C: FogOffset = (uint)(val & 0x7FFF); return;
case 0x04000600:
if ( (val & 0x8000) != 0 )
{
GXStat &= ~0x8000u;
ProjMatrixStackPointer = 0;
TexMatrixStackPointer = 0;
}
return;
case 0x04000602:
GXStat &= 0x3FFFFFFF;
GXStat |= (uint)(val & 0xC000) << 16;
CheckFIFOIRQ();
return;
case 0x04000610:
ZeroDotWLimit = (uint)(((val & 0x7FFF) * 0x200) + 0x1FF);
return;
}
if ( addr >= 0x04000330 && addr < 0x04000340 )
{
EdgeTable[(addr - 0x04000330) >> 1] = val;
return;
}
if ( addr >= 0x04000360 && addr < 0x04000380 )
{
uint a = addr - 0x04000360;
FogDensityTable[a] = (byte)(val & 0x7F);
FogDensityTable[a + 1] = (byte)((val >> 8) & 0x7F);
return;
}
if ( addr >= 0x04000380 && addr < 0x040003C0 )
{
ToonTable[(addr - 0x04000380) >> 1] = val;
return;
}
}
public void Write32( uint addr, uint val )
{
if ( !RenderingEnabled && addr >= 0x04000320 && addr < 0x04000400 ) return;
if ( !GeometryEnabled && addr >= 0x04000400 && addr < 0x04000700 ) return;
switch ( addr )
{
case 0x04000060:
DispCnt = (val & 0x4FFF) | (DispCnt & 0x3000);
if ( (val & (1 << 12)) != 0 ) DispCnt &= ~(1u << 12);
if ( (val & (1 << 13)) != 0 ) DispCnt &= ~(1u << 13);
AlphaRef = (DispCnt & (1 << 2)) != 0 ? AlphaRefVal : (byte)0;
return;
case 0x04000340:
AlphaRefVal = (byte)(val & 0x1F);
AlphaRef = (DispCnt & (1 << 2)) != 0 ? AlphaRefVal : (byte)0;
return;
case 0x04000350: ClearAttr1 = val; return;
case 0x04000354: ClearAttr2 = val; return;
case 0x04000358: FogColor = val; return;
case 0x0400035C: FogOffset = val & 0x7FFF; return;
case 0x04000600:
if ( (val & 0x8000) != 0 )
{
GXStat &= ~0x8000u;
ProjMatrixStackPointer = 0;
TexMatrixStackPointer = 0;
}
GXStat &= 0x3FFFFFFF;
GXStat |= val & 0xC0000000;
CheckFIFOIRQ();
return;
case 0x04000610:
ZeroDotWLimit = ((val & 0x7FFF) * 0x200) + 0x1FF;
return;
}
if ( addr >= 0x04000330 && addr < 0x04000340 )
{
uint a = (addr - 0x04000330) >> 1;
EdgeTable[a] = (ushort)(val & 0xFFFF);
EdgeTable[a + 1] = (ushort)(val >> 16);
return;
}
if ( addr >= 0x04000360 && addr < 0x04000380 )
{
uint a = addr - 0x04000360;
FogDensityTable[a] = (byte)(val & 0x7F);
FogDensityTable[a + 1] = (byte)((val >> 8) & 0x7F);
FogDensityTable[a + 2] = (byte)((val >> 16) & 0x7F);
FogDensityTable[a + 3] = (byte)((val >> 24) & 0x7F);
return;
}
if ( addr >= 0x04000380 && addr < 0x040003C0 )
{
uint a = (addr - 0x04000380) >> 1;
ToonTable[a] = (ushort)(val & 0xFFFF);
ToonTable[a + 1] = (ushort)(val >> 16);
return;
}
if ( addr >= 0x04000400 && addr < 0x04000440 )
{
WriteToGXFIFO( val );
return;
}
if ( addr >= 0x04000440 && addr < 0x040005CC )
{
CmdFIFOEntry entry;
entry.Command = (byte)((addr & 0x1FC) >> 2);
entry.Param = val;
CmdFIFOWrite( entry );
return;
}
}
}