From cf73207c98f0a261b34bc79538e77afe55de5912 Mon Sep 17 00:00:00 2001 From: Bart Trzynadlowski Date: Mon, 12 Sep 2011 05:43:37 +0000 Subject: [PATCH] - Nik's new decoupled audio code with fixes to minimize overruns. - VBlank timing: increased to 20% of a frame. --- Src/Model3/DSB.cpp | 13 + Src/Model3/Model3.cpp | 1366 +++++++++++++++++++++---------------- Src/Model3/Model3.h | 786 ++++++++++----------- Src/Model3/SoundBoard.cpp | 11 +- Src/Model3/SoundBoard.h | 4 +- Src/OSD/Audio.h | 8 +- Src/OSD/SDL/Audio.cpp | 39 +- Src/OSD/SDL/Main.cpp | 73 +- Src/OSD/SDL/Thread.cpp | 5 + 9 files changed, 1322 insertions(+), 983 deletions(-) diff --git a/Src/Model3/DSB.cpp b/Src/Model3/DSB.cpp index 2efeed5..d2b694e 100644 --- a/Src/Model3/DSB.cpp +++ b/Src/Model3/DSB.cpp @@ -509,6 +509,10 @@ void CDSB1::SaveState(CBlockFile *StateFile) // Z80 CPU state Z80.SaveState(StateFile, "DSB1 Z80"); + + //DEBUG + //printf("usingMPEGStart=%X usingMPEGEnd=%X\n", usingMPEGStart, usingMPEGEnd); + //printf("usingLoopStart=%X usingLoopEnd=%X\n", usingLoopStart, usingLoopEnd); } void CDSB1::LoadState(CBlockFile *StateFile) @@ -554,6 +558,10 @@ void CDSB1::LoadState(CBlockFile *StateFile) } else MPEG_StopPlaying(); + + //DEBUG + //printf("usingMPEGStart=%X usingMPEGEnd=%X\n", usingMPEGStart, usingMPEGEnd); + //printf("usingLoopStart=%X usingLoopEnd=%X\n", usingLoopStart, usingLoopEnd); } // Offsets of memory regions within DSB1's pool @@ -1065,6 +1073,11 @@ void CDSB2::SaveState(CBlockFile *StateFile) // 68K CPU state M68KSetContext(&M68K); M68KSaveState(StateFile, "DSB2 68K"); + + //DEBUG + //printf("mpegStart=%X, mpegEnd=%X\n", mpegStart, mpegEnd); + //printf("usingMPEGStart=%X, usingMPEGEnd=%X\n", usingMPEGStart, usingMPEGEnd); + //printf("usingLoopStart=%X, usingLoopEnd=%X\n", usingLoopStart, usingLoopEnd); } void CDSB2::LoadState(CBlockFile *StateFile) diff --git a/Src/Model3/Model3.cpp b/Src/Model3/Model3.cpp index f61650f..0c88969 100644 --- a/Src/Model3/Model3.cpp +++ b/Src/Model3/Model3.cpp @@ -158,10 +158,10 @@ * G20 View Change * * Lost World, LA Machineguns: - * G20 Gun trigger - * - * Star Wars Trilogy: - * G20 Event button + * G20 Gun trigger + * + * Star Wars Trilogy: + * G20 Event button * G25 Trigger * * Virtual On 2: @@ -795,18 +795,18 @@ void CModel3::WriteSystemRegister(unsigned reg, UINT8 data) /****************************************************************************** - Optimized Address Space Access Handlers - - Although I have not yet profiled the code, these ought to be more optimal, - especially if the compiler can generate jump tables. - - NOTE: Testing of some of the address ranges is not strict enough, especially - for the MPC10x. Write32() handles the MPC10x most correctly. For now, - accesses outside of the handled ranges have not been observed. Use the DEBUG + Optimized Address Space Access Handlers + + Although I have not yet profiled the code, these ought to be more optimal, + especially if the compiler can generate jump tables. + + NOTE: Testing of some of the address ranges is not strict enough, especially + for the MPC10x. Write32() handles the MPC10x most correctly. For now, + accesses outside of the handled ranges have not been observed. Use the DEBUG version of these handlers for validation of new games. ******************************************************************************/ - -#ifndef DEBUG + +#ifndef DEBUG /* * CModel3::Read8(addr): @@ -816,33 +816,33 @@ void CModel3::WriteSystemRegister(unsigned reg, UINT8 data) * * Read handlers. */ -UINT8 CModel3::Read8(UINT32 addr) -{ - // RAM (most frequently accessed) +UINT8 CModel3::Read8(UINT32 addr) +{ + // RAM (most frequently accessed) if (addr<0x00800000) - return ram[addr^3]; - - // Other - switch ((addr>>24)) - { - // CROM - case 0xFF: + return ram[addr^3]; + + // Other + switch ((addr>>24)) + { + // CROM + case 0xFF: if (addr < 0xFF800000) return cromBank[(addr&0x7FFFFF)^3]; else - return crom[(addr&0x7FFFFF)^3]; - - // Real3D DMA - case 0xC2: - return GPU.ReadDMARegister8(addr&0xFF); - - // Various - case 0xF0: - case 0xFE: // mirror - - switch ((addr>>16)&0xFF) - { - // Inputs + return crom[(addr&0x7FFFFF)^3]; + + // Real3D DMA + case 0xC2: + return GPU.ReadDMARegister8(addr&0xFF); + + // Various + case 0xF0: + case 0xFE: // mirror + + switch ((addr>>16)&0xFF) + { + // Inputs case 0x04: return ReadInputs(addr&0x3F); @@ -850,46 +850,46 @@ UINT8 CModel3::Read8(UINT32 addr) case 0x08: if ((addr&0xF) == 4) // MIDI control port return 0xFF; // one of these bits (0x80?) indicates "ready" - break; - - // System registers - case 0x10: - return ReadSystemRegister(addr&0x3F); - - // RTC - case 0x14: + break; + + // System registers + case 0x10: + return ReadSystemRegister(addr&0x3F); + + // RTC + case 0x14: if ((addr&3)==1) // battery voltage test return 0x03; else if ((addr&3)==0) return RTC.ReadRegister((addr>>2)&0xF); - return 0; - - // Unknown - default: - break; - } - - break; - - // 53C810 SCSI - case 0xC0: // only on Step 1.0 - if (Game->step != 0x10) // check for Step 1.0 - break; - case 0xF9: - case 0xC1: - return SCSI.ReadRegister(addr&0xFF); - - // Unknown - default: - break; - } - + return 0; + + // Unknown + default: + break; + } + + break; + + // 53C810 SCSI + case 0xC0: // only on Step 1.0 + if (Game->step != 0x10) // check for Step 1.0 + break; + case 0xF9: + case 0xC1: + return SCSI.ReadRegister(addr&0xFF); + + // Unknown + default: + break; + } + DebugLog("PC=%08X\tread8 : %08X\n", ppc_get_pc(), addr); - return 0xFF; + return 0xFF; } -UINT16 CModel3::Read16(UINT32 addr) -{ +UINT16 CModel3::Read16(UINT32 addr) +{ UINT16 data; if ((addr&1)) @@ -897,119 +897,119 @@ UINT16 CModel3::Read16(UINT32 addr) data = Read8(addr+0)<<8; data |= Read8(addr+1); return data; - } - - // RAM (most frequently accessed) + } + + // RAM (most frequently accessed) if (addr<0x00800000) - return *(UINT16 *) &ram[addr^2]; - - // Other - switch ((addr>>24)) - { - // CROM - case 0xFF: + return *(UINT16 *) &ram[addr^2]; + + // Other + switch ((addr>>24)) + { + // CROM + case 0xFF: if (addr < 0xFF800000) return *(UINT16 *) &cromBank[(addr&0x7FFFFF)^2]; else - return *(UINT16 *) &crom[(addr&0x7FFFFF)^2]; - - // Various - case 0xF0: - case 0xFE: // mirror - - switch ((addr>>16)&0xFF) - { - // Backup RAM - case 0x0C: - case 0x0D: + return *(UINT16 *) &crom[(addr&0x7FFFFF)^2]; + + // Various + case 0xF0: + case 0xFE: // mirror + + switch ((addr>>16)&0xFF) + { + // Backup RAM + case 0x0C: + case 0x0D: return *(UINT16 *) &backupRAM[(addr&0x1FFFF)^2]; // Sound Board case 0x08: //printf("PPC: Read16 %08X\n", addr); - break; - - // MPC105 - case 0xC0: // F0C00CF8 - return PCIBridge.ReadPCIConfigData(16,addr&3); - - // MPC106 - case 0xE0: - case 0xE1: - case 0xE2: - case 0xE3: - case 0xE4: - case 0xE5: - case 0xE6: - case 0xE7: - case 0xE8: - case 0xE9: - case 0xEA: - case 0xEB: - case 0xEC: - case 0xED: - case 0xEE: + break; + + // MPC105 + case 0xC0: // F0C00CF8 + return PCIBridge.ReadPCIConfigData(16,addr&3); + + // MPC106 + case 0xE0: + case 0xE1: + case 0xE2: + case 0xE3: + case 0xE4: + case 0xE5: + case 0xE6: + case 0xE7: + case 0xE8: + case 0xE9: + case 0xEA: + case 0xEB: + case 0xEC: + case 0xED: + case 0xEE: case 0xEF: - return PCIBridge.ReadPCIConfigData(16,addr&3); - - // Unknown - default: - break; - } - - break; - - // Unknown - default: - break; - } - + return PCIBridge.ReadPCIConfigData(16,addr&3); + + // Unknown + default: + break; + } + + break; + + // Unknown + default: + break; + } + DebugLog("PC=%08X\tread16: %08X\n", ppc_get_pc(), addr); - return 0xFFFF; + return 0xFFFF; } - -UINT32 CModel3::Read32(UINT32 addr) -{ - UINT32 data; - + +UINT32 CModel3::Read32(UINT32 addr) +{ + UINT32 data; + if ((addr&3)) { data = Read16(addr+0)<<16; data |= Read16(addr+2); return data; - } - - // RAM (most frequently accessed) - if (addr < 0x00800000) - return *(UINT32 *) &ram[addr]; - - // Other - switch ((addr>>24)) - { - // CROM - case 0xFF: + } + + // RAM (most frequently accessed) + if (addr < 0x00800000) + return *(UINT32 *) &ram[addr]; + + // Other + switch ((addr>>24)) + { + // CROM + case 0xFF: if (addr < 0xFF800000) return *(UINT32 *) &cromBank[(addr&0x7FFFFF)]; else - return *(UINT32 *) &crom[(addr&0x7FFFFF)]; - - // Real3D registers - case 0x84: - return GPU.ReadRegister(addr&0x3F); - - // Real3D DMA - case 0xC2: + return *(UINT32 *) &crom[(addr&0x7FFFFF)]; + + // Real3D registers + case 0x84: + return GPU.ReadRegister(addr&0x3F); + + // Real3D DMA + case 0xC2: data = GPU.ReadDMARegister32(addr&0xFF); - return FLIPENDIAN32(data); - - // Various - case 0xF0: - case 0xFE: // mirror - - switch ((addr>>16)&0xFF) - { - // Inputs - case 0x04: + return FLIPENDIAN32(data); + + // Various + case 0xF0: + case 0xFE: // mirror + + switch ((addr>>16)&0xFF) + { + // Inputs + case 0x04: data = ReadInputs((addr&0x3F)+0) << 24; data |= ReadInputs((addr&0x3F)+1) << 16; data |= ReadInputs((addr&0x3F)+2) << 8; @@ -1019,101 +1019,101 @@ UINT32 CModel3::Read32(UINT32 addr) // Sound Board case 0x08: //printf("PPC: Read32 %08X\n", addr); - break; - - // Backup RAM - case 0x0C: - case 0x0D: - return *(UINT32 *) &backupRAM[(addr&0x1FFFF)]; - - // System registers - case 0x10: + break; + + // Backup RAM + case 0x0C: + case 0x0D: + return *(UINT32 *) &backupRAM[(addr&0x1FFFF)]; + + // System registers + case 0x10: data = ReadSystemRegister((addr&0x3F)+0) << 24; data |= ReadSystemRegister((addr&0x3F)+1) << 16; data |= ReadSystemRegister((addr&0x3F)+2) << 8; data |= ReadSystemRegister((addr&0x3F)+3) << 0; - return data; - - // MPC105 - case 0xC0: // F0C00CF8 - return PCIBridge.ReadPCIConfigData(32,0); - - // MPC106 - case 0xE0: - case 0xE1: - case 0xE2: - case 0xE3: - case 0xE4: - case 0xE5: - case 0xE6: - case 0xE7: - case 0xE8: - case 0xE9: - case 0xEA: - case 0xEB: - case 0xEC: - case 0xED: - case 0xEE: + return data; + + // MPC105 + case 0xC0: // F0C00CF8 + return PCIBridge.ReadPCIConfigData(32,0); + + // MPC106 + case 0xE0: + case 0xE1: + case 0xE2: + case 0xE3: + case 0xE4: + case 0xE5: + case 0xE6: + case 0xE7: + case 0xE8: + case 0xE9: + case 0xEA: + case 0xEB: + case 0xEC: + case 0xED: + case 0xEE: case 0xEF: - return PCIBridge.ReadPCIConfigData(32,0); - - // RTC - case 0x14: + return PCIBridge.ReadPCIConfigData(32,0); + + // RTC + case 0x14: data = (RTC.ReadRegister((addr>>2)&0xF) << 24); data |= 0x00030000; // set these bits to pass battery voltage test - return data; - - // Security board RAM - case 0x18: - case 0x19: - return *(UINT32 *) &securityRAM[(addr&0x1FFFF)]; // so far, only 32-bit access observed, so we use little endian access - - // Security board registers - case 0x1A: - return ReadSecurity(addr&0x3F); - - // Unknown - default: - break; - } - - break; - - // Tile generator + return data; + + // Security board RAM + case 0x18: + case 0x19: + return *(UINT32 *) &securityRAM[(addr&0x1FFFF)]; // so far, only 32-bit access observed, so we use little endian access + + // Security board registers + case 0x1A: + return ReadSecurity(addr&0x3F); + + // Unknown + default: + break; + } + + break; + + // Tile generator case 0xF1: if (addr==0xF1180000) // fixes 2D graphics (TO-DO: integrate register reads into TileGen.cpp) - return 0; - + return 0; + // Tile generator accesses its RAM as little endian, must flip for big endian PowerPC - if (addr < 0xF1120000) - { + if (addr < 0xF1120000) + { data = TileGen.ReadRAM(addr&0x1FFFFF); - return FLIPENDIAN32(data); - } - - break; - - // 53C810 SCSI - case 0xC0: // only on Step 1.0 - if (Game->step != 0x10) // check for Step 1.0 - break; - case 0xF9: - case 0xC1: + return FLIPENDIAN32(data); + } + + break; + + // 53C810 SCSI + case 0xC0: // only on Step 1.0 + if (Game->step != 0x10) // check for Step 1.0 + break; + case 0xF9: + case 0xC1: data = (SCSI.ReadRegister((addr+0)&0xFF) << 24); data |= (SCSI.ReadRegister((addr+1)&0xFF) << 16); data |= (SCSI.ReadRegister((addr+2)&0xFF) << 8); data |= (SCSI.ReadRegister((addr+3)&0xFF) << 0); - return data; - - // Unknown - default: - break; - } - + return data; + + // Unknown + default: + break; + } + DebugLog("PC=%08X\tread32: %08X\n", ppc_get_pc(), addr); - return 0xFFFFFFFF; -} - + return 0xFFFFFFFF; +} + UINT64 CModel3::Read64(UINT32 addr) { UINT64 data; @@ -1133,32 +1133,32 @@ UINT64 CModel3::Read64(UINT32 addr) * * Write handlers. */ -void CModel3::Write8(UINT32 addr, UINT8 data) -{ - // RAM (most frequently accessed) - if (addr < 0x00800000) - { - ram[addr^3] = data; - return; - } - - // Other - switch ((addr>>24)) - { - // Real3D DMA - case 0xC2: - GPU.WriteDMARegister8(addr&0xFF,data); - break; - - // Various - case 0xF0: - case 0xFE: // mirror - - switch ((addr>>16)&0xFF) - { - // Inputs - case 0x04: - WriteInputs(addr&0x3F,data); +void CModel3::Write8(UINT32 addr, UINT8 data) +{ + // RAM (most frequently accessed) + if (addr < 0x00800000) + { + ram[addr^3] = data; + return; + } + + // Other + switch ((addr>>24)) + { + // Real3D DMA + case 0xC2: + GPU.WriteDMARegister8(addr&0xFF,data); + break; + + // Various + case 0xF0: + case 0xFE: // mirror + + switch ((addr>>16)&0xFF) + { + // Inputs + case 0x04: + WriteInputs(addr&0x3F,data); break; // Sound Board @@ -1168,322 +1168,322 @@ void CModel3::Write8(UINT32 addr, UINT8 data) SoundBoard.WriteMIDIPort(data); else if ((addr&0xF) == 4) // MIDI control port midiCtrlPort = data; - break; - - // Backup RAM - case 0x0C: - case 0x0D: - backupRAM[(addr&0x1FFFF)^3] = data; - break; - - // System registers - case 0x10: - WriteSystemRegister(addr&0x3F,data); - break; - - // RTC - case 0x14: + break; + + // Backup RAM + case 0x0C: + case 0x0D: + backupRAM[(addr&0x1FFFF)^3] = data; + break; + + // System registers + case 0x10: + WriteSystemRegister(addr&0x3F,data); + break; + + // RTC + case 0x14: if ((addr&3)==0) - RTC.WriteRegister((addr>>2)&0xF,data); - break; - - // Unknown - default: - break; - } - - DebugLog("PC=%08X\twrite8 : %08X=%02X\n", ppc_get_pc(), addr, data); - break; - - // MPC105/106 - case 0xF8: - PCIBridge.WriteRegister(addr&0xFF,data); - break; - - // 53C810 SCSI - case 0xC0: // only on Step 1.0 - if (Game->step != 0x10) - goto Unknown8; - case 0xF9: - case 0xC1: - SCSI.WriteRegister(addr&0xFF,data); - break; - - // Unknown: - default: - Unknown8: - DebugLog("PC=%08X\twrite8 : %08X=%02X\n", ppc_get_pc(), addr, data); - break; - } + RTC.WriteRegister((addr>>2)&0xF,data); + break; + + // Unknown + default: + break; + } + + DebugLog("PC=%08X\twrite8 : %08X=%02X\n", ppc_get_pc(), addr, data); + break; + + // MPC105/106 + case 0xF8: + PCIBridge.WriteRegister(addr&0xFF,data); + break; + + // 53C810 SCSI + case 0xC0: // only on Step 1.0 + if (Game->step != 0x10) + goto Unknown8; + case 0xF9: + case 0xC1: + SCSI.WriteRegister(addr&0xFF,data); + break; + + // Unknown: + default: + Unknown8: + DebugLog("PC=%08X\twrite8 : %08X=%02X\n", ppc_get_pc(), addr, data); + break; + } } - -void CModel3::Write16(UINT32 addr, UINT16 data) -{ - if ((addr&1)) - { + +void CModel3::Write16(UINT32 addr, UINT16 data) +{ + if ((addr&1)) + { Write8(addr+0,data>>8); Write8(addr+1,data&0xFF); return; - } - - // RAM (most frequently accessed) - if (addr < 0x00800000) - { - *(UINT16 *) &ram[addr^2] = data; - return; - } - - // Other - switch ((addr>>24)) - { - // Various - case 0xF0: - case 0xFE: // mirror - - switch ((addr>>16)&0xFF) - { + } + + // RAM (most frequently accessed) + if (addr < 0x00800000) + { + *(UINT16 *) &ram[addr^2] = data; + return; + } + + // Other + switch ((addr>>24)) + { + // Various + case 0xF0: + case 0xFE: // mirror + + switch ((addr>>16)&0xFF) + { // Sound Board case 0x08: //printf("%08X=%04X\n", addr, data); break; - // Backup RAM - case 0x0C: - case 0x0D: - *(UINT16 *) &backupRAM[(addr&0x1FFFF)^2] = data; - break; - - // MPC105 - case 0xC0: // F0C00CF8 - PCIBridge.WritePCIConfigData(16,addr&2,data); - break; - - // Unknown - default: - break; - } - - DebugLog("PC=%08X\twrite16 : %08X=%04X\n", ppc_get_pc(), addr, data); - break; - - // MPC105/106 - case 0xF8: + // Backup RAM + case 0x0C: + case 0x0D: + *(UINT16 *) &backupRAM[(addr&0x1FFFF)^2] = data; + break; + + // MPC105 + case 0xC0: // F0C00CF8 + PCIBridge.WritePCIConfigData(16,addr&2,data); + break; + + // Unknown + default: + break; + } + + DebugLog("PC=%08X\twrite16 : %08X=%04X\n", ppc_get_pc(), addr, data); + break; + + // MPC105/106 + case 0xF8: // Write in big endian order, like a real PowerPC PCIBridge.WriteRegister((addr&0xFF)+0,data>>8); - PCIBridge.WriteRegister((addr&0xFF)+1,data&0xFF); - break; - - // Unknown - default: - DebugLog("PC=%08X\twrite16: %08X=%04X\n", ppc_get_pc(), addr, data); - break; - } + PCIBridge.WriteRegister((addr&0xFF)+1,data&0xFF); + break; + + // Unknown + default: + DebugLog("PC=%08X\twrite16: %08X=%04X\n", ppc_get_pc(), addr, data); + break; + } } - -void CModel3::Write32(UINT32 addr, UINT32 data) + +void CModel3::Write32(UINT32 addr, UINT32 data) { if ((addr&3)) { Write16(addr+0,data>>16); Write16(addr+2,data); return; - } - - // RAM (most frequently accessed) - if (addr<0x00800000) + } + + // RAM (most frequently accessed) + if (addr<0x00800000) { - *(UINT32 *) &ram[addr] = data; - return; - } - - // Other - switch ((addr>>24)) - { - // Real3D trigger - case 0x88: // 88000000 - GPU.Flush(); - break; - - // Real3D low culling RAM - case 0x8C: // 8C000000-8C400000 - GPU.WriteLowCullingRAM(addr&0x3FFFFF,FLIPENDIAN32(data)); - break; - - // Real3D high culling RAM - case 0x8E: // 8E000000-8E100000 - GPU.WriteHighCullingRAM(addr&0xFFFFF,FLIPENDIAN32(data)); - break; - - // Real3D texture port - case 0x90: // 90000000-90000018 - GPU.WriteTexturePort(addr&0xFF,FLIPENDIAN32(data)); - break; - - // Real3D texture FIFO - case 0x94: // 94000000-94100000 - GPU.WriteTextureFIFO(FLIPENDIAN32(data)); - break; - - // Real3D polygon RAM - case 0x98: // 98000000-98400000 - GPU.WritePolygonRAM(addr&0x3FFFFF,FLIPENDIAN32(data)); - break; - - // Real3D DMA - case 0xC2: // C2000000-C2000100 - GPU.WriteDMARegister32(addr&0xFF,FLIPENDIAN32(data)); - break; - - // Various - case 0xF0: - case 0xFE: // mirror - - switch ((addr>>16)&0xFF) - { - // Inputs - case 0x04: + *(UINT32 *) &ram[addr] = data; + return; + } + + // Other + switch ((addr>>24)) + { + // Real3D trigger + case 0x88: // 88000000 + GPU.Flush(); + break; + + // Real3D low culling RAM + case 0x8C: // 8C000000-8C400000 + GPU.WriteLowCullingRAM(addr&0x3FFFFF,FLIPENDIAN32(data)); + break; + + // Real3D high culling RAM + case 0x8E: // 8E000000-8E100000 + GPU.WriteHighCullingRAM(addr&0xFFFFF,FLIPENDIAN32(data)); + break; + + // Real3D texture port + case 0x90: // 90000000-90000018 + GPU.WriteTexturePort(addr&0xFF,FLIPENDIAN32(data)); + break; + + // Real3D texture FIFO + case 0x94: // 94000000-94100000 + GPU.WriteTextureFIFO(FLIPENDIAN32(data)); + break; + + // Real3D polygon RAM + case 0x98: // 98000000-98400000 + GPU.WritePolygonRAM(addr&0x3FFFFF,FLIPENDIAN32(data)); + break; + + // Real3D DMA + case 0xC2: // C2000000-C2000100 + GPU.WriteDMARegister32(addr&0xFF,FLIPENDIAN32(data)); + break; + + // Various + case 0xF0: + case 0xFE: // mirror + + switch ((addr>>16)&0xFF) + { + // Inputs + case 0x04: WriteInputs((addr&0x3F)+0,(data>>24)&0xFF); WriteInputs((addr&0x3F)+1,(data>>16)&0xFF); WriteInputs((addr&0x3F)+2,(data>>8)&0xFF); - WriteInputs((addr&0x3F)+3,(data>>0)&0xFF); - break; - + WriteInputs((addr&0x3F)+3,(data>>0)&0xFF); + break; + // Sound Board case 0x08: //printf("PPC: %08X=%08X\n", addr, data); break; - // Backup RAM - case 0x0C: - case 0x0D: - *(UINT32 *) &backupRAM[(addr&0x1FFFF)] = data; - break; - - // MPC105 - case 0x80: // F0800CF8 (never observed at 0xFExxxxxx) - PCIBridge.WritePCIConfigAddress(data); - break; - - // MPC105/106 - case 0xC0: case 0xD0: case 0xE0: - case 0xC1: case 0xD1: case 0xE1: - case 0xC2: case 0xD2: case 0xE2: - case 0xC3: case 0xD3: case 0xE3: - case 0xC4: case 0xD4: case 0xE4: - case 0xC5: case 0xD5: case 0xE5: - case 0xC6: case 0xD6: case 0xE6: - case 0xC7: case 0xD7: case 0xE7: - case 0xC8: case 0xD8: case 0xE8: - case 0xC9: case 0xD9: case 0xE9: - case 0xCA: case 0xDA: case 0xEA: - case 0xCB: case 0xDB: case 0xEB: - case 0xCC: case 0xDC: case 0xEC: - case 0xCD: case 0xDD: case 0xED: - case 0xCE: case 0xDE: case 0xEE: - case 0xCF: case 0xDF: case 0xEF: + // Backup RAM + case 0x0C: + case 0x0D: + *(UINT32 *) &backupRAM[(addr&0x1FFFF)] = data; + break; + + // MPC105 + case 0x80: // F0800CF8 (never observed at 0xFExxxxxx) + PCIBridge.WritePCIConfigAddress(data); + break; + + // MPC105/106 + case 0xC0: case 0xD0: case 0xE0: + case 0xC1: case 0xD1: case 0xE1: + case 0xC2: case 0xD2: case 0xE2: + case 0xC3: case 0xD3: case 0xE3: + case 0xC4: case 0xD4: case 0xE4: + case 0xC5: case 0xD5: case 0xE5: + case 0xC6: case 0xD6: case 0xE6: + case 0xC7: case 0xD7: case 0xE7: + case 0xC8: case 0xD8: case 0xE8: + case 0xC9: case 0xD9: case 0xE9: + case 0xCA: case 0xDA: case 0xEA: + case 0xCB: case 0xDB: case 0xEB: + case 0xCC: case 0xDC: case 0xEC: + case 0xCD: case 0xDD: case 0xED: + case 0xCE: case 0xDE: case 0xEE: + case 0xCF: case 0xDF: case 0xEF: if ((addr>=0xF0C00CF8) && (addr<0xF0C00D00)) // MPC105 PCIBridge.WritePCIConfigData(32,0,data); else if ((addr>=0xFEC00000) && (addr<0xFEE00000)) // MPC106 PCIBridge.WritePCIConfigAddress(data); else if ((addr>=0xFEE00000) && (addr<0xFEF00000)) // MPC106 - PCIBridge.WritePCIConfigData(32,0,data); - break; - - // System registers - case 0x10: + PCIBridge.WritePCIConfigData(32,0,data); + break; + + // System registers + case 0x10: WriteSystemRegister((addr&0x3F)+0,(data>>24)&0xFF); WriteSystemRegister((addr&0x3F)+1,(data>>16)&0xFF); WriteSystemRegister((addr&0x3F)+2,(data>>8)&0xFF); - WriteSystemRegister((addr&0x3F)+3,(data>>0)&0xFF); - break; - - // RTC - case 0x14: - RTC.WriteRegister((addr>>2)&0xF,data); - break; - - // Security board RAM - case 0x18: - *(UINT32 *) &securityRAM[(addr&0x1FFFF)] = data; - break; - - // Security board registers - case 0x1A: - WriteSecurity(addr&0x3F,data); - break; - - // Unknown - default: - break; - } - - DebugLog("PC=%08X\twrite32: %08X=%08X\n", ppc_get_pc(), addr, data); - break; - - // Tile generator - case 0xF1: + WriteSystemRegister((addr&0x3F)+3,(data>>0)&0xFF); + break; + + // RTC + case 0x14: + RTC.WriteRegister((addr>>2)&0xF,data); + break; + + // Security board RAM + case 0x18: + *(UINT32 *) &securityRAM[(addr&0x1FFFF)] = data; + break; + + // Security board registers + case 0x1A: + WriteSecurity(addr&0x3F,data); + break; + + // Unknown + default: + break; + } + + DebugLog("PC=%08X\twrite32: %08X=%08X\n", ppc_get_pc(), addr, data); + break; + + // Tile generator + case 0xF1: if (addr < 0xF1120000) { // Tile generator accesses its RAM as little endian, must flip for big endian PowerPC data = FLIPENDIAN32(data); - TileGen.WriteRAM(addr&0x1FFFFF,data); + TileGen.WriteRAM(addr&0x1FFFFF,data); break; } - else if ((addr>=0xF1180000) && (addr<0xF1180100)) + else if ((addr>=0xF1180000) && (addr<0xF1180100)) { - TileGen.WriteRegister(addr&0xFF,FLIPENDIAN32(data)); - break; - } - - goto Unknown32; - - // MPC105/106 - case 0xF8: // F8FFF000-F8FFF100 + TileGen.WriteRegister(addr&0xFF,FLIPENDIAN32(data)); + break; + } + + goto Unknown32; + + // MPC105/106 + case 0xF8: // F8FFF000-F8FFF100 // Write in big endian order, like a real PowerPC PCIBridge.WriteRegister((addr&0xFF)+0,(data>>24)&0xFF); PCIBridge.WriteRegister((addr&0xFF)+1,(data>>16)&0xFF); PCIBridge.WriteRegister((addr&0xFF)+2,(data>>8)&0xFF); - PCIBridge.WriteRegister((addr&0xFF)+3,data&0xFF); - break; - - // 53C810 SCSI - case 0xC0: // step 1.0 only - if (Game->step != 0x10) - goto Unknown32; - case 0xF9: - case 0xC1: + PCIBridge.WriteRegister((addr&0xFF)+3,data&0xFF); + break; + + // 53C810 SCSI + case 0xC0: // step 1.0 only + if (Game->step != 0x10) + goto Unknown32; + case 0xF9: + case 0xC1: SCSI.WriteRegister((addr&0xFF)+0,(data>>24)&0xFF); SCSI.WriteRegister((addr&0xFF)+1,(data>>16)&0xFF); SCSI.WriteRegister((addr&0xFF)+2,(data>>8)&0xFF); - SCSI.WriteRegister((addr&0xFF)+3,data&0xFF); - break; - - // Unknown - default: - Unknown32: - DebugLog("PC=%08X\twrite32: %08X=%08X\n", ppc_get_pc(), addr, data); - break; - } -} + SCSI.WriteRegister((addr&0xFF)+3,data&0xFF); + break; + + // Unknown + default: + Unknown32: + DebugLog("PC=%08X\twrite32: %08X=%08X\n", ppc_get_pc(), addr, data); + break; + } +} void CModel3::Write64(UINT32 addr, UINT64 data) { Write32(addr+0, (UINT32) (data>>32)); Write32(addr+4, (UINT32) data); -} - -#endif - - +} + +#endif + + /****************************************************************************** - Debug Mode (Strict) Address Space Access Handlers - - Enabled only if DEBUG is defined. These perform stricter checks than the - "optimized" handlers but may be slower. + Debug Mode (Strict) Address Space Access Handlers + + Enabled only if DEBUG is defined. These perform stricter checks than the + "optimized" handlers but may be slower. ******************************************************************************/ - -#ifdef DEBUG - + +#ifdef DEBUG + /* * CModel3::Read8(addr): * CModel3::Read16(addr): @@ -1491,7 +1491,7 @@ void CModel3::Write64(UINT32 addr, UINT64 data) * CModel3::Read64(addr): * * Read handlers. - */ + */ UINT8 CModel3::Read8(UINT32 addr) { if (addr<0x00800000) @@ -1526,8 +1526,8 @@ UINT8 CModel3::Read8(UINT32 addr) DebugLog("PC=%08X\tread8 : %08X\n", ppc_get_pc(), addr); return 0xFF; -} - +} + UINT16 CModel3::Read16(UINT32 addr) { UINT16 data; @@ -1554,8 +1554,8 @@ UINT16 CModel3::Read16(UINT32 addr) DebugLog("PC=%08X\tread16: %08X\n", ppc_get_pc(), addr); return 0xFFFF; -} - +} + UINT32 CModel3::Read32(UINT32 addr) { UINT32 data; @@ -1633,7 +1633,7 @@ UINT32 CModel3::Read32(UINT32 addr) DebugLog("PC=%08X\tread32: %08X\n", ppc_get_pc(), addr); return 0xFFFFFFFF; -} +} UINT64 CModel3::Read64(UINT32 addr) { @@ -1645,7 +1645,7 @@ UINT64 CModel3::Read64(UINT32 addr) return data; } - + /* * CModel3::Write8(addr, data): * CModel3::Write16(addr, data): @@ -1653,7 +1653,7 @@ UINT64 CModel3::Read64(UINT32 addr) * CModel3::Write64(addr, data): * * Write handlers. - */ + */ void CModel3::Write8(UINT32 addr, UINT8 data) { if (addr<0x00800000) @@ -1687,8 +1687,8 @@ void CModel3::Write8(UINT32 addr, UINT8 data) DebugLog("PC=%08X\twrite8 : %08X=%02X\n", ppc_get_pc(), addr, data); //printf("PC=%08X\twrite8 : %08X=%02X\n", ppc_get_pc(), addr, data); } -} - +} + void CModel3::Write16(UINT32 addr, UINT16 data) { if ((addr&1)) @@ -1715,8 +1715,8 @@ void CModel3::Write16(UINT32 addr, UINT16 data) DebugLog("PC=%08X\twrite16: %08X=%04X\n", ppc_get_pc(), addr, data); //printf("PC=%08X\twrite16: %08X=%04X\n", ppc_get_pc(), addr, data); } -} - +} + void CModel3::Write32(UINT32 addr, UINT32 data) { if ((addr&3)) @@ -1724,8 +1724,8 @@ void CModel3::Write32(UINT32 addr, UINT32 data) Write16(addr+0,data>>16); Write16(addr+2,data); return; - } - + } + if (addr<0x00800000) *(UINT32 *) &ram[addr] = data; else if ((addr>=0x88000000) && (addr<0x88000008)) @@ -1807,8 +1807,8 @@ void CModel3::Write64(UINT32 addr, UINT64 data) Write32(addr+0, (UINT32) (data>>32)); Write32(addr+4, (UINT32) data); } - - + + #endif @@ -1909,7 +1909,7 @@ void CModel3::ClearNVRAM(void) } void CModel3::RunFrame(void) -{ +{ // See if currently running multi-threaded if (g_Config.multiThreaded) { @@ -1917,8 +1917,8 @@ void CModel3::RunFrame(void) if (!StartThreads()) goto ThreadError; - // Wake sound board and drive board threads so they can process a frame - if (!sndBrdThreadSync->Post() || DriveBoard.IsAttached() && !drvBrdThreadSync->Post()) + // Wake threads for sound board (if sync'd) and drive board (if attached) so they can process a frame + if (syncSndBrdThread && !sndBrdThreadSync->Post() || DriveBoard.IsAttached() && !drvBrdThreadSync->Post()) goto ThreadError; // At the same time, process a single frame for main board (PPC) in this thread @@ -1929,7 +1929,7 @@ void CModel3::RunFrame(void) goto ThreadError; // Wait for sound board and drive board threads to finish their work (if they haven't done so already) - while (!sndBrdThreadDone || DriveBoard.IsAttached() && !drvBrdThreadDone) + while (syncSndBrdThread && !sndBrdThreadDone || DriveBoard.IsAttached() && !drvBrdThreadDone) { if (!notifySync->Wait(notifyLock)) goto ThreadError; @@ -1957,7 +1957,7 @@ ThreadError: g_Config.multiThreaded = false; } -bool CModel3::StartThreads() +bool CModel3::StartThreads(void) { if (startedThreads) return true; @@ -1966,6 +1966,12 @@ bool CModel3::StartThreads() sndBrdThreadSync = CThread::CreateSemaphore(1); if (sndBrdThreadSync == NULL) goto ThreadError; + sndBrdNotifyLock = CThread::CreateMutex(); + if (sndBrdNotifyLock == NULL) + goto ThreadError; + sndBrdNotifySync = CThread::CreateCondVar(); + if (sndBrdNotifySync == NULL) + goto ThreadError; if (DriveBoard.IsAttached()) { drvBrdThreadSync = CThread::CreateSemaphore(1); @@ -1979,18 +1985,25 @@ bool CModel3::StartThreads() if (notifySync == NULL) goto ThreadError; - // Create sound board thread - sndBrdThread = CThread::CreateThread(StartSoundBoardThread, this); + // Create sound board thread (sync'd or unsync'd) + if (syncSndBrdThread) + sndBrdThread = CThread::CreateThread(StartSoundBoardThreadSyncd, this); + else + sndBrdThread = CThread::CreateThread(StartSoundBoardThread, this); if (sndBrdThread == NULL) goto ThreadError; - // Create drive board thread, if drive board is attached + // Create drive board thread (sync'd), if drive board is attached if (DriveBoard.IsAttached()) { - drvBrdThread = CThread::CreateThread(StartDriveBoardThread, this); + drvBrdThread = CThread::CreateThread(StartDriveBoardThreadSyncd, this); if (drvBrdThread == NULL) goto ThreadError; } + + // Set audio callback if unsync'd + if (!syncSndBrdThread) + SetAudioCallback(AudioCallback, this); startedThreads = true; return true; @@ -2002,16 +2015,55 @@ ThreadError: return false; } -void CModel3::StopThreads() +bool CModel3::PauseThreads(void) +{ + if (!startedThreads) + return true; + + // Enter notify critical section + if (!notifyLock->Lock()) + goto ThreadError; + + // Wait for all threads to finish their processing + pausedThreads = true; + while (sndBrdThreadRunning || drvBrdThreadRunning) + { + if (!notifySync->Wait(notifyLock)) + goto ThreadError; + } + + // Leave notify critical section + if (!notifyLock->Unlock()) + goto ThreadError; + return true; + +ThreadError: + ErrorLog("Threading error in CModel3::PauseThreads: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError()); + g_Config.multiThreaded = false; + return false; +} + +void CModel3::ResumeThreads(void) +{ + // No need to use any locking here + pausedThreads = false; + return; +} + +void CModel3::StopThreads(void) { if (!startedThreads) return; + // Remove callback + if (!syncSndBrdThread) + SetAudioCallback(NULL, NULL); + DeleteThreadObjects(); startedThreads = false; } -void CModel3::DeleteThreadObjects() +void CModel3::DeleteThreadObjects(void) { // Delete (which in turn kills) sound board and drive board threads // Note that can do so here safely because threads will always be waiting on their semaphores when this method is called @@ -2037,6 +2089,16 @@ void CModel3::DeleteThreadObjects() delete drvBrdThreadSync; drvBrdThreadSync = NULL; } + if (sndBrdNotifyLock != NULL) + { + delete sndBrdNotifyLock; + sndBrdNotifyLock = NULL; + } + if (sndBrdNotifySync != NULL) + { + delete sndBrdNotifySync; + sndBrdNotifySync = NULL; + } if (notifyLock != NULL) { delete notifyLock; @@ -2048,31 +2110,148 @@ void CModel3::DeleteThreadObjects() notifySync = NULL; } } - + int CModel3::StartSoundBoardThread(void *data) { - // Call sound board thread method on CModel3 + // Call method on CModel3 to run unsync'd sound board thread CModel3 *model3 = (CModel3*)data; model3->RunSoundBoardThread(); return 0; } -int CModel3::StartDriveBoardThread(void *data) +int CModel3::StartSoundBoardThreadSyncd(void *data) { - // Call drive board thread method on CModel3 + // Call method on CModel3 to run sync'd sound board thread CModel3 *model3 = (CModel3*)data; - model3->RunDriveBoardThread(); + model3->RunSoundBoardThreadSyncd(); return 0; } -void CModel3::RunSoundBoardThread() +int CModel3::StartDriveBoardThreadSyncd(void *data) +{ + // Call method on CModel3 to run sync'd drive board thread + CModel3 *model3 = (CModel3*)data; + model3->RunDriveBoardThreadSyncd(); + return 0; +} + +void CModel3::AudioCallback(void *data) +{ + // Call method on CModel3 to wake sound board thread + CModel3 *model3 = (CModel3*)data; + model3->WakeSoundBoardThread(); +} + +void CModel3::WakeSoundBoardThread(void) +{ + // Enter sound board notify critical section + if (!sndBrdNotifyLock->Lock()) + goto ThreadError; + + // Signal to sound board that it should start processing again + if (!sndBrdNotifySync->Signal()) + goto ThreadError; + + // Exit sound board notify critical section + if (!sndBrdNotifyLock->Unlock()) + goto ThreadError; + return; + +ThreadError: + ErrorLog("Threading error in WakeSoundBoardThread: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError()); + g_Config.multiThreaded = false; +} + +void CModel3::RunSoundBoardThread(void) { for (;;) { - // Wait on sound board thread semaphore - if (!sndBrdThreadSync->Wait()) + bool wait = true; + while (wait) + { + // Enter sound board notify critical section + if (!sndBrdNotifyLock->Lock()) + goto ThreadError; + + // Wait for notification from audio callback + if (!sndBrdNotifySync->Wait(sndBrdNotifyLock)) + goto ThreadError; + + // Exit sound board notify critical section + if (!sndBrdNotifyLock->Unlock()) + goto ThreadError; + + // Enter main notify critical section + if (!notifyLock->Lock()) + goto ThreadError; + + // Check threads not paused + if (!pausedThreads) + { + wait = false; + sndBrdThreadRunning = true; + } + + // Leave main notify critical section + if (!notifyLock->Unlock()) + goto ThreadError; + } + + // Keep processing frames until audio buffer is half full + bool repeat = true; + // NOTE - performs an unlocked read of pausedThreads here, but this is okay + while (!pausedThreads && !SoundBoard.RunFrame()) + { + //printf("Rerunning sound board\n"); + } + + // Enter main notify critical section + if (!notifyLock->Lock()) goto ThreadError; + // Let other threads know processing has finished + sndBrdThreadRunning = false; + sndBrdThreadDone = true; + if (!notifySync->SignalAll()) + goto ThreadError; + + // Leave main notify critical section + if (!notifyLock->Unlock()) + goto ThreadError; + } + +ThreadError: + ErrorLog("Threading error in RunSoundBoardThread: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError()); + g_Config.multiThreaded = false; +} + +void CModel3::RunSoundBoardThreadSyncd(void) +{ + for (;;) + { + bool wait = true; + while (wait) + { + // Wait on sound board thread semaphore + if (!sndBrdThreadSync->Wait()) + goto ThreadError; + + // Enter notify critical section + if (!notifyLock->Lock()) + goto ThreadError; + + // Check threads not paused + if (!pausedThreads) + { + wait = false; + sndBrdThreadRunning = true; + } + + // Leave notify critical section + if (!notifyLock->Unlock()) + goto ThreadError; + } + // Process a single frame for sound board SoundBoard.RunFrame(); @@ -2080,9 +2259,10 @@ void CModel3::RunSoundBoardThread() if (!notifyLock->Lock()) goto ThreadError; - // Let main thread know processing has finished + // Let other threads know processing has finished + sndBrdThreadRunning = false; sndBrdThreadDone = true; - if (!notifySync->Signal()) + if (!notifySync->SignalAll()) goto ThreadError; // Leave notify critical section @@ -2091,17 +2271,36 @@ void CModel3::RunSoundBoardThread() } ThreadError: - ErrorLog("Threading error in sound board thread: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError()); + ErrorLog("Threading error in RunSoundBoardThreadSyncd: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError()); g_Config.multiThreaded = false; } -void CModel3::RunDriveBoardThread() +void CModel3::RunDriveBoardThreadSyncd(void) { for (;;) { - // Wait on drive board thread semaphore - if (!drvBrdThreadSync->Wait()) - goto ThreadError; + bool wait = true; + while (wait) + { + // Wait on drive board thread semaphore + if (!drvBrdThreadSync->Wait()) + goto ThreadError; + + // Enter notify critical section + if (!notifyLock->Lock()) + goto ThreadError; + + // Check threads not paused + if (!pausedThreads) + { + wait = false; + drvBrdThreadRunning = true; + } + + // Leave notify critical section + if (!notifyLock->Unlock()) + goto ThreadError; + } // Process a single frame for drive board DriveBoard.RunFrame(); @@ -2110,9 +2309,10 @@ void CModel3::RunDriveBoardThread() if (!notifyLock->Lock()) goto ThreadError; - // Let main thread know processing has finished + // Let other threads know processing has finished + drvBrdThreadRunning = false; drvBrdThreadDone = true; - if (!notifySync->Signal()) + if (!notifySync->SignalAll()) goto ThreadError; // Leave notify critical section @@ -2121,13 +2321,19 @@ void CModel3::RunDriveBoardThread() } ThreadError: - ErrorLog("Threading error in drive board thread: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError()); + ErrorLog("Threading error in RunDriveBoardThreadSyncd: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError()); g_Config.multiThreaded = false; } + void CModel3::RunMainBoardFrame(void) { - // Run the PowerPC for a frame - ppc_execute(g_Config.GetPowerPCFrequency()*1000000/60-10000); + // Compute display and VBlank timings + unsigned frameCycles = g_Config.GetPowerPCFrequency()*1000000/60; + unsigned vblCycles = (unsigned) ((float) frameCycles * 20.0f/100.0f); // 20% vblank (just a guess; probably too long) + unsigned dispCycles = frameCycles - vblCycles; + + // Run the PowerPC for the active display part of the frame + ppc_execute(dispCycles); //printf("PC=%08X LR=%08X\n", ppc_get_pc(), ppc_get_lr()); // VBlank @@ -2135,7 +2341,7 @@ void CModel3::RunMainBoardFrame(void) GPU.BeginFrame(); GPU.RenderFrame(); IRQ.Assert(0x02); - ppc_execute(10000); // TO-DO: Vblank probably needs to be longer. Maybe that's why some games run too fast/slow + ppc_execute(vblCycles); //printf("PC=%08X LR=%08X\n", ppc_get_pc(), ppc_get_lr()); /* @@ -2271,8 +2477,8 @@ void CModel3::Patch(void) * 1540: Reset vector transfers control here. Effective start of * program. On error, game often resets here. * 14844: Appears to be beginning of the actual boot-up process. - */ - + */ + // Base offset of program in CROM: 710000 // *(UINT32 *) &crom[0x713724] = 0x60000000; // *(UINT32 *) &crom[0x713744] = 0x60000000; @@ -2316,7 +2522,7 @@ void CModel3::Patch(void) } else if (!strcmp(Game->id, "daytona2")) { - // Base address of program in CROM: 0x600000 + // Base address of program in CROM: 0x600000 // 0x10019E is the location in RAM which contains link type. // Region menu can be accessed by entering test mode, holding start, // and pressing: green, green, blue, yellow, red, yellow, blue (VR4,4,2,3,1,3,2) @@ -2736,10 +2942,14 @@ CModel3::CModel3(void) securityPtr = 0; startedThreads = false; + pausedThreads = false; sndBrdThread = NULL; drvBrdThread = NULL; + sndBrdThreadRunning = false; sndBrdThreadDone = false; + drvBrdThreadRunning = false; drvBrdThreadDone = false; + syncSndBrdThread = false; sndBrdThreadSync = NULL; drvBrdThreadSync = NULL; notifyLock = NULL; @@ -2789,4 +2999,4 @@ CModel3::~CModel3(void) securityRAM = NULL; DebugLog("Destroyed Model 3\n"); -} +} \ No newline at end of file diff --git a/Src/Model3/Model3.h b/Src/Model3/Model3.h index d436279..492d283 100644 --- a/Src/Model3/Model3.h +++ b/Src/Model3/Model3.h @@ -1,386 +1,400 @@ -/** - ** Supermodel - ** A Sega Model 3 Arcade Emulator. - ** Copyright 2011 Bart Trzynadlowski - ** - ** This file is part of Supermodel. - ** - ** Supermodel is free software: you can redistribute it and/or modify it under - ** the terms of the GNU General Public License as published by the Free - ** Software Foundation, either version 3 of the License, or (at your option) - ** any later version. - ** - ** Supermodel is distributed in the hope that it will be useful, but WITHOUT - ** ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - ** FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - ** more details. - ** - ** You should have received a copy of the GNU General Public License along - ** with Supermodel. If not, see . - **/ - -/* - * Model3.h - * - * Header file defining the CModel3, CModel3Config, and CModel3Inputs classes. - */ - -#ifndef INCLUDED_MODEL3_H -#define INCLUDED_MODEL3_H - -/* - * CModel3Config: - * - * Settings used by CModel3. - */ -class CModel3Config -{ -public: - bool multiThreaded; // Multi-threading (enabled if true) - - // PowerPC clock frequency in MHz (minimum: 1 MHz) - inline void SetPowerPCFrequency(unsigned f) - { - if ((f<1) || (f>1000)) - { - ErrorLog("PowerPC frequency must be between 1 and 1000 MHz; setting to 40 MHz."); - f = 40; - } - ppcFrequency = f*1000000; - } - inline unsigned GetPowerPCFrequency(void) - { - return ppcFrequency/1000000; - } - - // Defaults - CModel3Config(void) - { - multiThreaded = false; // disable by default - ppcFrequency = 40*1000000; // 40 MHz - } - -private: - unsigned ppcFrequency; // in Hz -}; - -/* - * CModel3: - * - * A complete Model 3 system. - * - * Inherits CBus in order to pass the address space handlers to devices that - * may need them (CPU, DMA, etc.) - * - * NOTE: Currently NOT re-entrant due to a non-OOP PowerPC core. Do NOT create - * create more than one CModel3 object! - */ -class CModel3: public CBus, public CPCIDevice -{ -public: - /* - * ReadPCIConfigSpace(device, reg, bits, offset): - * - * Handles unknown PCI devices. See CPCIDevice definition for more details. - * - * Parameters: - * device Device number. - * reg Register number. - * bits Bit width of access (8, 16, or 32 only).; - * offset Byte offset within register, aligned to the specified bit - * width, and offset from the 32-bit aligned base of the - * register number. - * - * Returns: - * Register data. - */ - UINT32 ReadPCIConfigSpace(unsigned device, unsigned reg, unsigned bits, unsigned width); - - /* - * WritePCIConfigSpace(device, reg, bits, offset, data): - * - * Handles unknown PCI devices. See CPCIDevice definition for more details. - * - * Parameters: - * device Device number. - * reg Register number. - * bits Bit width of access (8, 16, or 32 only). - * offset Byte offset within register, aligned to the specified bit - * width, and offset from the 32-bit aligned base of the - * register number. - * data Data. - */ - void WritePCIConfigSpace(unsigned device, unsigned reg, unsigned bits, unsigned width, UINT32 data); - - /* - * Read8(addr): - * Read16(addr): - * Read32(addr): - * Read64(addr): - * - * Read a byte, 16-bit half word, 32-bit word, or 64-bit double word from - * the PowerPC address space. This implements the PowerPC address bus. Note - * that it is big endian, so when accessing from a little endian device, - * the byte order must be manually reversed. - * - * Parameters: - * addr Address to read. - * - * Returns: - * Data at the address. - */ - UINT8 Read8(UINT32 addr); - UINT16 Read16(UINT32 addr); - UINT32 Read32(UINT32 addr); - UINT64 Read64(UINT32 addr); - - /* - * Write8(addr, data): - * Write16(addr, data): - * Write32(addr, data): - * Write64(addr, data): - * - * Write a byte, half word, word, or double word to the PowerPC address - * space. Note that everything is stored in big endian form, so when - * accessing with a little endian device, the byte order must be manually - * reversed. - * - * Parameters: - * addr Address to write. - * data Data to write. - */ - void Write8(UINT32 addr, UINT8 data); - void Write16(UINT32 addr, UINT16 data); - void Write32(UINT32 addr, UINT32 data); - void Write64(UINT32 addr, UINT64 data); - - /* - * SaveState(SaveState): - * - * Saves an image of the current state. Must never be called while emulator - * is running (inside RunFrame()). - * - * Parameters: - * SaveState Block file to save state information to. - */ - void SaveState(CBlockFile *SaveState); - - /* - * LoadState(SaveState): - * - * Loads and resumes execution from a state image. Modifies data that may - * be used by multiple threads -- use with caution and ensure threads are - * not accessing data that will be touched. Must never be called while - * emulator is running (inside RunFrame()). - * - * Parameters: - * SaveState Block file to load state information from. - */ - void LoadState(CBlockFile *SaveState); - - /* - * SaveNVRAM(NVRAM): - * - * Saves an image of the current NVRAM state. - * - * Parameters: - * NVRAM Block file to save NVRAM to. - */ - void SaveNVRAM(CBlockFile *NVRAM); - - /* - * LoadNVRAM(NVRAM): - * - * Loads an NVRAM image. - * - * Parameters: - * NVRAM Block file to load NVRAM state from. - */ - void LoadNVRAM(CBlockFile *NVRAM); - - /* - * ClearNVRAM(void): - * - * Clears all NVRAM (backup RAM and EEPROM). - */ - void ClearNVRAM(void); - - /* - * RunFrame(void): - * - * Runs one frame (assuming 60 Hz video refresh rate). - */ - void RunFrame(void); - - /* - * Reset(void): - * - * Resets the system. Does not modify non-volatile memory. - */ - void Reset(void); - - /* - * GetGameInfo(void): - * - * Returns: - * A pointer to the presently loaded game's information structure (or - * NULL if no ROM set has yet been loaded). - */ - const struct GameInfo * GetGameInfo(void); - - /* - * LoadROMSet(GameList, zipFile): - * - * Loads a complete ROM set from the specified ZIP archive. - * - * NOTE: Command line settings will not have been applied here yet. - * - * Parameters: - * GameList List of all supported games and their ROMs. - * zipFile ZIP file to load from. - * - * Returns: - * OKAY if successful, FAIL otherwise. Prints errors. - */ - bool LoadROMSet(const struct GameInfo *GameList, const char *zipFile); - - /* - * AttachRenderers(Render2DPtr, Render3DPtr): - * - * Attaches the renderers to the appropriate device objects. - * - * Parameters: - * Render2DPtr Pointer to a tile renderer object. - * Render3DPtr Same as above but for a 3D renderer. - */ - void AttachRenderers(CRender2D *Render2DPtr, CRender3D *Render3DPtr); - - /* - * AttachInputs(InputsPtr): - * - * Attaches OSD-managed inputs. - * - * Parameters: - * InputsPtr Pointer to the object containing input states. - */ - void AttachInputs(CInputs *InputsPtr); - - /* - * Init(void): - * - * One-time initialization of the context. Must be called prior to all - * other members. Allocates memory and initializes device states. - * - * NOTE: Command line settings will not have been applied here yet. - * - * Returns: - * OKAY is successful, otherwise FAILED if a non-recoverable error - * occurred. Prints own error messages. - */ - bool Init(void); - - /* - * CModel3(void): - * ~CModel3(void): - * - * Constructor and destructor for Model 3 class. Constructor performs a - * bare-bones initialization of object; does not perform any memory - * allocation or any actions that can fail. The destructor will deallocate - * memory and free resources used by the object (and its child objects). - */ - CModel3(void); - ~CModel3(void); - - /* - * Private Property. - * Tresspassers will be shot! ;) - */ -private: - // Private member functions - UINT8 ReadInputs(unsigned reg); - void WriteInputs(unsigned reg, UINT8 data); - UINT32 ReadSecurity(unsigned reg); - void WriteSecurity(unsigned reg, UINT32 data); - void SetCROMBank(unsigned idx); - UINT8 ReadSystemRegister(unsigned reg); - void WriteSystemRegister(unsigned reg, UINT8 data); - void Patch(void); - - void RunMainBoardFrame(); // Runs the main board (PPC) for a frame - bool StartThreads(); // Starts all threads - void StopThreads(); // Stops all threads - void DeleteThreadObjects(); // Deletes all threads and synchronization objects - - static int StartSoundBoardThread(void *data); // Callback to start sound board thread - static int StartDriveBoardThread(void *data); // Callback to start drive board thread - - void RunSoundBoardThread(); // Runs sound board thread - void RunDriveBoardThread(); // Runs drive board thread - - // Game and hardware information - const struct GameInfo *Game; - - // Game inputs - CInputs *Inputs; - - // Input registers (game controls) - UINT8 inputBank; - UINT8 serialFIFO1, serialFIFO2; - UINT8 gunReg; - int adcChannel; - - // MIDI port - UINT8 midiCtrlPort; // controls MIDI (SCSP) IRQ behavior - - // Emulated core Model 3 memory regions - UINT8 *memoryPool; // single allocated region for all ROM and system RAM - UINT8 *ram; // 8 MB PowerPC RAM - UINT8 *crom; // 8+128 MB CROM (fixed CROM first, then 64MB of banked CROMs -- Daytona2 might need extra?) - UINT8 *vrom; // 64 MB VROM (video ROM, visible only to Real3D) - UINT8 *soundROM; // 512 KB sound ROM (68K program) - UINT8 *sampleROM; // 8 MB samples (68K) - UINT8 *dsbROM; // 128 KB DSB ROM (Z80 program) - UINT8 *mpegROM; // 8 MB DSB MPEG ROM - UINT8 *backupRAM; // 128 KB Backup RAM (battery backed) - UINT8 *securityRAM; // 128 KB Security Board RAM - UINT8 *driveROM; // 32 KB drive board ROM (Z80 program) (optional) - - // Banked CROM - UINT8 *cromBank; // currently mapped in CROM bank - unsigned cromBankReg; // the CROM bank register - - // Security device - unsigned securityPtr; // pointer to current offset in security data - - // PowerPC - PPC_FETCH_REGION PPCFetchRegions[3]; - - // Multiple threading - bool startedThreads; // True if threads have been created and started - CThread *sndBrdThread; // Sound board thread - CThread *drvBrdThread; // Drive board thread - bool sndBrdThreadDone; // Flag to indicate sound board thread has finished processing for current frame - bool drvBrdThreadDone; // Flag to indicate drive board thread has finished processing for current frame - - // Thread synchronization objects - CSemaphore *sndBrdThreadSync; - CSemaphore *drvBrdThreadSync; - CMutex *notifyLock; - CCondVar *notifySync; - - // Other devices - CIRQ IRQ; // Model 3 IRQ controller - CMPC10x PCIBridge; // MPC10x PCI/bridge/memory controller - CPCIBus PCIBus; // Model 3's PCI bus - C53C810 SCSI; // NCR 53C810 SCSI controller - CRTC72421 RTC; // Epson RTC-72421 real-time clock - C93C46 EEPROM; // 93C46 EEPROM - CTileGen TileGen; // Sega 2D tile generator - CReal3D GPU; // Real3D graphics hardware - CSoundBoard SoundBoard; // Sound board - CDSB *DSB; // Digital Sound Board (type determined dynamically at load time) - CDriveBoard DriveBoard; // Drive board -}; - - -#endif // INCLUDED_MODEL3_H +/** + ** Supermodel + ** A Sega Model 3 Arcade Emulator. + ** Copyright 2011 Bart Trzynadlowski + ** + ** This file is part of Supermodel. + ** + ** Supermodel is free software: you can redistribute it and/or modify it under + ** the terms of the GNU General Public License as published by the Free + ** Software Foundation, either version 3 of the License, or (at your option) + ** any later version. + ** + ** Supermodel is distributed in the hope that it will be useful, but WITHOUT + ** ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + ** FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + ** more details. + ** + ** You should have received a copy of the GNU General Public License along + ** with Supermodel. If not, see . + **/ + +/* + * Model3.h + * + * Header file defining the CModel3, CModel3Config, and CModel3Inputs classes. + */ + +#ifndef INCLUDED_MODEL3_H +#define INCLUDED_MODEL3_H + +/* + * CModel3Config: + * + * Settings used by CModel3. + */ +class CModel3Config +{ +public: + bool multiThreaded; // Multi-threading (enabled if true) + + // PowerPC clock frequency in MHz (minimum: 1 MHz) + inline void SetPowerPCFrequency(unsigned f) + { + if ((f<1) || (f>1000)) + { + ErrorLog("PowerPC frequency must be between 1 and 1000 MHz; setting to 40 MHz."); + f = 40; + } + ppcFrequency = f*1000000; + } + inline unsigned GetPowerPCFrequency(void) + { + return ppcFrequency/1000000; + } + + // Defaults + CModel3Config(void) + { + multiThreaded = false; // disable by default + ppcFrequency = 40*1000000; // 40 MHz + } + +private: + unsigned ppcFrequency; // in Hz +}; + +/* + * CModel3: + * + * A complete Model 3 system. + * + * Inherits CBus in order to pass the address space handlers to devices that + * may need them (CPU, DMA, etc.) + * + * NOTE: Currently NOT re-entrant due to a non-OOP PowerPC core. Do NOT create + * create more than one CModel3 object! + */ +class CModel3: public CBus, public CPCIDevice +{ +public: + /* + * ReadPCIConfigSpace(device, reg, bits, offset): + * + * Handles unknown PCI devices. See CPCIDevice definition for more details. + * + * Parameters: + * device Device number. + * reg Register number. + * bits Bit width of access (8, 16, or 32 only).; + * offset Byte offset within register, aligned to the specified bit + * width, and offset from the 32-bit aligned base of the + * register number. + * + * Returns: + * Register data. + */ + UINT32 ReadPCIConfigSpace(unsigned device, unsigned reg, unsigned bits, unsigned width); + + /* + * WritePCIConfigSpace(device, reg, bits, offset, data): + * + * Handles unknown PCI devices. See CPCIDevice definition for more details. + * + * Parameters: + * device Device number. + * reg Register number. + * bits Bit width of access (8, 16, or 32 only). + * offset Byte offset within register, aligned to the specified bit + * width, and offset from the 32-bit aligned base of the + * register number. + * data Data. + */ + void WritePCIConfigSpace(unsigned device, unsigned reg, unsigned bits, unsigned width, UINT32 data); + + /* + * Read8(addr): + * Read16(addr): + * Read32(addr): + * Read64(addr): + * + * Read a byte, 16-bit half word, 32-bit word, or 64-bit double word from + * the PowerPC address space. This implements the PowerPC address bus. Note + * that it is big endian, so when accessing from a little endian device, + * the byte order must be manually reversed. + * + * Parameters: + * addr Address to read. + * + * Returns: + * Data at the address. + */ + UINT8 Read8(UINT32 addr); + UINT16 Read16(UINT32 addr); + UINT32 Read32(UINT32 addr); + UINT64 Read64(UINT32 addr); + + /* + * Write8(addr, data): + * Write16(addr, data): + * Write32(addr, data): + * Write64(addr, data): + * + * Write a byte, half word, word, or double word to the PowerPC address + * space. Note that everything is stored in big endian form, so when + * accessing with a little endian device, the byte order must be manually + * reversed. + * + * Parameters: + * addr Address to write. + * data Data to write. + */ + void Write8(UINT32 addr, UINT8 data); + void Write16(UINT32 addr, UINT16 data); + void Write32(UINT32 addr, UINT32 data); + void Write64(UINT32 addr, UINT64 data); + + /* + * SaveState(SaveState): + * + * Saves an image of the current state. Must never be called while emulator + * is running (inside RunFrame()). + * + * Parameters: + * SaveState Block file to save state information to. + */ + void SaveState(CBlockFile *SaveState); + + /* + * LoadState(SaveState): + * + * Loads and resumes execution from a state image. Modifies data that may + * be used by multiple threads -- use with caution and ensure threads are + * not accessing data that will be touched. Must never be called while + * emulator is running (inside RunFrame()). + * + * Parameters: + * SaveState Block file to load state information from. + */ + void LoadState(CBlockFile *SaveState); + + /* + * SaveNVRAM(NVRAM): + * + * Saves an image of the current NVRAM state. + * + * Parameters: + * NVRAM Block file to save NVRAM to. + */ + void SaveNVRAM(CBlockFile *NVRAM); + + /* + * LoadNVRAM(NVRAM): + * + * Loads an NVRAM image. + * + * Parameters: + * NVRAM Block file to load NVRAM state from. + */ + void LoadNVRAM(CBlockFile *NVRAM); + + /* + * ClearNVRAM(void): + * + * Clears all NVRAM (backup RAM and EEPROM). + */ + void ClearNVRAM(void); + + /* + * RunFrame(void): + * + * Runs one frame (assuming 60 Hz video refresh rate). + */ + void RunFrame(void); + + /* + * Reset(void): + * + * Resets the system. Does not modify non-volatile memory. + */ + void Reset(void); + + /* + * GetGameInfo(void): + * + * Returns: + * A pointer to the presently loaded game's information structure (or + * NULL if no ROM set has yet been loaded). + */ + const struct GameInfo * GetGameInfo(void); + + /* + * LoadROMSet(GameList, zipFile): + * + * Loads a complete ROM set from the specified ZIP archive. + * + * NOTE: Command line settings will not have been applied here yet. + * + * Parameters: + * GameList List of all supported games and their ROMs. + * zipFile ZIP file to load from. + * + * Returns: + * OKAY if successful, FAIL otherwise. Prints errors. + */ + bool LoadROMSet(const struct GameInfo *GameList, const char *zipFile); + + /* + * AttachRenderers(Render2DPtr, Render3DPtr): + * + * Attaches the renderers to the appropriate device objects. + * + * Parameters: + * Render2DPtr Pointer to a tile renderer object. + * Render3DPtr Same as above but for a 3D renderer. + */ + void AttachRenderers(CRender2D *Render2DPtr, CRender3D *Render3DPtr); + + /* + * AttachInputs(InputsPtr): + * + * Attaches OSD-managed inputs. + * + * Parameters: + * InputsPtr Pointer to the object containing input states. + */ + void AttachInputs(CInputs *InputsPtr); + + /* + * Init(void): + * + * One-time initialization of the context. Must be called prior to all + * other members. Allocates memory and initializes device states. + * + * NOTE: Command line settings will not have been applied here yet. + * + * Returns: + * OKAY is successful, otherwise FAILED if a non-recoverable error + * occurred. Prints own error messages. + */ + bool Init(void); + + /* + * CModel3(void): + * ~CModel3(void): + * + * Constructor and destructor for Model 3 class. Constructor performs a + * bare-bones initialization of object; does not perform any memory + * allocation or any actions that can fail. The destructor will deallocate + * memory and free resources used by the object (and its child objects). + */ + CModel3(void); + ~CModel3(void); + + bool PauseThreads(void); + void ResumeThreads(void); + + /* + * Private Property. + * Tresspassers will be shot! ;) + */ +private: + // Private member functions + UINT8 ReadInputs(unsigned reg); + void WriteInputs(unsigned reg, UINT8 data); + UINT32 ReadSecurity(unsigned reg); + void WriteSecurity(unsigned reg, UINT32 data); + void SetCROMBank(unsigned idx); + UINT8 ReadSystemRegister(unsigned reg); + void WriteSystemRegister(unsigned reg, UINT8 data); + void Patch(void); + + void RunMainBoardFrame(void); // Runs the main board (PPC) for a frame + bool StartThreads(void); // Starts all threads + void StopThreads(void); // Stops all threads + void DeleteThreadObjects(void); // Deletes all threads and synchronization objects + + static int StartSoundBoardThread(void *data); // Callback to start unsync'd sound board thread + static int StartSoundBoardThreadSyncd(void *data); // Callback to start sync'd sound board thread + static int StartDriveBoardThreadSyncd(void *data); // Callback to start sync'd drive board thread + + static void AudioCallback(void *data); // Audio buffer callback + + void WakeSoundBoardThread(void); // Used by audio callback to wake sound board thread when not sync'd with PPC thread + void RunSoundBoardThread(void); // Runs sound board thread unsync'd with PPC thread, ie at full speed + void RunSoundBoardThreadSyncd(void); // Runs sound board thread sync'd in step with PPC thread + void RunDriveBoardThreadSyncd(void); // Runs drive board thread sync'd in step with PPC thread + + // Game and hardware information + const struct GameInfo *Game; + + // Game inputs + CInputs *Inputs; + + // Input registers (game controls) + UINT8 inputBank; + UINT8 serialFIFO1, serialFIFO2; + UINT8 gunReg; + int adcChannel; + + // MIDI port + UINT8 midiCtrlPort; // controls MIDI (SCSP) IRQ behavior + + // Emulated core Model 3 memory regions + UINT8 *memoryPool; // single allocated region for all ROM and system RAM + UINT8 *ram; // 8 MB PowerPC RAM + UINT8 *crom; // 8+128 MB CROM (fixed CROM first, then 64MB of banked CROMs -- Daytona2 might need extra?) + UINT8 *vrom; // 64 MB VROM (video ROM, visible only to Real3D) + UINT8 *soundROM; // 512 KB sound ROM (68K program) + UINT8 *sampleROM; // 8 MB samples (68K) + UINT8 *dsbROM; // 128 KB DSB ROM (Z80 program) + UINT8 *mpegROM; // 8 MB DSB MPEG ROM + UINT8 *backupRAM; // 128 KB Backup RAM (battery backed) + UINT8 *securityRAM; // 128 KB Security Board RAM + UINT8 *driveROM; // 32 KB drive board ROM (Z80 program) (optional) + + // Banked CROM + UINT8 *cromBank; // currently mapped in CROM bank + unsigned cromBankReg; // the CROM bank register + + // Security device + unsigned securityPtr; // pointer to current offset in security data + + // PowerPC + PPC_FETCH_REGION PPCFetchRegions[3]; + + // Multiple threading + bool startedThreads; // True if threads have been created and started + bool pausedThreads; // True if threads are currently paused + CThread *sndBrdThread; // Sound board thread + CThread *drvBrdThread; // Drive board thread + bool sndBrdThreadRunning; // Flag to indicate sound board thread is currently processing + bool sndBrdThreadDone; // Flag to indicate sound board thread has finished processing + bool drvBrdThreadRunning; // Flag to indicate drive board thread is currently processing + bool drvBrdThreadDone; // Flag to indicate drive board thread has finished processing + + // Thread synchronization objects + bool syncSndBrdThread; + CSemaphore *sndBrdThreadSync; + CMutex *sndBrdNotifyLock; + CCondVar *sndBrdNotifySync; + CSemaphore *drvBrdThreadSync; + CMutex *notifyLock; + CCondVar *notifySync; + + // Other devices + CIRQ IRQ; // Model 3 IRQ controller + CMPC10x PCIBridge; // MPC10x PCI/bridge/memory controller + CPCIBus PCIBus; // Model 3's PCI bus + C53C810 SCSI; // NCR 53C810 SCSI controller + CRTC72421 RTC; // Epson RTC-72421 real-time clock + C93C46 EEPROM; // 93C46 EEPROM + CTileGen TileGen; // Sega 2D tile generator + CReal3D GPU; // Real3D graphics hardware + CSoundBoard SoundBoard; // Sound board + CDSB *DSB; // Digital Sound Board (type determined dynamically at load time) + CDriveBoard DriveBoard; // Drive board +}; + + +#endif // INCLUDED_MODEL3_H \ No newline at end of file diff --git a/Src/Model3/SoundBoard.cpp b/Src/Model3/SoundBoard.cpp index 441d374..becb3d2 100644 --- a/Src/Model3/SoundBoard.cpp +++ b/Src/Model3/SoundBoard.cpp @@ -1,4 +1,3 @@ -//TODO: before release, comment out printf()'s /** ** Supermodel ** A Sega Model 3 Arcade Emulator. @@ -358,7 +357,7 @@ void CSoundBoard::WriteMIDIPort(UINT8 data) DSB->SendCommand(data); } -void CSoundBoard::RunFrame(void) +bool CSoundBoard::RunFrame(void) { #ifdef SUPERMODEL_SOUND // Run sound board first to generate SCSP audio @@ -379,7 +378,7 @@ void CSoundBoard::RunFrame(void) DSB->RunFrame(audioL, audioR); // Output the audio buffers - OutputAudio(44100/60, audioL, audioR); + bool bufferFull = OutputAudio(44100/60, audioL, audioR); #ifdef SUPERMODEL_LOG_AUDIO // Output to binary file @@ -391,8 +390,10 @@ void CSoundBoard::RunFrame(void) s = audioR[i]; fwrite(&s, sizeof(INT16), 1, soundFP); // right channel } -#endif -#endif +#endif // SUPERMODEL_LOG_AUDIO +#endif // SUPERMODEL_SOUND + + return bufferFull; } void CSoundBoard::Reset(void) diff --git a/Src/Model3/SoundBoard.h b/Src/Model3/SoundBoard.h index be5c8b4..c8e0924 100644 --- a/Src/Model3/SoundBoard.h +++ b/Src/Model3/SoundBoard.h @@ -126,7 +126,7 @@ public: * * Runs the sound board for one frame, updating sound in the process. */ - void RunFrame(void); + bool RunFrame(void); /* * Reset(void): @@ -161,7 +161,7 @@ public: * error messages. */ bool Init(const UINT8 *soundROMPtr, const UINT8 *sampleROMPtr); - + /* * CSoundBoard(void): * ~CSoundBoard(void): diff --git a/Src/OSD/Audio.h b/Src/OSD/Audio.h index 241cab9..0b075b2 100755 --- a/Src/OSD/Audio.h +++ b/Src/OSD/Audio.h @@ -7,6 +7,12 @@ * Function-based interface for audio output. */ +typedef void (*AudioCallbackFPtr)(void *data); + +extern void SetAudioCallback(AudioCallbackFPtr callback, void *data); + +extern void SetAudioEnabled(bool enabled); + /* * OpenAudio() * @@ -19,7 +25,7 @@ extern bool OpenAudio(); * * Sends a chunk of two-channel audio with the given number of samples to the audio system. */ -extern void OutputAudio(unsigned numSamples, INT16 *leftBuffer, INT16 *rightBuffer); +extern bool OutputAudio(unsigned numSamples, INT16 *leftBuffer, INT16 *rightBuffer); /* * CloseAudio() diff --git a/Src/OSD/SDL/Audio.cpp b/Src/OSD/SDL/Audio.cpp index 3d0f07a..e7e6f45 100755 --- a/Src/OSD/SDL/Audio.cpp +++ b/Src/OSD/SDL/Audio.cpp @@ -38,11 +38,30 @@ static bool writeWrapped = false; // True if write position has wrapped around static unsigned underRuns = 0; // Number of buffer under-runs that have occured static unsigned overRuns = 0; // Number of buffer over-runs that have occured +static AudioCallbackFPtr callback = NULL; +static void *callbackData = NULL; + +void SetAudioCallback(AudioCallbackFPtr newCallback, void *newData) +{ + // Lock audio whilst changing callback pointers + SDL_LockAudio(); + + callback = newCallback; + callbackData = newData; + + SDL_UnlockAudio(); +} + +void SetAudioEnabled(bool newEnabled) +{ + enabled = newEnabled; +} + static void PlayCallback(void *data, Uint8 *stream, int len) { //printf("PlayCallback(%d) [writePos = %u, writeWrapped = %s, playPos = %u, audioBufferSize = %u]\n", // len, writePos, (writeWrapped ? "true" : "false"), playPos, audioBufferSize); - + // Get current write position and adjust it if write has wrapped but play position has not UINT32 adjWritePos = writePos; if (writeWrapped) @@ -128,6 +147,8 @@ static void PlayCallback(void *data, Uint8 *stream, int len) // Move play position forward for next time playPos += len; + bool halfEmpty = adjWritePos + audioBufferSize / 2 - BYTES_PER_FRAME / 2 < playPos + audioBufferSize; + // Check if play position has moved past end of buffer if (playPos >= audioBufferSize) { @@ -135,6 +156,10 @@ static void PlayCallback(void *data, Uint8 *stream, int len) playPos -= audioBufferSize; writeWrapped = false; } + + // If buffer is more than half empty then call callback + if (callback && halfEmpty) + callback(callbackData); } static void MixChannels(unsigned numSamples, INT16 *leftBuffer, INT16 *rightBuffer, void *dest) @@ -227,11 +252,13 @@ bool OpenAudio() return OKAY; } -void OutputAudio(unsigned numSamples, INT16 *leftBuffer, INT16 *rightBuffer) +bool OutputAudio(unsigned numSamples, INT16 *leftBuffer, INT16 *rightBuffer) { //printf("OutputAudio(%u) [writePos = %u, writeWrapped = %s, playPos = %u, audioBufferSize = %u]\n", // numSamples, writePos, (writeWrapped ? "true" : "false"), playPos, audioBufferSize); + bool halfFull = false; + UINT32 bytesRemaining; UINT32 bytesToCopy; INT16 *src; @@ -296,6 +323,9 @@ void OutputAudio(unsigned numSamples, INT16 *leftBuffer, INT16 *rightBuffer) // Check if write position has caught up with play region and now overlaps it (ie buffer over-run) bool overRun = writePos + numBytes > playPos + audioBufferSize; + if (writePos + audioBufferSize / 2 + BYTES_PER_FRAME / 2 > playPos + audioBufferSize) + halfFull = true; + // Move write position back to within buffer if (writePos >= audioBufferSize) writePos -= audioBufferSize; @@ -308,6 +338,8 @@ void OutputAudio(unsigned numSamples, INT16 *leftBuffer, INT16 *rightBuffer) //printf("Audio buffer over-run #%u in OutputAudio(%u) [writePos = %u, writeWrapped = %s, playPos = %u, audioBufferSize = %u, numBytes = %u]\n", // overRuns, numSamples, writePos, (writeWrapped ? "true" : "false"), playPos, audioBufferSize, numBytes); + halfFull = true; + // Discard current chunk of data goto Finish; } @@ -366,6 +398,9 @@ void OutputAudio(unsigned numSamples, INT16 *leftBuffer, INT16 *rightBuffer) Finish: // Unlock SDL audio callback SDL_UnlockAudio(); + + // Return whether buffer is half full + return halfFull; } void CloseAudio() diff --git a/Src/OSD/SDL/Main.cpp b/Src/OSD/SDL/Main.cpp index 4a4ed58..3acae72 100644 --- a/Src/OSD/SDL/Main.cpp +++ b/Src/OSD/SDL/Main.cpp @@ -762,17 +762,25 @@ int Supermodel(const char *zipFile, CInputs *Inputs, CINIFile *CmdLine) quit = 1; #ifdef SUPERMODEL_DEBUGGER + bool processUI = true; if (Debugger != NULL) { Debugger->Poll(); // Check if debugger requests exit or pause if (Debugger->CheckExit()) - quit = 1; - else if (Debugger->CheckPause()) - paused = 1; - else { + quit = 1; + processUI = false; + } + else if (Debugger->CheckPause()) + { + paused = 1; + processUI = false; + } + } + if (processUI) + { #endif // SUPERMODEL_DEBUGGER // Check UI controls @@ -783,6 +791,12 @@ int Supermodel(const char *zipFile, CInputs *Inputs, CINIFile *CmdLine) } else if (Inputs->uiReset->Pressed()) { + if (!paused) + { + Model3->PauseThreads(); + SetAudioEnabled(false); + } + // Reset emulator Model3->Reset(); @@ -792,17 +806,46 @@ int Supermodel(const char *zipFile, CInputs *Inputs, CINIFile *CmdLine) Debugger->Reset(); #endif // SUPERMODEL_DEBUGGER + if (!paused) + { + Model3->ResumeThreads(); + SetAudioEnabled(true); + } + puts("Model 3 reset."); } else if (Inputs->uiPause->Pressed()) { // Toggle emulator paused flag paused = !paused; + + if (paused) + { + Model3->PauseThreads(); + SetAudioEnabled(false); + } + else + { + Model3->ResumeThreads(); + SetAudioEnabled(true); + } } else if (Inputs->uiSaveState->Pressed()) { + if (!paused) + { + Model3->PauseThreads(); + SetAudioEnabled(false); + } + // Save game state SaveState(Model3); + + if (!paused) + { + Model3->ResumeThreads(); + SetAudioEnabled(true); + } } else if (Inputs->uiChangeSlot->Pressed()) { @@ -813,6 +856,12 @@ int Supermodel(const char *zipFile, CInputs *Inputs, CINIFile *CmdLine) } else if (Inputs->uiLoadState->Pressed()) { + if (!paused) + { + Model3->PauseThreads(); + SetAudioEnabled(false); + } + // Load game state LoadState(Model3); @@ -821,6 +870,12 @@ int Supermodel(const char *zipFile, CInputs *Inputs, CINIFile *CmdLine) if (Debugger != NULL) Debugger->Reset(); #endif // SUPERMODEL_DEBUGGER + + if (!paused) + { + Model3->ResumeThreads(); + SetAudioEnabled(true); + } } else if (Inputs->uiMusicVolUp->Pressed()) { @@ -904,12 +959,12 @@ int Supermodel(const char *zipFile, CInputs *Inputs, CINIFile *CmdLine) printf("Frame limiting: %s\n", g_Config.throttle?"On":"Off"); } #ifdef SUPERMODEL_DEBUGGER - else if (Inputs->uiEnterDebugger->Pressed()) - { - // Break execution and enter debugger - Debugger->ForceBreak(true); - } + else if (Debugger != NULL && Inputs->uiEnterDebugger->Pressed()) + { + // Break execution and enter debugger + Debugger->ForceBreak(true); } + } #endif // SUPERMODEL_DEBUGGER // FPS and frame rate diff --git a/Src/OSD/SDL/Thread.cpp b/Src/OSD/SDL/Thread.cpp index d73df16..fa3e776 100755 --- a/Src/OSD/SDL/Thread.cpp +++ b/Src/OSD/SDL/Thread.cpp @@ -122,6 +122,11 @@ bool CCondVar::Signal() return SDL_CondSignal((SDL_cond*)m_impl) == 0; } +bool CCondVar::SignalAll() +{ + return SDL_CondBroadcast((SDL_cond*)m_impl) == 0; +} + CMutex::CMutex(void *impl) : m_impl(impl) { //