Merge pull request #13311 from iwubcode/dynamic_input_textures_reduce_image_writes

Core / DolphinQt / InputCommon: reduce the number disk writes when using DynamicInputTextures
This commit is contained in:
JMC47 2025-03-23 15:12:28 -04:00 committed by GitHub
commit 2da255d8cd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 155 additions and 59 deletions

View file

@ -88,8 +88,10 @@ JNIEXPORT void JNICALL
Java_org_dolphinemu_dolphinemu_features_input_model_controlleremu_EmulatedController_updateSingleControlReference( Java_org_dolphinemu_dolphinemu_features_input_model_controlleremu_EmulatedController_updateSingleControlReference(
JNIEnv* env, jobject obj, jobject control_reference) JNIEnv* env, jobject obj, jobject control_reference)
{ {
return EmulatedControllerFromJava(env, obj)->UpdateSingleControlReference( ControllerEmu::EmulatedController* controller = EmulatedControllerFromJava(env, obj);
g_controller_interface, ControlReferenceFromJava(env, control_reference)); controller->GetConfig()->GenerateControllerTextures();
return controller->UpdateSingleControlReference(g_controller_interface,
ControlReferenceFromJava(env, control_reference));
} }
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
@ -100,6 +102,7 @@ Java_org_dolphinemu_dolphinemu_features_input_model_controlleremu_EmulatedContro
controller->LoadDefaults(g_controller_interface); controller->LoadDefaults(g_controller_interface);
controller->UpdateReferences(g_controller_interface); controller->UpdateReferences(g_controller_interface);
controller->GetConfig()->GenerateControllerTextures();
} }
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
@ -113,6 +116,7 @@ Java_org_dolphinemu_dolphinemu_features_input_model_controlleremu_EmulatedContro
controller->LoadConfig(&section); controller->LoadConfig(&section);
controller->UpdateReferences(g_controller_interface); controller->UpdateReferences(g_controller_interface);
controller->GetConfig()->GenerateControllerTextures();
} }
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
@ -126,6 +130,7 @@ Java_org_dolphinemu_dolphinemu_features_input_model_controlleremu_EmulatedContro
controller->LoadConfig(ini.GetOrCreateSection("Profile")); controller->LoadConfig(ini.GetOrCreateSection("Profile"));
controller->UpdateReferences(g_controller_interface); controller->UpdateReferences(g_controller_interface);
controller->GetConfig()->GenerateControllerTextures();
} }
JNIEXPORT void JNICALL JNIEXPORT void JNICALL

View file

@ -550,7 +550,7 @@ bool CBoot::BootUp(Core::System& system, const Core::CPUThreadGuard& guard,
if (!EmulatedBS2(system, guard, system.IsWii(), *volume, riivolution_patches)) if (!EmulatedBS2(system, guard, system.IsWii(), *volume, riivolution_patches))
return false; return false;
SConfig::OnNewTitleLoad(guard); SConfig::OnTitleDirectlyBooted(guard);
return true; return true;
} }
@ -601,7 +601,7 @@ bool CBoot::BootUp(Core::System& system, const Core::CPUThreadGuard& guard,
return false; return false;
} }
SConfig::OnNewTitleLoad(guard); SConfig::OnTitleDirectlyBooted(guard);
ppc_state.pc = executable.reader->GetEntryPoint(); ppc_state.pc = executable.reader->GetEntryPoint();
@ -621,7 +621,7 @@ bool CBoot::BootUp(Core::System& system, const Core::CPUThreadGuard& guard,
if (!Boot_WiiWAD(system, wad)) if (!Boot_WiiWAD(system, wad))
return false; return false;
SConfig::OnNewTitleLoad(guard); SConfig::OnTitleDirectlyBooted(guard);
return true; return true;
} }
@ -631,7 +631,7 @@ bool CBoot::BootUp(Core::System& system, const Core::CPUThreadGuard& guard,
if (!BootNANDTitle(system, nand_title.id)) if (!BootNANDTitle(system, nand_title.id))
return false; return false;
SConfig::OnNewTitleLoad(guard); SConfig::OnTitleDirectlyBooted(guard);
return true; return true;
} }
@ -657,7 +657,7 @@ bool CBoot::BootUp(Core::System& system, const Core::CPUThreadGuard& guard,
ipl.disc->auto_disc_change_paths); ipl.disc->auto_disc_change_paths);
} }
SConfig::OnNewTitleLoad(guard); SConfig::OnTitleDirectlyBooted(guard);
return true; return true;
} }

View file

@ -44,8 +44,11 @@
#include "Core/HLE/HLE.h" #include "Core/HLE/HLE.h"
#include "Core/HW/DVD/DVDInterface.h" #include "Core/HW/DVD/DVDInterface.h"
#include "Core/HW/EXI/EXI_Device.h" #include "Core/HW/EXI/EXI_Device.h"
#include "Core/HW/GCKeyboard.h"
#include "Core/HW/GCPad.h"
#include "Core/HW/SI/SI.h" #include "Core/HW/SI/SI.h"
#include "Core/HW/SI/SI_Device.h" #include "Core/HW/SI/SI_Device.h"
#include "Core/HW/Wiimote.h"
#include "Core/Host.h" #include "Core/Host.h"
#include "Core/IOS/ES/ES.h" #include "Core/IOS/ES/ES.h"
#include "Core/IOS/ES/Formats.h" #include "Core/IOS/ES/Formats.h"
@ -239,7 +242,20 @@ void SConfig::SetRunningGameMetadata(const std::string& game_id, const std::stri
DolphinAnalytics::Instance().ReportGameStart(); DolphinAnalytics::Instance().ReportGameStart();
} }
void SConfig::OnNewTitleLoad(const Core::CPUThreadGuard& guard) void SConfig::OnESTitleChanged()
{
auto& system = Core::System::GetInstance();
Pad::LoadConfig();
Keyboard::LoadConfig();
if (system.IsWii() && !Config::Get(Config::MAIN_BLUETOOTH_PASSTHROUGH_ENABLED))
{
Wiimote::LoadConfig();
}
ReloadTextures(system);
}
void SConfig::OnTitleDirectlyBooted(const Core::CPUThreadGuard& guard)
{ {
auto& system = guard.GetSystem(); auto& system = guard.GetSystem();
if (!Core::IsRunningOrStarting(system)) if (!Core::IsRunningOrStarting(system))
@ -253,9 +269,27 @@ void SConfig::OnNewTitleLoad(const Core::CPUThreadGuard& guard)
} }
CBoot::LoadMapFromFilename(guard, ppc_symbol_db); CBoot::LoadMapFromFilename(guard, ppc_symbol_db);
HLE::Reload(system); HLE::Reload(system);
PatchEngine::Reload(system); PatchEngine::Reload(system);
HiresTexture::Update();
WC24PatchEngine::Reload(); WC24PatchEngine::Reload();
// Note: Wii is handled by ES title change
if (!system.IsWii())
{
ReloadTextures(system);
}
}
void SConfig::ReloadTextures(Core::System& system)
{
Pad::GenerateDynamicInputTextures();
Keyboard::GenerateDynamicInputTextures();
if (system.IsWii() && !Config::Get(Config::MAIN_BLUETOOTH_PASSTHROUGH_ENABLED))
{
Wiimote::GenerateDynamicInputTextures();
}
HiresTexture::Update();
} }
void SConfig::LoadDefaults() void SConfig::LoadDefaults()

View file

@ -73,9 +73,15 @@ struct SConfig
void SetRunningGameMetadata(const DiscIO::Volume& volume, const DiscIO::Partition& partition); void SetRunningGameMetadata(const DiscIO::Volume& volume, const DiscIO::Partition& partition);
void SetRunningGameMetadata(const IOS::ES::TMDReader& tmd, DiscIO::Platform platform); void SetRunningGameMetadata(const IOS::ES::TMDReader& tmd, DiscIO::Platform platform);
void SetRunningGameMetadata(const std::string& game_id); void SetRunningGameMetadata(const std::string& game_id);
// Reloads title-specific map files, patches, custom textures, etc.
// This should only be called after the new title has been loaded into memory. // Triggered when Dolphin loads a title directly
static void OnNewTitleLoad(const Core::CPUThreadGuard& guard); // Reloads title-specific map files, patches, etc.
static void OnTitleDirectlyBooted(const Core::CPUThreadGuard& guard);
// Direct title change from ES (Wii system)
// Wii titles will still hit OnTitleDirectlyBooted
// but GC titles will never see this call
static void OnESTitleChanged();
void LoadDefaults(); void LoadDefaults();
static std::string MakeGameID(std::string_view file_name); static std::string MakeGameID(std::string_view file_name);
@ -112,6 +118,8 @@ private:
SConfig(); SConfig();
~SConfig(); ~SConfig();
static void ReloadTextures(Core::System& system);
void SetRunningGameMetadata(const std::string& game_id, const std::string& gametdb_id, void SetRunningGameMetadata(const std::string& game_id, const std::string& gametdb_id,
u64 title_id, u16 revision, DiscIO::Region region); u64 title_id, u16 revision, DiscIO::Region region);

View file

@ -524,11 +524,7 @@ static void EmuThread(Core::System& system, std::unique_ptr<BootParameters> boot
} }
}}; }};
// Load Wiimotes - only if we are booting in Wii mode // Wiimote input config is loaded in OnESTitleChanged
if (system.IsWii() && !Config::Get(Config::MAIN_BLUETOOTH_PASSTHROUGH_ENABLED))
{
Wiimote::LoadConfig();
}
FreeLook::LoadInputConfig(); FreeLook::LoadInputConfig();

View file

@ -49,6 +49,11 @@ void LoadConfig()
s_config.LoadConfig(); s_config.LoadConfig();
} }
void GenerateDynamicInputTextures()
{
s_config.GenerateControllerTextures();
}
ControllerEmu::ControlGroup* GetGroup(int port, KeyboardGroup group) ControllerEmu::ControlGroup* GetGroup(int port, KeyboardGroup group)
{ {
return static_cast<GCKeyboard*>(s_config.GetController(port))->GetGroup(group); return static_cast<GCKeyboard*>(s_config.GetController(port))->GetGroup(group);

View file

@ -19,6 +19,7 @@ namespace Keyboard
void Shutdown(); void Shutdown();
void Initialize(); void Initialize();
void LoadConfig(); void LoadConfig();
void GenerateDynamicInputTextures();
InputConfig* GetConfig(); InputConfig* GetConfig();
ControllerEmu::ControlGroup* GetGroup(int port, KeyboardGroup group); ControllerEmu::ControlGroup* GetGroup(int port, KeyboardGroup group);

View file

@ -46,6 +46,11 @@ void LoadConfig()
s_config.LoadConfig(); s_config.LoadConfig();
} }
void GenerateDynamicInputTextures()
{
s_config.GenerateControllerTextures();
}
bool IsInitialized() bool IsInitialized()
{ {
return !s_config.ControllersNeedToBeCreated(); return !s_config.ControllersNeedToBeCreated();

View file

@ -20,6 +20,7 @@ namespace Pad
void Shutdown(); void Shutdown();
void Initialize(); void Initialize();
void LoadConfig(); void LoadConfig();
void GenerateDynamicInputTextures();
bool IsInitialized(); bool IsInitialized();
InputConfig* GetConfig(); InputConfig* GetConfig();

View file

@ -211,6 +211,11 @@ void LoadConfig()
s_last_connect_request_counter.fill(0); s_last_connect_request_counter.fill(0);
} }
void GenerateDynamicInputTextures()
{
s_config.GenerateControllerTextures();
}
void Resume() void Resume()
{ {
WiimoteReal::Resume(); WiimoteReal::Resume();

View file

@ -78,6 +78,7 @@ void Shutdown();
void Initialize(InitializeMode init_mode); void Initialize(InitializeMode init_mode);
void ResetAllWiimotes(); void ResetAllWiimotes();
void LoadConfig(); void LoadConfig();
void GenerateDynamicInputTextures();
void Resume(); void Resume();
void Pause(); void Pause();

View file

@ -205,6 +205,7 @@ void TitleContext::Update(const ES::TMDReader& tmd_, const ES::TicketReader& tic
if (first_change) if (first_change)
{ {
SConfig::GetInstance().SetRunningGameMetadata(tmd, platform); SConfig::GetInstance().SetRunningGameMetadata(tmd, platform);
SConfig::GetInstance().OnESTitleChanged();
first_change = false; first_change = false;
} }
} }

View file

@ -952,7 +952,7 @@ static void FinishPPCBootstrap(Core::System& system, u64 userdata, s64 cycles_la
ASSERT(Core::IsCPUThread()); ASSERT(Core::IsCPUThread());
Core::CPUThreadGuard guard(system); Core::CPUThreadGuard guard(system);
SConfig::OnNewTitleLoad(guard); SConfig::OnTitleDirectlyBooted(guard);
INFO_LOG_FMT(IOS, "Bootstrapping done."); INFO_LOG_FMT(IOS, "Bootstrapping done.");
} }

View file

@ -103,7 +103,7 @@ bool Load(Core::System& system)
NOTICE_LOG_FMT(IOS, "IPL ready."); NOTICE_LOG_FMT(IOS, "IPL ready.");
system.SetIsMIOS(true); system.SetIsMIOS(true);
system.GetDVDInterface().UpdateRunningGameMetadata(); system.GetDVDInterface().UpdateRunningGameMetadata();
SConfig::OnNewTitleLoad(guard); SConfig::OnTitleDirectlyBooted(guard);
return true; return true;
} }
} // namespace IOS::HLE::MIOS } // namespace IOS::HLE::MIOS

View file

@ -16,6 +16,7 @@
#include "InputCommon/ControllerEmu/ControllerEmu.h" #include "InputCommon/ControllerEmu/ControllerEmu.h"
#include "InputCommon/ControllerInterface/ControllerInterface.h" #include "InputCommon/ControllerInterface/ControllerInterface.h"
#include "InputCommon/ControllerInterface/MappingCommon.h" #include "InputCommon/ControllerInterface/MappingCommon.h"
#include "InputCommon/InputConfig.h"
namespace MappingCommon namespace MappingCommon
{ {
@ -135,6 +136,7 @@ public:
m_parent->Save(); m_parent->Save();
m_parent->GetController()->UpdateSingleControlReference(g_controller_interface, m_parent->GetController()->UpdateSingleControlReference(g_controller_interface,
control_reference); control_reference);
m_parent->GetController()->GetConfig()->GenerateControllerTextures();
} }
void UpdateInputDetectionStartTimer() void UpdateInputDetectionStartTimer()

View file

@ -322,6 +322,7 @@ void MappingWindow::OnLoadProfilePressed()
m_controller->LoadConfig(ini.GetOrCreateSection("Profile")); m_controller->LoadConfig(ini.GetOrCreateSection("Profile"));
m_controller->UpdateReferences(g_controller_interface); m_controller->UpdateReferences(g_controller_interface);
m_controller->GetConfig()->GenerateControllerTextures();
const auto lock = GetController()->GetStateLock(); const auto lock = GetController()->GetStateLock();
emit ConfigChanged(); emit ConfigChanged();
@ -561,6 +562,7 @@ void MappingWindow::OnDefaultFieldsPressed()
{ {
m_controller->LoadDefaults(g_controller_interface); m_controller->LoadDefaults(g_controller_interface);
m_controller->UpdateReferences(g_controller_interface); m_controller->UpdateReferences(g_controller_interface);
m_controller->GetConfig()->GenerateControllerTextures();
const auto lock = GetController()->GetStateLock(); const auto lock = GetController()->GetStateLock();
emit ConfigChanged(); emit ConfigChanged();
@ -578,6 +580,7 @@ void MappingWindow::OnClearFieldsPressed()
m_controller->SetDefaultDevice(default_device); m_controller->SetDefaultDevice(default_device);
m_controller->UpdateReferences(g_controller_interface); m_controller->UpdateReferences(g_controller_interface);
m_controller->GetConfig()->GenerateControllerTextures();
const auto lock = GetController()->GetStateLock(); const auto lock = GetController()->GetStateLock();
emit ConfigChanged(); emit ConfigChanged();

View file

@ -12,6 +12,7 @@
#include "Core/Core.h" #include "Core/Core.h"
#include "InputCommon/DynamicInputTextures/DITConfiguration.h" #include "InputCommon/DynamicInputTextures/DITConfiguration.h"
#include "InputCommon/ImageOperations.h"
#include "VideoCommon/HiresTextures.h" #include "VideoCommon/HiresTextures.h"
#include "VideoCommon/TextureCacheBase.h" #include "VideoCommon/TextureCacheBase.h"
@ -42,9 +43,34 @@ void DynamicInputTextureManager::Load()
void DynamicInputTextureManager::GenerateTextures(const Common::IniFile& file, void DynamicInputTextureManager::GenerateTextures(const Common::IniFile& file,
const std::vector<std::string>& controller_names) const std::vector<std::string>& controller_names)
{ {
DynamicInputTextures::OutputDetails output;
for (const auto& configuration : m_configuration) for (const auto& configuration : m_configuration)
{ {
(void)configuration.GenerateTextures(file, controller_names); configuration.GenerateTextures(file, controller_names, &output);
}
const std::string& game_id = SConfig::GetInstance().GetGameID();
for (const auto& [generated_folder_name, images] : output)
{
const auto hi_res_folder = File::GetUserPath(D_HIRESTEXTURES_IDX) + generated_folder_name;
if (!File::IsDirectory(hi_res_folder))
{
File::CreateDir(hi_res_folder);
}
const auto game_id_folder = hi_res_folder + DIR_SEP + "gameids";
if (!File::IsDirectory(game_id_folder))
{
File::CreateDir(game_id_folder);
}
File::CreateEmptyFile(game_id_folder + DIR_SEP + game_id + ".txt");
for (const auto& [image_name, image] : images)
{
WriteImage(hi_res_folder + DIR_SEP + image_name, image);
}
} }
} }
} // namespace InputCommon } // namespace InputCommon

View file

@ -10,7 +10,6 @@
#include <picojson.h> #include <picojson.h>
#include "Common/CommonPaths.h" #include "Common/CommonPaths.h"
#include "Common/FileUtil.h"
#include "Common/IniFile.h" #include "Common/IniFile.h"
#include "Common/JsonUtil.h" #include "Common/JsonUtil.h"
#include "Common/Logging/Log.h" #include "Common/Logging/Log.h"
@ -68,26 +67,30 @@ Configuration::Configuration(const std::string& json_path)
Configuration::~Configuration() = default; Configuration::~Configuration() = default;
bool Configuration::GenerateTextures(const Common::IniFile& file, void Configuration::GenerateTextures(const Common::IniFile& file,
const std::vector<std::string>& controller_names) const const std::vector<std::string>& controller_names,
OutputDetails* output) const
{ {
bool any_dirty = false;
for (const auto& texture_data : m_dynamic_input_textures) for (const auto& texture_data : m_dynamic_input_textures)
{ {
any_dirty |= GenerateTexture(file, controller_names, texture_data); GenerateTexture(file, controller_names, texture_data, output);
} }
return any_dirty;
} }
bool Configuration::GenerateTexture(const Common::IniFile& file, void Configuration::GenerateTexture(const Common::IniFile& file,
const std::vector<std::string>& controller_names, const std::vector<std::string>& controller_names,
const Data& texture_data) const const Data& texture_data, OutputDetails* output) const
{ {
// Two copies of the loaded texture // Two copies of the loaded texture
// The first one is used as a fallback if a key or device isn't mapped // The first one is used as a fallback if a key or device isn't mapped
// the second one is used as the final image to write to the textures directory // the second one is used as the final image to write to the textures directory
const auto original_image = LoadImage(m_base_path + texture_data.m_image_name); const auto original_image = LoadImage(m_base_path + texture_data.m_image_name);
if (!original_image)
{
ERROR_LOG_FMT(VIDEO, "Failed to load image '{}' needed for dynamic input texture generation",
texture_data.m_image_name);
return;
}
auto image_to_write = original_image; auto image_to_write = original_image;
bool dirty = false; bool dirty = false;
@ -179,25 +182,8 @@ bool Configuration::GenerateTexture(const Common::IniFile& file,
if (dirty) if (dirty)
{ {
const std::string& game_id = SConfig::GetInstance().GetGameID(); (*output)[texture_data.m_generated_folder_name][texture_data.m_hires_texture_name] =
const auto hi_res_folder = std::move(*image_to_write);
File::GetUserPath(D_HIRESTEXTURES_IDX) + texture_data.m_generated_folder_name;
if (!File::IsDirectory(hi_res_folder))
{
File::CreateDir(hi_res_folder);
} }
WriteImage(hi_res_folder + DIR_SEP + texture_data.m_hires_texture_name, *image_to_write);
const auto game_id_folder = hi_res_folder + DIR_SEP + "gameids";
if (!File::IsDirectory(game_id_folder))
{
File::CreateDir(game_id_folder);
}
File::CreateEmptyFile(game_id_folder + DIR_SEP + game_id + ".txt");
return true;
}
return false;
} }
} // namespace InputCommon::DynamicInputTextures } // namespace InputCommon::DynamicInputTextures

View file

@ -3,11 +3,13 @@
#pragma once #pragma once
#include <map>
#include <string> #include <string>
#include <vector> #include <vector>
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "InputCommon/DynamicInputTextures/DITData.h" #include "InputCommon/DynamicInputTextures/DITData.h"
#include "InputCommon/ImageOperations.h"
namespace Common namespace Common
{ {
@ -16,18 +18,21 @@ class IniFile;
namespace InputCommon::DynamicInputTextures namespace InputCommon::DynamicInputTextures
{ {
// Output folder name to image name to image data
using OutputDetails = std::map<std::string, std::map<std::string, ImagePixelData>>;
class Configuration class Configuration
{ {
public: public:
explicit Configuration(const std::string& json_path); explicit Configuration(const std::string& json_path);
~Configuration(); ~Configuration();
bool GenerateTextures(const Common::IniFile& file, void GenerateTextures(const Common::IniFile& file,
const std::vector<std::string>& controller_names) const; const std::vector<std::string>& controller_names,
OutputDetails* output) const;
private: private:
bool GenerateTexture(const Common::IniFile& file, void GenerateTexture(const Common::IniFile& file,
const std::vector<std::string>& controller_names, const std::vector<std::string>& controller_names, const Data& texture_data,
const Data& texture_data) const; OutputDetails* output) const;
std::vector<Data> m_dynamic_input_textures; std::vector<Data> m_dynamic_input_textures;
std::string m_base_path; std::string m_base_path;

View file

@ -35,8 +35,6 @@ bool InputConfig::LoadConfig()
static constexpr std::array<std::string_view, MAX_BBMOTES> num = {"1", "2", "3", "4", "BB"}; static constexpr std::array<std::string_view, MAX_BBMOTES> num = {"1", "2", "3", "4", "BB"};
std::string profile[MAX_BBMOTES]; std::string profile[MAX_BBMOTES];
m_dynamic_input_tex_config_manager.Load();
if (SConfig::GetInstance().GetGameID() != "00000000") if (SConfig::GetInstance().GetGameID() != "00000000")
{ {
const std::string profile_directory = GetUserProfileDirectoryPath(); const std::string profile_directory = GetUserProfileDirectoryPath();
@ -102,8 +100,6 @@ bool InputConfig::LoadConfig()
// Next profile // Next profile
n++; n++;
} }
m_dynamic_input_tex_config_manager.GenerateTextures(inifile, controller_names);
return true; return true;
} }
else else
@ -139,8 +135,6 @@ void InputConfig::SaveConfig()
controller_names.push_back(controller->GetName()); controller_names.push_back(controller->GetName());
} }
m_dynamic_input_tex_config_manager.GenerateTextures(inifile, controller_names);
inifile.Save(ini_filename); inifile.Save(ini_filename);
} }
@ -210,6 +204,8 @@ bool InputConfig::IsControllerControlledByGamepadDevice(int index) const
void InputConfig::GenerateControllerTextures(const Common::IniFile& file) void InputConfig::GenerateControllerTextures(const Common::IniFile& file)
{ {
m_dynamic_input_tex_config_manager.Load();
std::vector<std::string> controller_names; std::vector<std::string> controller_names;
for (auto& controller : m_controllers) for (auto& controller : m_controllers)
{ {
@ -218,3 +214,18 @@ void InputConfig::GenerateControllerTextures(const Common::IniFile& file)
m_dynamic_input_tex_config_manager.GenerateTextures(file, controller_names); m_dynamic_input_tex_config_manager.GenerateTextures(file, controller_names);
} }
void InputConfig::GenerateControllerTextures()
{
const std::string ini_filename = File::GetUserPath(D_CONFIG_IDX) + m_ini_name + ".ini";
Common::IniFile inifile;
inifile.Load(ini_filename);
for (auto& controller : m_controllers)
{
controller->SaveConfig(inifile.GetOrCreateSection(controller->GetName()));
}
GenerateControllerTextures(inifile);
}

View file

@ -55,6 +55,7 @@ public:
void UnregisterHotplugCallback(); void UnregisterHotplugCallback();
void GenerateControllerTextures(const Common::IniFile& file); void GenerateControllerTextures(const Common::IniFile& file);
void GenerateControllerTextures();
private: private:
ControllerInterface::HotplugCallbackHandle m_hotplug_callback_handle; ControllerInterface::HotplugCallbackHandle m_hotplug_callback_handle;