diff --git a/ProjectPSX.OpenTK/AudioPlayer.cs b/ProjectPSX.OpenTK/AudioPlayer.cs index 7022da2..e1bd897 100644 --- a/ProjectPSX.OpenTK/AudioPlayer.cs +++ b/ProjectPSX.OpenTK/AudioPlayer.cs @@ -52,7 +52,7 @@ public unsafe void UpdateAudio(byte[] samples) { queueLength++; } - if (AL.GetSourceState(audioSource) != ALSourceState.Playing) + if (GetSourceState(audioSource) != ALSourceState.Playing) AL.SourcePlay(audioSource); } @@ -62,5 +62,11 @@ public unsafe void UpdateAudio(byte[] samples) { ALC.DestroyContext(audioContext); ALC.CloseDevice(audioDevice); } + + public static ALSourceState GetSourceState(int sid) + { + AL.GetSource(sid, ALGetSourcei.SourceState, out var value); + return (ALSourceState)value; + } } } diff --git a/ProjectPSX.OpenTK/Program.cs b/ProjectPSX.OpenTK/Program.cs index a7bfb6e..86a0f96 100644 --- a/ProjectPSX.OpenTK/Program.cs +++ b/ProjectPSX.OpenTK/Program.cs @@ -11,7 +11,6 @@ static class Program { [STAThread] static void Main() { GameWindowSettings settings = new GameWindowSettings(); - settings.RenderFrequency = 60; settings.UpdateFrequency = 60; NativeWindowSettings nativeWindow = new NativeWindowSettings(); nativeWindow.API = ContextAPI.OpenGL; diff --git a/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj b/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj index b163b1c..610a82b 100644 --- a/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj +++ b/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj @@ -2,7 +2,7 @@ <PropertyGroup> <OutputType>Exe</OutputType> - <TargetFramework>net5.0</TargetFramework> + <TargetFramework>net9.0</TargetFramework> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> @@ -15,7 +15,7 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="OpenTK" Version="4.6.7" /> + <PackageReference Include="OpenTK" Version="4.8.1" /> </ItemGroup> <ItemGroup> diff --git a/ProjectPSX.WinForms/.editorconfig b/ProjectPSX.WinForms/.editorconfig index 89f1f31..ce299b4 100644 --- a/ProjectPSX.WinForms/.editorconfig +++ b/ProjectPSX.WinForms/.editorconfig @@ -91,9 +91,9 @@ csharp_style_inlined_variable_declaration = true:suggestion ############################### # New line preferences csharp_new_line_before_open_brace = none -csharp_new_line_before_else = none -csharp_new_line_before_catch = none -csharp_new_line_before_finally = none +csharp_new_line_before_else = false +csharp_new_line_before_catch =false +csharp_new_line_before_finally =false csharp_new_line_before_members_in_object_initializers = none csharp_new_line_before_members_in_anonymous_types = none csharp_new_line_between_query_expression_clauses = none diff --git a/ProjectPSX.WinForms/ProjectPSX.WinForms.csproj b/ProjectPSX.WinForms/ProjectPSX.WinForms.csproj index 5276ddd..c5115d3 100644 --- a/ProjectPSX.WinForms/ProjectPSX.WinForms.csproj +++ b/ProjectPSX.WinForms/ProjectPSX.WinForms.csproj @@ -2,9 +2,10 @@ <PropertyGroup> <OutputType>Exe</OutputType> - <TargetFramework>net5.0-windows</TargetFramework> + <TargetFramework>net9.0-windows7.0</TargetFramework> <UseWindowsForms>true</UseWindowsForms> <DisableWinExeOutputInference>true</DisableWinExeOutputInference> + <AllowUnsafeBlocks>True</AllowUnsafeBlocks> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> @@ -12,7 +13,7 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="NAudio.WinMM" Version="2.0.1" /> + <PackageReference Include="NAudio.WinMM" Version="2.2.1" /> </ItemGroup> <ItemGroup> diff --git a/ProjectPSX.WinForms/UI/Window.cs b/ProjectPSX.WinForms/UI/Window.cs index be20845..73ce2f2 100644 --- a/ProjectPSX.WinForms/UI/Window.cs +++ b/ProjectPSX.WinForms/UI/Window.cs @@ -68,6 +68,8 @@ public Window() { screen.MouseDoubleClick += new MouseEventHandler(toggleDebug); Controls.Add(screen); + // Debug will crash if not: + CheckForIllegalCrossThreadCalls = false; KeyDown += new KeyEventHandler(handleJoyPadDown); KeyUp += new KeyEventHandler(handleJoyPadUp); @@ -169,16 +171,20 @@ public void Render(int[] vram) { } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void blit24bpp(int[] vramBits) { + private unsafe void blit24bpp(int[] vramBits) { int yRangeOffset = (240 - (displayY2 - displayY1)) >> (verticalRes == 480 ? 0 : 1); if (yRangeOffset < 0) yRangeOffset = 0; + var display = new Span<int>(this.display.BitmapData.ToPointer(), 0x80000); + Span<int> scanLine = stackalloc int[horizontalRes]; + for (int y = yRangeOffset; y < verticalRes - yRangeOffset; y++) { int offset = 0; + var startXYPosition = displayVRAMXStart + ((y - yRangeOffset + displayVRAMYStart) * 1024); for (int x = 0; x < horizontalRes; x += 2) { - int p0rgb = vramBits[(offset++ + displayVRAMXStart) + ((y - yRangeOffset + displayVRAMYStart) * 1024)]; - int p1rgb = vramBits[(offset++ + displayVRAMXStart) + ((y - yRangeOffset + displayVRAMYStart) * 1024)]; - int p2rgb = vramBits[(offset++ + displayVRAMXStart) + ((y - yRangeOffset + displayVRAMYStart) * 1024)]; + int p0rgb = vramBits[startXYPosition + offset++]; + int p1rgb = vramBits[startXYPosition + offset++]; + int p2rgb = vramBits[startXYPosition + offset++]; ushort p0bgr555 = GetPixelBGR555(p0rgb); ushort p1bgr555 = GetPixelBGR555(p1rgb); @@ -197,9 +203,10 @@ private void blit24bpp(int[] vramBits) { int p0rgb24bpp = p0R << 16 | p0G << 8 | p0B; int p1rgb24bpp = p1R << 16 | p1G << 8 | p1B; - display.DrawPixel(x, y, p0rgb24bpp); - display.DrawPixel(x + 1, y, p1rgb24bpp); + scanLine[x] = p0rgb24bpp; + scanLine[x + 1] = p1rgb24bpp; } + scanLine.CopyTo(display.Slice(y * 1024)); } } diff --git a/ProjectPSX/Core/BUS.cs b/ProjectPSX/Core/BUS.cs index a851143..d21abe6 100644 --- a/ProjectPSX/Core/BUS.cs +++ b/ProjectPSX/Core/BUS.cs @@ -1,16 +1,12 @@ using ProjectPSX.Devices; +using ProjectPSX.Devices.Expansion; using System; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Threading.Tasks; namespace ProjectPSX { - //TODO: - //Do loadX and WriteX simple functions that return a value based on a generic load and write giant switch pointer return - // WIP: Already got rid of the multiple loadX writeX variants need to adress the giant switches but then how to handle the individual - // components? they expect allways an uint and they transform to variables. (uint)(object) is out probably because it kills perf and - // Unsafe.As is still noticeable so... some rework needed on the interaction between components and bus. + public class BUS { //Memory @@ -33,12 +29,13 @@ public class BUS { private JOYPAD joypad; private MDEC mdec; private SPU spu; + private Exp2 exp2; //temporary hardcoded bios/ex1 - private static string bios = "./SCPH1001.BIN"; + private static string bios = "./SCPH1001.BIN"; //SCPH1001 //openbios private static string ex1 = "./caetlaEXP.BIN"; - public BUS(GPU gpu, CDROM cdrom, SPU spu, JOYPAD joypad, TIMERS timers, MDEC mdec, InterruptController interruptController) { + public BUS(GPU gpu, CDROM cdrom, SPU spu, JOYPAD joypad, TIMERS timers, MDEC mdec, InterruptController interruptController, Exp2 exp2) { dma = new DMA(this); this.gpu = gpu; this.cdrom = cdrom; @@ -47,6 +44,7 @@ public BUS(GPU gpu, CDROM cdrom, SPU spu, JOYPAD joypad, TIMERS timers, MDEC mde this.spu = spu; this.joypad = joypad; this.interruptController = interruptController; + this.exp2 = exp2; } public unsafe uint load32(uint address) { @@ -63,11 +61,13 @@ public unsafe uint load32(uint address) { } else if (addr < 0x1F80_1050) { return joypad.load(addr); } else if (addr < 0x1F80_1060) { + //HardCode SIO_STAT + if (addr == 0x1F80_1054) return 0x0000_0805; Console.WriteLine($"[BUS] Read Unsupported to SIO address: {addr:x8}"); return load<uint>(addr & 0xF, sio); } else if (addr < 0x1F80_1070) { return load<uint>(addr & 0xF, memoryControl2); - } else if (addr < 0x1F801080) { + } else if (addr < 0x1F80_1080) { return interruptController.load(addr); } else if (addr < 0x1F80_1100) { return dma.load(addr); @@ -83,8 +83,10 @@ public unsafe uint load32(uint address) { return mdec.readMDEC0_Data(); } else if (addr == 0x1F80_1824) { return mdec.readMDEC1_Status(); - } else if (addr < 0x1F802000) { + } else if (addr < 0x1F80_2000) { return spu.load(addr); + } else if (addr < 0x1F80_4000) { + return exp2.load(addr); } else if (addr < 0x1FC8_0000) { return load<uint>(addr & 0x7_FFFF, biosPtr); } else if (addr == 0xFFFE0130) { @@ -113,7 +115,7 @@ public unsafe void write32(uint address, uint value) { write(addr & 0xF, value, sio); } else if (addr < 0x1F80_1070) { write(addr & 0xF, value, memoryControl2); - } else if (addr < 0x1F801080) { + } else if (addr < 0x1F80_1080) { interruptController.write(addr, value); } else if (addr < 0x1F80_1100) { dma.write(addr, value); @@ -125,9 +127,11 @@ public unsafe void write32(uint address, uint value) { gpu.write(addr, value); } else if (addr < 0x1F80_1830) { mdec.write(addr, value); - } else if (addr < 0x1F802000) { + } else if (addr < 0x1F80_2000) { spu.write(addr, (ushort)value); - } else if (addr == 0xFFFE0130) { + } else if (addr < 0x1F80_4000) { + exp2.write(addr, value); + } else if (addr == 0xFFFE_0130) { memoryCache = value; } else { Console.WriteLine($"[BUS] Write32 Unsupported: {addr:x8}"); @@ -141,7 +145,7 @@ public unsafe void write16(uint address, ushort value) { write(addr & 0x1F_FFFF, value, ramPtr); } else if (addr < 0x1F80_0000) { write(addr & 0x7_FFFF, value, ex1Ptr); - } else if (addr < 0x1f80_0400) { + } else if (addr < 0x1F80_0400) { write(addr & 0x3FF, value, scrathpadPtr); } else if (addr < 0x1F80_1040) { write(addr & 0x3F, value, memoryControl1); @@ -152,7 +156,7 @@ public unsafe void write16(uint address, ushort value) { write(addr & 0xF, value, sio); } else if (addr < 0x1F80_1070) { write(addr & 0xF, value, memoryControl2); - } else if (addr < 0x1F801080) { + } else if (addr < 0x1F80_1080) { interruptController.write(addr, value); } else if (addr < 0x1F80_1100) { dma.write(addr, value); @@ -164,9 +168,11 @@ public unsafe void write16(uint address, ushort value) { gpu.write(addr, value); } else if (addr < 0x1F80_1830) { mdec.write(addr, value); - } else if (addr < 0x1F802000) { - spu.write(addr, (ushort)value); - } else if (addr == 0xFFFE0130) { + } else if (addr < 0x1F80_2000) { + spu.write(addr, value); + } else if (addr < 0x1F80_4000) { + exp2.write(addr, value); + } else if (addr == 0xFFFE_0130) { memoryCache = value; } else { Console.WriteLine($"[BUS] Write16 Unsupported: {addr:x8}"); @@ -191,7 +197,7 @@ public unsafe void write8(uint address, byte value) { write(addr & 0xF, value, sio); } else if (addr < 0x1F80_1070) { write(addr & 0xF, value, memoryControl2); - } else if (addr < 0x1F801080) { + } else if (addr < 0x1F80_1080) { interruptController.write(addr, value); } else if (addr < 0x1F80_1100) { dma.write(addr, value); @@ -203,20 +209,17 @@ public unsafe void write8(uint address, byte value) { gpu.write(addr, value); } else if (addr < 0x1F80_1830) { mdec.write(addr, value); - } else if (addr < 0x1F802000) { - spu.write(addr, (ushort)value); - } else if (addr == 0xFFFE0130) { + } else if (addr < 0x1F80_2000) { + spu.write(addr, value); + } else if (addr < 0x1F80_4000) { + exp2.write(addr, value); + } else if (addr == 0xFFFE_0130) { memoryCache = value; } else { Console.WriteLine($"[BUS] Write8 Unsupported: {addr:x8}"); } } - private uint maskAddr(uint addr) { - uint i = addr >> 29; - return addr & RegionMask[i]; - } - internal unsafe void loadBios() { try { byte[] rom = File.ReadAllBytes(bios); @@ -278,7 +281,7 @@ public unsafe void loadEXE(String fileName) { write(0x6FF0 + 8, 0x3C1C0000 | R28 >> 16, biosPtr); write(0x6FF0 + 12, 0x379C0000 | R28 & 0xFFFF, biosPtr); - if(R29 != 0) { + if (R29 != 0) { write(0x6FF0 + 16, 0x3C1D0000 | R29 >> 16, biosPtr); write(0x6FF0 + 20, 0x37BD0000 | R29 & 0xFFFF, biosPtr); @@ -410,19 +413,19 @@ public unsafe void DmaOTC(uint baseAddress, int size) { //var dest = new Span<uint>(ramPtr + (destAddress & 0x1F_FFFC), size); //dma.CopyTo(dest); - for(int i = 0; i < size - 1; i++) { + for (int i = 0; i < size - 1; i++) { DmaToRam(baseAddress, baseAddress - 4); baseAddress -= 4; } - + DmaToRam(baseAddress, 0xFF_FFFF); } private static uint[] RegionMask = { - 0xFFFF_FFFF, 0xFFFF_FFFF, 0xFFFF_FFFF, 0xFFFF_FFFF, // KUSEG: 2048MB - 0x7FFF_FFFF, // KSEG0: 512MB - 0x1FFF_FFFF, // KSEG1: 512MB - 0xFFFF_FFFF, 0xFFFF_FFFF, // KSEG2: 1024MB + 0xFFFF_FFFF, 0xFFFF_FFFF, 0xFFFF_FFFF, 0xFFFF_FFFF, // KUSEG: 2048MB + 0x7FFF_FFFF, // KSEG0: 512MB + 0x1FFF_FFFF, // KSEG1: 512MB + 0xFFFF_FFFF, 0xFFFF_FFFF, // KSEG2: 1024MB }; } } diff --git a/ProjectPSX/Core/CPU.cs b/ProjectPSX/Core/CPU.cs index 380c4c3..ff143fd 100644 --- a/ProjectPSX/Core/CPU.cs +++ b/ProjectPSX/Core/CPU.cs @@ -1,5 +1,7 @@ -using System; +//#define CPU_EXCEPTIONS +using System; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using ProjectPSX.Disassembler; namespace ProjectPSX { @@ -50,10 +52,10 @@ public struct Instr { public uint opcode => value >> 26; //Instr opcode //I-Type - public uint rs => (value >> 21) & 0x1F;//Register Source - public uint rt => (value >> 16) & 0x1F;//Register Target - public uint imm => value & 0xFFFF; //Immediate value - public uint imm_s => (uint)(short)imm; //Immediate value sign extended + public uint rs => (value >> 21) & 0x1F; //Register Source + public uint rt => (value >> 16) & 0x1F; //Register Target + public uint imm => (ushort)value; //Immediate value + public uint imm_s => (uint)(short)value; //Immediate value sign extended //R-Type public uint rd => (value >> 11) & 0x1F; @@ -69,7 +71,6 @@ public struct Instr { private Instr instr; //Debug - private long cycle; //current CPU cycle counter for debug public bool debug = false; public CPU(BUS bus) { @@ -79,50 +80,9 @@ public CPU(BUS bus) { gte = new GTE(); COP0_GPR[15] = 0x2; //PRID Processor ID + } - initOpCodeTable(); - } - - private static delegate*<CPU, void>[] opcodeMainTable; - private static delegate*<CPU, void>[] opcodeSpecialTable; - - private void initOpCodeTable() { - static void SPECIAL(CPU cpu) => cpu.SPECIAL(); - static void BCOND(CPU cpu) => cpu.BCOND(); - static void J(CPU cpu) => cpu.J(); - static void JAL(CPU cpu) => cpu.JAL(); - static void BEQ(CPU cpu) => cpu.BEQ(); - static void BNE(CPU cpu) => cpu.BNE(); - static void BLEZ(CPU cpu) => cpu.BLEZ(); - static void BGTZ(CPU cpu) => cpu.BGTZ(); - static void ADDI(CPU cpu) => cpu.ADDI(); - static void ADDIU(CPU cpu) => cpu.ADDIU(); - static void SLTI(CPU cpu) => cpu.SLTI(); - static void SLTIU(CPU cpu) => cpu.SLTIU(); - static void ANDI(CPU cpu) => cpu.ANDI(); - static void ORI(CPU cpu) => cpu.ORI(); - static void XORI(CPU cpu) => cpu.XORI(); - static void LUI(CPU cpu) => cpu.LUI(); - static void COP0(CPU cpu) => cpu.COP0(); - static void NOP(CPU cpu) => cpu.NOP(); - static void COP2(CPU cpu) => cpu.COP2(); - static void NA(CPU cpu) => cpu.NA(); - static void LB(CPU cpu) => cpu.LB(); - static void LH(CPU cpu) => cpu.LH(); - static void LWL(CPU cpu) => cpu.LWL(); - static void LW(CPU cpu) => cpu.LW(); - static void LBU(CPU cpu) => cpu.LBU(); - static void LHU(CPU cpu) => cpu.LHU(); - static void LWR(CPU cpu) => cpu.LWR(); - static void SB(CPU cpu) => cpu.SB(); - static void SH(CPU cpu) => cpu.SH(); - static void SWL(CPU cpu) => cpu.SWL(); - static void SW(CPU cpu) => cpu.SW(); - static void SWR(CPU cpu) => cpu.SWR(); - static void LWC2(CPU cpu) => cpu.LWC2(); - static void SWC2(CPU cpu) => cpu.SWC2(); - - opcodeMainTable = new delegate*<CPU, void>[] { + private static delegate*<CPU, void>[] opcodeMainTable = new delegate*<CPU, void>[] { &SPECIAL, &BCOND, &J, &JAL, &BEQ, &BNE, &BLEZ, &BGTZ, &ADDI, &ADDIU, &SLTI, &SLTIU, &ANDI, &ORI, &XORI, &LUI, &COP0, &NOP, &COP2, &NOP, &NA, &NA, &NA, &NA, @@ -133,36 +93,7 @@ private void initOpCodeTable() { &NOP, &NOP, &SWC2, &NOP, &NA, &NA, &NA, &NA, }; - static void SLL(CPU cpu) => cpu.SLL(); - static void SRL(CPU cpu) => cpu.SRL(); - static void SRA(CPU cpu) => cpu.SRA(); - static void SLLV(CPU cpu) => cpu.SLLV(); - static void SRLV(CPU cpu) => cpu.SRLV(); - static void SRAV(CPU cpu) => cpu.SRAV(); - static void JR(CPU cpu) => cpu.JR(); - static void SYSCALL(CPU cpu) => cpu.SYSCALL(); - static void BREAK(CPU cpu) => cpu.BREAK(); - static void JALR(CPU cpu) => cpu.JALR(); - static void MFHI(CPU cpu) => cpu.MFHI(); - static void MTHI(CPU cpu) => cpu.MTHI(); - static void MFLO(CPU cpu) => cpu.MFLO(); - static void MTLO(CPU cpu) => cpu.MTLO(); - static void MULT(CPU cpu) => cpu.MULT(); - static void MULTU(CPU cpu) => cpu.MULTU(); - static void DIV(CPU cpu) => cpu.DIV(); - static void DIVU(CPU cpu) => cpu.DIVU(); - static void ADD(CPU cpu) => cpu.ADD(); - static void ADDU(CPU cpu) => cpu.ADDU(); - static void SUB(CPU cpu) => cpu.SUB(); - static void SUBU(CPU cpu) => cpu.SUBU(); - static void AND(CPU cpu) => cpu.AND(); - static void OR(CPU cpu) => cpu.OR(); - static void XOR(CPU cpu) => cpu.XOR(); - static void NOR(CPU cpu) => cpu.NOR(); - static void SLT(CPU cpu) => cpu.SLT(); - static void SLTU(CPU cpu) => cpu.SLTU(); - - opcodeSpecialTable = new delegate*<CPU, void>[] { + private static delegate*<CPU, void>[] opcodeSpecialTable = new delegate*<CPU, void>[] { &SLL, &NA, &SRL, &SRA, &SLLV, &NA, &SRLV, &SRAV, &JR, &JALR, &NA, &NA, &SYSCALL, &BREAK, &NA, &NA, &MFHI, &MTHI, &MFLO, &MTLO, &NA, &NA, &NA, &NA, @@ -172,11 +103,10 @@ private void initOpCodeTable() { &NA, &NA, &NA, &NA, &NA, &NA, &NA, &NA, &NA, &NA, &NA, &NA, &NA, &NA, &NA, &NA, }; - } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Run() { - fetchDecode(); + public int Run() { + int ticks = fetchDecode(); if (instr.value != 0) { //Skip Nops opcodeMainTable[instr.opcode](this); //Execute } @@ -190,6 +120,8 @@ public void Run() { //TTY(); //bios.verbose(PC_Now, GPR); + + return ticks; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -219,24 +151,18 @@ public void handleInterrupts() { } bool IEC = (COP0_GPR[SR] & 0x1) == 1; - uint IM = (COP0_GPR[SR] >> 8) & 0xFF; - uint IP = (COP0_GPR[CAUSE] >> 8) & 0xFF; + byte IM = (byte)((COP0_GPR[SR] >> 8) & 0xFF); + byte IP = (byte)((COP0_GPR[CAUSE] >> 8) & 0xFF); if (IEC && (IM & IP) > 0) { - EXCEPTION(EX.INTERRUPT); + EXCEPTION(this, EX.INTERRUPT); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void fetchDecode() { + private int fetchDecode() { //Executable address space is limited to ram and bios on psx uint maskedPC = PC & 0x1FFF_FFFF; - uint load; - if (maskedPC < 0x1F00_0000) { - load = bus.LoadFromRam(maskedPC); - } else { - load = bus.LoadFromBios(maskedPC); - } PC_Now = PC; PC = PC_Predictor; @@ -247,20 +173,28 @@ private void fetchDecode() { opcodeIsBranch = false; opcodeTookBranch = false; +#if CPU_EXCEPTIONS if ((PC_Now & 0x3) != 0) { COP0_GPR[BADA] = PC_Now; - EXCEPTION(EX.LOAD_ADRESS_ERROR); - return; + EXCEPTION(this, EX.LOAD_ADRESS_ERROR); + return 1; } +#endif - instr.value = load; - //cycle++; + if (maskedPC < 0x1F00_0000) { + instr.value = bus.LoadFromRam(maskedPC); + return 1; + } else { + instr.value = bus.LoadFromBios(maskedPC); + return 20; + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void MemAccess() { if (delayedMemoryLoad.register != memoryLoad.register) { //if loadDelay on same reg it is lost/overwritten (amidog tests) - GPR[memoryLoad.register] = memoryLoad.value; + ref uint r0 = ref MemoryMarshal.GetArrayDataReference(GPR); + Unsafe.Add(ref r0, (nint)memoryLoad.register) = memoryLoad.value; } memoryLoad = delayedMemoryLoad; delayedMemoryLoad.register = 0; @@ -268,539 +202,572 @@ private void MemAccess() { [MethodImpl(MethodImplOptions.AggressiveInlining)] private void WriteBack() { - GPR[writeBack.register] = writeBack.value; + ref uint r0 = ref MemoryMarshal.GetArrayDataReference(GPR); + Unsafe.Add(ref r0, (nint)writeBack.register) = writeBack.value; writeBack.register = 0; - GPR[0] = 0; + r0 = 0; } // Non Implemented by the CPU Opcodes - private void NOP() { /*nop*/ } + private static void NOP(CPU cpu) { /*nop*/ } - private void NA() => EXCEPTION(EX.ILLEGAL_INSTR, instr.id); + private static void NA(CPU cpu) => EXCEPTION(cpu, EX.ILLEGAL_INSTR, cpu.instr.id); // Main Table Opcodes - private void SPECIAL() => opcodeSpecialTable[instr.function](this); + private static void SPECIAL(CPU cpu) => opcodeSpecialTable[cpu.instr.function](cpu); - private void BCOND() { - opcodeIsBranch = true; - uint op = instr.rt; + private static void BCOND(CPU cpu) { + cpu.opcodeIsBranch = true; + uint op = cpu.instr.rt; bool should_link = (op & 0x1E) == 0x10; - bool should_branch = (int)(GPR[instr.rs] ^ (op << 31)) < 0; + bool should_branch = (int)(cpu.GPR[cpu.instr.rs] ^ (op << 31)) < 0; - if (should_link) GPR[31] = PC_Predictor; - if (should_branch) BRANCH(); + if (should_link) cpu.GPR[31] = cpu.PC_Predictor; + if (should_branch) BRANCH(cpu); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void J() { - opcodeIsBranch = true; - opcodeTookBranch = true; - PC_Predictor = (PC_Predictor & 0xF000_0000) | (instr.addr << 2); + private static void J(CPU cpu) { + cpu.opcodeIsBranch = true; + cpu.opcodeTookBranch = true; + cpu.PC_Predictor = (cpu.PC_Predictor & 0xF000_0000) | (cpu.instr.addr << 2); } - private void JAL() { - setGPR(31, PC_Predictor); - J(); + private static void JAL(CPU cpu) { + cpu.setGPR(31, cpu.PC_Predictor); + J(cpu); } - private void BEQ() { - opcodeIsBranch = true; - if (GPR[instr.rs] == GPR[instr.rt]) { - BRANCH(); + private static void BEQ(CPU cpu) { + cpu.opcodeIsBranch = true; + if (cpu.GPR[cpu.instr.rs] == cpu.GPR[cpu.instr.rt]) { + BRANCH(cpu); } } - private void BNE() { - opcodeIsBranch = true; - if (GPR[instr.rs] != GPR[instr.rt]) { - BRANCH(); + private static void BNE(CPU cpu) { + cpu.opcodeIsBranch = true; + if (cpu.GPR[cpu.instr.rs] != cpu.GPR[cpu.instr.rt]) { + BRANCH(cpu); } } - private void BLEZ() { - opcodeIsBranch = true; - if (((int)GPR[instr.rs]) <= 0) { - BRANCH(); + private static void BLEZ(CPU cpu) { + cpu.opcodeIsBranch = true; + if (((int)cpu.GPR[cpu.instr.rs]) <= 0) { + BRANCH(cpu); } } - private void BGTZ() { - opcodeIsBranch = true; - if (((int)GPR[instr.rs]) > 0) { - BRANCH(); + private static void BGTZ(CPU cpu) { + cpu.opcodeIsBranch = true; + if (((int)cpu.GPR[cpu.instr.rs]) > 0) { + BRANCH(cpu); } } - private void ADDI() { - uint rs = GPR[instr.rs]; - uint imm_s = instr.imm_s; + private static void ADDI(CPU cpu) { + uint rs = cpu.GPR[cpu.instr.rs]; + uint imm_s = cpu.instr.imm_s; uint result = rs + imm_s; + +#if CPU_EXCEPTIONS if(checkOverflow(rs, imm_s, result)) { - EXCEPTION(EX.OVERFLOW, instr.id); + EXCEPTION(cpu, EX.OVERFLOW, cpu.instr.id); } else { - setGPR(instr.rt, result); + cpu.setGPR(cpu.instr.rt, result); } +#else + cpu.setGPR(cpu.instr.rt, result); +#endif } - private void ADDIU() => setGPR(instr.rt, GPR[instr.rs] + instr.imm_s); + private static void ADDIU(CPU cpu) => cpu.setGPR(cpu.instr.rt, cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s); - private void SLTI() { - bool condition = (int)GPR[instr.rs] < (int)instr.imm_s; - setGPR(instr.rt, Unsafe.As<bool, uint>(ref condition)); + private static void SLTI(CPU cpu) { + bool condition = (int)cpu.GPR[cpu.instr.rs] < (int)cpu.instr.imm_s; + cpu.setGPR(cpu.instr.rt, Unsafe.As<bool, uint>(ref condition)); } - private void SLTIU() { - bool condition = GPR[instr.rs] < instr.imm_s; - setGPR(instr.rt, Unsafe.As<bool, uint>(ref condition)); + private static void SLTIU(CPU cpu) { + bool condition = cpu.GPR[cpu.instr.rs] < cpu.instr.imm_s; + cpu.setGPR(cpu.instr.rt, Unsafe.As<bool, uint>(ref condition)); } - private void ANDI() => setGPR(instr.rt, GPR[instr.rs] & instr.imm); + private static void ANDI(CPU cpu) => cpu.setGPR(cpu.instr.rt, cpu.GPR[cpu.instr.rs] & cpu.instr.imm); - private void ORI() => setGPR(instr.rt, GPR[instr.rs] | instr.imm); + private static void ORI(CPU cpu) => cpu.setGPR(cpu.instr.rt, cpu.GPR[cpu.instr.rs] | cpu.instr.imm); - private void XORI() => setGPR(instr.rt, GPR[instr.rs] ^ instr.imm); + private static void XORI(CPU cpu) => cpu.setGPR(cpu.instr.rt, cpu.GPR[cpu.instr.rs] ^ cpu.instr.imm); - private void LUI() => setGPR(instr.rt, instr.imm << 16); + private static void LUI(CPU cpu) => cpu.setGPR(cpu.instr.rt, cpu.instr.imm << 16); - private void COP0() { - if (instr.rs == 0b0_0000) MFC0(); - else if (instr.rs == 0b0_0100) MTC0(); - else if (instr.rs == 0b1_0000) RFE(); - else EXCEPTION(EX.ILLEGAL_INSTR, instr.id); + private static void COP0(CPU cpu) { + if (cpu.instr.rs == 0b0_0000) MFC0(cpu); + else if (cpu.instr.rs == 0b0_0100) MTC0(cpu); + else if (cpu.instr.rs == 0b1_0000) RFE(cpu); + else EXCEPTION(cpu, EX.ILLEGAL_INSTR, cpu.instr.id); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void MFC0() { - uint mfc = instr.rd; + private static void MFC0(CPU cpu) { + uint mfc = cpu.instr.rd; if (mfc == 3 || mfc >= 5 && mfc <= 9 || mfc >= 11 && mfc <= 15) { - delayedLoad(instr.rt, COP0_GPR[mfc]); + delayedLoad(cpu, cpu.instr.rt, cpu.COP0_GPR[mfc]); } else { - EXCEPTION(EX.ILLEGAL_INSTR, instr.id); + EXCEPTION(cpu, EX.ILLEGAL_INSTR, cpu.instr.id); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void MTC0() { - uint value = GPR[instr.rt]; - uint register = instr.rd; + private static void MTC0(CPU cpu) { + uint value = cpu.GPR[cpu.instr.rt]; + uint register = cpu.instr.rd; if (register == CAUSE) { //only bits 8 and 9 are writable - COP0_GPR[CAUSE] &= ~(uint)0x300; - COP0_GPR[CAUSE] |= value & 0x300; + cpu.COP0_GPR[CAUSE] &= ~(uint)0x300; + cpu.COP0_GPR[CAUSE] |= value & 0x300; } else if (register == SR) { //This can trigger soft interrupts - dontIsolateCache = (value & 0x10000) == 0; - bool prevIEC = (COP0_GPR[SR] & 0x1) == 1; + cpu.dontIsolateCache = (value & 0x10000) == 0; + bool prevIEC = (cpu.COP0_GPR[SR] & 0x1) == 1; bool currentIEC = (value & 0x1) == 1; - COP0_GPR[SR] = value; + cpu.COP0_GPR[SR] = value; uint IM = (value >> 8) & 0x3; - uint IP = (COP0_GPR[CAUSE] >> 8) & 0x3; + uint IP = (cpu.COP0_GPR[CAUSE] >> 8) & 0x3; if (!prevIEC && currentIEC && (IM & IP) > 0) { - PC = PC_Predictor; - EXCEPTION(EX.INTERRUPT, instr.id); + cpu.PC = cpu.PC_Predictor; + EXCEPTION(cpu, EX.INTERRUPT, cpu.instr.id); } } else { - COP0_GPR[register] = value; + cpu.COP0_GPR[register] = value; } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void RFE() { - uint mode = COP0_GPR[SR] & 0x3F; - COP0_GPR[SR] &= ~(uint)0xF; - COP0_GPR[SR] |= mode >> 2; + private static void RFE(CPU cpu) { + uint mode = cpu.COP0_GPR[SR] & 0x3F; + cpu.COP0_GPR[SR] &= ~(uint)0xF; + cpu.COP0_GPR[SR] |= mode >> 2; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void EXCEPTION(EX cause, uint coprocessor = 0) { - uint mode = COP0_GPR[SR] & 0x3F; - COP0_GPR[SR] &= ~(uint)0x3F; - COP0_GPR[SR] |= (mode << 2) & 0x3F; + private static void EXCEPTION(CPU cpu, EX cause, uint coprocessor = 0) { + uint mode = cpu.COP0_GPR[SR] & 0x3F; + cpu.COP0_GPR[SR] &= ~(uint)0x3F; + cpu.COP0_GPR[SR] |= (mode << 2) & 0x3F; - uint OldCause = COP0_GPR[CAUSE] & 0xff00; - COP0_GPR[CAUSE] = (uint)cause << 2; - COP0_GPR[CAUSE] |= OldCause; - COP0_GPR[CAUSE] |= coprocessor << 28; + uint OldCause = cpu.COP0_GPR[CAUSE] & 0xff00; + cpu.COP0_GPR[CAUSE] = (uint)cause << 2; + cpu.COP0_GPR[CAUSE] |= OldCause; + cpu.COP0_GPR[CAUSE] |= coprocessor << 28; if (cause == EX.INTERRUPT) { - COP0_GPR[EPC] = PC; + cpu.COP0_GPR[EPC] = cpu.PC; //hack: related to the delay of the ex interrupt - opcodeIsDelaySlot = opcodeIsBranch; - opcodeInDelaySlotTookBranch = opcodeTookBranch; + cpu.opcodeIsDelaySlot = cpu.opcodeIsBranch; + cpu.opcodeInDelaySlotTookBranch = cpu.opcodeTookBranch; } else { - COP0_GPR[EPC] = PC_Now; + cpu.COP0_GPR[EPC] = cpu.PC_Now; } - if (opcodeIsDelaySlot) { - COP0_GPR[EPC] -= 4; - COP0_GPR[CAUSE] |= (uint)1 << 31; - COP0_GPR[JUMPDEST] = PC; + if (cpu.opcodeIsDelaySlot) { + cpu.COP0_GPR[EPC] -= 4; + cpu.COP0_GPR[CAUSE] |= (uint)1 << 31; + cpu.COP0_GPR[JUMPDEST] = cpu.PC; - if (opcodeInDelaySlotTookBranch) { - COP0_GPR[CAUSE] |= (1 << 30); + if (cpu.opcodeInDelaySlotTookBranch) { + cpu.COP0_GPR[CAUSE] |= (1 << 30); } } - PC = ExceptionAdress[COP0_GPR[SR] & 0x400000 >> 22]; - PC_Predictor = PC + 4; + cpu.PC = ExceptionAdress[cpu.COP0_GPR[SR] & 0x400000 >> 22]; + cpu.PC_Predictor = cpu.PC + 4; } - private void COP2() { - if ((instr.rs & 0x10) == 0) { - switch (instr.rs) { - case 0b0_0000: MFC2(); break; - case 0b0_0010: CFC2(); break; - case 0b0_0100: MTC2(); break; - case 0b0_0110: CTC2(); break; - default: EXCEPTION(EX.ILLEGAL_INSTR, instr.id); break; + private static void COP2(CPU cpu) { + if ((cpu.instr.rs & 0x10) == 0) { + switch (cpu.instr.rs) { + case 0b0_0000: MFC2(cpu); break; + case 0b0_0010: CFC2(cpu); break; + case 0b0_0100: MTC2(cpu); break; + case 0b0_0110: CTC2(cpu); break; + default: EXCEPTION(cpu, EX.ILLEGAL_INSTR, cpu.instr.id); break; } } else { - gte.execute(instr.value); + cpu.gte.execute(cpu.instr.value); } } - private void MFC2() => delayedLoad(instr.rt, gte.loadData(instr.rd)); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void MFC2(CPU cpu) => delayedLoad(cpu, cpu.instr.rt, cpu.gte.loadData(cpu.instr.rd)); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void CFC2() => delayedLoad(instr.rt, gte.loadControl(instr.rd)); + private static void CFC2(CPU cpu) => delayedLoad(cpu, cpu.instr.rt, cpu.gte.loadControl(cpu.instr.rd)); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void MTC2() => gte.writeData(instr.rd, GPR[instr.rt]); + private static void MTC2(CPU cpu) => cpu.gte.writeData(cpu.instr.rd, cpu.GPR[cpu.instr.rt]); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void CTC2() => gte.writeControl(instr.rd, GPR[instr.rt]); + private static void CTC2(CPU cpu) => cpu.gte.writeControl(cpu.instr.rd, cpu.GPR[cpu.instr.rt]); - private void LWC2() { //TODO WARNING THIS SHOULD HAVE DELAY? - uint addr = GPR[instr.rs] + instr.imm_s; + private static void LWC2(CPU cpu) { + uint addr = cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s; +#if CPU_EXCEPTIONS if ((addr & 0x3) == 0) { - uint value = bus.load32(addr); - gte.writeData(instr.rt, value); + uint value = cpu.bus.load32(addr); + cpu.gte.writeData(cpu.instr.rt, value); } else { - COP0_GPR[BADA] = addr; - EXCEPTION(EX.LOAD_ADRESS_ERROR, instr.id); + cpu.COP0_GPR[BADA] = addr; + EXCEPTION(cpu, EX.LOAD_ADRESS_ERROR, cpu.instr.id); } +#else + uint value = cpu.bus.load32(addr); + cpu.gte.writeData(cpu.instr.rt, value); +#endif } - private void SWC2() { //TODO WARNING THIS SHOULD HAVE DELAY? - uint addr = GPR[instr.rs] + instr.imm_s; + private static void SWC2(CPU cpu) { + uint addr = cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s; +#if CPU_EXCEPTIONS if ((addr & 0x3) == 0) { - bus.write32(addr, gte.loadData(instr.rt)); + cpu.bus.write32(addr, cpu.gte.loadData(cpu.instr.rt)); } else { - COP0_GPR[BADA] = addr; - EXCEPTION(EX.LOAD_ADRESS_ERROR, instr.id); + cpu.COP0_GPR[BADA] = addr; + EXCEPTION(cpu, EX.LOAD_ADRESS_ERROR, cpu.instr.id); } +#else + cpu.bus.write32(addr, cpu.gte.loadData(cpu.instr.rt)); +#endif } - private void LB() { //todo redo this as it unnecesary load32 - if (dontIsolateCache) { - uint value = (uint)(sbyte)bus.load32(GPR[instr.rs] + instr.imm_s); - delayedLoad(instr.rt, value); + private static void LB(CPU cpu) { //todo redo this as it unnecesary load32 + if (cpu.dontIsolateCache) { + uint value = (uint)(sbyte)cpu.bus.load32(cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s); + delayedLoad(cpu, cpu.instr.rt, value); } //else Console.WriteLine("IsolatedCache: Ignoring Load"); } - private void LBU() { - if (dontIsolateCache) { - uint value = (byte)bus.load32(GPR[instr.rs] + instr.imm_s); - delayedLoad(instr.rt, value); + private static void LBU(CPU cpu) { + if (cpu.dontIsolateCache) { + uint value = (byte)cpu.bus.load32(cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s); + delayedLoad(cpu, cpu.instr.rt, value); } //else Console.WriteLine("IsolatedCache: Ignoring Load"); } - private void LH() { - if (dontIsolateCache) { - uint addr = GPR[instr.rs] + instr.imm_s; + private static void LH(CPU cpu) { + if (cpu.dontIsolateCache) { + uint addr = cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s; +#if CPU_EXCEPTIONS if ((addr & 0x1) == 0) { - uint value = (uint)(short)bus.load32(addr); - delayedLoad(instr.rt, value); + uint value = (uint)(short)cpu.bus.load32(addr); + delayedLoad(cpu, cpu.instr.rt, value); } else { - COP0_GPR[BADA] = addr; - EXCEPTION(EX.LOAD_ADRESS_ERROR, instr.id); + cpu.COP0_GPR[BADA] = addr; + EXCEPTION(cpu, EX.LOAD_ADRESS_ERROR, cpu.instr.id); } +#else + uint value = (uint)(short)cpu.bus.load32(addr); + delayedLoad(cpu, cpu.instr.rt, value); +#endif } //else Console.WriteLine("IsolatedCache: Ignoring Load"); } - private void LHU() { - if (dontIsolateCache) { - uint addr = GPR[instr.rs] + instr.imm_s; + private static void LHU(CPU cpu) { + if (cpu.dontIsolateCache) { + uint addr = cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s; +#if CPU_EXCEPTIONS if ((addr & 0x1) == 0) { - uint value = (ushort)bus.load32(addr); - delayedLoad(instr.rt, value); + uint value = (ushort)cpu.bus.load32(addr); + delayedLoad(cpu, cpu.instr.rt, value); } else { - COP0_GPR[BADA] = addr; - EXCEPTION(EX.LOAD_ADRESS_ERROR, instr.id); + cpu.COP0_GPR[BADA] = addr; + EXCEPTION(cpu, EX.LOAD_ADRESS_ERROR, cpu.instr.id); } +#else + uint value = (ushort)cpu.bus.load32(addr); + delayedLoad(cpu, cpu.instr.rt, value); +#endif } //else Console.WriteLine("IsolatedCache: Ignoring Load"); } - private void LW() { - if (dontIsolateCache) { - uint addr = GPR[instr.rs] + instr.imm_s; + private static void LW(CPU cpu) { + if (cpu.dontIsolateCache) { + uint addr = cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s; +#if CPU_EXCEPTIONS if ((addr & 0x3) == 0) { - uint value = bus.load32(addr); - delayedLoad(instr.rt, value); + uint value = cpu.bus.load32(addr); + delayedLoad(cpu, cpu.instr.rt, value); } else { - COP0_GPR[BADA] = addr; - EXCEPTION(EX.LOAD_ADRESS_ERROR, instr.id); + cpu.COP0_GPR[BADA] = addr; + EXCEPTION(cpu, EX.LOAD_ADRESS_ERROR, cpu.instr.id); } +#else + uint value = cpu.bus.load32(addr); + delayedLoad(cpu, cpu.instr.rt, value); +#endif } //else Console.WriteLine("IsolatedCache: Ignoring Load"); } - private void LWL() { - uint addr = GPR[instr.rs] + instr.imm_s; + private static void LWL(CPU cpu) { + uint addr = cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s; uint aligned_addr = addr & 0xFFFF_FFFC; - uint aligned_load = bus.load32(aligned_addr); + uint aligned_load = cpu.bus.load32(aligned_addr); - uint value = 0; - uint LRValue = GPR[instr.rt]; + uint LRValue = cpu.GPR[cpu.instr.rt]; - if (instr.rt == memoryLoad.register) { - LRValue = memoryLoad.value; + if (cpu.instr.rt == cpu.memoryLoad.register) { + LRValue = cpu.memoryLoad.value; } - switch (addr & 0b11) { - case 0: value = (LRValue & 0x00FF_FFFF) | (aligned_load << 24); break; - case 1: value = (LRValue & 0x0000_FFFF) | (aligned_load << 16); break; - case 2: value = (LRValue & 0x0000_00FF) | (aligned_load << 8); break; - case 3: value = aligned_load; break; - } + int shift = (int)((addr & 0x3) << 3); + uint mask = (uint)0x00FF_FFFF >> shift; + uint value = (LRValue & mask) | (aligned_load << (24 - shift)); - delayedLoad(instr.rt, value); + delayedLoad(cpu, cpu.instr.rt, value); } - private void LWR() { - uint addr = GPR[instr.rs] + instr.imm_s; + private static void LWR(CPU cpu) { + uint addr = cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s; uint aligned_addr = addr & 0xFFFF_FFFC; - uint aligned_load = bus.load32(aligned_addr); + uint aligned_load = cpu.bus.load32(aligned_addr); - uint value = 0; - uint LRValue = GPR[instr.rt]; + uint LRValue = cpu.GPR[cpu.instr.rt]; - if (instr.rt == memoryLoad.register) { - LRValue = memoryLoad.value; + if (cpu.instr.rt == cpu.memoryLoad.register) { + LRValue = cpu.memoryLoad.value; } - switch (addr & 0b11) { - case 0: value = aligned_load; break; - case 1: value = (LRValue & 0xFF00_0000) | (aligned_load >> 8); break; - case 2: value = (LRValue & 0xFFFF_0000) | (aligned_load >> 16); break; - case 3: value = (LRValue & 0xFFFF_FF00) | (aligned_load >> 24); break; - } + int shift = (int)((addr & 0x3) << 3); + uint mask = 0xFFFF_FF00 << (24 - shift); + uint value = (LRValue & mask) | (aligned_load >> shift); - delayedLoad(instr.rt, value); + delayedLoad(cpu, cpu.instr.rt, value); } - private void SB() { - if (dontIsolateCache) - bus.write8(GPR[instr.rs] + instr.imm_s, (byte)GPR[instr.rt]); + private static void SB(CPU cpu) { + if (cpu.dontIsolateCache) + cpu.bus.write8(cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s, (byte)cpu.GPR[cpu.instr.rt]); //else Console.WriteLine("IsolatedCache: Ignoring Write"); } - private void SH() { - if (dontIsolateCache) { - uint addr = GPR[instr.rs] + instr.imm_s; + private static void SH(CPU cpu) { + if (cpu.dontIsolateCache) { + uint addr = cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s; +#if CPU_EXCEPTIONS if ((addr & 0x1) == 0) { - bus.write16(addr, (ushort)GPR[instr.rt]); + cpu.bus.write16(addr, (ushort)cpu.GPR[cpu.instr.rt]); } else { - COP0_GPR[BADA] = addr; - EXCEPTION(EX.STORE_ADRESS_ERROR, instr.id); + cpu.COP0_GPR[BADA] = addr; + EXCEPTION(cpu, EX.STORE_ADRESS_ERROR, cpu.instr.id); } +#else + cpu.bus.write16(addr, (ushort)cpu.GPR[cpu.instr.rt]); +#endif } //else Console.WriteLine("IsolatedCache: Ignoring Write"); } - private void SW() { - if (dontIsolateCache) { - uint addr = GPR[instr.rs] + instr.imm_s; + private static void SW(CPU cpu) { + if (cpu.dontIsolateCache) { + uint addr = cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s; +#if CPU_EXCEPTIONS if ((addr & 0x3) == 0) { - bus.write32(addr, GPR[instr.rt]); + cpu.bus.write32(addr, cpu.GPR[cpu.instr.rt]); } else { - COP0_GPR[BADA] = addr; - EXCEPTION(EX.STORE_ADRESS_ERROR, instr.id); + cpu.COP0_GPR[BADA] = addr; + EXCEPTION(cpu, EX.STORE_ADRESS_ERROR, cpu.instr.id); } +#else + cpu.bus.write32(addr, cpu.GPR[cpu.instr.rt]); +#endif } //else Console.WriteLine("IsolatedCache: Ignoring Write"); } - private void SWR() { - uint addr = GPR[instr.rs] + instr.imm_s; + private static void SWR(CPU cpu) { + uint addr = cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s; uint aligned_addr = addr & 0xFFFF_FFFC; - uint aligned_load = bus.load32(aligned_addr); - - uint value = 0; - switch (addr & 0b11) { - case 0: value = GPR[instr.rt]; break; - case 1: value = (aligned_load & 0x0000_00FF) | (GPR[instr.rt] << 8); break; - case 2: value = (aligned_load & 0x0000_FFFF) | (GPR[instr.rt] << 16); break; - case 3: value = (aligned_load & 0x00FF_FFFF) | (GPR[instr.rt] << 24); break; - } + uint aligned_load = cpu.bus.load32(aligned_addr); - bus.write32(aligned_addr, value); + int shift = (int)((addr & 0x3) << 3); + uint mask = (uint)0x00FF_FFFF >> (24 - shift); + uint value = (aligned_load & mask) | (cpu.GPR[cpu.instr.rt] << shift); + + cpu.bus.write32(aligned_addr, value); } - private void SWL() { - uint addr = GPR[instr.rs] + instr.imm_s; + private static void SWL(CPU cpu) { + uint addr = cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s; uint aligned_addr = addr & 0xFFFF_FFFC; - uint aligned_load = bus.load32(aligned_addr); - - uint value = 0; - switch (addr & 0b11) { - case 0: value = (aligned_load & 0xFFFF_FF00) | (GPR[instr.rt] >> 24); break; - case 1: value = (aligned_load & 0xFFFF_0000) | (GPR[instr.rt] >> 16); break; - case 2: value = (aligned_load & 0xFF00_0000) | (GPR[instr.rt] >> 8); break; - case 3: value = GPR[instr.rt]; break; - } + uint aligned_load = cpu.bus.load32(aligned_addr); + + int shift = (int)((addr & 0x3) << 3); + uint mask = 0xFFFF_FF00 << shift; + uint value = (aligned_load & mask) | (cpu.GPR[cpu.instr.rt] >> (24 - shift)); - bus.write32(aligned_addr, value); + cpu.bus.write32(aligned_addr, value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void BRANCH() { - opcodeTookBranch = true; - PC_Predictor = PC + (instr.imm_s << 2); + private static void BRANCH(CPU cpu) { + cpu.opcodeTookBranch = true; + cpu.PC_Predictor = cpu.PC + (cpu.instr.imm_s << 2); } // Special Table Opcodes (Nested on Opcode 0x00 with additional function param) - private void SLL() => setGPR(instr.rd, GPR[instr.rt] << (int)instr.sa); + private static void SLL(CPU cpu) => cpu.setGPR(cpu.instr.rd, cpu.GPR[cpu.instr.rt] << (int)cpu.instr.sa); - private void SRL() => setGPR(instr.rd, GPR[instr.rt] >> (int)instr.sa); + private static void SRL(CPU cpu) => cpu.setGPR(cpu.instr.rd, cpu.GPR[cpu.instr.rt] >> (int)cpu.instr.sa); - private void SRA() => setGPR(instr.rd, (uint)((int)GPR[instr.rt] >> (int)instr.sa)); + private static void SRA(CPU cpu) => cpu.setGPR(cpu.instr.rd, (uint)((int)cpu.GPR[cpu.instr.rt] >> (int)cpu.instr.sa)); - private void SLLV() => setGPR(instr.rd, GPR[instr.rt] << (int)(GPR[instr.rs] & 0x1F)); + private static void SLLV(CPU cpu) => cpu.setGPR(cpu.instr.rd, cpu.GPR[cpu.instr.rt] << (int)(cpu.GPR[cpu.instr.rs] & 0x1F)); - private void SRLV() => setGPR(instr.rd, GPR[instr.rt] >> (int)(GPR[instr.rs] & 0x1F)); + private static void SRLV(CPU cpu) => cpu.setGPR(cpu.instr.rd, cpu.GPR[cpu.instr.rt] >> (int)(cpu.GPR[cpu.instr.rs] & 0x1F)); - private void SRAV() => setGPR(instr.rd, (uint)((int)GPR[instr.rt] >> (int)(GPR[instr.rs] & 0x1F))); + private static void SRAV(CPU cpu) => cpu.setGPR(cpu.instr.rd, (uint)((int)cpu.GPR[cpu.instr.rt] >> (int)(cpu.GPR[cpu.instr.rs] & 0x1F))); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void JR() { - opcodeIsBranch = true; - opcodeTookBranch = true; - PC_Predictor = GPR[instr.rs]; + private static void JR(CPU cpu) { + cpu.opcodeIsBranch = true; + cpu.opcodeTookBranch = true; + cpu.PC_Predictor = cpu.GPR[cpu.instr.rs]; } - private void SYSCALL() => EXCEPTION(EX.SYSCALL, instr.id); + private static void SYSCALL(CPU cpu) => EXCEPTION(cpu, EX.SYSCALL, cpu.instr.id); - private void BREAK() => EXCEPTION(EX.BREAK); + private static void BREAK(CPU cpu) => EXCEPTION(cpu, EX.BREAK); - private void JALR() { - setGPR(instr.rd, PC_Predictor); - JR(); + private static void JALR(CPU cpu) { + cpu.setGPR(cpu.instr.rd, cpu.PC_Predictor); + JR(cpu); } - private void MFHI() => setGPR(instr.rd, HI); + private static void MFHI(CPU cpu) => cpu.setGPR(cpu.instr.rd, cpu.HI); - private void MTHI() => HI = GPR[instr.rs]; + private static void MTHI(CPU cpu) => cpu.HI = cpu.GPR[cpu.instr.rs]; - private void MFLO() => setGPR(instr.rd, LO); + private static void MFLO(CPU cpu) => cpu.setGPR(cpu.instr.rd, cpu.LO); - private void MTLO() => LO = GPR[instr.rs]; + private static void MTLO(CPU cpu) => cpu.LO = cpu.GPR[cpu.instr.rs]; - private void MULT() { - long value = (long)(int)GPR[instr.rs] * (long)(int)GPR[instr.rt]; //sign extend to pass amidog cpu test + private static void MULT(CPU cpu) { + long value = (long)(int)cpu.GPR[cpu.instr.rs] * (long)(int)cpu.GPR[cpu.instr.rt]; //sign extend to pass amidog cpu test - HI = (uint)(value >> 32); - LO = (uint)value; + cpu.HI = (uint)(value >> 32); + cpu.LO = (uint)value; } - private void MULTU() { - ulong value = (ulong)GPR[instr.rs] * (ulong)GPR[instr.rt]; //sign extend to pass amidog cpu test + private static void MULTU(CPU cpu) { + ulong value = (ulong)cpu.GPR[cpu.instr.rs] * (ulong)cpu.GPR[cpu.instr.rt]; //sign extend to pass amidog cpu test - HI = (uint)(value >> 32); - LO = (uint)value; + cpu.HI = (uint)(value >> 32); + cpu.LO = (uint)value; } - private void DIV() { - int n = (int)GPR[instr.rs]; - int d = (int)GPR[instr.rt]; + private static void DIV(CPU cpu) { + int n = (int)cpu.GPR[cpu.instr.rs]; + int d = (int)cpu.GPR[cpu.instr.rt]; if (d == 0) { - HI = (uint)n; + cpu.HI = (uint)n; if (n >= 0) { - LO = 0xFFFF_FFFF; + cpu.LO = 0xFFFF_FFFF; } else { - LO = 1; + cpu.LO = 1; } } else if ((uint)n == 0x8000_0000 && d == -1) { - HI = 0; - LO = 0x8000_0000; + cpu.HI = 0; + cpu.LO = 0x8000_0000; } else { - HI = (uint)(n % d); - LO = (uint)(n / d); + cpu.HI = (uint)(n % d); + cpu.LO = (uint)(n / d); } } - private void DIVU() { - uint n = GPR[instr.rs]; - uint d = GPR[instr.rt]; + private static void DIVU(CPU cpu) { + uint n = cpu.GPR[cpu.instr.rs]; + uint d = cpu.GPR[cpu.instr.rt]; if (d == 0) { - HI = n; - LO = 0xFFFF_FFFF; + cpu.HI = n; + cpu.LO = 0xFFFF_FFFF; } else { - HI = n % d; - LO = n / d; + cpu.HI = n % d; + cpu.LO = n / d; } } - private void ADD() { - uint rs = GPR[instr.rs]; - uint rt = GPR[instr.rt]; + private static void ADD(CPU cpu) { + uint rs = cpu.GPR[cpu.instr.rs]; + uint rt = cpu.GPR[cpu.instr.rt]; uint result = rs + rt; + +#if CPU_EXCEPTIONS if (checkOverflow(rs, rt, result)) { - EXCEPTION(EX.OVERFLOW, instr.id); + EXCEPTION(cpu, EX.OVERFLOW, cpu.instr.id); } else { - setGPR(instr.rd, result); + cpu.setGPR(cpu.instr.rd, result); } +#else + cpu.setGPR(cpu.instr.rd, result); +#endif } - private void ADDU() => setGPR(instr.rd, GPR[instr.rs] + GPR[instr.rt]); + private static void ADDU(CPU cpu) => cpu.setGPR(cpu.instr.rd, cpu.GPR[cpu.instr.rs] + cpu.GPR[cpu.instr.rt]); - private void SUB() { - uint rs = GPR[instr.rs]; - uint rt = GPR[instr.rt]; + private static void SUB(CPU cpu) { + uint rs = cpu.GPR[cpu.instr.rs]; + uint rt = cpu.GPR[cpu.instr.rt]; uint result = rs - rt; + +#if CPU_EXCEPTIONS if (checkUnderflow(rs, rt, result)) { - EXCEPTION(EX.OVERFLOW, instr.id); + EXCEPTION(cpu, EX.OVERFLOW, cpu.instr.id); } else { - setGPR(instr.rd, result); + cpu.setGPR(cpu.instr.rd, result); } +#else + cpu.setGPR(cpu.instr.rd, result); +#endif } - private void SUBU() => setGPR(instr.rd, GPR[instr.rs] - GPR[instr.rt]); + private static void SUBU(CPU cpu) => cpu.setGPR(cpu.instr.rd, cpu.GPR[cpu.instr.rs] - cpu.GPR[cpu.instr.rt]); - private void AND() => setGPR(instr.rd, GPR[instr.rs] & GPR[instr.rt]); + private static void AND(CPU cpu) => cpu.setGPR(cpu.instr.rd, cpu.GPR[cpu.instr.rs] & cpu.GPR[cpu.instr.rt]); - private void OR() => setGPR(instr.rd, GPR[instr.rs] | GPR[instr.rt]); + private static void OR(CPU cpu) => cpu.setGPR(cpu.instr.rd, cpu.GPR[cpu.instr.rs] | cpu.GPR[cpu.instr.rt]); - private void XOR() => setGPR(instr.rd, GPR[instr.rs] ^ GPR[instr.rt]); + private static void XOR(CPU cpu) => cpu.setGPR(cpu.instr.rd, cpu.GPR[cpu.instr.rs] ^ cpu.GPR[cpu.instr.rt]); - private void NOR() => setGPR(instr.rd, ~(GPR[instr.rs] | GPR[instr.rt])); + private static void NOR(CPU cpu) => cpu.setGPR(cpu.instr.rd, ~(cpu.GPR[cpu.instr.rs] | cpu.GPR[cpu.instr.rt])); - private void SLT() { - bool condition = (int)GPR[instr.rs] < (int)GPR[instr.rt]; - setGPR(instr.rd, Unsafe.As<bool, uint>(ref condition)); + private static void SLT(CPU cpu) { + bool condition = (int)cpu.GPR[cpu.instr.rs] < (int)cpu.GPR[cpu.instr.rt]; + cpu.setGPR(cpu.instr.rd, Unsafe.As<bool, uint>(ref condition)); } - private void SLTU() { - bool condition = GPR[instr.rs] < GPR[instr.rt]; - setGPR(instr.rd, Unsafe.As<bool, uint>(ref condition)); + private static void SLTU(CPU cpu) { + bool condition = cpu.GPR[cpu.instr.rs] < cpu.GPR[cpu.instr.rt]; + cpu.setGPR(cpu.instr.rd, Unsafe.As<bool, uint>(ref condition)); } @@ -819,9 +786,9 @@ private void setGPR(uint regN, uint value) { } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void delayedLoad(uint regN, uint value) { - delayedMemoryLoad.register = regN; - delayedMemoryLoad.value = value; + private static void delayedLoad(CPU cpu, uint regN, uint value) { + cpu.delayedMemoryLoad.register = regN; + cpu.delayedMemoryLoad.value = value; } private void TTY() { diff --git a/ProjectPSX/Core/ProjectPSX.cs b/ProjectPSX/Core/ProjectPSX.cs index abbe1c2..22e9d1d 100644 --- a/ProjectPSX/Core/ProjectPSX.cs +++ b/ProjectPSX/Core/ProjectPSX.cs @@ -1,5 +1,6 @@ using ProjectPSX.Devices; using ProjectPSX.Devices.CdRom; +using ProjectPSX.Devices.Expansion; using ProjectPSX.Devices.Input; namespace ProjectPSX { @@ -22,6 +23,7 @@ public class ProjectPSX { private MemoryCard memoryCard; private CD cd; private InterruptController interruptController; + private Exp2 exp2; public ProjectPSX(IHostWindow window, string diskFilename) { controller = new DigitalController(); @@ -36,7 +38,8 @@ public ProjectPSX(IHostWindow window, string diskFilename) { joypad = new JOYPAD(controller, memoryCard); timers = new TIMERS(); mdec = new MDEC(); - bus = new BUS(gpu, cdrom, spu, joypad, timers, mdec, interruptController); + exp2 = new Exp2(); + bus = new BUS(gpu, cdrom, spu, joypad, timers, mdec, interruptController, exp2); cpu = new CPU(bus); bus.loadBios(); @@ -47,12 +50,14 @@ public ProjectPSX(IHostWindow window, string diskFilename) { public void RunFrame() { //A lame mainloop with a workaround to be able to underclock. + int sync = 0; for (int i = 0; i < SYNC_LOOPS; i++) { - for (int j = 0; j < SYNC_CYCLES; j++) { - cpu.Run(); + while (sync < SYNC_CYCLES) { + sync += cpu.Run(); //cpu.handleInterrupts(); } - bus.tick(SYNC_CYCLES * MIPS_UNDERCLOCK); + sync -= SYNC_CYCLES; + bus.tick(SYNC_CYCLES * MIPS_UNDERCLOCK + 1); cpu.handleInterrupts(); } } @@ -66,5 +71,9 @@ public void toggleDebug() { gpu.debug = !gpu.debug; } + public void toggleCdRomLid() { + cdrom.toggleLid(); + } + } } diff --git a/ProjectPSX/Devices/CDROM/CD.cs b/ProjectPSX/Devices/CDROM/CD.cs index b131bd9..26a3361 100644 --- a/ProjectPSX/Devices/CDROM/CD.cs +++ b/ProjectPSX/Devices/CDROM/CD.cs @@ -14,6 +14,8 @@ public class CD { public List<Track> tracks; + public bool isTrackChange; + public CD(string diskFilename) { string ext = Path.GetExtension(diskFilename); @@ -53,8 +55,9 @@ public byte[] Read(int loc) { public Track getTrackFromLoc(int loc) { foreach (Track track in tracks) { - //Console.WriteLine(loc + " " + track.file + track.lbaEnd); - if (track.lbaEnd > loc) return track; + isTrackChange = loc == track.lbaEnd; + //Console.WriteLine(loc + " " + track.number + " " + track.lbaEnd + " " + isTrackChange); + if (track.lbaEnd >= loc) return track; } Console.WriteLine("[CD] WARNING: LBA beyond tracks!"); return tracks[0]; //and explode ¯\_(ツ)_/¯ @@ -70,5 +73,9 @@ public int getLBA() { return lba; } + public bool isAudioCD() { + return tracks[0].isAudio; + } + } } diff --git a/ProjectPSX/Devices/CDROM/CDROM.cs b/ProjectPSX/Devices/CDROM/CDROM.cs index c20b2c2..d0f77f0 100644 --- a/ProjectPSX/Devices/CDROM/CDROM.cs +++ b/ProjectPSX/Devices/CDROM/CDROM.cs @@ -68,6 +68,7 @@ public class CDROM { private byte volumeRtoR = 0xFF; private bool cdDebug = false; + private bool isLidOpen = false; private struct SectorHeader { public byte mm; @@ -104,7 +105,37 @@ private enum Mode { private Mode mode = Mode.Idle; private int counter; - private Queue<byte> interruptQueue = new Queue<byte>(); + private Queue<DelayedInterrupt> interruptQueue = new Queue<DelayedInterrupt>(); + + public class DelayedInterrupt { + public int delay; + public byte interrupt; + + public DelayedInterrupt(int delay, byte interrupt) { + this.delay = delay; + this.interrupt = interrupt; + } + } + + //INT0 No response received(no interrupt request) + //INT1 Received SECOND(or further) response to ReadS/ReadN(and Play+Report) + //INT2 Received SECOND response(to various commands) + //INT3 Received FIRST response(to any command) + //INT4 DataEnd(when Play/Forward reaches end of disk) (maybe also for Read?) + //INT5 Received error-code(in FIRST or SECOND response) + //INT5 also occurs on SECOND GetID response, on unlicensed disks + //INT5 also occurs when opening the drive door(even if no command + // was sent, ie.even if no read-command or other command is active) + // INT6 N/A + //INT7 N/A + public class Interrupt { + public const byte INT0_NO_RESPONSE = 0; + public const byte INT1_SECOND_RESPONSE_READ_PLAY = 1; + public const byte INT2_SECOND_RESPONSE = 2; + public const byte INT3_FIRST_RESPONSE = 3; + public const byte INT4_DATA_END = 4; + public const byte INT5_ERROR = 5; + } private CD cd; private SPU spu; @@ -116,12 +147,51 @@ public CDROM(CD cd, SPU spu) { private bool edgeTrigger; + private Dictionary<uint, string> commands = new Dictionary<uint, string> { + [0x01] = "Cmd_01_GetStat", + [0x02] = "Cmd_02_SetLoc", + [0x03] = "Cmd_03_Play", + [0x04] = "Cmd_04_Forward", + [0x05] = "Cmd_05_Backward", + [0x06] = "Cmd_06_ReadN", + [0x07] = "Cmd_07_MotorOn", + [0x08] = "Cmd_08_Stop", + [0x09] = "Cmd_09_Pause", + [0x0A] = "Cmd_0A_Init", + [0x0B] = "Cmd_0B_Mute", + [0x0C] = "Cmd_0C_Demute", + [0x0D] = "Cmd_0D_SetFilter", + [0x0E] = "Cmd_0E_SetMode", + [0x0F] = "Cmd_0F_GetParam", + [0x10] = "Cmd_10_GetLocL", + [0x11] = "Cmd_11_GetLocP", + [0x12] = "Cmd_12_SetSession", + [0x13] = "Cmd_13_GetTN", + [0x14] = "Cmd_14_GetTD", + [0x15] = "Cmd_15_SeekL", + [0x16] = "Cmd_16_SeekP", + [0x17] = "--- [Unimplemented]", + [0x18] = "--- [Unimplemented]", + [0x19] = "Cmd_19_Test", + [0x1A] = "Cmd_1A_GetID", + [0x1B] = "Cmd_1B_ReadS", + [0x1C] = "Cmd_1C_Reset [Unimplemented]", + [0x1D] = "Cmd_1D_GetQ [Unimplemented]", + [0x1E] = "Cmd_1E_ReadTOC", + [0x1F] = "Cmd_1F_VideoCD", + }; + public bool tick(int cycles) { counter += cycles; - if (interruptQueue.Count != 0 && IF == 0) { + if (interruptQueue.Count != 0) { + var delayedInterrupt = interruptQueue.Peek(); + delayedInterrupt.delay -= cycles; + } + + if (interruptQueue.Count != 0 && IF == 0 && interruptQueue.Peek().delay <= 0) { if (cdDebug) Console.WriteLine($"[CDROM] Interrupt Queue is size: {interruptQueue.Count} dequeue to IF next Interrupt: {interruptQueue.Peek()}"); - IF |= interruptQueue.Dequeue(); + IF |= interruptQueue.Dequeue().interrupt; //edgeTrigger = true; } @@ -134,8 +204,8 @@ public bool tick(int cycles) { switch (mode) { case Mode.Idle: - counter = 0; - return false; + counter = 0; + return false; case Mode.Seek: //Hardcoded seek time... if (counter < 33868800 / 3 || interruptQueue.Count != 0) { @@ -146,11 +216,11 @@ public bool tick(int cycles) { STAT = (byte)(STAT & (~0x40)); responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x2); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT2_SECOND_RESPONSE); break; case Mode.Read: - case Mode.Play: + case Mode.Play: if (counter < (33868800 / (isDoubleSpeed ? 150 : 75)) || interruptQueue.Count != 0) { return false; } @@ -171,7 +241,43 @@ public bool tick(int cycles) { spu.pushCdBufferSamples(readSector); } - return false; //CDDA isn't delivered to CPU and doesn't raise interrupt + if (isAutoPause && cd.isTrackChange) { + responseBuffer.Enqueue(STAT); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT4_DATA_END, 1); + + STAT = 0x2; + mode = Mode.Idle; + } + + if (isReport) { + (byte amm, byte ass, byte aff) = getMMSSFFfromLBA(readLoc); + Track track = cd.getTrackFromLoc(readLoc); + bool inPregap = readLoc < track.lbaStart; + byte index = inPregap ? (byte)0 : (byte)1; + + responseBuffer.Enqueue(STAT); + responseBuffer.Enqueue(track.number); + responseBuffer.Enqueue(index); + + if ((aff & 0x10) != 0) { + (byte mm, byte ss, byte ff) = getMMSSFFfromLBA(Math.Abs(readLoc - track.lbaStart)); + + responseBuffer.Enqueue(DecToBcd(mm)); + responseBuffer.Enqueue((byte)(DecToBcd(ss) | 0x80)); + responseBuffer.Enqueue(DecToBcd(ff)); + } else { + responseBuffer.Enqueue(DecToBcd(amm)); + responseBuffer.Enqueue(DecToBcd(ass)); + responseBuffer.Enqueue(DecToBcd(aff)); + } + + responseBuffer.Enqueue(0x80); // peekLo + responseBuffer.Enqueue(0x80); // peekHi + + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT1_SECOND_RESPONSE_READ_PLAY, 1); + } + + return false; //CDDA without report isn't delivered to CPU and doesn't raise interrupt } //Handle Mode.Read: @@ -207,7 +313,7 @@ public bool tick(int cycles) { if (cdDebug) Console.WriteLine("[CDROM] XA ON: Realtime + Audio"); //todo flag to pass to SPU? - if(!mutedAudio && !mutedXAADPCM) { + if (!mutedAudio && !mutedXAADPCM) { byte[] decodedXaAdpcm = XaAdpcm.Decode(readSector, sectorSubHeader.codingInfo); applyVolume(decodedXaAdpcm); spu.pushCdBufferSamples(decodedXaAdpcm); @@ -227,7 +333,7 @@ public bool tick(int cycles) { } responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x1); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT1_SECOND_RESPONSE_READ_PLAY); break; @@ -237,7 +343,7 @@ public bool tick(int cycles) { } mode = Mode.Idle; responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x2); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT2_SECOND_RESPONSE); counter = 0; break; } @@ -252,12 +358,15 @@ public uint load(uint addr) { return STATUS(); case 0x1F801801: - if (cdDebug) Console.WriteLine("[CDROM] [L01] RESPONSE " /*+ responseBuffer.Peek().ToString("x8")*/); //Console.ReadLine(); //if (w == Width.HALF || w == Width.WORD) Console.WriteLine("WARNING RESPONSE BUFFER LOAD " + w); - if (responseBuffer.Count > 0) + if (responseBuffer.Count > 0) { + if (cdDebug) Console.WriteLine("[CDROM] [L01] RESPONSE " + responseBuffer.Peek().ToString("x8")); return responseBuffer.Dequeue(); + } + + if (cdDebug) Console.WriteLine("[CDROM] [L01] RESPONSE 0xFF"); return 0xFF; @@ -295,8 +404,8 @@ public void write(uint addr, uint value) { case 0x1F801801: if (INDEX == 0) { if (cdDebug) { - Console.BackgroundColor = ConsoleColor.Yellow; - Console.WriteLine($"[CDROM] [W01.0] [COMMAND] >>> {value:x2}"); + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine($"[CDROM] [W01.0] [COMMAND] {value:x2} {commands.GetValueOrDefault(value, "---")}"); Console.ResetColor(); } ExecuteCommand(value); @@ -357,8 +466,8 @@ public void write(uint addr, uint value) { case 1: IF &= (byte)~(value & 0x1F); if (cdDebug) Console.WriteLine($"[CDROM] [W03.1] Set IF: {value:x8} -> IF = {IF:x8}"); - if (interruptQueue.Count > 0) { - IF |= interruptQueue.Dequeue(); + if (interruptQueue.Count > 0 && interruptQueue.Peek().delay <= 0) { + IF |= interruptQueue.Dequeue().interrupt; } if ((value & 0x40) == 0x40) { @@ -378,8 +487,8 @@ public void write(uint addr, uint value) { bool applyVolume = (value & 0x20) != 0; if (applyVolume) { volumeLtoL = pendingVolumeLtoL; - volumeLtoR = pendingVolumeLtoR; - volumeRtoL = pendingVolumeRtoL; + volumeLtoR = pendingVolumeLtoR; + volumeRtoL = pendingVolumeRtoL; volumeRtoR = pendingVolumeRtoR; } break; @@ -397,258 +506,203 @@ public void write(uint addr, uint value) { private void ExecuteCommand(uint value) { if (cdDebug) Console.WriteLine($"[CDROM] Command {value:x4}"); + //Console.WriteLine($"PRE STAT {STAT:x2}"); interruptQueue.Clear(); responseBuffer.Clear(); isBusy = true; switch (value) { - case 0x01: getStat(); break; - case 0x02: setLoc(); break; - case 0x03: play(); break; - case 0x06: readN(); break; - case 0x07: motorOn(); break; - case 0x08: stop(); break; - case 0x09: pause(); break; - case 0x0A: init(); break; - case 0x0B: mute(); break; - case 0x0C: demute(); break; - case 0x0D: setFilter(); break; - case 0x0E: setMode(); break; - case 0x10: getLocL(); break; - case 0x11: getLocP(); break; - case 0x12: setSession(); break; - case 0x13: getTN(); break; - case 0x14: getTD(); break; - case 0x15: seekL(); break; - case 0x16: seekP(); break; - case 0x19: test(); break; - case 0x1A: getID(); break; - case 0x1B: readS(); break; - case 0x1E: readTOC(); break; - case 0x1F: videoCD(); break; - case uint _ when value >= 0x50 && value <= 0x57: lockUnlock(); break; + case 0x01: Cmd_01_GetStat(); break; + case 0x02: Cmd_02_SetLoc(); break; + case 0x03: Cmd_03_Play(); break; + //case 0x04: Cmd_04_Forward(); break; //todo + //case 0x05: Cmd_05_Backward(); break; //todo + case 0x06: Cmd_06_ReadN(); break; + case 0x07: Cmd_07_MotorOn(); break; + case 0x08: Cmd_08_Stop(); break; + case 0x09: Cmd_09_Pause(); break; + case 0x0A: Cmd_0A_Init(); break; + case 0x0B: Cmd_0B_Mute(); break; + case 0x0C: Cmd_0C_Demute(); break; + case 0x0D: Cmd_0D_SetFilter(); break; + case 0x0E: Cmd_0E_SetMode(); break; + //case 0x0F: Cmd_0F_GetParam(); break; //todo + case 0x10: Cmd_10_GetLocL(); break; + case 0x11: Cmd_11_GetLocP(); break; + case 0x12: Cmd_12_SetSession(); break; + case 0x13: Cmd_13_GetTN(); break; + case 0x14: Cmd_14_GetTD(); break; + case 0x15: Cmd_15_SeekL(); break; + case 0x16: Cmd_16_SeekP(); break; + case 0x19: Cmd_19_Test(); break; + case 0x1A: Cmd_1A_GetID(); break; + case 0x1B: Cmd_1B_ReadS(); break; + case 0x1E: Cmd_1E_ReadTOC(); break; + case 0x1F: Cmd_1F_VideoCD(); break; + case uint _ when value >= 0x50 && value <= 0x57: Cmd_5x_lockUnlock(); break; default: UnimplementedCDCommand(value); break; } + //Console.WriteLine($"POST STAT {STAT:x2}"); } - private void mute() { - mutedAudio = true; - - responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); - } - - private void readTOC() { - mode = Mode.TOC; - responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); - } - - private void motorOn() { - STAT = 0x2; - - responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); + private void Cmd_01_GetStat() { + if (!isLidOpen) { + STAT = (byte)(STAT & (~0x18)); + STAT |= 0x2; + } responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x2); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); } - private void getLocL() { - if (cdDebug) { - Console.WriteLine($"mm: {sectorHeader.mm} ss: {sectorHeader.ss} ff: {sectorHeader.ff} mode: {sectorHeader.mode}" + - $" file: {sectorSubHeader.file} channel: {sectorSubHeader.channel} subMode: {sectorSubHeader.subMode} codingInfo: {sectorSubHeader.codingInfo}"); + private void Cmd_02_SetLoc() { + if (isLidOpen) { + responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR); + return; } - Span<byte> response = stackalloc byte[] { - sectorHeader.mm, - sectorHeader.ss, - sectorHeader.ff, - sectorHeader.mode, - sectorSubHeader.file, - sectorSubHeader.channel, - sectorSubHeader.subMode, - sectorSubHeader.codingInfo - }; + byte mm = parameterBuffer.Dequeue(); + byte ss = parameterBuffer.Dequeue(); + byte ff = parameterBuffer.Dequeue(); - responseBuffer.EnqueueRange(response); + //Console.WriteLine($"[CDROM] setLoc BCD {mm:x2}:{ss:x2}:{ff:x2}"); - interruptQueue.Enqueue(0x3); - } - private void getLocP() { //SubQ missing... + int minute = BcdToDec(mm); + int second = BcdToDec(ss); + int sector = BcdToDec(ff); - Track track = cd.getTrackFromLoc(readLoc); - (byte mm, byte ss, byte ff) = getMMSSFFfromLBA(readLoc - track.lbaStart); - (byte amm, byte ass, byte aff) = getMMSSFFfromLBA(readLoc); + //There are 75 sectors on a second + seekLoc = sector + (second * 75) + (minute * 60 * 75); + + if (seekLoc < 0) { + Console.WriteLine($"[CDROM] WARNING NEGATIVE setLOC {seekLoc:x8}"); + seekLoc = 0; + } if (cdDebug) { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine($"track: {track.number} index: {1} mm: {mm} ss: {ss}" + - $" ff: {ff} amm: {amm} ass: {ass} aff: {aff}"); - Console.WriteLine($"track: {track.number} index: {1} mm: {DecToBcd(mm)} ss: {DecToBcd(ss)}" + - $" ff: {DecToBcd(ff)} amm: {DecToBcd(amm)} ass: {DecToBcd(ass)} aff: {DecToBcd(aff)}"); + Console.ForegroundColor = ConsoleColor.DarkGreen; + Console.WriteLine($"[CDROM] setLoc {mm:x2}:{ss:x2}:{ff:x2} Loc: {seekLoc:x8}"); Console.ResetColor(); } - Span<byte> response = stackalloc byte[] { track.number, 1, DecToBcd(mm), DecToBcd(ss), DecToBcd(ff), DecToBcd(amm), DecToBcd(ass), DecToBcd(aff) }; - responseBuffer.EnqueueRange(response); - - interruptQueue.Enqueue(0x3); - } - - private void lockUnlock() { - interruptQueue.Enqueue(0x5); - } - - private void videoCD() { //INT5(11h,40h) ;-Unused/invalid - responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x40 }); - - interruptQueue.Enqueue(0x5); + responseBuffer.Enqueue(STAT); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); } - private void setSession() { //broken - parameterBuffer.Clear(); + private void Cmd_03_Play() { + if (isLidOpen) { + responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR); + return; + } + //If theres a trackN param it seeks and plays from the start location of it + int track = 0; + if (parameterBuffer.Count > 0 && parameterBuffer.Peek() != 0) { + track = BcdToDec(parameterBuffer.Dequeue()); + if (cd.isAudioCD()) { + readLoc = seekLoc = cd.tracks[track - 1].lbaStart; + } else { + readLoc = seekLoc = cd.tracks[track].lbaStart; + } + //else it plays from the previously seekLoc and seeks if not done (actually not checking if already seeked) + } else { + readLoc = seekLoc; + } - STAT = 0x42; + Console.WriteLine($"[CDROM] CDDA Play Triggered Track: {track} readLoc: {readLoc}"); - responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); + STAT = 0x82; + mode = Mode.Play; responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x2); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); } - private void setFilter() { - filterFile = parameterBuffer.Dequeue(); - filterChannel = parameterBuffer.Dequeue(); - - responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); - } + private void Cmd_06_ReadN() { + if (isLidOpen) { + responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR); + return; + } - private void readS() { readLoc = seekLoc; STAT = 0x2; STAT |= 0x20; responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); mode = Mode.Read; } - private void seekP() { - readLoc = seekLoc; - STAT = 0x42; // seek - - mode = Mode.Seek; + private void Cmd_07_MotorOn() { + STAT = 0x2; responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); - } - - - private void play() { - //If theres a trackN param it seeks and plays from the start location of it - int track = 0; - if (parameterBuffer.Count > 0) { - track = BcdToDec(parameterBuffer.Dequeue()); - readLoc = seekLoc = cd.tracks[track].lbaStart; - //else it plays from the previously seekLoc and seeks if not done (actually not checking if already seeked) - } else { - readLoc = seekLoc; - } - - Console.WriteLine($"[CDROM] CDDA Play Triggered Track: {track} readLoc: {readLoc}"); - - STAT = 0x82; - mode = Mode.Play; + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT2_SECOND_RESPONSE); } - private void stop() { - STAT = 0; - + private void Cmd_08_Stop() { + STAT = 0x2; responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); + STAT = 0; responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x2); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT2_SECOND_RESPONSE); mode = Mode.Idle; } - private void getTD() { - int track = BcdToDec(parameterBuffer.Dequeue()); - - if (track == 0) { //returns CD LBA / End of last track - (byte mm, byte ss, byte ff) = getMMSSFFfromLBA(cd.getLBA()); - responseBuffer.EnqueueRange(stackalloc byte[] { STAT, DecToBcd(mm), DecToBcd(ss) }); - //if (cdDebug) - Console.WriteLine($"[CDROM] getTD Track: {track} STAT: {STAT:x2} {mm}:{ss}"); - } else { //returns Track Start - (byte mm, byte ss, byte ff) = getMMSSFFfromLBA(cd.tracks[track - 1].lbaStart); - responseBuffer.EnqueueRange(stackalloc byte[] { STAT, DecToBcd(mm), DecToBcd(ss) }); - //if (cdDebug) - Console.WriteLine($"[CDROM] getTD Track: {track} STAT: {STAT:x2} {mm}:{ss}"); - } - - //Console.ReadLine(); - interruptQueue.Enqueue(0x3); - } - - private void getTN() { - //if (cdDebug) - Console.WriteLine($"[CDROM] getTN First Track: 1 (Hardcoded) - Last Track: {cd.tracks.Count}"); - //Console.ReadLine(); - responseBuffer.EnqueueRange(stackalloc byte[] { STAT, 1, DecToBcd((byte)cd.tracks.Count) }); - interruptQueue.Enqueue(0x3); - } + private void Cmd_09_Pause() { + responseBuffer.Enqueue(STAT); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); - private void demute() { - mutedAudio = false; + STAT = 0x2; + mode = Mode.Idle; responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT2_SECOND_RESPONSE); } - private void init() { + private void Cmd_0A_Init() { STAT = 0x2; responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x2); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT2_SECOND_RESPONSE); } - private void pause() { + private void Cmd_0B_Mute() { + mutedAudio = true; + responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); + } - STAT = 0x2; - mode = Mode.Idle; + private void Cmd_0C_Demute() { + mutedAudio = false; responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x2); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); } - private void readN() { - readLoc = seekLoc; - - STAT = 0x2; - STAT |= 0x20; + private void Cmd_0D_SetFilter() { + filterFile = parameterBuffer.Dequeue(); + filterChannel = parameterBuffer.Dequeue(); responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); - - mode = Mode.Read; + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); } - private void setMode() { + private void Cmd_0E_SetMode() { //7 Speed(0 = Normal speed, 1 = Double speed) //6 XA - ADPCM(0 = Off, 1 = Send XA - ADPCM sectors to SPU Audio Input) //5 Sector Size(0 = 800h = DataOnly, 1 = 924h = WholeSectorExceptSyncBytes) @@ -671,85 +725,152 @@ private void setMode() { isCDDA = (mode & 0x1) == 1; responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); } - private void seekL() { - readLoc = seekLoc; - STAT = 0x42; // seek + private void Cmd_10_GetLocL() { + if (cdDebug) { + Console.WriteLine($"mm: {sectorHeader.mm} ss: {sectorHeader.ss} ff: {sectorHeader.ff} mode: {sectorHeader.mode}" + + $" file: {sectorSubHeader.file} channel: {sectorSubHeader.channel} subMode: {sectorSubHeader.subMode} codingInfo: {sectorSubHeader.codingInfo}"); + } - mode = Mode.Seek; + if (isLidOpen) { + responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR); + return; + } - responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); + Span<byte> response = stackalloc byte[] { + sectorHeader.mm, + sectorHeader.ss, + sectorHeader.ff, + sectorHeader.mode, + sectorSubHeader.file, + sectorSubHeader.channel, + sectorSubHeader.subMode, + sectorSubHeader.codingInfo + }; + + responseBuffer.EnqueueRange(response); + + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); } - private void setLoc() { - byte mm = parameterBuffer.Dequeue(); - byte ss = parameterBuffer.Dequeue(); - byte ff = parameterBuffer.Dequeue(); + private void Cmd_11_GetLocP() { //SubQ missing... + if (isLidOpen) { + responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR); + return; + } - //Console.WriteLine($"[CDROM] setLoc BCD {mm:x2}:{ss:x2}:{ff:x2}"); + Track track = cd.getTrackFromLoc(readLoc); + (byte mm, byte ss, byte ff) = getMMSSFFfromLBA(readLoc - track.lbaStart); + (byte amm, byte ass, byte aff) = getMMSSFFfromLBA(readLoc); + + if (cdDebug) { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"track: {track.number} index: {1} mm: {mm} ss: {ss}" + + $" ff: {ff} amm: {amm} ass: {ass} aff: {aff}"); + Console.WriteLine($"track: {track.number} index: {1} mm: {DecToBcd(mm)} ss: {DecToBcd(ss)}" + + $" ff: {DecToBcd(ff)} amm: {DecToBcd(amm)} ass: {DecToBcd(ass)} aff: {DecToBcd(aff)}"); + Console.ResetColor(); + } + Span<byte> response = stackalloc byte[] { track.number, 1, DecToBcd(mm), DecToBcd(ss), DecToBcd(ff), DecToBcd(amm), DecToBcd(ass), DecToBcd(aff) }; + responseBuffer.EnqueueRange(response); - int minute = BcdToDec(mm); - int second = BcdToDec(ss); - int sector = BcdToDec(ff); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); + } - //There are 75 sectors on a second - seekLoc = sector + (second * 75) + (minute * 60 * 75); + private void Cmd_12_SetSession() { //broken + parameterBuffer.Clear(); - if (seekLoc < 0) { - Console.WriteLine($"[CDROM] WARNING NEGATIVE setLOC {seekLoc:x8}"); - seekLoc = 0; + if (isLidOpen) { + responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR); + return; } - if (cdDebug) { - Console.ForegroundColor = ConsoleColor.DarkGreen; - Console.WriteLine($"[CDROM] setLoc {mm:x2}:{ss:x2}:{ff:x2} Loc: {seekLoc:x8}"); - Console.ResetColor(); - } + STAT = 0x42; + + responseBuffer.Enqueue(STAT); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT2_SECOND_RESPONSE); } - private void getID() { - //No Disk INT3(stat) INT5(08h, 40h, 00h, 00h, 00h, 00h, 00h, 00h) - //STAT = 0x2; //0x40 seek - //responseBuffer.Enqueue(STAT); - //interruptQueue.Enqueue(0x3); - // - //responseBuffer.EnqueueRange(stackalloc byte[] { 0x08, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); - //interruptQueue.Enqueue(0x5); + private void Cmd_13_GetTN() { + if (isLidOpen) { + responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR); + return; + } + //if (cdDebug) + Console.WriteLine($"[CDROM] getTN First Track: 1 (Hardcoded) - Last Track: {cd.tracks.Count}"); + //Console.ReadLine(); + responseBuffer.EnqueueRange(stackalloc byte[] { STAT, 1, DecToBcd((byte)cd.tracks.Count) }); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); + } - //Door Open INT5(11h,80h) N/A - //STAT = 0x10; //Shell Open - //responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80, 0x00, 0x00 }); - //interruptQueue.Enqueue(0x5); + private void Cmd_14_GetTD() { + if (isLidOpen) { + responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR); + return; + } - //Licensed: Mode2 INT3(stat) INT2(02h, 00h, 20h, 00h, 53h, 43h, 45h, 4xh) - STAT = 0x40; //0x40 seek - STAT |= 0x2; - responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); + int track = BcdToDec(parameterBuffer.Dequeue()); - Span<byte> response = stackalloc byte[] { 0x02, 0x00, 0x20, 0x00, 0x53, 0x43, 0x45, 0x41 }; //SCE | //A 0x41 (America) - I 0x49 (Japan) - E 0x45 (Europe) - responseBuffer.EnqueueRange(response); - interruptQueue.Enqueue(0x2); + if (track == 0) { //returns CD LBA / End of last track + (byte mm, byte ss, byte ff) = getMMSSFFfromLBA(cd.getLBA()); + responseBuffer.EnqueueRange(stackalloc byte[] { STAT, DecToBcd(mm), DecToBcd(ss) }); + //if (cdDebug) + Console.WriteLine($"[CDROM] getTD Track: {track} STAT: {STAT:x2} {mm}:{ss}"); + } else { //returns Track Start + (byte mm, byte ss, byte ff) = getMMSSFFfromLBA(cd.tracks[track - 1].lbaStart); + responseBuffer.EnqueueRange(stackalloc byte[] { STAT, DecToBcd(mm), DecToBcd(ss) }); + //if (cdDebug) + Console.WriteLine($"[CDROM] getTD Track: {track} STAT: {STAT:x2} {mm}:{ss}"); + } + + //Console.ReadLine(); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); } - private void getStat() { + private void Cmd_15_SeekL() { + if (isLidOpen) { + responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR); + return; + } + + readLoc = seekLoc; + STAT = 0x42; // seek + + mode = Mode.Seek; + responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); } - private void UnimplementedCDCommand(uint value) { - Console.WriteLine($"[CDROM] Unimplemented CD Command {value}"); - //Console.ReadLine(); + private void Cmd_16_SeekP() { + if (isLidOpen) { + responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR); + return; + } + + readLoc = seekLoc; + STAT = 0x42; // seek + + mode = Mode.Seek; + + responseBuffer.Enqueue(STAT); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); } - private void test() { + private void Cmd_19_Test() { uint command = parameterBuffer.Dequeue(); responseBuffer.Clear(); //we need to clear the delay on response to get the actual 0 0 to bypass antimodchip protection switch (command) { @@ -757,24 +878,24 @@ private void test() { Console.WriteLine("[CDROM] Command 19 04 ResetSCExInfo Anti Mod Chip Meassures"); STAT = 0x2; responseBuffer.Enqueue(STAT); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); break; case 0x05:// 05h - INT3(total,success);Stop SCEx reading and get counters Console.WriteLine("[CDROM] Command 19 05 GetSCExInfo Hack 0 0 Bypass Response"); responseBuffer.EnqueueRange(stackalloc byte[] { 0, 0 }); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); break; case 0x20: //INT3(yy,mm,dd,ver) ;Get cdrom BIOS date/version (yy,mm,dd,ver) http://www.psxdev.net/forum/viewtopic.php?f=70&t=557 responseBuffer.EnqueueRange(stackalloc byte[] { 0x94, 0x09, 0x19, 0xC0 }); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); break; case 0x22: //INT3("for US/AEP") --> Region-free debug version --> accepts unlicensed CDRs responseBuffer.EnqueueRange(stackalloc byte[] { 0x66, 0x6F, 0x72, 0x20, 0x55, 0x53, 0x2F, 0x41, 0x45, 0x50 }); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); break; case 0x60:// 60h lo,hi INT3(databyte) ;HC05 SUB-CPU read RAM and I/O ports responseBuffer.Enqueue(0); - interruptQueue.Enqueue(0x3); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); break; default: Console.WriteLine($"[CDROM] Unimplemented 0x19 Test Command {command:x8}"); @@ -782,6 +903,87 @@ private void test() { } } + private void Cmd_1A_GetID() { + //Door Open INT5(11h,80h) N/A + if (isLidOpen) { + responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR); + return; + } + + //No Disk INT3(stat) INT5(08h, 40h, 00h, 00h, 00h, 00h, 00h, 00h) + //STAT = 0x2; //0x40 seek + //responseBuffer.Enqueue(STAT); + //interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_RECEIVED_FIRST_RESPONSE); + // + //responseBuffer.EnqueueRange(stackalloc byte[] { 0x08, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); + //interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR); + + //Licensed: Mode2 INT3(stat) INT2(02h, 00h, 20h, 00h, 53h, 43h, 45h, 4xh) + STAT = 0x40; //0x40 seek + STAT |= 0x2; + responseBuffer.Enqueue(STAT); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); + + // Audio Disk INT3(stat) INT5(0Ah,90h, 00h,00h, 00h,00h,00h,00h) + if (cd.isAudioCD()) { + Span<byte> audioCdResponse = stackalloc byte[] { 0x0A, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + responseBuffer.EnqueueRange(audioCdResponse); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR); + return; + } + + // Licensed: Mode2 INT3(stat) INT2(02h, 00h, 20h, 00h, 53h, 43h, 45h, 4xh) + Span<byte> gameResponse = stackalloc byte[] { 0x02, 0x00, 0x20, 0x00, 0x53, 0x43, 0x45, 0x41 }; //SCE | //A 0x41 (America) - I 0x49 (Japan) - E 0x45 (Europe) + responseBuffer.EnqueueRange(gameResponse); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT2_SECOND_RESPONSE); + } + + private void Cmd_1B_ReadS() { + if (isLidOpen) { + responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR); + return; + } + + readLoc = seekLoc; + + STAT = 0x2; + STAT |= 0x20; + + responseBuffer.Enqueue(STAT); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); + + mode = Mode.Read; + } + + private void Cmd_1E_ReadTOC() { + if (isLidOpen) { + responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 }); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR); + return; + } + + mode = Mode.TOC; + responseBuffer.Enqueue(STAT); + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE); + } + + private void Cmd_1F_VideoCD() { //INT5(11h,40h) ;-Unused/invalid + responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x40 }); + + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR); + } + + private void Cmd_5x_lockUnlock() { + interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR); + } + + private void UnimplementedCDCommand(uint value) { + Console.WriteLine($"[CDROM] Unimplemented CD Command {value}"); + Console.ReadLine(); + } + private byte STATUS() { //1F801800h - Index/Status Register (Bit0-1 R/W) (Bit2-7 Read Only) //0 - 1 Index Port 1F801801h - 1F801803h index(0..3 = Index0..Index3) (R / W) @@ -842,7 +1044,7 @@ private static (byte mm, byte ss, byte ff) getMMSSFFfromLBA(int lba) { private void applyVolume(byte[] rawSector) { var samples = MemoryMarshal.Cast<byte, short>(rawSector); - for(int i = 0; i < samples.Length; i+=2) { + for (int i = 0; i < samples.Length; i += 2) { short l = samples[i]; short r = samples[i + 1]; @@ -859,5 +1061,19 @@ public Span<uint> processDmaLoad(int size) { return currentSector.read(size); } + internal void toggleLid() { + isLidOpen = !isLidOpen; + if (isLidOpen) { + STAT = 0x18; + mode = Mode.Idle; + interruptQueue.Clear(); + responseBuffer.Clear(); + } else { + //todo handle the Cd load and not this hardcoded test: + //cd = new CD(@"cd_change_path"); + } + Console.WriteLine($"[CDROM] Shell is Open: {isLidOpen} STAT: {STAT}"); + } + } } diff --git a/ProjectPSX/Devices/CDROM/Sector.cs b/ProjectPSX/Devices/CDROM/Sector.cs index cc5f9bf..843a242 100644 --- a/ProjectPSX/Devices/CDROM/Sector.cs +++ b/ProjectPSX/Devices/CDROM/Sector.cs @@ -8,9 +8,10 @@ class Sector { // Standard size for a raw sector / CDDA public const int RAW_BUFFER = 2352; - // This sector data is already pre decoded and resampled so we need a bigger buffer (RAW_BUFFER * 4) - // and on the case of mono even a bigger one, as samples are mirrored to L/R as our output is allways stereo - public const int XA_BUFFER = RAW_BUFFER * 8; + // Only for the SPU: It represents a sector of data already pre decoded AND resampled so we need a bigger buffer (RAW_BUFFER * 4) + // and on the case of mono even a bigger one, as samples are mirrored to L/R as our output is allways stereo (that would be * 8) + // but on the special case of 18900 resampling we need even a bigger one... so go * 16 + public const int XA_BUFFER = RAW_BUFFER * 16; private byte[] sectorBuffer; diff --git a/ProjectPSX/Devices/CDROM/TrackBuilder.cs b/ProjectPSX/Devices/CDROM/TrackBuilder.cs index 5146205..9ee1539 100644 --- a/ProjectPSX/Devices/CDROM/TrackBuilder.cs +++ b/ProjectPSX/Devices/CDROM/TrackBuilder.cs @@ -15,14 +15,16 @@ public class Track { public int lba { get; private set; } public int lbaStart { get; private set; } public int lbaEnd { get; private set; } + public bool isAudio { get; private set; } - public Track(String file, long size, byte number, int lba, int lbaStart, int lbaEnd) { + public Track(String file, long size, byte number, int lba, int lbaStart, int lbaEnd, bool isAudio) { this.file = file; this.size = size; this.number = number; this.lba = lba; this.lbaStart = lbaStart; this.lbaEnd = lbaEnd; + this.isAudio = isAudio; } } @@ -52,9 +54,12 @@ public static List<Track> fromCue(String cue) { lbaCounter += lba; - tracks.Add(new Track(file, size, number, lba, lbaStart, lbaEnd)); ; + string trackTypeLine = cueFile.ReadLine(); + bool isAudio = trackTypeLine.Contains("AUDIO"); - Console.WriteLine($"File: {file} Size: {size} Number: {number} LbaStart: {lbaStart} LbaEnd: {lbaEnd}"); + tracks.Add(new Track(file, size, number, lba, lbaStart, lbaEnd, isAudio)); + + Console.WriteLine($"File: {file} Size: {size} Number: {number} LbaStart: {lbaStart} LbaEnd: {lbaEnd} isAudio {isAudio}"); } } @@ -71,10 +76,11 @@ public static List<Track> fromBin(string file) { int lbaStart = 150; // 150 frames (2 seconds) offset from track 1 int lbaEnd = lba; byte number = 1; + bool isAudio = false; - tracks.Add(new Track(file, size, number, lba, lbaStart, lbaEnd)); + tracks.Add(new Track(file, size, number, lba, lbaStart, lbaEnd, isAudio)); - Console.WriteLine($"File: {file} Size: {size} Number: {number} LbaStart: {lbaStart} LbaEnd: {lbaEnd}"); + Console.WriteLine($"File: {file} Size: {size} Number: {number} LbaStart: {lbaStart} LbaEnd: {lbaEnd} isAudio {isAudio}"); return tracks; } diff --git a/ProjectPSX/Devices/CDROM/XaAdpcm.cs b/ProjectPSX/Devices/CDROM/XaAdpcm.cs index cffefb2..8a4f8ab 100644 --- a/ProjectPSX/Devices/CDROM/XaAdpcm.cs +++ b/ProjectPSX/Devices/CDROM/XaAdpcm.cs @@ -122,7 +122,6 @@ private static List<short> resampleTo44100Hz(List<short> samples, bool isStereo, List<short> resamples = isStereo ? stereoResamples : monoResamples; resamples.Clear(); - //todo handle 18900hz for (int i = 0; i < samples.Count; i++) { resampleRingBuffer[channel][resamplePointer++ & 0x1F] = samples[i]; @@ -131,7 +130,11 @@ private static List<short> resampleTo44100Hz(List<short> samples, bool isStereo, if (sixStep == 0) { sixStep = 6; for (int table = 0; table < 7; table++) { - resamples.Add(zigZagInterpolate(resamplePointer, table, channel)); + short sample = zigZagInterpolate(resamplePointer, table, channel); + resamples.Add(sample); + if(is18900hz) { + resamples.Add(sample); + } } } } @@ -149,7 +152,9 @@ private static short zigZagInterpolate(int resamplePointer, int table, int chann } public static short[] decodeNibbles(byte[] xaapdcm, int position, int blk, int nibble, ref short old, ref short older) { - int shift = 12 - (xaapdcm[position + 4 + blk * 2 + nibble] & 0x0F); + int headerShift = xaapdcm[position + 4 + blk * 2 + nibble] & 0x0F; + if (headerShift > 12) headerShift = 9; + int shift = 12 - headerShift; int filter = (xaapdcm[position + 4 + blk * 2 + nibble] & 0x30) >> 4; int f0 = positiveXaAdpcmTable[filter]; diff --git a/ProjectPSX/Devices/DMA.cs b/ProjectPSX/Devices/DMA.cs deleted file mode 100644 index a0a8974..0000000 --- a/ProjectPSX/Devices/DMA.cs +++ /dev/null @@ -1,278 +0,0 @@ - -using System; -using System.Collections.Generic; - -namespace ProjectPSX.Devices { - public class DMA { - - public abstract class AChannel { - public abstract void write(uint register, uint value); - public abstract uint load(uint regiter); - } - - private class InterruptChannel : AChannel { - - private uint control; - - private bool forceIRQ; - private uint irqEnable; - private bool masterEnable; - private uint irqFlag; - private bool masterFlag; - - private bool edgeInterruptTrigger; - - public InterruptChannel() { - control = 0x07654321; - } - - public override uint load(uint register) { - switch (register) { - case 0: return control; - case 4: return loadInterrupt(); - case 6: return loadInterrupt() >> 16; //castlevania symphony of the night and dino crisis 2 ask for this - default: Console.WriteLine("Unhandled register on interruptChannel DMA load " + register); return 0xFFFF_FFFF; - } - } - - private uint loadInterrupt() { - uint interruptRegister = 0; - - interruptRegister |= (forceIRQ ? 1u : 0) << 15; - interruptRegister |= irqEnable << 16; - interruptRegister |= (masterEnable ? 1u : 0) << 23; - interruptRegister |= irqFlag << 24; - interruptRegister |= (masterFlag ? 1u : 0) << 31; - - return interruptRegister; - } - - public override void write(uint register, uint value) { - //Console.WriteLine("irqflag pre: " + irqFlag.ToString("x8")); - switch (register) { - case 0: control = value; break; - case 4: writeInterrupt(value); break; - case 6: writeInterrupt(value << 16 | (forceIRQ ? 1u : 0) << 15); break; - default: Console.WriteLine($"Unhandled write on DMA Interrupt register {register}"); break; - } - //Console.WriteLine("irqflag post: " + irqFlag.ToString("x8")); - } - - private void writeInterrupt(uint value) { - forceIRQ = ((value >> 15) & 0x1) != 0; - irqEnable = (value >> 16) & 0x7F; - masterEnable = ((value >> 23) & 0x1) != 0; - irqFlag &= ~((value >> 24) & 0x7F); - - masterFlag = updateMasterFlag(); - } - - public void handleInterrupt(int channel) { - //IRQ flags in Bit(24 + n) are set upon DMAn completion - but caution - they are set ONLY if enabled in Bit(16 + n). - if ((irqEnable & (1 << channel)) != 0) { - irqFlag |= (uint)(1 << channel); - } - - //Console.WriteLine($"MasterFlag: {masterFlag} irqEnable16: {irqEnable:x8} irqFlag24: {irqFlag:x8} {forceIRQ} {masterEnable} {((irqEnable & irqFlag) > 0)}"); - masterFlag = updateMasterFlag(); - - if (masterFlag) { - edgeInterruptTrigger = true; - } - } - - private bool updateMasterFlag() { - //Bit31 is a simple readonly flag that follows the following rules: - //IF b15 = 1 OR(b23 = 1 AND(b16 - 22 AND b24 - 30) > 0) THEN b31 = 1 ELSE b31 = 0 - return forceIRQ || (masterEnable && ((irqEnable & irqFlag) > 0)); - } - - public bool tick() { - if (edgeInterruptTrigger) { - edgeInterruptTrigger = false; - //Console.WriteLine("[IRQ] Triggering DMA"); - return true; - } - return false; - } - } - - private class Channel : AChannel { - - private uint baseAddress; - private uint blockSize; - private uint blockCount; - - private uint transferDirection; - private uint memoryStep; - private uint choppingEnable; - private uint syncMode; - private uint choppingDMAWindowSize; - private uint choppingCPUWindowSize; - private bool enable; - private bool trigger; - - private BUS bus; - private InterruptChannel interrupt; - private int channelNumber; - - public Channel(int channelNumber, InterruptChannel interrupt, BUS bus) { - this.channelNumber = channelNumber; - this.interrupt = interrupt; - this.bus = bus; - } - - public override uint load(uint register) { - switch (register) { - case 0: return baseAddress; - case 4: return blockCount << 16 | blockSize; - case 8: return loadChannelControl(); - default: return 0; - } - } - - private uint loadChannelControl() { - uint channelControl = 0; - - channelControl |= transferDirection; - channelControl |= (memoryStep == 4 ? 0 : 1u) << 1; - channelControl |= choppingEnable << 8; - channelControl |= syncMode << 9; - channelControl |= choppingDMAWindowSize << 16; - channelControl |= choppingCPUWindowSize << 20; - channelControl |= (enable ? 1u : 0) << 24; - channelControl |= (trigger ? 1u : 0) << 28; - - return channelControl; - } - - public override void write(uint register, uint value) { - switch (register) { - case 0: baseAddress = value & 0xFFFFFF; break; - case 4: blockCount = value >> 16; blockSize = value & 0xFFFF; break; - case 8: writeChannelControl(value); break; - default: Console.WriteLine($"Unhandled Write on DMA Channel: {channelNumber} register: {register} value: {value}"); break; - } - } - - private void writeChannelControl(uint value) { - transferDirection = value & 0x1; - memoryStep = (uint)(((value >> 1) & 0x1) == 0 ? 4 : -4); - choppingEnable = (value >> 8) & 0x1; - syncMode = (value >> 9) & 0x3; - choppingDMAWindowSize = (value >> 16) & 0x7; - choppingCPUWindowSize = (value >> 20) & 0x7; - enable = ((value >> 24) & 0x1) != 0; - trigger = ((value >> 28) & 0x1) != 0; - - handleDMA(); - } - - private void handleDMA() { - if (!isActive()) return; - if (syncMode == 0) { - blockCopy(blockSize); - } else if (syncMode == 1) { - blockCopy(blockSize * blockCount); - } else if (syncMode == 2) { - linkedList(); - } - - //disable channel - enable = false; - trigger = false; - - interrupt.handleInterrupt(channelNumber); - } - - - private void blockCopy(uint size) { - if (transferDirection == 0) { //To Ram - - switch (channelNumber) { - case 1: bus.DmaFromMdecOut(baseAddress, (int)size); break; - case 2: bus.DmaFromGpu(baseAddress, (int)size); break; - case 3: bus.DmaFromCD(baseAddress, (int)size); break; - case 4: bus.DmaFromSpu(baseAddress, (int)size); break; - case 6: bus.DmaOTC(baseAddress, (int)size); break; - default: Console.WriteLine($"[DMA] [BLOCK COPY] Unsupported Channel (to Ram) {channelNumber}"); break; - } - - baseAddress += memoryStep * size; - - } else { //From Ram - - Span<uint> dma = bus.DmaFromRam(baseAddress & 0x1F_FFFC, size); - - switch(channelNumber) { - case 0: bus.DmaToMdecIn(dma); break; - case 2: bus.DmaToGpu(dma); break; - case 4: bus.DmaToSpu(dma); break; - default: Console.WriteLine($"[DMA] [BLOCK COPY] Unsupported Channel (from Ram) {channelNumber}"); break; - } - - baseAddress += memoryStep * size; - } - - } - - private void linkedList() { - uint header = 0; - uint linkedListHardStop = 0xFFFF; //an arbitrary value to avoid infinity linked lists as we don't run the cpu in between blocks - - while ((header & 0x800000) == 0 && linkedListHardStop-- != 0) { - header = bus.LoadFromRam(baseAddress); - uint size = header >> 24; - //Console.WriteLine($"[DMA] [LinkedList] Header: {baseAddress:x8} size: {size}"); - - if (size > 0) { - baseAddress = (baseAddress + 4) & 0x1ffffc; - Span<uint> load = bus.DmaFromRam(baseAddress, size); - //Console.WriteLine($"[DMA] [LinkedList] DMAtoGPU size: {load.Length}"); - bus.DmaToGpu(load); - } - - if (baseAddress == (header & 0x1ffffc)) break; //Tekken2 hangs here if not handling this posible forever loop - baseAddress = header & 0x1ffffc; - } - - } - - //0 Start immediately and transfer all at once (used for CDROM, OTC) needs TRIGGER - private bool isActive() => syncMode == 0 ? enable && trigger : enable; - - } - - AChannel[] channels = new AChannel[8]; - - public DMA(BUS bus) { - InterruptChannel interrupt = new InterruptChannel(); - channels[0] = new Channel(0, interrupt, bus); - channels[1] = new Channel(1, interrupt, bus); - channels[2] = new Channel(2, interrupt, bus); - channels[3] = new Channel(3, interrupt, bus); - channels[4] = new Channel(4, interrupt, bus); - channels[5] = new Channel(5, interrupt, bus); - channels[6] = new Channel(6, interrupt, bus); - channels[7] = interrupt; - } - - public uint load(uint addr) { - uint channel = (addr & 0x70) >> 4; - uint register = addr & 0xF; - //Console.WriteLine("DMA load " + channel + " " + register + ":" + channels[channel].load(register).ToString("x8")); - return channels[channel].load(register); - } - - public void write(uint addr, uint value) { - uint channel = (addr & 0x70) >> 4; - uint register = addr & 0xF; - //Console.WriteLine("DMA write " + channel + " " + register + ":" + value.ToString("x8")); - - channels[channel].write(register, value); - } - - public bool tick() => ((InterruptChannel)channels[7]).tick(); - - } -} diff --git a/ProjectPSX/Devices/DMA/Channel.cs b/ProjectPSX/Devices/DMA/Channel.cs new file mode 100644 index 0000000..ea34d55 --- /dev/null +++ b/ProjectPSX/Devices/DMA/Channel.cs @@ -0,0 +1,5 @@ +namespace ProjectPSX.Devices; +public abstract class Channel { + public abstract void write(uint register, uint value); + public abstract uint load(uint regiter); +} diff --git a/ProjectPSX/Devices/DMA/DMA.cs b/ProjectPSX/Devices/DMA/DMA.cs new file mode 100644 index 0000000..d619e56 --- /dev/null +++ b/ProjectPSX/Devices/DMA/DMA.cs @@ -0,0 +1,42 @@ +using System; + +namespace ProjectPSX.Devices; +public class DMA { + + Channel[] channels = new Channel[8]; + + public DMA(BUS bus) { + var interrupt = new InterruptChannel(); + channels[0] = new DmaChannel(0, interrupt, bus); + channels[1] = new DmaChannel(1, interrupt, bus); + channels[2] = new DmaChannel(2, interrupt, bus); + channels[3] = new DmaChannel(3, interrupt, bus); + channels[4] = new DmaChannel(4, interrupt, bus); + channels[5] = new DmaChannel(5, interrupt, bus); + channels[6] = new DmaChannel(6, interrupt, bus); + channels[7] = interrupt; + } + + public uint load(uint addr) { + var channel = (addr & 0x70) >> 4; + var register = addr & 0xF; + //Console.WriteLine("DMA load " + channel + " " + register + ":" + channels[channel].load(register).ToString("x8")); + return channels[channel].load(register); + } + + public void write(uint addr, uint value) { + var channel = (addr & 0x70) >> 4; + var register = addr & 0xF; + //Console.WriteLine("DMA write " + channel + " " + register + ":" + value.ToString("x8")); + + channels[channel].write(register, value); + } + + public bool tick() { + for (var i = 0; i < 7; i++) { + ((DmaChannel)channels[i]).transferBlockIfPending(); + } + return ((InterruptChannel)channels[7]).tick(); + } + +} diff --git a/ProjectPSX/Devices/DMA/DmaChannel.cs b/ProjectPSX/Devices/DMA/DmaChannel.cs new file mode 100644 index 0000000..63ea45e --- /dev/null +++ b/ProjectPSX/Devices/DMA/DmaChannel.cs @@ -0,0 +1,193 @@ +using System; + +namespace ProjectPSX.Devices; +public sealed class DmaChannel : Channel { + + private uint baseAddress; + private uint blockSize; + private uint blockCount; + + private uint transferDirection; + private uint memoryStep; + private uint choppingEnable; + private uint syncMode; + private uint choppingDMAWindowSize; + private uint choppingCPUWindowSize; + private bool enable; + private bool trigger; + + private uint unknownBit29; + private uint unknownBit30; + + private BUS bus; + private InterruptChannel interrupt; + private int channelNumber; + + private uint pendingBlocks; + + public DmaChannel(int channelNumber, InterruptChannel interrupt, BUS bus) { + this.channelNumber = channelNumber; + this.interrupt = interrupt; + this.bus = bus; + } + + public override uint load(uint register) { + switch (register) { + case 0: return baseAddress; + case 4: return blockCount << 16 | blockSize; + case 8: return loadChannelControl(); + default: return 0; + } + } + + private uint loadChannelControl() { + uint channelControl = 0; + + channelControl |= transferDirection; + channelControl |= (memoryStep == 4 ? 0 : 1u) << 1; + channelControl |= choppingEnable << 8; + channelControl |= syncMode << 9; + channelControl |= choppingDMAWindowSize << 16; + channelControl |= choppingCPUWindowSize << 20; + channelControl |= (enable ? 1u : 0) << 24; + channelControl |= (trigger ? 1u : 0) << 28; + channelControl |= unknownBit29 << 29; + channelControl |= unknownBit30 << 30; + + if (channelNumber == 6) { + return channelControl & 0x5000_0002 | 0x2; + } + + return channelControl; + } + + public override void write(uint register, uint value) { + switch (register) { + case 0: baseAddress = value & 0xFFFFFF; break; + case 4: blockCount = value >> 16; blockSize = value & 0xFFFF; break; + case 8: writeChannelControl(value); break; + default: Console.WriteLine($"Unhandled Write on DMA Channel: {channelNumber} register: {register} value: {value}"); break; + } + } + + private void writeChannelControl(uint value) { + transferDirection = value & 0x1; + memoryStep = (uint)((value >> 1 & 0x1) == 0 ? 4 : -4); + choppingEnable = value >> 8 & 0x1; + syncMode = value >> 9 & 0x3; + choppingDMAWindowSize = value >> 16 & 0x7; + choppingCPUWindowSize = value >> 20 & 0x7; + enable = (value >> 24 & 0x1) != 0; + trigger = (value >> 28 & 0x1) != 0; + unknownBit29 = value >> 29 & 0x1; + unknownBit30 = value >> 30 & 0x1; + + if (!enable) pendingBlocks = 0; + + handleDMA(); + } + + private void handleDMA() { + if (!isActive() || !interrupt.isDMAControlMasterEnabled(channelNumber)) return; + if (syncMode == 0) { + //if (choppingEnable == 1) { + // Console.WriteLine($"[DMA] Chopping Syncmode 0 not supported. DmaWindow: {choppingDMAWindowSize} CpuWindow: {choppingCPUWindowSize}"); + //} + + blockCopy(blockSize == 0 ? 0x10_000 : blockSize); + finishDMA(); + + } else if (syncMode == 1) { + // HACK: + // GPUIn: Bypass blocks to elude mdec/gpu desync as MDEC is actually too fast decoding blocks + // MdecIn: GranTurismo produces some artifacts that still needs to be checked otherwise it's ok on other games i've checked + if (channelNumber == 2 && transferDirection == 1 || channelNumber == 0) { + blockCopy(blockSize * blockCount); + finishDMA(); + return; + } + + trigger = false; + pendingBlocks = blockCount; + transferBlockIfPending(); + } else if (syncMode == 2) { + linkedList(); + finishDMA(); + } + } + + private void finishDMA() { + enable = false; + trigger = false; + + interrupt.handleInterrupt(channelNumber); + } + + private void blockCopy(uint size) { + if (transferDirection == 0) { //To Ram + + switch (channelNumber) { + case 1: bus.DmaFromMdecOut(baseAddress, (int)size); break; + case 2: bus.DmaFromGpu(baseAddress, (int)size); break; + case 3: bus.DmaFromCD(baseAddress, (int)size); break; + case 4: bus.DmaFromSpu(baseAddress, (int)size); break; + case 6: bus.DmaOTC(baseAddress, (int)size); break; + default: Console.WriteLine($"[DMA] [BLOCK COPY] Unsupported Channel (to Ram) {channelNumber}"); break; + } + + baseAddress += memoryStep * size; + + } else { //From Ram + + var dma = bus.DmaFromRam(baseAddress & 0x1F_FFFC, size); + + switch (channelNumber) { + case 0: bus.DmaToMdecIn(dma); break; + case 2: bus.DmaToGpu(dma); break; + case 4: bus.DmaToSpu(dma); break; + default: Console.WriteLine($"[DMA] [BLOCK COPY] Unsupported Channel (from Ram) {channelNumber}"); break; + } + + baseAddress += memoryStep * size; + } + + } + + private void linkedList() { + uint header = 0; + uint linkedListHardStop = 0xFFFF; //an arbitrary value to avoid infinity linked lists as we don't run the cpu in between blocks + + while ((header & 0x800000) == 0 && linkedListHardStop-- != 0) { + header = bus.LoadFromRam(baseAddress); + var size = header >> 24; + //Console.WriteLine($"[DMA] [LinkedList] Header: {baseAddress:x8} size: {size}"); + + if (size > 0) { + baseAddress = baseAddress + 4 & 0x1ffffc; + var load = bus.DmaFromRam(baseAddress, size); + //Console.WriteLine($"[DMA] [LinkedList] DMAtoGPU size: {load.Length}"); + bus.DmaToGpu(load); + } + + if (baseAddress == (header & 0x1ffffc)) break; //Tekken2 hangs here if not handling this posible forever loop + baseAddress = header & 0x1ffffc; + } + + } + + //0 Start immediately and transfer all at once (used for CDROM, OTC) needs TRIGGER + private bool isActive() => syncMode == 0 ? enable && trigger : enable; + + public void transferBlockIfPending() { + //TODO: check if device can actually transfer. Here we assume devices are always + // capable of processing the dmas and never busy. + if (pendingBlocks > 0) { + pendingBlocks--; + blockCopy(blockSize); + + if (pendingBlocks == 0) { + finishDMA(); + } + } + } +} diff --git a/ProjectPSX/Devices/DMA/InterruptChannel.cs b/ProjectPSX/Devices/DMA/InterruptChannel.cs new file mode 100644 index 0000000..7e45e27 --- /dev/null +++ b/ProjectPSX/Devices/DMA/InterruptChannel.cs @@ -0,0 +1,92 @@ +using System; + +namespace ProjectPSX.Devices; +public sealed class InterruptChannel : Channel { + + // 1F8010F0h DPCR - DMA Control register + private uint control; + + // 1F8010F4h DICR - DMA Interrupt register + private bool forceIRQ; + private uint irqEnable; + private bool masterEnable; + private uint irqFlag; + private bool masterFlag; + + private bool edgeInterruptTrigger; + + public InterruptChannel() { + control = 0x07654321; + } + + public override uint load(uint register) { + switch (register) { + case 0: return control; + case 4: return loadInterrupt(); + case 6: return loadInterrupt() >> 16; //castlevania symphony of the night and dino crisis 2 ask for this + default: Console.WriteLine("Unhandled register on interruptChannel DMA load " + register); return 0xFFFF_FFFF; + } + } + + private uint loadInterrupt() { + uint interruptRegister = 0; + + interruptRegister |= (forceIRQ ? 1u : 0) << 15; + interruptRegister |= irqEnable << 16; + interruptRegister |= (masterEnable ? 1u : 0) << 23; + interruptRegister |= irqFlag << 24; + interruptRegister |= (masterFlag ? 1u : 0) << 31; + + return interruptRegister; + } + + public override void write(uint register, uint value) { + //Console.WriteLine("irqflag pre: " + irqFlag.ToString("x8")); + switch (register) { + case 0: control = value; break; + case 4: writeInterrupt(value); break; + case 6: writeInterrupt(value << 16 | (forceIRQ ? 1u : 0) << 15); break; + default: Console.WriteLine($"Unhandled write on DMA Interrupt register {register}"); break; + } + //Console.WriteLine("irqflag post: " + irqFlag.ToString("x8")); + } + + private void writeInterrupt(uint value) { + forceIRQ = (value >> 15 & 0x1) != 0; + irqEnable = value >> 16 & 0x7F; + masterEnable = (value >> 23 & 0x1) != 0; + irqFlag &= ~(value >> 24 & 0x7F); + + masterFlag = updateMasterFlag(); + } + + public void handleInterrupt(int channel) { + //IRQ flags in Bit(24 + n) are set upon DMAn completion - but caution - they are set ONLY if enabled in Bit(16 + n). + if ((irqEnable & 1 << channel) != 0) { + irqFlag |= (uint)(1 << channel); + } + + //Console.WriteLine($"MasterFlag: {masterFlag} irqEnable16: {irqEnable:x8} irqFlag24: {irqFlag:x8} {forceIRQ} {masterEnable} {((irqEnable & irqFlag) > 0)}"); + masterFlag = updateMasterFlag(); + edgeInterruptTrigger |= masterFlag; + } + + public bool isDMAControlMasterEnabled(int channelNumber) { + return (control >> 3 >> 4 * channelNumber & 0x1) != 0; + } + + private bool updateMasterFlag() { + //Bit31 is a simple readonly flag that follows the following rules: + //IF b15 = 1 OR(b23 = 1 AND(b16 - 22 AND b24 - 30) > 0) THEN b31 = 1 ELSE b31 = 0 + return forceIRQ || masterEnable && (irqEnable & irqFlag) > 0; + } + + public bool tick() { + if (edgeInterruptTrigger) { + edgeInterruptTrigger = false; + //Console.WriteLine("[IRQ] Triggering DMA"); + return true; + } + return false; + } +} diff --git a/ProjectPSX/Devices/Expansion/Exp2.cs b/ProjectPSX/Devices/Expansion/Exp2.cs new file mode 100644 index 0000000..7e274a0 --- /dev/null +++ b/ProjectPSX/Devices/Expansion/Exp2.cs @@ -0,0 +1,29 @@ +using System; + +namespace ProjectPSX.Devices.Expansion; +public class Exp2 { + + public uint load(uint addr) { + //Console.WriteLine($"[BUS] Read Unsupported to EXP2 address: {addr:x8}"); + return 0xFF; + } + + public void write(uint addr, uint value) { + switch (addr) { + case 0x1F802041: + Console.ForegroundColor = ConsoleColor.Cyan; + Console.WriteLine($"[EXP2] PSX: POST [{value:x1}]"); + Console.ResetColor(); + break; + case 0x1F802023: + case 0x1F802080: + Console.ForegroundColor = ConsoleColor.Cyan; + Console.Write((char)value); + Console.ResetColor(); + break; + default: + Console.WriteLine($"[BUS] Write Unsupported to EXP2: {addr:x8} Value: {value:x8}"); + break; + } + } +} diff --git a/ProjectPSX/Devices/GPU/GPU.cs b/ProjectPSX/Devices/GPU/GPU.cs index 6f9acda..6676915 100644 --- a/ProjectPSX/Devices/GPU/GPU.cs +++ b/ProjectPSX/Devices/GPU/GPU.cs @@ -20,8 +20,10 @@ public class GPU { private IHostWindow window; - private VRAM vram = new VRAM(1024, 512); //Vram is 8888 and we transform everything to it - private VRAM1555 vram1555 = new VRAM1555(1024, 512); //an un transformed 1555 to 8888 vram so we can fetch clut indexes without reverting to 1555 + private VRAM vram = new VRAM(); //Vram is 8888 and we transform everything to it + private VRAM1555 vram1555 = new VRAM1555(); //an un transformed 1555 to 8888 vram so we can fetch clut indexes without reverting to 1555 + + private int[] color1555to8888LUT; public bool debug; @@ -31,7 +33,7 @@ private enum Mode { } private Mode mode; - private ref struct Primitive { + private struct Primitive { public bool isShaded; public bool isTextured; public bool isSemiTransparent; @@ -54,12 +56,9 @@ private struct VramTransfer { [StructLayout(LayoutKind.Explicit)] private struct Point2D { - [FieldOffset(0)] public uint val; [FieldOffset(0)] public short x; [FieldOffset(2)] public short y; } - - private Point2D[] v = new Point2D[4]; private Point2D min = new Point2D(); private Point2D max = new Point2D(); @@ -69,7 +68,7 @@ private struct TextureData { [FieldOffset(0)] public byte x; [FieldOffset(1)] public byte y; } - private TextureData[] t = new TextureData[4]; + TextureData textureData = new TextureData(); [StructLayout(LayoutKind.Explicit)] private struct Color { @@ -107,9 +106,9 @@ private struct Color { private bool isInterruptRequested; private bool isDmaRequest; - private bool isReadyToReceiveCommand; - private bool isReadyToSendVRAMToCPU; - private bool isReadyToReceiveDMABlock; + private bool isReadyToReceiveCommand = true; //todo + private bool isReadyToSendVRAMToCPU; + private bool isReadyToReceiveDMABlock = true; //todo private byte dmaDirection; private bool isOddLine; @@ -117,6 +116,11 @@ private struct Color { private bool isTexturedRectangleXFlipped; private bool isTexturedRectangleYFlipped; + private uint drawModeBits = 0xFFFF_FFFF; + private uint displayModeBits = 0xFFFF_FFFF; + uint displayVerticalRange = 0xFFFF_FFFF; + uint displayHorizontalRange = 0xFFFF_FFFF; + private uint textureWindowBits = 0xFFFF_FFFF; private int preMaskX; private int preMaskY; @@ -144,9 +148,23 @@ private struct Color { public GPU(IHostWindow window) { this.window = window; mode = Mode.COMMAND; + initColorTable(); GP1_00_ResetGPU(); } + public void initColorTable() { + color1555to8888LUT = new int[ushort.MaxValue + 1]; + for (int m = 0; m < 2; m++) { + for (int r = 0; r < 32; r++) { + for (int g = 0; g < 32; g++) { + for (int b = 0; b < 32; b++) { + color1555to8888LUT[m << 15 | b << 10 | g << 5 | r] = m << 24 | r << 16 + 3 | g << 8 + 3| b << 3; + } + } + } + } + } + public bool tick(int cycles) { //Video clock is the cpu clock multiplied by 11/7. videoCycles += cycles * 11 / 7; @@ -165,6 +183,7 @@ public bool tick(int cycles) { if (isVerticalInterlace && isVerticalResolution480) { isOddLine = !isOddLine; + isInterlaceField = !isOddLine; } window.Render(vram.Bits); @@ -185,12 +204,7 @@ public bool tick(int cycles) { public uint loadGPUSTAT() { uint GPUSTAT = 0; - GPUSTAT |= textureXBase; - GPUSTAT |= (uint)textureYBase << 4; - GPUSTAT |= (uint)transparencyMode << 5; - GPUSTAT |= (uint)textureDepth << 7; - GPUSTAT |= (uint)(isDithered ? 1 : 0) << 9; - GPUSTAT |= (uint)(isDrawingToDisplayAllowed ? 1 : 0) << 10; + GPUSTAT |= drawModeBits & 0x7FF; GPUSTAT |= (uint)maskWhileDrawing << 11; GPUSTAT |= (uint)(checkMaskBeforeDraw ? 1 : 0) << 12; GPUSTAT |= (uint)(isInterlaceField ? 1 : 0) << 13; @@ -198,7 +212,7 @@ public uint loadGPUSTAT() { GPUSTAT |= (uint)(isTextureDisabled ? 1 : 0) << 15; GPUSTAT |= (uint)horizontalResolution2 << 16; GPUSTAT |= (uint)horizontalResolution1 << 17; - GPUSTAT |= (uint)(isVerticalResolution480 ? 1 : 0); + GPUSTAT |= (uint)(isVerticalResolution480 ? 1 : 0) << 19; GPUSTAT |= (uint)(isPal ? 1 : 0) << 20; GPUSTAT |= (uint)(is24BitDepth ? 1 : 0) << 21; GPUSTAT |= (uint)(isVerticalInterlace ? 1 : 0) << 22; @@ -206,9 +220,9 @@ public uint loadGPUSTAT() { GPUSTAT |= (uint)(isInterruptRequested ? 1 : 0) << 24; GPUSTAT |= (uint)(isDmaRequest ? 1 : 0) << 25; - GPUSTAT |= (uint)/*(isReadyToReceiveCommand ? 1 : 0)*/1 << 26; - GPUSTAT |= (uint)/*(isReadyToSendVRAMToCPU ? 1 : 0)*/1 << 27; - GPUSTAT |= (uint)/*(isReadyToReceiveDMABlock ? 1 : 0)*/1 << 28; + GPUSTAT |= (uint)(isReadyToReceiveCommand ? 1 : 0) << 26; + GPUSTAT |= (uint)(isReadyToSendVRAMToCPU ? 1 : 0) << 27; + GPUSTAT |= (uint)(isReadyToReceiveDMABlock ? 1 : 0) << 28; GPUSTAT |= (uint)dmaDirection << 29; GPUSTAT |= (uint)(isOddLine ? 1 : 0) << 31; @@ -254,7 +268,7 @@ public void writeGP0(uint value) { [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void processDma(Span<uint> dma) { if (mode == Mode.COMMAND) { - DecodeGP0Command(dma); + DecodeGP0Command(dma); } else { for (int i = 0; i < dma.Length; i++) { WriteToVRAM(dma[i]); @@ -287,38 +301,39 @@ private void WriteToVRAM(uint value) { [MethodImpl(MethodImplOptions.AggressiveInlining)] private uint readFromVRAM() { - ushort pixel0 = vram.GetPixelBGR555(vramTransfer.x++ & 0x3FF, vramTransfer.y & 0x1FF); - ushort pixel1 = vram.GetPixelBGR555(vramTransfer.x++ & 0x3FF, vramTransfer.y & 0x1FF); - if (vramTransfer.x == vramTransfer.origin_x + vramTransfer.w) { - vramTransfer.x -= vramTransfer.w; - vramTransfer.y++; - } - vramTransfer.halfWords -= 2; - return (uint)(pixel1 << 16 | pixel0); - } + ushort pixel0 = vram.GetPixelBGR555(vramTransfer.x & 0x3FF, vramTransfer.y & 0x1FF); + stepVramTransfer(); + ushort pixel1 = vram.GetPixelBGR555(vramTransfer.x & 0x3FF, vramTransfer.y & 0x1FF); + stepVramTransfer(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void drawVRAMPixel(ushort val) { - if (checkMaskBeforeDraw) { - int bg = vram.GetPixelRGB888(vramTransfer.x, vramTransfer.y); + vramTransfer.halfWords -= 2; - if (bg >> 24 == 0) { - vram.SetPixel(vramTransfer.x & 0x3FF, vramTransfer.y & 0x1FF, color1555to8888(val)); - vram1555.SetPixel(vramTransfer.x & 0x3FF, vramTransfer.y & 0x1FF, val); - } - } else { - vram.SetPixel(vramTransfer.x & 0x3FF, vramTransfer.y & 0x1FF, color1555to8888(val)); - vram1555.SetPixel(vramTransfer.x & 0x3FF, vramTransfer.y & 0x1FF, val); + if (vramTransfer.halfWords == 0) { + isReadyToSendVRAMToCPU = false; + isReadyToReceiveDMABlock = true; } - vramTransfer.x++; + return (uint)(pixel1 << 16 | pixel0); + } - if (vramTransfer.x == vramTransfer.origin_x + vramTransfer.w) { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void stepVramTransfer() { + if (++vramTransfer.x == vramTransfer.origin_x + vramTransfer.w) { vramTransfer.x -= vramTransfer.w; vramTransfer.y++; } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void drawVRAMPixel(ushort color1555) { + if (!checkMaskBeforeDraw || vram.GetPixelRGB888(vramTransfer.x, vramTransfer.y) >> 24 == 0) { + vram.SetPixel(vramTransfer.x & 0x3FF, vramTransfer.y & 0x1FF, color1555to8888LUT[color1555]); + vram1555.SetPixel(vramTransfer.x & 0x3FF, vramTransfer.y & 0x1FF, color1555); + } + + stepVramTransfer(); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DecodeGP0Command(uint value) { if (pointer == 0) { @@ -407,11 +422,18 @@ private void GP0_02_FillRectVRAM(Span<uint> buffer) { ushort w = (ushort)(((hw & 0x3FF) + 0xF) & ~0xF); ushort h = (ushort)((hw >> 16) & 0x1FF); - int color = (color0.r << 16 | color0.g << 8 | color0.b); + int color = color0.r << 16 | color0.g << 8 | color0.b; - for (int yPos = y; yPos < h + y; yPos++) { - for (int xPos = x; xPos < w + x; xPos++) { - vram.SetPixel(xPos & 0x3FF, yPos & 0x1FF, color); + if(x + w <= 0x3FF && y + h <= 0x1FF) { + var vramSpan = new Span<int>(vram.Bits); + for (int yPos = y; yPos < h + y; yPos++) { + vramSpan.Slice(x + (yPos * 1024), w).Fill(color); + } + } else { + for (int yPos = y; yPos < h + y; yPos++) { + for (int xPos = x; xPos < w + x; xPos++) { + vram.SetPixel(xPos & 0x3FF, yPos & 0x1FF, color); + } } } } @@ -441,11 +463,14 @@ public void GP0_RenderPolygon(Span<uint> buffer) { int vertexN = isQuad ? 4 : 3; Span<uint> c = stackalloc uint[vertexN]; + Span<Point2D> v = stackalloc Point2D[vertexN]; + Span<TextureData> t = stackalloc TextureData[vertexN]; if (!isShaded) { uint color = buffer[pointer++]; - c[0] = color; //triangle 1 opaque color - c[1] = color; //triangle 2 opaque color + uint rgbColor = (uint)GetRgbColor(color); + c[0] = rgbColor; //triangle 1 opaque color + c[1] = rgbColor; //triangle 2 opaque color } primitive.semiTransparencyMode = transparencyMode; @@ -453,9 +478,9 @@ public void GP0_RenderPolygon(Span<uint> buffer) { for (int i = 0; i < vertexN; i++) { if (isShaded) c[i] = buffer[pointer++]; - v[i].val = buffer[pointer++]; - v[i].x = (short)(signed11bit((uint)v[i].x) + drawingXOffset); - v[i].y = (short)(signed11bit((uint)v[i].y) + drawingYOffset); + uint xy = buffer[pointer++]; + v[i].x = (short)(signed11bit(xy & 0xFFFF) + drawingXOffset); + v[i].y = (short)(signed11bit(xy >> 16) + drawingYOffset); if (isTextured) { uint textureData = buffer[pointer++]; @@ -468,12 +493,13 @@ public void GP0_RenderPolygon(Span<uint> buffer) { } else if (i == 1) { uint texpage = textureData >> 16; - primitive.depth = (int)(texpage >> 7) & 0x3; - primitive.textureBase.x = (short)((texpage & 0xF) << 6); - primitive.textureBase.y = (short)(((texpage >> 4) & 0x1) << 8); - primitive.semiTransparencyMode = (int)((texpage >> 5) & 0x3); + //SET GLOBAL GPU E1 + GP0_E1_SetDrawMode(texpage); - forceSetE1(texpage); + primitive.depth = textureDepth; + primitive.textureBase.x = (short)(textureXBase << 6); + primitive.textureBase.y = (short)(textureYBase << 8); + primitive.semiTransparencyMode = transparencyMode; } } } @@ -506,8 +532,8 @@ private void rasterizeTri(Point2D v0, Point2D v1, Point2D v2, TextureData t0, Te /*clip*/ min.x = (short)Math.Max(minX, drawingAreaLeft); min.y = (short)Math.Max(minY, drawingAreaTop); - max.x = (short)Math.Min(maxX, drawingAreaRight); - max.y = (short)Math.Min(maxY, drawingAreaBottom); + max.x = (short)Math.Min(maxX, drawingAreaRight + 1); + max.y = (short)Math.Min(maxY, drawingAreaBottom + 1); int A01 = v0.y - v1.y, B01 = v1.x - v0.x; int A12 = v1.y - v2.y, B12 = v2.x - v1.x; @@ -517,11 +543,9 @@ private void rasterizeTri(Point2D v0, Point2D v1, Point2D v2, TextureData t0, Te int bias1 = isTopLeft(v2, v0) ? 0 : -1; int bias2 = isTopLeft(v0, v1) ? 0 : -1; - int w0_row = orient2d(v1, v2, min); - int w1_row = orient2d(v2, v0, min); - int w2_row = orient2d(v0, v1, min); - - int baseColor = GetRgbColor(c0); + int w0_row = orient2d(v1, v2, min) + bias0; + int w1_row = orient2d(v2, v0, min) + bias1; + int w2_row = orient2d(v0, v1, min) + bias2; // Rasterize for (int y = min.y; y < max.y; y++) { @@ -532,7 +556,7 @@ private void rasterizeTri(Point2D v0, Point2D v1, Point2D v2, TextureData t0, Te for (int x = min.x; x < max.x; x++) { // If p is on or inside all edges, render pixel. - if ((w0 + bias0 | w1 + bias1 | w2 + bias2) >= 0) { + if ((w0 | w1 | w2) >= 0) { //Adjustements per triangle instead of per pixel can be done at area level //but it still does some little by 1 error apreciable on some textured quads //I assume it could be handled recalculating AXX and BXX offsets but those maths are beyond my scope @@ -549,13 +573,22 @@ private void rasterizeTri(Point2D v0, Point2D v1, Point2D v2, TextureData t0, Te } // reset default color of the triangle calculated outside the for as it gets overwriten as follows... - int color = baseColor; + int color = (int)c0; - if (primitive.isShaded) color = getShadedColor(w0, w1, w2, c0, c1, c2, area); + if (primitive.isShaded) { + color0.val = c0; + color1.val = c1; + color2.val = c2; + + int r = interpolate(w0 - bias0, w1 - bias1, w2 - bias2, color0.r, color1.r, color2.r, area); + int g = interpolate(w0 - bias0, w1 - bias1, w2 - bias2, color0.g, color1.g, color2.g, area); + int b = interpolate(w0 - bias0, w1 - bias1, w2 - bias2, color0.b, color1.b, color2.b, area); + color = r << 16 | g << 8 | b; + } if (primitive.isTextured) { - int texelX = interpolateCoords(w0, w1, w2, t0.x, t1.x, t2.x, area); - int texelY = interpolateCoords(w0, w1, w2, t0.y, t1.y, t2.y, area); + int texelX = interpolate(w0 - bias0, w1 - bias1, w2 - bias2, t0.x, t1.x, t2.x, area); + int texelY = interpolate(w0 - bias0, w1 - bias1, w2 - bias2, t0.y, t1.y, t2.y, area); int texel = getTexel(maskTexelAxis(texelX, preMaskX, postMaskX), maskTexelAxis(texelY, preMaskY, postMaskY), primitive.clut, primitive.textureBase, primitive.depth); if (texel == 0) { w0 += A12; @@ -734,74 +767,61 @@ private void GP0_RenderRectangle(Span<uint> buffer) { short xo = (short)(vertex & 0xFFFF); short yo = (short)(vertex >> 16); - ushort palette = 0; - byte textureX = 0; - byte textureY = 0; if (isTextured) { uint texture = buffer[pointer++]; - palette = (ushort)((texture >> 16) & 0xFFFF); - textureX = (byte)(texture & 0xFF); - textureY = (byte)((texture >> 8) & 0xFF); - } - - short width = 0; - short heigth = 0; + textureData.val = (ushort)texture; - switch ((opcode & 0x18) >> 3) { - case 0x0: - uint hw = buffer[pointer++]; - width = (short)(hw & 0xFFFF); - heigth = (short)(hw >> 16); - break; - case 0x1: - width = 1; heigth = 1; - break; - case 0x2: - width = 8; heigth = 8; - break; - case 0x3: - width = 16; heigth = 16; - break; + ushort palette = (ushort)((texture >> 16) & 0xFFFF); + primitive.clut.x = (short)((palette & 0x3f) << 4); + primitive.clut.y = (short)((palette >> 6) & 0x1FF); } - int y = signed11bit((uint)(yo + drawingYOffset)); - int x = signed11bit((uint)(xo + drawingXOffset)); - - v[0].x = (short)x; - v[0].y = (short)y; + primitive.depth = textureDepth; + primitive.textureBase.x = (short)(textureXBase << 6); + primitive.textureBase.y = (short)(textureYBase << 8); + primitive.semiTransparencyMode = transparencyMode; - v[3].x = (short)(x + width); - v[3].y = (short)(y + heigth); + short width; + short heigth; - t[0].x = textureX; - t[0].y = textureY; + uint type = (opcode & 0x18) >> 3; - uint texpage = getTexpageFromGPU(); + if (type == 0) { + uint hw = buffer[pointer++]; + width = (short)(hw & 0xFFFF); + heigth = (short)(hw >> 16); + } else if (type == 1) { + width = 1; heigth = 1; + } else if (type == 2) { + width = 8; heigth = 8; + } else { + width = 16; heigth = 16; + } - rasterizeRect(v, t[0], color, palette, texpage, primitive); - } + short y = signed11bit((uint)(yo + drawingYOffset)); + short x = signed11bit((uint)(xo + drawingXOffset)); - private void rasterizeRect(Point2D[] vec, TextureData t, uint c, ushort palette, uint texpage, Primitive primitive) { - int xOrigin = Math.Max(vec[0].x, drawingAreaLeft); - int yOrigin = Math.Max(vec[0].y, drawingAreaTop); - int width = Math.Min(vec[3].x, drawingAreaRight); - int height = Math.Min(vec[3].y, drawingAreaBottom); + Point2D origin; + origin.x = x; + origin.y = y; - int depth = (int)(texpage >> 7) & 0x3; - int semiTransparencyMode = (int)((texpage >> 5) & 0x3); + Point2D size; + size.x = (short)(x + width); + size.y = (short)(y + heigth); - Point2D clut = new Point2D(); - clut.x = (short)((palette & 0x3f) << 4); - clut.y = (short)((palette >> 6) & 0x1FF); + rasterizeRect(origin, size, textureData, color, primitive); + } - Point2D textureBase = new Point2D(); - textureBase.x = (short)((texpage & 0xF) << 6); - textureBase.y = (short)(((texpage >> 4) & 0x1) << 8); + private void rasterizeRect(Point2D origin, Point2D size, TextureData texture, uint bgrColor, Primitive primitive) { + int xOrigin = Math.Max(origin.x, drawingAreaLeft); + int yOrigin = Math.Max(origin.y, drawingAreaTop); + int width = Math.Min(size.x, drawingAreaRight + 1); + int height = Math.Min(size.y, drawingAreaBottom + 1); - int uOrigin = t.x + (xOrigin - vec[0].x); - int vOrigin = t.y + (yOrigin - vec[0].y); + int uOrigin = texture.x + (xOrigin - origin.x); + int vOrigin = texture.y + (yOrigin - origin.y); - int baseColor = GetRgbColor(c); + int baseColor = GetRgbColor(bgrColor); for (int y = yOrigin, v = vOrigin; y < height; y++, v++) { for (int x = xOrigin, u = uOrigin; x < width; x++, u++) { @@ -815,7 +835,7 @@ private void rasterizeRect(Point2D[] vec, TextureData t, uint c, ushort palette, if (primitive.isTextured) { //int texel = getTexel(u, v, clut, textureBase, depth); - int texel = getTexel(maskTexelAxis(u, preMaskX, postMaskX), maskTexelAxis(v, preMaskY, postMaskY), clut, textureBase, depth); + int texel = getTexel(maskTexelAxis(u, preMaskX, postMaskX),maskTexelAxis(v, preMaskY, postMaskY),primitive.clut, primitive.textureBase, primitive.depth); if (texel == 0) { continue; } @@ -834,7 +854,7 @@ private void rasterizeRect(Point2D[] vec, TextureData t, uint c, ushort palette, } if (primitive.isSemiTransparent && (!primitive.isTextured || (color & 0xFF00_0000) != 0)) { - color = handleSemiTransp(x, y, color, semiTransparencyMode); + color = handleSemiTransp(x, y, color, primitive.semiTransparencyMode); } color |= maskWhileDrawing << 24; @@ -916,25 +936,9 @@ private void GP0_MemCopyRectVRAMtoCPU(Span<uint> buffer) { vramTransfer.origin_x = x; vramTransfer.origin_y = y; vramTransfer.halfWords = w * h; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int getShadedColor(int w0, int w1, int w2, uint c0, uint c1, uint c2, int area) { - color0.val = c0; - color1.val = c1; - color2.val = c2; - - int r = (color0.r * w0 + color1.r * w1 + color2.r * w2) / area; - int g = (color0.g * w0 + color1.g * w1 + color2.g * w2) / area; - int b = (color0.b * w0 + color1.b * w1 + color2.b * w2) / area; - - return (r << 16 | g << 8 | b); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int interpolateCoords(int w0, int w1, int w2, int t0, int t1, int t2, int area) { - //https://codeplea.com/triangular-interpolation - return (t0 * w0 + t1 * w1 + t2 * w2) / area; + isReadyToSendVRAMToCPU = true; + isReadyToReceiveDMABlock = false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -977,17 +981,13 @@ private static int orient2d(Point2D a, Point2D b, Point2D c) { return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x); } - private void forceSetE1(uint texpage) { - textureXBase = (byte)(texpage & 0xF); - textureYBase = (byte)((texpage >> 4) & 0x1); - transparencyMode = (byte)((texpage >> 5) & 0x3); - textureDepth = (byte)((texpage >> 7) & 0x3); - isTextureDisabled = isTextureDisabledAllowed && ((texpage >> 11) & 0x1) != 0; + private void GP0_E1_SetDrawMode(uint val) { + uint bits = val & 0xFF_FFFF; - //Console.WriteLine("[GPU] [GP0] Force DrawMode "); - } + if (bits == drawModeBits) return; + + drawModeBits = bits; - private void GP0_E1_SetDrawMode(uint val) { textureXBase = (byte)(val & 0xF); textureYBase = (byte)((val >> 4) & 0x1); transparencyMode = (byte)((val >> 5) & 0x3); @@ -998,7 +998,7 @@ private void GP0_E1_SetDrawMode(uint val) { isTexturedRectangleXFlipped = ((val >> 12) & 0x1) != 0; isTexturedRectangleYFlipped = ((val >> 13) & 0x1) != 0; - //Console.WriteLine("[GPU] [GP0] DrawMode "); + //Console.WriteLine("[GPU] [GP0] DrawMode " + val.ToString("x8")); } private void GP0_E2_SetTextureWindow(uint val) { @@ -1083,7 +1083,18 @@ private void GP1_00_ResetGPU() { private void GP1_03_DisplayEnable(uint value) => isDisplayDisabled = (value & 1) != 0; - private void GP1_04_DMADirection(uint value) => dmaDirection = (byte)(value & 0x3); + private void GP1_04_DMADirection(uint value) { + dmaDirection = (byte)(value & 0x3); + + isDmaRequest = dmaDirection switch { + 0 => false, + 1 => isReadyToReceiveDMABlock, + 2 => isReadyToReceiveDMABlock, + 3 => isReadyToSendVRAMToCPU, + _ => false, + }; + } + private void GP1_05_DisplayVRAMStart(uint value) { displayVRAMXStart = (ushort)(value & 0x3FE); @@ -1093,6 +1104,12 @@ private void GP1_05_DisplayVRAMStart(uint value) { } private void GP1_06_DisplayHorizontalRange(uint value) { + uint bits = value & 0xFF_FFFF; + + if (bits == displayHorizontalRange) return; + + displayHorizontalRange = bits; + displayX1 = (ushort)(value & 0xFFF); displayX2 = (ushort)((value >> 12) & 0xFFF); @@ -1100,6 +1117,12 @@ private void GP1_06_DisplayHorizontalRange(uint value) { } private void GP1_07_DisplayVerticalRange(uint value) { + uint bits = value & 0xFF_FFFF; + + if (bits == displayVerticalRange) return; + + displayVerticalRange = bits; + displayY1 = (ushort)(value & 0x3FF); displayY2 = (ushort)((value >> 10) & 0x3FF); @@ -1107,6 +1130,12 @@ private void GP1_07_DisplayVerticalRange(uint value) { } private void GP1_08_DisplayMode(uint value) { + uint bits = value & 0xFF_FFFF; + + if (bits == displayModeBits) return; + + displayModeBits = bits; + horizontalResolution1 = (byte)(value & 0x3); isVerticalResolution480 = (value & 0x4) != 0; isPal = (value & 0x8) != 0; @@ -1141,22 +1170,6 @@ private void GP1_GPUInfo(uint value) { } } - private uint getTexpageFromGPU() { - uint texpage = 0; - - texpage |= (isTexturedRectangleYFlipped ? 1u : 0) << 13; - texpage |= (isTexturedRectangleXFlipped ? 1u : 0) << 12; - texpage |= (isTextureDisabled ? 1u : 0) << 11; - texpage |= (isDrawingToDisplayAllowed ? 1u : 0) << 10; - texpage |= (isDithered ? 1u : 0) << 9; - texpage |= (uint)(textureDepth << 7); - texpage |= (uint)(transparencyMode << 5); - texpage |= (uint)(textureYBase << 4); - texpage |= textureXBase; - - return texpage; - } - private int handleSemiTransp(int x, int y, int color, int semiTranspMode) { color0.val = (uint)vram.GetPixelRGB888(x, y); //back color1.val = (uint)color; //front @@ -1219,19 +1232,14 @@ private int interpolate(uint c1, uint c2, float ratio) { } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static short signed11bit(uint n) { - return (short)(((int)n << 21) >> 21); + private static int interpolate(int w0, int w1, int w2, int t0, int t1, int t2, int area) { + //https://codeplea.com/triangular-interpolation + return (t0 * w0 + t1 * w1 + t2 * w2) / area; } - //This needs to go away once a BGR bitmap is achieved [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int color1555to8888(ushort val) { - byte m = (byte)(val >> 15); - byte r = (byte)((val & 0x1F) << 3); - byte g = (byte)(((val >> 5) & 0x1F) << 3); - byte b = (byte)(((val >> 10) & 0x1F) << 3); - - return (m << 24 | r << 16 | g << 8 | b); + private static short signed11bit(uint n) { + return (short)(((int)n << 21) >> 21); } //This is only needed for the Direct GP0 commands as the command number needs to be diff --git a/ProjectPSX/Devices/GPU/VRAM.cs b/ProjectPSX/Devices/GPU/VRAM.cs index 6c22e1a..5ec4ebb 100644 --- a/ProjectPSX/Devices/GPU/VRAM.cs +++ b/ProjectPSX/Devices/GPU/VRAM.cs @@ -3,15 +3,13 @@ namespace ProjectPSX { public class VRAM { - public int[] Bits { get; private set; } - public int Height; - public int Width; + public int[] Bits; + public const int Height = 512; + public const int Width = 1024; - protected GCHandle BitsHandle { get; private set; } + protected GCHandle BitsHandle; - public VRAM(int width, int height) { - Height = height; - Width = width; + public VRAM() { Bits = new int[Width * Height]; BitsHandle = GCHandle.Alloc(Bits, GCHandleType.Pinned); } @@ -19,19 +17,27 @@ public VRAM(int width, int height) { [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetPixel(int x, int y, int color) { int index = x + (y * Width); - Bits[index] = color; + + ref int r0 = ref MemoryMarshal.GetArrayDataReference(Bits); + Unsafe.Add(ref r0, (nint)index) = color; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int GetPixelRGB888(int x, int y) { + public ref int GetPixelRGB888(int x, int y) { int index = x + (y * Width); - return Bits[index]; + + ref int r0 = ref MemoryMarshal.GetArrayDataReference(Bits); + ref int ri = ref Unsafe.Add(ref r0, (nint)index); + + return ref ri; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public ushort GetPixelBGR555(int x, int y) { int index = x + (y * Width); - int color = Bits[index]; + + ref int r0 = ref MemoryMarshal.GetArrayDataReference(Bits); + ref int color = ref Unsafe.Add(ref r0, (nint)index); byte m = (byte)((color & 0xFF000000) >> 24); byte r = (byte)((color & 0x00FF0000) >> 16 + 3); diff --git a/ProjectPSX/Devices/GPU/VRAM1555.cs b/ProjectPSX/Devices/GPU/VRAM1555.cs index 4c28f2f..17f6c3e 100644 --- a/ProjectPSX/Devices/GPU/VRAM1555.cs +++ b/ProjectPSX/Devices/GPU/VRAM1555.cs @@ -3,15 +3,13 @@ namespace ProjectPSX { public class VRAM1555 { - public ushort[] Bits { get; private set; } - public int Height; - public int Width; + public ushort[] Bits; + public const int Height = 512; + public const int Width = 1024; - protected GCHandle BitsHandle { get; private set; } + protected GCHandle BitsHandle; - public VRAM1555(int width, int height) { - Height = height; - Width = width; + public VRAM1555() { Bits = new ushort[Width * Height]; BitsHandle = GCHandle.Alloc(Bits, GCHandleType.Pinned); } @@ -19,13 +17,19 @@ public VRAM1555(int width, int height) { [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetPixel(int x, int y, ushort color) { int index = x + (y * Width); - Bits[index] = color; + + ref ushort r0 = ref MemoryMarshal.GetArrayDataReference(Bits); + Unsafe.Add(ref r0, (nint)index) = color; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ushort GetPixel(int x, int y) { + public ref ushort GetPixel(int x, int y) { int index = x + (y * Width); - return Bits[index]; + + ref ushort r0 = ref MemoryMarshal.GetArrayDataReference(Bits); + ref ushort ri = ref Unsafe.Add(ref r0, (nint)index); + + return ref ri; } } diff --git a/ProjectPSX/Devices/Input/Controller.cs b/ProjectPSX/Devices/Input/Controller.cs index 8094873..4b979d0 100644 --- a/ProjectPSX/Devices/Input/Controller.cs +++ b/ProjectPSX/Devices/Input/Controller.cs @@ -3,7 +3,6 @@ namespace ProjectPSX { public abstract class Controller { - protected IHostWindow window; protected Queue<byte> transferDataFifo = new Queue<byte>(); protected ushort buttons = 0xFFFF; diff --git a/ProjectPSX/Devices/Input/DigitalController.cs b/ProjectPSX/Devices/Input/DigitalController.cs index 3387aad..a2182f2 100644 --- a/ProjectPSX/Devices/Input/DigitalController.cs +++ b/ProjectPSX/Devices/Input/DigitalController.cs @@ -1,5 +1,5 @@ namespace ProjectPSX { - public class DigitalController : Controller { + public sealed class DigitalController : Controller { private const ushort CONTROLLER_TYPE = 0x5A41; //digital diff --git a/ProjectPSX/Devices/MDEC.cs b/ProjectPSX/Devices/MDEC.cs index e684a6f..6fc74ee 100644 --- a/ProjectPSX/Devices/MDEC.cs +++ b/ProjectPSX/Devices/MDEC.cs @@ -13,8 +13,8 @@ public class MDEC { private const int MACRO_BLOCK_DECODED_BYTES = 256 * 3; //Status Register - //private bool isDataOutFifoEmpty; private bool isDataInFifoFull; + private bool isDataOutFifoEmpty; private bool isCommandBusy; private bool isDataInRequested; private bool isDataOutRequested; @@ -41,6 +41,8 @@ public class MDEC { private IMemoryOwner<byte> outBuffer = MemoryPool<byte>.Shared.Rent(0x30000); //wild guess while resumable dmas come... private int outBufferPos = 0; + private int pendingBytesToTransfer; + public void write(uint addr, uint value) { uint register = addr & 0xF; if (register == 0) { @@ -53,7 +55,8 @@ public void write(uint addr, uint value) { } public void writeMDEC0_Command(uint value) { //1F801820h - MDEC0 - MDEC Command/Parameter Register (W) - //Console.WriteLine("[MDEC] Write " + value.ToString("x8")); + isCommandBusy = true; + if (remainingDataWords == 0) { decodeCommand(value); } else { @@ -61,18 +64,15 @@ public void writeMDEC0_Command(uint value) { //1F801820h - MDEC0 - MDEC Command/ inBuffer.Enqueue((ushort)(value >> 16)); remainingDataWords--; - //Console.WriteLine("[MDEC] remaining " + remainingDataWords); if (command == decodeMacroBlocks) { command(); } } if (remainingDataWords == 0) { - isCommandBusy = true; - yuvToRgbBlockPos = 0; - outBufferPos = 0; if (command != decodeMacroBlocks) { command(); + isCommandBusy = false; } } @@ -107,17 +107,19 @@ private void decodeMacroBlocks() { currentBlock = 0; + blockPointer = 64; + q_scale = 0; + val = 0; + n = 0; + yuv_to_rgb(block[2], 0, 0); yuv_to_rgb(block[3], 8, 0); yuv_to_rgb(block[4], 0, 8); yuv_to_rgb(block[5], 8, 8); + isDataOutFifoEmpty = false; yuvToRgbBlockPos += MACRO_BLOCK_DECODED_BYTES; - - blockPointer = 64; - q_scale = 0; - val = 0; - n = 0; + pendingBytesToTransfer += MACRO_BLOCK_DECODED_BYTES; } } @@ -158,7 +160,7 @@ private void yuv_to_rgb(short[] Yblk, int xx, int yy) { private int val; private ushort n; public bool rl_decode_block(short[] blk, byte[] qt) { - if (blockPointer >= 64) { //Start of new block + if (blockPointer >= 63) { //Start of new block for (int i = 0; i < blk.Length; i++) { blk[i] = 0; } @@ -177,7 +179,7 @@ public bool rl_decode_block(short[] blk, byte[] qt) { } - while (blockPointer < blk.Length) { + while (blockPointer < 63) { if (q_scale == 0) { val = signed10bit(n & 0x3FF) * 2; } @@ -253,6 +255,11 @@ public void writeMDEC1_Control(uint value) { //1F801824h - MDEC1 - MDEC Control/ outBufferPos = 0; currentBlock = 0; remainingDataWords = 0; + pendingBytesToTransfer = 0; + yuvToRgbBlockPos = 0; + + inBuffer.Clear(); + //outBuffer.Memory.Span.Clear(); blockPointer = 64; q_scale = 0; @@ -262,20 +269,28 @@ public void writeMDEC1_Control(uint value) { //1F801824h - MDEC1 - MDEC Control/ command = null; } - isDataInRequested = ((value >> 30) & 0x1) == 1; //todo enable dma + isDataInRequested = ((value >> 30) & 0x1) == 1; isDataOutRequested = ((value >> 29) & 0x1) == 1; - - //Console.WriteLine("[MDEC] dataInRequest " + isDataInRequested + " dataOutRequested " + isDataOutRequested); } public uint readMDEC0_Data() { //1F801820h.Read - MDEC Data/Response Register (R) if (dataOutputDepth == 2) { //2 24b - var span = outBuffer.Memory.Span.Slice(outBufferPos++ * 4, 4); + int size = 4; + var span = outBuffer.Memory.Span.Slice(outBufferPos, size); + outBufferPos += size; + pendingBytesToTransfer -= size; + + handlePossibleDataOutEnd(); return Unsafe.ReadUnaligned<uint>(ref MemoryMarshal.GetReference(span)); } else if (dataOutputDepth == 3) { //3 15b - var span = outBuffer.Memory.Span.Slice(outBufferPos++ * 6, 6); + int size = 6; // 6 bytes for 2 packed 15b + var span = outBuffer.Memory.Span.Slice(outBufferPos, size); + outBufferPos += size; + pendingBytesToTransfer -= size; + + handlePossibleDataOutEnd(); ushort lo = (ushort)(bit15 << 15 | convert24to15bpp(span[0], span[1], span[2])); ushort hi = (ushort)(bit15 << 15 | convert24to15bpp(span[3], span[4], span[5])); @@ -287,13 +302,23 @@ public uint readMDEC0_Data() { //1F801820h.Read - MDEC Data/Response Register (R } - public Span<uint> processDmaLoad(int size) { + public Span<uint> processDmaLoad(int dmaSize) { if (dataOutputDepth == 2) { //2 24b - var byteSpan = outBuffer.Memory.Span.Slice(outBufferPos++ * 4 * size, 4 * size); //4 = RGBR, GBRG, BRGB... + int size = dmaSize * 4; // DMA Size in words to bytes extracting 4 bytes of packed RGB data + var byteSpan = outBuffer.Memory.Span.Slice(outBufferPos, size); //4 = RGBR, GBRG, BRGB... + outBufferPos += size; + pendingBytesToTransfer -= size; + + handlePossibleDataOutEnd(); return MemoryMarshal.Cast<byte, uint>(byteSpan); } else if (dataOutputDepth == 3) { //3 15b - var byteSpan = outBuffer.Memory.Span.Slice(outBufferPos++ * 6 * size, 6 * size); //6 = RGB * 2 => b15|b15 + int size = dmaSize * 6; // DMA Size in words to bytes as packed 15b data (RGB * 2 => b15|b15) + var byteSpan = outBuffer.Memory.Span.Slice(outBufferPos, size); + outBufferPos += size; + pendingBytesToTransfer -= size; + + handlePossibleDataOutEnd(); for (int b24 = 0, b15 = 0; b24 < byteSpan.Length; b24 += 3, b15 += 2) { var r = byteSpan[b24 + 0] >> 3; @@ -308,11 +333,11 @@ public Span<uint> processDmaLoad(int size) { byteSpan[b15 + 1] = hi; } - var dma = MemoryMarshal.Cast<byte, uint>(byteSpan.Slice(0, 4 * size)); + var dma = MemoryMarshal.Cast<byte, uint>(byteSpan.Slice(0, 4 * dmaSize)); return dma; } else { //unsupported mode allocate and return garbage so can be seen - uint[] garbage = new uint[size]; + uint[] garbage = new uint[dmaSize]; var dma = new Span<uint>(garbage); dma.Fill(0xFF00FF00); return dma; @@ -330,25 +355,29 @@ public ushort convert24to15bpp(byte sr, byte sg, byte sb) { public uint readMDEC1_Status() {//1F801824h - MDEC1 - MDEC Status Register (R) uint status = 0; - status |= (isDataOutFifoEmpty() ? 1u : 0) << 31; + status |= (isDataOutFifoEmpty ? 1u : 0) << 31; status |= (isDataInFifoFull ? 1u : 0) << 30; status |= (isCommandBusy ? 1u : 0) << 29; - status |= (isDataInRequested ? 1u : 0) << 28; - status |= (isDataOutRequested ? 1u : 0) << 27; + status |= (isDataInRequested ? 1u : 0) << 28; // this should be && "enough space in the inQueue" + status |= ((isDataOutRequested && pendingBytesToTransfer != 0) ? 1u : 0) << 27; status |= dataOutputDepth << 25; status |= (isSigned ? 1u : 0) << 24; status |= bit15 << 23; status |= (currentBlock + 4) % NUM_BLOCKS << 16; status |= (ushort)(remainingDataWords - 1); //Console.WriteLine("[MDEC] Load Status " + status.ToString("x8")); - //Console.ReadLine(); - - isCommandBusy = false; return status; } - private bool isDataOutFifoEmpty() => outBufferPos == 0; //Todo compare to inbufferpos when we handle full dma in + private void handlePossibleDataOutEnd() { + if (pendingBytesToTransfer <= 0 && remainingDataWords == 0) { + outBufferPos = 0; + yuvToRgbBlockPos = 0; + isCommandBusy = false; + isDataOutFifoEmpty = true; + } + } private static ReadOnlySpan<byte> zagzig => new byte[] { 0, 1, 8, 16, 9, 2, 3, 10, diff --git a/ProjectPSX/Devices/SPU/SPU.cs b/ProjectPSX/Devices/SPU/SPU.cs index e26337e..2023912 100644 --- a/ProjectPSX/Devices/SPU/SPU.cs +++ b/ProjectPSX/Devices/SPU/SPU.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using ProjectPSX.Devices.CdRom; using ProjectPSX.Devices.Spu; @@ -8,11 +8,8 @@ namespace ProjectPSX.Devices { public class SPU { // Todo: - // Spu Enable/Disable (koff voices? Ints?) // lr sweep envelope - // reverb // clean up queue/list dequeues enqueues and casts - // ... private static short[] gaussTable = new short[] { -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, @@ -86,13 +83,13 @@ public class SPU { private Sector cdBuffer = new Sector(Sector.XA_BUFFER); - private byte[] ram = new byte[512 * 1024]; + private unsafe byte* ram = (byte*)Marshal.AllocHGlobal(512 * 1024); private Voice[] voices = new Voice[24]; - private ushort mainVolumeLeft; - private ushort mainVolumeRight; - private ushort reverbOutputLeft; - private ushort reverbOutputRight; + private short mainVolumeLeft; + private short mainVolumeRight; + private short reverbOutputVolumeLeft; + private short reverbOutputVolumeRight; private uint keyOn; private uint keyOff; @@ -103,7 +100,8 @@ public class SPU { private ushort unknownA0; - private ushort ramReverbStartAddress; + private uint ramReverbStartAddress; + private uint ramReverbInternalAddress; private ushort ramIrqAddress; private ushort ramDataTransferAddress; private uint ramDataTransferAddressInternal; @@ -122,6 +120,40 @@ public class SPU { private int captureBufferPos; + //Reverb Area + private uint dAPF1; // Reverb APF Offset 1 + private uint dAPF2; // Reverb APF Offset 2 + private short vIIR; // Reverb Reflection Volume 1 + private short vCOMB1; // Reverb Comb Volume 1 + private short vCOMB2; // Reverb Comb Volume 2 + private short vCOMB3; // Reverb Comb Volume 3 + private short vCOMB4; // Reverb Comb Volume 4 + private short vWALL; // Reverb Reflection Volume 2 + private short vAPF1; // Reverb APF Volume 1 + private short vAPF2; // Reverb APF Volume 2 + private uint mLSAME; // Reverb Same Side Reflection Address 1 Left + private uint mRSAME; // Reverb Same Side Reflection Address 1 Right + private uint mLCOMB1; // Reverb Comb Address 1 Left + private uint mLCOMB2; // Reverb Comb Address 2 Left + private uint mRCOMB1; // Reverb Comb Address 1 Right + private uint mRCOMB2; // Reverb Comb Address 2 Right + private uint dLSAME; // Reverb Same Side Reflection Address 2 Left + private uint dRSAME; // Reverb Same Side Reflection Address 2 Right + private uint mLDIFF; // Reverb Different Side Reflection Address 1 Left + private uint mRDIFF; // Reverb Different Side Reflection Address 1 Right + private uint mLCOMB3; // Reverb Comb Address 3 Left + private uint mRCOMB3; // Reverb Comb Address 3 Right + private uint mLCOMB4; // Reverb Comb Address 4 Left + private uint mRCOMB4; // Reverb Comb Address 4 Right + private uint dLDIFF; // Reverb Different Side Reflection Address 2 Left + private uint dRDIFF; // Reverb Different Side Reflection Address 2 Right + private uint mLAPF1; // Reverb APF Address 1 Left + private uint mRAPF1; // Reverb APF Address 1 Right + private uint mLAPF2; // Reverb APF Address 2 Left + private uint mRAPF2; // Reverb APF Address 2 Right + private short vLIN; // Reverb Input Volume Left + private short vRIN; // Reverb Input Volume Right + private struct Control { public ushort register; public bool spuEnabled => ((register >> 15) & 0x1) != 0; @@ -144,11 +176,12 @@ private struct Status { public bool dataTransferBusyFlag => ((register >> 10) & 0x1) != 0; public bool dataTransferDmaReadRequest => ((register >> 9) & 0x1) != 0; public bool dataTransferDmaWriteRequest => ((register >> 8) & 0x1) != 0; - // 7 Data Transfer DMA Read/Write Request ;seems to be same as SPUCNT.Bit5 todo + // 7 Data Transfer DMA Read/Write Request ;seems to be same as SPUCNT.Bit5 public bool irq9Flag { get { return ((register >> 6) & 0x1) != 0; } set { register = value ? (ushort)(register | (1 << 6)) : (ushort)(register & ~(1 << 6)); } } + // 0..5 Mode same as SPUCNT 0..5 } Status status; @@ -183,19 +216,19 @@ internal void write(uint addr, ushort value) { break; case 0x1F801D80: - mainVolumeLeft = value; + mainVolumeLeft = (short)value; break; case 0x1F801D82: - mainVolumeRight = value; + mainVolumeRight = (short)value; break; case 0x1F801D84: - reverbOutputLeft = value; + reverbOutputVolumeLeft = (short)value; break; case 0x1F801D86: - reverbOutputRight = value; + reverbOutputVolumeRight = (short)value; break; case 0x1F801D88: @@ -252,7 +285,8 @@ internal void write(uint addr, ushort value) { break; case 0x1F801DA2: - ramReverbStartAddress = value; + ramReverbStartAddress = (uint)(value << 3); + ramReverbInternalAddress = (uint)(value << 3); break; case 0x1F801DA4: @@ -267,20 +301,27 @@ internal void write(uint addr, ushort value) { case 0x1F801DA8: //Console.WriteLine($"[SPU] Manual DMA Write {ramDataTransferAddressInternal:x8} {value:x4}"); ramDataTransferFifo = value; - ram[ramDataTransferAddressInternal++] = (byte)value; - ram[ramDataTransferAddressInternal++] = (byte)(value >> 8); + writeRam(ramDataTransferAddressInternal, value); + ramDataTransferAddressInternal += 2; break; case 0x1F801DAA: control.register = value; + if (!control.spuEnabled) { + foreach (Voice v in voices) { + v.adsrPhase = Voice.Phase.Off; + v.adsrVolume = 0; + } + } + //Irq Flag is reseted on ack if (!control.irq9Enabled) status.irq9Flag = false; - //Status lower 5 bits are the same as control - status.register &= 0xFFE0; - status.register |= (ushort)(value & 0x1F); + //Status 0..5 bits are the same as control + status.register &= 0xFFC0; + status.register |= (ushort)(value & 0x3F); break; case 0x1F801DAC: @@ -322,6 +363,140 @@ internal void write(uint addr, ushort value) { case 0x1F801DBE: unknownBC = (unknownBC & 0xFFFF) | (uint)(value << 16); break; + + // SPU Reverb Configuration Area + case 0x1F801DC0: + dAPF1 = (uint)(value << 3); + break; + + case 0x1F801DC2: + dAPF2 = (uint)(value << 3); + break; + + case 0x1F801DC4: + vIIR = (short)value; + break; + + case 0x1F801DC6: + vCOMB1 = (short)value; + break; + + case 0x1F801DC8: + vCOMB2 = (short)value; + break; + + case 0x1F801DCA: + vCOMB3 = (short)value; + break; + + case 0x1F801DCC: + vCOMB4 = (short)value; + break; + + case 0x1F801DCE: + vWALL = (short)value; + break; + + case 0x1F801DD0: + vAPF1 = (short)value; + break; + + case 0x1F801DD2: + vAPF2 = (short)value; + break; + + case 0x1F801DD4: + mLSAME = (uint)(value << 3); + break; + + case 0x1F801DD6: + mRSAME = (uint)(value << 3); + break; + + case 0x1F801DD8: + mLCOMB1 = (uint)(value << 3); + break; + + case 0x1F801DDA: + mRCOMB1 = (uint)(value << 3); + break; + + case 0x1F801DDC: + mLCOMB2 = (uint)(value << 3); + break; + + case 0x1F801DDE: + mRCOMB2 = (uint)(value << 3); + break; + + case 0x1F801DE0: + dLSAME = (uint)(value << 3); + break; + + case 0x1F801DE2: + dRSAME = (uint)(value << 3); + break; + + case 0x1F801DE4: + mLDIFF = (uint)(value << 3); + break; + + case 0x1F801DE6: + mRDIFF = (uint)(value << 3); + break; + + case 0x1F801DE8: + mLCOMB3 = (uint)(value << 3); + break; + + case 0x1F801DEA: + mRCOMB3 = (uint)(value << 3); + break; + + case 0x1F801DEC: + mLCOMB4 = (uint)(value << 3); + break; + + case 0x1F801DEE: + mRCOMB4 = (uint)(value << 3); + break; + + case 0x1F801DF0: + dLDIFF = (uint)(value << 3); + break; + + case 0x1F801DF2: + dRDIFF = (uint)(value << 3); + break; + + case 0x1F801DF4: + mLAPF1 = (uint)(value << 3); + break; + + case 0x1F801DF6: + mRAPF1 = (uint)(value << 3); + break; + + case 0x1F801DF8: + mLAPF2 = (uint)(value << 3); + break; + + case 0x1F801DFA: + mRAPF2 = (uint)(value << 3); + break; + + case 0x1F801DFC: + vLIN = (short)value; + break; + + case 0x1F801DFE: + vRIN = (short)value; + break; + + default: + Console.WriteLine($"[SPU] Warning write:{addr:x8} value:{value:x8}"); + writeRam(addr, value); + break; } } @@ -332,29 +507,29 @@ internal ushort load(uint addr) { uint index = ((addr & 0xFF0) >> 4) - 0xC0; - switch (addr & 0xF) { - case 0x0: return voices[index].volumeLeft.register; - case 0x2: return voices[index].volumeRight.register; - case 0x4: return voices[index].pitch; - case 0x6: return voices[index].startAddress; - case 0x8: return voices[index].adsr.lo; - case 0xA: return voices[index].adsr.hi; - case 0xC: return voices[index].adsrVolume; - case 0xE: return voices[index].adpcmRepeatAddress; - } - return 0xFFFF; + return (addr & 0xF) switch { + 0x0 => voices[index].volumeLeft.register, + 0x2 => voices[index].volumeRight.register, + 0x4 => voices[index].pitch, + 0x6 => voices[index].startAddress, + 0x8 => voices[index].adsr.lo, + 0xA => voices[index].adsr.hi, + 0xC => voices[index].adsrVolume, + 0xE => voices[index].adpcmRepeatAddress, + _ => 0xFFFF, + }; case 0x1F801D80: - return mainVolumeLeft; + return (ushort)mainVolumeLeft; case 0x1F801D82: - return mainVolumeRight; + return (ushort)mainVolumeRight; case 0x1F801D84: - return reverbOutputLeft; + return (ushort)reverbOutputVolumeLeft; case 0x1F801D86: - return reverbOutputRight; + return (ushort)reverbOutputVolumeRight; case 0x1F801D88: return (ushort)keyOn; @@ -396,7 +571,7 @@ internal ushort load(uint addr) { return unknownA0; case 0x1F801DA2: - return ramReverbStartAddress; + return (ushort)(ramReverbStartAddress >> 3); case 0x1F801DA4: return ramIrqAddress; @@ -440,8 +615,105 @@ internal ushort load(uint addr) { case 0x1F801DBE: return (ushort)(unknownBC >> 16); + // SPU Reverb Configuration Area + case 0x1F801DC0: + return (ushort)(dAPF1 >> 3); + + case 0x1F801DC2: + return (ushort)(dAPF2 >> 3); + + case 0x1F801DC4: + return (ushort)vIIR; + + case 0x1F801DC6: + return (ushort)vCOMB1; + + case 0x1F801DC8: + return (ushort)vCOMB2; + + case 0x1F801DCA: + return (ushort)vCOMB3; + + case 0x1F801DCC: + return (ushort)vCOMB4; + + case 0x1F801DCE: + return (ushort)vWALL; + + case 0x1F801DD0: + return (ushort)vAPF1; + + case 0x1F801DD2: + return (ushort)vAPF2; + + case 0x1F801DD4: + return (ushort)(mLSAME >> 3); + + case 0x1F801DD6: + return (ushort)(mRSAME >> 3); + + case 0x1F801DD8: + return (ushort)(mLCOMB1 >> 3); + + case 0x1F801DDA: + return (ushort)(mRCOMB1 >> 3); + + case 0x1F801DDC: + return (ushort)(mLCOMB2 >> 3); + + case 0x1F801DDE: + return (ushort)(mRCOMB2 >> 3); + + case 0x1F801DE0: + return (ushort)(dLSAME >> 3); + + case 0x1F801DE2: + return (ushort)(dRSAME >> 3); + + case 0x1F801DE4: + return (ushort)(mLDIFF >> 3); + + case 0x1F801DE6: + return (ushort)(mRDIFF >> 3); + + case 0x1F801DE8: + return (ushort)(mLCOMB3 >> 3); + + case 0x1F801DEA: + return (ushort)(mRCOMB3 >> 3); + + case 0x1F801DEC: + return (ushort)(mLCOMB4 >> 3); + + case 0x1F801DEE: + return (ushort)(mRCOMB4 >> 3); + + case 0x1F801DF0: + return (ushort)(dLDIFF >> 3); + + case 0x1F801DF2: + return (ushort)(dRDIFF >> 3); + + case 0x1F801DF4: + return (ushort)(mLAPF1 >> 3); + + case 0x1F801DF6: + return (ushort)(mRAPF1 >> 3); + + case 0x1F801DF8: + return (ushort)(mLAPF2 >> 3); + + case 0x1F801DFA: + return (ushort)(mRAPF2 >> 3); + + case 0x1F801DFC: + return (ushort)vLIN; + + case 0x1F801DFE: + return (ushort)vRIN; + default: - return 0xFFFF; + return (ushort)loadRam(addr); } } @@ -450,7 +722,8 @@ internal void pushCdBufferSamples(byte[] decodedXaAdpcm) { } private int counter = 0; - private int CYCLES_PER_SAMPLE = 0x300; //33868800 / 44100hz + private const int CYCLES_PER_SAMPLE = 0x300; //33868800 / 44100hz + private int reverbCounter = 0; public bool tick(int cycles) { bool edgeTrigger = false; counter += cycles; @@ -463,6 +736,9 @@ public bool tick(int cycles) { int sumLeft = 0; int sumRight = 0; + int sumLeftReverb = 0; + int sumRightReverb = 0; + uint edgeKeyOn = keyOn; uint edgeKeyOff = keyOff; keyOn = 0; @@ -509,6 +785,11 @@ public bool tick(int cycles) { //Sum each voice sample sumLeft += (sample * v.processVolume(v.volumeLeft)) >> 15; sumRight += (sample * v.processVolume(v.volumeRight)) >> 15; + + if((channelReverbMode & (0x1 << i)) != 0) { + sumLeftReverb += (sample * v.processVolume(v.volumeLeft)) >> 15; + sumRightReverb += (sample * v.processVolume(v.volumeRight)) >> 15; + } } if (!control.spuUnmuted) { //todo merge this on the for voice loop @@ -520,19 +801,38 @@ public bool tick(int cycles) { //Merge in CD audio (CDDA or XA) short cdL = 0; short cdR = 0; - if(control.cdAudioEnabled && cdBuffer.hasSamples()) { //Be sure theres something on the queue... - //todo refactor the byte/short queues and casts + + // CD Audio samples are always "consumed" even if cdAudio is disabled and will end + // on the capture buffer area of ram, this is needed for VibRibbon to be playable + if(cdBuffer.hasSamples()) { cdL = cdBuffer.readShort(); cdR = cdBuffer.readShort(); + } + if(control.cdAudioEnabled) { //Be sure theres something on the queue... //Apply Spu Cd In (CDDA/XA) Volume cdL = (short)((cdL * cdVolumeLeft) >> 15); cdR = (short)((cdR * cdVolumeRight) >> 15); sumLeft += cdL; sumRight += cdR; + + if(control.cdAudioReverb) { + sumLeftReverb += cdL; + sumRightReverb += cdR; + } + } + + if (reverbCounter == 0) { + var (reverbL, reverbR) = processReverb(sumLeftReverb, sumRightReverb); + + sumLeft += reverbL; + sumRight += reverbR; } + // reverb is on a 22050hz clock + reverbCounter = (reverbCounter + 1) & 0x1; + //Write to capture buffers and check ram irq edgeTrigger |= handleCaptureBuffer(0 * 1024 + captureBufferPos, cdL); edgeTrigger |= handleCaptureBuffer(1 * 1024 + captureBufferPos, cdR); @@ -541,8 +841,8 @@ public bool tick(int cycles) { captureBufferPos = (captureBufferPos + 2) & 0x3FF; //Clamp sum - sumLeft = (Math.Clamp(sumLeft, -0x8000, 0x7FFF) * mainVolumeLeft) >> 15; - sumRight = (Math.Clamp(sumRight, -0x8000, 0x7FFF) * mainVolumeRight) >> 15; + sumLeft = (Math.Clamp(sumLeft, -0x8000, 0x7FFF) * (mainVolumeLeft << 1)) >> 15; + sumRight = (Math.Clamp(sumRight, -0x8000, 0x7FFF) * (mainVolumeRight << 1)) >> 15; //Add to samples bytes to output array spuOutput[spuOutputPointer++] = (byte)sumLeft; @@ -555,16 +855,14 @@ public bool tick(int cycles) { spuOutputPointer = 0; } - if (control.spuEnabled && control.irq9Enabled && edgeTrigger) { + if (control.irq9Enabled && edgeTrigger) { status.irq9Flag = true; } - return control.spuEnabled && control.irq9Enabled && edgeTrigger; //todo move spuEnabled outside + return control.irq9Enabled && edgeTrigger; } private bool handleCaptureBuffer(int address, short sample) { - ram[address] = (byte)(sample & 0xFF); - ram[address + 1] = (byte)((sample >> 8) & 0xFF); - + writeRam((uint)address, sample); return address >> 3 == ramIrqAddress; } @@ -587,7 +885,7 @@ private void tickNoiseGenerator() { if (noiseTimer < 0) noiseTimer += 0x20000 >> noiseShift; } - public short sampleVoice(int v) { + public unsafe short sampleVoice(int v) { Voice voice = voices[v]; //Decode samples if its empty / next block @@ -651,40 +949,114 @@ public short sampleVoice(int v) { return (short)interpolated; } + public (short, short) processReverb(int lInput, int rInput) { + // Input from mixer + int Lin = (vLIN * lInput) >> 15; + int Rin = (vRIN * rInput) >> 15; + + // Same side reflection LtoL and RtoR + short mlSame = saturateSample(Lin + ((loadReverb(dLSAME) * vWALL) >> 15) - ((loadReverb(mLSAME - 2) * vIIR) >> 15) + loadReverb(mLSAME - 2)); + short mrSame = saturateSample(Rin + ((loadReverb(dRSAME) * vWALL) >> 15) - ((loadReverb(mRSAME - 2) * vIIR) >> 15) + loadReverb(mRSAME - 2)); + writeReverb(mLSAME, mlSame); + writeReverb(mRSAME, mrSame); + + // Different side reflection LtoR and RtoL + short mldiff = saturateSample(Lin + ((loadReverb(dRDIFF) * vWALL) >> 15) - ((loadReverb(mLDIFF - 2) * vIIR) >> 15) + loadReverb(mLDIFF - 2)); + short mrdiff = saturateSample(Rin + ((loadReverb(dLDIFF) * vWALL) >> 15) - ((loadReverb(mRDIFF - 2) * vIIR) >> 15) + loadReverb(mRDIFF - 2)); + writeReverb(mLDIFF, mldiff); + writeReverb(mRDIFF, mrdiff); + + // Early echo (comb filter with input from buffer) + short l = saturateSample((vCOMB1 * loadReverb(mLCOMB1) >> 15) + (vCOMB2 * loadReverb(mLCOMB2) >> 15) + (vCOMB3 * loadReverb(mLCOMB3) >> 15) + (vCOMB4 * loadReverb(mLCOMB4) >> 15)); + short r = saturateSample((vCOMB1 * loadReverb(mRCOMB1) >> 15) + (vCOMB2 * loadReverb(mRCOMB2) >> 15) + (vCOMB3 * loadReverb(mRCOMB3) >> 15) + (vCOMB4 * loadReverb(mRCOMB4) >> 15)); + + // Late reverb APF1 (All pass filter 1 with input from COMB) + l = saturateSample(l - saturateSample((vAPF1 * loadReverb(mLAPF1 - dAPF1)) >> 15)); + r = saturateSample(r - saturateSample((vAPF1 * loadReverb(mRAPF1 - dAPF1)) >> 15)); + + writeReverb(mLAPF1, l); + writeReverb(mRAPF1, r); + + l = saturateSample((l * vAPF1 >> 15) + loadReverb(mLAPF1 - dAPF1)); + r = saturateSample((r * vAPF1 >> 15) + loadReverb(mRAPF1 - dAPF1)); + + // Late reverb APF2 (All pass filter 2 with input from APF1) + l = saturateSample(l - saturateSample((vAPF2 * loadReverb(mLAPF2 - dAPF2)) >> 15)); + r = saturateSample(r - saturateSample((vAPF2 * loadReverb(mRAPF2 - dAPF2)) >> 15)); + + writeReverb(mLAPF2, l); + writeReverb(mRAPF2, r); + + l = saturateSample((l * vAPF2 >> 15) + loadReverb(mLAPF2 - dAPF2)); + r = saturateSample((r * vAPF2 >> 15) + loadReverb(mRAPF2 - dAPF2)); + + // Output to mixer (output volume multiplied with input from APF2) + l = saturateSample(l * reverbOutputVolumeLeft >> 15); + r = saturateSample(r * reverbOutputVolumeRight >> 15); + + // Saturate address + ramReverbInternalAddress = Math.Max(ramReverbStartAddress, (ramReverbInternalAddress + 2) & 0x7_FFFE); + + return (l, r); + } + + public short saturateSample(int sample) { + if(sample < -0x8000) { + return -0x8000; + } + + if(sample > 0x7FFF) { + return 0x7FFF; + } + + return (short)sample; + } - public Span<uint> processDmaLoad(int size) { //todo trigger interrupt - Span<byte> dma = ram.AsSpan().Slice((int)ramDataTransferAddressInternal, size); - //ramDataTransferAddressInternal and ramIrqAddress already are >> 3 + public unsafe Span<uint> processDmaLoad(int size) { //todo trigger interrupt + Span<byte> dma = new Span<byte>(ram, 1024*512).Slice((int)ramDataTransferAddressInternal, size * 4); + + //ramDataTransferAddressInternal already is >> 3 while ramIrqAddress is set as ushort //so check if it's in the size range and trigger int - if (ramIrqAddress > ramDataTransferAddressInternal && ramIrqAddress < ramDataTransferAddressInternal + size) { - interruptController.set(Interrupt.SPU); + uint ramIrqAddress32 = (uint)ramIrqAddress << 3; + if (ramIrqAddress32 > ramDataTransferAddressInternal && ramIrqAddress32 < (ramDataTransferAddressInternal + (size * 4))) { + if(control.irq9Enabled) { + status.irq9Flag = true; + interruptController.set(Interrupt.SPU); + } } - ramDataTransferAddressInternal = (uint)(ramDataTransferAddressInternal + size * 4); + ramDataTransferAddressInternal += (uint)(size * 4); + ramDataTransferAddressInternal &= 0x7FFFF; return MemoryMarshal.Cast<byte, uint>(dma); } - public void processDmaWrite(Span<uint> dma) { //todo trigger interrupt + public unsafe void processDmaWrite(Span<uint> dma) { //todo trigger interrupt //Tekken 3 and FF8 overflows SPU Ram int size = dma.Length * 4; int destAddress = (int)ramDataTransferAddressInternal + size - 1; Span<byte> dmaSpan = MemoryMarshal.Cast<uint, byte>(dma); - Span<byte> ramStartSpan = ram.AsSpan(); + Span<byte> ramStartSpan = new Span<byte>(ram, 1024 * 512); Span<byte> ramDestSpan = ramStartSpan.Slice((int)ramDataTransferAddressInternal); - if (ramIrqAddress > ramDataTransferAddressInternal && ramIrqAddress < ramDataTransferAddressInternal + size) { - interruptController.set(Interrupt.SPU); + //ramDataTransferAddressInternal already is >> 3 while ramIrqAddress is set as ushort + //so check if it's in the size range and trigger int + uint ramIrqAddress32 = (uint)ramIrqAddress << 3; + if (ramIrqAddress32 > ramDataTransferAddressInternal && ramIrqAddress32 < (ramDataTransferAddressInternal + size)) { + if (control.irq9Enabled) { + status.irq9Flag = true; + interruptController.set(Interrupt.SPU); + } } if (destAddress <= 0x7FFFF) { dmaSpan.CopyTo(ramDestSpan); } else { int overflow = destAddress - 0x7FFFF; - + Span<byte> firstSlice = dmaSpan.Slice(0, dmaSpan.Length - overflow); Span<byte> overflowSpan = dmaSpan.Slice(dmaSpan.Length - overflow); @@ -692,7 +1064,33 @@ public void processDmaWrite(Span<uint> dma) { //todo trigger interrupt overflowSpan.CopyTo(ramStartSpan); } - ramDataTransferAddressInternal = (uint)(ramDataTransferAddressInternal + size); + ramDataTransferAddressInternal = (uint)((ramDataTransferAddressInternal + size) & 0x7FFFF); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe void writeRam<T>(uint addr, T value) where T : unmanaged { + *(T*)(ram + addr) = value; + } + + private unsafe short loadRam(uint addr) { + //Console.WriteLine($"loadSPURam from addr {(addr & 0x3_FFFF):x8}"); + return *(short*)(ram + (addr & 0x7_FFFF)); + } + + private unsafe void writeReverb(uint addr, short value) { + if (!control.reverbMasterEnabled) return; + + uint relative = (addr + ramReverbInternalAddress - ramReverbStartAddress) % (0x8_0000 - ramReverbStartAddress); + uint wrapped = (ramReverbStartAddress + relative) & 0x7_FFFE; + + *(short*)(ram + wrapped) = value; + } + + private unsafe short loadReverb(uint addr) { + uint relative = (addr + ramReverbInternalAddress - ramReverbStartAddress) % (0x8_0000 - ramReverbStartAddress); + uint wrapped = (ramReverbStartAddress + relative) & 0x7_FFFE; + + return *(short*)(ram + wrapped); } } diff --git a/ProjectPSX/Devices/SPU/Voice.cs b/ProjectPSX/Devices/SPU/Voice.cs index 7d5107d..e07bef2 100644 --- a/ProjectPSX/Devices/SPU/Voice.cs +++ b/ProjectPSX/Devices/SPU/Voice.cs @@ -98,19 +98,22 @@ public enum Phase { public byte[] spuAdpcm = new byte[16]; public short[] decodedSamples = new short[31]; //28 samples from current block + 3 to make room for interpolation - internal void decodeSamples(byte[] ram, ushort ramIrqAddress) { + internal unsafe void decodeSamples(byte* ram, ushort ramIrqAddress) { //save the last 3 samples from the last decoded block //this are needed for interpolation in case the voice.counter.currentSampleIndex is 0 1 or 2 decodedSamples[2] = decodedSamples[decodedSamples.Length - 1]; decodedSamples[1] = decodedSamples[decodedSamples.Length - 2]; decodedSamples[0] = decodedSamples[decodedSamples.Length - 3]; - Array.Copy(ram, currentAddress * 8, spuAdpcm, 0, 16); + new Span<byte>(ram, 1024 * 512).Slice(currentAddress * 8, 16).CopyTo(spuAdpcm); //ramIrqAddress is >> 8 so we only need to check for currentAddress and + 1 - readRamIrq |= currentAddress == ramIrqAddress || currentAddress + 1 == ramIrqAddress; + readRamIrq |= currentAddress == ramIrqAddress || currentAddress + 1 == ramIrqAddress; + + int headerShift = spuAdpcm[0] & 0x0F; + if (headerShift > 12) headerShift = 9; + int shift = 12 - headerShift; - int shift = 12 - (spuAdpcm[0] & 0x0F); int filter = (spuAdpcm[0] & 0x70) >> 4; //filter on SPU adpcm is 0-4 vs XA wich is 0-3 if (filter > 4) filter = 4; //Crash Bandicoot sets this to 7 at the end of the first level and overflows the filter @@ -144,7 +147,7 @@ internal short processVolume(Volume volume) { if (!volume.isSweepMode) { return volume.fixedVolume; } else { - return 0; //todo handle sweep mode volume envelope + return 0x7FFF; //todo handle sweep mode volume envelope } } diff --git a/ProjectPSX/Devices/TIMERS.cs b/ProjectPSX/Devices/TIMERS.cs index dd7d583..9d4b96f 100644 --- a/ProjectPSX/Devices/TIMERS.cs +++ b/ProjectPSX/Devices/TIMERS.cs @@ -137,8 +137,7 @@ public bool tick(int cyclesTicked) { //todo this needs rework counterValue += (ushort)cycles; cycles = 0; } else { - counterValue += (ushort)(cycles / 2160); - cycles %= 2160; + if (!prevHblank && hblank) counterValue += 1; } return handleIrq(); diff --git a/ProjectPSX/ProjectPSX.csproj b/ProjectPSX/ProjectPSX.csproj index ad133fa..c7918ea 100644 --- a/ProjectPSX/ProjectPSX.csproj +++ b/ProjectPSX/ProjectPSX.csproj @@ -2,7 +2,7 @@ <PropertyGroup> <OutputType>Library</OutputType> - <TargetFramework>net5.0</TargetFramework> + <TargetFramework>net9.0</TargetFramework> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> @@ -10,7 +10,7 @@ </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> - <DefineConstants>TRACE</DefineConstants> + <DefineConstants>TRACE</DefineConstants> <AllowUnsafeBlocks>true</AllowUnsafeBlocks> </PropertyGroup> diff --git a/ProjectPSX/Util/Extension.cs b/ProjectPSX/Util/Extension.cs index 723feca..8008b4c 100644 --- a/ProjectPSX/Util/Extension.cs +++ b/ProjectPSX/Util/Extension.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using static ProjectPSX.Devices.CDROM; namespace ProjectPSX.Devices { static class Extension { @@ -7,5 +8,9 @@ public static void EnqueueRange<T>(this Queue<T> queue, Span<T> parameters) { foreach (T parameter in parameters) queue.Enqueue(parameter); } + + public static void EnqueueDelayedInterrupt(this Queue<DelayedInterrupt> queue, byte interrupt, int delay = 50_000) { + queue.Enqueue(new DelayedInterrupt(delay, interrupt)); + } } }