CDVD: Add precaching option

This commit is contained in:
Stenzek 2024-06-10 02:27:38 +10:00 committed by Connor McLaughlin
parent e1596c7911
commit 7ad27e6e9d
27 changed files with 565 additions and 59 deletions

View File

@ -101,40 +101,48 @@ void ProgressCallback::DisplayFormattedModalInformation(const char* format, ...)
ModalInformation(str.c_str());
}
class NullProgressCallbacks final : public ProgressCallback
namespace
{
public:
void PushState() override {}
void PopState() override {}
bool IsCancelled() const override { return false; }
bool IsCancellable() const override { return false; }
void SetCancellable(bool cancellable) override {}
void SetTitle(const char* title) override {}
void SetStatusText(const char* statusText) override {}
void SetProgressRange(u32 range) override {}
void SetProgressValue(u32 value) override {}
void IncrementProgressValue() override {}
void SetProgressState(ProgressState state) override {}
void DisplayError(const char* message) override { Console.Error("%s", message); }
void DisplayWarning(const char* message) override { Console.Warning("%s", message); }
void DisplayInformation(const char* message) override { Console.WriteLn("%s", message); }
void DisplayDebugMessage(const char* message) override { DevCon.WriteLn("%s", message); }
void ModalError(const char* message) override { Console.Error(message); }
bool ModalConfirmation(const char* message) override
class NullProgressCallbacks final : public ProgressCallback
{
Console.WriteLn("%s", message);
return false;
}
void ModalInformation(const char* message) override { Console.WriteLn("%s", message); }
};
public:
void PushState() override {}
void PopState() override {}
bool IsCancelled() const override { return false; }
bool IsCancellable() const override { return false; }
void SetCancellable(bool cancellable) override {}
void SetTitle(const char* title) override {}
void SetStatusText(const char* statusText) override {}
void SetProgressRange(u32 range) override {}
void SetProgressValue(u32 value) override {}
void IncrementProgressValue() override {}
void SetProgressState(ProgressState state) override {}
void DisplayError(const char* message) override { Console.Error("%s", message); }
void DisplayWarning(const char* message) override { Console.Warning("%s", message); }
void DisplayInformation(const char* message) override { Console.WriteLn("%s", message); }
void DisplayDebugMessage(const char* message) override { DevCon.WriteLn("%s", message); }
void ModalError(const char* message) override { Console.Error(message); }
bool ModalConfirmation(const char* message) override
{
Console.WriteLn("%s", message);
return false;
}
void ModalInformation(const char* message) override { Console.WriteLn("%s", message); }
};
} // namespace
static NullProgressCallbacks s_nullProgressCallbacks;
ProgressCallback* ProgressCallback::NullProgressCallback = &s_nullProgressCallbacks;
std::unique_ptr<ProgressCallback> ProgressCallback::CreateNullProgressCallback()
{
return std::make_unique<NullProgressCallbacks>();
}
BaseProgressCallback::BaseProgressCallback()
{
}
@ -171,8 +179,8 @@ void BaseProgressCallback::PopState()
// impose the current position into the previous range
const u32 new_progress_value =
(m_progress_range != 0) ?
static_cast<u32>(((float)m_progress_value / (float)m_progress_range) * (float)state->progress_range) :
state->progress_value;
static_cast<u32>(((float)m_progress_value / (float)m_progress_range) * (float)state->progress_range) :
state->progress_value;
m_cancellable = state->cancellable;
m_status_text = std::move(state->status_text);

View File

@ -3,6 +3,8 @@
#pragma once
#include "Pcsx2Defs.h"
#include <memory>
#include <string>
/**
@ -59,6 +61,8 @@ public:
public:
static ProgressCallback* NullProgressCallback;
static std::unique_ptr<ProgressCallback> CreateNullProgressCallback();
};
class BaseProgressCallback : public ProgressCallback

View File

@ -20,6 +20,7 @@
#include "common/FileSystem.h"
#include "common/MemorySettingsInterface.h"
#include "common/Path.h"
#include "common/ProgressCallback.h"
#include "common/SettingsWrapper.h"
#include "common/StringUtil.h"
@ -165,6 +166,11 @@ void Host::SetDefaultUISettings(SettingsInterface& si)
// nothing
}
std::unique_ptr<ProgressCallback> Host::CreateHostProgressCallback()
{
return ProgressCallback::CreateNullProgressCallback();
}
void Host::ReportErrorAsync(const std::string_view title, const std::string_view message)
{
if (!title.empty() && !message.empty())

View File

@ -1704,6 +1704,247 @@ void Host::SetMouseMode(bool relative_mode, bool hide_cursor)
emit g_emu_thread->onMouseModeRequested(relative_mode, hide_cursor);
}
namespace {
class QtHostProgressCallback final : public BaseProgressCallback
{
public:
QtHostProgressCallback();
~QtHostProgressCallback() override;
__fi const std::string& GetName() const { return m_name; }
void PushState() override;
void PopState() override;
bool IsCancelled() const override;
void SetCancellable(bool cancellable) override;
void SetTitle(const char* title) override;
void SetStatusText(const char* text) override;
void SetProgressRange(u32 range) override;
void SetProgressValue(u32 value) override;
void DisplayError(const char* message) override;
void DisplayWarning(const char* message) override;
void DisplayInformation(const char* message) override;
void DisplayDebugMessage(const char* message) override;
void ModalError(const char* message) override;
bool ModalConfirmation(const char* message) override;
void ModalInformation(const char* message) override;
void SetCancelled();
private:
struct SharedData
{
QProgressDialog* dialog = nullptr;
QString init_title;
QString init_status_text;
std::atomic_bool cancelled{false};
bool cancellable = true;
bool was_fullscreen = false;
};
void EnsureHasData();
static void EnsureDialogVisible(const std::shared_ptr<SharedData>& data);
void Redraw(bool force);
std::string m_name;
std::shared_ptr<SharedData> m_data;
int m_last_progress_percent = -1;
};
}
QtHostProgressCallback::QtHostProgressCallback()
: BaseProgressCallback()
{
}
QtHostProgressCallback::~QtHostProgressCallback()
{
if (m_data)
{
QtHost::RunOnUIThread([data = m_data]() {
if (!data->dialog)
return;
data->dialog->close();
delete data->dialog;
if (data->was_fullscreen)
g_emu_thread->setFullscreen(true, false);
});
}
}
void QtHostProgressCallback::PushState()
{
BaseProgressCallback::PushState();
}
void QtHostProgressCallback::PopState()
{
BaseProgressCallback::PopState();
Redraw(true);
}
void QtHostProgressCallback::SetCancellable(bool cancellable)
{
BaseProgressCallback::SetCancellable(cancellable);
EnsureHasData();
m_data->cancellable = cancellable;
}
void QtHostProgressCallback::SetTitle(const char* title)
{
EnsureHasData();
QtHost::RunOnUIThread([data = m_data, title = QString::fromUtf8(title)]() {
if (data->dialog)
data->dialog->setWindowTitle(title);
else
data->init_title = title;
});
}
void QtHostProgressCallback::SetStatusText(const char* text)
{
BaseProgressCallback::SetStatusText(text);
EnsureHasData();
QtHost::RunOnUIThread([data = m_data, text = QString::fromUtf8(text)]() {
if (data->dialog)
data->dialog->setLabelText(text);
else
data->init_status_text = text;
});
}
void QtHostProgressCallback::SetProgressRange(u32 range)
{
u32 last_range = m_progress_range;
BaseProgressCallback::SetProgressRange(range);
if (m_progress_range != last_range)
Redraw(false);
}
void QtHostProgressCallback::SetProgressValue(u32 value)
{
u32 lastValue = m_progress_value;
BaseProgressCallback::SetProgressValue(value);
if (m_progress_value != lastValue)
Redraw(false);
}
void QtHostProgressCallback::Redraw(bool force)
{
const int percent = static_cast<int>((static_cast<float>(m_progress_value) / static_cast<float>(m_progress_range)) * 100.0f);
if (percent == m_last_progress_percent && !force)
return;
// If this is the emu uthread, we need to process the un-fullscreen message.
if (g_emu_thread->isOnEmuThread())
Host::PumpMessagesOnCPUThread();
m_last_progress_percent = percent;
EnsureHasData();
QtHost::RunOnUIThread([data = m_data, percent]() {
EnsureDialogVisible(data);
data->dialog->setValue(percent);
});
}
void QtHostProgressCallback::DisplayError(const char* message)
{
Console.Error(message);
Host::ReportErrorAsync("Error", message);
}
void QtHostProgressCallback::DisplayWarning(const char* message)
{
Console.Warning(message);
}
void QtHostProgressCallback::DisplayInformation(const char* message)
{
Console.WriteLn(message);
}
void QtHostProgressCallback::DisplayDebugMessage(const char* message)
{
DevCon.WriteLn(message);
}
void QtHostProgressCallback::ModalError(const char* message)
{
Console.Error(message);
Host::ReportErrorAsync("Error", message);
}
bool QtHostProgressCallback::ModalConfirmation(const char* message)
{
return false;
}
void QtHostProgressCallback::ModalInformation(const char* message)
{
Console.WriteLn(message);
}
void QtHostProgressCallback::SetCancelled()
{
// not done here
}
bool QtHostProgressCallback::IsCancelled() const
{
return m_data && m_data->cancelled.load(std::memory_order_acquire);
}
void QtHostProgressCallback::EnsureHasData()
{
if (!m_data)
m_data = std::make_shared<SharedData>();
}
void QtHostProgressCallback::EnsureDialogVisible(const std::shared_ptr<SharedData>& data)
{
pxAssert(data);
if (data->dialog)
return;
data->was_fullscreen = g_emu_thread->isFullscreen();
if (data->was_fullscreen)
g_emu_thread->setFullscreen(false, true);
data->dialog = new QProgressDialog(data->init_status_text,
data->cancellable ? qApp->translate("QtHost", "Cancel") : QString(),
0, 100, g_main_window);
if (data->cancellable)
{
data->dialog->connect(data->dialog, &QProgressDialog::canceled,
[data]() { data->cancelled.store(true, std::memory_order_release); });
}
data->dialog->setWindowIcon(QtHost::GetAppIcon());
data->dialog->setMinimumWidth(400);
data->dialog->show();
data->dialog->raise();
data->dialog->activateWindow();
if (!data->init_title.isEmpty())
{
data->dialog->setWindowTitle(data->init_title);
data->init_title = QString();
}
}
std::unique_ptr<ProgressCallback> Host::CreateHostProgressCallback()
{
return std::make_unique<QtHostProgressCallback>();
}
//////////////////////////////////////////////////////////////////////////
// Hotkeys
//////////////////////////////////////////////////////////////////////////

View File

@ -114,7 +114,7 @@ public Q_SLOTS:
void endCapture();
void setAudioOutputVolume(int volume, int fast_forward_volume);
void setAudioOutputMuted(bool muted);
Q_SIGNALS:
bool messageConfirmed(const QString& title, const QString& message);
void statusMessage(const QString& message);

View File

@ -45,6 +45,7 @@ EmulationSettingsWidget::EmulationSettingsWidget(SettingsWindow* dialog, QWidget
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.MTVU, "EmuCore/Speedhacks", "vuThread", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.instantVU1, "EmuCore/Speedhacks", "vu1Instant", true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.fastCDVD, "EmuCore/Speedhacks", "fastCDVD", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.precacheCDVD, "EmuCore", "CdvdPrecache", false);
if (m_dialog->isPerGameSettings())
{
@ -124,6 +125,9 @@ EmulationSettingsWidget::EmulationSettingsWidget(SettingsWindow* dialog, QWidget
"Safe for most games, but a few games may exhibit graphical errors."));
dialog->registerWidgetHelp(m_ui.fastCDVD, tr("Enable Fast CDVD"), tr("Unchecked"),
tr("Fast disc access, less loading times. Check HDLoader compatibility lists for known games that have issues with this."));
dialog->registerWidgetHelp(m_ui.precacheCDVD, tr("Enable CDVD Precaching"), tr("Unchecked"),
tr("Loads the disc image into RAM before starting the virtual machine. Can reduce stutter on systems with hard drives that "
"have long wake times, but significantly increases boot times."));
dialog->registerWidgetHelp(m_ui.cheats, tr("Enable Cheats"), tr("Unchecked"),
tr("Automatically loads and applies cheats on game start."));
dialog->registerWidgetHelp(m_ui.hostFilesystem, tr("Enable Host Filesystem"), tr("Unchecked"),

View File

@ -98,13 +98,20 @@
</property>
</widget>
</item>
<item row="3" column="0">
<item row="2" column="1">
<widget class="QCheckBox" name="fastCDVD">
<property name="text">
<string>Enable Fast CDVD</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="precacheCDVD">
<property name="text">
<string>Enable CDVD Precaching</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">

View File

@ -12,8 +12,10 @@
#include "common/Assertions.h"
#include "common/Console.h"
#include "common/EnumOps.h"
#include "common/Error.h"
#include "common/FileSystem.h"
#include "common/Path.h"
#include "common/ProgressCallback.h"
#include "common/StringUtil.h"
#include <ctype.h>
@ -413,6 +415,13 @@ bool DoCDVDopen(Error* error)
return true;
}
bool DoCDVDprecache(ProgressCallback* progress, Error* error)
{
CheckNullCDVD();
progress->SetTitle(TRANSLATE("CDVD", "Precaching CDVD"));
return CDVD->precache(progress, error);
}
void DoCDVDclose()
{
CheckNullCDVD();
@ -525,6 +534,11 @@ static bool NODISCopen(std::string filename, Error* error)
return true;
}
static bool NODISCprecache(ProgressCallback* progress, Error* error)
{
return true;
}
static void NODISCclose()
{
}
@ -592,6 +606,7 @@ const CDVD_API CDVDapi_NoDisc =
{
NODISCclose,
NODISCopen,
NODISCprecache,
NODISCreadTrack,
NODISCgetBuffer,
NODISCreadSubQ,

View File

@ -8,6 +8,7 @@
#include <string>
class Error;
class ProgressCallback;
typedef struct _cdvdSubQ
{
@ -79,6 +80,7 @@ typedef struct _cdvdTN
// CDVD
typedef bool (*_CDVDopen)(std::string filename, Error* error);
typedef bool (*_CDVDprecache)(ProgressCallback* progress, Error* error);
// Initiates an asynchronous track read operation.
// Returns -1 on error (invalid track)
@ -118,6 +120,7 @@ struct CDVD_API
// Don't need init or shutdown. iso/nodisc have no init/shutdown.
_CDVDopen open;
_CDVDprecache precache;
_CDVDreadTrack readTrack;
_CDVDgetBuffer getBuffer;
_CDVDreadSubQ readSubQ;
@ -152,6 +155,7 @@ extern CDVD_SourceType CDVDsys_GetSourceType();
extern void CDVDsys_ClearFiles();
extern bool DoCDVDopen(Error* error);
extern bool DoCDVDprecache(ProgressCallback* progress, Error* error);
extern void DoCDVDclose();
extern s32 DoCDVDreadSector(u8* buffer, u32 lsn, int mode);
extern s32 DoCDVDreadTrack(u32 lsn, int mode);

View File

@ -1,8 +1,9 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include "CDVDdiscReader.h"
#include "CDVD/CDVD.h"
#include "Host.h"
#include "common/Error.h"
@ -192,6 +193,12 @@ static bool DISCopen(std::string filename, Error* error)
return true;
}
static bool DISCprecache(ProgressCallback* progress, Error* error)
{
Error::SetStringView(error, TRANSLATE_SV("CDVD", "Precaching is not supported for discs."));
return false;
}
static void DISCclose()
{
StopKeepAliveThread();
@ -531,6 +538,7 @@ const CDVD_API CDVDapi_Disc =
{
DISCclose,
DISCopen,
DISCprecache,
DISCreadTrack,
DISCgetBuffer,
DISCreadSubQ,

View File

@ -55,6 +55,11 @@ static bool ISOopen(std::string filename, Error* error)
return true;
}
static bool ISOprecache(ProgressCallback* progress, Error* error)
{
return iso.Precache(progress, error);
}
static s32 ISOreadSubQ(u32 lsn, cdvdSubQ* subq)
{
// fake it
@ -400,6 +405,7 @@ const CDVD_API CDVDapi_Iso =
ISOclose,
ISOopen,
ISOprecache,
ISOreadTrack,
ISOgetBuffer,
ISOreadSubQ,

View File

@ -8,6 +8,8 @@
#include "common/Error.h"
#include "common/FileSystem.h"
#include "common/Path.h"
#include "common/ProgressCallback.h"
#include "common/SmallString.h"
#include "common/StringUtil.h"
#include "libchdr/chd.h"
@ -188,6 +190,32 @@ bool ChdFileReader::Open2(std::string filename, Error* error)
return true;
}
bool ChdFileReader::Precache2(ProgressCallback* progress, Error* error)
{
if (!CheckAvailableMemoryForPrecaching(chd_get_compressed_size(ChdFile), error))
return false;
progress->SetProgressRange(100);
const auto callback = [](size_t pos, size_t total, void* param) -> bool {
ProgressCallback* progress = static_cast<ProgressCallback*>(param);
const u32 percent = static_cast<u32>((pos * 100) / total);
progress->SetProgressValue(std::min<u32>(percent, 100));
return !progress->IsCancelled();
};
const chd_error cerror = chd_precache_progress(ChdFile, callback, progress);
if (cerror != CHDERR_NONE)
{
if (cerror != CHDERR_CANCELLED)
Error::SetStringView(error, "Failed to read part of the file.");
return false;
}
return true;
}
ThreadedFileReader::Chunk ChdFileReader::ChunkForOffset(u64 offset)
{
Chunk chunk = {0};

View File

@ -16,6 +16,8 @@ public:
~ChdFileReader() override;
bool Open2(std::string filename, Error* error) override;
bool Precache2(ProgressCallback* progress, Error* error) override;
Chunk ChunkForOffset(u64 offset) override;
int ReadChunk(void* dst, s64 blockID) override;

View File

@ -84,6 +84,31 @@ bool CsoFileReader::Open2(std::string filename, Error* error)
return true;
}
bool CsoFileReader::Precache2(ProgressCallback* progress, Error* error)
{
if (!m_src)
return false;
const s64 size = FileSystem::FSize64(m_src);
if (size < 0 || !CheckAvailableMemoryForPrecaching(static_cast<u64>(size), error))
return false;
m_file_cache_size = static_cast<size_t>(size);
m_file_cache = std::make_unique_for_overwrite<u8[]>(m_file_cache_size);
if (FileSystem::FSeek64(m_src, 0, SEEK_SET) != 0 ||
FileSystem::ReadFileWithProgress(
m_src, m_file_cache.get(), m_file_cache_size, progress, error) != m_file_cache_size)
{
m_file_cache.reset();
return false;
}
m_readBuffer.reset();
std::fclose(m_src);
m_src = nullptr;
return true;
}
bool CsoFileReader::ReadFileHeader(Error* error)
{
CsoHeader hdr;
@ -141,11 +166,7 @@ bool CsoFileReader::InitializeBuffers(Error* error)
// initialize zlib if not a ZSO
if (!m_uselz4)
{
m_z_stream = std::make_unique<z_stream>();
m_z_stream->zalloc = Z_NULL;
m_z_stream->zfree = Z_NULL;
m_z_stream->opaque = Z_NULL;
if (inflateInit2(m_z_stream.get(), -15) != Z_OK)
if (inflateInit2(&m_z_stream, -15) != Z_OK)
{
Error::SetString(error, "Unable to initialize zlib for CSO decompression.");
return false;
@ -164,11 +185,10 @@ void CsoFileReader::Close2()
fclose(m_src);
m_src = nullptr;
}
if (m_z_stream)
{
inflateEnd(m_z_stream.get());
m_z_stream.reset();
}
if (m_file_cache)
m_file_cache.reset();
if (!m_uselz4)
inflateEnd(&m_z_stream);
m_readBuffer.reset();
m_index.reset();
@ -213,6 +233,16 @@ int CsoFileReader::ReadChunk(void* dst, s64 chunkID)
if (!compressed)
{
if (m_file_cache)
{
if (frameRawPos >= m_file_cache_size)
return 0;
const size_t read_count = std::min<size_t>(m_file_cache_size - frameRawPos, frameRawSize);
std::memcpy(dst, &m_file_cache[frameRawPos], read_count);
return static_cast<int>(read_count);
}
// Just read directly, easy.
if (FileSystem::FSeek64(m_src, frameRawPos, SEEK_SET) != 0)
{
@ -223,21 +253,36 @@ int CsoFileReader::ReadChunk(void* dst, s64 chunkID)
}
else
{
if (FileSystem::FSeek64(m_src, frameRawPos, SEEK_SET) != 0)
{
Console.Error("Unable to seek to compressed CSO data.");
return 0;
}
// This might be less bytes than frameRawSize in case of padding on the last frame.
// This is because the index positions must be aligned.
const u32 readRawBytes = fread(m_readBuffer.get(), 1, frameRawSize, m_src);
u32 readRawBytes;
u8* readBuffer;
if (m_file_cache)
{
if (frameRawPos >= m_file_cache_size)
return 0;
readRawBytes = static_cast<u32>(std::min<size_t>(m_file_cache_size - frameRawPos, frameRawSize));
readBuffer = &m_file_cache[frameRawPos];
}
else
{
if (FileSystem::FSeek64(m_src, frameRawPos, SEEK_SET) != 0)
{
Console.Error("Unable to seek to compressed CSO data.");
return 0;
}
readBuffer = m_readBuffer.get();
readRawBytes = fread(m_readBuffer.get(), 1, frameRawSize, m_src);
}
bool success = false;
if (m_uselz4)
{
const int src_size = static_cast<int>(readRawBytes);
const int dst_size = static_cast<int>(m_frameSize);
const char* src_buf = reinterpret_cast<const char*>(m_readBuffer.get());
const char* src_buf = reinterpret_cast<const char*>(readBuffer);
char* dst_buf = static_cast<char*>(dst);
const int res = LZ4_decompress_safe_partial(src_buf, dst_buf, src_size, dst_size, dst_size);
@ -245,20 +290,20 @@ int CsoFileReader::ReadChunk(void* dst, s64 chunkID)
}
else
{
m_z_stream->next_in = m_readBuffer.get();
m_z_stream->avail_in = readRawBytes;
m_z_stream->next_out = static_cast<Bytef*>(dst);
m_z_stream->avail_out = m_frameSize;
m_z_stream.next_in = readBuffer;
m_z_stream.avail_in = readRawBytes;
m_z_stream.next_out = static_cast<Bytef*>(dst);
m_z_stream.avail_out = m_frameSize;
const int status = inflate(m_z_stream.get(), Z_FINISH);
success = (status == Z_STREAM_END && m_z_stream->total_out == m_frameSize);
const int status = inflate(&m_z_stream, Z_FINISH);
success = (status == Z_STREAM_END && m_z_stream.total_out == m_frameSize);
}
if (!success)
Console.Error(fmt::format("Unable to decompress CSO frame using {}", (m_uselz4)? "lz4":"zlib"));
if (!m_uselz4)
inflateReset(m_z_stream.get());
inflateReset(&m_z_stream);
return success ? m_frameSize : 0;
}

View File

@ -7,7 +7,6 @@
#include <zlib.h>
struct CsoHeader;
typedef struct z_stream_s z_stream;
class CsoFileReader final : public ThreadedFileReader
{
@ -19,6 +18,8 @@ public:
bool Open2(std::string filename, Error* error) override;
bool Precache2(ProgressCallback* progress, Error* error) override;
Chunk ChunkForOffset(u64 offset) override;
int ReadChunk(void* dst, s64 chunkID) override;
@ -44,5 +45,7 @@ private:
u64 m_totalSize = 0;
// The actual source cso file handle.
std::FILE* m_src = nullptr;
std::unique_ptr<z_stream> m_z_stream;
std::unique_ptr<u8[]> m_file_cache;
size_t m_file_cache_size = 0;
z_stream m_z_stream = {};
};

View File

@ -38,6 +38,25 @@ bool FlatFileReader::Open2(std::string filename, Error* error)
return true;
}
bool FlatFileReader::Precache2(ProgressCallback* progress, Error* error)
{
if (!m_file || !CheckAvailableMemoryForPrecaching(m_file_size, error))
return false;
m_file_cache = std::make_unique_for_overwrite<u8[]>(m_file_size);
if (FileSystem::FSeek64(m_file, 0, SEEK_SET) != 0 ||
FileSystem::ReadFileWithProgress(
m_file, m_file_cache.get(), m_file_size, progress, error) != m_file_size)
{
m_file_cache.reset();
return false;
}
std::fclose(m_file);
m_file = nullptr;
return true;
}
ThreadedFileReader::Chunk FlatFileReader::ChunkForOffset(u64 offset)
{
ThreadedFileReader::Chunk chunk = {};
@ -61,6 +80,16 @@ int FlatFileReader::ReadChunk(void* dst, s64 blockID)
return -1;
const u64 file_offset = static_cast<u64>(blockID) * CHUNK_SIZE;
if (m_file_cache)
{
if (file_offset >= m_file_size)
return -1;
const u64 read_size = std::min<u64>(m_file_size - file_offset, CHUNK_SIZE);
std::memcpy(dst, &m_file_cache[file_offset], read_size);
return static_cast<int>(read_size);
}
if (FileSystem::FSeek64(m_file, file_offset, SEEK_SET) != 0)
return -1;

View File

@ -12,6 +12,7 @@ class FlatFileReader final : public ThreadedFileReader
DeclareNoncopyableObject(FlatFileReader);
std::FILE* m_file = nullptr;
std::unique_ptr<u8[]> m_file_cache;
u64 m_file_size = 0;
public:
@ -20,6 +21,8 @@ public:
bool Open2(std::string filename, Error* error) override;
bool Precache2(ProgressCallback* progress, Error* error) override;
Chunk ChunkForOffset(u64 offset) override;
int ReadChunk(void* dst, s64 blockID) override;

View File

@ -221,6 +221,11 @@ bool InputIsoFile::Open(std::string srcfile, Error* error)
return true;
}
bool InputIsoFile::Precache(ProgressCallback* progress, Error* error)
{
return m_reader->Precache(progress, error);
}
void InputIsoFile::Close()
{
if (m_reader)

View File

@ -10,6 +10,7 @@
#include <vector>
class Error;
class ProgressCallback;
enum isoType
{
@ -65,6 +66,7 @@ public:
}
bool Open(std::string srcfile, Error* error);
bool Precache(ProgressCallback* progress, Error* error);
void Close();
bool Detect(bool readType = true);

View File

@ -2,8 +2,15 @@
// SPDX-License-Identifier: LGPL-3.0+
#include "ThreadedFileReader.h"
#include "Host.h"
#include "common/Error.h"
#include "common/HostSys.h"
#include "common/Path.h"
#include "common/ProgressCallback.h"
#include "common/SmallString.h"
#include "common/Threading.h"
#include <cstring>
// Make sure buffer size is bigger than the cutoff where PCSX2 emulates a seek
@ -244,6 +251,36 @@ bool ThreadedFileReader::TryCachedRead(void*& buffer, u64& offset, u32& size, co
return allDone;
}
bool ThreadedFileReader::Precache(ProgressCallback* progress, Error* error)
{
CancelAndWaitUntilStopped();
progress->SetStatusText(SmallString::from_format("Precaching {}...", Path::GetFileName(m_filename)).c_str());
return Precache2(progress, error);
}
bool ThreadedFileReader::Precache2(ProgressCallback* progress, Error* error)
{
Error::SetStringView(error, "Precaching is not supported for this file format.");
return false;
}
bool ThreadedFileReader::CheckAvailableMemoryForPrecaching(u64 required_size, Error* error)
{
// Don't allow precaching to use more than 50% of system memory.
// Hopefully nobody's running 2-4GB potatoes anymore....
const u64 memory_size = GetPhysicalMemory();
const u64 max_precache_size = memory_size / 2;
if (required_size > max_precache_size)
{
Error::SetStringFmt(error,
TRANSLATE_FS("CDVD", "Required memory ({}GB) is the above the maximum allowed ({}GB)."),
required_size / _1gb, max_precache_size / _1gb);
return false;
}
return true;
}
bool ThreadedFileReader::Open(std::string filename, Error* error)
{
CancelAndWaitUntilStopped();

View File

@ -11,6 +11,7 @@
#include <condition_variable>
class Error;
class ProgressCallback;
/// A file reader for use with compressed formats
/// Calls decompression code on a separate thread to make a synchronous decompression API async
@ -42,8 +43,12 @@ protected:
virtual int ReadChunk(void* dst, s64 chunkID) = 0;
/// AsyncFileReader open but ThreadedFileReader needs prep work first
virtual bool Open2(std::string filename, Error* error) = 0;
/// AsyncFileReader precache but ThreadedFileReader needs prep work first
virtual bool Precache2(ProgressCallback* progress, Error* error);
/// AsyncFileReader close but ThreadedFileReader needs prep work first
virtual void Close2() = 0;
/// Checks system memory, to ensure that precaching would not exceed a reasonable amount.
bool CheckAvailableMemoryForPrecaching(u64 required_size, Error* error);
ThreadedFileReader();
@ -109,7 +114,9 @@ public:
virtual u32 GetBlockCount() const = 0;
bool Open(std::string filename, Error* error);
bool Precache(ProgressCallback* progress, Error* error);
int ReadSync(void* pBuffer, u32 sector, u32 count);
void BeginRead(void* pBuffer, u32 sector, u32 count);
int FinishRead();

View File

@ -1111,6 +1111,7 @@ struct Pcsx2Config
bool
CdvdVerboseReads : 1, // enables cdvd read activity verbosely dumped to the console
CdvdDumpBlocks : 1, // enables cdvd block dumping
CdvdPrecache : 1, // enables cdvd precaching of compressed images
EnablePatches : 1, // enables patch detection and application
EnableCheats : 1, // enables cheat detection and application
EnablePINE : 1, // enables inter-process communication

View File

@ -16,6 +16,7 @@
#include <string_view>
#include <vector>
class ProgressCallback;
class SettingsInterface;
namespace Host
@ -131,6 +132,9 @@ namespace Host
/// Sets host-specific default settings.
void SetDefaultUISettings(SettingsInterface& si);
/// Creates a progress callback that displays in the host.
std::unique_ptr<ProgressCallback> CreateHostProgressCallback();
namespace Internal
{
/// Retrieves the base settings layer. Must call with lock held.

View File

@ -3330,6 +3330,9 @@ void FullscreenUI::DrawEmulationSettingsPage()
"EmuCore/Speedhacks", "fastCDVD", false);
}
DrawToggleSetting(bsi, FSUI_CSTR("Enable CDVD Precaching"), FSUI_CSTR("Loads the disc image into RAM before starting the virtual machine."),
"EmuCore", "CdvdPrecache", false);
MenuHeading(FSUI_CSTR("Frame Pacing/Latency Control"));
bool optimal_frame_pacing = (bsi->GetIntValue("EmuCore/GS", "VsyncQueueSize", DEFAULT_FRAME_LATENCY) == 0);
@ -6949,6 +6952,8 @@ TRANSLATE_NOOP("FullscreenUI", "Enable Host Filesystem");
TRANSLATE_NOOP("FullscreenUI", "Enables access to files from the host: namespace in the virtual machine.");
TRANSLATE_NOOP("FullscreenUI", "Enable Fast CDVD");
TRANSLATE_NOOP("FullscreenUI", "Fast disc access, less loading times. Not recommended.");
TRANSLATE_NOOP("FullscreenUI", "Enable CDVD Precaching");
TRANSLATE_NOOP("FullscreenUI", "Loads the disc image into RAM before starting the virtual machine.");
TRANSLATE_NOOP("FullscreenUI", "Frame Pacing/Latency Control");
TRANSLATE_NOOP("FullscreenUI", "Maximum Frame Latency");
TRANSLATE_NOOP("FullscreenUI", "Sets the number of frames which can be queued.");

View File

@ -1706,6 +1706,7 @@ void Pcsx2Config::LoadSaveCore(SettingsWrapper& wrap)
SettingsWrapBitBool(CdvdVerboseReads);
SettingsWrapBitBool(CdvdDumpBlocks);
SettingsWrapBitBool(CdvdPrecache);
SettingsWrapBitBool(EnablePatches);
SettingsWrapBitBool(EnableCheats);
SettingsWrapBitBool(EnablePINE);

View File

@ -113,6 +113,7 @@ namespace VMManager
static void ReportGameChangeToHost();
static bool HasBootedELF();
static bool HasValidOrInitializingVM();
static void PrecacheCDVDFile();
static std::string GetCurrentSaveStateFileName(s32 slot);
static bool DoLoadState(const char* filename);
@ -1223,6 +1224,27 @@ bool VMManager::AutoDetectSource(const std::string& filename)
}
}
void VMManager::PrecacheCDVDFile()
{
Error error;
std::unique_ptr<ProgressCallback> progress = Host::CreateHostProgressCallback();
if (!DoCDVDprecache(progress.get(), &error))
{
if (progress->IsCancelled())
{
Host::AddIconOSDMessage("PrecacheCDVDFile", ICON_FA_COMPACT_DISC,
TRANSLATE_STR("VMManager", "CDVD precaching was cancelled."),
Host::OSD_WARNING_DURATION);
}
else
{
Host::AddIconOSDMessage("PrecacheCDVDFile", ICON_FA_EXCLAMATION_TRIANGLE,
fmt::format(TRANSLATE_FS("VMManager", "CDVD precaching failed: {}"), error.GetDescription()),
Host::OSD_ERROR_DURATION);
}
}
}
bool VMManager::Initialize(VMBootParameters boot_params)
{
const Common::Timer init_timer;
@ -1522,6 +1544,9 @@ bool VMManager::Initialize(VMBootParameters boot_params)
close_cdvd_files.Cancel();
close_state.Cancel();
if (EmuConfig.CdvdPrecache)
PrecacheCDVDFile();
hwReset();
Console.WriteLn("VM subsystems initialized in %.2f ms", init_timer.GetTimeMilliseconds());

View File

@ -11,6 +11,7 @@
#include "pcsx2/ImGui/ImGuiManager.h"
#include "pcsx2/Input/InputManager.h"
#include "pcsx2/VMManager.h"
#include "common/ProgressCallback.h"
void Host::CommitBaseSettingChanges()
{
@ -33,6 +34,11 @@ void Host::SetDefaultUISettings(SettingsInterface& si)
{
}
std::unique_ptr<ProgressCallback> Host::CreateHostProgressCallback()
{
return ProgressCallback::CreateNullProgressCallback();
}
void Host::ReportErrorAsync(const std::string_view title, const std::string_view message)
{
}