mirror of https://github.com/PCSX2/pcsx2.git
CDVD: Add precaching option
This commit is contained in:
parent
e1596c7911
commit
7ad27e6e9d
|
@ -101,9 +101,11 @@ void ProgressCallback::DisplayFormattedModalInformation(const char* format, ...)
|
|||
ModalInformation(str.c_str());
|
||||
}
|
||||
|
||||
class NullProgressCallbacks final : public ProgressCallback
|
||||
namespace
|
||||
{
|
||||
public:
|
||||
class NullProgressCallbacks final : public ProgressCallback
|
||||
{
|
||||
public:
|
||||
void PushState() override {}
|
||||
void PopState() override {}
|
||||
|
||||
|
@ -130,11 +132,17 @@ public:
|
|||
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()
|
||||
{
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -17,6 +17,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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
@ -222,22 +252,37 @@ int CsoFileReader::ReadChunk(void* dst, s64 chunkID)
|
|||
return fread(dst, 1, m_frameSize, m_src);
|
||||
}
|
||||
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)
|
||||
{
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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 = {};
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.");
|
||||
|
|
|
@ -1706,6 +1706,7 @@ void Pcsx2Config::LoadSaveCore(SettingsWrapper& wrap)
|
|||
|
||||
SettingsWrapBitBool(CdvdVerboseReads);
|
||||
SettingsWrapBitBool(CdvdDumpBlocks);
|
||||
SettingsWrapBitBool(CdvdPrecache);
|
||||
SettingsWrapBitBool(EnablePatches);
|
||||
SettingsWrapBitBool(EnableCheats);
|
||||
SettingsWrapBitBool(EnablePINE);
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue