diff --git a/Source/Core/Core/HW/DVD/DVDInterface.cpp b/Source/Core/Core/HW/DVD/DVDInterface.cpp index 885b8dd0a1..775c67ca70 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.cpp +++ b/Source/Core/Core/HW/DVD/DVDInterface.cpp @@ -1315,7 +1315,7 @@ void ScheduleReads(u64 offset, u32 length, const DiscIO::Partition& partition, u // TODO: This calculation is slightly wrong when decrypt is true - it uses the size of // the copy from IOS to PPC but is supposed to model the copy from the disc drive to IOS. ticks_until_completion += - static_cast(chunk_length) * SystemTimers::GetTicksPerSecond() / BUFFER_TRANSFER_RATE; + static_cast(chunk_length) * ticks_per_second / BUFFER_TRANSFER_RATE; buffered_blocks++; } else @@ -1328,6 +1328,15 @@ void ScheduleReads(u64 offset, u32 length, const DiscIO::Partition& partition, u ticks_until_completion += static_cast( ticks_per_second * DVDMath::CalculateSeekTime(head_position, dvd_offset)); + // TODO: The above emulates seeking and then reading one ECC block of data, + // and then the below emulates the rotational latency. The rotational latency + // should actually happen before reading data from the disc. + + const double time_after_seek = + (CoreTiming::GetTicks() + ticks_until_completion) / ticks_per_second; + ticks_until_completion += ticks_per_second * DVDMath::CalculateRotationalLatency( + dvd_offset, time_after_seek, wii_disc); + DEBUG_LOG(DVDINTERFACE, "Seek+read 0x%" PRIx32 " bytes @ 0x%" PRIx64 " ticks=%" PRId64, chunk_length, offset, ticks_until_completion); } diff --git a/Source/Core/Core/HW/DVD/DVDMath.cpp b/Source/Core/Core/HW/DVD/DVDMath.cpp index cb37aae412..7313b1de17 100644 --- a/Source/Core/Core/HW/DVD/DVDMath.cpp +++ b/Source/Core/Core/HW/DVD/DVDMath.cpp @@ -21,6 +21,8 @@ constexpr double DVD_INNER_RADIUS = 0.024; constexpr double WII_DVD_OUTER_RADIUS = 0.058; // 38 mm constexpr double GC_DVD_OUTER_RADIUS = 0.038; +// 0.74 um +constexpr double DVD_TRACK_PITCH = 0.00000074; // Approximate read speeds at the inner and outer locations of Wii and GC // discs. These speeds are approximations of speeds measured on real Wiis. @@ -29,12 +31,18 @@ constexpr double GC_DISC_OUTER_READ_SPEED = 1024 * 1024 * 3.325; // bytes/s constexpr double WII_DISC_INNER_READ_SPEED = 1024 * 1024 * 3.48; // bytes/s constexpr double WII_DISC_OUTER_READ_SPEED = 1024 * 1024 * 8.41; // bytes/s +// The speed at which discs rotate. These have not been directly measured on hardware - +// rather, the read speeds above have been matched to the closest standard DVD speed +// (3x for GC and 6x for Wii) and the rotational speeds of those have been used. +constexpr double GC_ROTATIONS_PER_SECOND = 28.5; +constexpr double WII_ROTATIONS_PER_SECOND = 57; + // Experimentally measured seek constants. The time to seek appears to be // linear, but short seeks appear to be lower velocity. constexpr double SHORT_SEEK_MAX_DISTANCE = 0.001; // 1 mm -constexpr double SHORT_SEEK_CONSTANT = 0.045; // seconds +constexpr double SHORT_SEEK_CONSTANT = 0.035; // seconds constexpr double SHORT_SEEK_VELOCITY_INVERSE = 50; // inverse: s/m -constexpr double LONG_SEEK_CONSTANT = 0.085; // seconds +constexpr double LONG_SEEK_CONSTANT = 0.075; // seconds constexpr double LONG_SEEK_VELOCITY_INVERSE = 4.5; // inverse: s/m // We can approximate the relationship between a byte offset on disc and its @@ -89,8 +97,6 @@ double CalculatePhysicalDiscPosition(u64 offset) if (offset > WII_DISC_LAYER_SIZE) offset = WII_DISC_LAYER_SIZE * 2 - offset; - // The track pitch here is 0.74 um, but it cancels out and we don't need it - // Note that because Wii and GC discs have identical data densities // we can simply use the Wii numbers in both cases return std::sqrt( @@ -118,6 +124,38 @@ double CalculateSeekTime(u64 offset_from, u64 offset_to) return distance * LONG_SEEK_VELOCITY_INVERSE + LONG_SEEK_CONSTANT; } +// Returns the time in seconds it takes for the disc to spin to the angle where the +// read head is over the given offset, starting from the given time in seconds. +double CalculateRotationalLatency(u64 offset, double time, bool wii_disc) +{ + // The data track on the disc is modelled as an Archimedean spiral. + + // We assume that the inserted disc is spinning constantly, which + // is not always true (especially not when no disc is inserted), + // but doesn't lead to any problems in practice. It's as if every + // newly inserted disc is inserted at such an angle that it ends + // up rotating identically to a disc inserted at boot. + + // To make the calculations simpler, the angles go up to 1, not tau. + // But tau (or anything else) would also work. + constexpr double MAX_ANGLE = 1; + + const double rotations_per_second = wii_disc ? WII_ROTATIONS_PER_SECOND : GC_ROTATIONS_PER_SECOND; + + const double target_angle = + fmod(CalculatePhysicalDiscPosition(offset) / DVD_TRACK_PITCH * MAX_ANGLE, MAX_ANGLE); + const double start_angle = fmod(time * rotations_per_second * MAX_ANGLE, MAX_ANGLE); + + // MAX_ANGLE is added so that fmod can't get a negative argument + const double angle_diff = fmod(target_angle + MAX_ANGLE - start_angle, MAX_ANGLE); + + const double result = angle_diff / MAX_ANGLE / rotations_per_second; + + DEBUG_LOG(DVDINTERFACE, "Rotational latency: %lf ms", result * 1000); + + return result; +} + // Returns the time in seconds it takes to read an amount of data from a disc, // ignoring factors such as seek times. This is the streaming rate of the // drive and varies between ~3-8MiB/s for Wii discs. Note that there is technically diff --git a/Source/Core/Core/HW/DVD/DVDMath.h b/Source/Core/Core/HW/DVD/DVDMath.h index 8c76a6db15..0a7d7d6215 100644 --- a/Source/Core/Core/HW/DVD/DVDMath.h +++ b/Source/Core/Core/HW/DVD/DVDMath.h @@ -10,6 +10,7 @@ namespace DVDMath { double CalculatePhysicalDiscPosition(u64 offset); double CalculateSeekTime(u64 offset_from, u64 offset_to); +double CalculateRotationalLatency(u64 offset, double time, bool wii_disc); double CalculateRawDiscReadTime(u64 offset, u64 length, bool wii_disc); } // namespace DVDMath