CDVD: Better simulate RPM changes going in to CLV

- Fix bugs with rotational latency
[SAVEVERSION+]
This commit is contained in:
refractionpcsx2 2023-10-08 15:56:08 +01:00
parent be7d4c3c93
commit 5f11ba0445
4 changed files with 43 additions and 50 deletions

View File

@ -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<float>(((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<float>(((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<float>(((mode == MODE_CDROM) ? CD_MAX_ROTATION_X1 : DVD_MAX_ROTATION_X1) * cdvd.Speed * sectorSpeed) / 60.0f;
const float rotationPerSecond = static_cast<float>(((mode == MODE_CDROM) ? CD_MAX_ROTATION_X1 : DVD_MAX_ROTATION_X1) * std::min(static_cast<float>(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<float>(PSXCLK) / static_cast<float>(((mode == MODE_CDROM) ? CD_SECTORS_PERSECOND : DVD_SECTORS_PERSECOND) * cdvd.Speed);
float cycles = static_cast<float>(PSXCLK) / static_cast<float>(((mode == MODE_CDROM) ? CD_SECTORS_PERSECOND : DVD_SECTORS_PERSECOND) * std::min(static_cast<float>(cdvd.Speed), (mode == MODE_CDROM) ? 10.3f : 1.6f));
return static_cast<int>(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<float>(PSXCLK) / static_cast<float>(old_rotspeed)) * 60.0f;
const float new_rpm = (static_cast<float>(PSXCLK) / static_cast<float>(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<CDVD_MODE_TYPE>(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<uint*>(cdvd.NCMDParam + 0), static_cast<CDVD_MODE_TYPE>(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<CDVD_MODE_TYPE>(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)

View File

@ -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;

View File

@ -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;

View File

@ -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