diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index cc4b66a290..7d17cde612 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -90,6 +90,7 @@ set(SRCS HW/DSPLLE/DSPLLE.cpp HW/DSPLLE/DSPLLETools.cpp HW/DVD/DVDInterface.cpp + HW/DVD/DVDMath.cpp HW/DVD/DVDThread.cpp HW/DVD/FileMonitor.cpp HW/EXI/EXI_Channel.cpp diff --git a/Source/Core/Core/Core.vcxproj b/Source/Core/Core/Core.vcxproj index 0c4234cdb7..23e6c0df6e 100644 --- a/Source/Core/Core/Core.vcxproj +++ b/Source/Core/Core/Core.vcxproj @@ -115,6 +115,7 @@ + @@ -369,6 +370,7 @@ + diff --git a/Source/Core/Core/Core.vcxproj.filters b/Source/Core/Core/Core.vcxproj.filters index 6deff72abd..a3773aad87 100644 --- a/Source/Core/Core/Core.vcxproj.filters +++ b/Source/Core/Core/Core.vcxproj.filters @@ -347,6 +347,9 @@ HW %28Flipper/Hollywood%29\DI - Drive Interface + + HW %28Flipper/Hollywood%29\DI - Drive Interface + HW %28Flipper/Hollywood%29\DI - Drive Interface @@ -1003,6 +1006,9 @@ HW %28Flipper/Hollywood%29\DI - Drive Interface + + HW %28Flipper/Hollywood%29\DI - Drive Interface + HW %28Flipper/Hollywood%29\DI - Drive Interface diff --git a/Source/Core/Core/HW/DVD/DVDInterface.cpp b/Source/Core/Core/HW/DVD/DVDInterface.cpp index 9f52a34c5b..49c7926c07 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.cpp +++ b/Source/Core/Core/HW/DVD/DVDInterface.cpp @@ -4,7 +4,6 @@ #include #include -#include #include #include @@ -13,12 +12,14 @@ #include "Common/Align.h" #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" +#include "Common/Logging/Log.h" #include "Core/ConfigManager.h" #include "Core/Core.h" #include "Core/CoreTiming.h" #include "Core/HW/AudioInterface.h" #include "Core/HW/DVD/DVDInterface.h" +#include "Core/HW/DVD/DVDMath.h" #include "Core/HW/DVD/DVDThread.h" #include "Core/HW/DVD/FileMonitor.h" #include "Core/HW/MMIO.h" @@ -52,31 +53,6 @@ constexpr u64 DVD_ECC_BLOCK_SIZE = 16 * DVD_SECTOR_SIZE; // is already buffered. Measured in bytes per second. constexpr u64 BUFFER_TRANSFER_RATE = 32 * 1024 * 1024; -// The size of the first Wii disc layer in bytes (2294912 sectors per layer) -constexpr u64 WII_DISC_LAYER_SIZE = 2294912 * DVD_SECTOR_SIZE; - -// 24 mm -constexpr double DVD_INNER_RADIUS = 0.024; -// 58 mm -constexpr double WII_DVD_OUTER_RADIUS = 0.058; -// 38 mm -constexpr double GC_DVD_OUTER_RADIUS = 0.038; - -// Approximate read speeds at the inner and outer locations of Wii and GC -// discs. These speeds are approximations of speeds measured on real Wiis. -constexpr double GC_DISC_INNER_READ_SPEED = 1024 * 1024 * 2.1; // bytes/s -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 - -// 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_VELOCITY_INVERSE = 50; // inverse: s/m -constexpr double LONG_SEEK_CONSTANT = 0.085; // seconds -constexpr double LONG_SEEK_VELOCITY_INVERSE = 4.5; // inverse: s/m - namespace DVDInterface { // internal hardware addresses @@ -276,9 +252,6 @@ bool ExecuteReadCommand(u64 DVD_offset, u32 output_address, u32 DVD_length, u32 u64 PackFinishExecutingCommandUserdata(ReplyType reply_type, DIInterruptType interrupt_type); void ScheduleReads(u64 offset, u32 length, bool decrypt, u32 output_address, ReplyType reply_type); -double CalculatePhysicalDiscPosition(u64 offset); -u64 CalculateSeekTime(u64 offset_from, u64 offset_to); -u64 CalculateRawDiscReadTime(u64 offset, u64 length); void DoState(PointerWrap& p) { @@ -1191,6 +1164,8 @@ void ScheduleReads(u64 offset, u32 length, bool decrypt, u32 output_address, Rep // places, the video before the save-file select screen lags. const u64 current_time = CoreTiming::GetTicks(); + const u32 ticks_per_second = SystemTimers::GetTicksPerSecond(); + const bool wii_disc = s_inserted_volume->GetVolumeType() == DiscIO::Platform::WII_DISC; // Where the DVD read head is (usually parked at the end of the buffer, // unless we've interrupted it mid-buffer-read). @@ -1300,14 +1275,18 @@ void ScheduleReads(u64 offset, u32 length, bool decrypt, u32 output_address, Rep if (dvd_offset != head_position) { // Unbuffered seek+read - ticks_until_completion += CalculateSeekTime(head_position, dvd_offset); + ticks_until_completion += static_cast( + ticks_per_second * DVDMath::CalculateSeekTime(head_position, dvd_offset)); + DEBUG_LOG(DVDINTERFACE, "Seek+read 0x%" PRIx32 " bytes @ 0x%" PRIx64 " ticks=%" PRId64, chunk_length, offset, ticks_until_completion); } else { // Unbuffered read - ticks_until_completion += CalculateRawDiscReadTime(dvd_offset, DVD_ECC_BLOCK_SIZE); + ticks_until_completion += + static_cast(ticks_per_second * DVDMath::CalculateRawDiscReadTime( + dvd_offset, DVD_ECC_BLOCK_SIZE, wii_disc)); } unbuffered_blocks++; @@ -1350,8 +1329,10 @@ void ScheduleReads(u64 offset, u32 length, bool decrypt, u32 output_address, Rep s_read_buffer_start_time = current_time + ticks_until_completion; s_read_buffer_end_time = s_read_buffer_start_time + - CalculateRawDiscReadTime(s_read_buffer_start_offset, - s_read_buffer_end_offset - s_read_buffer_start_offset); + static_cast(ticks_per_second * + DVDMath::CalculateRawDiscReadTime( + s_read_buffer_start_offset, + s_read_buffer_end_offset - s_read_buffer_start_offset, wii_disc)); } DEBUG_LOG(DVDINTERFACE, "Schedule reads: ECC blocks unbuffered=%d, buffered=%d, " @@ -1360,124 +1341,4 @@ void ScheduleReads(u64 offset, u32 length, bool decrypt, u32 output_address, Rep ticks_until_completion * 1000000 / SystemTimers::GetTicksPerSecond()); } -// We can approximate the relationship between a byte offset on disc and its -// radial distance from the center by using an approximation for the length of -// a rolled material, which is the area of the material divided by the pitch -// (ie: assume that you can squish and deform the area of the disc into a -// rectangle as thick as the track pitch). -// -// In practice this yields good-enough numbers as a more exact formula -// involving the integral over a polar equation (too complex to describe here) -// or the approximation of a DVD as a set of concentric circles (which is a -// better approximation, but makes futher derivations more complicated than -// they need to be). -// -// From the area approximation, we end up with this formula: -// -// L = pi*(r.outer^2-r.inner^2)/pitch -// -// Where: -// L = the data track's physical length -// r.{inner,outer} = the inner/outer radii (24 mm and 58 mm) -// pitch = the track pitch (.74 um) -// -// We can then use this equation to compute the radius for a given sector in -// the disc by mapping it along the length to a linear position and inverting -// the equation and solving for r.outer (using the DVD's r.inner and pitch) -// given that linear position: -// -// r.outer = sqrt(L * pitch / pi + r.inner^2) -// -// Where: -// L = the offset's linear position, as offset/density -// r.outer = the radius for the offset -// r.inner and pitch are the same as before. -// -// The data density of the disc is just the number of bytes addressable on a -// DVD, divided by the spiral length holding that data. offset/density yields -// the linear position for a given offset. -// -// When we put it all together and simplify, we can compute the radius for a -// given byte offset as a drastically simplified: -// -// r = sqrt(offset/total_bytes*(r.outer^2-r.inner^2) + r.inner^2) -double CalculatePhysicalDiscPosition(u64 offset) -{ - // Just in case someone has an overly large disc image - // that can't exist in reality... - offset %= WII_DISC_LAYER_SIZE * 2; - - // Assumption: the layout on the second disc layer is opposite of the first, - // ie layer 2 starts where layer 1 ends and goes backwards. - 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( - static_cast(offset) / WII_DISC_LAYER_SIZE * - (WII_DVD_OUTER_RADIUS * WII_DVD_OUTER_RADIUS - DVD_INNER_RADIUS * DVD_INNER_RADIUS) + - DVD_INNER_RADIUS * DVD_INNER_RADIUS); -} - -// Returns the number of ticks to move the read head from one offset to -// another, plus the number of ticks to read one ECC block immediately -// afterwards. Based on hardware testing, this appears to be a function of the -// linear distance between the radius of the first and second positions on the -// disc, though the head speed varies depending on the length of the seek. -u64 CalculateSeekTime(u64 offset_from, u64 offset_to) -{ - const double position_from = CalculatePhysicalDiscPosition(offset_from); - const double position_to = CalculatePhysicalDiscPosition(offset_to); - - // Seek time is roughly linear based on head distance travelled - const double distance = fabs(position_from - position_to); - - double time_in_seconds; - if (distance < SHORT_SEEK_MAX_DISTANCE) - time_in_seconds = distance * SHORT_SEEK_VELOCITY_INVERSE + SHORT_SEEK_CONSTANT; - else - time_in_seconds = distance * LONG_SEEK_VELOCITY_INVERSE + LONG_SEEK_CONSTANT; - - return static_cast(time_in_seconds * SystemTimers::GetTicksPerSecond()); -} - -// Returns the number of ticks 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 -// a DMA delay on top of this, but we model that as part of this read time. -u64 CalculateRawDiscReadTime(u64 offset, u64 length) -{ - // The Wii/GC have a CAV drive and the data has a constant pit length - // regardless of location on disc. This means we can linearly interpolate - // speed from the inner to outer radius. This matches a hardware test. - // We're just picking a point halfway into the read as our benchmark for - // read speed as speeds don't change materially in this small window. - const double physical_offset = CalculatePhysicalDiscPosition(offset + length / 2); - - double speed; - if (s_inserted_volume->GetVolumeType() == DiscIO::Platform::WII_DISC) - { - speed = (physical_offset - DVD_INNER_RADIUS) / (WII_DVD_OUTER_RADIUS - DVD_INNER_RADIUS) * - (WII_DISC_OUTER_READ_SPEED - WII_DISC_INNER_READ_SPEED) + - WII_DISC_INNER_READ_SPEED; - } - else - { - speed = (physical_offset - DVD_INNER_RADIUS) / (GC_DVD_OUTER_RADIUS - DVD_INNER_RADIUS) * - (GC_DISC_OUTER_READ_SPEED - GC_DISC_INNER_READ_SPEED) + - GC_DISC_INNER_READ_SPEED; - } - - DEBUG_LOG(DVDINTERFACE, "Read 0x%" PRIx64 " @ 0x%" PRIx64 " @%lf mm: %lf us, %lf MiB/s", length, - offset, physical_offset * 1000, length / speed * 1000 * 1000, speed / 1024 / 1024); - - // (ticks/second) / (bytes/second) * bytes = ticks - const double ticks = static_cast(SystemTimers::GetTicksPerSecond()) * length / speed; - - return static_cast(ticks); -} - } // namespace diff --git a/Source/Core/Core/HW/DVD/DVDMath.cpp b/Source/Core/Core/HW/DVD/DVDMath.cpp new file mode 100644 index 0000000000..cb37aae412 --- /dev/null +++ b/Source/Core/Core/HW/DVD/DVDMath.cpp @@ -0,0 +1,154 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "Core/HW/DVD/DVDMath.h" + +#include +#include + +#include "Common/CommonTypes.h" +#include "Common/Logging/Log.h" + +namespace DVDMath +{ +// The size of the first Wii disc layer in bytes (2294912 sectors, 2048 bytes per sector) +constexpr u64 WII_DISC_LAYER_SIZE = 0x118240000; + +// 24 mm +constexpr double DVD_INNER_RADIUS = 0.024; +// 58 mm +constexpr double WII_DVD_OUTER_RADIUS = 0.058; +// 38 mm +constexpr double GC_DVD_OUTER_RADIUS = 0.038; + +// Approximate read speeds at the inner and outer locations of Wii and GC +// discs. These speeds are approximations of speeds measured on real Wiis. +constexpr double GC_DISC_INNER_READ_SPEED = 1024 * 1024 * 2.1; // bytes/s +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 + +// 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_VELOCITY_INVERSE = 50; // inverse: s/m +constexpr double LONG_SEEK_CONSTANT = 0.085; // 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 +// radial distance from the center by using an approximation for the length of +// a rolled material, which is the area of the material divided by the pitch +// (ie: assume that you can squish and deform the area of the disc into a +// rectangle as thick as the track pitch). +// +// In practice this yields good-enough numbers as a more exact formula +// involving the integral over a polar equation (too complex to describe here) +// or the approximation of a DVD as a set of concentric circles (which is a +// better approximation, but makes futher derivations more complicated than +// they need to be). +// +// From the area approximation, we end up with this formula: +// +// L = pi*(r.outer^2-r.inner^2)/pitch +// +// Where: +// L = the data track's physical length +// r.{inner,outer} = the inner/outer radii (24 mm and 58 mm) +// pitch = the track pitch (.74 um) +// +// We can then use this equation to compute the radius for a given sector in +// the disc by mapping it along the length to a linear position and inverting +// the equation and solving for r.outer (using the DVD's r.inner and pitch) +// given that linear position: +// +// r.outer = sqrt(L * pitch / pi + r.inner^2) +// +// Where: +// L = the offset's linear position, as offset/density +// r.outer = the radius for the offset +// r.inner and pitch are the same as before. +// +// The data density of the disc is just the number of bytes addressable on a +// DVD, divided by the spiral length holding that data. offset/density yields +// the linear position for a given offset. +// +// When we put it all together and simplify, we can compute the radius for a +// given byte offset as a drastically simplified: +// +// r = sqrt(offset/total_bytes*(r.outer^2-r.inner^2) + r.inner^2) +double CalculatePhysicalDiscPosition(u64 offset) +{ + // Just in case someone has an overly large disc image + // that can't exist in reality... + offset %= WII_DISC_LAYER_SIZE * 2; + + // Assumption: the layout on the second disc layer is opposite of the first, + // ie layer 2 starts where layer 1 ends and goes backwards. + 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( + static_cast(offset) / WII_DISC_LAYER_SIZE * + (WII_DVD_OUTER_RADIUS * WII_DVD_OUTER_RADIUS - DVD_INNER_RADIUS * DVD_INNER_RADIUS) + + DVD_INNER_RADIUS * DVD_INNER_RADIUS); +} + +// Returns the time in seconds to move the read head from one offset to +// another, plus the number of ticks to read one ECC block immediately +// afterwards. Based on hardware testing, this appears to be a function of the +// linear distance between the radius of the first and second positions on the +// disc, though the head speed varies depending on the length of the seek. +double CalculateSeekTime(u64 offset_from, u64 offset_to) +{ + const double position_from = CalculatePhysicalDiscPosition(offset_from); + const double position_to = CalculatePhysicalDiscPosition(offset_to); + + // Seek time is roughly linear based on head distance travelled + const double distance = fabs(position_from - position_to); + + if (distance < SHORT_SEEK_MAX_DISTANCE) + return distance * SHORT_SEEK_VELOCITY_INVERSE + SHORT_SEEK_CONSTANT; + else + return distance * LONG_SEEK_VELOCITY_INVERSE + LONG_SEEK_CONSTANT; +} + +// 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 +// a DMA delay on top of this, but we model that as part of this read time. +double CalculateRawDiscReadTime(u64 offset, u64 length, bool wii_disc) +{ + // The Wii/GC have a CAV drive and the data has a constant pit length + // regardless of location on disc. This means we can linearly interpolate + // speed from the inner to outer radius. This matches a hardware test. + // We're just picking a point halfway into the read as our benchmark for + // read speed as speeds don't change materially in this small window. + const double physical_offset = CalculatePhysicalDiscPosition(offset + length / 2); + + double speed; + if (wii_disc) + { + speed = (physical_offset - DVD_INNER_RADIUS) / (WII_DVD_OUTER_RADIUS - DVD_INNER_RADIUS) * + (WII_DISC_OUTER_READ_SPEED - WII_DISC_INNER_READ_SPEED) + + WII_DISC_INNER_READ_SPEED; + } + else + { + speed = (physical_offset - DVD_INNER_RADIUS) / (GC_DVD_OUTER_RADIUS - DVD_INNER_RADIUS) * + (GC_DISC_OUTER_READ_SPEED - GC_DISC_INNER_READ_SPEED) + + GC_DISC_INNER_READ_SPEED; + } + + DEBUG_LOG(DVDINTERFACE, "Read 0x%" PRIx64 " @ 0x%" PRIx64 " @%lf mm: %lf us, %lf MiB/s", length, + offset, physical_offset * 1000, length / speed * 1000 * 1000, speed / 1024 / 1024); + + return length / speed; +} + +} // namespace DVDMath diff --git a/Source/Core/Core/HW/DVD/DVDMath.h b/Source/Core/Core/HW/DVD/DVDMath.h new file mode 100644 index 0000000000..8c76a6db15 --- /dev/null +++ b/Source/Core/Core/HW/DVD/DVDMath.h @@ -0,0 +1,15 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include "Common/CommonTypes.h" + +namespace DVDMath +{ +double CalculatePhysicalDiscPosition(u64 offset); +double CalculateSeekTime(u64 offset_from, u64 offset_to); +double CalculateRawDiscReadTime(u64 offset, u64 length, bool wii_disc); + +} // namespace DVDMath