mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-06-09 00:37:50 +00:00
Compare commits
35 Commits
39525d6abd
...
c762b7a210
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c762b7a210 | ||
![]() |
52fcdde485 | ||
![]() |
5eb61024c6 | ||
![]() |
a27b845514 | ||
![]() |
1b1ca019a4 | ||
![]() |
241834709b | ||
![]() |
185b080f03 | ||
![]() |
c3d3b81533 | ||
![]() |
774a84a953 | ||
![]() |
b3f50c969e | ||
![]() |
3b83907b88 | ||
![]() |
d940d62cae | ||
![]() |
7afa9e6c6f | ||
![]() |
12d178a8df | ||
![]() |
f910c1d934 | ||
![]() |
70abcb2030 | ||
![]() |
d8ea31ca46 | ||
![]() |
2ae43324cb | ||
![]() |
7d59c2743d | ||
![]() |
8113399b68 | ||
![]() |
bafe78203d | ||
![]() |
15f125ebee | ||
![]() |
316740daed | ||
![]() |
903eafcf65 | ||
![]() |
2a7e8a4003 | ||
![]() |
9ec69b5925 | ||
![]() |
d7de49ccf6 | ||
![]() |
5ec5db9240 | ||
![]() |
974c56d3c5 | ||
![]() |
ae26b38fc0 | ||
![]() |
2de9122b5f | ||
![]() |
a6a5e201b6 | ||
![]() |
ec29d120b5 | ||
![]() |
553a75c996 | ||
![]() |
d48e6e25ad |
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -84,6 +84,9 @@
|
||||
[submodule "Externals/Vulkan-Headers"]
|
||||
path = Externals/Vulkan-Headers
|
||||
url = https://github.com/KhronosGroup/Vulkan-Headers.git
|
||||
[submodule "Externals/watcher/watcher"]
|
||||
path = Externals/watcher/watcher
|
||||
url = https://github.com/e-dant/watcher.git
|
||||
[submodule "Externals/SFML/SFML"]
|
||||
path = Externals/SFML/SFML
|
||||
url = https://github.com/SFML/SFML.git
|
||||
|
@ -2,6 +2,18 @@ cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
# for revision info
|
||||
if(GIT_FOUND)
|
||||
# Determine whether upstream/master exists
|
||||
execute_process(WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} ls-remote --heads upstream master
|
||||
RESULT_VARIABLE UPSTREAM_RESULT
|
||||
OUTPUT_VARIABLE UPSTREAM_OUTPUT
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
if(UPSTREAM_RESULT EQUAL 0 AND UPSTREAM_OUTPUT)
|
||||
set(BASE_BRANCH "upstream/master")
|
||||
else()
|
||||
set(BASE_BRANCH "master")
|
||||
endif()
|
||||
|
||||
# defines DOLPHIN_WC_REVISION
|
||||
execute_process(WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} rev-parse HEAD
|
||||
OUTPUT_VARIABLE DOLPHIN_WC_REVISION
|
||||
@ -19,7 +31,7 @@ if(GIT_FOUND)
|
||||
OUTPUT_VARIABLE DOLPHIN_WC_BRANCH
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
# defines DOLPHIN_WC_COMMITS_AHEAD_MASTER
|
||||
execute_process(WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} rev-list --count HEAD ^master
|
||||
execute_process(WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} COMMAND ${GIT_EXECUTABLE} rev-list --count HEAD ^${BASE_BRANCH}
|
||||
OUTPUT_VARIABLE DOLPHIN_WC_COMMITS_AHEAD_MASTER
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
|
@ -784,6 +784,8 @@ if (USE_RETRO_ACHIEVEMENTS)
|
||||
add_subdirectory(Externals/rcheevos)
|
||||
endif()
|
||||
|
||||
add_subdirectory(Externals/watcher)
|
||||
|
||||
########################################
|
||||
# Pre-build events: Define configuration variables and write SCM info header
|
||||
#
|
||||
|
4
Externals/watcher/CMakeLists.txt
vendored
Normal file
4
Externals/watcher/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
add_library(watcher INTERFACE IMPORTED GLOBAL)
|
||||
set_target_properties(watcher PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_LIST_DIR}/watcher/include
|
||||
)
|
1
Externals/watcher/watcher
vendored
Submodule
1
Externals/watcher/watcher
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit b03bdcfc11549df595b77239cefe2643943a3e2f
|
@ -64,6 +64,8 @@ add_library(common
|
||||
FatFsUtil.h
|
||||
FileSearch.cpp
|
||||
FileSearch.h
|
||||
FilesystemWatcher.cpp
|
||||
FilesystemWatcher.h
|
||||
FileUtil.cpp
|
||||
FileUtil.h
|
||||
FixedSizeQueue.h
|
||||
@ -184,6 +186,7 @@ PRIVATE
|
||||
FatFs
|
||||
Iconv::Iconv
|
||||
spng::spng
|
||||
watcher
|
||||
${VTUNE_LIBRARIES}
|
||||
)
|
||||
|
||||
|
67
Source/Core/Common/FilesystemWatcher.cpp
Normal file
67
Source/Core/Common/FilesystemWatcher.cpp
Normal file
@ -0,0 +1,67 @@
|
||||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "Common/FilesystemWatcher.h"
|
||||
|
||||
#include <wtr/watcher.hpp>
|
||||
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/StringUtil.h"
|
||||
|
||||
namespace Common
|
||||
{
|
||||
FilesystemWatcher::FilesystemWatcher() = default;
|
||||
FilesystemWatcher::~FilesystemWatcher() = default;
|
||||
|
||||
void FilesystemWatcher::Watch(const std::string& path)
|
||||
{
|
||||
const auto [iter, inserted] = m_watched_paths.try_emplace(path, nullptr);
|
||||
if (inserted)
|
||||
{
|
||||
iter->second = std::make_unique<wtr::watch>(path, [this](wtr::event e) {
|
||||
const auto watched_path = PathToString(e.path_name);
|
||||
if (e.path_type == wtr::event::path_type::watcher)
|
||||
{
|
||||
if (watched_path.starts_with('e'))
|
||||
ERROR_LOG_FMT(COMMON, "Filesystem watcher: '{}'", watched_path);
|
||||
else if (watched_path.starts_with('w'))
|
||||
WARN_LOG_FMT(COMMON, "Filesystem watcher: '{}'", watched_path);
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.effect_type == wtr::event::effect_type::create)
|
||||
{
|
||||
const auto path = WithUnifiedPathSeparators(watched_path);
|
||||
PathAdded(path);
|
||||
}
|
||||
else if (e.effect_type == wtr::event::effect_type::modify)
|
||||
{
|
||||
const auto path = WithUnifiedPathSeparators(watched_path);
|
||||
PathModified(path);
|
||||
}
|
||||
else if (e.effect_type == wtr::event::effect_type::rename)
|
||||
{
|
||||
if (!e.associated)
|
||||
{
|
||||
WARN_LOG_FMT(COMMON, "Rename on path '{}' seen without association!", watched_path);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto old_path = WithUnifiedPathSeparators(watched_path);
|
||||
const auto new_path = WithUnifiedPathSeparators(PathToString(e.associated->path_name));
|
||||
PathRenamed(old_path, new_path);
|
||||
}
|
||||
else if (e.effect_type == wtr::event::effect_type::destroy)
|
||||
{
|
||||
const auto path = WithUnifiedPathSeparators(watched_path);
|
||||
PathDeleted(path);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void FilesystemWatcher::Unwatch(const std::string& path)
|
||||
{
|
||||
m_watched_paths.erase(path);
|
||||
}
|
||||
} // namespace Common
|
47
Source/Core/Common/FilesystemWatcher.h
Normal file
47
Source/Core/Common/FilesystemWatcher.h
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace wtr
|
||||
{
|
||||
inline namespace watcher
|
||||
{
|
||||
class watch;
|
||||
}
|
||||
} // namespace wtr
|
||||
|
||||
namespace Common
|
||||
{
|
||||
// A class that can watch a path and receive callbacks
|
||||
// when files or directories underneath that path receive events
|
||||
class FilesystemWatcher
|
||||
{
|
||||
public:
|
||||
FilesystemWatcher();
|
||||
virtual ~FilesystemWatcher();
|
||||
|
||||
void Watch(const std::string& path);
|
||||
void Unwatch(const std::string& path);
|
||||
|
||||
private:
|
||||
// A new file or folder was added to one of the watched paths
|
||||
virtual void PathAdded(std::string_view path) {}
|
||||
|
||||
// A file or folder was modified in one of the watched paths
|
||||
virtual void PathModified(std::string_view path) {}
|
||||
|
||||
// A file or folder was renamed in one of the watched paths
|
||||
virtual void PathRenamed(std::string_view old_path, std::string_view new_path) {}
|
||||
|
||||
// A file or folder was deleted in one of the watched paths
|
||||
virtual void PathDeleted(std::string_view path) {}
|
||||
|
||||
std::map<std::string, std::unique_ptr<wtr::watch>> m_watched_paths;
|
||||
};
|
||||
} // namespace Common
|
@ -5,9 +5,18 @@ var outfile = "./scmrev.h";
|
||||
var cmd_revision = " rev-parse HEAD";
|
||||
var cmd_describe = " describe --always --long --dirty";
|
||||
var cmd_branch = " rev-parse --abbrev-ref HEAD";
|
||||
var cmd_commits_ahead = " rev-list --count HEAD ^master";
|
||||
var cmd_get_tag = " describe --exact-match HEAD";
|
||||
|
||||
function RemoteBranchExists(remote) {
|
||||
var cmd = gitexe + " ls-remote --heads " + remote + " master";
|
||||
try {
|
||||
var output = wshShell.Exec(cmd).StdOut.ReadLine();
|
||||
return output.trim() !== "";
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function GetGitExe()
|
||||
{
|
||||
try
|
||||
@ -92,8 +101,12 @@ function GetFileContents(f)
|
||||
}
|
||||
}
|
||||
|
||||
// get info from git
|
||||
var gitexe = GetGitExe();
|
||||
|
||||
var base_branch = RemoteBranchExists("upstream") ? "upstream/master" : "master";
|
||||
var cmd_commits_ahead = " rev-list --count HEAD ^" + base_branch;
|
||||
|
||||
// get info from git
|
||||
var revision = GetFirstStdOutLine(gitexe + cmd_revision);
|
||||
var describe = GetFirstStdOutLine(gitexe + cmd_describe);
|
||||
var branch = GetFirstStdOutLine(gitexe + cmd_branch);
|
||||
|
@ -45,6 +45,7 @@ const Info<bool> MAIN_ACCURATE_CPU_CACHE{{System::Main, "Core", "AccurateCPUCach
|
||||
const Info<bool> MAIN_DSP_HLE{{System::Main, "Core", "DSPHLE"}, true};
|
||||
const Info<int> MAIN_MAX_FALLBACK{{System::Main, "Core", "MaxFallback"}, 100};
|
||||
const Info<int> MAIN_TIMING_VARIANCE{{System::Main, "Core", "TimingVariance"}, 40};
|
||||
const Info<bool> MAIN_CORRECT_TIME_DRIFT{{System::Main, "Core", "CorrectTimeDrift"}, false};
|
||||
const Info<bool> MAIN_CPU_THREAD{{System::Main, "Core", "CPUThread"}, true};
|
||||
const Info<bool> MAIN_SYNC_ON_SKIP_IDLE{{System::Main, "Core", "SyncOnSkipIdle"}, true};
|
||||
const Info<std::string> MAIN_DEFAULT_ISO{{System::Main, "Core", "DefaultISO"}, ""};
|
||||
|
@ -63,6 +63,7 @@ extern const Info<bool> MAIN_ACCURATE_CPU_CACHE;
|
||||
extern const Info<bool> MAIN_DSP_HLE;
|
||||
extern const Info<int> MAIN_MAX_FALLBACK;
|
||||
extern const Info<int> MAIN_TIMING_VARIANCE;
|
||||
extern const Info<bool> MAIN_CORRECT_TIME_DRIFT;
|
||||
extern const Info<bool> MAIN_CPU_THREAD;
|
||||
extern const Info<bool> MAIN_SYNC_ON_SKIP_IDLE;
|
||||
extern const Info<std::string> MAIN_DEFAULT_ISO;
|
||||
|
@ -82,7 +82,6 @@
|
||||
#include "InputCommon/ControllerInterface/ControllerInterface.h"
|
||||
#include "InputCommon/GCAdapter.h"
|
||||
|
||||
#include "VideoCommon/Assets/CustomAssetLoader.h"
|
||||
#include "VideoCommon/AsyncRequests.h"
|
||||
#include "VideoCommon/Fifo.h"
|
||||
#include "VideoCommon/FrameDumper.h"
|
||||
@ -528,9 +527,6 @@ static void EmuThread(Core::System& system, std::unique_ptr<BootParameters> boot
|
||||
|
||||
FreeLook::LoadInputConfig();
|
||||
|
||||
system.GetCustomAssetLoader().Init();
|
||||
Common::ScopeGuard asset_loader_guard([&system] { system.GetCustomAssetLoader().Shutdown(); });
|
||||
|
||||
system.GetMovie().Init(*boot);
|
||||
Common::ScopeGuard movie_guard([&system] { system.GetMovie().Shutdown(); });
|
||||
|
||||
|
@ -105,10 +105,20 @@ void CoreTimingManager::Init()
|
||||
|
||||
m_last_oc_factor = m_config_oc_factor;
|
||||
m_globals.last_OC_factor_inverted = m_config_oc_inv_factor;
|
||||
|
||||
m_on_state_changed_handle = Core::AddOnStateChangedCallback([this](Core::State state) {
|
||||
if (state == Core::State::Running)
|
||||
{
|
||||
// We don't want Throttle to attempt catch-up for all the time lost while paused.
|
||||
ResetThrottle(GetTicks());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void CoreTimingManager::Shutdown()
|
||||
{
|
||||
Core::RemoveOnStateChangedCallback(&m_on_state_changed_handle);
|
||||
|
||||
std::lock_guard lk(m_ts_write_lock);
|
||||
MoveEvents();
|
||||
ClearPendingEvents();
|
||||
@ -131,6 +141,8 @@ void CoreTimingManager::RefreshConfig()
|
||||
|
||||
m_max_variance = std::chrono::duration_cast<DT>(DT_ms(Config::Get(Config::MAIN_TIMING_VARIANCE)));
|
||||
|
||||
m_correct_time_drift = Config::Get(Config::MAIN_CORRECT_TIME_DRIFT);
|
||||
|
||||
if (AchievementManager::GetInstance().IsHardcoreModeActive() &&
|
||||
Config::Get(Config::MAIN_EMULATION_SPEED) < 1.0f &&
|
||||
Config::Get(Config::MAIN_EMULATION_SPEED) > 0.0f)
|
||||
@ -428,7 +440,9 @@ void CoreTimingManager::Throttle(const s64 target_cycle)
|
||||
const TimePoint time = Clock::now();
|
||||
|
||||
const TimePoint min_target = time - m_max_fallback;
|
||||
if (target_time < min_target)
|
||||
|
||||
// "Correct Time Drift" setting prevents timing relaxing.
|
||||
if (!m_correct_time_drift && target_time < min_target)
|
||||
{
|
||||
// Core is running too slow.. i.e. CPU bottleneck.
|
||||
const DT adjustment = min_target - target_time;
|
||||
|
@ -211,6 +211,7 @@ private:
|
||||
|
||||
DT m_max_fallback = {};
|
||||
DT m_max_variance = {};
|
||||
bool m_correct_time_drift = false;
|
||||
double m_emulation_speed = 1.0;
|
||||
|
||||
bool IsSpeedUnlimited() const;
|
||||
@ -225,6 +226,8 @@ private:
|
||||
std::atomic_bool m_use_precision_timer = false;
|
||||
Common::PrecisionTimer m_precision_cpu_timer;
|
||||
Common::PrecisionTimer m_precision_gpu_timer;
|
||||
|
||||
int m_on_state_changed_handle;
|
||||
};
|
||||
|
||||
} // namespace CoreTiming
|
||||
|
@ -78,8 +78,8 @@ std::string USBHost::GetDeviceNameFromVIDPID(u16 vid, u16 pid)
|
||||
libusb_get_string_descriptor_ascii(handle, desc.iProduct, buffer, sizeof(buffer)) > 0)
|
||||
{
|
||||
device_name = reinterpret_cast<char*>(buffer);
|
||||
libusb_close(handle);
|
||||
}
|
||||
libusb_close(handle);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -33,7 +33,7 @@
|
||||
#include "IOS/USB/Emulated/Infinity.h"
|
||||
#include "IOS/USB/Emulated/Skylanders/Skylander.h"
|
||||
#include "IOS/USB/USBScanner.h"
|
||||
#include "VideoCommon/Assets/CustomAssetLoader.h"
|
||||
#include "VideoCommon/Assets/CustomResourceManager.h"
|
||||
#include "VideoCommon/CommandProcessor.h"
|
||||
#include "VideoCommon/Fifo.h"
|
||||
#include "VideoCommon/GeometryShaderManager.h"
|
||||
@ -96,7 +96,7 @@ struct System::Impl
|
||||
VideoInterface::VideoInterfaceManager m_video_interface;
|
||||
Interpreter m_interpreter;
|
||||
JitInterface m_jit_interface;
|
||||
VideoCommon::CustomAssetLoader m_custom_asset_loader;
|
||||
VideoCommon::CustomResourceManager m_custom_resource_manager;
|
||||
FifoPlayer m_fifo_player;
|
||||
FifoRecorder m_fifo_recorder;
|
||||
Movie::MovieManager m_movie;
|
||||
@ -335,8 +335,8 @@ VideoInterface::VideoInterfaceManager& System::GetVideoInterface() const
|
||||
return m_impl->m_video_interface;
|
||||
}
|
||||
|
||||
VideoCommon::CustomAssetLoader& System::GetCustomAssetLoader() const
|
||||
VideoCommon::CustomResourceManager& System::GetCustomResourceManager() const
|
||||
{
|
||||
return m_impl->m_custom_asset_loader;
|
||||
return m_impl->m_custom_resource_manager;
|
||||
}
|
||||
} // namespace Core
|
||||
|
@ -108,8 +108,8 @@ class SystemTimersManager;
|
||||
}
|
||||
namespace VideoCommon
|
||||
{
|
||||
class CustomAssetLoader;
|
||||
}
|
||||
class CustomResourceManager;
|
||||
} // namespace VideoCommon
|
||||
namespace VideoInterface
|
||||
{
|
||||
class VideoInterfaceManager;
|
||||
@ -197,7 +197,7 @@ public:
|
||||
VertexShaderManager& GetVertexShaderManager() const;
|
||||
XFStateManager& GetXFStateManager() const;
|
||||
VideoInterface::VideoInterfaceManager& GetVideoInterface() const;
|
||||
VideoCommon::CustomAssetLoader& GetCustomAssetLoader() const;
|
||||
VideoCommon::CustomResourceManager& GetCustomResourceManager() const;
|
||||
|
||||
private:
|
||||
System();
|
||||
|
@ -59,6 +59,7 @@
|
||||
<ClInclude Include="Common\Event.h" />
|
||||
<ClInclude Include="Common\FatFsUtil.h" />
|
||||
<ClInclude Include="Common\FileSearch.h" />
|
||||
<ClInclude Include="Common\FilesystemWatcher.h" />
|
||||
<ClInclude Include="Common\FileUtil.h" />
|
||||
<ClInclude Include="Common\FixedSizeQueue.h" />
|
||||
<ClInclude Include="Common\Flag.h" />
|
||||
@ -669,12 +670,16 @@
|
||||
<ClInclude Include="VideoCommon\Assets\CustomAsset.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\CustomAssetLibrary.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\CustomAssetLoader.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\CustomResourceManager.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\CustomTextureData.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\DirectFilesystemAssetLibrary.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\MaterialAsset.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\MeshAsset.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\ShaderAsset.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\TextureAsset.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\TextureAssetUtils.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\Types.h" />
|
||||
<ClInclude Include="VideoCommon\Assets\WatchableFilesystemAssetLibrary.h" />
|
||||
<ClInclude Include="VideoCommon\AsyncRequests.h" />
|
||||
<ClInclude Include="VideoCommon\AsyncShaderCompiler.h" />
|
||||
<ClInclude Include="VideoCommon\BoundingBox.h" />
|
||||
@ -814,6 +819,7 @@
|
||||
<ClCompile Include="Common\ENet.cpp" />
|
||||
<ClCompile Include="Common\FatFsUtil.cpp" />
|
||||
<ClCompile Include="Common\FileSearch.cpp" />
|
||||
<ClCompile Include="Common\FilesystemWatcher.cpp" />
|
||||
<ClCompile Include="Common\FileUtil.cpp" />
|
||||
<ClCompile Include="Common\FloatUtils.cpp" />
|
||||
<ClCompile Include="Common\GekkoDisassembler.cpp" />
|
||||
@ -1320,14 +1326,15 @@
|
||||
<ClCompile Include="VideoCommon\AbstractStagingTexture.cpp" />
|
||||
<ClCompile Include="VideoCommon\AbstractTexture.cpp" />
|
||||
<ClCompile Include="VideoCommon\Assets\CustomAsset.cpp" />
|
||||
<ClCompile Include="VideoCommon\Assets\CustomAssetLibrary.cpp" />
|
||||
<ClCompile Include="VideoCommon\Assets\CustomAssetLoader.cpp" />
|
||||
<ClCompile Include="VideoCommon\Assets\CustomResourceManager.cpp" />
|
||||
<ClCompile Include="VideoCommon\Assets\CustomTextureData.cpp" />
|
||||
<ClCompile Include="VideoCommon\Assets\DirectFilesystemAssetLibrary.cpp" />
|
||||
<ClCompile Include="VideoCommon\Assets\MaterialAsset.cpp" />
|
||||
<ClCompile Include="VideoCommon\Assets\MeshAsset.cpp" />
|
||||
<ClCompile Include="VideoCommon\Assets\ShaderAsset.cpp" />
|
||||
<ClCompile Include="VideoCommon\Assets\TextureAsset.cpp" />
|
||||
<ClCompile Include="VideoCommon\Assets\TextureAssetUtils.cpp" />
|
||||
<ClCompile Include="VideoCommon\AsyncRequests.cpp" />
|
||||
<ClCompile Include="VideoCommon\AsyncShaderCompiler.cpp" />
|
||||
<ClCompile Include="VideoCommon\BoundingBox.cpp" />
|
||||
|
@ -15,7 +15,6 @@
|
||||
AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent)
|
||||
{
|
||||
setWindowTitle(tr("About Dolphin"));
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
QString branch_str = QString::fromStdString(Common::GetScmBranchStr());
|
||||
const int commits_ahead = Common::GetScmCommitsAheadMaster();
|
||||
|
@ -27,7 +27,6 @@
|
||||
AchievementsWindow::AchievementsWindow(QWidget* parent) : QDialog(parent)
|
||||
{
|
||||
setWindowTitle(tr("Achievements"));
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
CreateMainLayout();
|
||||
ConnectWidgets();
|
||||
|
@ -26,7 +26,6 @@ CheatsManager::CheatsManager(Core::System& system, QWidget* parent)
|
||||
: QDialog(parent), m_system(system)
|
||||
{
|
||||
setWindowTitle(tr("Cheats Manager"));
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
|
||||
&CheatsManager::OnStateChanged);
|
||||
|
@ -23,7 +23,6 @@
|
||||
|
||||
CheatCodeEditor::CheatCodeEditor(QWidget* parent) : QDialog(parent)
|
||||
{
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
setWindowTitle(tr("Cheat Code Editor"));
|
||||
|
||||
CreateWidgets();
|
||||
|
@ -80,6 +80,36 @@ void ConfigSlider::OnConfigChanged()
|
||||
}
|
||||
}
|
||||
|
||||
ConfigSliderU32::ConfigSliderU32(u32 minimum, u32 maximum, const Config::Info<u32>& setting,
|
||||
u32 scale)
|
||||
: ConfigSliderU32(minimum, maximum, setting, nullptr, scale)
|
||||
{
|
||||
}
|
||||
|
||||
ConfigSliderU32::ConfigSliderU32(u32 minimum, u32 maximum, const Config::Info<u32>& setting,
|
||||
Config::Layer* layer, u32 scale)
|
||||
: ConfigControl(Qt::Horizontal, setting.GetLocation(), layer), m_setting(setting),
|
||||
m_scale(scale)
|
||||
|
||||
{
|
||||
setMinimum(minimum);
|
||||
setMaximum(maximum);
|
||||
setValue(ReadValue(setting));
|
||||
OnConfigChanged();
|
||||
|
||||
connect(this, &ConfigSliderU32::valueChanged, this, &ConfigSliderU32::Update);
|
||||
}
|
||||
|
||||
void ConfigSliderU32::Update(u32 value)
|
||||
{
|
||||
SaveValue(m_setting, value * m_scale);
|
||||
}
|
||||
|
||||
void ConfigSliderU32::OnConfigChanged()
|
||||
{
|
||||
setValue(ReadValue(m_setting) / m_scale);
|
||||
}
|
||||
|
||||
ConfigSliderLabel::ConfigSliderLabel(const QString& text, ConfigSlider* slider)
|
||||
: QLabel(text), m_slider(QPointer<ConfigSlider>(slider))
|
||||
{
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "DolphinQt/Config/ConfigControls/ConfigControl.h"
|
||||
#include "DolphinQt/Config/ToolTipControls/ToolTipSlider.h"
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Config/ConfigInfo.h"
|
||||
|
||||
class ConfigSlider final : public ConfigControl<ToolTipSlider>
|
||||
@ -38,6 +39,25 @@ private:
|
||||
std::vector<int> m_tick_values;
|
||||
};
|
||||
|
||||
class ConfigSliderU32 final : public ConfigControl<ToolTipSlider>
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ConfigSliderU32(u32 minimum, u32 maximum, const Config::Info<u32>& setting, u32 scale = 1);
|
||||
ConfigSliderU32(u32 minimum, u32 maximum, const Config::Info<u32>& setting, Config ::Layer* layer,
|
||||
u32 scale = 1);
|
||||
|
||||
void Update(u32 value);
|
||||
|
||||
protected:
|
||||
void OnConfigChanged() override;
|
||||
|
||||
private:
|
||||
const Config::Info<u32> m_setting;
|
||||
|
||||
u32 m_scale = 1;
|
||||
};
|
||||
|
||||
class ConfigSliderLabel final : public QLabel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -17,7 +17,6 @@ ControllerInterfaceWindow::ControllerInterfaceWindow(QWidget* parent) : QDialog(
|
||||
CreateMainLayout();
|
||||
|
||||
setWindowTitle(tr("Alternate Input Sources"));
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
}
|
||||
|
||||
void ControllerInterfaceWindow::CreateMainLayout()
|
||||
|
@ -29,7 +29,6 @@ DualShockUDPClientAddServerDialog::DualShockUDPClientAddServerDialog(QWidget* pa
|
||||
void DualShockUDPClientAddServerDialog::CreateWidgets()
|
||||
{
|
||||
setWindowTitle(tr("Add New DSU Server"));
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
m_main_layout = new QGridLayout;
|
||||
|
||||
|
@ -17,7 +17,6 @@ FreeLookWindow::FreeLookWindow(QWidget* parent) : QDialog(parent)
|
||||
ConnectWidgets();
|
||||
|
||||
setWindowTitle(tr("Free Look Settings"));
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
}
|
||||
|
||||
void FreeLookWindow::CreateMainLayout()
|
||||
|
@ -22,7 +22,6 @@
|
||||
ColorCorrectionConfigWindow::ColorCorrectionConfigWindow(QWidget* parent) : QDialog(parent)
|
||||
{
|
||||
setWindowTitle(tr("Color Correction Configuration"));
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
Create();
|
||||
ConnectWidgets();
|
||||
|
@ -28,7 +28,6 @@ GraphicsWindow::GraphicsWindow(MainWindow* parent) : QDialog(parent), m_main_win
|
||||
CreateMainLayout();
|
||||
|
||||
setWindowTitle(tr("Graphics"));
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
OnBackendChanged(QString::fromStdString(Config::Get(Config::MAIN_GFX_BACKEND)));
|
||||
|
||||
|
@ -42,7 +42,6 @@ PostProcessingConfigWindow::PostProcessingConfigWindow(EnhancementsWidget* paren
|
||||
}
|
||||
|
||||
setWindowTitle(tr("Post-Processing Shader Configuration"));
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
PopulateGroups();
|
||||
Create();
|
||||
|
@ -17,8 +17,6 @@
|
||||
GCPadWiiUConfigDialog::GCPadWiiUConfigDialog(int port, QWidget* parent)
|
||||
: QDialog(parent), m_port{port}
|
||||
{
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
CreateLayout();
|
||||
|
||||
LoadSettings();
|
||||
|
@ -268,7 +268,6 @@ IOWindow::IOWindow(MappingWindow* window, ControllerEmu::EmulatedController* con
|
||||
connect(&Settings::Instance(), &Settings::ConfigChanged, this, &IOWindow::ConfigChanged);
|
||||
|
||||
setWindowTitle(type == IOWindow::Type::Input ? tr("Configure Input") : tr("Configure Output"));
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
ConfigChanged();
|
||||
|
||||
|
@ -65,7 +65,6 @@ MappingWindow::MappingWindow(QWidget* parent, Type type, int port_num)
|
||||
: QDialog(parent), m_port(port_num)
|
||||
{
|
||||
setWindowTitle(tr("Port %1").arg(port_num + 1));
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
CreateDevicesLayout();
|
||||
CreateProfilesLayout();
|
||||
|
@ -38,7 +38,6 @@ NewPatchDialog::NewPatchDialog(QWidget* parent, PatchEngine::Patch& patch)
|
||||
: QDialog(parent), m_patch(patch)
|
||||
{
|
||||
setWindowTitle(tr("Patch Editor"));
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
CreateWidgets();
|
||||
ConnectWidgets();
|
||||
|
@ -25,7 +25,7 @@
|
||||
#include "UICommon/GameFile.h"
|
||||
|
||||
PropertiesDialog::PropertiesDialog(QWidget* parent, const UICommon::GameFile& game)
|
||||
: StackedSettingsWindow{parent}
|
||||
: StackedSettingsWindow{parent}, m_filepath(game.GetFilePath())
|
||||
{
|
||||
setWindowTitle(QStringLiteral("%1: %2 - %3")
|
||||
.arg(QString::fromStdString(game.GetFileName()),
|
||||
|
@ -17,6 +17,7 @@ class PropertiesDialog final : public StackedSettingsWindow
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit PropertiesDialog(QWidget* parent, const UICommon::GameFile& game);
|
||||
const std::string& GetFilePath() const { return m_filepath; }
|
||||
|
||||
signals:
|
||||
void OpenGeneralSettings();
|
||||
@ -24,4 +25,7 @@ signals:
|
||||
#ifdef USE_RETRO_ACHIEVEMENTS
|
||||
void OpenAchievementSettings();
|
||||
#endif // USE_RETRO_ACHIEVEMENTS
|
||||
|
||||
private:
|
||||
const std::string m_filepath;
|
||||
};
|
||||
|
@ -24,8 +24,6 @@
|
||||
|
||||
StackedSettingsWindow::StackedSettingsWindow(QWidget* parent) : QDialog{parent}
|
||||
{
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
// This eliminates the ugly line between the title bar and window contents with KDE Plasma.
|
||||
setStyleSheet(QStringLiteral("QDialog { border: none; }"));
|
||||
|
||||
|
@ -42,7 +42,6 @@ ConvertDialog::ConvertDialog(QList<std::shared_ptr<const UICommon::GameFile>> fi
|
||||
ASSERT(!m_files.empty());
|
||||
|
||||
setWindowTitle(tr("Convert"));
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
QGridLayout* grid_layout = new QGridLayout;
|
||||
grid_layout->setColumnStretch(1, 1);
|
||||
|
@ -41,7 +41,6 @@ QString HtmlFormatErrorLine(const Common::GekkoAssembler::AssemblerError& err)
|
||||
AssembleInstructionDialog::AssembleInstructionDialog(QWidget* parent, u32 address, u32 value)
|
||||
: QDialog(parent), m_code(value), m_address(address)
|
||||
{
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
setWindowModality(Qt::WindowModal);
|
||||
setWindowTitle(tr("Instruction"));
|
||||
|
||||
|
@ -200,7 +200,7 @@ BranchWatchDialog::BranchWatchDialog(Core::System& system, Core::BranchWatch& br
|
||||
: QDialog(parent), m_system(system), m_branch_watch(branch_watch), m_code_widget(code_widget)
|
||||
{
|
||||
setWindowTitle(tr("Branch Watch Tool"));
|
||||
setWindowFlags((windowFlags() | Qt::WindowMinMaxButtonsHint) & ~Qt::WindowContextHelpButtonHint);
|
||||
setWindowFlags(windowFlags() | Qt::WindowMinMaxButtonsHint);
|
||||
|
||||
// Branch Watch Table
|
||||
m_table_view = new QTableView(nullptr);
|
||||
|
@ -24,7 +24,6 @@
|
||||
BreakpointDialog::BreakpointDialog(BreakpointWidget* parent)
|
||||
: QDialog(parent), m_parent(parent), m_open_mode(OpenMode::New)
|
||||
{
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
setWindowTitle(tr("New Breakpoint"));
|
||||
CreateWidgets();
|
||||
ConnectWidgets();
|
||||
@ -36,7 +35,6 @@ BreakpointDialog::BreakpointDialog(BreakpointWidget* parent)
|
||||
BreakpointDialog::BreakpointDialog(BreakpointWidget* parent, const TBreakPoint* breakpoint)
|
||||
: QDialog(parent), m_parent(parent), m_open_mode(OpenMode::EditBreakPoint)
|
||||
{
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
setWindowTitle(tr("Edit Breakpoint"));
|
||||
CreateWidgets();
|
||||
ConnectWidgets();
|
||||
@ -56,7 +54,6 @@ BreakpointDialog::BreakpointDialog(BreakpointWidget* parent, const TBreakPoint*
|
||||
BreakpointDialog::BreakpointDialog(BreakpointWidget* parent, const TMemCheck* memcheck)
|
||||
: QDialog(parent), m_parent(parent), m_open_mode(OpenMode::EditMemCheck)
|
||||
{
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
setWindowTitle(tr("Edit Breakpoint"));
|
||||
|
||||
CreateWidgets();
|
||||
|
@ -14,7 +14,6 @@
|
||||
PatchInstructionDialog::PatchInstructionDialog(QWidget* parent, u32 address, u32 value)
|
||||
: QDialog(parent), m_address(address)
|
||||
{
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
setWindowModality(Qt::WindowModal);
|
||||
setWindowTitle(tr("Instruction"));
|
||||
|
||||
|
@ -21,7 +21,6 @@ DiscordJoinRequestDialog::DiscordJoinRequestDialog(QWidget* parent, const std::s
|
||||
: QDialog(parent), m_user_id(id), m_close_timestamp(std::time(nullptr) + s_max_lifetime_seconds)
|
||||
{
|
||||
setWindowTitle(tr("Request to Join Your Party"));
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
QPixmap avatar_pixmap;
|
||||
|
||||
|
@ -59,10 +59,18 @@ FIFOPlayerWindow::FIFOPlayerWindow(FifoPlayer& fifo_player, FifoRecorder& fifo_r
|
||||
});
|
||||
|
||||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, [this](Core::State state) {
|
||||
// We don't want to trigger OnEmulationStarted when going from Paused to Running,
|
||||
// and nothing in UpdateControls treats Paused and Running differently
|
||||
if (state == Core::State::Paused)
|
||||
state = Core::State::Running;
|
||||
|
||||
// Skip redundant updates
|
||||
if (state == m_emu_state)
|
||||
return;
|
||||
|
||||
if (state == Core::State::Running && m_emu_state != Core::State::Paused)
|
||||
UpdateControls();
|
||||
|
||||
if (state == Core::State::Running)
|
||||
OnEmulationStarted();
|
||||
else if (state == Core::State::Uninitialized)
|
||||
OnEmulationStopped();
|
||||
@ -266,8 +274,6 @@ void FIFOPlayerWindow::StopRecording()
|
||||
|
||||
void FIFOPlayerWindow::OnEmulationStarted()
|
||||
{
|
||||
UpdateControls();
|
||||
|
||||
if (m_fifo_player.GetFile())
|
||||
OnFIFOLoaded();
|
||||
}
|
||||
@ -278,7 +284,6 @@ void FIFOPlayerWindow::OnEmulationStopped()
|
||||
if (m_fifo_recorder.IsRecording())
|
||||
StopRecording();
|
||||
|
||||
UpdateControls();
|
||||
// When emulation stops, switch away from the analyzer tab, as it no longer shows anything useful
|
||||
m_tab_widget->setCurrentWidget(m_main_widget);
|
||||
m_analyzer->Update();
|
||||
|
@ -60,7 +60,6 @@ GCMemcardCreateNewDialog::GCMemcardCreateNewDialog(QWidget* parent) : QDialog(pa
|
||||
});
|
||||
|
||||
setWindowTitle(tr("Create New Memory Card"));
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
}
|
||||
|
||||
GCMemcardCreateNewDialog::~GCMemcardCreateNewDialog() = default;
|
||||
|
@ -98,7 +98,6 @@ GCMemcardManager::GCMemcardManager(QWidget* parent) : QDialog(parent)
|
||||
resize(650, 500);
|
||||
|
||||
setWindowTitle(tr("GameCube Memory Card Manager"));
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
}
|
||||
|
||||
GCMemcardManager::~GCMemcardManager() = default;
|
||||
|
@ -566,6 +566,15 @@ void GameList::OpenProperties()
|
||||
if (!game)
|
||||
return;
|
||||
|
||||
auto property_windows = this->findChildren<PropertiesDialog*>();
|
||||
auto it =
|
||||
std::ranges::find(property_windows, game->GetFilePath(), &PropertiesDialog::GetFilePath);
|
||||
if (it != property_windows.end())
|
||||
{
|
||||
(*it)->raise();
|
||||
return;
|
||||
}
|
||||
|
||||
PropertiesDialog* properties = new PropertiesDialog(this, *game);
|
||||
|
||||
connect(properties, &PropertiesDialog::OpenGeneralSettings, this, &GameList::OpenGeneralSettings);
|
||||
|
@ -27,7 +27,6 @@ NANDRepairDialog::NANDRepairDialog(const WiiUtils::NANDCheckResult& result, QWid
|
||||
: QDialog(parent)
|
||||
{
|
||||
setWindowTitle(tr("NAND Check"));
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
setWindowIcon(Resources::GetAppIcon());
|
||||
|
||||
QVBoxLayout* main_layout = new QVBoxLayout();
|
||||
|
@ -30,7 +30,6 @@ bool NKitWarningDialog::ShowUnlessDisabled(QWidget* parent)
|
||||
NKitWarningDialog::NKitWarningDialog(QWidget* parent) : QDialog(parent)
|
||||
{
|
||||
setWindowTitle(tr("NKit Warning"));
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
setWindowIcon(Resources::GetAppIcon());
|
||||
|
||||
QVBoxLayout* main_layout = new QVBoxLayout;
|
||||
|
@ -45,7 +45,6 @@ ChunkedProgressDialog::ChunkedProgressDialog(QWidget* parent) : QDialog(parent)
|
||||
CreateWidgets();
|
||||
ConnectWidgets();
|
||||
setWindowTitle(tr("Data Transfer"));
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
}
|
||||
|
||||
void ChunkedProgressDialog::CreateWidgets()
|
||||
|
@ -14,7 +14,6 @@
|
||||
GameListDialog::GameListDialog(const GameListModel& game_list_model, QWidget* parent)
|
||||
: QDialog(parent), m_game_list_model(game_list_model)
|
||||
{
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
setWindowTitle(tr("Select a game"));
|
||||
|
||||
CreateWidgets();
|
||||
|
@ -32,7 +32,6 @@
|
||||
NetPlayBrowser::NetPlayBrowser(QWidget* parent) : QDialog(parent)
|
||||
{
|
||||
setWindowTitle(tr("NetPlay Session Browser"));
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
CreateWidgets();
|
||||
RestoreSettings();
|
||||
@ -297,7 +296,6 @@ void NetPlayBrowser::accept()
|
||||
{
|
||||
QInputDialog dialog(this);
|
||||
|
||||
dialog.setWindowFlags(dialog.windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
dialog.setWindowTitle(tr("Enter password"));
|
||||
dialog.setLabelText(tr("This session requires a password:"));
|
||||
dialog.setWindowModality(Qt::WindowModal);
|
||||
|
@ -97,8 +97,6 @@ NetPlayDialog::NetPlayDialog(const GameListModel& game_list_model,
|
||||
: QDialog(parent), m_game_list_model(game_list_model),
|
||||
m_start_game_callback(std::move(start_game_callback))
|
||||
{
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
setWindowTitle(tr("NetPlay"));
|
||||
setWindowIcon(Resources::GetAppIcon());
|
||||
|
||||
|
@ -32,7 +32,6 @@ NetPlaySetupDialog::NetPlaySetupDialog(const GameListModel& game_list_model, QWi
|
||||
: QDialog(parent), m_game_list_model(game_list_model)
|
||||
{
|
||||
setWindowTitle(tr("NetPlay Setup"));
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
CreateMainLayout();
|
||||
|
||||
|
@ -17,7 +17,6 @@
|
||||
|
||||
PadMappingDialog::PadMappingDialog(QWidget* parent) : QDialog(parent)
|
||||
{
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
setWindowTitle(tr("Assign Controllers"));
|
||||
|
||||
CreateWidgets();
|
||||
|
@ -26,7 +26,6 @@ public:
|
||||
ParallelProgressDialog(Args&&... args) : m_dialog{std::forward<Args>(args)...}
|
||||
{
|
||||
setParent(m_dialog.parent());
|
||||
m_dialog.setWindowFlags(m_dialog.windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
ConnectSignalsAndSlots();
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,6 @@ ResourcePackManager::ResourcePackManager(QWidget* widget) : QDialog(widget)
|
||||
RepopulateTable();
|
||||
|
||||
setWindowTitle(tr("Resource Pack Manager"));
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
resize(QSize(900, 600));
|
||||
}
|
||||
|
@ -46,7 +46,6 @@ RiivolutionBootWidget::RiivolutionBootWidget(std::string game_id, std::optional<
|
||||
m_base_game_path(std::move(base_game_path))
|
||||
{
|
||||
setWindowTitle(tr("Start with Riivolution Patches"));
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
CreateWidgets();
|
||||
ConnectWidgets();
|
||||
|
@ -12,7 +12,6 @@
|
||||
#include <QLabel>
|
||||
#include <QRadioButton>
|
||||
#include <QSignalBlocker>
|
||||
#include <QSlider>
|
||||
#include <QVBoxLayout>
|
||||
#include <cmath>
|
||||
|
||||
@ -25,6 +24,8 @@
|
||||
#include "Core/System.h"
|
||||
|
||||
#include "DolphinQt/Config/ConfigControls/ConfigBool.h"
|
||||
#include "DolphinQt/Config/ConfigControls/ConfigFloatSlider.h"
|
||||
#include "DolphinQt/Config/ConfigControls/ConfigSlider.h"
|
||||
#include "DolphinQt/QtUtils/QtUtils.h"
|
||||
#include "DolphinQt/QtUtils/SignalBlocking.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
@ -89,6 +90,18 @@ void AdvancedPane::CreateLayout()
|
||||
"needed.<br><br><dolphin_emphasis>If unsure, leave this unchecked.</dolphin_emphasis>"));
|
||||
cpu_options_group_layout->addWidget(m_accurate_cpu_cache_checkbox);
|
||||
|
||||
auto* const timing_group = new QGroupBox(tr("Timing"));
|
||||
main_layout->addWidget(timing_group);
|
||||
auto* timing_group_layout = new QVBoxLayout{timing_group};
|
||||
auto* const correct_time_drift =
|
||||
new ConfigBool{tr("Correct Time Drift"), Config::MAIN_CORRECT_TIME_DRIFT};
|
||||
correct_time_drift->SetDescription(
|
||||
tr("Allow the emulated console to run fast after stutters,"
|
||||
"<br>pursuing accurate overall elapsed time unless paused or speed-adjusted."
|
||||
"<br><br>This may be useful for internet play."
|
||||
"<br><br><dolphin_emphasis>If unsure, leave this unchecked.</dolphin_emphasis>"));
|
||||
timing_group_layout->addWidget(correct_time_drift);
|
||||
|
||||
auto* clock_override = new QGroupBox(tr("Clock Override"));
|
||||
auto* clock_override_layout = new QVBoxLayout();
|
||||
clock_override->setLayout(clock_override_layout);
|
||||
@ -103,12 +116,24 @@ void AdvancedPane::CreateLayout()
|
||||
cpu_clock_override_slider_layout->setContentsMargins(0, 0, 0, 0);
|
||||
clock_override_layout->addLayout(cpu_clock_override_slider_layout);
|
||||
|
||||
m_cpu_clock_override_slider = new QSlider(Qt::Horizontal);
|
||||
m_cpu_clock_override_slider->setRange(1, 400);
|
||||
m_cpu_clock_override_slider = new ConfigFloatSlider(0.01f, 4.0f, Config::MAIN_OVERCLOCK, 0.01f);
|
||||
cpu_clock_override_slider_layout->addWidget(m_cpu_clock_override_slider);
|
||||
|
||||
m_cpu_clock_override_slider_label = new QLabel();
|
||||
cpu_clock_override_slider_layout->addWidget(m_cpu_clock_override_slider_label);
|
||||
m_cpu_label = new QLabel();
|
||||
cpu_clock_override_slider_layout->addWidget(m_cpu_label);
|
||||
|
||||
std::function<void()> cpu_text = [this]() {
|
||||
const float multi = Config::Get(Config::MAIN_OVERCLOCK);
|
||||
const int percent = static_cast<int>(std::round(multi * 100.f));
|
||||
const int core_clock =
|
||||
Core::System::GetInstance().GetSystemTimers().GetTicksPerSecond() / std::pow(10, 6);
|
||||
const int clock = static_cast<int>(std::round(multi * core_clock));
|
||||
m_cpu_label->setText(tr("%1% (%2 MHz)").arg(QString::number(percent), QString::number(clock)));
|
||||
};
|
||||
|
||||
cpu_text();
|
||||
connect(m_cpu_clock_override_slider, &QSlider::valueChanged, this,
|
||||
[this, cpu_text]() { cpu_text(); });
|
||||
|
||||
m_cpu_clock_override_checkbox->SetDescription(
|
||||
tr("Adjusts the emulated CPU's clock rate.<br><br>"
|
||||
@ -135,12 +160,25 @@ void AdvancedPane::CreateLayout()
|
||||
vi_rate_override_slider_layout->setContentsMargins(0, 0, 0, 0);
|
||||
vi_rate_override_layout->addLayout(vi_rate_override_slider_layout);
|
||||
|
||||
m_vi_rate_override_slider = new QSlider(Qt::Horizontal);
|
||||
m_vi_rate_override_slider->setRange(1, 500);
|
||||
m_vi_rate_override_slider = new ConfigFloatSlider(0.01f, 5.0f, Config::MAIN_VI_OVERCLOCK, 0.01f);
|
||||
vi_rate_override_slider_layout->addWidget(m_vi_rate_override_slider);
|
||||
|
||||
m_vi_rate_override_slider_label = new QLabel();
|
||||
vi_rate_override_slider_layout->addWidget(m_vi_rate_override_slider_label);
|
||||
m_vi_label = new QLabel();
|
||||
vi_rate_override_slider_layout->addWidget(m_vi_label);
|
||||
std::function<void()> vi_text = [this]() {
|
||||
const int percent =
|
||||
static_cast<int>(std::round(Config::Get(Config::MAIN_VI_OVERCLOCK) * 100.f));
|
||||
float vps =
|
||||
static_cast<float>(Core::System::GetInstance().GetVideoInterface().GetTargetRefreshRate());
|
||||
if (vps == 0.0f || !Config::Get(Config::MAIN_VI_OVERCLOCK_ENABLE))
|
||||
vps = 59.94f * Config::Get(Config::MAIN_VI_OVERCLOCK);
|
||||
m_vi_label->setText(
|
||||
tr("%1% (%2 VPS)").arg(QString::number(percent), QString::number(vps, 'f', 2)));
|
||||
};
|
||||
|
||||
vi_text();
|
||||
connect(m_vi_rate_override_slider, &QSlider::valueChanged, this,
|
||||
[this, vi_text]() { vi_text(); });
|
||||
|
||||
m_vi_rate_override_checkbox->SetDescription(
|
||||
tr("Adjusts the VBI frequency. Also adjusts the emulated CPU's "
|
||||
@ -167,27 +205,34 @@ void AdvancedPane::CreateLayout()
|
||||
mem1_override_slider_layout->setContentsMargins(0, 0, 0, 0);
|
||||
ram_override_layout->addLayout(mem1_override_slider_layout);
|
||||
|
||||
m_mem1_override_slider = new QSlider(Qt::Horizontal);
|
||||
m_mem1_override_slider->setRange(24, 64);
|
||||
m_mem1_override_slider = new ConfigSliderU32(24, 64, Config::MAIN_MEM1_SIZE, 0x100000);
|
||||
mem1_override_slider_layout->addWidget(m_mem1_override_slider);
|
||||
|
||||
m_mem1_override_slider_label = new QLabel();
|
||||
mem1_override_slider_layout->addWidget(m_mem1_override_slider_label);
|
||||
m_mem1_label =
|
||||
new QLabel(tr("%1 MB (MEM1)").arg(QString::number(m_mem1_override_slider->value())));
|
||||
mem1_override_slider_layout->addWidget(m_mem1_label);
|
||||
connect(m_mem1_override_slider, &QSlider::valueChanged, this, [this](int value) {
|
||||
m_mem1_label->setText(tr("%1 MB (MEM1)").arg(QString::number(value)));
|
||||
});
|
||||
|
||||
auto* mem2_override_slider_layout = new QHBoxLayout();
|
||||
mem2_override_slider_layout->setContentsMargins(0, 0, 0, 0);
|
||||
ram_override_layout->addLayout(mem2_override_slider_layout);
|
||||
|
||||
m_mem2_override_slider = new QSlider(Qt::Horizontal);
|
||||
m_mem2_override_slider->setRange(64, 128);
|
||||
m_mem2_override_slider = new ConfigSliderU32(64, 128, Config::MAIN_MEM2_SIZE, 0x100000);
|
||||
mem2_override_slider_layout->addWidget(m_mem2_override_slider);
|
||||
|
||||
m_mem2_override_slider_label = new QLabel();
|
||||
mem2_override_slider_layout->addWidget(m_mem2_override_slider_label);
|
||||
m_mem2_label =
|
||||
new QLabel(tr("%1 MB (MEM2)").arg(QString::number(m_mem2_override_slider->value())));
|
||||
mem2_override_slider_layout->addWidget(m_mem2_label);
|
||||
connect(m_mem2_override_slider, &QSlider::valueChanged, this, [this](int value) {
|
||||
m_mem2_label->setText(tr("%1 MB (MEM2)").arg(QString::number(value)));
|
||||
});
|
||||
|
||||
m_ram_override_checkbox->SetDescription(
|
||||
tr("Adjusts the amount of RAM in the emulated console.<br><br>"
|
||||
"<b>WARNING</b>: Enabling this will completely break many games.<br>Only a small number "
|
||||
"<b>WARNING</b>: Enabling this will completely break many games.<br>Only a small "
|
||||
"number "
|
||||
"of games can benefit from this."
|
||||
"<br><br><dolphin_emphasis>If unsure, leave this unchecked.</dolphin_emphasis>"));
|
||||
|
||||
@ -227,36 +272,12 @@ void AdvancedPane::ConnectLayout()
|
||||
Config::SetBaseOrCurrent(Config::MAIN_CPU_CORE, cpu_cores[index]);
|
||||
});
|
||||
|
||||
connect(m_cpu_clock_override_slider, &QSlider::valueChanged, [this](int oc_factor) {
|
||||
const float factor = m_cpu_clock_override_slider->value() / 100.f;
|
||||
Config::SetBaseOrCurrent(Config::MAIN_OVERCLOCK, factor);
|
||||
Update();
|
||||
});
|
||||
|
||||
connect(m_vi_rate_override_slider, &QSlider::valueChanged, [this](int oc_factor) {
|
||||
const float factor = m_vi_rate_override_slider->value() / 100.f;
|
||||
Config::SetBaseOrCurrent(Config::MAIN_VI_OVERCLOCK, factor);
|
||||
Update();
|
||||
});
|
||||
|
||||
m_ram_override_checkbox->setChecked(Config::Get(Config::MAIN_RAM_OVERRIDE_ENABLE));
|
||||
connect(m_ram_override_checkbox, &QCheckBox::toggled, [this](bool enable_ram_override) {
|
||||
Config::SetBaseOrCurrent(Config::MAIN_RAM_OVERRIDE_ENABLE, enable_ram_override);
|
||||
Update();
|
||||
});
|
||||
|
||||
connect(m_mem1_override_slider, &QSlider::valueChanged, [this](int slider_value) {
|
||||
const u32 mem1_size = m_mem1_override_slider->value() * 0x100000;
|
||||
Config::SetBaseOrCurrent(Config::MAIN_MEM1_SIZE, mem1_size);
|
||||
Update();
|
||||
});
|
||||
|
||||
connect(m_mem2_override_slider, &QSlider::valueChanged, [this](int slider_value) {
|
||||
const u32 mem2_size = m_mem2_override_slider->value() * 0x100000;
|
||||
Config::SetBaseOrCurrent(Config::MAIN_MEM2_SIZE, mem2_size);
|
||||
Update();
|
||||
});
|
||||
|
||||
connect(m_custom_rtc_datetime, &QDateTimeEdit::dateTimeChanged, [this](QDateTime date_time) {
|
||||
Config::SetBaseOrCurrent(Config::MAIN_CUSTOM_RTC_VALUE,
|
||||
static_cast<u32>(date_time.toSecsSinceEpoch()));
|
||||
@ -295,21 +316,7 @@ void AdvancedPane::Update()
|
||||
}
|
||||
|
||||
m_cpu_clock_override_slider->setEnabled(enable_cpu_clock_override_widgets);
|
||||
m_cpu_clock_override_slider_label->setEnabled(enable_cpu_clock_override_widgets);
|
||||
|
||||
{
|
||||
const QSignalBlocker blocker(m_cpu_clock_override_slider);
|
||||
m_cpu_clock_override_slider->setValue(
|
||||
static_cast<int>(std::round(Config::Get(Config::MAIN_OVERCLOCK) * 100.f)));
|
||||
}
|
||||
|
||||
m_cpu_clock_override_slider_label->setText([] {
|
||||
int core_clock =
|
||||
Core::System::GetInstance().GetSystemTimers().GetTicksPerSecond() / std::pow(10, 6);
|
||||
int percent = static_cast<int>(std::round(Config::Get(Config::MAIN_OVERCLOCK) * 100.f));
|
||||
int clock = static_cast<int>(std::round(Config::Get(Config::MAIN_OVERCLOCK) * core_clock));
|
||||
return tr("%1% (%2 MHz)").arg(QString::number(percent), QString::number(clock));
|
||||
}());
|
||||
m_cpu_label->setEnabled(enable_cpu_clock_override_widgets);
|
||||
|
||||
QFont vi_bf = font();
|
||||
vi_bf.setBold(Config::GetActiveLayerForConfig(Config::MAIN_VI_OVERCLOCK_ENABLE) !=
|
||||
@ -318,53 +325,16 @@ void AdvancedPane::Update()
|
||||
m_vi_rate_override_checkbox->setChecked(enable_vi_rate_override_widgets);
|
||||
|
||||
m_vi_rate_override_slider->setEnabled(enable_vi_rate_override_widgets);
|
||||
m_vi_rate_override_slider_label->setEnabled(enable_vi_rate_override_widgets);
|
||||
|
||||
{
|
||||
const QSignalBlocker blocker(m_vi_rate_override_slider);
|
||||
m_vi_rate_override_slider->setValue(
|
||||
static_cast<int>(std::round(Config::Get(Config::MAIN_VI_OVERCLOCK) * 100.f)));
|
||||
}
|
||||
|
||||
m_vi_rate_override_slider_label->setText([] {
|
||||
int percent = static_cast<int>(std::round(Config::Get(Config::MAIN_VI_OVERCLOCK) * 100.f));
|
||||
float vps =
|
||||
static_cast<float>(Core::System::GetInstance().GetVideoInterface().GetTargetRefreshRate());
|
||||
if (vps == 0.0f || !Config::Get(Config::MAIN_VI_OVERCLOCK_ENABLE))
|
||||
vps = 59.94f * Config::Get(Config::MAIN_VI_OVERCLOCK);
|
||||
return tr("%1% (%2 VPS)").arg(QString::number(percent), QString::number(vps, 'f', 2));
|
||||
}());
|
||||
m_vi_label->setEnabled(enable_vi_rate_override_widgets);
|
||||
|
||||
m_ram_override_checkbox->setEnabled(is_uninitialized);
|
||||
SignalBlocking(m_ram_override_checkbox)->setChecked(enable_ram_override_widgets);
|
||||
|
||||
m_mem1_override_slider->setEnabled(enable_ram_override_widgets && is_uninitialized);
|
||||
m_mem1_override_slider_label->setEnabled(enable_ram_override_widgets && is_uninitialized);
|
||||
|
||||
{
|
||||
const QSignalBlocker blocker(m_mem1_override_slider);
|
||||
const u32 mem1_size = Config::Get(Config::MAIN_MEM1_SIZE) / 0x100000;
|
||||
m_mem1_override_slider->setValue(mem1_size);
|
||||
}
|
||||
|
||||
m_mem1_override_slider_label->setText([] {
|
||||
const u32 mem1_size = Config::Get(Config::MAIN_MEM1_SIZE) / 0x100000;
|
||||
return tr("%1 MB (MEM1)").arg(QString::number(mem1_size));
|
||||
}());
|
||||
m_mem1_label->setEnabled(enable_ram_override_widgets && is_uninitialized);
|
||||
|
||||
m_mem2_override_slider->setEnabled(enable_ram_override_widgets && is_uninitialized);
|
||||
m_mem2_override_slider_label->setEnabled(enable_ram_override_widgets && is_uninitialized);
|
||||
|
||||
{
|
||||
const QSignalBlocker blocker(m_mem2_override_slider);
|
||||
const u32 mem2_size = Config::Get(Config::MAIN_MEM2_SIZE) / 0x100000;
|
||||
m_mem2_override_slider->setValue(mem2_size);
|
||||
}
|
||||
|
||||
m_mem2_override_slider_label->setText([] {
|
||||
const u32 mem2_size = Config::Get(Config::MAIN_MEM2_SIZE) / 0x100000;
|
||||
return tr("%1 MB (MEM2)").arg(QString::number(mem2_size));
|
||||
}());
|
||||
m_mem2_label->setEnabled(enable_ram_override_widgets && is_uninitialized);
|
||||
|
||||
m_custom_rtc_checkbox->setEnabled(is_uninitialized);
|
||||
SignalBlocking(m_custom_rtc_checkbox)->setChecked(Config::Get(Config::MAIN_CUSTOM_RTC_ENABLE));
|
||||
|
@ -8,6 +8,9 @@
|
||||
#include <QWidget>
|
||||
|
||||
class ConfigBool;
|
||||
class ConfigFloatSlider;
|
||||
class ConfigSlider;
|
||||
class ConfigSliderU32;
|
||||
class QCheckBox;
|
||||
class QComboBox;
|
||||
class QLabel;
|
||||
@ -36,19 +39,19 @@ private:
|
||||
ConfigBool* m_pause_on_panic_checkbox;
|
||||
ConfigBool* m_accurate_cpu_cache_checkbox;
|
||||
ConfigBool* m_cpu_clock_override_checkbox;
|
||||
QSlider* m_cpu_clock_override_slider;
|
||||
QLabel* m_cpu_clock_override_slider_label;
|
||||
ConfigFloatSlider* m_cpu_clock_override_slider;
|
||||
QLabel* m_cpu_label;
|
||||
|
||||
ConfigBool* m_vi_rate_override_checkbox;
|
||||
QSlider* m_vi_rate_override_slider;
|
||||
QLabel* m_vi_rate_override_slider_label;
|
||||
ConfigFloatSlider* m_vi_rate_override_slider;
|
||||
QLabel* m_vi_label;
|
||||
|
||||
ConfigBool* m_custom_rtc_checkbox;
|
||||
QDateTimeEdit* m_custom_rtc_datetime;
|
||||
|
||||
ConfigBool* m_ram_override_checkbox;
|
||||
QSlider* m_mem1_override_slider;
|
||||
QLabel* m_mem1_override_slider_label;
|
||||
QSlider* m_mem2_override_slider;
|
||||
QLabel* m_mem2_override_slider_label;
|
||||
ConfigSliderU32* m_mem1_override_slider;
|
||||
QLabel* m_mem1_label;
|
||||
ConfigSliderU32* m_mem2_override_slider;
|
||||
QLabel* m_mem2_label;
|
||||
};
|
||||
|
@ -95,7 +95,6 @@ void BroadbandAdapterSettingsDialog::InitControls()
|
||||
}
|
||||
|
||||
setWindowTitle(window_title);
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
m_address_input = new QLineEdit(current_address);
|
||||
m_address_input->setPlaceholderText(address_placeholder);
|
||||
|
@ -42,7 +42,6 @@ USBDeviceAddToWhitelistDialog::USBDeviceAddToWhitelistDialog(QWidget* parent) :
|
||||
void USBDeviceAddToWhitelistDialog::InitControls()
|
||||
{
|
||||
setWindowTitle(tr("Add New USB Device"));
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
m_whitelist_buttonbox = new QDialogButtonBox();
|
||||
auto* add_button = new QPushButton(tr("Add"));
|
||||
|
@ -45,7 +45,6 @@ ControllerEmu::InputOverrideFunction InputOverrider::GetInputOverrideFunction()
|
||||
|
||||
TASInputWindow::TASInputWindow(QWidget* parent) : QDialog(parent)
|
||||
{
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
setWindowIcon(Resources::GetAppIcon());
|
||||
|
||||
QGridLayout* settings_layout = new QGridLayout;
|
||||
|
@ -59,7 +59,6 @@ void Updater::OnUpdateAvailable(const NewVersionInformation& info)
|
||||
QDialog* dialog = new QDialog(m_parent);
|
||||
dialog->setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
dialog->setWindowTitle(tr("Update available"));
|
||||
dialog->setWindowFlags(dialog->windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
auto* label = new QLabel(
|
||||
tr("<h2>A new version of Dolphin is available!</h2>Dolphin %1 is available for "
|
||||
|
@ -96,7 +96,6 @@ static WiiUtils::UpdateResult ShowProgress(QWidget* parent, Callable function, A
|
||||
UpdateProgressDialog dialog{parent};
|
||||
dialog.setLabelText(QObject::tr("Preparing to update...\nThis can take a while."));
|
||||
dialog.setWindowTitle(QObject::tr("Updating"));
|
||||
dialog.setWindowFlags(dialog.windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
// QProgressDialog doesn't set its minimum size correctly.
|
||||
dialog.setMinimumSize(360, 150);
|
||||
|
||||
|
@ -3,46 +3,54 @@
|
||||
|
||||
#include "VideoCommon/Assets/CustomAsset.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
CustomAsset::CustomAsset(std::shared_ptr<CustomAssetLibrary> library,
|
||||
const CustomAssetLibrary::AssetID& asset_id)
|
||||
: m_owning_library(std::move(library)), m_asset_id(asset_id)
|
||||
const CustomAssetLibrary::AssetID& asset_id, u64 asset_handle)
|
||||
: m_owning_library(std::move(library)), m_asset_id(asset_id), m_handle(asset_handle)
|
||||
{
|
||||
}
|
||||
|
||||
bool CustomAsset::Load()
|
||||
{
|
||||
const auto load_information = LoadImpl(m_asset_id);
|
||||
if (load_information.m_bytes_loaded > 0)
|
||||
{
|
||||
std::lock_guard lk(m_info_lock);
|
||||
m_bytes_loaded = load_information.m_bytes_loaded;
|
||||
m_last_loaded_time = load_information.m_load_time;
|
||||
}
|
||||
return load_information.m_bytes_loaded != 0;
|
||||
}
|
||||
|
||||
CustomAssetLibrary::TimeType CustomAsset::GetLastWriteTime() const
|
||||
{
|
||||
return m_owning_library->GetLastAssetWriteTime(m_asset_id);
|
||||
}
|
||||
|
||||
const CustomAssetLibrary::TimeType& CustomAsset::GetLastLoadedTime() const
|
||||
std::size_t CustomAsset::Load()
|
||||
{
|
||||
std::lock_guard lk(m_info_lock);
|
||||
// The load time needs to come from before the data is actually read.
|
||||
// Using a time point from after the read marks the asset as more up-to-date than it actually is,
|
||||
// and has potential to race (and not be updated) if a change happens immediately after load.
|
||||
const auto load_time = ClockType::now();
|
||||
|
||||
const auto load_information = LoadImpl(m_asset_id);
|
||||
if (load_information.bytes_loaded > 0)
|
||||
{
|
||||
m_bytes_loaded = load_information.bytes_loaded;
|
||||
m_last_loaded_time = load_time;
|
||||
return m_bytes_loaded;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::size_t CustomAsset::Unload()
|
||||
{
|
||||
std::lock_guard lk(m_info_lock);
|
||||
UnloadImpl();
|
||||
return std::exchange(m_bytes_loaded, 0);
|
||||
}
|
||||
|
||||
CustomAsset::TimeType CustomAsset::GetLastLoadedTime() const
|
||||
{
|
||||
return m_last_loaded_time;
|
||||
}
|
||||
|
||||
std::size_t CustomAsset::GetHandle() const
|
||||
{
|
||||
return m_handle;
|
||||
}
|
||||
|
||||
const CustomAssetLibrary::AssetID& CustomAsset::GetAssetId() const
|
||||
{
|
||||
return m_asset_id;
|
||||
}
|
||||
|
||||
std::size_t CustomAsset::GetByteSizeInMemory() const
|
||||
{
|
||||
std::lock_guard lk(m_info_lock);
|
||||
return m_bytes_loaded;
|
||||
}
|
||||
|
||||
} // namespace VideoCommon
|
||||
|
@ -6,9 +6,9 @@
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "VideoCommon/Assets/CustomAssetLibrary.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
@ -17,42 +17,47 @@ namespace VideoCommon
|
||||
class CustomAsset
|
||||
{
|
||||
public:
|
||||
using ClockType = std::chrono::steady_clock;
|
||||
using TimeType = ClockType::time_point;
|
||||
|
||||
CustomAsset(std::shared_ptr<CustomAssetLibrary> library,
|
||||
const CustomAssetLibrary::AssetID& asset_id);
|
||||
const CustomAssetLibrary::AssetID& asset_id, u64 session_id);
|
||||
virtual ~CustomAsset() = default;
|
||||
CustomAsset(const CustomAsset&) = delete;
|
||||
CustomAsset(CustomAsset&&) = delete;
|
||||
CustomAsset& operator=(const CustomAsset&) = delete;
|
||||
CustomAsset& operator=(CustomAsset&&) = delete;
|
||||
|
||||
// Loads the asset from the library returning a pass/fail result
|
||||
bool Load();
|
||||
// Loads the asset from the library returning the number of bytes loaded
|
||||
std::size_t Load();
|
||||
|
||||
// Queries the last time the asset was modified or standard epoch time
|
||||
// if the asset hasn't been modified yet
|
||||
// Note: not thread safe, expected to be called by the loader
|
||||
CustomAssetLibrary::TimeType GetLastWriteTime() const;
|
||||
// Unloads the asset data, resets the bytes loaded and
|
||||
// returns the number of bytes unloaded
|
||||
std::size_t Unload();
|
||||
|
||||
// Returns the time that the data was last loaded
|
||||
const CustomAssetLibrary::TimeType& GetLastLoadedTime() const;
|
||||
TimeType GetLastLoadedTime() const;
|
||||
|
||||
// Returns an id that uniquely identifies this asset
|
||||
const CustomAssetLibrary::AssetID& GetAssetId() const;
|
||||
|
||||
// A rough estimate of how much space this asset
|
||||
// will take in memroy
|
||||
std::size_t GetByteSizeInMemory() const;
|
||||
// Returns an id that is unique to this game session
|
||||
// This is a faster form to hash and can be used
|
||||
// as an index
|
||||
std::size_t GetHandle() const;
|
||||
|
||||
protected:
|
||||
const std::shared_ptr<CustomAssetLibrary> m_owning_library;
|
||||
|
||||
private:
|
||||
virtual CustomAssetLibrary::LoadInfo LoadImpl(const CustomAssetLibrary::AssetID& asset_id) = 0;
|
||||
virtual void UnloadImpl() = 0;
|
||||
CustomAssetLibrary::AssetID m_asset_id;
|
||||
std::size_t m_handle;
|
||||
|
||||
mutable std::mutex m_info_lock;
|
||||
std::size_t m_bytes_loaded = 0;
|
||||
CustomAssetLibrary::TimeType m_last_loaded_time = {};
|
||||
std::atomic<TimeType> m_last_loaded_time = {};
|
||||
};
|
||||
|
||||
// An abstract class that is expected to
|
||||
@ -83,6 +88,14 @@ protected:
|
||||
bool m_loaded = false;
|
||||
mutable std::mutex m_data_lock;
|
||||
std::shared_ptr<UnderlyingType> m_data;
|
||||
|
||||
private:
|
||||
void UnloadImpl() override
|
||||
{
|
||||
std::lock_guard lk(m_data_lock);
|
||||
m_loaded = false;
|
||||
m_data.reset();
|
||||
}
|
||||
};
|
||||
|
||||
// A helper struct that contains
|
||||
@ -96,7 +109,7 @@ template <typename AssetType>
|
||||
struct CachedAsset
|
||||
{
|
||||
std::shared_ptr<AssetType> m_asset;
|
||||
VideoCommon::CustomAssetLibrary::TimeType m_cached_write_time;
|
||||
CustomAsset::TimeType m_cached_write_time;
|
||||
};
|
||||
|
||||
} // namespace VideoCommon
|
||||
|
@ -1,85 +0,0 @@
|
||||
// Copyright 2023 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "VideoCommon/Assets/CustomAssetLibrary.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "VideoCommon/Assets/TextureAsset.h"
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
CustomAssetLibrary::LoadInfo CustomAssetLibrary::LoadGameTexture(const AssetID& asset_id,
|
||||
TextureData* data)
|
||||
{
|
||||
const auto load_info = LoadTexture(asset_id, data);
|
||||
if (load_info.m_bytes_loaded == 0)
|
||||
return {};
|
||||
|
||||
if (data->m_type != TextureData::Type::Type_Texture2D)
|
||||
{
|
||||
ERROR_LOG_FMT(
|
||||
VIDEO,
|
||||
"Custom asset '{}' is not a valid game texture, it is expected to be a 2d texture "
|
||||
"but was a '{}'.",
|
||||
asset_id, data->m_type);
|
||||
return {};
|
||||
}
|
||||
|
||||
// Note: 'LoadTexture()' ensures we have a level loaded
|
||||
for (std::size_t slice_index = 0; slice_index < data->m_texture.m_slices.size(); slice_index++)
|
||||
{
|
||||
auto& slice = data->m_texture.m_slices[slice_index];
|
||||
const auto& first_mip = slice.m_levels[0];
|
||||
|
||||
// Verify that each mip level is the correct size (divide by 2 each time).
|
||||
u32 current_mip_width = first_mip.width;
|
||||
u32 current_mip_height = first_mip.height;
|
||||
for (u32 mip_level = 1; mip_level < static_cast<u32>(slice.m_levels.size()); mip_level++)
|
||||
{
|
||||
if (current_mip_width != 1 || current_mip_height != 1)
|
||||
{
|
||||
current_mip_width = std::max(current_mip_width / 2, 1u);
|
||||
current_mip_height = std::max(current_mip_height / 2, 1u);
|
||||
|
||||
const VideoCommon::CustomTextureData::ArraySlice::Level& level = slice.m_levels[mip_level];
|
||||
if (current_mip_width == level.width && current_mip_height == level.height)
|
||||
continue;
|
||||
|
||||
ERROR_LOG_FMT(VIDEO,
|
||||
"Invalid custom game texture size {}x{} for texture asset {}. Slice {} with "
|
||||
"mipmap level {} "
|
||||
"must be {}x{}.",
|
||||
level.width, level.height, asset_id, slice_index, mip_level,
|
||||
current_mip_width, current_mip_height);
|
||||
}
|
||||
else
|
||||
{
|
||||
// It is invalid to have more than a single 1x1 mipmap.
|
||||
ERROR_LOG_FMT(
|
||||
VIDEO,
|
||||
"Custom game texture {} has too many 1x1 mipmaps for slice {}. Skipping extra levels.",
|
||||
asset_id, slice_index);
|
||||
}
|
||||
|
||||
// Drop this mip level and any others after it.
|
||||
while (slice.m_levels.size() > mip_level)
|
||||
slice.m_levels.pop_back();
|
||||
}
|
||||
|
||||
// All levels have to have the same format.
|
||||
if (std::ranges::any_of(slice.m_levels,
|
||||
[&first_mip](const auto& l) { return l.format != first_mip.format; }))
|
||||
{
|
||||
ERROR_LOG_FMT(
|
||||
VIDEO, "Custom game texture {} has inconsistent formats across mip levels for slice {}.",
|
||||
asset_id, slice_index);
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
return load_info;
|
||||
}
|
||||
} // namespace VideoCommon
|
@ -10,10 +10,11 @@
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
class CustomTextureData;
|
||||
struct MaterialData;
|
||||
struct MeshData;
|
||||
struct PixelShaderData;
|
||||
struct TextureData;
|
||||
struct TextureAndSamplerData;
|
||||
|
||||
// This class provides functionality to load
|
||||
// specific data (like textures). Where this data
|
||||
@ -21,28 +22,21 @@ struct TextureData;
|
||||
class CustomAssetLibrary
|
||||
{
|
||||
public:
|
||||
using TimeType = std::chrono::system_clock::time_point;
|
||||
|
||||
// The AssetID is a unique identifier for a particular asset
|
||||
using AssetID = std::string;
|
||||
|
||||
struct LoadInfo
|
||||
{
|
||||
std::size_t m_bytes_loaded = 0;
|
||||
TimeType m_load_time = {};
|
||||
std::size_t bytes_loaded = 0;
|
||||
};
|
||||
|
||||
virtual ~CustomAssetLibrary() = default;
|
||||
|
||||
// Loads a texture with a sampler and type, if there are no levels, bytes loaded will be empty
|
||||
virtual LoadInfo LoadTexture(const AssetID& asset_id, TextureAndSamplerData* data) = 0;
|
||||
|
||||
// Loads a texture, if there are no levels, bytes loaded will be empty
|
||||
virtual LoadInfo LoadTexture(const AssetID& asset_id, TextureData* data) = 0;
|
||||
|
||||
// Gets the last write time for a given asset id
|
||||
virtual TimeType GetLastAssetWriteTime(const AssetID& asset_id) const = 0;
|
||||
|
||||
// Loads a texture as a game texture, providing additional checks like confirming
|
||||
// each mip level size is correct and that the format is consistent across the data
|
||||
LoadInfo LoadGameTexture(const AssetID& asset_id, TextureData* data);
|
||||
virtual LoadInfo LoadTexture(const AssetID& asset_id, CustomTextureData* data) = 0;
|
||||
|
||||
// Loads a pixel shader
|
||||
virtual LoadInfo LoadPixelShader(const AssetID& asset_id, PixelShaderData* data) = 0;
|
||||
|
@ -1,108 +1,157 @@
|
||||
// Copyright 2023 Dolphin Emulator Project
|
||||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "VideoCommon/Assets/CustomAssetLoader.h"
|
||||
|
||||
#include "Common/MemoryUtil.h"
|
||||
#include "VideoCommon/Assets/CustomAssetLibrary.h"
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/Thread.h"
|
||||
|
||||
#include "UICommon/UICommon.h"
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
void CustomAssetLoader::Init()
|
||||
void CustomAssetLoader::Initialize()
|
||||
{
|
||||
m_asset_monitor_thread_shutdown.Clear();
|
||||
|
||||
const size_t sys_mem = Common::MemPhysical();
|
||||
const size_t recommended_min_mem = 2 * size_t(1024 * 1024 * 1024);
|
||||
// keep 2GB memory for system stability if system RAM is 4GB+ - use half of memory in other cases
|
||||
m_max_memory_available =
|
||||
(sys_mem / 2 < recommended_min_mem) ? (sys_mem / 2) : (sys_mem - recommended_min_mem);
|
||||
|
||||
m_asset_monitor_thread = std::thread([this]() {
|
||||
Common::SetCurrentThreadName("Asset monitor");
|
||||
while (true)
|
||||
{
|
||||
if (m_asset_monitor_thread_shutdown.IsSet())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(TIME_BETWEEN_ASSET_MONITOR_CHECKS);
|
||||
|
||||
std::lock_guard lk(m_asset_load_lock);
|
||||
for (auto& [asset_id, asset_to_monitor] : m_assets_to_monitor)
|
||||
{
|
||||
if (auto ptr = asset_to_monitor.lock())
|
||||
{
|
||||
const auto write_time = ptr->GetLastWriteTime();
|
||||
if (write_time > ptr->GetLastLoadedTime())
|
||||
{
|
||||
(void)ptr->Load();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
m_asset_load_thread.Reset("Custom Asset Loader", [this](std::weak_ptr<CustomAsset> asset) {
|
||||
if (auto ptr = asset.lock())
|
||||
{
|
||||
if (m_memory_exceeded)
|
||||
return;
|
||||
|
||||
if (ptr->Load())
|
||||
{
|
||||
std::lock_guard lk(m_asset_load_lock);
|
||||
const std::size_t asset_memory_size = ptr->GetByteSizeInMemory();
|
||||
m_total_bytes_loaded += asset_memory_size;
|
||||
m_assets_to_monitor.try_emplace(ptr->GetAssetId(), ptr);
|
||||
if (m_total_bytes_loaded > m_max_memory_available)
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO,
|
||||
"Asset memory exceeded with asset '{}', future assets won't load until "
|
||||
"memory is available.",
|
||||
ptr->GetAssetId());
|
||||
m_memory_exceeded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
ResizeWorkerThreads(2);
|
||||
}
|
||||
|
||||
void CustomAssetLoader::Shutdown()
|
||||
{
|
||||
m_asset_load_thread.StopAndCancel();
|
||||
|
||||
m_asset_monitor_thread_shutdown.Set();
|
||||
m_asset_monitor_thread.join();
|
||||
m_assets_to_monitor.clear();
|
||||
m_total_bytes_loaded = 0;
|
||||
Reset(false);
|
||||
}
|
||||
|
||||
std::shared_ptr<GameTextureAsset>
|
||||
CustomAssetLoader::LoadGameTexture(const CustomAssetLibrary::AssetID& asset_id,
|
||||
std::shared_ptr<CustomAssetLibrary> library)
|
||||
bool CustomAssetLoader::StartWorkerThreads(u32 num_worker_threads)
|
||||
{
|
||||
return LoadOrCreateAsset<GameTextureAsset>(asset_id, m_game_textures, std::move(library));
|
||||
for (u32 i = 0; i < num_worker_threads; i++)
|
||||
{
|
||||
m_worker_threads.emplace_back(&CustomAssetLoader::WorkerThreadRun, this, i);
|
||||
}
|
||||
|
||||
return HasWorkerThreads();
|
||||
}
|
||||
|
||||
std::shared_ptr<PixelShaderAsset>
|
||||
CustomAssetLoader::LoadPixelShader(const CustomAssetLibrary::AssetID& asset_id,
|
||||
std::shared_ptr<CustomAssetLibrary> library)
|
||||
bool CustomAssetLoader::ResizeWorkerThreads(u32 num_worker_threads)
|
||||
{
|
||||
return LoadOrCreateAsset<PixelShaderAsset>(asset_id, m_pixel_shaders, std::move(library));
|
||||
if (m_worker_threads.size() == num_worker_threads)
|
||||
return true;
|
||||
|
||||
StopWorkerThreads();
|
||||
return StartWorkerThreads(num_worker_threads);
|
||||
}
|
||||
|
||||
std::shared_ptr<MaterialAsset>
|
||||
CustomAssetLoader::LoadMaterial(const CustomAssetLibrary::AssetID& asset_id,
|
||||
std::shared_ptr<CustomAssetLibrary> library)
|
||||
bool CustomAssetLoader::HasWorkerThreads() const
|
||||
{
|
||||
return LoadOrCreateAsset<MaterialAsset>(asset_id, m_materials, std::move(library));
|
||||
return !m_worker_threads.empty();
|
||||
}
|
||||
|
||||
std::shared_ptr<MeshAsset> CustomAssetLoader::LoadMesh(const CustomAssetLibrary::AssetID& asset_id,
|
||||
std::shared_ptr<CustomAssetLibrary> library)
|
||||
void CustomAssetLoader::StopWorkerThreads()
|
||||
{
|
||||
return LoadOrCreateAsset<MeshAsset>(asset_id, m_meshes, std::move(library));
|
||||
if (!HasWorkerThreads())
|
||||
return;
|
||||
|
||||
// Signal worker threads to stop, and wake all of them.
|
||||
{
|
||||
std::lock_guard guard(m_assets_to_load_lock);
|
||||
m_exit_flag.Set();
|
||||
m_worker_thread_wake.notify_all();
|
||||
}
|
||||
|
||||
// Wait for worker threads to exit.
|
||||
for (std::thread& thr : m_worker_threads)
|
||||
thr.join();
|
||||
m_worker_threads.clear();
|
||||
m_exit_flag.Clear();
|
||||
}
|
||||
|
||||
void CustomAssetLoader::WorkerThreadRun(u32 thread_index)
|
||||
{
|
||||
Common::SetCurrentThreadName(fmt::format("Asset Loader {}", thread_index).c_str());
|
||||
|
||||
std::unique_lock load_lock(m_assets_to_load_lock);
|
||||
while (true)
|
||||
{
|
||||
m_worker_thread_wake.wait(load_lock,
|
||||
[&] { return !m_assets_to_load.empty() || m_exit_flag.IsSet(); });
|
||||
|
||||
if (m_exit_flag.IsSet())
|
||||
return;
|
||||
|
||||
// If more memory than allowed has already been loaded, we will load nothing more
|
||||
// until the next ScheduleAssetsToLoad from Manager.
|
||||
if (m_change_in_memory > m_allowed_memory)
|
||||
{
|
||||
m_assets_to_load.clear();
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* const item = m_assets_to_load.front();
|
||||
m_assets_to_load.pop_front();
|
||||
|
||||
// Make sure another thread isn't loading this handle.
|
||||
if (!m_handles_in_progress.insert(item->GetHandle()).second)
|
||||
continue;
|
||||
|
||||
load_lock.unlock();
|
||||
|
||||
// Unload previously loaded asset.
|
||||
m_change_in_memory -= item->Unload();
|
||||
|
||||
const std::size_t bytes_loaded = item->Load();
|
||||
m_change_in_memory += s64(bytes_loaded);
|
||||
|
||||
load_lock.lock();
|
||||
|
||||
{
|
||||
INFO_LOG_FMT(VIDEO, "CustomAssetLoader thread {} loaded: {} ({})", thread_index,
|
||||
item->GetAssetId(), UICommon::FormatSize(bytes_loaded));
|
||||
|
||||
std::lock_guard lk{m_assets_loaded_lock};
|
||||
m_asset_handles_loaded.emplace_back(item->GetHandle(), bytes_loaded > 0);
|
||||
|
||||
// Make sure no other threads try to re-process this item.
|
||||
// Manager will take the handles and re-ScheduleAssetsToLoad based on timestamps if needed.
|
||||
std::erase(m_assets_to_load, item);
|
||||
}
|
||||
|
||||
m_handles_in_progress.erase(item->GetHandle());
|
||||
}
|
||||
}
|
||||
|
||||
auto CustomAssetLoader::TakeLoadResults() -> LoadResults
|
||||
{
|
||||
std::lock_guard guard(m_assets_loaded_lock);
|
||||
return {std::move(m_asset_handles_loaded), m_change_in_memory.exchange(0)};
|
||||
}
|
||||
|
||||
void CustomAssetLoader::ScheduleAssetsToLoad(std::list<CustomAsset*> assets_to_load,
|
||||
u64 allowed_memory)
|
||||
{
|
||||
if (assets_to_load.empty()) [[unlikely]]
|
||||
return;
|
||||
|
||||
// There's new assets to process, notify worker threads
|
||||
std::lock_guard guard(m_assets_to_load_lock);
|
||||
m_allowed_memory = allowed_memory;
|
||||
m_assets_to_load = std::move(assets_to_load);
|
||||
m_worker_thread_wake.notify_all();
|
||||
}
|
||||
|
||||
void CustomAssetLoader::Reset(bool restart_worker_threads)
|
||||
{
|
||||
const std::size_t worker_thread_count = m_worker_threads.size();
|
||||
StopWorkerThreads();
|
||||
|
||||
m_assets_to_load.clear();
|
||||
m_asset_handles_loaded.clear();
|
||||
m_allowed_memory = 0;
|
||||
m_change_in_memory = 0;
|
||||
|
||||
if (restart_worker_threads)
|
||||
{
|
||||
StartWorkerThreads(static_cast<u32>(worker_thread_count));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace VideoCommon
|
||||
|
@ -1,27 +1,24 @@
|
||||
// Copyright 2023 Dolphin Emulator Project
|
||||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/Flag.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/WorkQueueThread.h"
|
||||
#include "VideoCommon/Assets/CustomAsset.h"
|
||||
#include "VideoCommon/Assets/MaterialAsset.h"
|
||||
#include "VideoCommon/Assets/MeshAsset.h"
|
||||
#include "VideoCommon/Assets/ShaderAsset.h"
|
||||
#include "VideoCommon/Assets/TextureAsset.h"
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
// This class is responsible for loading data asynchronously when requested
|
||||
// and watches that data asynchronously reloading it if it changes
|
||||
// This class takes any number of assets
|
||||
// and loads them across a configurable
|
||||
// thread pool
|
||||
class CustomAssetLoader
|
||||
{
|
||||
public:
|
||||
@ -32,77 +29,54 @@ public:
|
||||
CustomAssetLoader& operator=(const CustomAssetLoader&) = delete;
|
||||
CustomAssetLoader& operator=(CustomAssetLoader&&) = delete;
|
||||
|
||||
void Init();
|
||||
void Initialize();
|
||||
void Shutdown();
|
||||
|
||||
// The following Load* functions will load or create an asset associated
|
||||
// with the given asset id
|
||||
// Loads happen asynchronously where the data will be set now or in the future
|
||||
// Callees are expected to query the underlying data with 'GetData()'
|
||||
// from the 'CustomLoadableAsset' class to determine if the data is ready for use
|
||||
std::shared_ptr<GameTextureAsset> LoadGameTexture(const CustomAssetLibrary::AssetID& asset_id,
|
||||
std::shared_ptr<CustomAssetLibrary> library);
|
||||
using AssetHandle = std::pair<std::size_t, bool>;
|
||||
struct LoadResults
|
||||
|
||||
std::shared_ptr<PixelShaderAsset> LoadPixelShader(const CustomAssetLibrary::AssetID& asset_id,
|
||||
std::shared_ptr<CustomAssetLibrary> library);
|
||||
{
|
||||
std::vector<AssetHandle> asset_handles;
|
||||
s64 change_in_memory;
|
||||
};
|
||||
|
||||
std::shared_ptr<MaterialAsset> LoadMaterial(const CustomAssetLibrary::AssetID& asset_id,
|
||||
std::shared_ptr<CustomAssetLibrary> library);
|
||||
// Returns a vector of loaded asset handle / loaded result pairs
|
||||
// and the change in memory.
|
||||
LoadResults TakeLoadResults();
|
||||
|
||||
std::shared_ptr<MeshAsset> LoadMesh(const CustomAssetLibrary::AssetID& asset_id,
|
||||
std::shared_ptr<CustomAssetLibrary> library);
|
||||
// Schedule assets to load on the worker threads
|
||||
// and set how much memory is available for loading these additional assets.
|
||||
void ScheduleAssetsToLoad(std::list<CustomAsset*> assets_to_load, u64 allowed_memory);
|
||||
|
||||
void Reset(bool restart_worker_threads = true);
|
||||
|
||||
private:
|
||||
// TODO C++20: use a 'derived_from' concept against 'CustomAsset' when available
|
||||
template <typename AssetType>
|
||||
std::shared_ptr<AssetType>
|
||||
LoadOrCreateAsset(const CustomAssetLibrary::AssetID& asset_id,
|
||||
std::map<CustomAssetLibrary::AssetID, std::weak_ptr<AssetType>>& asset_map,
|
||||
std::shared_ptr<CustomAssetLibrary> library)
|
||||
{
|
||||
auto [it, inserted] = asset_map.try_emplace(asset_id);
|
||||
if (!inserted)
|
||||
{
|
||||
auto shared = it->second.lock();
|
||||
if (shared)
|
||||
return shared;
|
||||
}
|
||||
std::shared_ptr<AssetType> ptr(new AssetType(std::move(library), asset_id), [&](AssetType* a) {
|
||||
{
|
||||
std::lock_guard lk(m_asset_load_lock);
|
||||
m_total_bytes_loaded -= a->GetByteSizeInMemory();
|
||||
m_assets_to_monitor.erase(a->GetAssetId());
|
||||
if (m_max_memory_available >= m_total_bytes_loaded && m_memory_exceeded)
|
||||
{
|
||||
INFO_LOG_FMT(VIDEO, "Asset memory went below limit, new assets can begin loading.");
|
||||
m_memory_exceeded = false;
|
||||
}
|
||||
}
|
||||
delete a;
|
||||
});
|
||||
it->second = ptr;
|
||||
m_asset_load_thread.Push(it->second);
|
||||
return ptr;
|
||||
}
|
||||
bool StartWorkerThreads(u32 num_worker_threads);
|
||||
bool ResizeWorkerThreads(u32 num_worker_threads);
|
||||
bool HasWorkerThreads() const;
|
||||
void StopWorkerThreads();
|
||||
|
||||
static constexpr auto TIME_BETWEEN_ASSET_MONITOR_CHECKS = std::chrono::milliseconds{500};
|
||||
void WorkerThreadRun(u32 thread_index);
|
||||
|
||||
std::map<CustomAssetLibrary::AssetID, std::weak_ptr<GameTextureAsset>> m_game_textures;
|
||||
std::map<CustomAssetLibrary::AssetID, std::weak_ptr<PixelShaderAsset>> m_pixel_shaders;
|
||||
std::map<CustomAssetLibrary::AssetID, std::weak_ptr<MaterialAsset>> m_materials;
|
||||
std::map<CustomAssetLibrary::AssetID, std::weak_ptr<MeshAsset>> m_meshes;
|
||||
std::thread m_asset_monitor_thread;
|
||||
Common::Flag m_asset_monitor_thread_shutdown;
|
||||
Common::Flag m_exit_flag;
|
||||
|
||||
std::size_t m_total_bytes_loaded = 0;
|
||||
std::size_t m_max_memory_available = 0;
|
||||
std::atomic_bool m_memory_exceeded = false;
|
||||
std::vector<std::thread> m_worker_threads;
|
||||
|
||||
std::map<CustomAssetLibrary::AssetID, std::weak_ptr<CustomAsset>> m_assets_to_monitor;
|
||||
std::mutex m_assets_to_load_lock;
|
||||
std::list<CustomAsset*> m_assets_to_load;
|
||||
|
||||
// Use a recursive mutex to handle the scenario where an asset goes out of scope while
|
||||
// iterating over the assets to monitor which calls the lock above in 'LoadOrCreateAsset'
|
||||
std::recursive_mutex m_asset_load_lock;
|
||||
Common::WorkQueueThread<std::weak_ptr<CustomAsset>> m_asset_load_thread;
|
||||
std::condition_variable m_worker_thread_wake;
|
||||
|
||||
std::vector<AssetHandle> m_asset_handles_loaded;
|
||||
|
||||
// Memory available to load new assets.
|
||||
s64 m_allowed_memory = 0;
|
||||
|
||||
// Change in memory from just-loaded/unloaded asset results yet to be taken by the Manager.
|
||||
std::atomic<s64> m_change_in_memory = 0;
|
||||
|
||||
std::mutex m_assets_loaded_lock;
|
||||
|
||||
std::set<std::size_t> m_handles_in_progress;
|
||||
};
|
||||
} // namespace VideoCommon
|
||||
|
229
Source/Core/VideoCommon/Assets/CustomResourceManager.cpp
Normal file
229
Source/Core/VideoCommon/Assets/CustomResourceManager.cpp
Normal file
@ -0,0 +1,229 @@
|
||||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "VideoCommon/Assets/CustomResourceManager.h"
|
||||
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MemoryUtil.h"
|
||||
|
||||
#include "UICommon/UICommon.h"
|
||||
|
||||
#include "VideoCommon/Assets/CustomAsset.h"
|
||||
#include "VideoCommon/Assets/TextureAsset.h"
|
||||
#include "VideoCommon/VideoEvents.h"
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
void CustomResourceManager::Initialize()
|
||||
{
|
||||
// Use half of available system memory but leave at least 2GiB unused for system stability.
|
||||
constexpr size_t must_keep_unused = 2 * size_t(1024 * 1024 * 1024);
|
||||
|
||||
const size_t sys_mem = Common::MemPhysical();
|
||||
const size_t keep_unused_mem = std::max(sys_mem / 2, std::min(sys_mem, must_keep_unused));
|
||||
|
||||
m_max_ram_available = sys_mem - keep_unused_mem;
|
||||
|
||||
if (m_max_ram_available == 0)
|
||||
ERROR_LOG_FMT(VIDEO, "Not enough system memory for custom resources.");
|
||||
|
||||
m_asset_loader.Initialize();
|
||||
|
||||
m_xfb_event =
|
||||
AfterFrameEvent::Register([this](Core::System&) { XFBTriggered(); }, "CustomResourceManager");
|
||||
}
|
||||
|
||||
void CustomResourceManager::Shutdown()
|
||||
{
|
||||
Reset();
|
||||
|
||||
m_asset_loader.Shutdown();
|
||||
}
|
||||
|
||||
void CustomResourceManager::Reset()
|
||||
{
|
||||
m_asset_loader.Reset(true);
|
||||
|
||||
m_active_assets = {};
|
||||
m_pending_assets = {};
|
||||
m_asset_handle_to_data.clear();
|
||||
m_asset_id_to_handle.clear();
|
||||
m_texture_data_asset_cache.clear();
|
||||
m_dirty_assets.clear();
|
||||
m_ram_used = 0;
|
||||
}
|
||||
|
||||
void CustomResourceManager::MarkAssetDirty(const CustomAssetLibrary::AssetID& asset_id)
|
||||
{
|
||||
std::lock_guard guard(m_dirty_mutex);
|
||||
m_dirty_assets.insert(asset_id);
|
||||
}
|
||||
|
||||
CustomResourceManager::TextureTimePair CustomResourceManager::GetTextureDataFromAsset(
|
||||
const CustomAssetLibrary::AssetID& asset_id,
|
||||
std::shared_ptr<VideoCommon::CustomAssetLibrary> library)
|
||||
{
|
||||
auto& resource = m_texture_data_asset_cache[asset_id];
|
||||
if (resource.asset_data != nullptr &&
|
||||
resource.asset_data->load_status == AssetData::LoadStatus::ResourceDataAvailable)
|
||||
{
|
||||
m_active_assets.MakeAssetHighestPriority(resource.asset->GetHandle(), resource.asset);
|
||||
return {resource.texture_data, resource.asset->GetLastLoadedTime()};
|
||||
}
|
||||
|
||||
// If there is an error, don't try and load again until the error is fixed
|
||||
if (resource.asset_data != nullptr && resource.asset_data->has_load_error)
|
||||
return {};
|
||||
|
||||
LoadTextureDataAsset(asset_id, std::move(library), &resource);
|
||||
m_active_assets.MakeAssetHighestPriority(resource.asset->GetHandle(), resource.asset);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void CustomResourceManager::LoadTextureDataAsset(
|
||||
const CustomAssetLibrary::AssetID& asset_id,
|
||||
std::shared_ptr<VideoCommon::CustomAssetLibrary> library, InternalTextureDataResource* resource)
|
||||
{
|
||||
if (!resource->asset)
|
||||
{
|
||||
resource->asset =
|
||||
CreateAsset<TextureAsset>(asset_id, AssetData::AssetType::TextureData, std::move(library));
|
||||
resource->asset_data = &m_asset_handle_to_data[resource->asset->GetHandle()];
|
||||
}
|
||||
|
||||
auto texture_data = resource->asset->GetData();
|
||||
if (!texture_data || resource->asset_data->load_status == AssetData::LoadStatus::PendingReload)
|
||||
{
|
||||
// Tell the system we are still interested in loading this asset
|
||||
const auto asset_handle = resource->asset->GetHandle();
|
||||
m_pending_assets.MakeAssetHighestPriority(asset_handle,
|
||||
m_asset_handle_to_data[asset_handle].asset.get());
|
||||
}
|
||||
else if (resource->asset_data->load_status == AssetData::LoadStatus::LoadFinished)
|
||||
{
|
||||
resource->texture_data = std::move(texture_data);
|
||||
resource->asset_data->load_status = AssetData::LoadStatus::ResourceDataAvailable;
|
||||
}
|
||||
}
|
||||
|
||||
void CustomResourceManager::XFBTriggered()
|
||||
{
|
||||
ProcessDirtyAssets();
|
||||
ProcessLoadedAssets();
|
||||
|
||||
if (m_ram_used > m_max_ram_available)
|
||||
{
|
||||
RemoveAssetsUntilBelowMemoryLimit();
|
||||
}
|
||||
|
||||
if (m_pending_assets.IsEmpty())
|
||||
return;
|
||||
|
||||
if (m_ram_used > m_max_ram_available)
|
||||
return;
|
||||
|
||||
const u64 allowed_memory = m_max_ram_available - m_ram_used;
|
||||
m_asset_loader.ScheduleAssetsToLoad(m_pending_assets.Elements(), allowed_memory);
|
||||
}
|
||||
|
||||
void CustomResourceManager::ProcessDirtyAssets()
|
||||
{
|
||||
decltype(m_dirty_assets) dirty_assets;
|
||||
|
||||
if (const auto lk = std::unique_lock{m_dirty_mutex, std::try_to_lock})
|
||||
std::swap(dirty_assets, m_dirty_assets);
|
||||
|
||||
const auto now = CustomAsset::ClockType::now();
|
||||
for (const auto& asset_id : dirty_assets)
|
||||
{
|
||||
if (const auto it = m_asset_id_to_handle.find(asset_id); it != m_asset_id_to_handle.end())
|
||||
{
|
||||
const auto asset_handle = it->second;
|
||||
AssetData& asset_data = m_asset_handle_to_data[asset_handle];
|
||||
asset_data.load_status = AssetData::LoadStatus::PendingReload;
|
||||
asset_data.load_request_time = now;
|
||||
|
||||
// Asset was reloaded, clear any errors we might have
|
||||
asset_data.has_load_error = false;
|
||||
|
||||
m_pending_assets.InsertAsset(it->second, asset_data.asset.get());
|
||||
|
||||
DEBUG_LOG_FMT(VIDEO, "Dirty asset pending reload: {}", asset_data.asset->GetAssetId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CustomResourceManager::ProcessLoadedAssets()
|
||||
{
|
||||
const auto load_results = m_asset_loader.TakeLoadResults();
|
||||
|
||||
// Update the ram with the change in memory from the loader
|
||||
//
|
||||
// Note: Assets with outstanding reload requests will have
|
||||
// two copies in memory temporarily (the old data stored in
|
||||
// the asset shared_ptr that the resource manager owns, and
|
||||
// the new data loaded from the loader in the asset's shared_ptr)
|
||||
// This temporary duplication will not be reflected in the
|
||||
// resource manager's ram used
|
||||
m_ram_used += load_results.change_in_memory;
|
||||
|
||||
for (const auto& [handle, load_successful] : load_results.asset_handles)
|
||||
{
|
||||
AssetData& asset_data = m_asset_handle_to_data[handle];
|
||||
|
||||
// If we have a reload request that is newer than our loaded time
|
||||
// we need to wait for another reload.
|
||||
if (asset_data.load_request_time > asset_data.asset->GetLastLoadedTime())
|
||||
continue;
|
||||
|
||||
m_pending_assets.RemoveAsset(handle);
|
||||
|
||||
asset_data.load_request_time = {};
|
||||
if (!load_successful)
|
||||
{
|
||||
asset_data.has_load_error = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_active_assets.InsertAsset(handle, asset_data.asset.get());
|
||||
asset_data.load_status = AssetData::LoadStatus::LoadFinished;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CustomResourceManager::RemoveAssetsUntilBelowMemoryLimit()
|
||||
{
|
||||
const u64 threshold_ram = m_max_ram_available * 8 / 10;
|
||||
|
||||
if (m_ram_used > threshold_ram)
|
||||
{
|
||||
INFO_LOG_FMT(VIDEO, "Memory usage over threshold: {}", UICommon::FormatSize(m_ram_used));
|
||||
}
|
||||
|
||||
// Clear out least recently used resources until
|
||||
// we get safely in our threshold
|
||||
while (m_ram_used > threshold_ram && m_active_assets.Size() > 0)
|
||||
{
|
||||
auto* const asset = m_active_assets.RemoveLowestPriorityAsset();
|
||||
|
||||
AssetData& asset_data = m_asset_handle_to_data[asset->GetHandle()];
|
||||
|
||||
// Remove the resource manager's cached entry with its asset data
|
||||
if (asset_data.type == AssetData::AssetType::TextureData)
|
||||
{
|
||||
m_texture_data_asset_cache.erase(asset->GetAssetId());
|
||||
}
|
||||
// Remove the asset's copy
|
||||
const std::size_t bytes_unloaded = asset_data.asset->Unload();
|
||||
m_ram_used -= bytes_unloaded;
|
||||
|
||||
asset_data.load_status = AssetData::LoadStatus::Unloaded;
|
||||
asset_data.load_request_time = {};
|
||||
|
||||
INFO_LOG_FMT(VIDEO, "Unloading asset: {} ({})", asset_data.asset->GetAssetId(),
|
||||
UICommon::FormatSize(bytes_unloaded));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace VideoCommon
|
215
Source/Core/VideoCommon/Assets/CustomResourceManager.h
Normal file
215
Source/Core/VideoCommon/Assets/CustomResourceManager.h
Normal file
@ -0,0 +1,215 @@
|
||||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/HookableEvent.h"
|
||||
|
||||
#include "VideoCommon/Assets/CustomAsset.h"
|
||||
#include "VideoCommon/Assets/CustomAssetLibrary.h"
|
||||
#include "VideoCommon/Assets/CustomAssetLoader.h"
|
||||
#include "VideoCommon/Assets/CustomTextureData.h"
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
class TextureAsset;
|
||||
|
||||
// The resource manager manages custom resources (textures, shaders, meshes)
|
||||
// called assets. These assets are loaded using a priority system,
|
||||
// where assets requested more often gets loaded first. This system
|
||||
// also tracks memory usage and if memory usage goes over a calculated limit,
|
||||
// then assets will be purged with older assets being targeted first.
|
||||
class CustomResourceManager
|
||||
{
|
||||
public:
|
||||
void Initialize();
|
||||
void Shutdown();
|
||||
|
||||
void Reset();
|
||||
|
||||
// Request that an asset be reloaded
|
||||
void MarkAssetDirty(const CustomAssetLibrary::AssetID& asset_id);
|
||||
|
||||
void XFBTriggered();
|
||||
|
||||
using TextureTimePair = std::pair<std::shared_ptr<CustomTextureData>, CustomAsset::TimeType>;
|
||||
|
||||
// Returns a pair with the custom texture data and the time it was last loaded
|
||||
// Callees are not expected to hold onto the shared_ptr as that will prevent
|
||||
// the resource manager from being able to properly release data
|
||||
TextureTimePair GetTextureDataFromAsset(const CustomAssetLibrary::AssetID& asset_id,
|
||||
std::shared_ptr<VideoCommon::CustomAssetLibrary> library);
|
||||
|
||||
private:
|
||||
// A generic interface to describe an assets' type
|
||||
// and load state
|
||||
struct AssetData
|
||||
{
|
||||
std::unique_ptr<CustomAsset> asset;
|
||||
CustomAsset::TimeType load_request_time = {};
|
||||
bool has_load_error = false;
|
||||
|
||||
enum class AssetType
|
||||
{
|
||||
TextureData
|
||||
};
|
||||
AssetType type;
|
||||
|
||||
enum class LoadStatus
|
||||
{
|
||||
PendingReload,
|
||||
LoadFinished,
|
||||
ResourceDataAvailable,
|
||||
Unloaded,
|
||||
};
|
||||
LoadStatus load_status = LoadStatus::PendingReload;
|
||||
};
|
||||
|
||||
// A structure to represent some raw texture data
|
||||
// (this data hasn't hit the GPU yet, used for custom textures)
|
||||
struct InternalTextureDataResource
|
||||
{
|
||||
AssetData* asset_data = nullptr;
|
||||
VideoCommon::TextureAsset* asset = nullptr;
|
||||
std::shared_ptr<CustomTextureData> texture_data;
|
||||
};
|
||||
|
||||
void LoadTextureDataAsset(const CustomAssetLibrary::AssetID& asset_id,
|
||||
std::shared_ptr<VideoCommon::CustomAssetLibrary> library,
|
||||
InternalTextureDataResource* resource);
|
||||
|
||||
void ProcessDirtyAssets();
|
||||
void ProcessLoadedAssets();
|
||||
void RemoveAssetsUntilBelowMemoryLimit();
|
||||
|
||||
template <typename T>
|
||||
T* CreateAsset(const CustomAssetLibrary::AssetID& asset_id, AssetData::AssetType asset_type,
|
||||
std::shared_ptr<VideoCommon::CustomAssetLibrary> library)
|
||||
{
|
||||
const auto [it, added] =
|
||||
m_asset_id_to_handle.try_emplace(asset_id, m_asset_handle_to_data.size());
|
||||
|
||||
if (added)
|
||||
{
|
||||
AssetData asset_data;
|
||||
asset_data.asset = std::make_unique<T>(library, asset_id, it->second);
|
||||
asset_data.type = asset_type;
|
||||
asset_data.load_request_time = {};
|
||||
asset_data.has_load_error = false;
|
||||
|
||||
m_asset_handle_to_data.insert_or_assign(it->second, std::move(asset_data));
|
||||
}
|
||||
auto& asset_data_from_handle = m_asset_handle_to_data[it->second];
|
||||
asset_data_from_handle.load_status = AssetData::LoadStatus::PendingReload;
|
||||
|
||||
return static_cast<T*>(asset_data_from_handle.asset.get());
|
||||
}
|
||||
|
||||
// Maintains a priority-sorted list of assets.
|
||||
// Used to figure out which assets to load or unload first.
|
||||
// Most recently used assets get marked with highest priority.
|
||||
class AssetPriorityQueue
|
||||
{
|
||||
public:
|
||||
const auto& Elements() const { return m_assets; }
|
||||
|
||||
// Inserts or moves the asset to the top of the queue.
|
||||
void MakeAssetHighestPriority(u64 asset_handle, CustomAsset* asset)
|
||||
{
|
||||
RemoveAsset(asset_handle);
|
||||
m_assets.push_front(asset);
|
||||
|
||||
// See CreateAsset for how a handle gets defined
|
||||
if (asset_handle >= m_iterator_lookup.size())
|
||||
m_iterator_lookup.resize(asset_handle + 1, m_assets.end());
|
||||
|
||||
m_iterator_lookup[asset_handle] = m_assets.begin();
|
||||
}
|
||||
|
||||
// Inserts an asset at lowest priority or
|
||||
// does nothing if asset is already in the queue.
|
||||
void InsertAsset(u64 asset_handle, CustomAsset* asset)
|
||||
{
|
||||
if (asset_handle >= m_iterator_lookup.size())
|
||||
m_iterator_lookup.resize(asset_handle + 1, m_assets.end());
|
||||
|
||||
if (m_iterator_lookup[asset_handle] == m_assets.end())
|
||||
{
|
||||
m_assets.push_back(asset);
|
||||
m_iterator_lookup[asset_handle] = std::prev(m_assets.end());
|
||||
}
|
||||
}
|
||||
|
||||
CustomAsset* RemoveLowestPriorityAsset()
|
||||
{
|
||||
if (m_assets.empty()) [[unlikely]]
|
||||
return nullptr;
|
||||
auto* const ret = m_assets.back();
|
||||
if (ret != nullptr)
|
||||
{
|
||||
m_iterator_lookup[ret->GetHandle()] = m_assets.end();
|
||||
}
|
||||
m_assets.pop_back();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void RemoveAsset(u64 asset_handle)
|
||||
{
|
||||
if (asset_handle >= m_iterator_lookup.size())
|
||||
return;
|
||||
|
||||
const auto iter = m_iterator_lookup[asset_handle];
|
||||
if (iter != m_assets.end())
|
||||
{
|
||||
m_assets.erase(iter);
|
||||
m_iterator_lookup[asset_handle] = m_assets.end();
|
||||
}
|
||||
}
|
||||
|
||||
bool IsEmpty() const { return m_assets.empty(); }
|
||||
|
||||
std::size_t Size() const { return m_assets.size(); }
|
||||
|
||||
private:
|
||||
std::list<CustomAsset*> m_assets;
|
||||
|
||||
// Handle-to-iterator lookup for fast access.
|
||||
// Grows as needed on insert.
|
||||
std::vector<decltype(m_assets)::iterator> m_iterator_lookup;
|
||||
};
|
||||
|
||||
// Assets that are currently active in memory, in order of most recently used by the game.
|
||||
AssetPriorityQueue m_active_assets;
|
||||
|
||||
// Assets that need to be loaded.
|
||||
// e.g. Because the game tried to use them or because they changed on disk.
|
||||
// Ordered by most recently used.
|
||||
AssetPriorityQueue m_pending_assets;
|
||||
|
||||
std::map<std::size_t, AssetData> m_asset_handle_to_data;
|
||||
std::map<CustomAssetLibrary::AssetID, std::size_t> m_asset_id_to_handle;
|
||||
|
||||
// Memory used by currently "loaded" assets.
|
||||
u64 m_ram_used = 0;
|
||||
|
||||
// A calculated amount of memory to avoid exceeding.
|
||||
u64 m_max_ram_available = 0;
|
||||
|
||||
std::map<CustomAssetLibrary::AssetID, InternalTextureDataResource> m_texture_data_asset_cache;
|
||||
|
||||
std::mutex m_dirty_mutex;
|
||||
std::set<CustomAssetLibrary::AssetID> m_dirty_assets;
|
||||
|
||||
CustomAssetLoader m_asset_loader;
|
||||
|
||||
Common::EventHook m_xfb_event;
|
||||
};
|
||||
|
||||
} // namespace VideoCommon
|
@ -13,30 +13,19 @@
|
||||
#include "Common/JsonUtil.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Core/System.h"
|
||||
#include "VideoCommon/Assets/CustomResourceManager.h"
|
||||
#include "VideoCommon/Assets/MaterialAsset.h"
|
||||
#include "VideoCommon/Assets/MeshAsset.h"
|
||||
#include "VideoCommon/Assets/ShaderAsset.h"
|
||||
#include "VideoCommon/Assets/TextureAsset.h"
|
||||
#include "VideoCommon/Assets/TextureAssetUtils.h"
|
||||
#include "VideoCommon/RenderState.h"
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::chrono::system_clock::time_point FileTimeToSysTime(std::filesystem::file_time_type file_time)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return std::chrono::clock_cast<std::chrono::system_clock>(file_time);
|
||||
#else
|
||||
// Note: all compilers should switch to chrono::clock_cast
|
||||
// once it is available for use
|
||||
const auto system_time_now = std::chrono::system_clock::now();
|
||||
const auto file_time_now = decltype(file_time)::clock::now();
|
||||
return std::chrono::time_point_cast<std::chrono::system_clock::duration>(
|
||||
file_time - file_time_now + system_time_now);
|
||||
#endif
|
||||
}
|
||||
|
||||
std::size_t GetAssetSize(const CustomTextureData& data)
|
||||
{
|
||||
std::size_t total = 0;
|
||||
@ -50,30 +39,6 @@ std::size_t GetAssetSize(const CustomTextureData& data)
|
||||
return total;
|
||||
}
|
||||
} // namespace
|
||||
CustomAssetLibrary::TimeType
|
||||
DirectFilesystemAssetLibrary::GetLastAssetWriteTime(const AssetID& asset_id) const
|
||||
{
|
||||
std::lock_guard lk(m_lock);
|
||||
if (auto iter = m_assetid_to_asset_map_path.find(asset_id);
|
||||
iter != m_assetid_to_asset_map_path.end())
|
||||
{
|
||||
const auto& asset_map_path = iter->second;
|
||||
CustomAssetLibrary::TimeType max_entry;
|
||||
for (const auto& [key, value] : asset_map_path)
|
||||
{
|
||||
std::error_code ec;
|
||||
const auto tp = std::filesystem::last_write_time(value, ec);
|
||||
if (ec)
|
||||
continue;
|
||||
auto tp_sys = FileTimeToSysTime(tp);
|
||||
if (tp_sys > max_entry)
|
||||
max_entry = tp_sys;
|
||||
}
|
||||
return max_entry;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadPixelShader(const AssetID& asset_id,
|
||||
PixelShaderData* data)
|
||||
@ -158,7 +123,7 @@ CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadPixelShader(const
|
||||
if (!PixelShaderData::FromJson(asset_id, root_obj, data))
|
||||
return {};
|
||||
|
||||
return LoadInfo{approx_mem_size, GetLastAssetWriteTime(asset_id)};
|
||||
return LoadInfo{approx_mem_size};
|
||||
}
|
||||
|
||||
CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadMaterial(const AssetID& asset_id,
|
||||
@ -216,7 +181,7 @@ CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadMaterial(const As
|
||||
return {};
|
||||
}
|
||||
|
||||
return LoadInfo{metadata_size, GetLastAssetWriteTime(asset_id)};
|
||||
return LoadInfo{metadata_size};
|
||||
}
|
||||
|
||||
CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadMesh(const AssetID& asset_id,
|
||||
@ -311,11 +276,41 @@ CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadMesh(const AssetI
|
||||
if (!MeshData::FromJson(asset_id, root_obj, data))
|
||||
return {};
|
||||
|
||||
return LoadInfo{approx_mem_size, GetLastAssetWriteTime(asset_id)};
|
||||
return LoadInfo{approx_mem_size};
|
||||
}
|
||||
|
||||
CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadTexture(const AssetID& asset_id,
|
||||
TextureData* data)
|
||||
CustomTextureData* data)
|
||||
{
|
||||
const auto asset_map = GetAssetMapForID(asset_id);
|
||||
if (asset_map.empty())
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - raw texture expected to have one or two files mapped!",
|
||||
asset_id);
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto texture_path = asset_map.find("texture");
|
||||
|
||||
if (texture_path == asset_map.end())
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Asset '{}' expected to have a texture entry mapped!", asset_id);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!LoadTextureDataFromFile(asset_id, texture_path->second,
|
||||
TextureAndSamplerData::Type::Type_Texture2D, data))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
if (!PurgeInvalidMipsFromTextureData(asset_id, data))
|
||||
return {};
|
||||
|
||||
return LoadInfo{GetAssetSize(*data)};
|
||||
}
|
||||
|
||||
CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadTexture(const AssetID& asset_id,
|
||||
TextureAndSamplerData* data)
|
||||
{
|
||||
const auto asset_map = GetAssetMapForID(asset_id);
|
||||
|
||||
@ -368,7 +363,7 @@ CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadTexture(const Ass
|
||||
}
|
||||
|
||||
const auto& root_obj = root.get<picojson::object>();
|
||||
if (!TextureData::FromJson(asset_id, root_obj, data))
|
||||
if (!TextureAndSamplerData::FromJson(asset_id, root_obj, data))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
@ -376,128 +371,62 @@ CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadTexture(const Ass
|
||||
else
|
||||
{
|
||||
data->m_sampler = RenderState::GetLinearSamplerState();
|
||||
data->m_type = TextureData::Type::Type_Texture2D;
|
||||
data->m_type = TextureAndSamplerData::Type::Type_Texture2D;
|
||||
}
|
||||
|
||||
auto ext = PathToString(texture_path->second.extension());
|
||||
Common::ToLower(&ext);
|
||||
if (ext == ".dds")
|
||||
{
|
||||
if (!LoadDDSTexture(&data->m_texture, PathToString(texture_path->second)))
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - could not load dds texture!", asset_id);
|
||||
return {};
|
||||
}
|
||||
if (!LoadTextureDataFromFile(asset_id, texture_path->second, data->m_type, &data->m_texture))
|
||||
return {};
|
||||
if (!PurgeInvalidMipsFromTextureData(asset_id, &data->m_texture))
|
||||
return {};
|
||||
|
||||
if (data->m_texture.m_slices.empty()) [[unlikely]]
|
||||
data->m_texture.m_slices.push_back({});
|
||||
|
||||
if (!LoadMips(texture_path->second, &data->m_texture.m_slices[0]))
|
||||
return {};
|
||||
|
||||
return LoadInfo{GetAssetSize(data->m_texture) + metadata_size, GetLastAssetWriteTime(asset_id)};
|
||||
}
|
||||
else if (ext == ".png")
|
||||
{
|
||||
// PNG could support more complicated texture types in the future
|
||||
// but for now just error
|
||||
if (data->m_type != TextureData::Type::Type_Texture2D)
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - PNG is not supported for texture type '{}'!",
|
||||
asset_id, data->m_type);
|
||||
return {};
|
||||
}
|
||||
|
||||
// If we have no slices, create one
|
||||
if (data->m_texture.m_slices.empty())
|
||||
data->m_texture.m_slices.push_back({});
|
||||
|
||||
auto& slice = data->m_texture.m_slices[0];
|
||||
// If we have no levels, create one to pass into LoadPNGTexture
|
||||
if (slice.m_levels.empty())
|
||||
slice.m_levels.push_back({});
|
||||
|
||||
if (!LoadPNGTexture(&slice.m_levels[0], PathToString(texture_path->second)))
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - could not load png texture!", asset_id);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!LoadMips(texture_path->second, &slice))
|
||||
return {};
|
||||
|
||||
return LoadInfo{GetAssetSize(data->m_texture) + metadata_size, GetLastAssetWriteTime(asset_id)};
|
||||
}
|
||||
|
||||
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - extension '{}' unknown!", asset_id, ext);
|
||||
return {};
|
||||
return LoadInfo{GetAssetSize(data->m_texture) + metadata_size};
|
||||
}
|
||||
|
||||
void DirectFilesystemAssetLibrary::SetAssetIDMapData(const AssetID& asset_id,
|
||||
AssetMap asset_path_map)
|
||||
VideoCommon::Assets::AssetMap asset_path_map)
|
||||
{
|
||||
std::lock_guard lk(m_lock);
|
||||
m_assetid_to_asset_map_path[asset_id] = std::move(asset_path_map);
|
||||
}
|
||||
|
||||
bool DirectFilesystemAssetLibrary::LoadMips(const std::filesystem::path& asset_path,
|
||||
CustomTextureData::ArraySlice* data)
|
||||
{
|
||||
if (!data) [[unlikely]]
|
||||
return false;
|
||||
|
||||
std::string path;
|
||||
std::string filename;
|
||||
std::string extension;
|
||||
SplitPath(PathToString(asset_path), &path, &filename, &extension);
|
||||
|
||||
std::string extension_lower = extension;
|
||||
Common::ToLower(&extension_lower);
|
||||
|
||||
// Load additional mip levels
|
||||
for (u32 mip_level = static_cast<u32>(data->m_levels.size());; mip_level++)
|
||||
VideoCommon::Assets::AssetMap previous_asset_map;
|
||||
{
|
||||
const auto mip_level_filename = filename + fmt::format("_mip{}", mip_level);
|
||||
|
||||
const auto full_path = path + mip_level_filename + extension;
|
||||
if (!File::Exists(full_path))
|
||||
return true;
|
||||
|
||||
VideoCommon::CustomTextureData::ArraySlice::Level level;
|
||||
if (extension_lower == ".dds")
|
||||
{
|
||||
if (!LoadDDSTexture(&level, full_path, mip_level))
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Custom mipmap '{}' failed to load", mip_level_filename);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (extension_lower == ".png")
|
||||
{
|
||||
if (!LoadPNGTexture(&level, full_path))
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Custom mipmap '{}' failed to load", mip_level_filename);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Custom mipmap '{}' has unsupported extension", mip_level_filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
data->m_levels.push_back(std::move(level));
|
||||
std::lock_guard lk(m_asset_map_lock);
|
||||
previous_asset_map = m_asset_id_to_asset_map_path[asset_id];
|
||||
}
|
||||
|
||||
return true;
|
||||
{
|
||||
std::lock_guard lk(m_path_map_lock);
|
||||
for (const auto& [name, path] : previous_asset_map)
|
||||
{
|
||||
m_path_to_asset_id.erase(PathToString(path));
|
||||
}
|
||||
|
||||
for (const auto& [name, path] : asset_path_map)
|
||||
{
|
||||
m_path_to_asset_id[PathToString(path)] = asset_id;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard lk(m_asset_map_lock);
|
||||
m_asset_id_to_asset_map_path[asset_id] = std::move(asset_path_map);
|
||||
}
|
||||
}
|
||||
|
||||
DirectFilesystemAssetLibrary::AssetMap
|
||||
void DirectFilesystemAssetLibrary::PathModified(std::string_view path)
|
||||
{
|
||||
std::lock_guard lk(m_path_map_lock);
|
||||
if (const auto iter = m_path_to_asset_id.find(path); iter != m_path_to_asset_id.end())
|
||||
{
|
||||
auto& system = Core::System::GetInstance();
|
||||
auto& resource_manager = system.GetCustomResourceManager();
|
||||
resource_manager.MarkAssetDirty(iter->second);
|
||||
}
|
||||
}
|
||||
|
||||
VideoCommon::Assets::AssetMap
|
||||
DirectFilesystemAssetLibrary::GetAssetMapForID(const AssetID& asset_id) const
|
||||
{
|
||||
std::lock_guard lk(m_lock);
|
||||
if (auto iter = m_assetid_to_asset_map_path.find(asset_id);
|
||||
iter != m_assetid_to_asset_map_path.end())
|
||||
std::lock_guard lk(m_asset_map_lock);
|
||||
if (auto iter = m_asset_id_to_asset_map_path.find(asset_id);
|
||||
iter != m_asset_id_to_asset_map_path.end())
|
||||
{
|
||||
return iter->second;
|
||||
}
|
||||
|
@ -8,39 +8,39 @@
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
#include "VideoCommon/Assets/CustomAssetLibrary.h"
|
||||
#include "VideoCommon/Assets/CustomTextureData.h"
|
||||
#include "VideoCommon/Assets/TextureAsset.h"
|
||||
#include "VideoCommon/Assets/Types.h"
|
||||
#include "VideoCommon/Assets/WatchableFilesystemAssetLibrary.h"
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
// This class implements 'CustomAssetLibrary' and loads any assets
|
||||
// directly from the filesystem
|
||||
class DirectFilesystemAssetLibrary final : public CustomAssetLibrary
|
||||
class DirectFilesystemAssetLibrary final : public WatchableFilesystemAssetLibrary
|
||||
{
|
||||
public:
|
||||
using AssetMap = std::map<std::string, std::filesystem::path>;
|
||||
|
||||
LoadInfo LoadTexture(const AssetID& asset_id, TextureData* data) override;
|
||||
LoadInfo LoadTexture(const AssetID& asset_id, TextureAndSamplerData* data) override;
|
||||
LoadInfo LoadTexture(const AssetID& asset_id, CustomTextureData* data) override;
|
||||
LoadInfo LoadPixelShader(const AssetID& asset_id, PixelShaderData* data) override;
|
||||
LoadInfo LoadMaterial(const AssetID& asset_id, MaterialData* data) override;
|
||||
LoadInfo LoadMesh(const AssetID& asset_id, MeshData* data) override;
|
||||
|
||||
// Gets the latest time from amongst all the files in the asset map
|
||||
TimeType GetLastAssetWriteTime(const AssetID& asset_id) const override;
|
||||
|
||||
// Assigns the asset id to a map of files, how this map is read is dependent on the data
|
||||
// For instance, a raw texture would expect the map to have a single entry and load that
|
||||
// file as the asset. But a model file data might have its data spread across multiple files
|
||||
void SetAssetIDMapData(const AssetID& asset_id, AssetMap asset_path_map);
|
||||
void SetAssetIDMapData(const AssetID& asset_id, Assets::AssetMap asset_path_map);
|
||||
|
||||
private:
|
||||
// Loads additional mip levels into the texture structure until _mip<N> texture is not found
|
||||
bool LoadMips(const std::filesystem::path& asset_path, CustomTextureData::ArraySlice* data);
|
||||
void PathModified(std::string_view path) override;
|
||||
|
||||
// Gets the asset map given an asset id
|
||||
AssetMap GetAssetMapForID(const AssetID& asset_id) const;
|
||||
Assets::AssetMap GetAssetMapForID(const AssetID& asset_id) const;
|
||||
|
||||
mutable std::mutex m_lock;
|
||||
std::map<AssetID, std::map<std::string, std::filesystem::path>> m_assetid_to_asset_map_path;
|
||||
mutable std::mutex m_asset_map_lock;
|
||||
std::map<AssetID, Assets::AssetMap> m_asset_id_to_asset_map_path;
|
||||
|
||||
mutable std::mutex m_path_map_lock;
|
||||
std::map<std::string, AssetID, std::less<>> m_path_to_asset_id;
|
||||
};
|
||||
} // namespace VideoCommon
|
||||
|
@ -384,7 +384,7 @@ CustomAssetLibrary::LoadInfo MaterialAsset::LoadImpl(const CustomAssetLibrary::A
|
||||
{
|
||||
auto potential_data = std::make_shared<MaterialData>();
|
||||
const auto loaded_info = m_owning_library->LoadMaterial(asset_id, potential_data.get());
|
||||
if (loaded_info.m_bytes_loaded == 0)
|
||||
if (loaded_info.bytes_loaded == 0)
|
||||
return {};
|
||||
{
|
||||
std::lock_guard lk(m_data_lock);
|
||||
|
@ -651,7 +651,7 @@ CustomAssetLibrary::LoadInfo MeshAsset::LoadImpl(const CustomAssetLibrary::Asset
|
||||
{
|
||||
auto potential_data = std::make_shared<MeshData>();
|
||||
const auto loaded_info = m_owning_library->LoadMesh(asset_id, potential_data.get());
|
||||
if (loaded_info.m_bytes_loaded == 0)
|
||||
if (loaded_info.bytes_loaded == 0)
|
||||
return {};
|
||||
{
|
||||
std::lock_guard lk(m_data_lock);
|
||||
|
@ -439,7 +439,7 @@ CustomAssetLibrary::LoadInfo PixelShaderAsset::LoadImpl(const CustomAssetLibrary
|
||||
{
|
||||
auto potential_data = std::make_shared<PixelShaderData>();
|
||||
const auto loaded_info = m_owning_library->LoadPixelShader(asset_id, potential_data.get());
|
||||
if (loaded_info.m_bytes_loaded == 0)
|
||||
if (loaded_info.bytes_loaded == 0)
|
||||
return {};
|
||||
{
|
||||
std::lock_guard lk(m_data_lock);
|
||||
|
@ -153,8 +153,8 @@ bool ParseSampler(const VideoCommon::CustomAssetLibrary::AssetID& asset_id,
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
bool TextureData::FromJson(const CustomAssetLibrary::AssetID& asset_id,
|
||||
const picojson::object& json, TextureData* data)
|
||||
bool TextureAndSamplerData::FromJson(const CustomAssetLibrary::AssetID& asset_id,
|
||||
const picojson::object& json, TextureAndSamplerData* data)
|
||||
{
|
||||
const auto type_iter = json.find("type");
|
||||
if (type_iter == json.end())
|
||||
@ -176,7 +176,7 @@ bool TextureData::FromJson(const CustomAssetLibrary::AssetID& asset_id,
|
||||
|
||||
if (type == "texture2d")
|
||||
{
|
||||
data->m_type = TextureData::Type::Type_Texture2D;
|
||||
data->m_type = TextureAndSamplerData::Type::Type_Texture2D;
|
||||
|
||||
if (!ParseSampler(asset_id, json, &data->m_sampler))
|
||||
{
|
||||
@ -185,7 +185,7 @@ bool TextureData::FromJson(const CustomAssetLibrary::AssetID& asset_id,
|
||||
}
|
||||
else if (type == "texturecube")
|
||||
{
|
||||
data->m_type = TextureData::Type::Type_TextureCube;
|
||||
data->m_type = TextureAndSamplerData::Type::Type_TextureCube;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -199,7 +199,7 @@ bool TextureData::FromJson(const CustomAssetLibrary::AssetID& asset_id,
|
||||
return true;
|
||||
}
|
||||
|
||||
void TextureData::ToJson(picojson::object* obj, const TextureData& data)
|
||||
void TextureAndSamplerData::ToJson(picojson::object* obj, const TextureAndSamplerData& data)
|
||||
{
|
||||
if (!obj) [[unlikely]]
|
||||
return;
|
||||
@ -207,13 +207,13 @@ void TextureData::ToJson(picojson::object* obj, const TextureData& data)
|
||||
auto& json_obj = *obj;
|
||||
switch (data.m_type)
|
||||
{
|
||||
case TextureData::Type::Type_Texture2D:
|
||||
case TextureAndSamplerData::Type::Type_Texture2D:
|
||||
json_obj.emplace("type", "texture2d");
|
||||
break;
|
||||
case TextureData::Type::Type_TextureCube:
|
||||
case TextureAndSamplerData::Type::Type_TextureCube:
|
||||
json_obj.emplace("type", "texturecube");
|
||||
break;
|
||||
case TextureData::Type::Type_Undefined:
|
||||
case TextureAndSamplerData::Type::Type_Undefined:
|
||||
break;
|
||||
};
|
||||
|
||||
@ -254,11 +254,11 @@ void TextureData::ToJson(picojson::object* obj, const TextureData& data)
|
||||
json_obj.emplace("filter_mode", filter_mode);
|
||||
}
|
||||
|
||||
CustomAssetLibrary::LoadInfo GameTextureAsset::LoadImpl(const CustomAssetLibrary::AssetID& asset_id)
|
||||
CustomAssetLibrary::LoadInfo TextureAsset::LoadImpl(const CustomAssetLibrary::AssetID& asset_id)
|
||||
{
|
||||
auto potential_data = std::make_shared<TextureData>();
|
||||
const auto loaded_info = m_owning_library->LoadGameTexture(asset_id, potential_data.get());
|
||||
if (loaded_info.m_bytes_loaded == 0)
|
||||
auto potential_data = std::make_shared<CustomTextureData>();
|
||||
const auto loaded_info = m_owning_library->LoadTexture(asset_id, potential_data.get());
|
||||
if (loaded_info.bytes_loaded == 0)
|
||||
return {};
|
||||
{
|
||||
std::lock_guard lk(m_data_lock);
|
||||
@ -267,75 +267,4 @@ CustomAssetLibrary::LoadInfo GameTextureAsset::LoadImpl(const CustomAssetLibrary
|
||||
}
|
||||
return loaded_info;
|
||||
}
|
||||
|
||||
bool GameTextureAsset::Validate(u32 native_width, u32 native_height) const
|
||||
{
|
||||
std::lock_guard lk(m_data_lock);
|
||||
|
||||
if (!m_loaded)
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO,
|
||||
"Game texture can't be validated for asset '{}' because it is not loaded yet.",
|
||||
GetAssetId());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_data->m_texture.m_slices.empty())
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO,
|
||||
"Game texture can't be validated for asset '{}' because no data was available.",
|
||||
GetAssetId());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_data->m_texture.m_slices.size() > 1)
|
||||
{
|
||||
ERROR_LOG_FMT(
|
||||
VIDEO,
|
||||
"Game texture can't be validated for asset '{}' because it has more slices than expected.",
|
||||
GetAssetId());
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& slice = m_data->m_texture.m_slices[0];
|
||||
if (slice.m_levels.empty())
|
||||
{
|
||||
ERROR_LOG_FMT(
|
||||
VIDEO,
|
||||
"Game texture can't be validated for asset '{}' because first slice has no data available.",
|
||||
GetAssetId());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify that the aspect ratio of the texture hasn't changed, as this could have
|
||||
// side-effects.
|
||||
const VideoCommon::CustomTextureData::ArraySlice::Level& first_mip = slice.m_levels[0];
|
||||
if (first_mip.width * native_height != first_mip.height * native_width)
|
||||
{
|
||||
// Note: this feels like this should return an error but
|
||||
// for legacy reasons this is only a notice that something *could*
|
||||
// go wrong
|
||||
WARN_LOG_FMT(
|
||||
VIDEO,
|
||||
"Invalid custom texture size {}x{} for game texture asset '{}'. The aspect differs "
|
||||
"from the native size {}x{}.",
|
||||
first_mip.width, first_mip.height, GetAssetId(), native_width, native_height);
|
||||
}
|
||||
|
||||
// Same deal if the custom texture isn't a multiple of the native size.
|
||||
if (native_width != 0 && native_height != 0 &&
|
||||
(first_mip.width % native_width || first_mip.height % native_height))
|
||||
{
|
||||
// Note: this feels like this should return an error but
|
||||
// for legacy reasons this is only a notice that something *could*
|
||||
// go wrong
|
||||
WARN_LOG_FMT(
|
||||
VIDEO,
|
||||
"Invalid custom texture size {}x{} for game texture asset '{}'. Please use an integer "
|
||||
"upscaling factor based on the native size {}x{}.",
|
||||
first_mip.width, first_mip.height, GetAssetId(), native_width, native_height);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace VideoCommon
|
||||
|
@ -13,11 +13,11 @@
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
struct TextureData
|
||||
struct TextureAndSamplerData
|
||||
{
|
||||
static bool FromJson(const CustomAssetLibrary::AssetID& asset_id, const picojson::object& json,
|
||||
TextureData* data);
|
||||
static void ToJson(picojson::object* obj, const TextureData& data);
|
||||
TextureAndSamplerData* data);
|
||||
static void ToJson(picojson::object* obj, const TextureAndSamplerData& data);
|
||||
enum class Type
|
||||
{
|
||||
Type_Undefined,
|
||||
@ -30,23 +30,19 @@ struct TextureData
|
||||
SamplerState m_sampler;
|
||||
};
|
||||
|
||||
class GameTextureAsset final : public CustomLoadableAsset<TextureData>
|
||||
class TextureAsset final : public CustomLoadableAsset<CustomTextureData>
|
||||
{
|
||||
public:
|
||||
using CustomLoadableAsset::CustomLoadableAsset;
|
||||
|
||||
// Validates that the game texture matches the native dimensions provided
|
||||
// Callees are expected to call this once the data is loaded
|
||||
bool Validate(u32 native_width, u32 native_height) const;
|
||||
|
||||
private:
|
||||
CustomAssetLibrary::LoadInfo LoadImpl(const CustomAssetLibrary::AssetID& asset_id) override;
|
||||
};
|
||||
} // namespace VideoCommon
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<VideoCommon::TextureData::Type>
|
||||
: EnumFormatter<VideoCommon::TextureData::Type::Type_Max>
|
||||
struct fmt::formatter<VideoCommon::TextureAndSamplerData::Type>
|
||||
: EnumFormatter<VideoCommon::TextureAndSamplerData::Type::Type_Max>
|
||||
{
|
||||
constexpr formatter() : EnumFormatter({"Undefined", "Texture2D", "TextureCube"}) {}
|
||||
};
|
||||
|
244
Source/Core/VideoCommon/Assets/TextureAssetUtils.cpp
Normal file
244
Source/Core/VideoCommon/Assets/TextureAssetUtils.cpp
Normal file
@ -0,0 +1,244 @@
|
||||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "VideoCommon/Assets/TextureAssetUtils.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/StringUtil.h"
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
namespace
|
||||
{
|
||||
// Loads additional mip levels into the texture structure until _mip<N> texture is not found
|
||||
bool LoadMips(const std::filesystem::path& asset_path, CustomTextureData::ArraySlice* data)
|
||||
{
|
||||
if (!data) [[unlikely]]
|
||||
return false;
|
||||
|
||||
std::string path;
|
||||
std::string filename;
|
||||
std::string extension;
|
||||
SplitPath(PathToString(asset_path), &path, &filename, &extension);
|
||||
|
||||
std::string extension_lower = extension;
|
||||
Common::ToLower(&extension_lower);
|
||||
|
||||
// Load additional mip levels
|
||||
for (u32 mip_level = static_cast<u32>(data->m_levels.size());; mip_level++)
|
||||
{
|
||||
const auto mip_level_filename = filename + fmt::format("_mip{}", mip_level);
|
||||
|
||||
const auto full_path = path + mip_level_filename + extension;
|
||||
if (!File::Exists(full_path))
|
||||
return true;
|
||||
|
||||
VideoCommon::CustomTextureData::ArraySlice::Level level;
|
||||
if (extension_lower == ".dds")
|
||||
{
|
||||
if (!LoadDDSTexture(&level, full_path, mip_level))
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Custom mipmap '{}' failed to load", mip_level_filename);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (extension_lower == ".png")
|
||||
{
|
||||
if (!LoadPNGTexture(&level, full_path))
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Custom mipmap '{}' failed to load", mip_level_filename);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Custom mipmap '{}' has unsupported extension", mip_level_filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
data->m_levels.push_back(std::move(level));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
bool LoadTextureDataFromFile(const CustomAssetLibrary::AssetID& asset_id,
|
||||
const std::filesystem::path& asset_path,
|
||||
TextureAndSamplerData::Type type, CustomTextureData* data)
|
||||
{
|
||||
auto ext = PathToString(asset_path.extension());
|
||||
Common::ToLower(&ext);
|
||||
if (ext == ".dds")
|
||||
{
|
||||
if (!LoadDDSTexture(data, PathToString(asset_path)))
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - could not load dds texture!", asset_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data->m_slices.empty()) [[unlikely]]
|
||||
data->m_slices.emplace_back();
|
||||
|
||||
if (!LoadMips(asset_path, data->m_slices.data()))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ext == ".png")
|
||||
{
|
||||
// PNG could support more complicated texture types in the future
|
||||
// but for now just error
|
||||
if (type != TextureAndSamplerData::Type::Type_Texture2D)
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - PNG is not supported for texture type '{}'!",
|
||||
asset_id, type);
|
||||
return {};
|
||||
}
|
||||
|
||||
// If we have no slices, create one
|
||||
if (data->m_slices.empty())
|
||||
data->m_slices.emplace_back();
|
||||
|
||||
auto& slice = data->m_slices[0];
|
||||
// If we have no levels, create one to pass into LoadPNGTexture
|
||||
if (slice.m_levels.empty())
|
||||
slice.m_levels.emplace_back();
|
||||
|
||||
if (!LoadPNGTexture(slice.m_levels.data(), PathToString(asset_path)))
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - could not load png texture!", asset_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!LoadMips(asset_path, &slice))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - extension '{}' unknown!", asset_id, ext);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ValidateTextureData(const CustomAssetLibrary::AssetID& asset_id, const CustomTextureData& data,
|
||||
u32 native_width, u32 native_height)
|
||||
{
|
||||
if (data.m_slices.empty())
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO,
|
||||
"Texture data can't be validated for asset '{}' because no data was available.",
|
||||
asset_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data.m_slices.size() > 1)
|
||||
{
|
||||
ERROR_LOG_FMT(
|
||||
VIDEO,
|
||||
"Texture data can't be validated for asset '{}' because it has more slices than expected.",
|
||||
asset_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& slice = data.m_slices[0];
|
||||
if (slice.m_levels.empty())
|
||||
{
|
||||
ERROR_LOG_FMT(
|
||||
VIDEO,
|
||||
"Texture data can't be validated for asset '{}' because first slice has no data available.",
|
||||
asset_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify that the aspect ratio of the texture hasn't changed, as this could have
|
||||
// side-effects.
|
||||
const CustomTextureData::ArraySlice::Level& first_mip = slice.m_levels[0];
|
||||
if (first_mip.width * native_height != first_mip.height * native_width)
|
||||
{
|
||||
// Note: this feels like this should return an error but
|
||||
// for legacy reasons this is only a notice that something *could*
|
||||
// go wrong
|
||||
WARN_LOG_FMT(VIDEO,
|
||||
"Invalid texture data size {}x{} for asset '{}'. The aspect differs "
|
||||
"from the native size {}x{}.",
|
||||
first_mip.width, first_mip.height, asset_id, native_width, native_height);
|
||||
}
|
||||
|
||||
// Same deal if the custom texture isn't a multiple of the native size.
|
||||
if (native_width != 0 && native_height != 0 &&
|
||||
(first_mip.width % native_width || first_mip.height % native_height))
|
||||
{
|
||||
// Note: this feels like this should return an error but
|
||||
// for legacy reasons this is only a notice that something *could*
|
||||
// go wrong
|
||||
WARN_LOG_FMT(VIDEO,
|
||||
"Invalid texture data size {}x{} for asset '{}'. Please use an integer "
|
||||
"upscaling factor based on the native size {}x{}.",
|
||||
first_mip.width, first_mip.height, asset_id, native_width, native_height);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PurgeInvalidMipsFromTextureData(const CustomAssetLibrary::AssetID& asset_id,
|
||||
CustomTextureData* data)
|
||||
{
|
||||
for (std::size_t slice_index = 0; slice_index < data->m_slices.size(); slice_index++)
|
||||
{
|
||||
auto& slice = data->m_slices[slice_index];
|
||||
const auto& first_mip = slice.m_levels[0];
|
||||
|
||||
// Verify that each mip level is the correct size (divide by 2 each time).
|
||||
u32 current_mip_width = first_mip.width;
|
||||
u32 current_mip_height = first_mip.height;
|
||||
for (u32 mip_level = 1; mip_level < static_cast<u32>(slice.m_levels.size()); mip_level++)
|
||||
{
|
||||
if (current_mip_width != 1 || current_mip_height != 1)
|
||||
{
|
||||
current_mip_width = std::max(current_mip_width / 2, 1u);
|
||||
current_mip_height = std::max(current_mip_height / 2, 1u);
|
||||
|
||||
const VideoCommon::CustomTextureData::ArraySlice::Level& level = slice.m_levels[mip_level];
|
||||
if (current_mip_width == level.width && current_mip_height == level.height)
|
||||
continue;
|
||||
|
||||
ERROR_LOG_FMT(VIDEO,
|
||||
"Invalid custom game texture size {}x{} for texture asset {}. Slice {} with "
|
||||
"mipmap level {} "
|
||||
"must be {}x{}.",
|
||||
level.width, level.height, asset_id, slice_index, mip_level,
|
||||
current_mip_width, current_mip_height);
|
||||
}
|
||||
else
|
||||
{
|
||||
// It is invalid to have more than a single 1x1 mipmap.
|
||||
ERROR_LOG_FMT(VIDEO,
|
||||
"Custom game texture {} has too many 1x1 mipmaps for slice {}. Skipping "
|
||||
"extra levels.",
|
||||
asset_id, slice_index);
|
||||
}
|
||||
|
||||
// Drop this mip level and any others after it.
|
||||
while (slice.m_levels.size() > mip_level)
|
||||
slice.m_levels.pop_back();
|
||||
}
|
||||
|
||||
// All levels have to have the same format.
|
||||
if (std::ranges::any_of(slice.m_levels,
|
||||
[&first_mip](const auto& l) { return l.format != first_mip.format; }))
|
||||
{
|
||||
ERROR_LOG_FMT(
|
||||
VIDEO, "Custom game texture {} has inconsistent formats across mip levels for slice {}.",
|
||||
asset_id, slice_index);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace VideoCommon
|
22
Source/Core/VideoCommon/Assets/TextureAssetUtils.h
Normal file
22
Source/Core/VideoCommon/Assets/TextureAssetUtils.h
Normal file
@ -0,0 +1,22 @@
|
||||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include "VideoCommon/Assets/CustomAssetLibrary.h"
|
||||
#include "VideoCommon/Assets/TextureAsset.h"
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
bool LoadTextureDataFromFile(const CustomAssetLibrary::AssetID& asset_id,
|
||||
const std::filesystem::path& asset_path,
|
||||
TextureAndSamplerData::Type type, CustomTextureData* data);
|
||||
|
||||
bool ValidateTextureData(const CustomAssetLibrary::AssetID& asset_id, const CustomTextureData& data,
|
||||
u32 native_width, u32 native_height);
|
||||
|
||||
bool PurgeInvalidMipsFromTextureData(const CustomAssetLibrary::AssetID& asset_id,
|
||||
CustomTextureData* data);
|
||||
} // namespace VideoCommon
|
13
Source/Core/VideoCommon/Assets/Types.h
Normal file
13
Source/Core/VideoCommon/Assets/Types.h
Normal file
@ -0,0 +1,13 @@
|
||||
// Copyright 2024 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace VideoCommon::Assets
|
||||
{
|
||||
using AssetMap = std::map<std::string, std::filesystem::path>;
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
// Copyright 2024 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Common/FilesystemWatcher.h"
|
||||
#include "VideoCommon/Assets/CustomAssetLibrary.h"
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
class WatchableFilesystemAssetLibrary : public CustomAssetLibrary, public Common::FilesystemWatcher
|
||||
{
|
||||
};
|
||||
} // namespace VideoCommon
|
@ -10,10 +10,11 @@ add_library(videocommon
|
||||
AbstractTexture.h
|
||||
Assets/CustomAsset.cpp
|
||||
Assets/CustomAsset.h
|
||||
Assets/CustomAssetLibrary.cpp
|
||||
Assets/CustomAssetLibrary.h
|
||||
Assets/CustomAssetLoader.cpp
|
||||
Assets/CustomAssetLoader.h
|
||||
Assets/CustomResourceManager.cpp
|
||||
Assets/CustomResourceManager.h
|
||||
Assets/CustomTextureData.cpp
|
||||
Assets/CustomTextureData.h
|
||||
Assets/DirectFilesystemAssetLibrary.cpp
|
||||
@ -26,6 +27,10 @@ add_library(videocommon
|
||||
Assets/ShaderAsset.h
|
||||
Assets/TextureAsset.cpp
|
||||
Assets/TextureAsset.h
|
||||
Assets/TextureAssetUtils.cpp
|
||||
Assets/TextureAssetUtils.h
|
||||
Assets/Types.h
|
||||
Assets/WatchableFilesystemAssetLibrary.h
|
||||
AsyncRequests.cpp
|
||||
AsyncRequests.h
|
||||
AsyncShaderCompiler.cpp
|
||||
|
@ -7,12 +7,13 @@
|
||||
|
||||
#include <picojson.h>
|
||||
|
||||
#include "VideoCommon/Assets/DirectFilesystemAssetLibrary.h"
|
||||
#include "VideoCommon/Assets/CustomAssetLibrary.h"
|
||||
#include "VideoCommon/Assets/Types.h"
|
||||
|
||||
struct GraphicsModAssetConfig
|
||||
{
|
||||
VideoCommon::CustomAssetLibrary::AssetID m_asset_id;
|
||||
VideoCommon::DirectFilesystemAssetLibrary::AssetMap m_map;
|
||||
VideoCommon::Assets::AssetMap m_map;
|
||||
|
||||
void SerializeToConfig(picojson::object& json_obj) const;
|
||||
bool DeserializeFromConfig(const picojson::object& obj);
|
||||
|
@ -16,7 +16,6 @@
|
||||
#include "Core/System.h"
|
||||
|
||||
#include "VideoCommon/AbstractGfx.h"
|
||||
#include "VideoCommon/Assets/CustomAssetLoader.h"
|
||||
#include "VideoCommon/Assets/DirectFilesystemAssetLibrary.h"
|
||||
#include "VideoCommon/ShaderGenCommon.h"
|
||||
#include "VideoCommon/TextureCacheBase.h"
|
||||
@ -98,28 +97,6 @@ CustomPipelineAction::CustomPipelineAction(
|
||||
m_pipeline_passes.resize(m_passes_config.size());
|
||||
}
|
||||
|
||||
void CustomPipelineAction::OnDrawStarted(GraphicsModActionData::DrawStarted* draw_started)
|
||||
void CustomPipelineAction::OnDrawStarted(GraphicsModActionData::DrawStarted*)
|
||||
{
|
||||
if (!draw_started) [[unlikely]]
|
||||
return;
|
||||
|
||||
if (!draw_started->custom_pixel_shader) [[unlikely]]
|
||||
return;
|
||||
|
||||
if (m_pipeline_passes.empty()) [[unlikely]]
|
||||
return;
|
||||
|
||||
auto& loader = Core::System::GetInstance().GetCustomAssetLoader();
|
||||
|
||||
// For now assume a single pass
|
||||
const auto& pass_config = m_passes_config[0];
|
||||
auto& pass = m_pipeline_passes[0];
|
||||
|
||||
pass.UpdatePixelData(loader, m_library, draw_started->texture_units,
|
||||
pass_config.m_pixel_material_asset);
|
||||
CustomPixelShader custom_pixel_shader;
|
||||
custom_pixel_shader.custom_shader = pass.m_last_generated_shader_code.GetBuffer();
|
||||
custom_pixel_shader.material_uniform_block = pass.m_last_generated_material_code.GetBuffer();
|
||||
*draw_started->custom_pixel_shader = custom_pixel_shader;
|
||||
*draw_started->material_uniform_buffer = pass.m_material_data;
|
||||
}
|
||||
|
@ -12,7 +12,6 @@
|
||||
#include "Common/VariantUtil.h"
|
||||
|
||||
#include "VideoCommon/AbstractGfx.h"
|
||||
#include "VideoCommon/Assets/CustomAssetLoader.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
@ -172,238 +171,8 @@ std::vector<std::string> GlobalConflicts(std::string_view source)
|
||||
|
||||
} // namespace
|
||||
|
||||
void CustomPipeline::UpdatePixelData(
|
||||
VideoCommon::CustomAssetLoader& loader,
|
||||
std::shared_ptr<VideoCommon::CustomAssetLibrary> library, std::span<const u32> texture_units,
|
||||
const VideoCommon::CustomAssetLibrary::AssetID& material_to_load)
|
||||
void CustomPipeline::UpdatePixelData(std::shared_ptr<VideoCommon::CustomAssetLibrary>,
|
||||
std::span<const u32>,
|
||||
const VideoCommon::CustomAssetLibrary::AssetID&)
|
||||
{
|
||||
if (!m_pixel_material.m_asset || material_to_load != m_pixel_material.m_asset->GetAssetId())
|
||||
{
|
||||
m_pixel_material.m_asset = loader.LoadMaterial(material_to_load, library);
|
||||
}
|
||||
|
||||
const auto material_data = m_pixel_material.m_asset->GetData();
|
||||
if (!material_data)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::size_t max_material_data_size = 0;
|
||||
if (m_pixel_material.m_asset->GetLastLoadedTime() > m_pixel_material.m_cached_write_time)
|
||||
{
|
||||
m_last_generated_material_code = ShaderCode{};
|
||||
m_pixel_material.m_cached_write_time = m_pixel_material.m_asset->GetLastLoadedTime();
|
||||
std::size_t texture_count = 0;
|
||||
for (const auto& property : material_data->properties)
|
||||
{
|
||||
max_material_data_size += VideoCommon::MaterialProperty::GetMemorySize(property);
|
||||
VideoCommon::MaterialProperty::WriteAsShaderCode(m_last_generated_material_code, property);
|
||||
if (std::holds_alternative<VideoCommon::CustomAssetLibrary::AssetID>(property.m_value))
|
||||
{
|
||||
texture_count++;
|
||||
}
|
||||
}
|
||||
m_material_data.resize(max_material_data_size);
|
||||
m_game_textures.resize(texture_count);
|
||||
}
|
||||
|
||||
if (!m_pixel_shader.m_asset ||
|
||||
m_pixel_shader.m_asset->GetLastLoadedTime() > m_pixel_shader.m_cached_write_time ||
|
||||
material_data->shader_asset != m_pixel_shader.m_asset->GetAssetId())
|
||||
{
|
||||
m_pixel_shader.m_asset = loader.LoadPixelShader(material_data->shader_asset, library);
|
||||
m_pixel_shader.m_cached_write_time = m_pixel_shader.m_asset->GetLastLoadedTime();
|
||||
|
||||
m_last_generated_shader_code = ShaderCode{};
|
||||
}
|
||||
|
||||
const auto shader_data = m_pixel_shader.m_asset->GetData();
|
||||
if (!shader_data)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (shader_data->m_properties.size() != material_data->properties.size())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
u8* material_buffer = m_material_data.data();
|
||||
u32 sampler_index = 8;
|
||||
for (std::size_t index = 0; index < material_data->properties.size(); index++)
|
||||
{
|
||||
auto& property = material_data->properties[index];
|
||||
const auto shader_it = shader_data->m_properties.find(property.m_code_name);
|
||||
if (shader_it == shader_data->m_properties.end())
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO,
|
||||
"Custom pipeline, has material asset '{}' that uses a "
|
||||
"code name of '{}' but that can't be found on shader asset '{}'!",
|
||||
m_pixel_material.m_asset->GetAssetId(), property.m_code_name,
|
||||
m_pixel_shader.m_asset->GetAssetId());
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto* texture_asset_id =
|
||||
std::get_if<VideoCommon::CustomAssetLibrary::AssetID>(&property.m_value))
|
||||
{
|
||||
if (*texture_asset_id != "")
|
||||
{
|
||||
auto asset = loader.LoadGameTexture(*texture_asset_id, library);
|
||||
if (!asset)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto& texture_asset = m_game_textures[index];
|
||||
if (!texture_asset ||
|
||||
texture_asset->m_cached_asset.m_asset->GetLastLoadedTime() >
|
||||
texture_asset->m_cached_asset.m_cached_write_time ||
|
||||
*texture_asset_id != texture_asset->m_cached_asset.m_asset->GetAssetId())
|
||||
{
|
||||
if (!texture_asset)
|
||||
{
|
||||
texture_asset = CachedTextureAsset{};
|
||||
}
|
||||
const auto loaded_time = asset->GetLastLoadedTime();
|
||||
texture_asset->m_cached_asset = VideoCommon::CachedAsset<VideoCommon::GameTextureAsset>{
|
||||
std::move(asset), loaded_time};
|
||||
texture_asset->m_texture.reset();
|
||||
|
||||
if (std::holds_alternative<VideoCommon::ShaderProperty::Sampler2D>(
|
||||
shader_it->second.m_default))
|
||||
{
|
||||
texture_asset->m_sampler_code =
|
||||
fmt::format("SAMPLER_BINDING({}) uniform sampler2D samp_{};\n", sampler_index,
|
||||
property.m_code_name);
|
||||
texture_asset->m_define_code = fmt::format("#define HAS_{} 1\n", property.m_code_name);
|
||||
}
|
||||
else if (std::holds_alternative<VideoCommon::ShaderProperty::Sampler2DArray>(
|
||||
shader_it->second.m_default))
|
||||
{
|
||||
texture_asset->m_sampler_code =
|
||||
fmt::format("SAMPLER_BINDING({}) uniform sampler2DArray samp_{};\n", sampler_index,
|
||||
property.m_code_name);
|
||||
texture_asset->m_define_code = fmt::format("#define HAS_{} 1\n", property.m_code_name);
|
||||
}
|
||||
else if (std::holds_alternative<VideoCommon::ShaderProperty::SamplerCube>(
|
||||
shader_it->second.m_default))
|
||||
{
|
||||
texture_asset->m_sampler_code =
|
||||
fmt::format("SAMPLER_BINDING({}) uniform samplerCube samp_{};\n", sampler_index,
|
||||
property.m_code_name);
|
||||
texture_asset->m_define_code = fmt::format("#define HAS_{} 1\n", property.m_code_name);
|
||||
}
|
||||
}
|
||||
|
||||
const auto texture_data = texture_asset->m_cached_asset.m_asset->GetData();
|
||||
if (!texture_data)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (texture_asset->m_texture)
|
||||
{
|
||||
g_gfx->SetTexture(sampler_index, texture_asset->m_texture.get());
|
||||
g_gfx->SetSamplerState(sampler_index, texture_data->m_sampler);
|
||||
}
|
||||
else
|
||||
{
|
||||
AbstractTextureType texture_type = AbstractTextureType::Texture_2DArray;
|
||||
if (std::holds_alternative<VideoCommon::ShaderProperty::SamplerCube>(
|
||||
shader_it->second.m_default))
|
||||
{
|
||||
texture_type = AbstractTextureType::Texture_CubeMap;
|
||||
}
|
||||
else if (std::holds_alternative<VideoCommon::ShaderProperty::Sampler2D>(
|
||||
shader_it->second.m_default))
|
||||
{
|
||||
texture_type = AbstractTextureType::Texture_2D;
|
||||
}
|
||||
|
||||
if (texture_data->m_texture.m_slices.empty() ||
|
||||
texture_data->m_texture.m_slices[0].m_levels.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto& first_slice = texture_data->m_texture.m_slices[0];
|
||||
const TextureConfig texture_config(
|
||||
first_slice.m_levels[0].width, first_slice.m_levels[0].height,
|
||||
static_cast<u32>(first_slice.m_levels.size()),
|
||||
static_cast<u32>(texture_data->m_texture.m_slices.size()), 1,
|
||||
first_slice.m_levels[0].format, 0, texture_type);
|
||||
texture_asset->m_texture = g_gfx->CreateTexture(
|
||||
texture_config, fmt::format("Custom shader texture '{}'", property.m_code_name));
|
||||
if (texture_asset->m_texture)
|
||||
{
|
||||
for (std::size_t slice_index = 0; slice_index < texture_data->m_texture.m_slices.size();
|
||||
slice_index++)
|
||||
{
|
||||
auto& slice = texture_data->m_texture.m_slices[slice_index];
|
||||
for (u32 level_index = 0; level_index < static_cast<u32>(slice.m_levels.size());
|
||||
++level_index)
|
||||
{
|
||||
auto& level = slice.m_levels[level_index];
|
||||
texture_asset->m_texture->Load(level_index, level.width, level.height,
|
||||
level.row_length, level.data.data(),
|
||||
level.data.size(), static_cast<u32>(slice_index));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sampler_index++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
VideoCommon::MaterialProperty::WriteToMemory(material_buffer, property);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_last_generated_shader_code.GetBuffer().empty())
|
||||
{
|
||||
// Calculate shader details
|
||||
std::string color_shader_data =
|
||||
ReplaceAll(shader_data->m_shader_source, "custom_main", CUSTOM_PIXELSHADER_COLOR_FUNC);
|
||||
const auto global_conflicts = GlobalConflicts(color_shader_data);
|
||||
color_shader_data = ReplaceAll(color_shader_data, "\r\n", "\n");
|
||||
color_shader_data = ReplaceAll(color_shader_data, "{", "{{");
|
||||
color_shader_data = ReplaceAll(color_shader_data, "}", "}}");
|
||||
// First replace global conflicts with dummy strings
|
||||
// This avoids the problem where a shorter word
|
||||
// is in a longer word, ex two functions: 'execute' and 'execute_fast'
|
||||
for (std::size_t i = 0; i < global_conflicts.size(); i++)
|
||||
{
|
||||
const std::string& identifier = global_conflicts[i];
|
||||
color_shader_data =
|
||||
ReplaceAll(color_shader_data, identifier, fmt::format("_{0}_DOLPHIN_TEMP_{0}_", i));
|
||||
}
|
||||
// Now replace the temporaries with the actual value
|
||||
for (std::size_t i = 0; i < global_conflicts.size(); i++)
|
||||
{
|
||||
const std::string& identifier = global_conflicts[i];
|
||||
color_shader_data = ReplaceAll(color_shader_data, fmt::format("_{0}_DOLPHIN_TEMP_{0}_", i),
|
||||
fmt::format("{}_{{0}}", identifier));
|
||||
}
|
||||
|
||||
for (const auto& game_texture : m_game_textures)
|
||||
{
|
||||
if (!game_texture)
|
||||
continue;
|
||||
|
||||
m_last_generated_shader_code.Write("{}", game_texture->m_sampler_code);
|
||||
m_last_generated_shader_code.Write("{}", game_texture->m_define_code);
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < texture_units.size(); i++)
|
||||
{
|
||||
const auto& texture_unit = texture_units[i];
|
||||
m_last_generated_shader_code.Write(
|
||||
"#define TEX_COORD{} data.texcoord[data.texmap_to_texcoord_index[{}]].xy\n", i,
|
||||
texture_unit);
|
||||
}
|
||||
m_last_generated_shader_code.Write("{}", color_shader_data);
|
||||
}
|
||||
}
|
||||
|
@ -17,15 +17,9 @@
|
||||
#include "VideoCommon/Assets/TextureAsset.h"
|
||||
#include "VideoCommon/ShaderGenCommon.h"
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
class CustomAssetLoader;
|
||||
}
|
||||
|
||||
struct CustomPipeline
|
||||
{
|
||||
void UpdatePixelData(VideoCommon::CustomAssetLoader& loader,
|
||||
std::shared_ptr<VideoCommon::CustomAssetLibrary> library,
|
||||
void UpdatePixelData(std::shared_ptr<VideoCommon::CustomAssetLibrary> library,
|
||||
std::span<const u32> texture_units,
|
||||
const VideoCommon::CustomAssetLibrary::AssetID& material_to_load);
|
||||
|
||||
@ -34,7 +28,7 @@ struct CustomPipeline
|
||||
|
||||
struct CachedTextureAsset
|
||||
{
|
||||
VideoCommon::CachedAsset<VideoCommon::GameTextureAsset> m_cached_asset;
|
||||
VideoCommon::CachedAsset<VideoCommon::TextureAsset> m_cached_asset;
|
||||
std::unique_ptr<AbstractTexture> m_texture;
|
||||
std::string m_sampler_code;
|
||||
std::string m_define_code;
|
||||
|
@ -47,7 +47,7 @@ struct TextureCreate
|
||||
std::string_view texture_name;
|
||||
u32 texture_width;
|
||||
u32 texture_height;
|
||||
std::vector<VideoCommon::CachedAsset<VideoCommon::GameTextureAsset>>* custom_textures;
|
||||
std::vector<VideoCommon::CachedAsset<VideoCommon::TextureAsset>>* custom_textures;
|
||||
|
||||
// Dependencies needed to reload the texture and trigger this create again
|
||||
std::vector<VideoCommon::CachedAsset<VideoCommon::CustomAsset>>* additional_dependencies;
|
||||
|
@ -25,7 +25,6 @@
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/System.h"
|
||||
#include "VideoCommon/Assets/CustomAsset.h"
|
||||
#include "VideoCommon/Assets/CustomAssetLoader.h"
|
||||
#include "VideoCommon/Assets/DirectFilesystemAssetLibrary.h"
|
||||
#include "VideoCommon/OnScreenDisplay.h"
|
||||
#include "VideoCommon/VideoConfig.h"
|
||||
@ -95,10 +94,11 @@ void HiresTexture::Update()
|
||||
GetTextureDirectoriesWithGameId(File::GetUserPath(D_HIRESTEXTURES_IDX), game_id);
|
||||
const std::vector<std::string> extensions{".png", ".dds"};
|
||||
|
||||
auto& system = Core::System::GetInstance();
|
||||
|
||||
for (const auto& texture_directory : texture_directories)
|
||||
{
|
||||
// Watch this directory for any texture reloads
|
||||
s_file_library->Watch(texture_directory);
|
||||
|
||||
const auto texture_paths =
|
||||
Common::DoFileSearch({texture_directory}, extensions, /*recursive*/ true);
|
||||
|
||||
@ -130,10 +130,10 @@ void HiresTexture::Update()
|
||||
|
||||
if (g_ActiveConfig.bCacheHiresTextures)
|
||||
{
|
||||
auto hires_texture = std::make_shared<HiresTexture>(
|
||||
has_arbitrary_mipmaps,
|
||||
system.GetCustomAssetLoader().LoadGameTexture(filename, s_file_library));
|
||||
s_hires_texture_cache.try_emplace(filename, std::move(hires_texture));
|
||||
auto hires_texture =
|
||||
std::make_shared<HiresTexture>(has_arbitrary_mipmaps, std::move(filename));
|
||||
static_cast<void>(hires_texture->LoadTexture());
|
||||
s_hires_texture_cache.try_emplace(hires_texture->GetId(), hires_texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -167,7 +167,7 @@ void HiresTexture::Clear()
|
||||
|
||||
std::shared_ptr<HiresTexture> HiresTexture::Search(const TextureInfo& texture_info)
|
||||
{
|
||||
const auto [base_filename, has_arb_mipmaps] = GetNameArbPair(texture_info);
|
||||
auto [base_filename, has_arb_mipmaps] = GetNameArbPair(texture_info);
|
||||
if (base_filename == "")
|
||||
return nullptr;
|
||||
|
||||
@ -177,24 +177,27 @@ std::shared_ptr<HiresTexture> HiresTexture::Search(const TextureInfo& texture_in
|
||||
}
|
||||
else
|
||||
{
|
||||
auto& system = Core::System::GetInstance();
|
||||
auto hires_texture = std::make_shared<HiresTexture>(
|
||||
has_arb_mipmaps,
|
||||
system.GetCustomAssetLoader().LoadGameTexture(base_filename, s_file_library));
|
||||
auto hires_texture = std::make_shared<HiresTexture>(has_arb_mipmaps, std::move(base_filename));
|
||||
if (g_ActiveConfig.bCacheHiresTextures)
|
||||
{
|
||||
s_hires_texture_cache.try_emplace(base_filename, hires_texture);
|
||||
s_hires_texture_cache.try_emplace(hires_texture->GetId(), hires_texture);
|
||||
}
|
||||
return hires_texture;
|
||||
}
|
||||
}
|
||||
|
||||
HiresTexture::HiresTexture(bool has_arbitrary_mipmaps,
|
||||
std::shared_ptr<VideoCommon::GameTextureAsset> asset)
|
||||
: m_has_arbitrary_mipmaps(has_arbitrary_mipmaps), m_game_texture(std::move(asset))
|
||||
HiresTexture::HiresTexture(bool has_arbitrary_mipmaps, std::string id)
|
||||
: m_has_arbitrary_mipmaps(has_arbitrary_mipmaps), m_id(std::move(id))
|
||||
{
|
||||
}
|
||||
|
||||
VideoCommon::CustomResourceManager::TextureTimePair HiresTexture::LoadTexture() const
|
||||
{
|
||||
auto& system = Core::System::GetInstance();
|
||||
auto& custom_resource_manager = system.GetCustomResourceManager();
|
||||
return custom_resource_manager.GetTextureDataFromAsset(m_id, s_file_library);
|
||||
}
|
||||
|
||||
std::set<std::string> GetTextureDirectoriesWithGameId(const std::string& root_directory,
|
||||
const std::string& game_id)
|
||||
{
|
||||
|
@ -9,8 +9,8 @@
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "VideoCommon/Assets/CustomResourceManager.h"
|
||||
#include "VideoCommon/Assets/CustomTextureData.h"
|
||||
#include "VideoCommon/Assets/TextureAsset.h"
|
||||
#include "VideoCommon/TextureConfig.h"
|
||||
#include "VideoCommon/TextureInfo.h"
|
||||
|
||||
@ -27,12 +27,13 @@ public:
|
||||
static void Shutdown();
|
||||
static std::shared_ptr<HiresTexture> Search(const TextureInfo& texture_info);
|
||||
|
||||
HiresTexture(bool has_arbitrary_mipmaps, std::shared_ptr<VideoCommon::GameTextureAsset> asset);
|
||||
HiresTexture(bool has_arbitrary_mipmaps, std::string id);
|
||||
|
||||
bool HasArbitraryMipmaps() const { return m_has_arbitrary_mipmaps; }
|
||||
const std::shared_ptr<VideoCommon::GameTextureAsset>& GetAsset() const { return m_game_texture; }
|
||||
VideoCommon::CustomResourceManager::TextureTimePair LoadTexture() const;
|
||||
const std::string& GetId() const { return m_id; }
|
||||
|
||||
private:
|
||||
bool m_has_arbitrary_mipmaps = false;
|
||||
std::shared_ptr<VideoCommon::GameTextureAsset> m_game_texture;
|
||||
std::string m_id;
|
||||
};
|
||||
|
@ -1007,6 +1007,12 @@ ShaderCode GeneratePixelShaderCode(APIType api_type, const ShaderHostConfig& hos
|
||||
out.Write("\tfrag_input.tex{0} = tex{0};\n", i);
|
||||
}
|
||||
|
||||
// Initialize other texture coordinates that are unused
|
||||
for (u32 i = uid_data->genMode_numtexgens; i < 8; i++)
|
||||
{
|
||||
out.Write("\tfrag_input.tex{0} = vec3(0, 0, 0);\n", i);
|
||||
}
|
||||
|
||||
if (!custom_contents.shader.empty())
|
||||
GenerateCustomLighting(&out, uid_data->lighting);
|
||||
|
||||
@ -2061,11 +2067,7 @@ static void WriteFragmentDefinitions(APIType api_type, const ShaderHostConfig& h
|
||||
out.Write("\tint layer;\n");
|
||||
out.Write("\tvec3 normal;\n");
|
||||
out.Write("\tvec3 position;\n");
|
||||
for (u32 i = 0; i < uid_data->genMode_numtexgens; i++)
|
||||
{
|
||||
out.Write("\tvec3 tex{};\n", i);
|
||||
}
|
||||
for (u32 i = uid_data->genMode_numtexgens; i < 8; i++)
|
||||
for (u32 i = 0; i < 8; i++)
|
||||
{
|
||||
out.Write("\tvec3 tex{};\n", i);
|
||||
}
|
||||
|
@ -37,13 +37,14 @@
|
||||
#include "VideoCommon/AbstractFramebuffer.h"
|
||||
#include "VideoCommon/AbstractGfx.h"
|
||||
#include "VideoCommon/AbstractStagingTexture.h"
|
||||
#include "VideoCommon/Assets/CustomResourceManager.h"
|
||||
#include "VideoCommon/Assets/CustomTextureData.h"
|
||||
#include "VideoCommon/Assets/TextureAssetUtils.h"
|
||||
#include "VideoCommon/BPMemory.h"
|
||||
#include "VideoCommon/FramebufferManager.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/FBInfo.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionData.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h"
|
||||
#include "VideoCommon/HiresTextures.h"
|
||||
#include "VideoCommon/OpcodeDecoding.h"
|
||||
#include "VideoCommon/PixelShaderManager.h"
|
||||
#include "VideoCommon/Present.h"
|
||||
@ -262,25 +263,12 @@ void TextureCacheBase::SetBackupConfig(const VideoConfig& config)
|
||||
|
||||
bool TextureCacheBase::DidLinkedAssetsChange(const TCacheEntry& entry)
|
||||
{
|
||||
for (const auto& cached_asset : entry.linked_game_texture_assets)
|
||||
{
|
||||
if (cached_asset.m_asset)
|
||||
{
|
||||
if (cached_asset.m_asset->GetLastLoadedTime() > cached_asset.m_cached_write_time)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!entry.hires_texture)
|
||||
return false;
|
||||
|
||||
for (const auto& cached_asset : entry.linked_asset_dependencies)
|
||||
{
|
||||
if (cached_asset.m_asset)
|
||||
{
|
||||
if (cached_asset.m_asset->GetLastLoadedTime() > cached_asset.m_cached_write_time)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
const auto [texture_data, load_time] = entry.hires_texture->LoadTexture();
|
||||
|
||||
return false;
|
||||
return load_time > entry.last_load_time;
|
||||
}
|
||||
|
||||
RcTcacheEntry TextureCacheBase::ApplyPaletteToEntry(RcTcacheEntry& entry, const u8* palette,
|
||||
@ -1566,80 +1554,50 @@ RcTcacheEntry TextureCacheBase::GetTexture(const int textureCacheSafetyColorSamp
|
||||
InvalidateTexture(oldest_entry);
|
||||
}
|
||||
|
||||
std::vector<VideoCommon::CachedAsset<VideoCommon::GameTextureAsset>> cached_game_assets;
|
||||
std::vector<std::shared_ptr<VideoCommon::TextureData>> data_for_assets;
|
||||
std::shared_ptr<HiresTexture> hires_texture;
|
||||
bool has_arbitrary_mipmaps = false;
|
||||
bool skip_texture_dump = false;
|
||||
std::shared_ptr<HiresTexture> hires_texture;
|
||||
std::shared_ptr<VideoCommon::CustomTextureData> custom_texture_data = nullptr;
|
||||
VideoCommon::CustomAsset::TimeType load_time = {};
|
||||
if (g_ActiveConfig.bHiresTextures)
|
||||
{
|
||||
hires_texture = HiresTexture::Search(texture_info);
|
||||
if (hires_texture)
|
||||
{
|
||||
auto asset = hires_texture->GetAsset();
|
||||
const auto loaded_time = asset->GetLastLoadedTime();
|
||||
cached_game_assets.push_back(
|
||||
VideoCommon::CachedAsset<VideoCommon::GameTextureAsset>{std::move(asset), loaded_time});
|
||||
has_arbitrary_mipmaps = hires_texture->HasArbitraryMipmaps();
|
||||
std::tie(custom_texture_data, load_time) = hires_texture->LoadTexture();
|
||||
if (custom_texture_data && !VideoCommon::ValidateTextureData(
|
||||
hires_texture->GetId(), *custom_texture_data,
|
||||
texture_info.GetRawWidth(), texture_info.GetRawHeight()))
|
||||
{
|
||||
custom_texture_data = nullptr;
|
||||
load_time = {};
|
||||
}
|
||||
skip_texture_dump = true;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<VideoCommon::CachedAsset<VideoCommon::CustomAsset>> additional_dependencies;
|
||||
|
||||
std::string texture_name = "";
|
||||
|
||||
if (g_ActiveConfig.bGraphicMods)
|
||||
{
|
||||
u32 height = texture_info.GetRawHeight();
|
||||
u32 width = texture_info.GetRawWidth();
|
||||
if (hires_texture)
|
||||
{
|
||||
auto asset = hires_texture->GetAsset();
|
||||
if (asset)
|
||||
{
|
||||
auto data = asset->GetData();
|
||||
if (data)
|
||||
{
|
||||
if (!data->m_texture.m_slices.empty())
|
||||
{
|
||||
if (!data->m_texture.m_slices[0].m_levels.empty())
|
||||
{
|
||||
height = data->m_texture.m_slices[0].m_levels[0].height;
|
||||
width = data->m_texture.m_slices[0].m_levels[0].width;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
texture_name = texture_info.CalculateTextureName().GetFullName();
|
||||
GraphicsModActionData::TextureCreate texture_create{
|
||||
texture_name, width, height, &cached_game_assets, &additional_dependencies};
|
||||
GraphicsModActionData::TextureCreate texture_create{texture_name, width, height, nullptr,
|
||||
nullptr};
|
||||
for (const auto& action : g_graphics_mod_manager->GetTextureCreateActions(texture_name))
|
||||
{
|
||||
action->OnTextureCreate(&texture_create);
|
||||
}
|
||||
}
|
||||
|
||||
data_for_assets.reserve(cached_game_assets.size());
|
||||
for (auto& cached_asset : cached_game_assets)
|
||||
{
|
||||
auto data = cached_asset.m_asset->GetData();
|
||||
if (data)
|
||||
{
|
||||
if (cached_asset.m_asset->Validate(texture_info.GetRawWidth(), texture_info.GetRawHeight()))
|
||||
{
|
||||
data_for_assets.push_back(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto entry =
|
||||
CreateTextureEntry(TextureCreationInfo{base_hash, full_hash, bytes_per_block, palette_size},
|
||||
texture_info, textureCacheSafetyColorSampleSize,
|
||||
std::move(data_for_assets), has_arbitrary_mipmaps, skip_texture_dump);
|
||||
entry->linked_game_texture_assets = std::move(cached_game_assets);
|
||||
entry->linked_asset_dependencies = std::move(additional_dependencies);
|
||||
texture_info, textureCacheSafetyColorSampleSize, custom_texture_data.get(),
|
||||
has_arbitrary_mipmaps, skip_texture_dump);
|
||||
entry->hires_texture = std::move(hires_texture);
|
||||
entry->last_load_time = load_time;
|
||||
entry->texture_info_name = std::move(texture_name);
|
||||
return entry;
|
||||
}
|
||||
@ -1649,8 +1607,7 @@ RcTcacheEntry TextureCacheBase::GetTexture(const int textureCacheSafetyColorSamp
|
||||
// expected because each texture is loaded into a texture array
|
||||
RcTcacheEntry TextureCacheBase::CreateTextureEntry(
|
||||
const TextureCreationInfo& creation_info, const TextureInfo& texture_info,
|
||||
const int safety_color_sample_size,
|
||||
std::vector<std::shared_ptr<VideoCommon::TextureData>> assets_data,
|
||||
const int safety_color_sample_size, VideoCommon::CustomTextureData* custom_texture_data,
|
||||
const bool custom_arbitrary_mipmaps, bool skip_texture_dump)
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
@ -1660,33 +1617,22 @@ RcTcacheEntry TextureCacheBase::CreateTextureEntry(
|
||||
#endif
|
||||
|
||||
RcTcacheEntry entry;
|
||||
if (!assets_data.empty())
|
||||
if (custom_texture_data)
|
||||
{
|
||||
const auto calculate_max_levels = [&]() {
|
||||
const auto max_element = std::ranges::max_element(
|
||||
assets_data, {}, [](const auto& v) { return v->m_texture.m_slices[0].m_levels.size(); });
|
||||
return (*max_element)->m_texture.m_slices[0].m_levels.size();
|
||||
};
|
||||
const u32 texLevels = no_mips ? 1 : (u32)calculate_max_levels();
|
||||
const auto& first_level = assets_data[0]->m_texture.m_slices[0].m_levels[0];
|
||||
const TextureConfig config(first_level.width, first_level.height, texLevels,
|
||||
static_cast<u32>(assets_data.size()), 1, first_level.format, 0,
|
||||
AbstractTextureType::Texture_2DArray);
|
||||
const u32 texLevels = no_mips ? 1 : (u32)custom_texture_data->m_slices[0].m_levels.size();
|
||||
const auto& first_level = custom_texture_data->m_slices[0].m_levels[0];
|
||||
const TextureConfig config(first_level.width, first_level.height, texLevels, 1, 1,
|
||||
first_level.format, 0, AbstractTextureType::Texture_2DArray);
|
||||
entry = AllocateCacheEntry(config);
|
||||
if (!entry) [[unlikely]]
|
||||
return entry;
|
||||
for (u32 data_index = 0; data_index < static_cast<u32>(assets_data.size()); data_index++)
|
||||
const auto& slice = custom_texture_data->m_slices[0];
|
||||
for (u32 level_index = 0;
|
||||
level_index < std::min(texLevels, static_cast<u32>(slice.m_levels.size())); ++level_index)
|
||||
{
|
||||
const auto& asset = assets_data[data_index];
|
||||
const auto& slice = asset->m_texture.m_slices[0];
|
||||
for (u32 level_index = 0;
|
||||
level_index < std::min(texLevels, static_cast<u32>(slice.m_levels.size()));
|
||||
++level_index)
|
||||
{
|
||||
const auto& level = slice.m_levels[level_index];
|
||||
entry->texture->Load(level_index, level.width, level.height, level.row_length,
|
||||
level.data.data(), level.data.size(), data_index);
|
||||
}
|
||||
const auto& level = slice.m_levels[level_index];
|
||||
entry->texture->Load(level_index, level.width, level.height, level.row_length,
|
||||
level.data.data(), level.data.size());
|
||||
}
|
||||
|
||||
entry->has_arbitrary_mips = custom_arbitrary_mipmaps;
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "VideoCommon/AbstractTexture.h"
|
||||
#include "VideoCommon/Assets/CustomAsset.h"
|
||||
#include "VideoCommon/BPMemory.h"
|
||||
#include "VideoCommon/HiresTextures.h"
|
||||
#include "VideoCommon/TextureConfig.h"
|
||||
#include "VideoCommon/TextureDecoder.h"
|
||||
#include "VideoCommon/TextureInfo.h"
|
||||
@ -167,8 +168,8 @@ struct TCacheEntry
|
||||
|
||||
std::string texture_info_name = "";
|
||||
|
||||
std::vector<VideoCommon::CachedAsset<VideoCommon::GameTextureAsset>> linked_game_texture_assets;
|
||||
std::vector<VideoCommon::CachedAsset<VideoCommon::CustomAsset>> linked_asset_dependencies;
|
||||
VideoCommon::CustomAsset::TimeType last_load_time;
|
||||
std::shared_ptr<HiresTexture> hires_texture;
|
||||
|
||||
explicit TCacheEntry(std::unique_ptr<AbstractTexture> tex,
|
||||
std::unique_ptr<AbstractFramebuffer> fb);
|
||||
@ -351,11 +352,10 @@ private:
|
||||
|
||||
void SetBackupConfig(const VideoConfig& config);
|
||||
|
||||
RcTcacheEntry
|
||||
CreateTextureEntry(const TextureCreationInfo& creation_info, const TextureInfo& texture_info,
|
||||
int safety_color_sample_size,
|
||||
std::vector<std::shared_ptr<VideoCommon::TextureData>> assets_data,
|
||||
bool custom_arbitrary_mipmaps, bool skip_texture_dump);
|
||||
RcTcacheEntry CreateTextureEntry(const TextureCreationInfo& creation_info,
|
||||
const TextureInfo& texture_info, int safety_color_sample_size,
|
||||
VideoCommon::CustomTextureData* custom_texture_data,
|
||||
bool custom_arbitrary_mipmaps, bool skip_texture_dump);
|
||||
|
||||
RcTcacheEntry GetXFBFromCache(u32 address, u32 width, u32 height, u32 stride);
|
||||
|
||||
|
@ -41,6 +41,7 @@
|
||||
#endif
|
||||
|
||||
#include "VideoCommon/AbstractGfx.h"
|
||||
#include "VideoCommon/Assets/CustomResourceManager.h"
|
||||
#include "VideoCommon/AsyncRequests.h"
|
||||
#include "VideoCommon/BPStructs.h"
|
||||
#include "VideoCommon/BoundingBox.h"
|
||||
@ -341,12 +342,16 @@ bool VideoBackendBase::InitializeShared(std::unique_ptr<AbstractGfx> gfx,
|
||||
}
|
||||
|
||||
g_shader_cache->InitializeShaderCache();
|
||||
system.GetCustomResourceManager().Initialize();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void VideoBackendBase::ShutdownShared()
|
||||
{
|
||||
auto& system = Core::System::GetInstance();
|
||||
system.GetCustomResourceManager().Shutdown();
|
||||
|
||||
g_frame_dumper.reset();
|
||||
g_presenter.reset();
|
||||
|
||||
@ -369,7 +374,6 @@ void VideoBackendBase::ShutdownShared()
|
||||
|
||||
m_initialized = false;
|
||||
|
||||
auto& system = Core::System::GetInstance();
|
||||
VertexLoaderManager::Clear();
|
||||
system.GetFifo().Shutdown();
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
<AdditionalIncludeDirectories>$(ExternalsDir)rangeset\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(ExternalsDir)Vulkan-Headers\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(ExternalsDir)VulkanMemoryAllocator\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(ExternalsDir)watcher\watcher\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(ExternalsDir)WIL\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<!--WIL doesn't have it's own vcxproj/exports, and no externals reference WIL, so this is fine to define only for Dolphin-->
|
||||
<PreprocessorDefinitions>WIL_SUPPRESS_EXCEPTIONS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
|
Loading…
Reference in New Issue
Block a user