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,9 +101,11 @@ void ProgressCallback::DisplayFormattedModalInformation(const char* format, ...)
ModalInformation(str.c_str()); ModalInformation(str.c_str());
} }
class NullProgressCallbacks final : public ProgressCallback namespace
{ {
public: class NullProgressCallbacks final : public ProgressCallback
{
public:
void PushState() override {} void PushState() override {}
void PopState() override {} void PopState() override {}
@ -130,11 +132,17 @@ public:
return false; return false;
} }
void ModalInformation(const char* message) override { Console.WriteLn("%s", message); } void ModalInformation(const char* message) override { Console.WriteLn("%s", message); }
}; };
} // namespace
static NullProgressCallbacks s_nullProgressCallbacks; static NullProgressCallbacks s_nullProgressCallbacks;
ProgressCallback* ProgressCallback::NullProgressCallback = &s_nullProgressCallbacks; ProgressCallback* ProgressCallback::NullProgressCallback = &s_nullProgressCallbacks;
std::unique_ptr<ProgressCallback> ProgressCallback::CreateNullProgressCallback()
{
return std::make_unique<NullProgressCallbacks>();
}
BaseProgressCallback::BaseProgressCallback() BaseProgressCallback::BaseProgressCallback()
{ {
} }

View File

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

View File

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

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.MTVU, "EmuCore/Speedhacks", "vuThread", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.instantVU1, "EmuCore/Speedhacks", "vu1Instant", true); 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.fastCDVD, "EmuCore/Speedhacks", "fastCDVD", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.precacheCDVD, "EmuCore", "CdvdPrecache", false);
if (m_dialog->isPerGameSettings()) 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.")); "Safe for most games, but a few games may exhibit graphical errors."));
dialog->registerWidgetHelp(m_ui.fastCDVD, tr("Enable Fast CDVD"), tr("Unchecked"), 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.")); 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"), dialog->registerWidgetHelp(m_ui.cheats, tr("Enable Cheats"), tr("Unchecked"),
tr("Automatically loads and applies cheats on game start.")); tr("Automatically loads and applies cheats on game start."));
dialog->registerWidgetHelp(m_ui.hostFilesystem, tr("Enable Host Filesystem"), tr("Unchecked"), dialog->registerWidgetHelp(m_ui.hostFilesystem, tr("Enable Host Filesystem"), tr("Unchecked"),

View File

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

View File

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

View File

@ -8,6 +8,7 @@
#include <string> #include <string>
class Error; class Error;
class ProgressCallback;
typedef struct _cdvdSubQ typedef struct _cdvdSubQ
{ {
@ -79,6 +80,7 @@ typedef struct _cdvdTN
// CDVD // CDVD
typedef bool (*_CDVDopen)(std::string filename, Error* error); typedef bool (*_CDVDopen)(std::string filename, Error* error);
typedef bool (*_CDVDprecache)(ProgressCallback* progress, Error* error);
// Initiates an asynchronous track read operation. // Initiates an asynchronous track read operation.
// Returns -1 on error (invalid track) // 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. // Don't need init or shutdown. iso/nodisc have no init/shutdown.
_CDVDopen open; _CDVDopen open;
_CDVDprecache precache;
_CDVDreadTrack readTrack; _CDVDreadTrack readTrack;
_CDVDgetBuffer getBuffer; _CDVDgetBuffer getBuffer;
_CDVDreadSubQ readSubQ; _CDVDreadSubQ readSubQ;
@ -152,6 +155,7 @@ extern CDVD_SourceType CDVDsys_GetSourceType();
extern void CDVDsys_ClearFiles(); extern void CDVDsys_ClearFiles();
extern bool DoCDVDopen(Error* error); extern bool DoCDVDopen(Error* error);
extern bool DoCDVDprecache(ProgressCallback* progress, Error* error);
extern void DoCDVDclose(); extern void DoCDVDclose();
extern s32 DoCDVDreadSector(u8* buffer, u32 lsn, int mode); extern s32 DoCDVDreadSector(u8* buffer, u32 lsn, int mode);
extern s32 DoCDVDreadTrack(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+ // SPDX-License-Identifier: LGPL-3.0+
#include "CDVDdiscReader.h" #include "CDVDdiscReader.h"
#include "CDVD/CDVD.h" #include "CDVD/CDVD.h"
#include "Host.h"
#include "common/Error.h" #include "common/Error.h"
@ -192,6 +193,12 @@ static bool DISCopen(std::string filename, Error* error)
return true; 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() static void DISCclose()
{ {
StopKeepAliveThread(); StopKeepAliveThread();
@ -531,6 +538,7 @@ const CDVD_API CDVDapi_Disc =
{ {
DISCclose, DISCclose,
DISCopen, DISCopen,
DISCprecache,
DISCreadTrack, DISCreadTrack,
DISCgetBuffer, DISCgetBuffer,
DISCreadSubQ, DISCreadSubQ,

View File

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

View File

@ -8,6 +8,8 @@
#include "common/Error.h" #include "common/Error.h"
#include "common/FileSystem.h" #include "common/FileSystem.h"
#include "common/Path.h" #include "common/Path.h"
#include "common/ProgressCallback.h"
#include "common/SmallString.h"
#include "common/StringUtil.h" #include "common/StringUtil.h"
#include "libchdr/chd.h" #include "libchdr/chd.h"
@ -188,6 +190,32 @@ bool ChdFileReader::Open2(std::string filename, Error* error)
return true; 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) ThreadedFileReader::Chunk ChdFileReader::ChunkForOffset(u64 offset)
{ {
Chunk chunk = {0}; Chunk chunk = {0};

View File

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

View File

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

View File

@ -7,7 +7,6 @@
#include <zlib.h> #include <zlib.h>
struct CsoHeader; struct CsoHeader;
typedef struct z_stream_s z_stream;
class CsoFileReader final : public ThreadedFileReader class CsoFileReader final : public ThreadedFileReader
{ {
@ -19,6 +18,8 @@ public:
bool Open2(std::string filename, Error* error) override; bool Open2(std::string filename, Error* error) override;
bool Precache2(ProgressCallback* progress, Error* error) override;
Chunk ChunkForOffset(u64 offset) override; Chunk ChunkForOffset(u64 offset) override;
int ReadChunk(void* dst, s64 chunkID) override; int ReadChunk(void* dst, s64 chunkID) override;
@ -44,5 +45,7 @@ private:
u64 m_totalSize = 0; u64 m_totalSize = 0;
// The actual source cso file handle. // The actual source cso file handle.
std::FILE* m_src = nullptr; 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; 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 FlatFileReader::ChunkForOffset(u64 offset)
{ {
ThreadedFileReader::Chunk chunk = {}; ThreadedFileReader::Chunk chunk = {};
@ -61,6 +80,16 @@ int FlatFileReader::ReadChunk(void* dst, s64 blockID)
return -1; return -1;
const u64 file_offset = static_cast<u64>(blockID) * CHUNK_SIZE; 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) if (FileSystem::FSeek64(m_file, file_offset, SEEK_SET) != 0)
return -1; return -1;

View File

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

View File

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

View File

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

View File

@ -2,8 +2,15 @@
// SPDX-License-Identifier: LGPL-3.0+ // SPDX-License-Identifier: LGPL-3.0+
#include "ThreadedFileReader.h" #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 "common/Threading.h"
#include <cstring> #include <cstring>
// Make sure buffer size is bigger than the cutoff where PCSX2 emulates a seek // 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; 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) bool ThreadedFileReader::Open(std::string filename, Error* error)
{ {
CancelAndWaitUntilStopped(); CancelAndWaitUntilStopped();

View File

@ -11,6 +11,7 @@
#include <condition_variable> #include <condition_variable>
class Error; class Error;
class ProgressCallback;
/// A file reader for use with compressed formats /// A file reader for use with compressed formats
/// Calls decompression code on a separate thread to make a synchronous decompression API async /// 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; virtual int ReadChunk(void* dst, s64 chunkID) = 0;
/// AsyncFileReader open but ThreadedFileReader needs prep work first /// AsyncFileReader open but ThreadedFileReader needs prep work first
virtual bool Open2(std::string filename, Error* error) = 0; 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 /// AsyncFileReader close but ThreadedFileReader needs prep work first
virtual void Close2() = 0; 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(); ThreadedFileReader();
@ -109,7 +114,9 @@ public:
virtual u32 GetBlockCount() const = 0; virtual u32 GetBlockCount() const = 0;
bool Open(std::string filename, Error* error); bool Open(std::string filename, Error* error);
bool Precache(ProgressCallback* progress, Error* error);
int ReadSync(void* pBuffer, u32 sector, u32 count); int ReadSync(void* pBuffer, u32 sector, u32 count);
void BeginRead(void* pBuffer, u32 sector, u32 count); void BeginRead(void* pBuffer, u32 sector, u32 count);
int FinishRead(); int FinishRead();

View File

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

View File

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

View File

@ -3330,6 +3330,9 @@ void FullscreenUI::DrawEmulationSettingsPage()
"EmuCore/Speedhacks", "fastCDVD", false); "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")); MenuHeading(FSUI_CSTR("Frame Pacing/Latency Control"));
bool optimal_frame_pacing = (bsi->GetIntValue("EmuCore/GS", "VsyncQueueSize", DEFAULT_FRAME_LATENCY) == 0); 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", "Enables access to files from the host: namespace in the virtual machine.");
TRANSLATE_NOOP("FullscreenUI", "Enable Fast CDVD"); TRANSLATE_NOOP("FullscreenUI", "Enable Fast CDVD");
TRANSLATE_NOOP("FullscreenUI", "Fast disc access, less loading times. Not recommended."); 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", "Frame Pacing/Latency Control");
TRANSLATE_NOOP("FullscreenUI", "Maximum Frame Latency"); TRANSLATE_NOOP("FullscreenUI", "Maximum Frame Latency");
TRANSLATE_NOOP("FullscreenUI", "Sets the number of frames which can be queued."); 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(CdvdVerboseReads);
SettingsWrapBitBool(CdvdDumpBlocks); SettingsWrapBitBool(CdvdDumpBlocks);
SettingsWrapBitBool(CdvdPrecache);
SettingsWrapBitBool(EnablePatches); SettingsWrapBitBool(EnablePatches);
SettingsWrapBitBool(EnableCheats); SettingsWrapBitBool(EnableCheats);
SettingsWrapBitBool(EnablePINE); SettingsWrapBitBool(EnablePINE);

View File

@ -113,6 +113,7 @@ namespace VMManager
static void ReportGameChangeToHost(); static void ReportGameChangeToHost();
static bool HasBootedELF(); static bool HasBootedELF();
static bool HasValidOrInitializingVM(); static bool HasValidOrInitializingVM();
static void PrecacheCDVDFile();
static std::string GetCurrentSaveStateFileName(s32 slot); static std::string GetCurrentSaveStateFileName(s32 slot);
static bool DoLoadState(const char* filename); 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) bool VMManager::Initialize(VMBootParameters boot_params)
{ {
const Common::Timer init_timer; const Common::Timer init_timer;
@ -1522,6 +1544,9 @@ bool VMManager::Initialize(VMBootParameters boot_params)
close_cdvd_files.Cancel(); close_cdvd_files.Cancel();
close_state.Cancel(); close_state.Cancel();
if (EmuConfig.CdvdPrecache)
PrecacheCDVDFile();
hwReset(); hwReset();
Console.WriteLn("VM subsystems initialized in %.2f ms", init_timer.GetTimeMilliseconds()); Console.WriteLn("VM subsystems initialized in %.2f ms", init_timer.GetTimeMilliseconds());

View File

@ -11,6 +11,7 @@
#include "pcsx2/ImGui/ImGuiManager.h" #include "pcsx2/ImGui/ImGuiManager.h"
#include "pcsx2/Input/InputManager.h" #include "pcsx2/Input/InputManager.h"
#include "pcsx2/VMManager.h" #include "pcsx2/VMManager.h"
#include "common/ProgressCallback.h"
void Host::CommitBaseSettingChanges() 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) void Host::ReportErrorAsync(const std::string_view title, const std::string_view message)
{ {
} }