Add mesh reading and conversion support

This commit is contained in:
Yukai Li 2021-03-17 03:18:15 -06:00
parent 65dc96c4d6
commit aa1277a4e1
25 changed files with 2212 additions and 9 deletions

View file

@ -0,0 +1,207 @@
using LibDgf.Txm;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace LibDgf.Ps2.Gs
{
public static class GsMemoryUtils
{
public const int BYTES_PER_COLUMN = 64;
public const int COLUMNS_PER_BLOCK = 4;
public const int BYTES_PER_BLOCK = BYTES_PER_COLUMN * COLUMNS_PER_BLOCK;
public const int BLOCKS_PER_PAGE = 32;
public const int BYTES_PER_PAGE = BYTES_PER_BLOCK * BLOCKS_PER_PAGE;
public const int TOTAL_PAGES = 512;
public const int TOTAL_BYTES = BYTES_PER_PAGE * TOTAL_PAGES;
public class ColumnParams
{
public int Width { get; }
public int Height { get; }
public int BitsPerPixel { get; }
public int ColsPerPage { get; }
public int RowsPerPage { get; }
internal ColumnParams(int width, int height, int bitsPerPixel, int colsPerPage, int rowsPerPage)
{
Width = width;
Height = height;
BitsPerPixel = bitsPerPixel;
ColsPerPage = colsPerPage;
RowsPerPage = rowsPerPage;
}
}
static readonly ColumnParams PSMCT32_COLUMN_PARAMS = new ColumnParams(8, 2, 32, 8, 4);
static readonly ColumnParams PSMT8_COLUMN_PARAMS = new ColumnParams(16, 4, 8, 8, 4);
static readonly ColumnParams PSMT4_COLUMN_PARAMS = new ColumnParams(32, 4, 4, 4, 8);
public static ColumnParams GetColumnParams(TxmPixelFormat format)
{
switch (format)
{
case TxmPixelFormat.PSMCT32:
case TxmPixelFormat.PSMCT24:
return PSMCT32_COLUMN_PARAMS;
case TxmPixelFormat.PSMT8:
return PSMT8_COLUMN_PARAMS;
case TxmPixelFormat.PSMT4:
return PSMT4_COLUMN_PARAMS;
default:
throw new NotSupportedException($"{format} not supported.");
}
}
public static byte[] ReadColumn(BinaryReader br, TxmPixelFormat format, int bytesPerLine)
{
ColumnParams colParams = GetColumnParams(format);
byte[] data = new byte[colParams.Width * colParams.Height * colParams.BitsPerPixel / 8];
int bytesPerDestLine = colParams.Width * colParams.BitsPerPixel / 8;
int skipLength = bytesPerLine - bytesPerDestLine;
for (int i = 0; i < colParams.Height; ++i)
{
byte[] lineData = br.ReadBytes(bytesPerDestLine);
br.BaseStream.Seek(skipLength, SeekOrigin.Current);
Buffer.BlockCopy(lineData, 0, data, i * bytesPerDestLine, lineData.Length);
}
return data;
}
public static void WriteColumn(BinaryWriter bw, TxmPixelFormat format, int bytesPerLine, byte[] data)
{
ColumnParams colParams = GetColumnParams(format);
int bytesPerDestLine = colParams.Width * colParams.BitsPerPixel / 8;
int skipLength = bytesPerLine - bytesPerDestLine;
for (int i = 0; i < colParams.Height; ++i)
{
bw.Write(data, i * bytesPerDestLine, bytesPerDestLine);
bw.Seek(skipLength, SeekOrigin.Current);
}
}
// --------------------------------------------------------------------
// Block address to linear offset lookup for PSMCT32
static readonly int[,] PSMCT32_BLOCK_LOOKUP = new[,]
{
{ 0, 1, 4, 5, 16, 17, 20, 21 },
{ 2, 3, 6, 7, 18, 19, 22, 23 },
{ 8, 9, 12, 13, 24, 25, 28, 29 },
{ 10, 11, 14, 15, 26, 27, 30, 31 }
};
static readonly int[,] PSMT8_BLOCK_LOOKUP = new[,]
{
{ 0, 1, 4, 5, 16, 17, 20, 21 },
{ 2, 3, 6, 7, 18, 19, 22, 23 },
{ 8, 9, 12, 13, 24, 25, 28, 29 },
{ 10, 11, 14, 15, 26, 27, 30, 31 }
};
static readonly int[,] PSMT4_BLOCK_LOOKUP = new[,]
{
{ 0, 2, 8, 10 },
{ 1, 3, 9, 11 },
{ 4, 6, 12, 14 },
{ 5, 7, 13, 15 },
{ 16, 18, 24, 26 },
{ 17, 19, 25, 27 },
{ 20, 22, 28, 30 },
{ 21, 23, 29, 31 }
};
static readonly int[] PSMCT32_BLOCK_REVERSE_LOOKUP;
static readonly int[] PSMT8_BLOCK_REVERSE_LOOKUP;
static readonly int[] PSMT4_BLOCK_REVERSE_LOOKUP;
static GsMemoryUtils()
{
PSMCT32_BLOCK_REVERSE_LOOKUP = MakeReverseLookup(PSMCT32_BLOCK_LOOKUP);
PSMT8_BLOCK_REVERSE_LOOKUP = MakeReverseLookup(PSMT8_BLOCK_LOOKUP);
PSMT4_BLOCK_REVERSE_LOOKUP = MakeReverseLookup(PSMT4_BLOCK_LOOKUP);
}
static int[] MakeReverseLookup(int[,] lut)
{
int[] reverse = new int[lut.Length];
int i = 0;
foreach (var n in lut)
{
reverse[n] = i++;
}
return reverse;
}
public static int CalcBlockNumber(TxmPixelFormat format, int blockX, int blockY, int texBufWidth)
{
ColumnParams colParams = GetColumnParams(format);
switch (format)
{
case TxmPixelFormat.PSMCT32:
return CalcBlockNumber(colParams, PSMCT32_BLOCK_LOOKUP,blockX, blockY, texBufWidth);
case TxmPixelFormat.PSMT8:
return CalcBlockNumber(colParams, PSMT8_BLOCK_LOOKUP, blockX, blockY, texBufWidth);
case TxmPixelFormat.PSMT4:
return CalcBlockNumber(colParams, PSMT4_BLOCK_LOOKUP, blockX, blockY, texBufWidth);
default:
throw new NotSupportedException($"{format} not supported");
}
}
public static int CalcBlockMemoryOffset(TxmPixelFormat format, int index)
{
ColumnParams colParams = GetColumnParams(format);
switch (format)
{
case TxmPixelFormat.PSMCT32:
return CalcBlockMemoryOffset(colParams, PSMCT32_BLOCK_REVERSE_LOOKUP, index);
case TxmPixelFormat.PSMT8:
return CalcBlockMemoryOffset(colParams, PSMT8_BLOCK_REVERSE_LOOKUP, index);
case TxmPixelFormat.PSMT4:
return CalcBlockMemoryOffset(colParams, PSMT4_BLOCK_REVERSE_LOOKUP, index);
default:
throw new NotSupportedException($"{format} not supported");
}
}
static int CalcBlockNumber(ColumnParams colParams, int[,] lut, int blockX, int blockY, int texBufWidth)
{
int pageX = blockX / colParams.ColsPerPage;
int pageY = blockY / colParams.RowsPerPage;
int blockXInPage = blockX % colParams.ColsPerPage;
int blockYInPage = blockY % colParams.RowsPerPage;
return (pageY * texBufWidth + pageX) * BLOCKS_PER_PAGE + lut[blockYInPage, blockXInPage];
}
static int CalcBlockMemoryOffset(ColumnParams colParams, int[] lut, int index)
{
int pageIndex = index / BLOCKS_PER_PAGE;
int rem = index % BLOCKS_PER_PAGE;
int pixelsPerBlock = colParams.Width * colParams.Height * COLUMNS_PER_BLOCK;
int memBlockNum = lut[rem];
int fullBlockRows = memBlockNum / colParams.ColsPerPage;
int partialBlocks = memBlockNum % colParams.ColsPerPage;
return (
pageIndex * BLOCKS_PER_PAGE * pixelsPerBlock + // Full pages
fullBlockRows * pixelsPerBlock * colParams.ColsPerPage + // Full row of blocks
partialBlocks * colParams.Width // Partial row of blocks
) * colParams.BitsPerPixel / 8;
}
public static int CalcTxmImageOffset(ColumnParams colParams, int blockIndex, int imageWidth)
{
int blocksPerRow = imageWidth / colParams.Width;
if (blocksPerRow == 0) blocksPerRow = 1;
int fullRows = blockIndex / blocksPerRow;
int rowBlockIndex = blockIndex % blocksPerRow;
return (
fullRows * imageWidth * colParams.Height * 4 +
rowBlockIndex * colParams.Width
) * colParams.BitsPerPixel / 8;
}
}
}

141
LibDgf/Ps2/Gs/PsmtMixer.cs Normal file
View file

@ -0,0 +1,141 @@
using LibDgf.Txm;
using System;
using System.Collections.Generic;
using System.Text;
namespace LibDgf.Ps2.Gs
{
public static class PsmtMixer
{
static int[,] PSMCT32_TO_PSMT4_COL02_LOOKUP = new int[16, 8];
static int[,] PSMCT32_TO_PSMT4_COL13_LOOKUP = new int[16, 8];
static int[,] PSMCT32_TO_PSMT8_COL02_LOOKUP = new int[16, 4];
static int[,] PSMCT32_TO_PSMT8_COL13_LOOKUP = new int[16, 4];
static PsmtMixer()
{
FillLookup(PSMCT32_TO_PSMT4_COL02_LOOKUP, false);
FillOddLookup(PSMCT32_TO_PSMT4_COL02_LOOKUP, PSMCT32_TO_PSMT4_COL13_LOOKUP);
FillLookup(PSMCT32_TO_PSMT8_COL02_LOOKUP, false);
FillOddLookup(PSMCT32_TO_PSMT8_COL02_LOOKUP, PSMCT32_TO_PSMT8_COL13_LOOKUP);
//PrintLookup(PSMCT32_TO_PSMT4_COL02_LOOKUP, nameof(PSMCT32_TO_PSMT4_COL02_LOOKUP));
//PrintLookup(PSMCT32_TO_PSMT4_COL13_LOOKUP, nameof(PSMCT32_TO_PSMT4_COL13_LOOKUP));
//PrintLookup(PSMCT32_TO_PSMT8_COL02_LOOKUP, nameof(PSMCT32_TO_PSMT8_COL02_LOOKUP));
//PrintLookup(PSMCT32_TO_PSMT8_COL13_LOOKUP, nameof(PSMCT32_TO_PSMT8_COL13_LOOKUP));
}
static void PrintLookup(int[,] lookup, string name)
{
Console.WriteLine(name);
int rowNum = lookup.GetLength(0);
int colNum = lookup.GetLength(1);
for (int row = 0; row < rowNum; ++row)
{
for (int col = colNum - 1; col >= 0; --col)
{
Console.Write("{0,3} ", lookup[row, col]);
}
Console.WriteLine();
}
Console.WriteLine();
}
static void FillLookup(int[,] lookup, bool skipNonConsec)
{
int numCols = lookup.GetLength(1);
int num = 0;
// Phase 1: consecutive numbers
// Fill every second column
// Top half then bottom half
for (int half = 0; half < 2; ++half)
{
for (int col = 0; col < numCols; col += skipNonConsec ? 1 : 2)
{
for (int row = 0; row < 8; ++row)
{
lookup[half * 8 + row, col] = num++;
}
}
}
// Phase 2: wrapped numbers
if (!skipNonConsec)
{
for (int half = 0; half < 2; ++half)
{
for (int col = 1; col < numCols; col += 2)
{
for (int row = 4; row < 12; ++row)
{
lookup[half * 8 + (row % 8), col] = num++;
}
}
}
}
}
static void FillOddLookup(int[,] evenLookup, int[,] oddLookup)
{
int numCols = evenLookup.GetLength(1);
for (int half = 0; half < 2; ++half)
{
for (int i = 0; i < 8; ++i)
{
for (int j = 0; j < numCols; ++j)
{
oddLookup[half * 8 + i, j] = evenLookup[half * 8 + ((i + 4) % 8), j];
}
}
}
}
public static byte[] MixColumn(byte[] column, TxmPixelFormat srcFormat, TxmPixelFormat destFormat, bool isOdd)
{
if (srcFormat != TxmPixelFormat.PSMCT32)
throw new NotSupportedException("Only PSMCT32 supported as source format.");
switch (destFormat)
{
case TxmPixelFormat.PSMT4:
return MixColumn32To4(column, isOdd);
case TxmPixelFormat.PSMT8:
return MixColumn32To8(column, isOdd);
default:
throw new NotSupportedException($"{destFormat} not supported as destination format.");
}
}
static byte[] MixColumn32To4(byte[] column, bool isOdd)
{
int[,] lookup = isOdd ? PSMCT32_TO_PSMT4_COL13_LOOKUP : PSMCT32_TO_PSMT4_COL02_LOOKUP;
int numCol = lookup.GetLength(1);
byte[] dest = new byte[column.Length];
byte b = 0;
for (int i = 0; i < column.Length * 2; ++i)
{
if (i % 2 == 0)
b = column[i / 2];
else
b >>= 4;
int index = lookup[i / numCol, i % numCol];
dest[index / 2] |= (byte)((b & 0x0f) << (index % 2 * 4));
}
return dest;
}
static byte[] MixColumn32To8(byte[] column, bool isOdd)
{
int[,] lookup = isOdd ? PSMCT32_TO_PSMT8_COL13_LOOKUP : PSMCT32_TO_PSMT8_COL02_LOOKUP;
int numCol = lookup.GetLength(1);
byte[] dest = new byte[column.Length];
for (int i = 0; i < column.Length; ++i)
{
int index = lookup[i / numCol, i % numCol];
dest[index] = column[i];
}
return dest;
}
}
}

View file

@ -0,0 +1,107 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace LibDgf.Ps2.Gs.Registers
{
public struct Tex0
{
public ulong Packed;
public ushort Tbp0
{
get
{
return (ushort)(Packed & 0x3fff);
}
}
public byte Tbw
{
get
{
return (byte)((Packed >> 14) & 0x3f);
}
}
public byte Psm
{
get
{
return (byte)((Packed >> 20) & 0x3f);
}
}
public byte Tw
{
get
{
return (byte)((Packed >> 26) & 0xf);
}
}
public byte Th
{
get
{
return (byte)((Packed >> 30) & 0xf);
}
}
public byte Tcc
{
get
{
return (byte)((Packed >> 34) & 0x1);
}
}
public byte Tfx
{
get
{
return (byte)((Packed >> 35) & 0x3);
}
}
public ushort Cbp
{
get
{
return (byte)((Packed >> 37) & 0x3ff);
}
}
public byte Cpsm
{
get
{
return (byte)((Packed >> 51) & 0xf);
}
}
public byte Csm
{
get
{
return (byte)((Packed >> 55) & 0x1);
}
}
public byte Csa
{
get
{
return (byte)((Packed >> 56) & 0x1f);
}
}
public byte Cld
{
get
{
return (byte)((Packed >> 61) & 0x7);
}
}
}
}

View file

@ -0,0 +1,332 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace LibDgf.Ps2.Vif
{
public static class BinaryReaderVifExtensions
{
public static VuFloat ReadPs2Float(this BinaryReader br)
{
return new VuFloat { Packed = br.ReadUInt32() };
}
public static VuVector[] ReadBoundingBox(this BinaryReader br)
{
var box = new VuVector[8];
for (int i = 0; i < box.Length; ++i)
{
box[i] = br.ReadV4_32();
}
return box;
}
public static VuVector ReadOneVifCodeUnpack(this BinaryReader br, VifCodeUnpack u)
{
switch (u.Vn)
{
case VifUnpackVnType.S:
switch (u.Vl)
{
case VifUnpackVlType.L_32:
return br.ReadS_32();
case VifUnpackVlType.L_16:
return u.Unsigned ? br.ReadS_16U() : br.ReadS_16S();
case VifUnpackVlType.L_8:
return u.Unsigned ? br.ReadS_8U() : br.ReadS_8S();
default:
break;
}
break;
case VifUnpackVnType.V2:
switch (u.Vl)
{
case VifUnpackVlType.L_32:
return br.ReadV2_32();
case VifUnpackVlType.L_16:
return u.Unsigned ? br.ReadV2_16U() : br.ReadV2_16S();
case VifUnpackVlType.L_8:
return u.Unsigned ? br.ReadV2_8U() : br.ReadV2_8S();
default:
break;
}
break;
case VifUnpackVnType.V3:
switch (u.Vl)
{
case VifUnpackVlType.L_32:
return br.ReadV3_32();
case VifUnpackVlType.L_16:
return u.Unsigned ? br.ReadV3_16U() : br.ReadV3_16S();
case VifUnpackVlType.L_8:
return u.Unsigned ? br.ReadV3_8U() : br.ReadV3_8S();
default:
break;
}
break;
case VifUnpackVnType.V4:
switch (u.Vl)
{
case VifUnpackVlType.L_32:
return br.ReadV4_32();
case VifUnpackVlType.L_16:
return u.Unsigned ? br.ReadV4_16U() : br.ReadV4_16S();
case VifUnpackVlType.L_8:
return u.Unsigned ? br.ReadV4_8U() : br.ReadV4_8S();
case VifUnpackVlType.L_5:
return br.ReadV4_5();
default:
break;
}
break;
default:
break;
}
throw new ArgumentException("Invalid vn/vl combination", nameof(u));
}
public static VuVector ReadV4_32(this BinaryReader br)
{
return new VuVector
{
X = new VuFloat { Packed = br.ReadUInt32() },
Y = new VuFloat { Packed = br.ReadUInt32() },
Z = new VuFloat { Packed = br.ReadUInt32() },
W = new VuFloat { Packed = br.ReadUInt32() }
};
}
public static VuVector ReadV4_16U(this BinaryReader br)
{
return new VuVector
{
X = new VuFloat { Packed = br.ReadUInt16() },
Y = new VuFloat { Packed = br.ReadUInt16() },
Z = new VuFloat { Packed = br.ReadUInt16() },
W = new VuFloat { Packed = br.ReadUInt16() },
};
}
public static VuVector ReadV4_16S(this BinaryReader br)
{
return new VuVector
{
X = new VuFloat { Packed = unchecked((uint)br.ReadInt16()) },
Y = new VuFloat { Packed = unchecked((uint)br.ReadInt16()) },
Z = new VuFloat { Packed = unchecked((uint)br.ReadInt16()) },
W = new VuFloat { Packed = unchecked((uint)br.ReadInt16()) },
};
}
public static VuVector ReadV4_8U(this BinaryReader br)
{
return new VuVector
{
X = new VuFloat { Packed = br.ReadByte() },
Y = new VuFloat { Packed = br.ReadByte() },
Z = new VuFloat { Packed = br.ReadByte() },
W = new VuFloat { Packed = br.ReadByte() },
};
}
public static VuVector ReadV4_8S(this BinaryReader br)
{
return new VuVector
{
X = new VuFloat { Packed = unchecked((uint)br.ReadSByte()) },
Y = new VuFloat { Packed = unchecked((uint)br.ReadSByte()) },
Z = new VuFloat { Packed = unchecked((uint)br.ReadSByte()) },
W = new VuFloat { Packed = unchecked((uint)br.ReadSByte()) },
};
}
public static VuVector ReadV3_32(this BinaryReader br)
{
return new VuVector
{
X = new VuFloat { Packed = br.ReadUInt32() },
Y = new VuFloat { Packed = br.ReadUInt32() },
Z = new VuFloat { Packed = br.ReadUInt32() },
};
}
public static VuVector ReadV3_16U(this BinaryReader br)
{
return new VuVector
{
X = new VuFloat { Packed = br.ReadUInt16() },
Y = new VuFloat { Packed = br.ReadUInt16() },
Z = new VuFloat { Packed = br.ReadUInt16() },
};
}
public static VuVector ReadV3_16S(this BinaryReader br)
{
return new VuVector
{
X = new VuFloat { Packed = unchecked((uint)br.ReadInt16()) },
Y = new VuFloat { Packed = unchecked((uint)br.ReadInt16()) },
Z = new VuFloat { Packed = unchecked((uint)br.ReadInt16()) },
};
}
public static VuVector ReadV3_8U(this BinaryReader br)
{
return new VuVector
{
X = new VuFloat { Packed = br.ReadByte() },
Y = new VuFloat { Packed = br.ReadByte() },
Z = new VuFloat { Packed = br.ReadByte() },
};
}
public static VuVector ReadV3_8S(this BinaryReader br)
{
return new VuVector
{
X = new VuFloat { Packed = unchecked((uint)br.ReadSByte()) },
Y = new VuFloat { Packed = unchecked((uint)br.ReadSByte()) },
Z = new VuFloat { Packed = unchecked((uint)br.ReadSByte()) },
};
}
public static VuVector ReadV2_32(this BinaryReader br)
{
var x = new VuFloat { Packed = br.ReadUInt32() };
var y = new VuFloat { Packed = br.ReadUInt32() };
return new VuVector
{
X = x,
Y = y,
Z = x,
W = y
};
}
public static VuVector ReadV2_16U(this BinaryReader br)
{
var x = new VuFloat { Packed = br.ReadUInt16() };
var y = new VuFloat { Packed = br.ReadUInt16() };
return new VuVector
{
X = x,
Y = y,
Z = x,
W = y
};
}
public static VuVector ReadV2_16S(this BinaryReader br)
{
var x = new VuFloat { Packed = unchecked((uint)br.ReadInt16()) };
var y = new VuFloat { Packed = unchecked((uint)br.ReadInt16()) };
return new VuVector
{
X = x,
Y = y,
Z = x,
W = y
};
}
public static VuVector ReadV2_8U(this BinaryReader br)
{
var x = new VuFloat { Packed = (uint)br.ReadByte() };
var y = new VuFloat { Packed = (uint)br.ReadByte() };
return new VuVector
{
X = x,
Y = y,
Z = x,
W = y
};
}
public static VuVector ReadV2_8S(this BinaryReader br)
{
var x = new VuFloat { Packed = unchecked((uint)br.ReadSByte()) };
var y = new VuFloat { Packed = unchecked((uint)br.ReadSByte()) };
return new VuVector
{
X = x,
Y = y,
Z = x,
W = y
};
}
public static VuVector ReadS_32(this BinaryReader br)
{
var s = new VuFloat { Packed = br.ReadUInt32() };
return new VuVector
{
X = s,
Y = s,
Z = s,
W = s
};
}
public static VuVector ReadS_16U(this BinaryReader br)
{
var s = new VuFloat { Packed = (uint)br.ReadUInt16() };
return new VuVector
{
X = s,
Y = s,
Z = s,
W = s
};
}
public static VuVector ReadS_16S(this BinaryReader br)
{
var s = new VuFloat { Packed = unchecked((uint)br.ReadInt16()) };
return new VuVector
{
X = s,
Y = s,
Z = s,
W = s
};
}
public static VuVector ReadS_8U(this BinaryReader br)
{
var s = new VuFloat { Packed = (uint)br.ReadByte() };
return new VuVector
{
X = s,
Y = s,
Z = s,
W = s
};
}
public static VuVector ReadS_8S(this BinaryReader br)
{
var s = new VuFloat { Packed = unchecked((uint)br.ReadSByte()) };
return new VuVector
{
X = s,
Y = s,
Z = s,
W = s
};
}
public static VuVector ReadV4_5(this BinaryReader br)
{
uint rgba = br.ReadUInt16();
return new VuVector
{
X = new VuFloat { Packed = (rgba << 3) & 0xf8 },
Y = new VuFloat { Packed = (rgba >> 2) & 0xf8 },
Z = new VuFloat { Packed = (rgba >> 7) & 0xf8 },
W = new VuFloat { Packed = (rgba >> 8) & 0x80 },
};
}
}
}

53
LibDgf/Ps2/Vif/VifCode.cs Normal file
View file

@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace LibDgf.Ps2.Vif
{
public struct VifCode
{
public uint Value;
public bool Interrupt
{
get
{
return (Cmd & VifCodeCmd.Interrupt) != 0;
}
}
public VifCodeCmd Cmd
{
get
{
return (VifCodeCmd)(byte)(Value >> 24);
}
}
public VifCodeCmd CmdWithoutInterrupt
{
get
{
return Cmd & ~VifCodeCmd.Interrupt;
}
}
public byte Num
{
get
{
return (byte)(Value >> 16);
}
}
public ushort Immediate
{
get
{
return (ushort)Value;
}
}
public bool IsUnpack => (Cmd & VifCodeCmd.Unpack) == VifCodeCmd.Unpack;
}
}

View file

@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace LibDgf.Ps2.Vif
{
public enum VifCodeCmd : byte
{
Nop = 0b000_0000,
StCycl = 0b000_0001,
Offset = 0b000_0010,
Base = 0b000_0011,
Itop = 0b000_0100,
StMod = 0b000_0101,
MskPath3 = 0b000_0110,
Mark = 0b000_0111,
FlushE = 0b001_0000,
Flush = 0b001_0001,
FlushA = 0b001_0011,
MsCal = 0b001_0100,
MsCnt = 0b001_0111,
MsCalF = 0b001_0101,
StMask = 0b010_0000,
StRow = 0b011_0000,
StCol = 0b011_0001,
Mpg = 0b100_1010,
Direct = 0b101_0000,
DirectHl = 0b101_0001,
Unpack = 0b11_00000,
Interrupt = 0b1000_0000
}
}

View file

@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace LibDgf.Ps2.Vif
{
public struct VifCodeUnpack
{
private uint Value;
public bool Interrupt
{
get
{
return (Value & 0x80000000) != 0;
}
}
public bool Mask
{
get
{
return (Value & 0x10000000) != 0;
}
}
public VifUnpackVnType Vn
{
get
{
return (VifUnpackVnType)((Value >> 26) & 0x3);
}
}
public VifUnpackVlType Vl
{
get
{
return (VifUnpackVlType)((Value >> 24) & 0x3);
}
}
public byte Num
{
get
{
return (byte)(Value >> 16);
}
}
public bool Flag
{
get
{
return (Value & 0x8000) != 0;
}
}
public bool Unsigned
{
get
{
return (Value & 0x4000) != 0;
}
}
public ushort Address
{
get
{
return (ushort)(Value & 0x3ff);
}
}
public static implicit operator VifCode(VifCodeUnpack v)
{
return new VifCode { Value = v.Value | ((uint)VifCodeCmd.Unpack << 24) };
}
public static explicit operator VifCodeUnpack(VifCode v)
{
if ((v.Cmd & VifCodeCmd.Unpack) != VifCodeCmd.Unpack)
throw new ArgumentException("Not an UNPACK code", nameof(v));
return new VifCodeUnpack { Value = v.Value };
}
}
}

View file

@ -0,0 +1,243 @@
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;
}
}
}

13
LibDgf/Ps2/Vif/VifMode.cs Normal file
View file

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace LibDgf.Ps2.Vif
{
public enum VifMode : byte
{
None,
Offset,
Difference
}
}

View file

@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace LibDgf.Ps2.Vif
{
public class VifRegisters
{
public uint[] R { get; } = new uint[4];
public uint[] C { get; } = new uint[4];
public uint Cycle { get; set; }
public uint Mask { get; set; }
public uint Mode { get; set; }
public uint ITop { get; set; }
public uint ITopS { get; set; }
public uint Base { get; set; }
public uint Ofst { get; set; }
public uint Top { get; set; }
public uint TopS { get; set; }
public uint Mark { get; set; }
public uint Num { get; set; }
public VifCode Code { get; set; }
// Just this flag because the other ones are not that interesting
public bool Stat_Dbf { get; set; }
public byte CycleCl
{
get
{
return (byte)Cycle;
}
set
{
Cycle = (Cycle & 0xffffff00) | value;
}
}
public byte CycleWl
{
get
{
return (byte)(Cycle >> 8);
}
set
{
Cycle = (Cycle & 0xffff00ff) | ((uint)value << 8);
}
}
public VifMode ModeMod
{
get
{
return (VifMode)(Mode & 3);
}
set
{
Mode = (uint)value & 3;
}
}
public void DoubleBufferSwap()
{
ITop = ITopS;
Top = TopS;
TopS = Base + (Stat_Dbf ? Ofst : 0);
Stat_Dbf = !Stat_Dbf;
}
}
}

View file

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace LibDgf.Ps2.Vif
{
public enum VifUnpackVlType
{
L_32,
L_16,
L_8,
L_5
}
}

View file

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace LibDgf.Ps2.Vif
{
public enum VifUnpackVnType
{
S,
V2,
V3,
V4
}
}

33
LibDgf/Ps2/Vif/VuFloat.cs Normal file
View file

@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace LibDgf.Ps2.Vif
{
public struct VuFloat
{
public uint Packed;
public static implicit operator double(VuFloat f)
{
ulong sign = (f.Packed >> 31) & 1;
ulong exponent = (f.Packed >> 23) & 0xff;
ulong mantissa = f.Packed & 0x7fffff;
ulong doubleValue;
if (exponent == 0)
{
doubleValue = sign << 63;
}
else
{
doubleValue = (sign << 63) | ((exponent + 1023 - 127) << 52) | (mantissa << 29);
}
return BitConverter.ToDouble(BitConverter.GetBytes(doubleValue), 0);
}
public override string ToString()
{
return ((double)this).ToString();
}
}
}

View file

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace LibDgf.Ps2.Vif
{
public struct VuVector
{
public VuFloat X;
public VuFloat Y;
public VuFloat Z;
public VuFloat W;
public override string ToString()
{
return $"<{X}, {Y}, {Z}, {W}>";
}
}
}