Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DVD emulation: make seek timing more accurate. #327

Merged
merged 3 commits into from
May 17, 2014
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 121 additions & 22 deletions Source/Core/Core/HW/DVDInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,17 @@
#include "Core/HW/SystemTimers.h"
#include "Core/PowerPC/PowerPC.h"

// Disc transfer rate measured in bytes per second
static const u32 DISC_TRANSFER_RATE_GC = 5 * 1024 * 1024;
// 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;

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

// Disc access time measured in milliseconds
static const u32 DISC_ACCESS_TIME_MS = 1;
static const u32 DISC_ACCESS_TIME_MS = 50;

namespace DVDInterface
{
Expand Down Expand Up @@ -201,6 +207,9 @@ bool g_bDiscInside = false;
bool g_bStream = false;
int tc = 0;

static u64 g_last_read_offset;
static u64 g_last_read_time;

// GC-AM only
static unsigned char media_buffer[0x40];

Expand All @@ -216,7 +225,8 @@ void InsertDiscCallback(u64 userdata, int cyclesLate);

void UpdateInterrupts();
void GenerateDIInterrupt(DI_InterruptType _DVDInterrupt);
void ExecuteCommand(UDICR& _DICR);
void ExecuteCommand();
void FinishExecuteRead();

void DoState(PointerWrap &p)
{
Expand All @@ -239,12 +249,15 @@ void DoState(PointerWrap &p)

p.Do(CurrentStart);
p.Do(CurrentLength);

p.Do(g_last_read_offset);
p.Do(g_last_read_time);
}

void TransferComplete(u64 userdata, int cyclesLate)
{
if (m_DICR.TSTART)
ExecuteCommand(m_DICR);
FinishExecuteRead();
}

void Init()
Expand Down Expand Up @@ -474,17 +487,7 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base)
m_DICR.Hex = val & 7;
if (m_DICR.TSTART)
{
if (!SConfig::GetInstance().m_LocalCoreStartupParameter.bFastDiscSpeed)
{
u64 ticksUntilTC = m_DILENGTH.Length *
(SystemTimers::GetTicksPerSecond() / (SConfig::GetInstance().m_LocalCoreStartupParameter.bWii ? 1 : DISC_TRANSFER_RATE_GC)) +
(SystemTimers::GetTicksPerSecond() * DISC_ACCESS_TIME_MS / 1000);
CoreTiming::ScheduleEvent((int)ticksUntilTC, tc);
}
else
{
ExecuteCommand(m_DICR);
}
ExecuteCommand();
}
})
);
Expand Down Expand Up @@ -532,7 +535,7 @@ void GenerateDIInterrupt(DI_InterruptType _DVDInterrupt)
UpdateInterrupts();
}

void ExecuteCommand(UDICR& _DICR)
void ExecuteCommand()
{
// _dbg_assert_(DVDINTERFACE, _DICR.RW == 0); // only DVD to Memory
int GCAM = ((SConfig::GetInstance().m_SIDevice[0] == SIDEVICE_AM_BASEBOARD) &&
Expand Down Expand Up @@ -650,11 +653,91 @@ void ExecuteCommand(UDICR& _DICR)
}
}

// Here is the actual Disk Reading
if (!DVDRead(iDVDOffset, m_DIMAR.Address, m_DILENGTH.Length))
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 %lld 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 %lld", 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 %lld", 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 %lld 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;

if (SConfig::GetInstance().m_LocalCoreStartupParameter.bFastDiscSpeed)
{
PanicAlertT("Can't read from DVD_Plugin - DVD-Interface: Fatal Error");
// Make the virtual DVD drive much faster than a physical drive if
// the user requests it; most games aren't very sensitive to the speed,
// and everyone likes shorter load times.
ticksUntilTC /= 8;
}

CoreTiming::ScheduleEvent((int)ticksUntilTC, tc);

// Early return; we'll finish executing the command in FinishExecuteRead.
return;
}
break;

Expand All @@ -675,7 +758,7 @@ void ExecuteCommand(UDICR& _DICR)
else
{
// there is no disc to read
_DICR.TSTART = 0;
m_DICR.TSTART = 0;
m_DILENGTH.Length = 0;
g_ErrorCode = ERROR_NO_DISK | ERROR_COVER_H;
GenerateDIInterrupt(INT_DEINT);
Expand Down Expand Up @@ -955,7 +1038,23 @@ void ExecuteCommand(UDICR& _DICR)
}

// transfer is done
_DICR.TSTART = 0;
m_DICR.TSTART = 0;
m_DILENGTH.Length = 0;
GenerateDIInterrupt(INT_TCINT);
g_ErrorCode = 0;
}

void FinishExecuteRead()
{
u32 iDVDOffset = m_DICMDBUF[1].Hex << 2;

if (!DVDRead(iDVDOffset, m_DIMAR.Address, m_DILENGTH.Length))
{
PanicAlertT("Can't read from DVD_Plugin - DVD-Interface: Fatal Error");
}

// transfer is done
m_DICR.TSTART = 0;
m_DILENGTH.Length = 0;
GenerateDIInterrupt(INT_TCINT);
g_ErrorCode = 0;
Expand Down