Compare commits

...

39 Commits

Author SHA1 Message Date
Joshua Vandaële
98203665ab
Merge f22b651379 into 52fcdde485 2025-06-07 21:52:42 +00:00
JMC47
52fcdde485
Merge pull request #13386 from iwubcode/resource_manager_system
VideoCommon: add resource manager, tracks resources to load assets in optimal way and manage memory
2025-06-07 15:51:34 -04:00
JMC47
5eb61024c6
Merge pull request #13740 from JoshuaVandaele/bsod-fix-i-hope
Host: Clean up device handle in all cases
2025-06-07 12:27:49 -04:00
Admiral H. Curtiss
a27b845514
Merge pull request #13710 from TryTwo/UI_Sliders_Update
Advanced Panel convert QSliders into ConfigSliders
2025-06-07 17:39:40 +02:00
Admiral H. Curtiss
1b1ca019a4
Merge pull request #13724 from SuperSamus/gamelist-properties-noduplicates
GameList: Prevent opening Properties multiple times for the same game
2025-06-07 17:24:50 +02:00
Joshua Vandaële
241834709b
Host: Clean up device handle 2025-06-07 16:55:45 +02:00
JosJuice
185b080f03
Merge pull request #13142 from JosJuice/fifo-window-starting-stopping
DolphinQt: Call FIFOPlayerWindow::UpdateControls for Starting/Stopping
2025-06-07 10:43:19 +02:00
iwubcode
c3d3b81533 VideoCommon: remove 'GetByteSizeInMemory()' from custom asset, it is not needed anymore 2025-06-06 23:03:02 -05:00
iwubcode
774a84a953 VideoCommon: avoid race conditions with asset load/unload by moving the lock to the entire function, favor atomics for the memory/time getters 2025-06-06 23:03:02 -05:00
iwubcode
b3f50c969e VideoCommon: rename m_bytes_loaded in asset library to bytes_loaded 2025-06-06 23:03:02 -05:00
iwubcode
3b83907b88 VideoCommon: update CustomAsset's load time to be before the load occurs (this prevents issues where the load time might be incorrectly inflated by long load operations)
Co-authored-by: Jordan Woyak <jordan.woyak@gmail.com>
2025-06-06 23:03:02 -05:00
iwubcode
d940d62cae VideoCommon: watch texture pack folder for texture reloads (from dynamic input textures) 2025-06-06 23:03:02 -05:00
iwubcode
7afa9e6c6f VideoCommon: use CustomResourceManager in the texture cache and hook up to our hires textures 2025-06-06 23:03:02 -05:00
iwubcode
12d178a8df VideoCommon: initialize and shutdown the CustomResourceManager when the video thread initializes and shuts down 2025-06-06 23:03:02 -05:00
iwubcode
f910c1d934 Core: add CustomResourceManager to System 2025-06-06 23:03:02 -05:00
iwubcode
70abcb2030 VideoCommon: add resource manager and new asset loader; the resource manager uses a least recently used cache to determine which assets get priority for loading. Additionally, if the system is low on memory, assets will be purged with the less requested assets being the first to go. The loader is multithreaded now and loads assets as quickly as possible as long as memory is available
Co-authored-by: Jordan Woyak <jordan.woyak@gmail.com>
2025-06-06 23:03:02 -05:00
iwubcode
d8ea31ca46 VideoCommon: rename GameTextureAsset into TextureAsset and make it only contain CustomTextureData. Move validation and load logic to individual functions 2025-06-06 23:03:02 -05:00
iwubcode
2ae43324cb VideoCommon: move AssetMap to a types header file, so it can be pulled in without the DirectFilesystemAssetLibrary dependencies, the header will be expanded later 2025-06-06 23:03:02 -05:00
iwubcode
7d59c2743d Common: Add class 'FilesystemWatcher' that is used to watch paths and receive callbacks about filesystem level events for anything under that path 2025-06-06 23:03:02 -05:00
iwubcode
8113399b68 Externals: add watcher, a library used to watch a filesystem location for changes 2025-06-06 23:03:02 -05:00
iwubcode
bafe78203d VideoCommon: remove 'GetLastAssetWriteTime' and switch to a steady_clock for asset times 2025-06-06 23:03:02 -05:00
iwubcode
15f125ebee VideoCommon: change asset loading to return the number of bytes loaded instead of a pass/fail 2025-06-06 23:03:02 -05:00
iwubcode
316740daed VideoCommon: add 'Unload' functionality to CustomAsset 2025-06-06 23:03:02 -05:00
Jordan Woyak
903eafcf65
Merge pull request #13714 from Dentomologist/dolphinqt_remove_redundant_window_hints
DolphinQt: Remove redundant window hints
2025-06-06 22:13:01 -05:00
Dentomologist
2a7e8a4003 DolphinQt: Remove redundant window hints
Remove window hints clearing the flag Qt::WindowContextHelpButtonHint,
which is already off by default in Qt 6.

In Qt 5 this flag was set by default for QDialogs, and on Windows put a
? button in the corner of the title bar allowing users to activate Qt's
QWhatsThis help system for a given widget. Since we don't set that text
the ? button was useless and so we hid it manually.
2025-06-06 19:35:13 -07:00
iwubcode
9ec69b5925 VideoCommon: add a handle to custom asset, this is an id that is only relevant for a particular game session but is slightly faster as a numeric value for lookups than the traditional asset id 2025-06-06 19:20:25 -05:00
iwubcode
d7de49ccf6 Core / VideoCommon: Remove original custom asset loader 2025-06-06 19:20:25 -05:00
JMC47
5ec5db9240
Merge pull request #13392 from jordan-woyak/frame-pacing-accurate-time
CoreTiming: Add setting to pursue accurate overall emulation runtime
2025-06-06 19:17:44 -04:00
JMC47
974c56d3c5
Merge pull request #13731 from iwubcode/pixel_shader_gen_fix_complete_initialize
VideoCommon: ensure pixel shader gen input structure is completely initialized
2025-06-06 19:08:57 -04:00
Jordan Woyak
977f2da6a7
Merge pull request #13735 from JMC47/triopatch
GamePatch: Disney Trio of Destruction Pagetable Speedhack.
2025-06-06 17:54:36 -05:00
JMC47
b1d114f7f7
Merge pull request #13730 from jordan-woyak/controllers-on-main-config-dialog
DolphinQt: Move "Controllers" to main settings window.
2025-06-06 18:54:12 -04:00
JMC47
be3d48ec5f GamePatch: Disney Trio of Destruction Pagetable Speedhack. 2025-06-06 17:17:11 -04:00
iwubcode
ae26b38fc0 VideoCommon: fix pixel shader gen error about structure not being fully initialized 2025-06-05 01:37:45 -05:00
Jordan Woyak
b9bea58f0f DolphinQt: Move "Controllers" to main settings window. 2025-06-04 19:33:01 -05:00
Martino Fontana
2de9122b5f GameList: Prevent opening Properties multiple times for the same game 2025-06-04 23:11:09 +02:00
TryTwo
a6a5e201b6 Qt Advanced Panel: Convert QSliders into ConfigSliders.
Part of the refactor into the config system.
2025-05-26 19:52:30 -07:00
Joshua Vandaële
f22b651379
discord-rpc: Bump CMake minimum requirement 2025-05-18 18:07:24 +02:00
Jordan Woyak
ec29d120b5 CoreTiming: Add a setting to pursue accurate emulation time. 2025-05-02 15:23:43 -05:00
JosJuice
d48e6e25ad DolphinQt: Call FIFOPlayerWindow::UpdateControls for Starting/Stopping
Follow-up for
https://github.com/dolphin-emu/dolphin/pull/12918#discussion_r1785153070.
2024-10-20 13:38:24 +02:00
122 changed files with 1735 additions and 1228 deletions

3
.gitmodules vendored
View File

@ -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

View File

@ -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
#

View File

@ -0,0 +1,9 @@
# SCYE4Q - Cars 2
[OnFrame]
#This speedhack modifies the way the game manages memory to run faster in Dolphin.
$BAT Speedhack
0x8019CB1C:dword:0x48000180
[OnFrame_Enabled]
$BAT Speedhack

View File

@ -0,0 +1,9 @@
# SCYP4Q - Cars 2
[OnFrame]
#This speedhack modifies the way the game manages memory to run faster in Dolphin.
$BAT Speedhack
0x8019CB1C:dword:0x48000180
[OnFrame_Enabled]
$BAT Speedhack

View File

@ -0,0 +1,9 @@
# SCYR4Q - Cars 2
[OnFrame]
#This speedhack modifies the way the game manages memory to run faster in Dolphin.
$BAT Speedhack
0x8019B4EC:dword:0x48000180
[OnFrame_Enabled]
$BAT Speedhack

View File

@ -0,0 +1,9 @@
# SCYX4Q - Cars 2
[OnFrame]
#This speedhack modifies the way the game manages memory to run faster in Dolphin.
$BAT Speedhack
0x8019CBBC:dword:0x48000180
[OnFrame_Enabled]
$BAT Speedhack

View File

@ -0,0 +1,9 @@
# SCYY4Q - Cars 2
[OnFrame]
#This speedhack modifies the way the game manages memory to run faster in Dolphin.
$BAT Speedhack
0x8019B55C:dword:0x48000180
[OnFrame_Enabled]
$BAT Speedhack

View File

@ -0,0 +1,9 @@
# SCYZ4Q - Cars 2
[OnFrame]
#This speedhack modifies the way the game manages memory to run faster in Dolphin.
$BAT Speedhack
0x8019B55C:dword:0x48000180
[OnFrame_Enabled]
$BAT Speedhack

View File

@ -1,4 +1,4 @@
# SQIE4Q, SQIP4Q - Disney Infinity
# SQIE4Q, SQIP4Q, SQIY4Q - Disney Infinity
[Core]
# Values set here will override the main Dolphin settings.

View File

@ -0,0 +1,9 @@
# SQIE4Q - Disney Infinity
[OnFrame]
#This speedhack modifies the way the game manages memory to run faster in Dolphin.
$BAT Speedhack
0x8008E60C:dword:0x48000180
[OnFrame_Enabled]
$BAT Speedhack

View File

@ -0,0 +1,9 @@
# SQIP4Q - Disney Infinity
[OnFrame]
#This speedhack modifies the way the game manages memory to run faster in Dolphin.
$BAT Speedhack
0x8008E60C:dword:0x48000180
[OnFrame_Enabled]
$BAT Speedhack

View File

@ -0,0 +1,9 @@
# SQIY4Q - Disney Infinity
[OnFrame]
#This speedhack modifies the way the game manages memory to run faster in Dolphin.
$BAT Speedhack
0x8008E60C:dword:0x48000180
[OnFrame_Enabled]
$BAT Speedhack

View File

@ -0,0 +1,9 @@
# STSE4Q - Toy Story 3
[OnFrame]
#This speedhack modifies the way the game manages memory to run faster in Dolphin.
$BAT Speedhack
0x801FA2E4:dword:0x48000180
[OnFrame_Enabled]
$BAT Speedhack

View File

@ -0,0 +1,9 @@
# STSP4Q - Toy Story 3
[OnFrame]
#This speedhack modifies the way the game manages memory to run faster in Dolphin.
$BAT Speedhack
0x801FA2E4:dword:0x48000180
[OnFrame_Enabled]
$BAT Speedhack

View File

@ -0,0 +1,9 @@
# STSP4Q - Toy Story 3
[OnFrame]
#This speedhack modifies the way the game manages memory to run faster in Dolphin.
$BAT Speedhack
0x801FA354:dword:0x48000180
[OnFrame_Enabled]
$BAT Speedhack

View File

@ -0,0 +1,9 @@
# STSX4Q - Toy Story 3
[OnFrame]
#This speedhack modifies the way the game manages memory to run faster in Dolphin.
$BAT Speedhack
0x801FA354:dword:0x48000180
[OnFrame_Enabled]
$BAT Speedhack

View File

@ -0,0 +1,9 @@
# STSY4Q - Toy Story 3
[OnFrame]
#This speedhack modifies the way the game manages memory to run faster in Dolphin.
$BAT Speedhack
0x801FA2E4:dword:0x48000180
[OnFrame_Enabled]
$BAT Speedhack

View File

@ -0,0 +1,9 @@
# STSY4Q - Toy Story 3
[OnFrame]
#This speedhack modifies the way the game manages memory to run faster in Dolphin.
$BAT Speedhack
0x801FA354:dword:0x48000180
[OnFrame_Enabled]
$BAT Speedhack

View File

@ -0,0 +1,9 @@
# STSZ4Q - Toy Story 3 Toy Box Special Edition
[OnFrame]
#This speedhack modifies the way the game manages memory to run faster in Dolphin.
$BAT Speedhack
0x801FA2E4:dword:0x48000180
[OnFrame_Enabled]
$BAT Speedhack

View File

@ -1,4 +1,4 @@
cmake_minimum_required (VERSION 3.2.0)
cmake_minimum_required (VERSION 3.6)
project (DiscordRPC)
include(GNUInstallDirs)

4
Externals/watcher/CMakeLists.txt vendored Normal file
View 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

@ -0,0 +1 @@
Subproject commit b03bdcfc11549df595b77239cefe2643943a3e2f

View File

@ -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}
)

View 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

View 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

View File

@ -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"}, ""};

View File

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

View File

@ -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(); });

View File

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

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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();

View File

@ -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" />

View File

@ -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();

View File

@ -27,7 +27,6 @@
AchievementsWindow::AchievementsWindow(QWidget* parent) : QDialog(parent)
{
setWindowTitle(tr("Achievements"));
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
CreateMainLayout();
ConnectWidgets();

View File

@ -69,8 +69,8 @@ add_executable(dolphin-emu
Config/ControllerInterface/DualShockUDPClientWidget.h
Config/ControllerInterface/ServerStringValidator.cpp
Config/ControllerInterface/ServerStringValidator.h
Config/ControllersWindow.cpp
Config/ControllersWindow.h
Config/ControllersPane.cpp
Config/ControllersPane.h
Config/FilesystemWidget.cpp
Config/FilesystemWidget.h
Config/FreeLookWidget.cpp

View File

@ -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);

View File

@ -23,7 +23,6 @@
CheatCodeEditor::CheatCodeEditor(QWidget* parent) : QDialog(parent)
{
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
setWindowTitle(tr("Cheat Code Editor"));
CreateWidgets();

View File

@ -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))
{

View File

@ -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

View File

@ -17,7 +17,6 @@ ControllerInterfaceWindow::ControllerInterfaceWindow(QWidget* parent) : QDialog(
CreateMainLayout();
setWindowTitle(tr("Alternate Input Sources"));
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
}
void ControllerInterfaceWindow::CreateMainLayout()

View File

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

View File

@ -0,0 +1,35 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "DolphinQt/Config/ControllersPane.h"
#include <QVBoxLayout>
#include "DolphinQt/Config/CommonControllersWidget.h"
#include "DolphinQt/Config/GamecubeControllersWidget.h"
#include "DolphinQt/Config/WiimoteControllersWidget.h"
ControllersPane::ControllersPane()
{
CreateMainLayout();
}
void ControllersPane::showEvent(QShowEvent* event)
{
QWidget::showEvent(event);
m_wiimote_controllers->UpdateBluetoothAvailableStatus();
}
void ControllersPane::CreateMainLayout()
{
auto* const layout = new QVBoxLayout{this};
auto* const gamecube_controllers = new GamecubeControllersWidget(this);
m_wiimote_controllers = new WiimoteControllersWidget(this);
auto* const common = new CommonControllersWidget(this);
layout->addWidget(gamecube_controllers);
layout->addWidget(m_wiimote_controllers);
layout->addWidget(common);
}

View File

@ -0,0 +1,23 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QWidget>
class WiimoteControllersWidget;
class ControllersPane final : public QWidget
{
Q_OBJECT
public:
ControllersPane();
protected:
void showEvent(QShowEvent* event) override;
private:
void CreateMainLayout();
WiimoteControllersWidget* m_wiimote_controllers;
};

View File

@ -1,51 +0,0 @@
// Copyright 2017 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "DolphinQt/Config/ControllersWindow.h"
#include <QDialogButtonBox>
#include <QVBoxLayout>
#include "DolphinQt/Config/CommonControllersWidget.h"
#include "DolphinQt/Config/GamecubeControllersWidget.h"
#include "DolphinQt/Config/WiimoteControllersWidget.h"
#include "DolphinQt/QtUtils/QtUtils.h"
#include "DolphinQt/QtUtils/WrapInScrollArea.h"
ControllersWindow::ControllersWindow(QWidget* parent) : QDialog(parent)
{
setWindowTitle(tr("Controller Settings"));
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
m_gamecube_controllers = new GamecubeControllersWidget(this);
m_wiimote_controllers = new WiimoteControllersWidget(this);
m_common = new CommonControllersWidget(this);
CreateMainLayout();
ConnectWidgets();
}
void ControllersWindow::showEvent(QShowEvent* event)
{
QDialog::showEvent(event);
m_wiimote_controllers->UpdateBluetoothAvailableStatus();
}
void ControllersWindow::CreateMainLayout()
{
auto* layout = new QVBoxLayout();
m_button_box = new QDialogButtonBox(QDialogButtonBox::Close);
layout->addWidget(m_gamecube_controllers);
layout->addWidget(m_wiimote_controllers);
layout->addWidget(m_common);
layout->addStretch();
layout->addWidget(m_button_box);
WrapInScrollArea(this, layout);
QtUtils::AdjustSizeWithinScreen(this);
}
void ControllersWindow::ConnectWidgets()
{
connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
}

View File

@ -1,31 +0,0 @@
// Copyright 2017 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QDialog>
class CommonControllersWidget;
class GamecubeControllersWidget;
class QDialogButtonBox;
class QShowEvent;
class WiimoteControllersWidget;
class ControllersWindow final : public QDialog
{
Q_OBJECT
public:
explicit ControllersWindow(QWidget* parent);
protected:
void showEvent(QShowEvent* event) override;
private:
void CreateMainLayout();
void ConnectWidgets();
QDialogButtonBox* m_button_box;
GamecubeControllersWidget* m_gamecube_controllers;
WiimoteControllersWidget* m_wiimote_controllers;
CommonControllersWidget* m_common;
};

View File

@ -17,7 +17,6 @@ FreeLookWindow::FreeLookWindow(QWidget* parent) : QDialog(parent)
ConnectWidgets();
setWindowTitle(tr("Free Look Settings"));
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
}
void FreeLookWindow::CreateMainLayout()

View File

@ -22,7 +22,6 @@
ColorCorrectionConfigWindow::ColorCorrectionConfigWindow(QWidget* parent) : QDialog(parent)
{
setWindowTitle(tr("Color Correction Configuration"));
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
Create();
ConnectWidgets();

View File

@ -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)));

View File

@ -42,7 +42,6 @@ PostProcessingConfigWindow::PostProcessingConfigWindow(EnhancementsWidget* paren
}
setWindowTitle(tr("Post-Processing Shader Configuration"));
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
PopulateGroups();
Create();

View File

@ -17,8 +17,6 @@
GCPadWiiUConfigDialog::GCPadWiiUConfigDialog(int port, QWidget* parent)
: QDialog(parent), m_port{port}
{
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
CreateLayout();
LoadSettings();

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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()),

View File

@ -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;
};

View File

@ -9,6 +9,9 @@
#include <QStackedWidget>
#include <QVBoxLayout>
#include "Common/EnumUtils.h"
#include "DolphinQt/Config/ControllersPane.h"
#include "DolphinQt/QtUtils/QtUtils.h"
#include "DolphinQt/QtUtils/WrapInScrollArea.h"
#include "DolphinQt/Settings/AdvancedPane.h"
@ -21,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; }"));
@ -133,7 +134,9 @@ SettingsWindow::SettingsWindow(QWidget* parent) : StackedSettingsWindow{parent}
{
setWindowTitle(tr("Settings"));
// If you change the order, don't forget to update the SettingsWindowPaneIndex enum.
AddWrappedPane(new GeneralPane, tr("General"));
AddWrappedPane(new ControllersPane, tr("Controllers"));
AddWrappedPane(new InterfacePane, tr("Interface"));
AddWrappedPane(new AudioPane, tr("Audio"));
AddWrappedPane(new PathPane, tr("Paths"));
@ -144,12 +147,7 @@ SettingsWindow::SettingsWindow(QWidget* parent) : StackedSettingsWindow{parent}
OnDoneCreatingPanes();
}
void SettingsWindow::SelectAudioPane()
void SettingsWindow::SelectPane(SettingsWindowPaneIndex index)
{
ActivatePane(static_cast<int>(TabIndex::Audio));
}
void SettingsWindow::SelectGeneralPane()
{
ActivatePane(static_cast<int>(TabIndex::General));
ActivatePane(Common::ToUnderlying(index));
}

View File

@ -31,10 +31,16 @@ private:
QListWidget* m_navigation_list;
};
enum class TabIndex
enum class SettingsWindowPaneIndex : int
{
General = 0,
Audio = 2
Controllers,
Interface,
Audio,
Paths,
GameCube,
Wii,
Advanced,
};
class SettingsWindow final : public StackedSettingsWindow
@ -43,6 +49,5 @@ class SettingsWindow final : public StackedSettingsWindow
public:
explicit SettingsWindow(QWidget* parent = nullptr);
void SelectGeneralPane();
void SelectAudioPane();
void SelectPane(SettingsWindowPaneIndex);
};

View File

@ -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);

View File

@ -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"));

View File

@ -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);

View File

@ -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();

View File

@ -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"));

View File

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

View File

@ -68,7 +68,7 @@
<ClCompile Include="Config\ControllerInterface\DualShockUDPClientAddServerDialog.cpp" />
<ClCompile Include="Config\ControllerInterface\DualShockUDPClientWidget.cpp" />
<ClCompile Include="Config\ControllerInterface\ServerStringValidator.cpp" />
<ClCompile Include="Config\ControllersWindow.cpp" />
<ClCompile Include="Config\ControllersPane.cpp" />
<ClCompile Include="Config\FilesystemWidget.cpp" />
<ClCompile Include="Config\FreeLookWidget.cpp" />
<ClCompile Include="Config\FreeLookWindow.cpp" />
@ -293,7 +293,7 @@
<QtMoc Include="Config\ControllerInterface\DualShockUDPClientAddServerDialog.h" />
<QtMoc Include="Config\ControllerInterface\DualShockUDPClientWidget.h" />
<QtMoc Include="Config\ControllerInterface\ServerStringValidator.h" />
<QtMoc Include="Config\ControllersWindow.h" />
<QtMoc Include="Config\ControllersPane.h" />
<QtMoc Include="Config\FilesystemWidget.h" />
<QtMoc Include="Config\FreeLookWidget.h" />
<QtMoc Include="Config\FreeLookWindow.h" />

View File

@ -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();

View File

@ -60,7 +60,6 @@ GCMemcardCreateNewDialog::GCMemcardCreateNewDialog(QWidget* parent) : QDialog(pa
});
setWindowTitle(tr("Create New Memory Card"));
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
}
GCMemcardCreateNewDialog::~GCMemcardCreateNewDialog() = default;

View File

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

View File

@ -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);

View File

@ -77,7 +77,6 @@
#include "DolphinQt/AboutDialog.h"
#include "DolphinQt/Achievements/AchievementsWindow.h"
#include "DolphinQt/CheatsManager.h"
#include "DolphinQt/Config/ControllersWindow.h"
#include "DolphinQt/Config/FreeLookWindow.h"
#include "DolphinQt/Config/Graphics/GraphicsWindow.h"
#include "DolphinQt/Config/LogConfigWidget.h"
@ -1283,16 +1282,8 @@ void MainWindow::HideRenderWidget(bool reinit, bool is_exit)
void MainWindow::ShowControllersWindow()
{
if (!m_controllers_window)
{
m_controllers_window = new ControllersWindow(this);
InstallHotkeyFilter(m_controllers_window);
}
SetQWidgetWindowDecorations(m_controllers_window);
m_controllers_window->show();
m_controllers_window->raise();
m_controllers_window->activateWindow();
ShowSettingsWindow();
m_settings_window->SelectPane(SettingsWindowPaneIndex::Controllers);
}
void MainWindow::ShowFreeLookWindow()
@ -1331,13 +1322,13 @@ void MainWindow::ShowSettingsWindow()
void MainWindow::ShowAudioWindow()
{
ShowSettingsWindow();
m_settings_window->SelectAudioPane();
m_settings_window->SelectPane(SettingsWindowPaneIndex::Audio);
}
void MainWindow::ShowGeneralWindow()
{
ShowSettingsWindow();
m_settings_window->SelectGeneralPane();
m_settings_window->SelectPane(SettingsWindowPaneIndex::General);
}
void MainWindow::ShowAboutDialog()

View File

@ -27,7 +27,6 @@ class BreakpointWidget;
struct BootParameters;
class CheatsManager;
class CodeWidget;
class ControllersWindow;
class DiscordHandler;
class DragEnterEvent;
class FIFOPlayerWindow;
@ -246,7 +245,6 @@ private:
u32 m_state_slot = 1;
std::unique_ptr<BootParameters> m_pending_boot;
ControllersWindow* m_controllers_window = nullptr;
SettingsWindow* m_settings_window = nullptr;
GraphicsWindow* m_graphics_window = nullptr;
FIFOPlayerWindow* m_fifo_window = nullptr;

View File

@ -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();

View File

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

View File

@ -45,7 +45,6 @@ ChunkedProgressDialog::ChunkedProgressDialog(QWidget* parent) : QDialog(parent)
CreateWidgets();
ConnectWidgets();
setWindowTitle(tr("Data Transfer"));
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
}
void ChunkedProgressDialog::CreateWidgets()

View File

@ -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();

View File

@ -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);

View File

@ -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());

View File

@ -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();

View File

@ -17,7 +17,6 @@
PadMappingDialog::PadMappingDialog(QWidget* parent) : QDialog(parent)
{
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
setWindowTitle(tr("Assign Controllers"));
CreateWidgets();

View File

@ -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();
}

View File

@ -24,7 +24,6 @@ ResourcePackManager::ResourcePackManager(QWidget* widget) : QDialog(widget)
RepopulateTable();
setWindowTitle(tr("Resource Pack Manager"));
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
resize(QSize(900, 600));
}

View File

@ -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();

View File

@ -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));

View File

@ -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;
};

View File

@ -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);

View File

@ -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"));

View File

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

View File

@ -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 "

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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

View File

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

View File

@ -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

View File

@ -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

View 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

View 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

View File

@ -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;
}

View File

@ -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

Some files were not shown because too many files have changed in this diff Show More