summaryrefslogtreecommitdiff
path: root/Source/Core
diff options
context:
space:
mode:
authorskidau <skidau@gmail.com>2014-11-20 13:30:02 +1100
committerskidau <skidau@gmail.com>2014-11-20 13:30:02 +1100
commitba1a8d2ef3e5dd602fa9ac054bcc787d7b400b16 (patch)
tree5e7099204be5c485b8a803a13ab8ddf8f58dfc15 /Source/Core
parentfb50cb6d99062069a71db49d56d975df5c6eecb3 (diff)
parent091db361620cc1cd316b9366b39e8d6a59acaec9 (diff)
Merge pull request #1517 from JosJuice/cav-disc-drive
DVDInterface: Timing accuracy improvements
Diffstat (limited to 'Source/Core')
-rw-r--r--Source/Core/Core/HW/DVDInterface.cpp303
1 files changed, 198 insertions, 105 deletions
diff --git a/Source/Core/Core/HW/DVDInterface.cpp b/Source/Core/Core/HW/DVDInterface.cpp
index 129ed3867d..825c9bce8a 100644
--- a/Source/Core/Core/HW/DVDInterface.cpp
+++ b/Source/Core/Core/HW/DVDInterface.cpp
@@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include <cinttypes>
+#include <cmath>
#include "AudioCommon/AudioCommon.h"
@@ -23,18 +24,55 @@
#include "Core/HW/SystemTimers.h"
#include "Core/PowerPC/PowerPC.h"
-// A GameCube disc can be read at somewhere between
-// 2 and 3MB/sec, depending on the location on disk. Wii disks
-// not yet tested.
-static const u32 DISC_TRANSFER_RATE_GC = 3 * 1024 * 1024;
+static const double PI = 3.14159265358979323846264338328;
// Rate the drive can transfer data to main memory, given the data
-// is already buffered.
-static const u32 BUFFER_TRANSFER_RATE_GC = 16 * 1024 * 1024;
+// is already buffered. Measured in bytes per second.
+static const u32 BUFFER_TRANSFER_RATE = 1024 * 1024 * 16;
// Disc access time measured in milliseconds
static const u32 DISC_ACCESS_TIME_MS = 50;
+// The size of a Wii disc layer in bytes (is this correct?)
+static const u64 WII_DISC_LAYER_SIZE = 4699979776;
+
+// By knowing the disc read speed at two locations defined here,
+// the program can calulate the speed at arbitrary locations.
+// Offsets are in bytes, and speeds are in bytes per second.
+//
+// These speeds are approximate. Using exact speeds is not possible
+// because of how much variation there is between different hardware.
+
+static const u32 GC_DISC_LOCATION_1_OFFSET = 0; // The beginning of a GC disc
+static const u32 GC_DISC_LOCATION_1_READ_SPEED = 1024 * 1024 * 2;
+static const u32 GC_DISC_LOCATION_2_OFFSET = 1459978239; // The end of a GC disc
+static const u32 GC_DISC_LOCATION_2_READ_SPEED = (u32)(1024 * 1024 * 3.3);
+
+static const u32 WII_DISC_LOCATION_1_OFFSET = 0; // The beginning of a Wii disc
+static const u32 WII_DISC_LOCATION_1_READ_SPEED = (u32)(1024 * 1024 * 3.5);
+static const u64 WII_DISC_LOCATION_2_OFFSET = WII_DISC_LAYER_SIZE; // The end of a Wii disc
+static const u32 WII_DISC_LOCATION_2_READ_SPEED = 1024 * 1024 * 9;
+
+// These values are used for disc read speed calculations. Calculations
+// are done using an arbitrary length unit where the radius of a disc track
+// is the same as the read speed at that track in bytes per second.
+
+static const double GC_DISC_AREA_UP_TO_LOCATION_1 =
+ PI * GC_DISC_LOCATION_1_READ_SPEED * GC_DISC_LOCATION_1_READ_SPEED;
+static const double GC_DISC_AREA_UP_TO_LOCATION_2 =
+ PI * GC_DISC_LOCATION_2_READ_SPEED * GC_DISC_LOCATION_2_READ_SPEED;
+static const double GC_BYTES_PER_AREA_UNIT =
+ (GC_DISC_LOCATION_2_OFFSET - GC_DISC_LOCATION_1_OFFSET) /
+ (GC_DISC_AREA_UP_TO_LOCATION_2 - GC_DISC_AREA_UP_TO_LOCATION_1);
+
+static const double WII_DISC_AREA_UP_TO_LOCATION_1 =
+ PI * WII_DISC_LOCATION_1_READ_SPEED * WII_DISC_LOCATION_1_READ_SPEED;
+static const double WII_DISC_AREA_UP_TO_LOCATION_2 =
+ PI * WII_DISC_LOCATION_2_READ_SPEED * WII_DISC_LOCATION_2_READ_SPEED;
+static const double WII_BYTES_PER_AREA_UNIT =
+ (WII_DISC_LOCATION_2_OFFSET - WII_DISC_LOCATION_1_OFFSET) /
+ (WII_DISC_AREA_UP_TO_LOCATION_2 - WII_DISC_AREA_UP_TO_LOCATION_1);
+
namespace DVDInterface
{
@@ -230,6 +268,8 @@ void UpdateInterrupts();
void GenerateDIInterrupt(DI_InterruptType _DVDInterrupt);
void ExecuteCommand();
void FinishExecuteRead();
+u64 SimulateDiscReadTime();
+s64 CalculateRawDiscReadTime(u64 offset, s64 length);
void DoState(PointerWrap &p)
{
@@ -262,7 +302,12 @@ void DoState(PointerWrap &p)
static void TransferComplete(u64 userdata, int cyclesLate)
{
if (m_DICR.TSTART)
- FinishExecuteRead();
+ {
+ m_DICR.TSTART = 0;
+ m_DILENGTH.Length = 0;
+ GenerateDIInterrupt(INT_TCINT);
+ g_ErrorCode = 0;
+ }
}
static u32 ProcessDTKSamples(short *tempPCM, u32 num_samples)
@@ -564,6 +609,11 @@ void GenerateDIInterrupt(DI_InterruptType _DVDInterrupt)
void ExecuteCommand()
{
+ // This variable is used to simulate the time is takes to execute a command.
+ // 1 / 15000 seconds is just some arbitrary default value.
+ // Commands that implement more precise timing are supposed to overwrite this.
+ u64 ticks_until_TC = SystemTimers::GetTicksPerSecond() / 15000;
+
// _dbg_assert_(DVDINTERFACE, _DICR.RW == 0); // only DVD to Memory
int GCAM = ((SConfig::GetInstance().m_SIDevice[0] == SIDEVICE_AM_BASEBOARD) &&
(SConfig::GetInstance().m_EXIDevice[2] == EXIDEVICE_AM_BASEBOARD))
@@ -681,93 +731,13 @@ void ExecuteCommand()
}
}
- u64 ticksUntilTC = 0;
-
- // The drive buffers 1MB (?) of data after every read request;
- // if a read request is covered by this buffer (or if it's
- // faster to wait for the data to be buffered), the drive
- // doesn't seek; it returns buffered data. Data can be
- // transferred from the buffer at up to 16MB/sec.
- //
- // If the drive has to seek, the time this takes varies a lot.
- // A short seek is around 50ms; a long seek is around 150ms.
- // However, the time isn't purely dependent on the distance; the
- // pattern of previous seeks seems to matter in a way I'm
- // not sure how to explain.
- //
- // Metroid Prime is a good example of a game that's sensitive to
- // all of these details; if there isn't enough latency in the
- // right places, doors open too quickly, and if there's too
- // much latency in the wrong places, the video before the
- // save-file select screen lags.
- //
- // For now, just use a very rough approximation: 50ms seek
- // and 3MB/sec for reads outside 1MB, acceleated reads
- // within 1MB. We can refine this if someone comes up
- // with a more complete model for seek times.
-
- u64 cur_time = CoreTiming::GetTicks();
- // Number of ticks it takes to seek and read directly from the disk.
- u64 disk_read_duration = m_DILENGTH.Length *
- (SystemTimers::GetTicksPerSecond() / DISC_TRANSFER_RATE_GC) +
- SystemTimers::GetTicksPerSecond() / 1000 * DISC_ACCESS_TIME_MS;
-
- if (iDVDOffset + m_DILENGTH.Length - g_last_read_offset > 1024 * 1024)
- {
- // No buffer; just use the simple seek time + read time.
- DEBUG_LOG(DVDINTERFACE, "Seeking %" PRId64 " bytes", s64(g_last_read_offset) - s64(iDVDOffset));
- ticksUntilTC = disk_read_duration;
- g_last_read_time = cur_time + ticksUntilTC;
- }
- else
- {
- // Possibly buffered; use the buffer if it saves time.
- // It's not proven that the buffer actually behaves like this, but
- // it appears to be a decent approximation.
-
- // Time at which the buffer will contain the data we need.
- u64 buffer_fill_time = (iDVDOffset + m_DILENGTH.Length - g_last_read_offset) *
- (SystemTimers::GetTicksPerSecond() / DISC_TRANSFER_RATE_GC) +
- g_last_read_time;
- // Number of ticks it takes to transfer the data from the buffer to memory.
- u64 buffer_read_duration = m_DILENGTH.Length *
- (SystemTimers::GetTicksPerSecond() / BUFFER_TRANSFER_RATE_GC);
-
- if (cur_time > buffer_fill_time)
- {
- DEBUG_LOG(DVDINTERFACE, "Fast buffer read at %" PRId64, s64(iDVDOffset));
- ticksUntilTC = buffer_read_duration;
- g_last_read_time = buffer_fill_time;
- }
- else if (cur_time + disk_read_duration > buffer_fill_time)
- {
- DEBUG_LOG(DVDINTERFACE, "Slow buffer read at %" PRId64, s64(iDVDOffset));
- ticksUntilTC = std::max(buffer_fill_time - cur_time, buffer_read_duration);
- g_last_read_time = buffer_fill_time;
- }
- else
- {
- DEBUG_LOG(DVDINTERFACE, "Short seek %" PRId64 " bytes", s64(g_last_read_offset) - s64(iDVDOffset));
- ticksUntilTC = disk_read_duration;
- g_last_read_time = cur_time + ticksUntilTC;
- }
- }
- g_last_read_offset = (iDVDOffset + m_DILENGTH.Length - 2048) & ~2047;
+ ticks_until_TC = SimulateDiscReadTime();
- if (SConfig::GetInstance().m_LocalCoreStartupParameter.bFastDiscSpeed)
+ // Here is the actual disc reading
+ if (!DVDRead(iDVDOffset, m_DIMAR.Address, m_DILENGTH.Length))
{
- // Make sure fast disc speed performs "instant" reads; in addition
- // to being used to speed up games, fast disc speed is used as a
- // workaround for crashes in certain games, including Star Wars
- // Rogue Leader.
- FinishExecuteRead();
- return;
+ PanicAlertT("Can't read from DVD_Plugin - DVD-Interface: Fatal Error");
}
-
- CoreTiming::ScheduleEvent((int)ticksUntilTC, tc);
-
- // Early return; we'll finish executing the command in FinishExecuteRead.
- return;
}
break;
@@ -1076,27 +1046,150 @@ void ExecuteCommand()
break;
}
- // transfer is done
- m_DICR.TSTART = 0;
- m_DILENGTH.Length = 0;
- GenerateDIInterrupt(INT_TCINT);
- g_ErrorCode = 0;
+ // The transfer is finished after a delay
+ CoreTiming::ScheduleEvent((int)ticks_until_TC, tc);
}
-void FinishExecuteRead()
+// Simulates the timing aspects of reading data from a disc.
+// Sets g_last_read_offset and g_last_read_time, and returns ticks_until_TC.
+u64 SimulateDiscReadTime()
{
- u32 iDVDOffset = m_DICMDBUF[1].Hex << 2;
+ u64 DVD_offset = (u64)m_DICMDBUF[1].Hex << 2;
+ u64 current_time = CoreTiming::GetTicks();
+ u64 ticks_until_TC;
- if (!DVDRead(iDVDOffset, m_DIMAR.Address, m_DILENGTH.Length))
+ if (SConfig::GetInstance().m_LocalCoreStartupParameter.bFastDiscSpeed)
{
- PanicAlertT("Can't read from DVD_Plugin - DVD-Interface: Fatal Error");
+ // Make sure fast disc speed performs "instant" reads; in addition
+ // to being used to speed up games, fast disc speed is used as a
+ // workaround for crashes in certain games, including Star Wars
+ // Rogue Leader.
+ ticks_until_TC = 0;
+ g_last_read_time = current_time;
+ }
+ else
+ {
+ // The drive buffers 1 MiB (?) of data after every read request;
+ // if a read request is covered by this buffer (or if it's
+ // faster to wait for the data to be buffered), the drive
+ // doesn't seek; it returns buffered data. Data can be
+ // transferred from the buffer at up to 16 MiB/s.
+ //
+ // If the drive has to seek, the time this takes varies a lot.
+ // A short seek is around 50 ms; a long seek is around 150 ms.
+ // However, the time isn't purely dependent on the distance; the
+ // pattern of previous seeks seems to matter in a way I'm
+ // not sure how to explain.
+ //
+ // Metroid Prime is a good example of a game that's sensitive to
+ // all of these details; if there isn't enough latency in the
+ // right places, doors open too quickly, and if there's too
+ // much latency in the wrong places, the video before the
+ // save-file select screen lags.
+ //
+ // For now, just use a very rough approximation: 50 ms seek
+ // for reads outside 1 MiB, accelerated reads within 1 MiB.
+ // We can refine this if someone comes up with a more complete
+ // model for seek times.
+
+ // Number of ticks it takes to seek and read directly from the disk.
+ u64 disk_read_duration = CalculateRawDiscReadTime(DVD_offset, m_DILENGTH.Length) +
+ SystemTimers::GetTicksPerSecond() / 1000 * DISC_ACCESS_TIME_MS;
+
+ if (DVD_offset + m_DILENGTH.Length - g_last_read_offset > 1024 * 1024)
+ {
+ // No buffer; just use the simple seek time + read time.
+ DEBUG_LOG(DVDINTERFACE, "Seeking %" PRId64 " bytes",
+ s64(g_last_read_offset) - s64(DVD_offset));
+ ticks_until_TC = disk_read_duration;
+ g_last_read_time = current_time + ticks_until_TC;
+ }
+ else
+ {
+ // Possibly buffered; use the buffer if it saves time.
+ // It's not proven that the buffer actually behaves like this, but
+ // it appears to be a decent approximation.
+
+ // Time at which the buffer will contain the data we need.
+ u64 buffer_fill_time = g_last_read_time +
+ CalculateRawDiscReadTime(g_last_read_offset,
+ DVD_offset + m_DILENGTH.Length - g_last_read_offset);
+ // Number of ticks it takes to transfer the data from the buffer to memory.
+ u64 buffer_read_duration = m_DILENGTH.Length *
+ (SystemTimers::GetTicksPerSecond() / BUFFER_TRANSFER_RATE);
+
+ if (current_time > buffer_fill_time)
+ {
+ DEBUG_LOG(DVDINTERFACE, "Fast buffer read at %" PRId64, s64(DVD_offset));
+ ticks_until_TC = buffer_read_duration;
+ g_last_read_time = buffer_fill_time;
+ }
+ else if (current_time + disk_read_duration > buffer_fill_time)
+ {
+ DEBUG_LOG(DVDINTERFACE, "Slow buffer read at %" PRId64, s64(DVD_offset));
+ ticks_until_TC = std::max(buffer_fill_time - current_time,
+ buffer_read_duration);
+ g_last_read_time = buffer_fill_time;
+ }
+ else
+ {
+ DEBUG_LOG(DVDINTERFACE, "Short seek %" PRId64 " bytes",
+ s64(g_last_read_offset) - s64(DVD_offset));
+ ticks_until_TC = disk_read_duration;
+ g_last_read_time = current_time + ticks_until_TC;
+ }
+ }
}
- // transfer is done
- m_DICR.TSTART = 0;
- m_DILENGTH.Length = 0;
- GenerateDIInterrupt(INT_TCINT);
- g_ErrorCode = 0;
+ g_last_read_offset = (DVD_offset + m_DILENGTH.Length - 2048) & ~2047;
+
+ return ticks_until_TC;
+}
+
+// Returns the number of ticks it takes to read an amount of
+// data from a disc, ignoring factors such as seek times.
+// The result will be negative if the length is negative.
+s64 CalculateRawDiscReadTime(u64 offset, s64 length)
+{
+ // The speed will be calculated using the average offset. This is a bit
+ // inaccurate since the speed doesn't increase linearly with the offset,
+ // but since reads only span a small part of the disc, it's insignificant.
+ u64 average_offset = offset + (length / 2);
+
+ // Here, addresses on the second layer of Wii discs are replaced with equivalent
+ // addresses on the first layer so that the speed calculation works correctly.
+ // This is wrong for reads spanning two layers, but those should be rare.
+ average_offset %= WII_DISC_LAYER_SIZE;
+
+ // The area on the disc between position 1 and the arbitrary position X is:
+ // LOCATION_X_SPEED * LOCATION_X_SPEED * pi - AREA_UP_TO_LOCATION_1
+ //
+ // The number of bytes between position 1 and position X is:
+ // LOCATION_X_OFFSET - LOCATION_1_OFFSET
+ //
+ // This means that the following equation is true:
+ // (LOCATION_X_SPEED * LOCATION_X_SPEED * pi - AREA_UP_TO_LOCATION_1) *
+ // BYTES_PER_AREA_UNIT = LOCATION_X_OFFSET - LOCATION_1_OFFSET
+ //
+ // Solving this equation for LOCATION_X_SPEED results in this:
+ // LOCATION_X_SPEED = sqrt(((LOCATION_X_OFFSET - LOCATION_1_OFFSET) /
+ // BYTES_PER_AREA_UNIT + AREA_UP_TO_LOCATION_1) / pi)
+ //
+ // Note that the speed at a track (in bytes per second) is the same as
+ // the radius of that track because of the length unit used.
+ double speed;
+ if (VolumeHandler::IsWii())
+ {
+ speed = std::sqrt(((average_offset - WII_DISC_LOCATION_1_OFFSET) /
+ WII_BYTES_PER_AREA_UNIT + WII_DISC_AREA_UP_TO_LOCATION_1) / PI);
+ }
+ else
+ {
+ speed = std::sqrt(((average_offset - GC_DISC_LOCATION_1_OFFSET) /
+ GC_BYTES_PER_AREA_UNIT + GC_DISC_AREA_UP_TO_LOCATION_1) / PI);
+ }
+
+ return (s64)(SystemTimers::GetTicksPerSecond() / speed * length);
}
} // namespace