From 5f11ba04454e540d85d6c02ee29441b57619f5e2 Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Sun, 8 Oct 2023 15:56:08 +0100 Subject: [PATCH] CDVD: Better simulate RPM changes going in to CLV - Fix bugs with rotational latency [SAVEVERSION+] --- pcsx2/CDVD/CDVD.cpp | 85 ++++++++++++++++++-------------------- pcsx2/CDVD/CDVD.h | 1 + pcsx2/CDVD/CDVD_internal.h | 5 +-- pcsx2/SaveState.h | 2 +- 4 files changed, 43 insertions(+), 50 deletions(-) diff --git a/pcsx2/CDVD/CDVD.cpp b/pcsx2/CDVD/CDVD.cpp index d4d9d9bd31..dfb6bb3076 100644 --- a/pcsx2/CDVD/CDVD.cpp +++ b/pcsx2/CDVD/CDVD.cpp @@ -817,14 +817,16 @@ static int cdvdTrayStateDetecting() else return CDVD_TYPE_DETCT; //Detecting any kind of disc existing } -static u32 cdvdRotationalLatency(CDVD_MODE_TYPE mode) +static u32 cdvdRotationTime(CDVD_MODE_TYPE mode) { // CAV rotation is constant (minimum speed to maintain exact speed on outer dge if (cdvd.SpindlCtrl & CDVD_SPINDLE_CAV) { - const float rotationPerSecond = static_cast(((mode == MODE_CDROM) ? CD_MIN_ROTATION_X1 : DVD_MIN_ROTATION_X1) * cdvd.Speed) / 60.0f; + // Calculate rotations per second from RPM + const float rotationPerSecond = static_cast(((mode == MODE_CDROM) ? CD_MAX_ROTATION_X1 : DVD_MAX_ROTATION_X1) * cdvd.Speed) / 60.0f; + // Calculate MS per rotation by dividing 1 second of milliseconds by the number of rotations. const float msPerRotation = 1000.0f / rotationPerSecond; - + // Calculate how many cycles 1 millisecond takes in IOP clocks, multiply by the time for 1 rotation. return ((PSXCLK / 1000) * msPerRotation); } else @@ -852,9 +854,10 @@ static u32 cdvdRotationalLatency(CDVD_MODE_TYPE mode) numSectors = 360000; break; } - const float sectorSpeed = (((float)(cdvd.SeekToSector - offset) / numSectors) * 0.60f) + 0.40f; + // CLV speeds are reversed, so the centre is the fastest position. + const float sectorSpeed = (1.0f - (((float)(cdvd.SeekToSector - offset) / numSectors) * 0.60f)) + 0.40f; - const float rotationPerSecond = static_cast(((mode == MODE_CDROM) ? CD_MAX_ROTATION_X1 : DVD_MAX_ROTATION_X1) * cdvd.Speed * sectorSpeed) / 60.0f; + const float rotationPerSecond = static_cast(((mode == MODE_CDROM) ? CD_MAX_ROTATION_X1 : DVD_MAX_ROTATION_X1) * std::min(static_cast(cdvd.Speed), (mode == MODE_CDROM) ? 10.3f : 1.6f) * sectorSpeed) / 60.0f; const float msPerRotation = 1000.0f / rotationPerSecond; //DevCon.Warning("Rotations per second %f, msPerRotation cycles per ms %f total cycles per ms %d cycles per rotation %d", rotationPerSecond, msPerRotation, (u32)(PSXCLK / 1000), (u32)((PSXCLK / 1000) * msPerRotation)); return ((PSXCLK / 1000) * msPerRotation); @@ -897,7 +900,7 @@ static uint cdvdBlockReadTime(CDVD_MODE_TYPE mode) } // CLV Read Speed is constant - float cycles = static_cast(PSXCLK) / static_cast(((mode == MODE_CDROM) ? CD_SECTORS_PERSECOND : DVD_SECTORS_PERSECOND) * cdvd.Speed); + float cycles = static_cast(PSXCLK) / static_cast(((mode == MODE_CDROM) ? CD_SECTORS_PERSECOND : DVD_SECTORS_PERSECOND) * std::min(static_cast(cdvd.Speed), (mode == MODE_CDROM) ? 10.3f : 1.6f)); return static_cast(cycles); } @@ -916,6 +919,7 @@ void cdvdReset() cdvd.BlockSize = 2064; cdvd.Action = cdvdAction_None; cdvd.ReadTime = cdvdBlockReadTime(MODE_DVDROM); + cdvd.RotSpeed = cdvdRotationTime(MODE_DVDROM); // If we are recording, always use the same RTC setting // for games that use the RTC to seed their RNG -- this is very important to be the same everytime! @@ -1350,12 +1354,12 @@ __fi void cdvdReadInterrupt() } // Returns the number of IOP cycles until the event completes. -static uint cdvdStartSeek(uint newsector, CDVD_MODE_TYPE mode) +static uint cdvdStartSeek(uint newsector, CDVD_MODE_TYPE mode, bool transition_to_CLV) { cdvd.SeekToSector = newsector; uint delta = abs((s32)(cdvd.SeekToSector - cdvd.Sector)); - uint seektime; + uint seektime = 0; bool isSeeking = cdvd.nCommand == N_CD_SEEK; cdvdUpdateReady(CDVD_DRIVE_BUSY); @@ -1366,7 +1370,20 @@ static uint cdvdStartSeek(uint newsector, CDVD_MODE_TYPE mode) // So In the case where it's seeking to data it will be Spinning (0x2) not reading (0x8) and Seeking (0x10, but because seeking is also spinning 0x2 is also set)) // Update - Apparently all that was rubbish and some games don't like it. WRC was the one in this scenario which hated SEEK |ZPAUSE, so just putting it back to pause for now. // We should really run some tests for this behaviour. + int drive_speed_change_cycles = 0; + const int old_rotspeed = cdvd.RotSpeed; + cdvd.RotSpeed = cdvdRotationTime(mode); + cdvd.ReadTime = cdvdBlockReadTime(mode); + + if (cdvd.Spinning && transition_to_CLV) + { + const float old_rpm = (static_cast(PSXCLK) / static_cast(old_rotspeed)) * 60.0f; + const float new_rpm = (static_cast(PSXCLK) / static_cast(cdvd.RotSpeed)) * 60.0f; + // A rough cycles per RPM change based on 333ms for a full spin up. + drive_speed_change_cycles = (PSXCLK / 1000) * (0.054950495049505f * std::abs(new_rpm - old_rpm)); + psxRegs.interrupt &= ~(1 << IopEvt_CdvdSectorReady); + } cdvdUpdateStatus(CDVD_STATUS_SEEK); if (!cdvd.Spinning) @@ -1395,12 +1412,12 @@ static uint cdvdStartSeek(uint newsector, CDVD_MODE_TYPE mode) } isSeeking = true; } - else + else if(!drive_speed_change_cycles) { CDVD_LOG("CdSeek Begin > Contiguous block without seek - delta=%d sectors", delta); // if delta > 0 it will read a new sector so the readInterrupt will account for this. - seektime = 0; + isSeeking = false; if (delta == 0) @@ -1444,10 +1461,12 @@ static uint cdvdStartSeek(uint newsector, CDVD_MODE_TYPE mode) } } + seektime += drive_speed_change_cycles; + // Only do this on reads, the seek kind of accounts for this and then it reads the sectors after if ((delta || cdvd.Action == cdvdAction_Seek) && !isSeeking && !cdvd.nextSectorsBuffered) { - const u32 rotationalLatency = cdvdRotationalLatency((CDVD_MODE_TYPE)cdvdIsDVD()); + const u32 rotationalLatency = cdvdRotationTime((CDVD_MODE_TYPE)cdvdIsDVD()) / 2; // Half it to average the rotational latency. //DevCon.Warning("%s rotational latency at sector %d is %d cycles", (cdvd.SpindlCtrl & CDVD_SPINDLE_CAV) ? "CAV" : "CLV", cdvd.SeekToSector, rotationalLatency); seektime += rotationalLatency + cdvd.ReadTime; CDVDSECTORREADY_INT(seektime); @@ -1466,7 +1485,6 @@ static uint cdvdStartSeek(uint newsector, CDVD_MODE_TYPE mode) { CDVDSECTORREADY_INT(seektime); } - // Clear the action on the following command, so we can rotate after seek. if (cdvd.nCommand != N_CD_SEEK) cdvd.Action = cdvdAction_None; @@ -1832,8 +1850,7 @@ static void cdvdWrite04(u8 rt) // spinup times if needed. cdvdUpdateReady(CDVD_DRIVE_BUSY); DevCon.Warning("CdStandby : %d", rt); - cdvd.ReadTime = cdvdBlockReadTime((CDVD_MODE_TYPE)cdvdIsDVD()); - CDVD_INT(cdvdStartSeek(0, MODE_DVDROM)); + CDVD_INT(cdvdStartSeek(0, static_cast(cdvdIsDVD()), false)); // Might not seek, but makes sense since it does move to the inner most track // It's only temporary until the interrupt anyway when it sets itself ready cdvdUpdateStatus(CDVD_STATUS_SEEK); @@ -1864,8 +1881,7 @@ static void cdvdWrite04(u8 rt) case N_CD_SEEK: // CdSeek cdvdUpdateReady(CDVD_DRIVE_BUSY); - cdvd.ReadTime = cdvdBlockReadTime((CDVD_MODE_TYPE)cdvdIsDVD()); - CDVD_INT(cdvdStartSeek(*(uint*)(cdvd.NCMDParam + 0), (CDVD_MODE_TYPE)cdvdIsDVD())); + CDVD_INT(cdvdStartSeek(*reinterpret_cast(cdvd.NCMDParam + 0), static_cast(cdvdIsDVD()), false)); cdvdUpdateStatus(CDVD_STATUS_SEEK); cdvd.Action = cdvdAction_Seek; break; @@ -1924,12 +1940,6 @@ static void cdvdWrite04(u8 rt) break; } - if ((cdvd.SpindlCtrl & CDVD_SPINDLE_CAV) != (oldSpindleCtrl & CDVD_SPINDLE_CAV) || oldSpeed != cdvd.Speed) - { - CDVD_LOG("CdRead > Speed change, adding delay"); - cdvd.Spinning = false; - } - if (cdvdIsDVD() && cdvd.NCMDParam[10] != 0) { ParamError = true; @@ -1973,7 +1983,7 @@ static void cdvdWrite04(u8 rt) cdvd.Action = cdvdAction_Error; cdvdUpdateStatus(CDVD_STATUS_SEEK); cdvdUpdateReady(CDVD_DRIVE_BUSY); - CDVD_INT(cdvdRotationalLatency((CDVD_MODE_TYPE)cdvdIsDVD())); + CDVD_INT(cdvdRotationTime((CDVD_MODE_TYPE)cdvdIsDVD())); break; } @@ -1984,9 +1994,8 @@ static void cdvdWrite04(u8 rt) Console.WriteLn(Color_Gray, "CDRead: Reading Sector %07d (%03d Blocks of Size %d) at Speed=%dx(%s) Spindle=%x", cdvd.SeekToSector, cdvd.nSectors, cdvd.BlockSize, cdvd.Speed, (cdvd.SpindlCtrl & CDVD_SPINDLE_CAV) ? "CAV" : "CLV", cdvd.SpindlCtrl); - cdvd.ReadTime = cdvdBlockReadTime((CDVD_MODE_TYPE)cdvdIsDVD()); - CDVDREAD_INT(cdvdStartSeek(cdvd.SeekToSector, (CDVD_MODE_TYPE)cdvdIsDVD())); - + CDVDREAD_INT(cdvdStartSeek(cdvd.SeekToSector, static_cast(cdvdIsDVD()), !(cdvd.SpindlCtrl & CDVD_SPINDLE_CAV) && (oldSpindleCtrl & CDVD_SPINDLE_CAV))); + cdvdUpdateReady(CDVD_DRIVE_BUSY); // Read-ahead by telling CDVD about the track now. // This helps improve performance on actual from-cd emulation // (ie, not using the hard drive) @@ -2050,12 +2059,6 @@ static void cdvdWrite04(u8 rt) break; } - if ((cdvd.SpindlCtrl & CDVD_SPINDLE_CAV) != (oldSpindleCtrl & CDVD_SPINDLE_CAV) || oldSpeed != cdvd.Speed) - { - CDVD_LOG("CdRead > Speed change, adding delay"); - cdvd.Spinning = false; - } - switch (cdvd.NCMDParam[10]) { case 1: @@ -2090,9 +2093,8 @@ static void cdvdWrite04(u8 rt) Console.WriteLn(Color_Gray, "CdAudioRead: Reading Sector %07d (%03d Blocks of Size %d) at Speed=%dx(%s) Spindle=%x", cdvd.Sector, cdvd.nSectors, cdvd.BlockSize, cdvd.Speed, (cdvd.SpindlCtrl & CDVD_SPINDLE_CAV) ? "CAV" : "CLV", cdvd.SpindlCtrl); - cdvd.ReadTime = cdvdBlockReadTime(MODE_CDROM); - CDVDREAD_INT(cdvdStartSeek(cdvd.SeekToSector, MODE_CDROM)); - + CDVDREAD_INT(cdvdStartSeek(cdvd.SeekToSector, MODE_CDROM, !(cdvd.SpindlCtrl& CDVD_SPINDLE_CAV) && (oldSpindleCtrl& CDVD_SPINDLE_CAV))); + cdvdUpdateReady(CDVD_DRIVE_BUSY); // Read-ahead by telling CDVD about the track now. // This helps improve performance on actual from-cd emulation // (ie, not using the hard drive) @@ -2153,12 +2155,6 @@ static void cdvdWrite04(u8 rt) break; } - if ((cdvd.SpindlCtrl & CDVD_SPINDLE_CAV) != (oldSpindleCtrl & CDVD_SPINDLE_CAV) || oldSpeed != cdvd.Speed) - { - CDVD_LOG("DvdRead > Speed change, adding delay"); - cdvd.Spinning = false; - } - if (cdvd.NCMDParam[10] != 0) ParamError = true; @@ -2182,7 +2178,7 @@ static void cdvdWrite04(u8 rt) cdvd.Action = cdvdAction_Error; cdvdUpdateStatus(CDVD_STATUS_SEEK); cdvdUpdateReady(CDVD_DRIVE_BUSY); - CDVD_INT(cdvdRotationalLatency((CDVD_MODE_TYPE)cdvdIsDVD())); + CDVD_INT(cdvdRotationTime((CDVD_MODE_TYPE)cdvdIsDVD())); break; } @@ -2193,9 +2189,8 @@ static void cdvdWrite04(u8 rt) Console.WriteLn(Color_Gray, "DvdRead: Reading Sector %07d (%03d Blocks of Size %d) at Speed=%dx(%s) SpindleCtrl=%x", cdvd.SeekToSector, cdvd.nSectors, cdvd.BlockSize, cdvd.Speed, (cdvd.SpindlCtrl & CDVD_SPINDLE_CAV) ? "CAV" : "CLV", cdvd.SpindlCtrl); - cdvd.ReadTime = cdvdBlockReadTime(MODE_DVDROM); - CDVDREAD_INT(cdvdStartSeek(cdvd.SeekToSector, MODE_DVDROM)); - + CDVDREAD_INT(cdvdStartSeek(cdvd.SeekToSector, MODE_DVDROM, !(cdvd.SpindlCtrl & CDVD_SPINDLE_CAV) && (oldSpindleCtrl& CDVD_SPINDLE_CAV))); + cdvdUpdateReady(CDVD_DRIVE_BUSY); // Read-ahead by telling CDVD about the track now. // This helps improve performance on actual from-cd emulation // (ie, not using the hard drive) diff --git a/pcsx2/CDVD/CDVD.h b/pcsx2/CDVD/CDVD.h index 1bb0478324..e3e95b50de 100644 --- a/pcsx2/CDVD/CDVD.h +++ b/pcsx2/CDVD/CDVD.h @@ -167,6 +167,7 @@ struct cdvdStruct u32 SeekToSector; // Holds the destination sector during seek operations. u32 MaxSector; // Current disc max sector. u32 ReadTime; // Avg. time to read one block of data (in Iop cycles) + u32 RotSpeed; // Rotational Speed bool Spinning; // indicates if the Cdvd is spinning or needs a spinup delay cdvdTrayTimer Tray; u8 nextSectorsBuffered; diff --git a/pcsx2/CDVD/CDVD_internal.h b/pcsx2/CDVD/CDVD_internal.h index 27688aa106..ad344d7694 100644 --- a/pcsx2/CDVD/CDVD_internal.h +++ b/pcsx2/CDVD/CDVD_internal.h @@ -151,16 +151,13 @@ static const uint tbl_ContigiousSeekDelta[3] = 16, // dual-layer DVD-ROM [currently unused] }; -// Note: DVD read times are modified to be faster, because games seem to be a lot more -// concerned with accurate(ish) seek delays and less concerned with actual block read speeds. -// Translation: it's a minor speedhack :D - static const uint PSX_CD_READSPEED = 153600; // Bytes per second, rough values from outer CD (CAV). static const uint PSX_DVD_READSPEED = 1382400; // Bytes per second, rough values from outer DVD (CAV). static const uint CD_SECTORS_PERSECOND = 75; static const uint DVD_SECTORS_PERSECOND = 675; +// Rotations per minute. static const uint CD_MIN_ROTATION_X1 = 214; static const uint CD_MAX_ROTATION_X1 = 497; diff --git a/pcsx2/SaveState.h b/pcsx2/SaveState.h index 9edfad7711..592dfc95f3 100644 --- a/pcsx2/SaveState.h +++ b/pcsx2/SaveState.h @@ -37,7 +37,7 @@ enum class FreezeAction // [SAVEVERSION+] // This informs the auto updater that the users savestates will be invalidated. -static const u32 g_SaveVersion = (0x9A42 << 16) | 0x0000; +static const u32 g_SaveVersion = (0x9A43 << 16) | 0x0000; // the freezing data between submodules and core