243 lines
9.6 KiB
C#
243 lines
9.6 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Text;
|
|
|
|
namespace LibDgf.Ps2.Vif
|
|
{
|
|
public class VifEmulator
|
|
{
|
|
public delegate void VuMpgActivate(ushort? address, bool waitGif);
|
|
|
|
VuVector[] memory;
|
|
VifRegisters registers;
|
|
bool maskPath3;
|
|
|
|
public VifEmulator()
|
|
{
|
|
memory = new VuVector[1024];
|
|
for (int i = 0; i < memory.Length; ++i)
|
|
{
|
|
memory[i] = new VuVector();
|
|
}
|
|
registers = new VifRegisters();
|
|
}
|
|
|
|
public VuVector[] VuMemory => memory;
|
|
public VifRegisters Registers => registers;
|
|
|
|
public void Process(BinaryReader br, int dataLength, VuMpgActivate onVuMpgActivate)
|
|
{
|
|
var startPos = br.BaseStream.Position;
|
|
var endPos = startPos + dataLength;
|
|
while (br.BaseStream.Position < endPos)
|
|
{
|
|
var vifcode = new VifCode { Value = br.ReadUInt32() };
|
|
registers.Code = vifcode;
|
|
//if ((vifcode.CmdWithoutInterrupt & VifCodeCmd.Unpack) != VifCodeCmd.Unpack)
|
|
//{
|
|
// Console.WriteLine($"VIFcode: {vifcode.CmdWithoutInterrupt} NUM={vifcode.Num} IMMEDIATE={vifcode.Immediate:x4}");
|
|
//}
|
|
switch (vifcode.CmdWithoutInterrupt)
|
|
{
|
|
case VifCodeCmd.Nop:
|
|
break;
|
|
case VifCodeCmd.StCycl:
|
|
registers.Cycle = vifcode.Immediate;
|
|
break;
|
|
case VifCodeCmd.Offset:
|
|
registers.Ofst = (uint)(vifcode.Immediate & 0x3ff);
|
|
registers.Stat_Dbf = false;
|
|
registers.TopS = registers.Base;
|
|
break;
|
|
case VifCodeCmd.Base:
|
|
registers.Base = (uint)(vifcode.Immediate & 0x3ff);
|
|
break;
|
|
case VifCodeCmd.Itop:
|
|
registers.ITopS = (uint)(vifcode.Immediate & 0x3ff);
|
|
break;
|
|
case VifCodeCmd.StMod:
|
|
registers.Mode = (uint)(vifcode.Immediate & 3);
|
|
break;
|
|
case VifCodeCmd.MskPath3:
|
|
maskPath3 = (vifcode.Immediate & 0x8000) != 0;
|
|
break;
|
|
case VifCodeCmd.Mark:
|
|
registers.Mark = vifcode.Immediate;
|
|
break;
|
|
case VifCodeCmd.FlushE:
|
|
case VifCodeCmd.Flush:
|
|
case VifCodeCmd.FlushA:
|
|
// Microprogram is run synchronously in emulation
|
|
break;
|
|
case VifCodeCmd.MsCal:
|
|
registers.DoubleBufferSwap();
|
|
onVuMpgActivate?.Invoke(vifcode.Immediate, false);
|
|
break;
|
|
case VifCodeCmd.MsCnt:
|
|
registers.DoubleBufferSwap();
|
|
onVuMpgActivate?.Invoke(null, false);
|
|
break;
|
|
case VifCodeCmd.MsCalF:
|
|
registers.DoubleBufferSwap();
|
|
onVuMpgActivate?.Invoke(vifcode.Immediate, true);
|
|
break;
|
|
case VifCodeCmd.StMask:
|
|
registers.Mask = br.ReadUInt32();
|
|
break;
|
|
case VifCodeCmd.StRow:
|
|
registers.R[0] = br.ReadUInt32();
|
|
registers.R[1] = br.ReadUInt32();
|
|
registers.R[2] = br.ReadUInt32();
|
|
registers.R[3] = br.ReadUInt32();
|
|
break;
|
|
case VifCodeCmd.StCol:
|
|
registers.C[0] = br.ReadUInt32();
|
|
registers.C[1] = br.ReadUInt32();
|
|
registers.C[2] = br.ReadUInt32();
|
|
registers.C[3] = br.ReadUInt32();
|
|
break;
|
|
case VifCodeCmd.Mpg:
|
|
{
|
|
if (!CheckAlignment(br, startPos, 8))
|
|
throw new InvalidDataException("MPG data is not aligned.");
|
|
//Console.WriteLine($"MPG load at 0x{vifcode.Immediate * 8:x4}");
|
|
// Skip MPG since we don't have a VU to execute it on
|
|
int skipLength = vifcode.Num;
|
|
if (skipLength == 0) skipLength = 256;
|
|
skipLength *= 8;
|
|
br.BaseStream.Seek(skipLength, SeekOrigin.Current);
|
|
break;
|
|
}
|
|
case VifCodeCmd.Direct:
|
|
case VifCodeCmd.DirectHl:
|
|
{
|
|
if (!CheckAlignment(br, startPos, 16))
|
|
throw new InvalidDataException("Direct data is not aligned.");
|
|
//Console.WriteLine($"Direct transfer");
|
|
// TODO: handle GIFtag
|
|
int skipLength = vifcode.Immediate;
|
|
if (skipLength == 0) skipLength = 65536;
|
|
skipLength *= 16;
|
|
br.BaseStream.Seek(skipLength, SeekOrigin.Current);
|
|
break;
|
|
}
|
|
default:
|
|
if ((vifcode.Cmd & VifCodeCmd.Unpack) == VifCodeCmd.Unpack)
|
|
{
|
|
ProcessVifCodeUnpack(br);
|
|
AlignReader(br, startPos, 4);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
throw new InvalidDataException("Invalid VIFcode command");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ProcessVifCodeUnpack(BinaryReader br)
|
|
{
|
|
var vifcode = (VifCodeUnpack)registers.Code;
|
|
registers.Num = vifcode.Num;
|
|
//Console.WriteLine($"VIFcode: {VifCodeCmd.Unpack} vn={vifcode.Vn} vl={vifcode.Vl} NUM={vifcode.Num} ADDR={vifcode.Address:x4} FLG={vifcode.Flag} USN={vifcode.Unsigned} m={vifcode.Mask}");
|
|
int addr = (int)((vifcode.Flag ? registers.TopS : 0) + vifcode.Address);
|
|
int cycle = 0;
|
|
bool isV4_5 = vifcode.Vn == VifUnpackVnType.V4 && vifcode.Vl == VifUnpackVlType.L_5;
|
|
while (registers.Num > 0)
|
|
{
|
|
VuVector result = default;
|
|
bool doSkip = false;
|
|
bool doMode;
|
|
bool doMask;
|
|
if (registers.CycleCl >= registers.CycleWl)
|
|
{
|
|
doMode = true;
|
|
doMask = false;
|
|
// Skipping write
|
|
if (cycle < registers.CycleWl)
|
|
{
|
|
// Write when under write limit
|
|
result = br.ReadOneVifCodeUnpack(vifcode);
|
|
}
|
|
|
|
if (cycle == registers.CycleWl - 1)
|
|
{
|
|
doSkip = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Filling write
|
|
throw new NotImplementedException("Filling write not implemented");
|
|
}
|
|
|
|
// Write result
|
|
result = ApplyMaskAndMode(result, doMode && !isV4_5, doMask);
|
|
memory[addr++] = result;
|
|
--registers.Num;
|
|
|
|
// TODO: figure out the proper behavior for filling write
|
|
if (doSkip)
|
|
{
|
|
addr += registers.CycleCl - registers.CycleWl;
|
|
cycle = 0;
|
|
}
|
|
else
|
|
{
|
|
++cycle;
|
|
}
|
|
}
|
|
}
|
|
|
|
VuVector ApplyMaskAndMode(VuVector vector, bool doMode, bool doMask)
|
|
{
|
|
if (!doMode && !doMask) return vector;
|
|
|
|
uint x = vector.X.Packed;
|
|
uint y = vector.Y.Packed;
|
|
uint z = vector.Z.Packed;
|
|
uint w = vector.W.Packed;
|
|
|
|
if (doMask)
|
|
{
|
|
throw new NotImplementedException("Masking not implemented");
|
|
}
|
|
|
|
if (doMode)
|
|
{
|
|
switch (registers.ModeMod)
|
|
{
|
|
case VifMode.None:
|
|
break;
|
|
default:
|
|
throw new NotImplementedException("Addition decompression write not implemented");
|
|
}
|
|
}
|
|
|
|
return new VuVector
|
|
{
|
|
X = new VuFloat { Packed = x },
|
|
Y = new VuFloat { Packed = y },
|
|
Z = new VuFloat { Packed = z },
|
|
W = new VuFloat { Packed = w },
|
|
};
|
|
}
|
|
|
|
// Alignment in bytes
|
|
static void AlignReader(BinaryReader br, long startPos, int alignment)
|
|
{
|
|
var read = br.BaseStream.Position - startPos;
|
|
var aligned = (read + alignment - 1) / alignment * alignment;
|
|
br.BaseStream.Seek(aligned - read, SeekOrigin.Current);
|
|
}
|
|
|
|
static bool CheckAlignment(BinaryReader br, long startPos, int alignment)
|
|
{
|
|
var read = br.BaseStream.Position - startPos;
|
|
var aligned = (read + alignment - 1) / alignment * alignment;
|
|
return read == aligned;
|
|
}
|
|
}
|
|
}
|