mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-09-10 19:45:49 +00:00
Merge branch 'master' of https://github.com/dolphin-emu/dolphin into dolphin-emu-maste2r
This commit is contained in:
commit
75a9451d0b
218 changed files with 53095 additions and 44354 deletions
|
@ -302,7 +302,7 @@ public final class NativeLibrary
|
|||
|
||||
public static native int DefaultCPUCore();
|
||||
|
||||
public static native String GetDefaultGraphicsBackendName();
|
||||
public static native String GetDefaultGraphicsBackendConfigName();
|
||||
|
||||
public static native int GetMaxLogLevel();
|
||||
|
||||
|
|
|
@ -789,7 +789,7 @@ enum class BooleanSetting(
|
|||
Settings.FILE_GFX,
|
||||
Settings.SECTION_GFX_HACKS,
|
||||
"EFBAccessEnable",
|
||||
true
|
||||
false
|
||||
),
|
||||
GFX_HACK_EFB_DEFER_INVALIDATION(
|
||||
Settings.FILE_GFX,
|
||||
|
|
|
@ -45,7 +45,7 @@ enum class StringSetting(
|
|||
Settings.FILE_DOLPHIN,
|
||||
Settings.SECTION_INI_CORE,
|
||||
"GFXBackend",
|
||||
NativeLibrary.GetDefaultGraphicsBackendName()
|
||||
NativeLibrary.GetDefaultGraphicsBackendConfigName()
|
||||
),
|
||||
MAIN_DUMP_PATH(Settings.FILE_DOLPHIN, Settings.SECTION_INI_GENERAL, "DumpPath", ""),
|
||||
MAIN_LOAD_PATH(Settings.FILE_DOLPHIN, Settings.SECTION_INI_GENERAL, "LoadPath", ""),
|
||||
|
|
|
@ -62,7 +62,7 @@ bool IsPathAndroidContent(std::string_view uri)
|
|||
std::string OpenModeToAndroid(std::string mode)
|
||||
{
|
||||
// The 'b' specifier is not supported by Android. Since we're on POSIX, it's fine to just skip it.
|
||||
mode.erase(std::remove(mode.begin(), mode.end(), 'b'));
|
||||
std::erase(mode, 'b');
|
||||
|
||||
if (mode == "r")
|
||||
return "r";
|
||||
|
|
|
@ -101,6 +101,10 @@ void Host_PPCSymbolsChanged()
|
|||
{
|
||||
}
|
||||
|
||||
void Host_PPCBreakpointsChanged()
|
||||
{
|
||||
}
|
||||
|
||||
void Host_RefreshDSPDebuggerWindow()
|
||||
{
|
||||
}
|
||||
|
@ -146,7 +150,7 @@ void Host_UpdateDisasmDialog()
|
|||
{
|
||||
}
|
||||
|
||||
void Host_JitCacheCleared()
|
||||
void Host_JitCacheInvalidation()
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -408,9 +412,10 @@ JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_DefaultCPUCo
|
|||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_NativeLibrary_GetDefaultGraphicsBackendName(JNIEnv* env, jclass)
|
||||
Java_org_dolphinemu_dolphinemu_NativeLibrary_GetDefaultGraphicsBackendConfigName(JNIEnv* env,
|
||||
jclass)
|
||||
{
|
||||
return ToJString(env, VideoBackendBase::GetDefaultBackendName());
|
||||
return ToJString(env, VideoBackendBase::GetDefaultBackendConfigName());
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetMaxLogLevel(JNIEnv*, jclass)
|
||||
|
|
|
@ -47,7 +47,7 @@ private:
|
|||
std::array<uint32_t, 36 + sizeof...(ExtraMatches)> _conns;
|
||||
std::optional<V> _val;
|
||||
|
||||
TrieEntry() { std::fill(_conns.begin(), _conns.end(), INVALID_CONN); }
|
||||
TrieEntry() { _conns.fill(INVALID_CONN); }
|
||||
};
|
||||
|
||||
constexpr size_t IndexOf(char c) const
|
||||
|
|
|
@ -98,7 +98,8 @@ std::vector<std::string> DoFileSearch(const std::vector<std::string>& directorie
|
|||
// not because std::filesystem returns duplicates). Also note that this pathname-based uniqueness
|
||||
// isn't as thorough as std::filesystem::equivalent.
|
||||
std::ranges::sort(result);
|
||||
result.erase(std::unique(result.begin(), result.end()), result.end());
|
||||
const auto unique_result = std::ranges::unique(result);
|
||||
result.erase(unique_result.begin(), unique_result.end());
|
||||
|
||||
// Dolphin expects to be able to use "/" (DIR_SEP) everywhere.
|
||||
// std::filesystem uses the OS separator.
|
||||
|
|
|
@ -17,72 +17,72 @@
|
|||
|
||||
namespace Common
|
||||
{
|
||||
SettingsHandler::SettingsHandler() : m_buffer{}, m_position{0}, m_key{INITIAL_SEED}, decoded{""}
|
||||
namespace
|
||||
{
|
||||
// Key used to encrypt/decrypt setting.txt contents
|
||||
constexpr u32 INITIAL_SEED = 0x73B5DBFA;
|
||||
} // namespace
|
||||
|
||||
SettingsWriter::SettingsWriter() : m_buffer{}, m_position{0}, m_key{INITIAL_SEED}
|
||||
{
|
||||
}
|
||||
|
||||
SettingsHandler::SettingsHandler(const Buffer& buffer) : SettingsHandler()
|
||||
{
|
||||
m_buffer = buffer;
|
||||
Decrypt();
|
||||
}
|
||||
|
||||
const SettingsHandler::Buffer& SettingsHandler::GetBytes() const
|
||||
const SettingsBuffer& SettingsWriter::GetBytes() const
|
||||
{
|
||||
return m_buffer;
|
||||
}
|
||||
|
||||
std::string SettingsHandler::GetValue(std::string_view key) const
|
||||
std::string SettingsReader::GetValue(std::string_view key) const
|
||||
{
|
||||
constexpr char delim[] = "\n";
|
||||
std::string toFind = std::string(delim).append(key).append("=");
|
||||
size_t found = decoded.find(toFind);
|
||||
size_t found = m_decoded.find(toFind);
|
||||
|
||||
if (found != std::string_view::npos)
|
||||
{
|
||||
size_t delimFound = decoded.find(delim, found + toFind.length());
|
||||
size_t delimFound = m_decoded.find(delim, found + toFind.length());
|
||||
if (delimFound == std::string_view::npos)
|
||||
delimFound = decoded.length() - 1;
|
||||
return decoded.substr(found + toFind.length(), delimFound - (found + toFind.length()));
|
||||
delimFound = m_decoded.length() - 1;
|
||||
return m_decoded.substr(found + toFind.length(), delimFound - (found + toFind.length()));
|
||||
}
|
||||
else
|
||||
{
|
||||
toFind = std::string(key).append("=");
|
||||
found = decoded.find(toFind);
|
||||
found = m_decoded.find(toFind);
|
||||
if (found == 0)
|
||||
{
|
||||
size_t delimFound = decoded.find(delim, found + toFind.length());
|
||||
size_t delimFound = m_decoded.find(delim, found + toFind.length());
|
||||
if (delimFound == std::string_view::npos)
|
||||
delimFound = decoded.length() - 1;
|
||||
return decoded.substr(found + toFind.length(), delimFound - (found + toFind.length()));
|
||||
delimFound = m_decoded.length() - 1;
|
||||
return m_decoded.substr(found + toFind.length(), delimFound - (found + toFind.length()));
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
void SettingsHandler::Decrypt()
|
||||
SettingsReader::SettingsReader(const SettingsBuffer& buffer) : m_decoded{""}
|
||||
{
|
||||
while (m_position < m_buffer.size())
|
||||
u32 key = INITIAL_SEED;
|
||||
for (u32 position = 0; position < buffer.size(); ++position)
|
||||
{
|
||||
decoded.push_back((u8)(m_buffer[m_position] ^ m_key));
|
||||
m_position++;
|
||||
m_key = (m_key >> 31) | (m_key << 1);
|
||||
m_decoded.push_back((u8)(buffer[position] ^ key));
|
||||
key = (key >> 31) | (key << 1);
|
||||
}
|
||||
|
||||
// The decoded data normally uses CRLF line endings, but occasionally
|
||||
// (see the comment in WriteLine), lines can be separated by CRLFLF.
|
||||
// To handle this, we remove every CR and treat LF as the line ending.
|
||||
// (We ignore empty lines.)
|
||||
std::erase(decoded, '\x0d');
|
||||
std::erase(m_decoded, '\x0d');
|
||||
}
|
||||
|
||||
void SettingsHandler::AddSetting(std::string_view key, std::string_view value)
|
||||
void SettingsWriter::AddSetting(std::string_view key, std::string_view value)
|
||||
{
|
||||
WriteLine(fmt::format("{}={}\r\n", key, value));
|
||||
}
|
||||
|
||||
void SettingsHandler::WriteLine(std::string_view str)
|
||||
void SettingsWriter::WriteLine(std::string_view str)
|
||||
{
|
||||
const u32 old_position = m_position;
|
||||
const u32 old_key = m_key;
|
||||
|
@ -106,7 +106,7 @@ void SettingsHandler::WriteLine(std::string_view str)
|
|||
}
|
||||
}
|
||||
|
||||
void SettingsHandler::WriteByte(u8 b)
|
||||
void SettingsWriter::WriteByte(u8 b)
|
||||
{
|
||||
if (m_position >= m_buffer.size())
|
||||
return;
|
||||
|
@ -116,7 +116,7 @@ void SettingsHandler::WriteByte(u8 b)
|
|||
m_key = (m_key >> 31) | (m_key << 1);
|
||||
}
|
||||
|
||||
std::string SettingsHandler::GenerateSerialNumber()
|
||||
std::string SettingsWriter::GenerateSerialNumber()
|
||||
{
|
||||
const std::time_t t = std::time(nullptr);
|
||||
|
||||
|
|
|
@ -13,34 +13,35 @@
|
|||
|
||||
namespace Common
|
||||
{
|
||||
class SettingsHandler
|
||||
using SettingsBuffer = std::array<u8, 0x100>;
|
||||
|
||||
class SettingsWriter
|
||||
{
|
||||
public:
|
||||
enum
|
||||
{
|
||||
SETTINGS_SIZE = 0x100,
|
||||
// Key used to encrypt/decrypt setting.txt contents
|
||||
INITIAL_SEED = 0x73B5DBFA
|
||||
};
|
||||
|
||||
using Buffer = std::array<u8, SETTINGS_SIZE>;
|
||||
SettingsHandler();
|
||||
explicit SettingsHandler(const Buffer& buffer);
|
||||
SettingsWriter();
|
||||
|
||||
void AddSetting(std::string_view key, std::string_view value);
|
||||
|
||||
const Buffer& GetBytes() const;
|
||||
std::string GetValue(std::string_view key) const;
|
||||
const SettingsBuffer& GetBytes() const;
|
||||
|
||||
static std::string GenerateSerialNumber();
|
||||
|
||||
private:
|
||||
void Decrypt();
|
||||
void WriteLine(std::string_view str);
|
||||
void WriteByte(u8 b);
|
||||
|
||||
std::array<u8, SETTINGS_SIZE> m_buffer;
|
||||
SettingsBuffer m_buffer;
|
||||
u32 m_position, m_key;
|
||||
std::string decoded;
|
||||
};
|
||||
|
||||
class SettingsReader
|
||||
{
|
||||
public:
|
||||
explicit SettingsReader(const SettingsBuffer& buffer);
|
||||
|
||||
std::string GetValue(std::string_view key) const;
|
||||
|
||||
private:
|
||||
std::string m_decoded;
|
||||
};
|
||||
} // namespace Common
|
||||
|
|
|
@ -44,6 +44,7 @@ struct Symbol
|
|||
|
||||
std::string name;
|
||||
std::string function_name; // stripped function name
|
||||
std::string object_name; // name of object/source file symbol belongs to
|
||||
std::vector<SCall> callers; // addresses of functions that call this function
|
||||
std::vector<SCall> calls; // addresses of functions that are called by this function
|
||||
u32 hash = 0; // use for HLE function finding
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "Common/Assert.h"
|
||||
#include "Common/BitUtils.h"
|
||||
#include "Common/CommonPaths.h"
|
||||
#include "Common/Config/Config.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/IOFile.h"
|
||||
#include "Common/Image.h"
|
||||
|
@ -23,8 +24,12 @@
|
|||
#include "Common/ScopeGuard.h"
|
||||
#include "Common/Version.h"
|
||||
#include "Common/WorkQueueThread.h"
|
||||
#include "Core/ActionReplay.h"
|
||||
#include "Core/Config/AchievementSettings.h"
|
||||
#include "Core/Config/FreeLookSettings.h"
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/GeckoCode.h"
|
||||
#include "Core/HW/Memmap.h"
|
||||
#include "Core/HW/VideoInterface.h"
|
||||
#include "Core/PatchEngine.h"
|
||||
|
@ -62,7 +67,8 @@ void AchievementManager::Init()
|
|||
[](const char* message, const rc_client_t* client) {
|
||||
INFO_LOG_FMT(ACHIEVEMENTS, "{}", message);
|
||||
});
|
||||
rc_client_set_hardcore_enabled(m_client, Config::Get(Config::RA_HARDCORE_ENABLED));
|
||||
Config::AddConfigChangedCallback([this] { SetHardcoreMode(); });
|
||||
SetHardcoreMode();
|
||||
m_queue.Reset("AchievementManagerQueue", [](const std::function<void()>& func) { func(); });
|
||||
m_image_queue.Reset("AchievementManagerImageQueue",
|
||||
[](const std::function<void()>& func) { func(); });
|
||||
|
@ -361,6 +367,12 @@ std::recursive_mutex& AchievementManager::GetLock()
|
|||
void AchievementManager::SetHardcoreMode()
|
||||
{
|
||||
rc_client_set_hardcore_enabled(m_client, Config::Get(Config::RA_HARDCORE_ENABLED));
|
||||
if (Config::Get(Config::RA_HARDCORE_ENABLED))
|
||||
{
|
||||
if (Config::Get(Config::MAIN_EMULATION_SPEED) < 1.0f)
|
||||
Config::SetBaseOrCurrent(Config::MAIN_EMULATION_SPEED, 1.0f);
|
||||
Config::SetBaseOrCurrent(Config::FREE_LOOK_ENABLED, false);
|
||||
}
|
||||
}
|
||||
|
||||
bool AchievementManager::IsHardcoreModeActive() const
|
||||
|
@ -373,10 +385,11 @@ bool AchievementManager::IsHardcoreModeActive() const
|
|||
return rc_client_is_processing_required(m_client);
|
||||
}
|
||||
|
||||
void AchievementManager::FilterApprovedPatches(std::vector<PatchEngine::Patch>& patches,
|
||||
const std::string& game_ini_id) const
|
||||
template <typename T>
|
||||
void AchievementManager::FilterApprovedIni(std::vector<T>& codes,
|
||||
const std::string& game_ini_id) const
|
||||
{
|
||||
if (patches.empty())
|
||||
if (codes.empty())
|
||||
{
|
||||
// There's nothing to verify, so let's save ourselves some work
|
||||
return;
|
||||
|
@ -387,46 +400,120 @@ void AchievementManager::FilterApprovedPatches(std::vector<PatchEngine::Patch>&
|
|||
if (!IsHardcoreModeActive())
|
||||
return;
|
||||
|
||||
// Approved codes list failed to hash
|
||||
if (!m_ini_root->is<picojson::value::object>())
|
||||
{
|
||||
codes.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& code : codes)
|
||||
{
|
||||
if (code.enabled && !CheckApprovedCode(code, game_ini_id))
|
||||
code.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool AchievementManager::CheckApprovedCode(const T& code, const std::string& game_ini_id) const
|
||||
{
|
||||
if (!IsHardcoreModeActive())
|
||||
return true;
|
||||
|
||||
// Approved codes list failed to hash
|
||||
if (!m_ini_root->is<picojson::value::object>())
|
||||
return false;
|
||||
|
||||
const bool known_id = m_ini_root->contains(game_ini_id);
|
||||
|
||||
auto patch_itr = patches.begin();
|
||||
while (patch_itr != patches.end())
|
||||
INFO_LOG_FMT(ACHIEVEMENTS, "Verifying code {}", code.name);
|
||||
|
||||
bool verified = false;
|
||||
|
||||
if (known_id)
|
||||
{
|
||||
INFO_LOG_FMT(ACHIEVEMENTS, "Verifying patch {}", patch_itr->name);
|
||||
auto digest = GetCodeHash(code);
|
||||
|
||||
bool verified = false;
|
||||
|
||||
if (known_id)
|
||||
{
|
||||
auto context = Common::SHA1::CreateContext();
|
||||
context->Update(Common::BitCastToArray<u8>(static_cast<u64>(patch_itr->entries.size())));
|
||||
for (const auto& entry : patch_itr->entries)
|
||||
{
|
||||
context->Update(Common::BitCastToArray<u8>(entry.type));
|
||||
context->Update(Common::BitCastToArray<u8>(entry.address));
|
||||
context->Update(Common::BitCastToArray<u8>(entry.value));
|
||||
context->Update(Common::BitCastToArray<u8>(entry.comparand));
|
||||
context->Update(Common::BitCastToArray<u8>(entry.conditional));
|
||||
}
|
||||
auto digest = context->Finish();
|
||||
|
||||
verified = m_ini_root->get(game_ini_id).contains(Common::SHA1::DigestToString(digest));
|
||||
}
|
||||
|
||||
if (!verified)
|
||||
{
|
||||
patch_itr = patches.erase(patch_itr);
|
||||
OSD::AddMessage(
|
||||
fmt::format("Failed to verify patch {} from file {}.", patch_itr->name, game_ini_id),
|
||||
OSD::Duration::VERY_LONG, OSD::Color::RED);
|
||||
OSD::AddMessage("Disable hardcore mode to enable this patch.", OSD::Duration::VERY_LONG,
|
||||
OSD::Color::RED);
|
||||
}
|
||||
else
|
||||
{
|
||||
patch_itr++;
|
||||
}
|
||||
verified = m_ini_root->get(game_ini_id).contains(Common::SHA1::DigestToString(digest));
|
||||
}
|
||||
|
||||
if (!verified)
|
||||
{
|
||||
OSD::AddMessage(fmt::format("Failed to verify code {} from file {}.", code.name, game_ini_id),
|
||||
OSD::Duration::VERY_LONG, OSD::Color::RED);
|
||||
OSD::AddMessage("Disable hardcore mode to enable this code.", OSD::Duration::VERY_LONG,
|
||||
OSD::Color::RED);
|
||||
}
|
||||
return verified;
|
||||
}
|
||||
|
||||
Common::SHA1::Digest AchievementManager::GetCodeHash(const PatchEngine::Patch& patch) const
|
||||
{
|
||||
auto context = Common::SHA1::CreateContext();
|
||||
context->Update(Common::BitCastToArray<u8>(static_cast<u64>(patch.entries.size())));
|
||||
for (const auto& entry : patch.entries)
|
||||
{
|
||||
context->Update(Common::BitCastToArray<u8>(entry.type));
|
||||
context->Update(Common::BitCastToArray<u8>(entry.address));
|
||||
context->Update(Common::BitCastToArray<u8>(entry.value));
|
||||
context->Update(Common::BitCastToArray<u8>(entry.comparand));
|
||||
context->Update(Common::BitCastToArray<u8>(entry.conditional));
|
||||
}
|
||||
return context->Finish();
|
||||
}
|
||||
|
||||
Common::SHA1::Digest AchievementManager::GetCodeHash(const Gecko::GeckoCode& code) const
|
||||
{
|
||||
auto context = Common::SHA1::CreateContext();
|
||||
context->Update(Common::BitCastToArray<u8>(static_cast<u64>(code.codes.size())));
|
||||
for (const auto& entry : code.codes)
|
||||
{
|
||||
context->Update(Common::BitCastToArray<u8>(entry.address));
|
||||
context->Update(Common::BitCastToArray<u8>(entry.data));
|
||||
}
|
||||
return context->Finish();
|
||||
}
|
||||
|
||||
Common::SHA1::Digest AchievementManager::GetCodeHash(const ActionReplay::ARCode& code) const
|
||||
{
|
||||
auto context = Common::SHA1::CreateContext();
|
||||
context->Update(Common::BitCastToArray<u8>(static_cast<u64>(code.ops.size())));
|
||||
for (const auto& entry : code.ops)
|
||||
{
|
||||
context->Update(Common::BitCastToArray<u8>(entry.cmd_addr));
|
||||
context->Update(Common::BitCastToArray<u8>(entry.value));
|
||||
}
|
||||
return context->Finish();
|
||||
}
|
||||
|
||||
void AchievementManager::FilterApprovedPatches(std::vector<PatchEngine::Patch>& patches,
|
||||
const std::string& game_ini_id) const
|
||||
{
|
||||
FilterApprovedIni(patches, game_ini_id);
|
||||
}
|
||||
|
||||
void AchievementManager::FilterApprovedGeckoCodes(std::vector<Gecko::GeckoCode>& codes,
|
||||
const std::string& game_ini_id) const
|
||||
{
|
||||
FilterApprovedIni(codes, game_ini_id);
|
||||
}
|
||||
|
||||
void AchievementManager::FilterApprovedARCodes(std::vector<ActionReplay::ARCode>& codes,
|
||||
const std::string& game_ini_id) const
|
||||
{
|
||||
FilterApprovedIni(codes, game_ini_id);
|
||||
}
|
||||
|
||||
bool AchievementManager::CheckApprovedGeckoCode(const Gecko::GeckoCode& code,
|
||||
const std::string& game_ini_id) const
|
||||
{
|
||||
return CheckApprovedCode(code, game_ini_id);
|
||||
}
|
||||
|
||||
bool AchievementManager::CheckApprovedARCode(const ActionReplay::ARCode& code,
|
||||
const std::string& game_ini_id) const
|
||||
{
|
||||
return CheckApprovedCode(code, game_ini_id);
|
||||
}
|
||||
|
||||
void AchievementManager::SetSpectatorMode()
|
||||
|
|
|
@ -45,6 +45,16 @@ namespace PatchEngine
|
|||
struct Patch;
|
||||
} // namespace PatchEngine
|
||||
|
||||
namespace Gecko
|
||||
{
|
||||
class GeckoCode;
|
||||
} // namespace Gecko
|
||||
|
||||
namespace ActionReplay
|
||||
{
|
||||
struct ARCode;
|
||||
} // namespace ActionReplay
|
||||
|
||||
class AchievementManager
|
||||
{
|
||||
public:
|
||||
|
@ -70,8 +80,8 @@ public:
|
|||
static constexpr std::string_view BLUE = "#0B71C1";
|
||||
static constexpr std::string_view APPROVED_LIST_FILENAME = "ApprovedInis.json";
|
||||
static const inline Common::SHA1::Digest APPROVED_LIST_HASH = {
|
||||
0x50, 0x2F, 0x58, 0x02, 0x94, 0x60, 0x1B, 0x9F, 0x92, 0xC7,
|
||||
0x04, 0x17, 0x50, 0x2E, 0xF3, 0x09, 0x8C, 0x8C, 0xD6, 0xC0};
|
||||
0xA4, 0x98, 0x59, 0x23, 0x10, 0x56, 0x45, 0x30, 0xA9, 0xC5,
|
||||
0x68, 0x5A, 0xB6, 0x47, 0x67, 0xF8, 0xF0, 0x7D, 0x1D, 0x14};
|
||||
|
||||
struct LeaderboardEntry
|
||||
{
|
||||
|
@ -122,11 +132,18 @@ public:
|
|||
void DoIdle();
|
||||
|
||||
std::recursive_mutex& GetLock();
|
||||
void SetHardcoreMode();
|
||||
bool IsHardcoreModeActive() const;
|
||||
void SetGameIniId(const std::string& game_ini_id) { m_game_ini_id = game_ini_id; }
|
||||
|
||||
void FilterApprovedPatches(std::vector<PatchEngine::Patch>& patches,
|
||||
const std::string& game_ini_id) const;
|
||||
void FilterApprovedGeckoCodes(std::vector<Gecko::GeckoCode>& codes,
|
||||
const std::string& game_ini_id) const;
|
||||
void FilterApprovedARCodes(std::vector<ActionReplay::ARCode>& codes,
|
||||
const std::string& game_ini_id) const;
|
||||
bool CheckApprovedGeckoCode(const Gecko::GeckoCode& code, const std::string& game_ini_id) const;
|
||||
bool CheckApprovedARCode(const ActionReplay::ARCode& code, const std::string& game_ini_id) const;
|
||||
|
||||
void SetSpectatorMode();
|
||||
std::string_view GetPlayerDisplayName() const;
|
||||
u32 GetPlayerScore() const;
|
||||
|
@ -181,6 +198,16 @@ private:
|
|||
void* userdata);
|
||||
void DisplayWelcomeMessage();
|
||||
|
||||
void SetHardcoreMode();
|
||||
|
||||
template <typename T>
|
||||
void FilterApprovedIni(std::vector<T>& codes, const std::string& game_ini_id) const;
|
||||
template <typename T>
|
||||
bool CheckApprovedCode(const T& code, const std::string& game_ini_id) const;
|
||||
Common::SHA1::Digest GetCodeHash(const PatchEngine::Patch& patch) const;
|
||||
Common::SHA1::Digest GetCodeHash(const Gecko::GeckoCode& code) const;
|
||||
Common::SHA1::Digest GetCodeHash(const ActionReplay::ARCode& code) const;
|
||||
|
||||
static void LeaderboardEntriesCallback(int result, const char* error_message,
|
||||
rc_client_leaderboard_entry_list_t* list,
|
||||
rc_client_t* client, void* userdata);
|
||||
|
@ -249,11 +276,21 @@ private:
|
|||
|
||||
#include <string>
|
||||
|
||||
namespace ActionReplay
|
||||
{
|
||||
struct ARCode;
|
||||
}
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
class Volume;
|
||||
}
|
||||
|
||||
namespace Gecko
|
||||
{
|
||||
class GeckoCode;
|
||||
}
|
||||
|
||||
class AchievementManager
|
||||
{
|
||||
public:
|
||||
|
@ -265,6 +302,18 @@ public:
|
|||
|
||||
constexpr bool IsHardcoreModeActive() { return false; }
|
||||
|
||||
constexpr bool CheckApprovedGeckoCode(const Gecko::GeckoCode& code,
|
||||
const std::string& game_ini_id)
|
||||
{
|
||||
return true;
|
||||
};
|
||||
|
||||
constexpr bool CheckApprovedARCode(const ActionReplay::ARCode& code,
|
||||
const std::string& game_ini_id)
|
||||
{
|
||||
return true;
|
||||
};
|
||||
|
||||
constexpr void LoadGame(const std::string&, const DiscIO::Volume*) {}
|
||||
|
||||
constexpr void SetBackgroundExecutionAllowed(bool allowed) {}
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
#include "Common/MsgHandler.h"
|
||||
|
||||
#include "Core/ARDecrypt.h"
|
||||
#include "Core/AchievementManager.h"
|
||||
#include "Core/CheatCodes.h"
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/PowerPC/MMU.h"
|
||||
|
@ -112,7 +113,7 @@ struct ARAddr
|
|||
|
||||
// ----------------------
|
||||
// AR Remote Functions
|
||||
void ApplyCodes(std::span<const ARCode> codes)
|
||||
void ApplyCodes(std::span<const ARCode> codes, const std::string& game_id)
|
||||
{
|
||||
if (!Config::AreCheatsEnabled())
|
||||
return;
|
||||
|
@ -121,7 +122,10 @@ void ApplyCodes(std::span<const ARCode> codes)
|
|||
s_disable_logging = false;
|
||||
s_active_codes.clear();
|
||||
std::copy_if(codes.begin(), codes.end(), std::back_inserter(s_active_codes),
|
||||
[](const ARCode& code) { return code.enabled; });
|
||||
[&game_id](const ARCode& code) {
|
||||
return code.enabled &&
|
||||
AchievementManager::GetInstance().CheckApprovedARCode(code, game_id);
|
||||
});
|
||||
s_active_codes.shrink_to_fit();
|
||||
}
|
||||
|
||||
|
@ -169,9 +173,10 @@ void AddCode(ARCode code)
|
|||
}
|
||||
}
|
||||
|
||||
void LoadAndApplyCodes(const Common::IniFile& global_ini, const Common::IniFile& local_ini)
|
||||
void LoadAndApplyCodes(const Common::IniFile& global_ini, const Common::IniFile& local_ini,
|
||||
const std::string& game_id)
|
||||
{
|
||||
ApplyCodes(LoadCodes(global_ini, local_ini));
|
||||
ApplyCodes(LoadCodes(global_ini, local_ini), game_id);
|
||||
}
|
||||
|
||||
// Parses the Action Replay section of a game ini file.
|
||||
|
|
|
@ -45,12 +45,13 @@ struct ARCode
|
|||
|
||||
void RunAllActive(const Core::CPUThreadGuard& cpu_guard);
|
||||
|
||||
void ApplyCodes(std::span<const ARCode> codes);
|
||||
void ApplyCodes(std::span<const ARCode> codes, const std::string& game_id);
|
||||
void SetSyncedCodesAsActive();
|
||||
void UpdateSyncedCodes(std::span<const ARCode> codes);
|
||||
std::vector<ARCode> ApplyAndReturnCodes(std::span<const ARCode> codes);
|
||||
void AddCode(ARCode new_code);
|
||||
void LoadAndApplyCodes(const Common::IniFile& global_ini, const Common::IniFile& local_ini);
|
||||
void LoadAndApplyCodes(const Common::IniFile& global_ini, const Common::IniFile& local_ini,
|
||||
const std::string& game_id);
|
||||
|
||||
std::vector<ARCode> LoadCodes(const Common::IniFile& global_ini, const Common::IniFile& local_ini);
|
||||
void SaveCodes(Common::IniFile* local_ini, std::span<const ARCode> codes);
|
||||
|
|
|
@ -605,7 +605,9 @@ bool CBoot::BootUp(Core::System& system, const Core::CPUThreadGuard& guard,
|
|||
|
||||
ppc_state.pc = executable.reader->GetEntryPoint();
|
||||
|
||||
if (executable.reader->LoadSymbols(guard, system.GetPPCSymbolDB()))
|
||||
const std::string filename = PathToFileName(executable.path);
|
||||
|
||||
if (executable.reader->LoadSymbols(guard, system.GetPPCSymbolDB(), filename))
|
||||
{
|
||||
Host_PPCSymbolsChanged();
|
||||
HLE::PatchFunctions(system);
|
||||
|
|
|
@ -215,7 +215,8 @@ public:
|
|||
virtual bool IsValid() const = 0;
|
||||
virtual bool IsWii() const = 0;
|
||||
virtual bool LoadIntoMemory(Core::System& system, bool only_in_mem1 = false) const = 0;
|
||||
virtual bool LoadSymbols(const Core::CPUThreadGuard& guard, PPCSymbolDB& ppc_symbol_db) const = 0;
|
||||
virtual bool LoadSymbols(const Core::CPUThreadGuard& guard, PPCSymbolDB& ppc_symbol_db,
|
||||
const std::string& filename) const = 0;
|
||||
|
||||
protected:
|
||||
std::vector<u8> m_bytes;
|
||||
|
|
|
@ -371,12 +371,12 @@ bool CBoot::SetupWiiMemory(Core::System& system, IOS::HLE::IOSC::ConsoleType con
|
|||
|
||||
const auto fs = system.GetIOS()->GetFS();
|
||||
{
|
||||
Common::SettingsHandler::Buffer data;
|
||||
Common::SettingsBuffer data;
|
||||
const auto file = fs->OpenFile(IOS::SYSMENU_UID, IOS::SYSMENU_GID, settings_file_path,
|
||||
IOS::HLE::FS::Mode::Read);
|
||||
if (file && file->Read(data.data(), data.size()))
|
||||
{
|
||||
Common::SettingsHandler settings_reader(data);
|
||||
const Common::SettingsReader settings_reader(data);
|
||||
serno = settings_reader.GetValue("SERNO");
|
||||
model = settings_reader.GetValue("MODEL");
|
||||
|
||||
|
@ -413,7 +413,7 @@ bool CBoot::SetupWiiMemory(Core::System& system, IOS::HLE::IOSC::ConsoleType con
|
|||
if (Core::WantsDeterminism())
|
||||
serno = "123456789";
|
||||
else
|
||||
serno = Common::SettingsHandler::GenerateSerialNumber();
|
||||
serno = Common::SettingsWriter::GenerateSerialNumber();
|
||||
INFO_LOG_FMT(BOOT, "No previous serial number found, generated one instead: {}", serno);
|
||||
}
|
||||
else
|
||||
|
@ -421,20 +421,21 @@ bool CBoot::SetupWiiMemory(Core::System& system, IOS::HLE::IOSC::ConsoleType con
|
|||
INFO_LOG_FMT(BOOT, "Using serial number: {}", serno);
|
||||
}
|
||||
|
||||
Common::SettingsHandler gen;
|
||||
gen.AddSetting("AREA", region_setting.area);
|
||||
gen.AddSetting("MODEL", model);
|
||||
gen.AddSetting("DVD", "0");
|
||||
gen.AddSetting("MPCH", "0x7FFE");
|
||||
gen.AddSetting("CODE", region_setting.code);
|
||||
gen.AddSetting("SERNO", serno);
|
||||
gen.AddSetting("VIDEO", region_setting.video);
|
||||
gen.AddSetting("GAME", region_setting.game);
|
||||
Common::SettingsWriter settings_writer;
|
||||
settings_writer.AddSetting("AREA", region_setting.area);
|
||||
settings_writer.AddSetting("MODEL", model);
|
||||
settings_writer.AddSetting("DVD", "0");
|
||||
settings_writer.AddSetting("MPCH", "0x7FFE");
|
||||
settings_writer.AddSetting("CODE", region_setting.code);
|
||||
settings_writer.AddSetting("SERNO", serno);
|
||||
settings_writer.AddSetting("VIDEO", region_setting.video);
|
||||
settings_writer.AddSetting("GAME", region_setting.game);
|
||||
|
||||
constexpr IOS::HLE::FS::Mode rw_mode = IOS::HLE::FS::Mode::ReadWrite;
|
||||
const auto settings_file = fs->CreateAndOpenFile(IOS::SYSMENU_UID, IOS::SYSMENU_GID,
|
||||
settings_file_path, {rw_mode, rw_mode, rw_mode});
|
||||
if (!settings_file || !settings_file->Write(gen.GetBytes().data(), gen.GetBytes().size()))
|
||||
if (!settings_file ||
|
||||
!settings_file->Write(settings_writer.GetBytes().data(), settings_writer.GetBytes().size()))
|
||||
{
|
||||
PanicAlertFmtT("SetupWiiMemory: Can't create setting.txt file");
|
||||
return false;
|
||||
|
@ -443,7 +444,7 @@ bool CBoot::SetupWiiMemory(Core::System& system, IOS::HLE::IOSC::ConsoleType con
|
|||
auto& memory = system.GetMemory();
|
||||
|
||||
// Write the 256 byte setting.txt to memory.
|
||||
memory.CopyToEmu(0x3800, gen.GetBytes().data(), gen.GetBytes().size());
|
||||
memory.CopyToEmu(0x3800, settings_writer.GetBytes().data(), settings_writer.GetBytes().size());
|
||||
|
||||
INFO_LOG_FMT(BOOT, "Setup Wii Memory...");
|
||||
|
||||
|
|
|
@ -27,7 +27,8 @@ public:
|
|||
bool IsAncast() const { return m_is_ancast; }
|
||||
u32 GetEntryPoint() const override { return m_dolheader.entryPoint; }
|
||||
bool LoadIntoMemory(Core::System& system, bool only_in_mem1 = false) const override;
|
||||
bool LoadSymbols(const Core::CPUThreadGuard& guard, PPCSymbolDB& ppc_symbol_db) const override
|
||||
bool LoadSymbols(const Core::CPUThreadGuard& guard, PPCSymbolDB& ppc_symbol_db,
|
||||
const std::string& filename) const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -180,7 +180,8 @@ SectionID ElfReader::GetSectionByName(const char* name, int firstSection) const
|
|||
return -1;
|
||||
}
|
||||
|
||||
bool ElfReader::LoadSymbols(const Core::CPUThreadGuard& guard, PPCSymbolDB& ppc_symbol_db) const
|
||||
bool ElfReader::LoadSymbols(const Core::CPUThreadGuard& guard, PPCSymbolDB& ppc_symbol_db,
|
||||
const std::string& filename) const
|
||||
{
|
||||
bool hasSymbols = false;
|
||||
SectionID sec = GetSectionByName(".symtab");
|
||||
|
@ -218,7 +219,7 @@ bool ElfReader::LoadSymbols(const Core::CPUThreadGuard& guard, PPCSymbolDB& ppc_
|
|||
default:
|
||||
continue;
|
||||
}
|
||||
ppc_symbol_db.AddKnownSymbol(guard, value, size, name, symtype);
|
||||
ppc_symbol_db.AddKnownSymbol(guard, value, size, name, filename, symtype);
|
||||
hasSymbols = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,8 @@ public:
|
|||
u32 GetEntryPoint() const override { return entryPoint; }
|
||||
u32 GetFlags() const { return (u32)(header->e_flags); }
|
||||
bool LoadIntoMemory(Core::System& system, bool only_in_mem1 = false) const override;
|
||||
bool LoadSymbols(const Core::CPUThreadGuard& guard, PPCSymbolDB& ppc_symbol_db) const override;
|
||||
bool LoadSymbols(const Core::CPUThreadGuard& guard, PPCSymbolDB& ppc_symbol_db,
|
||||
const std::string& filename) const override;
|
||||
// TODO: actually check for validity.
|
||||
bool IsValid() const override { return true; }
|
||||
bool IsWii() const override;
|
||||
|
|
|
@ -178,7 +178,7 @@ const Info<int> GFX_STEREO_DEPTH_PERCENTAGE{{System::GFX, "Stereoscopy", "Stereo
|
|||
|
||||
// Graphics.Hacks
|
||||
|
||||
const Info<bool> GFX_HACK_EFB_ACCESS_ENABLE{{System::GFX, "Hacks", "EFBAccessEnable"}, true};
|
||||
const Info<bool> GFX_HACK_EFB_ACCESS_ENABLE{{System::GFX, "Hacks", "EFBAccessEnable"}, false};
|
||||
const Info<bool> GFX_HACK_EFB_DEFER_INVALIDATION{
|
||||
{System::GFX, "Hacks", "EFBAccessDeferInvalidation"}, false};
|
||||
const Info<int> GFX_HACK_EFB_ACCESS_TILE_SIZE{{System::GFX, "Hacks", "EFBAccessTileSize"}, 64};
|
||||
|
|
|
@ -212,7 +212,7 @@ const Info<bool> MAIN_RAM_OVERRIDE_ENABLE{{System::Main, "Core", "RAMOverrideEna
|
|||
const Info<u32> MAIN_MEM1_SIZE{{System::Main, "Core", "MEM1Size"}, Memory::MEM1_SIZE_RETAIL};
|
||||
const Info<u32> MAIN_MEM2_SIZE{{System::Main, "Core", "MEM2Size"}, Memory::MEM2_SIZE_RETAIL};
|
||||
const Info<std::string> MAIN_GFX_BACKEND{{System::Main, "Core", "GFXBackend"},
|
||||
VideoBackendBase::GetDefaultBackendName()};
|
||||
VideoBackendBase::GetDefaultBackendConfigName()};
|
||||
const Info<HSP::HSPDeviceType> MAIN_HSP_DEVICE{{System::Main, "Core", "HSPDevice"},
|
||||
HSP::HSPDeviceType::None};
|
||||
const Info<u32> MAIN_ARAM_EXPANSION_SIZE{{System::Main, "Core", "ARAMExpansionSize"}, 0x400000};
|
||||
|
@ -751,8 +751,7 @@ bool IsDefaultGCIFolderPathConfigured(ExpansionInterface::Slot slot)
|
|||
|
||||
bool AreCheatsEnabled()
|
||||
{
|
||||
return Config::Get(::Config::MAIN_ENABLE_CHEATS) &&
|
||||
!AchievementManager::GetInstance().IsHardcoreModeActive();
|
||||
return Config::Get(::Config::MAIN_ENABLE_CHEATS);
|
||||
}
|
||||
|
||||
bool IsDebuggingEnabled()
|
||||
|
|
|
@ -6,19 +6,14 @@
|
|||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
#include "Common/Config/Config.h"
|
||||
#include "Core/Config/AchievementSettings.h"
|
||||
#include "Core/Config/GraphicsSettings.h"
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/Config/UISettings.h"
|
||||
#include "Core/Config/WiimoteSettings.h"
|
||||
|
||||
namespace ConfigLoaders
|
||||
{
|
||||
bool IsSettingSaveable(const Config::Location& config_location)
|
||||
{
|
||||
static constexpr std::array<Config::System, 3> systems_not_saveable = {
|
||||
Config::System::GCPad, Config::System::WiiPad, Config::System::GCKeyboard};
|
||||
static constexpr std::array systems_not_saveable = {Config::System::GCPad, Config::System::WiiPad,
|
||||
Config::System::GCKeyboard};
|
||||
|
||||
if (std::find(begin(systems_not_saveable), end(systems_not_saveable), config_location.system) ==
|
||||
end(systems_not_saveable))
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "Common/Config/Config.h"
|
||||
#include "Common/FileUtil.h"
|
||||
|
||||
#include "Core/Config/AchievementSettings.h"
|
||||
#include "Core/Config/GraphicsSettings.h"
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/Config/SYSCONFSettings.h"
|
||||
|
@ -33,6 +34,9 @@ public:
|
|||
layer->Set(Config::MAIN_CPU_THREAD, m_settings.cpu_thread);
|
||||
layer->Set(Config::MAIN_CPU_CORE, m_settings.cpu_core);
|
||||
layer->Set(Config::MAIN_ENABLE_CHEATS, m_settings.enable_cheats);
|
||||
#ifdef USE_RETRO_ACHIEVEMENTS
|
||||
layer->Set(Config::RA_HARDCORE_ENABLED, m_settings.enable_hardcore);
|
||||
#endif // USE_RETRO_ACHIEVEMENTS
|
||||
layer->Set(Config::MAIN_GC_LANGUAGE, m_settings.selected_language);
|
||||
layer->Set(Config::MAIN_OVERRIDE_REGION_SETTINGS, m_settings.override_region_settings);
|
||||
layer->Set(Config::MAIN_DSP_HLE, m_settings.dsp_hle);
|
||||
|
|
|
@ -217,7 +217,7 @@ void SConfig::OnNewTitleLoad(const Core::CPUThreadGuard& guard)
|
|||
}
|
||||
CBoot::LoadMapFromFilename(guard, ppc_symbol_db);
|
||||
HLE::Reload(system);
|
||||
PatchEngine::Reload();
|
||||
PatchEngine::Reload(system);
|
||||
HiresTexture::Update();
|
||||
WC24PatchEngine::Reload();
|
||||
}
|
||||
|
|
|
@ -375,6 +375,7 @@ void RSOView::LoadAll(const Core::CPUThreadGuard& guard, u32 address)
|
|||
|
||||
void RSOView::Apply(const Core::CPUThreadGuard& guard, PPCSymbolDB* symbol_db) const
|
||||
{
|
||||
const std::string rso_name = GetName();
|
||||
for (const RSOExport& rso_export : GetExports())
|
||||
{
|
||||
u32 address = GetExportAddress(rso_export);
|
||||
|
@ -389,15 +390,17 @@ void RSOView::Apply(const Core::CPUThreadGuard& guard, PPCSymbolDB* symbol_db) c
|
|||
{
|
||||
// Function symbol
|
||||
symbol->Rename(export_name);
|
||||
symbol->object_name = rso_name;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Data symbol
|
||||
symbol_db->AddKnownSymbol(guard, address, 0, export_name, Common::Symbol::Type::Data);
|
||||
symbol_db->AddKnownSymbol(guard, address, 0, export_name, rso_name,
|
||||
Common::Symbol::Type::Data);
|
||||
}
|
||||
}
|
||||
}
|
||||
DEBUG_LOG_FMT(SYMBOLS, "RSO({}): {} symbols applied", GetName(), GetExportsCount());
|
||||
DEBUG_LOG_FMT(SYMBOLS, "RSO({}): {} symbols applied", rso_name, GetExportsCount());
|
||||
}
|
||||
|
||||
u32 RSOView::GetNextEntry() const
|
||||
|
|
|
@ -15,8 +15,10 @@
|
|||
#include "Common/Config/Config.h"
|
||||
#include "Common/FileUtil.h"
|
||||
|
||||
#include "Core/AchievementManager.h"
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/Host.h"
|
||||
#include "Core/PowerPC/MMU.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
#include "ConfigManager.h"
|
||||
|
@ -52,7 +54,7 @@ static std::vector<GeckoCode> s_active_codes;
|
|||
static std::vector<GeckoCode> s_synced_codes;
|
||||
static std::mutex s_active_codes_lock;
|
||||
|
||||
void SetActiveCodes(std::span<const GeckoCode> gcodes)
|
||||
void SetActiveCodes(std::span<const GeckoCode> gcodes, const std::string& game_id)
|
||||
{
|
||||
std::lock_guard lk(s_active_codes_lock);
|
||||
|
||||
|
@ -60,8 +62,12 @@ void SetActiveCodes(std::span<const GeckoCode> gcodes)
|
|||
if (Config::AreCheatsEnabled())
|
||||
{
|
||||
s_active_codes.reserve(gcodes.size());
|
||||
|
||||
std::copy_if(gcodes.begin(), gcodes.end(), std::back_inserter(s_active_codes),
|
||||
[](const GeckoCode& code) { return code.enabled; });
|
||||
[&game_id](const GeckoCode& code) {
|
||||
return code.enabled &&
|
||||
AchievementManager::GetInstance().CheckApprovedGeckoCode(code, game_id);
|
||||
});
|
||||
}
|
||||
s_active_codes.shrink_to_fit();
|
||||
|
||||
|
@ -272,6 +278,7 @@ static Installation InstallCodeHandlerLocked(const Core::CPUThreadGuard& guard)
|
|||
{
|
||||
ppc_state.iCache.Invalidate(INSTALLER_BASE_ADDRESS + j);
|
||||
}
|
||||
Host_JitCacheInvalidation();
|
||||
return Installation::Installed;
|
||||
}
|
||||
|
||||
|
|
|
@ -80,7 +80,7 @@ constexpr u32 HLE_TRAMPOLINE_ADDRESS = INSTALLER_END_ADDRESS - 4;
|
|||
// preserve the emulation performance.
|
||||
constexpr u32 MAGIC_GAMEID = 0xD01F1BAD;
|
||||
|
||||
void SetActiveCodes(std::span<const GeckoCode> gcodes);
|
||||
void SetActiveCodes(std::span<const GeckoCode> gcodes, const std::string& game_id);
|
||||
void SetSyncedCodesAsActive();
|
||||
void UpdateSyncedCodes(std::span<const GeckoCode> gcodes);
|
||||
std::vector<GeckoCode> SetAndReturnActiveCodes(std::span<const GeckoCode> gcodes);
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include "Core/HLE/HLE_Misc.h"
|
||||
#include "Core/HLE/HLE_OS.h"
|
||||
#include "Core/HW/Memmap.h"
|
||||
#include "Core/Host.h"
|
||||
#include "Core/IOS/ES/ES.h"
|
||||
#include "Core/PowerPC/PPCSymbolDB.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
|
@ -187,6 +188,8 @@ void PatchFunctions(Core::System& system)
|
|||
INFO_LOG_FMT(OSHLE, "Patching {} {:08x}", os_patches[i].name, symbol->address);
|
||||
}
|
||||
}
|
||||
|
||||
Host_JitCacheInvalidation();
|
||||
}
|
||||
|
||||
void Clear()
|
||||
|
@ -305,6 +308,7 @@ u32 UnPatch(Core::System& system, std::string_view patch_name)
|
|||
++i;
|
||||
}
|
||||
}
|
||||
Host_JitCacheInvalidation();
|
||||
return addr;
|
||||
}
|
||||
|
||||
|
@ -317,6 +321,7 @@ u32 UnPatch(Core::System& system, std::string_view patch_name)
|
|||
s_hooked_addresses.erase(addr);
|
||||
ppc_state.iCache.Invalidate(addr);
|
||||
}
|
||||
Host_JitCacheInvalidation();
|
||||
return symbol->address;
|
||||
}
|
||||
|
||||
|
@ -338,6 +343,7 @@ u32 UnpatchRange(Core::System& system, u32 start_addr, u32 end_addr)
|
|||
i = s_hooked_addresses.erase(i);
|
||||
count += 1;
|
||||
}
|
||||
Host_JitCacheInvalidation();
|
||||
|
||||
return count;
|
||||
}
|
||||
|
|
|
@ -418,7 +418,7 @@ void AXWiiUCode::WritePB(Memory::MemoryManager& memory, u32 addr, const AXPBWii&
|
|||
case 0xadbc06bd:
|
||||
memory.CopyToEmuSwapped<u16>(addr, (const u16*)src, updates_begin);
|
||||
memory.CopyToEmuSwapped<u16>(addr + updates_begin, (const u16*)(src + updates_end),
|
||||
sizeof(PBUpdatesWii));
|
||||
gap_begin - updates_end);
|
||||
memory.CopyToEmuSwapped<u16>(addr + gap_begin, (const u16*)(src + gap_end),
|
||||
sizeof(pb) - gap_end);
|
||||
break;
|
||||
|
|
|
@ -834,7 +834,7 @@ BbaTcpSocket::ConnectingState BbaTcpSocket::Connected(StackRef* ref)
|
|||
fd_set read_fds;
|
||||
fd_set write_fds;
|
||||
fd_set except_fds;
|
||||
struct timeval t = {0, 0};
|
||||
timeval t = {0, 0};
|
||||
FD_ZERO(&read_fds);
|
||||
FD_ZERO(&write_fds);
|
||||
FD_ZERO(&except_fds);
|
||||
|
@ -965,7 +965,7 @@ sf::Socket::Status BbaUdpSocket::Bind(u16 port, u32 net_ip)
|
|||
|
||||
// Subscribe to the SSDP multicast group
|
||||
// NB: Other groups aren't supported because of HLE
|
||||
struct ip_mreq mreq;
|
||||
ip_mreq mreq;
|
||||
mreq.imr_multiaddr.s_addr = std::bit_cast<u32>(Common::IP_ADDR_SSDP);
|
||||
mreq.imr_interface.s_addr = net_ip;
|
||||
if (setsockopt(getHandle(), IPPROTO_IP, IP_ADD_MEMBERSHIP, reinterpret_cast<const char*>(&mreq),
|
||||
|
|
|
@ -99,6 +99,10 @@ void ProcessorInterfaceManager::RegisterMMIO(MMIO::Mapping* mmio, u32 base)
|
|||
{
|
||||
system.GetGPFifo().ResetGatherPipe();
|
||||
|
||||
// Assume that all bytes that made it into the GPU fifo did in fact execute
|
||||
// before this MMIO write takes effect.
|
||||
system.GetFifo().SyncGPUForRegisterAccess();
|
||||
|
||||
// Call Fifo::ResetVideoBuffer() from the video thread. Since that function
|
||||
// resets various pointers used by the video thread, we can't call it directly
|
||||
// from the CPU thread, so queue a task to do it instead. In single-core mode,
|
||||
|
|
|
@ -8,21 +8,12 @@
|
|||
#include "AudioCommon/AudioCommon.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MathUtil.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
|
||||
#include "Core/System.h"
|
||||
#include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h"
|
||||
#include "InputCommon/ControllerEmu/Setting/NumericSetting.h"
|
||||
|
||||
//#define WIIMOTE_SPEAKER_DUMP
|
||||
#ifdef WIIMOTE_SPEAKER_DUMP
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include "AudioCommon/WaveFile.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#endif
|
||||
|
||||
namespace WiimoteEmu
|
||||
{
|
||||
// Yamaha ADPCM decoder code based on The ffmpeg Project (Copyright (s) 2001-2003)
|
||||
|
@ -60,17 +51,6 @@ static s16 adpcm_yamaha_expand_nibble(ADPCMState& s, u8 nibble)
|
|||
return s.predictor;
|
||||
}
|
||||
|
||||
#ifdef WIIMOTE_SPEAKER_DUMP
|
||||
std::ofstream ofile;
|
||||
WaveFileWriter wav;
|
||||
|
||||
void stopdamnwav()
|
||||
{
|
||||
wav.Stop();
|
||||
ofile.close();
|
||||
}
|
||||
#endif
|
||||
|
||||
void SpeakerLogic::SpeakerData(const u8* data, int length, float speaker_pan)
|
||||
{
|
||||
// TODO: should we still process samples for the decoder state?
|
||||
|
@ -151,28 +131,6 @@ void SpeakerLogic::SpeakerData(const u8* data, int length, float speaker_pan)
|
|||
const unsigned int sample_rate = sample_rate_dividend / reg_data.sample_rate;
|
||||
sound_stream->GetMixer()->PushWiimoteSpeakerSamples(
|
||||
samples.get(), sample_length, Mixer::FIXED_SAMPLE_RATE_DIVIDEND / (sample_rate * 2));
|
||||
|
||||
#ifdef WIIMOTE_SPEAKER_DUMP
|
||||
static int num = 0;
|
||||
|
||||
if (num == 0)
|
||||
{
|
||||
File::Delete("rmtdump.wav");
|
||||
File::Delete("rmtdump.bin");
|
||||
atexit(stopdamnwav);
|
||||
File::OpenFStream(ofile, "rmtdump.bin", ofile.binary | ofile.out);
|
||||
wav.Start("rmtdump.wav", 6000);
|
||||
}
|
||||
wav.AddMonoSamples(samples.get(), length * 2);
|
||||
if (ofile.good())
|
||||
{
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
ofile << data[i];
|
||||
}
|
||||
}
|
||||
num++;
|
||||
#endif
|
||||
}
|
||||
|
||||
void SpeakerLogic::Reset()
|
||||
|
|
|
@ -197,8 +197,8 @@ void init_lib()
|
|||
|
||||
namespace WiimoteReal
|
||||
{
|
||||
int IOWrite(HANDLE& dev_handle, OVERLAPPED& hid_overlap_write, enum WinWriteMethod& stack,
|
||||
const u8* buf, size_t len, DWORD* written);
|
||||
int IOWrite(HANDLE& dev_handle, OVERLAPPED& hid_overlap_write, WinWriteMethod& stack, const u8* buf,
|
||||
size_t len, DWORD* written);
|
||||
int IORead(HANDLE& dev_handle, OVERLAPPED& hid_overlap_read, u8* buf, int index);
|
||||
|
||||
template <typename T>
|
||||
|
|
|
@ -57,10 +57,11 @@ bool Host_TASInputHasFocus();
|
|||
|
||||
void Host_Message(HostMessageID id);
|
||||
void Host_PPCSymbolsChanged();
|
||||
void Host_PPCBreakpointsChanged();
|
||||
void Host_RefreshDSPDebuggerWindow();
|
||||
void Host_RequestRenderWindowSize(int width, int height);
|
||||
void Host_UpdateDisasmDialog();
|
||||
void Host_JitCacheCleared();
|
||||
void Host_JitCacheInvalidation();
|
||||
void Host_JitProfileDataWiped();
|
||||
void Host_UpdateMainFrame();
|
||||
void Host_UpdateTitle(const std::string& title);
|
||||
|
|
|
@ -133,13 +133,13 @@ IPCReply GetRealProductCode(Core::System& system, const IOCtlVRequest& request)
|
|||
if (!file)
|
||||
return IPCReply(IPC_ENOENT);
|
||||
|
||||
Common::SettingsHandler::Buffer data;
|
||||
Common::SettingsBuffer data;
|
||||
|
||||
if (!file.ReadBytes(data.data(), data.size()))
|
||||
return IPCReply(IPC_ENOENT);
|
||||
|
||||
Common::SettingsHandler gen(data);
|
||||
const std::string code = gen.GetValue("CODE");
|
||||
const Common::SettingsReader settings_reader(data);
|
||||
const std::string code = settings_reader.GetValue("CODE");
|
||||
|
||||
const size_t length = std::min<size_t>(request.io_vectors[0].size, code.length());
|
||||
if (length == 0)
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
|
@ -575,7 +576,7 @@ SharedContentMap::GetFilenameFromSHA1(const std::array<u8, 20>& sha1) const
|
|||
if (it == m_entries.end())
|
||||
return {};
|
||||
|
||||
const std::string id_string(it->id.begin(), it->id.end());
|
||||
const std::string_view id_string(reinterpret_cast<const char*>(it->id.data()), it->id.size());
|
||||
return fmt::format("/shared1/{}.app", id_string);
|
||||
}
|
||||
|
||||
|
@ -591,20 +592,22 @@ std::vector<std::array<u8, 20>> SharedContentMap::GetHashes() const
|
|||
|
||||
std::string SharedContentMap::AddSharedContent(const std::array<u8, 20>& sha1)
|
||||
{
|
||||
auto filename = GetFilenameFromSHA1(sha1);
|
||||
if (filename)
|
||||
return *filename;
|
||||
if (auto filename = GetFilenameFromSHA1(sha1))
|
||||
return *std::move(filename);
|
||||
|
||||
const std::string id = fmt::format("{:08x}", m_last_id);
|
||||
Entry entry;
|
||||
std::copy(id.cbegin(), id.cend(), entry.id.begin());
|
||||
Entry& entry = m_entries.emplace_back();
|
||||
static_assert(sizeof(m_last_id) == 4,
|
||||
"'m_last_id' must be represented by 8 characters when formatted in hexadecimal.");
|
||||
static_assert(std::tuple_size_v<decltype(entry.id)> == sizeof(m_last_id) * 2,
|
||||
"'entry.id' must be a std::array capable of storing every nibble of 'm_last_id'.");
|
||||
fmt::format_to(entry.id.data(), "{:08x}", m_last_id);
|
||||
entry.sha1 = sha1;
|
||||
m_entries.push_back(entry);
|
||||
|
||||
WriteEntries();
|
||||
filename = fmt::format("/shared1/{}.app", id);
|
||||
m_last_id++;
|
||||
return *filename;
|
||||
|
||||
const std::string_view id_string(reinterpret_cast<const char*>(entry.id.data()), entry.id.size());
|
||||
return fmt::format("/shared1/{}.app", id_string);
|
||||
}
|
||||
|
||||
bool SharedContentMap::DeleteSharedContent(const std::array<u8, 20>& sha1)
|
||||
|
|
|
@ -798,7 +798,7 @@ IPCReply NetIPTopDevice::HandleInetAToNRequest(const IOCtlRequest& request)
|
|||
auto& memory = system.GetMemory();
|
||||
|
||||
const std::string hostname = memory.GetString(request.buffer_in);
|
||||
struct hostent* remoteHost = gethostbyname(hostname.c_str());
|
||||
hostent* remoteHost = gethostbyname(hostname.c_str());
|
||||
|
||||
if (remoteHost == nullptr || remoteHost->h_addr_list == nullptr ||
|
||||
remoteHost->h_addr_list[0] == nullptr)
|
||||
|
|
|
@ -923,7 +923,7 @@ IPCReply NetKDRequestDevice::HandleRequestRegisterUserId(const IOS::HLE::IOCtlRe
|
|||
return IPCReply{IPC_SUCCESS};
|
||||
}
|
||||
|
||||
Common::SettingsHandler::Buffer data;
|
||||
Common::SettingsBuffer data;
|
||||
if (!file->Read(data.data(), data.size()))
|
||||
{
|
||||
WriteReturnValue(memory, NWC24::WC24_ERR_FILE_READ, request.buffer_out);
|
||||
|
@ -931,8 +931,8 @@ IPCReply NetKDRequestDevice::HandleRequestRegisterUserId(const IOS::HLE::IOCtlRe
|
|||
return IPCReply{IPC_SUCCESS};
|
||||
}
|
||||
|
||||
const Common::SettingsHandler gen{data};
|
||||
const std::string serno = gen.GetValue("SERNO");
|
||||
const Common::SettingsReader settings_reader{data};
|
||||
const std::string serno = settings_reader.GetValue("SERNO");
|
||||
const std::string form_data =
|
||||
fmt::format("mlid=w{}&hdid={}&rgncd={}", m_config.Id(), m_ios.GetIOSC().GetDeviceId(), serno);
|
||||
const Common::HttpRequest::Response response = m_http.Post(m_config.GetAccountURL(), form_data);
|
||||
|
@ -1076,12 +1076,12 @@ std::optional<IPCReply> NetKDRequestDevice::IOCtl(const IOCtlRequest& request)
|
|||
const auto fs = m_ios.GetFS();
|
||||
if (const auto file = fs->OpenFile(PID_KD, PID_KD, settings_file_path, FS::Mode::Read))
|
||||
{
|
||||
Common::SettingsHandler::Buffer data;
|
||||
Common::SettingsBuffer data;
|
||||
if (file->Read(data.data(), data.size()))
|
||||
{
|
||||
const Common::SettingsHandler gen{data};
|
||||
area = gen.GetValue("AREA");
|
||||
model = gen.GetValue("MODEL");
|
||||
const Common::SettingsReader settings_reader{data};
|
||||
area = settings_reader.GetValue("AREA");
|
||||
model = settings_reader.GetValue("MODEL");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
|
||||
static DRESULT read_vff_header(IOS::HLE::FS::FileHandle* vff, FATFS* fs)
|
||||
{
|
||||
struct IOS::HLE::NWC24::VFFHeader header;
|
||||
IOS::HLE::NWC24::VFFHeader header;
|
||||
if (!vff->Read(&header, 1))
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_WC24, "Failed to read VFF header.");
|
||||
|
|
|
@ -770,7 +770,7 @@ WiiSocket::ConnectingState WiiSocket::GetConnectingState() const
|
|||
fd_set read_fds;
|
||||
fd_set write_fds;
|
||||
fd_set except_fds;
|
||||
struct timeval t = {0, 0};
|
||||
timeval t = {0, 0};
|
||||
FD_ZERO(&read_fds);
|
||||
FD_ZERO(&write_fds);
|
||||
FD_ZERO(&except_fds);
|
||||
|
@ -998,7 +998,7 @@ void WiiSockMan::Update()
|
|||
{
|
||||
s32 nfds = 0;
|
||||
fd_set read_fds, write_fds, except_fds;
|
||||
struct timeval t = {0, 0};
|
||||
timeval t = {0, 0};
|
||||
FD_ZERO(&read_fds);
|
||||
FD_ZERO(&write_fds);
|
||||
FD_ZERO(&except_fds);
|
||||
|
@ -1085,10 +1085,10 @@ void WiiSockMan::UpdatePollCommands()
|
|||
std::vector<int> original_order(pfds.size());
|
||||
std::iota(original_order.begin(), original_order.end(), 0);
|
||||
// Select indices with valid fds
|
||||
auto mid = std::partition(original_order.begin(), original_order.end(), [&](auto i) {
|
||||
const auto partition_result = std::ranges::partition(original_order, [&](auto i) {
|
||||
return GetHostSocket(memory.Read_U32(pcmd.buffer_out + 0xc * i)) >= 0;
|
||||
});
|
||||
const auto n_valid = std::distance(original_order.begin(), mid);
|
||||
const auto n_valid = std::distance(original_order.begin(), partition_result.begin());
|
||||
|
||||
// Move all the valid pollfds to the front of the vector
|
||||
for (auto i = 0; i < n_valid; ++i)
|
||||
|
|
|
@ -2505,7 +2505,7 @@ struct hci_filter
|
|||
uint32_t mask[8]; /* 256 bits */
|
||||
};
|
||||
|
||||
static __inline void hci_filter_set(uint8_t bit, struct hci_filter* filter)
|
||||
static __inline void hci_filter_set(uint8_t bit, hci_filter* filter)
|
||||
{
|
||||
uint8_t off = bit - 1;
|
||||
|
||||
|
@ -2513,7 +2513,7 @@ static __inline void hci_filter_set(uint8_t bit, struct hci_filter* filter)
|
|||
filter->mask[off] |= (1 << ((bit - 1) & 0x1f));
|
||||
}
|
||||
|
||||
static __inline void hci_filter_clr(uint8_t bit, struct hci_filter* filter)
|
||||
static __inline void hci_filter_clr(uint8_t bit, hci_filter* filter)
|
||||
{
|
||||
uint8_t off = bit - 1;
|
||||
|
||||
|
@ -2581,7 +2581,7 @@ struct btreq
|
|||
uint16_t btri_link_policy; /* Link Policy */
|
||||
uint16_t btri_packet_type; /* Packet Type */
|
||||
} btri;
|
||||
struct bt_stats btrs; /* unit stats */
|
||||
bt_stats btrs; /* unit stats */
|
||||
} btru;
|
||||
};
|
||||
|
||||
|
|
|
@ -96,10 +96,11 @@ std::optional<IPCReply> USB_HIDv4::GetDeviceChange(const IOCtlRequest& request)
|
|||
m_devicechange_hook_request = std::make_unique<IOCtlRequest>(GetSystem(), request.address);
|
||||
// If there are pending changes, the reply is sent immediately (instead of on device
|
||||
// insertion/removal).
|
||||
if (m_has_pending_changes)
|
||||
if (m_has_pending_changes || m_is_shut_down)
|
||||
{
|
||||
TriggerDeviceChangeReply();
|
||||
m_has_pending_changes = false;
|
||||
m_is_shut_down = false;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
@ -114,6 +115,7 @@ IPCReply USB_HIDv4::Shutdown(const IOCtlRequest& request)
|
|||
memory.Write_U32(0xffffffff, m_devicechange_hook_request->buffer_out);
|
||||
GetEmulationKernel().EnqueueIPCReply(*m_devicechange_hook_request, -1);
|
||||
m_devicechange_hook_request.reset();
|
||||
m_is_shut_down = true;
|
||||
}
|
||||
return IPCReply(IPC_SUCCESS);
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@ private:
|
|||
static constexpr u8 HID_CLASS = 0x03;
|
||||
|
||||
bool m_has_pending_changes = true;
|
||||
bool m_is_shut_down = false;
|
||||
std::mutex m_devicechange_hook_address_mutex;
|
||||
std::unique_ptr<IOCtlRequest> m_devicechange_hook_request;
|
||||
|
||||
|
|
|
@ -1081,11 +1081,11 @@ void MovieManager::LoadInput(const std::string& movie_path)
|
|||
std::vector<u8> movInput(m_current_byte);
|
||||
t_record.ReadArray(movInput.data(), movInput.size());
|
||||
|
||||
const auto result = std::mismatch(movInput.begin(), movInput.end(), m_temp_input.begin());
|
||||
const auto mismatch_result = std::ranges::mismatch(movInput, m_temp_input);
|
||||
|
||||
if (result.first != movInput.end())
|
||||
if (mismatch_result.in1 != movInput.end())
|
||||
{
|
||||
const ptrdiff_t mismatch_index = std::distance(movInput.begin(), result.first);
|
||||
const ptrdiff_t mismatch_index = std::distance(movInput.begin(), mismatch_result.in1);
|
||||
|
||||
// this is a "you did something wrong" alert for the user's benefit.
|
||||
// we'll try to say what's going on in excruciating detail, otherwise the user might not
|
||||
|
|
|
@ -873,6 +873,7 @@ void NetPlayClient::OnStartGame(sf::Packet& packet)
|
|||
packet >> m_net_settings.cpu_thread;
|
||||
packet >> m_net_settings.cpu_core;
|
||||
packet >> m_net_settings.enable_cheats;
|
||||
packet >> m_net_settings.enable_hardcore;
|
||||
packet >> m_net_settings.selected_language;
|
||||
packet >> m_net_settings.override_region_settings;
|
||||
packet >> m_net_settings.dsp_enable_jit;
|
||||
|
|
|
@ -35,6 +35,7 @@ struct NetSettings
|
|||
bool cpu_thread = false;
|
||||
PowerPC::CPUCore cpu_core{};
|
||||
bool enable_cheats = false;
|
||||
bool enable_hardcore = false;
|
||||
int selected_language = 0;
|
||||
bool override_region_settings = false;
|
||||
bool dsp_hle = false;
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include "Common/UPnP.h"
|
||||
#include "Common/Version.h"
|
||||
|
||||
#include "Core/AchievementManager.h"
|
||||
#include "Core/ActionReplay.h"
|
||||
#include "Core/Boot/Boot.h"
|
||||
#include "Core/Config/GraphicsSettings.h"
|
||||
|
@ -1412,6 +1413,7 @@ bool NetPlayServer::SetupNetSettings()
|
|||
settings.cpu_thread = Config::Get(Config::MAIN_CPU_THREAD);
|
||||
settings.cpu_core = Config::Get(Config::MAIN_CPU_CORE);
|
||||
settings.enable_cheats = Config::AreCheatsEnabled();
|
||||
settings.enable_hardcore = AchievementManager::GetInstance().IsHardcoreModeActive();
|
||||
settings.selected_language = Config::Get(Config::MAIN_GC_LANGUAGE);
|
||||
settings.override_region_settings = Config::Get(Config::MAIN_OVERRIDE_REGION_SETTINGS);
|
||||
settings.dsp_hle = Config::Get(Config::MAIN_DSP_HLE);
|
||||
|
@ -1640,6 +1642,7 @@ bool NetPlayServer::StartGame()
|
|||
spac << m_settings.cpu_thread;
|
||||
spac << m_settings.cpu_core;
|
||||
spac << m_settings.enable_cheats;
|
||||
spac << m_settings.enable_hardcore;
|
||||
spac << m_settings.selected_language;
|
||||
spac << m_settings.override_region_settings;
|
||||
spac << m_settings.dsp_enable_jit;
|
||||
|
@ -2121,13 +2124,18 @@ bool NetPlayServer::SyncCodes()
|
|||
}
|
||||
// Sync Gecko Codes
|
||||
{
|
||||
std::vector<Gecko::GeckoCode> codes = Gecko::LoadCodes(globalIni, localIni);
|
||||
|
||||
#ifdef USE_RETRO_ACHIEVEMENTS
|
||||
AchievementManager::GetInstance().FilterApprovedGeckoCodes(codes, game_id);
|
||||
#endif // USE_RETRO_ACHIEVEMENTS
|
||||
|
||||
// Create a Gecko Code Vector with just the active codes
|
||||
std::vector<Gecko::GeckoCode> s_active_codes =
|
||||
Gecko::SetAndReturnActiveCodes(Gecko::LoadCodes(globalIni, localIni));
|
||||
std::vector<Gecko::GeckoCode> active_codes = Gecko::SetAndReturnActiveCodes(codes);
|
||||
|
||||
// Determine Codelist Size
|
||||
u16 codelines = 0;
|
||||
for (const Gecko::GeckoCode& active_code : s_active_codes)
|
||||
for (const Gecko::GeckoCode& active_code : active_codes)
|
||||
{
|
||||
INFO_LOG_FMT(NETPLAY, "Indexing {}", active_code.name);
|
||||
for (const Gecko::GeckoCode::Code& code : active_code.codes)
|
||||
|
@ -2156,7 +2164,7 @@ bool NetPlayServer::SyncCodes()
|
|||
pac << SyncCodeID::GeckoData;
|
||||
std::vector<std::string> v_ActiveGeckoCodes = {};
|
||||
// Iterate through the active code vector and send each codeline
|
||||
for (const Gecko::GeckoCode& active_code : s_active_codes)
|
||||
for (const Gecko::GeckoCode& active_code : active_codes)
|
||||
{
|
||||
INFO_LOG_FMT(NETPLAY, "Sending {}", active_code.name);
|
||||
for (const Gecko::GeckoCode::Code& code : active_code.codes)
|
||||
|
@ -2180,13 +2188,16 @@ bool NetPlayServer::SyncCodes()
|
|||
|
||||
// Sync AR Codes
|
||||
{
|
||||
std::vector<ActionReplay::ARCode> codes = ActionReplay::LoadCodes(globalIni, localIni);
|
||||
#ifdef USE_RETRO_ACHIEVEMENTS
|
||||
AchievementManager::GetInstance().FilterApprovedARCodes(codes, game_id);
|
||||
#endif // USE_RETRO_ACHIEVEMENTS
|
||||
// Create an AR Code Vector with just the active codes
|
||||
std::vector<ActionReplay::ARCode> s_active_codes =
|
||||
ActionReplay::ApplyAndReturnCodes(ActionReplay::LoadCodes(globalIni, localIni));
|
||||
std::vector<ActionReplay::ARCode> active_codes = ActionReplay::ApplyAndReturnCodes(codes);
|
||||
|
||||
// Determine Codelist Size
|
||||
u16 codelines = 0;
|
||||
for (const ActionReplay::ARCode& active_code : s_active_codes)
|
||||
for (const ActionReplay::ARCode& active_code : active_codes)
|
||||
{
|
||||
INFO_LOG_FMT(NETPLAY, "Indexing {}", active_code.name);
|
||||
for (const ActionReplay::AREntry& op : active_code.ops)
|
||||
|
@ -2214,8 +2225,7 @@ bool NetPlayServer::SyncCodes()
|
|||
pac << MessageID::SyncCodes;
|
||||
pac << SyncCodeID::ARData;
|
||||
// Iterate through the active code vector and send each codeline
|
||||
std::vector<std::string> v_ActiveARCodes = {};
|
||||
for (const ActionReplay::ARCode& active_code : s_active_codes)
|
||||
for (const ActionReplay::ARCode& active_code : active_codes)
|
||||
{
|
||||
INFO_LOG_FMT(NETPLAY, "Sending {}", active_code.name);
|
||||
for (const ActionReplay::AREntry& op : active_code.ops)
|
||||
|
|
|
@ -198,8 +198,8 @@ void LoadPatches()
|
|||
}
|
||||
else
|
||||
{
|
||||
Gecko::SetActiveCodes(Gecko::LoadCodes(globalIni, localIni));
|
||||
ActionReplay::LoadAndApplyCodes(globalIni, localIni);
|
||||
Gecko::SetActiveCodes(Gecko::LoadCodes(globalIni, localIni), sconfig.GetGameID());
|
||||
ActionReplay::LoadAndApplyCodes(globalIni, localIni, sconfig.GetGameID());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -246,9 +246,6 @@ static void ApplyPatches(const Core::CPUThreadGuard& guard, const std::vector<Pa
|
|||
static void ApplyMemoryPatches(const Core::CPUThreadGuard& guard,
|
||||
std::span<const std::size_t> memory_patch_indices)
|
||||
{
|
||||
if (AchievementManager::GetInstance().IsHardcoreModeActive())
|
||||
return;
|
||||
|
||||
std::lock_guard lock(s_on_frame_memory_mutex);
|
||||
for (std::size_t index : memory_patch_indices)
|
||||
{
|
||||
|
@ -296,6 +293,13 @@ void RemoveMemoryPatch(std::size_t index)
|
|||
std::erase(s_on_frame_memory, index);
|
||||
}
|
||||
|
||||
static void ApplyStartupPatches(Core::System& system)
|
||||
{
|
||||
ASSERT(Core::IsCPUThread());
|
||||
Core::CPUThreadGuard guard(system);
|
||||
ApplyPatches(guard, s_on_frame);
|
||||
}
|
||||
|
||||
bool ApplyFramePatches(Core::System& system)
|
||||
{
|
||||
const auto& ppc_state = system.GetPPCState();
|
||||
|
@ -331,14 +335,15 @@ bool ApplyFramePatches(Core::System& system)
|
|||
void Shutdown()
|
||||
{
|
||||
s_on_frame.clear();
|
||||
ActionReplay::ApplyCodes({});
|
||||
ActionReplay::ApplyCodes({}, "");
|
||||
Gecko::Shutdown();
|
||||
}
|
||||
|
||||
void Reload()
|
||||
void Reload(Core::System& system)
|
||||
{
|
||||
Shutdown();
|
||||
LoadPatches();
|
||||
ApplyStartupPatches(system);
|
||||
}
|
||||
|
||||
} // namespace PatchEngine
|
||||
|
|
|
@ -61,7 +61,7 @@ void RemoveMemoryPatch(std::size_t index);
|
|||
|
||||
bool ApplyFramePatches(Core::System& system);
|
||||
void Shutdown();
|
||||
void Reload();
|
||||
void Reload(Core::System& system);
|
||||
|
||||
inline int GetPatchTypeCharLength(PatchType type)
|
||||
{
|
||||
|
|
|
@ -442,7 +442,7 @@ void CachedInterpreter::ClearCache()
|
|||
ClearCodeSpace();
|
||||
ResetFreeMemoryRanges();
|
||||
RefreshConfig();
|
||||
Host_JitCacheCleared();
|
||||
Host_JitCacheInvalidation();
|
||||
}
|
||||
|
||||
void CachedInterpreter::LogGeneratedCode() const
|
||||
|
|
|
@ -178,6 +178,7 @@ static void RemoveBreakpoint(BreakpointType type, u32 addr, u32 len)
|
|||
INFO_LOG_FMT(GDB_STUB, "gdb: removed a memcheck: {:08x} bytes at {:08x}", len, addr);
|
||||
}
|
||||
}
|
||||
Host_PPCBreakpointsChanged();
|
||||
}
|
||||
|
||||
static void Nack()
|
||||
|
@ -258,7 +259,7 @@ static void ReadCommand()
|
|||
|
||||
static bool IsDataAvailable()
|
||||
{
|
||||
struct timeval t;
|
||||
timeval t;
|
||||
fd_set _fds, *fds = &_fds;
|
||||
|
||||
FD_ZERO(fds);
|
||||
|
@ -896,6 +897,7 @@ static bool AddBreakpoint(BreakpointType type, u32 addr, u32 len)
|
|||
INFO_LOG_FMT(GDB_STUB, "gdb: added {} memcheck: {:08x} bytes at {:08x}", static_cast<int>(type),
|
||||
len, addr);
|
||||
}
|
||||
Host_PPCBreakpointsChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -313,7 +313,7 @@ void Jit64::ClearCache()
|
|||
RefreshConfig();
|
||||
asm_routines.Regenerate();
|
||||
ResetFreeMemoryRanges();
|
||||
Host_JitCacheCleared();
|
||||
Host_JitCacheInvalidation();
|
||||
}
|
||||
|
||||
void Jit64::FreeRanges()
|
||||
|
@ -350,8 +350,8 @@ void Jit64::Shutdown()
|
|||
|
||||
void Jit64::FallBackToInterpreter(UGeckoInstruction inst)
|
||||
{
|
||||
gpr.Flush();
|
||||
fpr.Flush();
|
||||
gpr.Flush(BitSet32(0xFFFFFFFF), RegCache::IgnoreDiscardedRegisters::Yes);
|
||||
fpr.Flush(BitSet32(0xFFFFFFFF), RegCache::IgnoreDiscardedRegisters::Yes);
|
||||
|
||||
if (js.op->canEndBlock)
|
||||
{
|
||||
|
|
|
@ -644,6 +644,20 @@ void Jit64::fselx(UGeckoInstruction inst)
|
|||
|
||||
if (cpu_info.bAVX)
|
||||
{
|
||||
// Prefer BLENDVPD over VBLENDVPD if the latter doesn't save any
|
||||
// instructions.
|
||||
//
|
||||
// VBLENDVPD allows separate source and destination registers, which can
|
||||
// eliminate a MOVAPD/MOVSD. However, on Intel since Skylake, VBLENDVPD
|
||||
// takes additional uops to execute compared to BLENDVPD (according to
|
||||
// https://uops.info). On AMD and older Intel microarchitectures there is no
|
||||
// difference.
|
||||
if (d == c)
|
||||
{
|
||||
BLENDVPD(Rd, Rb);
|
||||
return;
|
||||
}
|
||||
|
||||
X64Reg src1 = XMM1;
|
||||
if (Rc.IsSimpleReg())
|
||||
{
|
||||
|
@ -654,7 +668,7 @@ void Jit64::fselx(UGeckoInstruction inst)
|
|||
MOVAPD(XMM1, Rc);
|
||||
}
|
||||
|
||||
if (d == c || packed)
|
||||
if (packed)
|
||||
{
|
||||
VBLENDVPD(Rd, src1, Rb, XMM0);
|
||||
return;
|
||||
|
|
|
@ -391,7 +391,7 @@ void RegCache::Discard(BitSet32 pregs)
|
|||
}
|
||||
}
|
||||
|
||||
void RegCache::Flush(BitSet32 pregs)
|
||||
void RegCache::Flush(BitSet32 pregs, IgnoreDiscardedRegisters ignore_discarded_registers)
|
||||
{
|
||||
ASSERT_MSG(
|
||||
DYNA_REC,
|
||||
|
@ -410,7 +410,8 @@ void RegCache::Flush(BitSet32 pregs)
|
|||
case PPCCachedReg::LocationType::Default:
|
||||
break;
|
||||
case PPCCachedReg::LocationType::Discarded:
|
||||
ASSERT_MSG(DYNA_REC, false, "Attempted to flush discarded PPC reg {}", i);
|
||||
ASSERT_MSG(DYNA_REC, ignore_discarded_registers != IgnoreDiscardedRegisters::No,
|
||||
"Attempted to flush discarded PPC reg {}", i);
|
||||
break;
|
||||
case PPCCachedReg::LocationType::SpeculativeImmediate:
|
||||
// We can have a cached value without a host register through speculative constants.
|
||||
|
|
|
@ -126,6 +126,12 @@ public:
|
|||
MaintainState,
|
||||
};
|
||||
|
||||
enum class IgnoreDiscardedRegisters
|
||||
{
|
||||
No,
|
||||
Yes,
|
||||
};
|
||||
|
||||
explicit RegCache(Jit64& jit);
|
||||
virtual ~RegCache() = default;
|
||||
|
||||
|
@ -168,7 +174,8 @@ public:
|
|||
|
||||
RCForkGuard Fork();
|
||||
void Discard(BitSet32 pregs);
|
||||
void Flush(BitSet32 pregs = BitSet32::AllTrue(32));
|
||||
void Flush(BitSet32 pregs = BitSet32::AllTrue(32),
|
||||
IgnoreDiscardedRegisters ignore_discarded_registers = IgnoreDiscardedRegisters::No);
|
||||
void Reset(BitSet32 pregs);
|
||||
void Revert();
|
||||
void Commit();
|
||||
|
|
|
@ -197,7 +197,7 @@ void JitArm64::GenerateAsmAndResetFreeMemoryRanges()
|
|||
ResetFreeMemoryRanges(routines_near_end - routines_near_start,
|
||||
routines_far_end - routines_far_start);
|
||||
|
||||
Host_JitCacheCleared();
|
||||
Host_JitCacheInvalidation();
|
||||
}
|
||||
|
||||
void JitArm64::FreeRanges()
|
||||
|
@ -256,18 +256,17 @@ void JitArm64::Shutdown()
|
|||
void JitArm64::FallBackToInterpreter(UGeckoInstruction inst)
|
||||
{
|
||||
FlushCarry();
|
||||
gpr.Flush(FlushMode::All, ARM64Reg::INVALID_REG);
|
||||
fpr.Flush(FlushMode::All, ARM64Reg::INVALID_REG);
|
||||
gpr.Flush(FlushMode::All, ARM64Reg::INVALID_REG, IgnoreDiscardedRegisters::Yes);
|
||||
fpr.Flush(FlushMode::All, ARM64Reg::INVALID_REG, IgnoreDiscardedRegisters::Yes);
|
||||
|
||||
if (js.op->canEndBlock)
|
||||
{
|
||||
// also flush the program counter
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
auto WA = gpr.GetScopedReg();
|
||||
MOVI2R(WA, js.compilerPC);
|
||||
STR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(pc));
|
||||
ADD(WA, WA, 4);
|
||||
STR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(npc));
|
||||
gpr.Unlock(WA);
|
||||
}
|
||||
|
||||
Interpreter::Instruction instr = Interpreter::GetInterpreterOp(inst);
|
||||
|
@ -283,24 +282,23 @@ void JitArm64::FallBackToInterpreter(UGeckoInstruction inst)
|
|||
{
|
||||
if (js.isLastInstruction)
|
||||
{
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
auto WA = gpr.GetScopedReg();
|
||||
LDR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(npc));
|
||||
WriteExceptionExit(WA);
|
||||
gpr.Unlock(WA);
|
||||
}
|
||||
else
|
||||
{
|
||||
// only exit if ppcstate.npc was changed
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
auto WA = gpr.GetScopedReg();
|
||||
LDR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(npc));
|
||||
ARM64Reg WB = gpr.GetReg();
|
||||
MOVI2R(WB, js.compilerPC + 4);
|
||||
CMP(WB, WA);
|
||||
gpr.Unlock(WB);
|
||||
{
|
||||
auto WB = gpr.GetScopedReg();
|
||||
MOVI2R(WB, js.compilerPC + 4);
|
||||
CMP(WB, WA);
|
||||
}
|
||||
FixupBranch c = B(CC_EQ);
|
||||
WriteExceptionExit(WA);
|
||||
SetJumpTarget(c);
|
||||
gpr.Unlock(WA);
|
||||
}
|
||||
}
|
||||
else if (ShouldHandleFPExceptionForInstruction(js.op))
|
||||
|
@ -399,11 +397,12 @@ void JitArm64::IntializeSpeculativeConstants()
|
|||
SwitchToNearCode();
|
||||
}
|
||||
|
||||
ARM64Reg tmp = gpr.GetReg();
|
||||
ARM64Reg value = gpr.R(i);
|
||||
MOVI2R(tmp, compile_time_value);
|
||||
CMP(value, tmp);
|
||||
gpr.Unlock(tmp);
|
||||
{
|
||||
auto tmp = gpr.GetScopedReg();
|
||||
ARM64Reg value = gpr.R(i);
|
||||
MOVI2R(tmp, compile_time_value);
|
||||
CMP(value, tmp);
|
||||
}
|
||||
|
||||
FixupBranch no_fail = B(CCFlags::CC_EQ);
|
||||
B(fail);
|
||||
|
@ -442,16 +441,15 @@ void JitArm64::MSRUpdated(u32 msr)
|
|||
}
|
||||
else
|
||||
{
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
auto WA = gpr.GetScopedReg();
|
||||
MOVI2R(WA, feature_flags);
|
||||
STR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(feature_flags));
|
||||
gpr.Unlock(WA);
|
||||
}
|
||||
}
|
||||
|
||||
void JitArm64::MSRUpdated(ARM64Reg msr)
|
||||
{
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
auto WA = gpr.GetScopedReg();
|
||||
ARM64Reg XA = EncodeRegTo64(WA);
|
||||
|
||||
// Update mem_ptr
|
||||
|
@ -472,8 +470,6 @@ void JitArm64::MSRUpdated(ARM64Reg msr)
|
|||
if (other_feature_flags != 0)
|
||||
ORR(WA, WA, LogicalImm(other_feature_flags, GPRSize::B32));
|
||||
STR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(feature_flags));
|
||||
|
||||
gpr.Unlock(WA);
|
||||
}
|
||||
|
||||
void JitArm64::WriteExit(u32 destination, bool LK, u32 exit_address_after_return,
|
||||
|
@ -671,35 +667,37 @@ void JitArm64::FakeLKExit(u32 exit_address_after_return, ARM64Reg exit_address_a
|
|||
// function has been called!
|
||||
gpr.Lock(ARM64Reg::W30);
|
||||
}
|
||||
// Push {ARM_PC (64-bit); PPC_PC (32-bit); feature_flags (32-bit)} on the stack
|
||||
ARM64Reg after_reg = ARM64Reg::INVALID_REG;
|
||||
ARM64Reg reg_to_push;
|
||||
const u64 feature_flags = m_ppc_state.feature_flags;
|
||||
if (exit_address_after_return_reg == ARM64Reg::INVALID_REG)
|
||||
|
||||
const u8* host_address_after_return;
|
||||
{
|
||||
after_reg = gpr.GetReg();
|
||||
reg_to_push = EncodeRegTo64(after_reg);
|
||||
MOVI2R(reg_to_push, feature_flags << 32 | exit_address_after_return);
|
||||
// Push {ARM_PC (64-bit); PPC_PC (32-bit); feature_flags (32-bit)} on the stack
|
||||
Arm64RegCache::ScopedARM64Reg after_reg;
|
||||
ARM64Reg reg_to_push;
|
||||
const u64 feature_flags = m_ppc_state.feature_flags;
|
||||
if (exit_address_after_return_reg == ARM64Reg::INVALID_REG)
|
||||
{
|
||||
after_reg = gpr.GetScopedReg();
|
||||
reg_to_push = EncodeRegTo64(after_reg);
|
||||
MOVI2R(reg_to_push, feature_flags << 32 | exit_address_after_return);
|
||||
}
|
||||
else if (feature_flags == 0)
|
||||
{
|
||||
reg_to_push = EncodeRegTo64(exit_address_after_return_reg);
|
||||
}
|
||||
else
|
||||
{
|
||||
after_reg = gpr.GetScopedReg();
|
||||
reg_to_push = EncodeRegTo64(after_reg);
|
||||
ORRI2R(reg_to_push, EncodeRegTo64(exit_address_after_return_reg), feature_flags << 32,
|
||||
reg_to_push);
|
||||
}
|
||||
|
||||
auto code_reg = gpr.GetScopedReg();
|
||||
constexpr s32 adr_offset = sizeof(u32) * 3;
|
||||
host_address_after_return = GetCodePtr() + adr_offset;
|
||||
ADR(EncodeRegTo64(code_reg), adr_offset);
|
||||
STP(IndexType::Pre, EncodeRegTo64(code_reg), reg_to_push, ARM64Reg::SP, -16);
|
||||
}
|
||||
else if (feature_flags == 0)
|
||||
{
|
||||
reg_to_push = EncodeRegTo64(exit_address_after_return_reg);
|
||||
}
|
||||
else
|
||||
{
|
||||
after_reg = gpr.GetReg();
|
||||
reg_to_push = EncodeRegTo64(after_reg);
|
||||
ORRI2R(reg_to_push, EncodeRegTo64(exit_address_after_return_reg), feature_flags << 32,
|
||||
reg_to_push);
|
||||
}
|
||||
ARM64Reg code_reg = gpr.GetReg();
|
||||
constexpr s32 adr_offset = sizeof(u32) * 3;
|
||||
const u8* host_address_after_return = GetCodePtr() + adr_offset;
|
||||
ADR(EncodeRegTo64(code_reg), adr_offset);
|
||||
STP(IndexType::Pre, EncodeRegTo64(code_reg), reg_to_push, ARM64Reg::SP, -16);
|
||||
gpr.Unlock(code_reg);
|
||||
if (after_reg != ARM64Reg::INVALID_REG)
|
||||
gpr.Unlock(after_reg);
|
||||
|
||||
FixupBranch skip_exit = BL();
|
||||
DEBUG_ASSERT(GetCodePtr() == host_address_after_return || HasWriteFailed());
|
||||
|
@ -832,10 +830,9 @@ void JitArm64::WriteExceptionExit(ARM64Reg dest, bool only_external, bool always
|
|||
|
||||
void JitArm64::WriteConditionalExceptionExit(int exception, u64 increment_sp_on_exit)
|
||||
{
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
auto WA = gpr.GetScopedReg();
|
||||
WriteConditionalExceptionExit(exception, WA, Arm64Gen::ARM64Reg::INVALID_REG,
|
||||
increment_sp_on_exit);
|
||||
gpr.Unlock(WA);
|
||||
}
|
||||
|
||||
void JitArm64::WriteConditionalExceptionExit(int exception, ARM64Reg temp_gpr, ARM64Reg temp_fpr,
|
||||
|
@ -1227,7 +1224,7 @@ bool JitArm64::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
|
|||
// asynchronous.
|
||||
if (jo.optimizeGatherPipe && gatherPipeIntCheck)
|
||||
{
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
auto WA = gpr.GetScopedReg();
|
||||
ARM64Reg XA = EncodeRegTo64(WA);
|
||||
|
||||
LDR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(Exceptions));
|
||||
|
@ -1253,8 +1250,6 @@ bool JitArm64::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
|
|||
SwitchToNearCode();
|
||||
SetJumpTarget(no_ext_exception);
|
||||
SetJumpTarget(exit);
|
||||
|
||||
gpr.Unlock(WA);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1268,12 +1263,11 @@ bool JitArm64::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
|
|||
// The only thing that currently sets op.skip is the BLR following optimization.
|
||||
// If any non-branch instruction starts setting that too, this will need to be changed.
|
||||
ASSERT(op.inst.hex == 0x4e800020);
|
||||
const ARM64Reg bw_reg_a = gpr.GetReg(), bw_reg_b = gpr.GetReg();
|
||||
const auto bw_reg_a = gpr.GetScopedReg(), bw_reg_b = gpr.GetScopedReg();
|
||||
const BitSet32 gpr_caller_save =
|
||||
gpr.GetCallerSavedUsed() & ~BitSet32{DecodeReg(bw_reg_a), DecodeReg(bw_reg_b)};
|
||||
WriteBranchWatch<true>(op.address, op.branchTo, op.inst, bw_reg_a, bw_reg_b,
|
||||
gpr_caller_save, fpr.GetCallerSavedUsed());
|
||||
gpr.Unlock(bw_reg_a, bw_reg_b);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -1311,23 +1305,24 @@ bool JitArm64::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
|
|||
|
||||
if ((opinfo->flags & FL_USE_FPU) && !js.firstFPInstructionFound)
|
||||
{
|
||||
FixupBranch b1;
|
||||
// This instruction uses FPU - needs to add FP exception bailout
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
LDR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(msr));
|
||||
FixupBranch b1 = TBNZ(WA, 13); // Test FP enabled bit
|
||||
{
|
||||
auto WA = gpr.GetScopedReg();
|
||||
LDR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(msr));
|
||||
b1 = TBNZ(WA, 13); // Test FP enabled bit
|
||||
|
||||
FixupBranch far_addr = B();
|
||||
SwitchToFarCode();
|
||||
SetJumpTarget(far_addr);
|
||||
FixupBranch far_addr = B();
|
||||
SwitchToFarCode();
|
||||
SetJumpTarget(far_addr);
|
||||
|
||||
gpr.Flush(FlushMode::MaintainState, WA);
|
||||
fpr.Flush(FlushMode::MaintainState, ARM64Reg::INVALID_REG);
|
||||
gpr.Flush(FlushMode::MaintainState, WA);
|
||||
fpr.Flush(FlushMode::MaintainState, ARM64Reg::INVALID_REG);
|
||||
|
||||
LDR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(Exceptions));
|
||||
ORR(WA, WA, LogicalImm(EXCEPTION_FPU_UNAVAILABLE, GPRSize::B32));
|
||||
STR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(Exceptions));
|
||||
|
||||
gpr.Unlock(WA);
|
||||
LDR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(Exceptions));
|
||||
ORR(WA, WA, LogicalImm(EXCEPTION_FPU_UNAVAILABLE, GPRSize::B32));
|
||||
STR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(Exceptions));
|
||||
}
|
||||
|
||||
WriteExceptionExit(js.compilerPC, false, true);
|
||||
|
||||
|
|
|
@ -24,13 +24,12 @@ void JitArm64::sc(UGeckoInstruction inst)
|
|||
gpr.Flush(FlushMode::All, ARM64Reg::INVALID_REG);
|
||||
fpr.Flush(FlushMode::All, ARM64Reg::INVALID_REG);
|
||||
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
|
||||
LDR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(Exceptions));
|
||||
ORR(WA, WA, LogicalImm(EXCEPTION_SYSCALL, GPRSize::B32));
|
||||
STR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(Exceptions));
|
||||
|
||||
gpr.Unlock(WA);
|
||||
{
|
||||
auto WA = gpr.GetScopedReg();
|
||||
LDR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(Exceptions));
|
||||
ORR(WA, WA, LogicalImm(EXCEPTION_SYSCALL, GPRSize::B32));
|
||||
STR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(Exceptions));
|
||||
}
|
||||
|
||||
WriteExceptionExit(js.compilerPC + 4, false, true);
|
||||
}
|
||||
|
@ -51,28 +50,28 @@ void JitArm64::rfi(UGeckoInstruction inst)
|
|||
// R1 = MSR contents
|
||||
// R2 = Mask
|
||||
// R3 = Mask
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
ARM64Reg WB = gpr.GetReg();
|
||||
ARM64Reg WC = gpr.GetReg();
|
||||
auto WA = gpr.GetScopedReg();
|
||||
{
|
||||
auto WB = gpr.GetScopedReg();
|
||||
auto WC = gpr.GetScopedReg();
|
||||
|
||||
LDR(IndexType::Unsigned, WC, PPC_REG, PPCSTATE_OFF(msr));
|
||||
LDR(IndexType::Unsigned, WC, PPC_REG, PPCSTATE_OFF(msr));
|
||||
|
||||
ANDI2R(WC, WC, (~mask) & clearMSR13, WA); // rD = Masked MSR
|
||||
ANDI2R(WC, WC, (~mask) & clearMSR13, WA); // rD = Masked MSR
|
||||
|
||||
LDR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF_SPR(SPR_SRR1)); // rB contains SRR1 here
|
||||
LDR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF_SPR(SPR_SRR1)); // rB contains SRR1 here
|
||||
|
||||
ANDI2R(WA, WA, mask & clearMSR13, WB); // rB contains masked SRR1 here
|
||||
ORR(WA, WA, WC); // rB = Masked MSR OR masked SRR1
|
||||
ANDI2R(WA, WA, mask & clearMSR13, WB); // rB contains masked SRR1 here
|
||||
ORR(WA, WA, WC); // rB = Masked MSR OR masked SRR1
|
||||
|
||||
STR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(msr)); // STR rB in to rA
|
||||
gpr.Unlock(WB, WC);
|
||||
STR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(msr)); // STR rB in to rA
|
||||
}
|
||||
|
||||
MSRUpdated(WA);
|
||||
|
||||
LDR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF_SPR(SPR_SRR0));
|
||||
|
||||
WriteExceptionExit(WA);
|
||||
gpr.Unlock(WA);
|
||||
}
|
||||
|
||||
template <bool condition>
|
||||
|
@ -144,10 +143,10 @@ void JitArm64::bx(UGeckoInstruction inst)
|
|||
INSTRUCTION_START
|
||||
JITDISABLE(bJITBranchOff);
|
||||
|
||||
ARM64Reg WA = ARM64Reg::INVALID_REG;
|
||||
Arm64GPRCache::ScopedARM64Reg WA = ARM64Reg::INVALID_REG;
|
||||
if (inst.LK)
|
||||
{
|
||||
WA = gpr.GetReg();
|
||||
WA = gpr.GetScopedReg();
|
||||
MOVI2R(WA, js.compilerPC + 4);
|
||||
STR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF_SPR(SPR_LR));
|
||||
}
|
||||
|
@ -156,13 +155,12 @@ void JitArm64::bx(UGeckoInstruction inst)
|
|||
{
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
const ARM64Reg WB = gpr.GetReg(), WC = gpr.GetReg();
|
||||
const auto WB = gpr.GetScopedReg(), WC = gpr.GetScopedReg();
|
||||
BitSet32 gpr_caller_save = gpr.GetCallerSavedUsed() & ~BitSet32{DecodeReg(WB), DecodeReg(WC)};
|
||||
if (WA != ARM64Reg::INVALID_REG && js.op->skipLRStack)
|
||||
gpr_caller_save[DecodeReg(WA)] = false;
|
||||
WriteBranchWatch<true>(js.compilerPC, js.op->branchTo, inst, WB, WC, gpr_caller_save,
|
||||
fpr.GetCallerSavedUsed());
|
||||
gpr.Unlock(WB, WC);
|
||||
}
|
||||
if (inst.LK && !js.op->skipLRStack)
|
||||
{
|
||||
|
@ -172,9 +170,6 @@ void JitArm64::bx(UGeckoInstruction inst)
|
|||
FakeLKExit(js.compilerPC + 4, WA);
|
||||
}
|
||||
|
||||
if (WA != ARM64Reg::INVALID_REG)
|
||||
gpr.Unlock(WA);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -184,13 +179,12 @@ void JitArm64::bx(UGeckoInstruction inst)
|
|||
if (js.op->branchIsIdleLoop)
|
||||
{
|
||||
if (WA == ARM64Reg::INVALID_REG)
|
||||
WA = gpr.GetReg();
|
||||
WA = gpr.GetScopedReg();
|
||||
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
const ARM64Reg WB = gpr.GetReg();
|
||||
const auto WB = gpr.GetScopedReg();
|
||||
WriteBranchWatch<true>(js.compilerPC, js.op->branchTo, inst, WA, WB, {}, {});
|
||||
gpr.Unlock(WB);
|
||||
}
|
||||
|
||||
// make idle loops go faster
|
||||
|
@ -198,7 +192,7 @@ void JitArm64::bx(UGeckoInstruction inst)
|
|||
|
||||
MOVP2R(XA, &CoreTiming::GlobalIdle);
|
||||
BLR(XA);
|
||||
gpr.Unlock(WA);
|
||||
WA.Unlock();
|
||||
|
||||
WriteExceptionExit(js.op->branchTo);
|
||||
return;
|
||||
|
@ -206,16 +200,12 @@ void JitArm64::bx(UGeckoInstruction inst)
|
|||
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
const ARM64Reg WB = gpr.GetReg(), WC = gpr.GetReg();
|
||||
const auto WB = gpr.GetScopedReg(), WC = gpr.GetScopedReg();
|
||||
const BitSet32 gpr_caller_save =
|
||||
WA != ARM64Reg::INVALID_REG ? BitSet32{DecodeReg(WA)} & CALLER_SAVED_GPRS : BitSet32{};
|
||||
WriteBranchWatch<true>(js.compilerPC, js.op->branchTo, inst, WB, WC, gpr_caller_save, {});
|
||||
gpr.Unlock(WB, WC);
|
||||
}
|
||||
WriteExit(js.op->branchTo, inst.LK, js.compilerPC + 4, WA);
|
||||
|
||||
if (WA != ARM64Reg::INVALID_REG)
|
||||
gpr.Unlock(WA);
|
||||
}
|
||||
|
||||
void JitArm64::bcx(UGeckoInstruction inst)
|
||||
|
@ -223,77 +213,79 @@ void JitArm64::bcx(UGeckoInstruction inst)
|
|||
INSTRUCTION_START
|
||||
JITDISABLE(bJITBranchOff);
|
||||
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
ARM64Reg WB = inst.LK || IsDebuggingEnabled() ? gpr.GetReg() : WA;
|
||||
ARM64Reg WC = IsDebuggingEnabled() && inst.LK && !js.op->branchIsIdleLoop ? gpr.GetReg() :
|
||||
ARM64Reg::INVALID_REG;
|
||||
auto WA = gpr.GetScopedReg();
|
||||
auto WB = inst.LK || IsDebuggingEnabled() ? gpr.GetScopedReg() :
|
||||
Arm64GPRCache::ScopedARM64Reg(WA.GetReg());
|
||||
|
||||
FixupBranch pCTRDontBranch;
|
||||
if ((inst.BO & BO_DONT_DECREMENT_FLAG) == 0) // Decrement and test CTR
|
||||
{
|
||||
LDR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF_SPR(SPR_CTR));
|
||||
SUBS(WA, WA, 1);
|
||||
STR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF_SPR(SPR_CTR));
|
||||
auto WC = IsDebuggingEnabled() && inst.LK && !js.op->branchIsIdleLoop ?
|
||||
gpr.GetScopedReg() :
|
||||
Arm64GPRCache::ScopedARM64Reg(ARM64Reg::INVALID_REG);
|
||||
|
||||
if (inst.BO & BO_BRANCH_IF_CTR_0)
|
||||
pCTRDontBranch = B(CC_NEQ);
|
||||
FixupBranch pCTRDontBranch;
|
||||
if ((inst.BO & BO_DONT_DECREMENT_FLAG) == 0) // Decrement and test CTR
|
||||
{
|
||||
LDR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF_SPR(SPR_CTR));
|
||||
SUBS(WA, WA, 1);
|
||||
STR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF_SPR(SPR_CTR));
|
||||
|
||||
if (inst.BO & BO_BRANCH_IF_CTR_0)
|
||||
pCTRDontBranch = B(CC_NEQ);
|
||||
else
|
||||
pCTRDontBranch = B(CC_EQ);
|
||||
}
|
||||
|
||||
FixupBranch pConditionDontBranch;
|
||||
|
||||
if ((inst.BO & BO_DONT_CHECK_CONDITION) == 0) // Test a CR bit
|
||||
{
|
||||
pConditionDontBranch =
|
||||
JumpIfCRFieldBit(inst.BI >> 2, 3 - (inst.BI & 3), !(inst.BO_2 & BO_BRANCH_IF_TRUE));
|
||||
}
|
||||
|
||||
if (inst.LK)
|
||||
{
|
||||
MOVI2R(WA, js.compilerPC + 4);
|
||||
STR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF_SPR(SPR_LR));
|
||||
}
|
||||
|
||||
gpr.Flush(FlushMode::MaintainState, WB);
|
||||
fpr.Flush(FlushMode::MaintainState, ARM64Reg::INVALID_REG);
|
||||
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
ARM64Reg bw_reg_a, bw_reg_b;
|
||||
// WC is only allocated when WA is needed for WriteExit and cannot be clobbered.
|
||||
if (WC == ARM64Reg::INVALID_REG)
|
||||
bw_reg_a = WA, bw_reg_b = WB;
|
||||
else
|
||||
bw_reg_a = WB, bw_reg_b = WC;
|
||||
const BitSet32 gpr_caller_save =
|
||||
gpr.GetCallerSavedUsed() & ~BitSet32{DecodeReg(bw_reg_a), DecodeReg(bw_reg_b)};
|
||||
WriteBranchWatch<true>(js.compilerPC, js.op->branchTo, inst, bw_reg_a, bw_reg_b,
|
||||
gpr_caller_save, fpr.GetCallerSavedUsed());
|
||||
}
|
||||
if (js.op->branchIsIdleLoop)
|
||||
{
|
||||
// make idle loops go faster
|
||||
ARM64Reg XA = EncodeRegTo64(WA);
|
||||
|
||||
MOVP2R(XA, &CoreTiming::GlobalIdle);
|
||||
BLR(XA);
|
||||
|
||||
WriteExceptionExit(js.op->branchTo);
|
||||
}
|
||||
else
|
||||
pCTRDontBranch = B(CC_EQ);
|
||||
{
|
||||
WriteExit(js.op->branchTo, inst.LK, js.compilerPC + 4, WA);
|
||||
}
|
||||
|
||||
if ((inst.BO & BO_DONT_CHECK_CONDITION) == 0)
|
||||
SetJumpTarget(pConditionDontBranch);
|
||||
if ((inst.BO & BO_DONT_DECREMENT_FLAG) == 0)
|
||||
SetJumpTarget(pCTRDontBranch);
|
||||
}
|
||||
|
||||
FixupBranch pConditionDontBranch;
|
||||
|
||||
if ((inst.BO & BO_DONT_CHECK_CONDITION) == 0) // Test a CR bit
|
||||
{
|
||||
pConditionDontBranch =
|
||||
JumpIfCRFieldBit(inst.BI >> 2, 3 - (inst.BI & 3), !(inst.BO_2 & BO_BRANCH_IF_TRUE));
|
||||
}
|
||||
|
||||
if (inst.LK)
|
||||
{
|
||||
MOVI2R(WA, js.compilerPC + 4);
|
||||
STR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF_SPR(SPR_LR));
|
||||
}
|
||||
|
||||
gpr.Flush(FlushMode::MaintainState, WB);
|
||||
fpr.Flush(FlushMode::MaintainState, ARM64Reg::INVALID_REG);
|
||||
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
ARM64Reg bw_reg_a, bw_reg_b;
|
||||
// WC is only allocated when WA is needed for WriteExit and cannot be clobbered.
|
||||
if (WC == ARM64Reg::INVALID_REG)
|
||||
bw_reg_a = WA, bw_reg_b = WB;
|
||||
else
|
||||
bw_reg_a = WB, bw_reg_b = WC;
|
||||
const BitSet32 gpr_caller_save =
|
||||
gpr.GetCallerSavedUsed() & ~BitSet32{DecodeReg(bw_reg_a), DecodeReg(bw_reg_b)};
|
||||
WriteBranchWatch<true>(js.compilerPC, js.op->branchTo, inst, bw_reg_a, bw_reg_b,
|
||||
gpr_caller_save, fpr.GetCallerSavedUsed());
|
||||
}
|
||||
if (js.op->branchIsIdleLoop)
|
||||
{
|
||||
// make idle loops go faster
|
||||
ARM64Reg XA = EncodeRegTo64(WA);
|
||||
|
||||
MOVP2R(XA, &CoreTiming::GlobalIdle);
|
||||
BLR(XA);
|
||||
|
||||
WriteExceptionExit(js.op->branchTo);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteExit(js.op->branchTo, inst.LK, js.compilerPC + 4, WA);
|
||||
}
|
||||
|
||||
if ((inst.BO & BO_DONT_CHECK_CONDITION) == 0)
|
||||
SetJumpTarget(pConditionDontBranch);
|
||||
if ((inst.BO & BO_DONT_DECREMENT_FLAG) == 0)
|
||||
SetJumpTarget(pCTRDontBranch);
|
||||
|
||||
if (WC != ARM64Reg::INVALID_REG)
|
||||
gpr.Unlock(WC);
|
||||
|
||||
if (!analyzer.HasOption(PPCAnalyst::PPCAnalyzer::OPTION_CONDITIONAL_CONTINUE))
|
||||
{
|
||||
gpr.Flush(FlushMode::All, WA);
|
||||
|
@ -311,10 +303,6 @@ void JitArm64::bcx(UGeckoInstruction inst)
|
|||
WriteBranchWatch<false>(js.compilerPC, js.compilerPC + 4, inst, WA, WB, gpr_caller_save,
|
||||
fpr.GetCallerSavedUsed());
|
||||
}
|
||||
|
||||
gpr.Unlock(WA);
|
||||
if (WB != WA)
|
||||
gpr.Unlock(WB);
|
||||
}
|
||||
|
||||
void JitArm64::bcctrx(UGeckoInstruction inst)
|
||||
|
@ -337,34 +325,29 @@ void JitArm64::bcctrx(UGeckoInstruction inst)
|
|||
gpr.Flush(FlushMode::All, ARM64Reg::INVALID_REG);
|
||||
fpr.Flush(FlushMode::All, ARM64Reg::INVALID_REG);
|
||||
|
||||
ARM64Reg WB = ARM64Reg::INVALID_REG;
|
||||
Arm64GPRCache::ScopedARM64Reg WB = ARM64Reg::INVALID_REG;
|
||||
if (inst.LK_3)
|
||||
{
|
||||
WB = gpr.GetReg();
|
||||
WB = gpr.GetScopedReg();
|
||||
MOVI2R(WB, js.compilerPC + 4);
|
||||
STR(IndexType::Unsigned, WB, PPC_REG, PPCSTATE_OFF_SPR(SPR_LR));
|
||||
}
|
||||
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
auto WA = gpr.GetScopedReg();
|
||||
|
||||
LDR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF_SPR(SPR_CTR));
|
||||
AND(WA, WA, LogicalImm(~0x3, GPRSize::B32));
|
||||
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
const ARM64Reg WC = gpr.GetReg(), WD = gpr.GetReg();
|
||||
const auto WC = gpr.GetScopedReg(), WD = gpr.GetScopedReg();
|
||||
BitSet32 gpr_caller_save = BitSet32{DecodeReg(WA)};
|
||||
if (WB != ARM64Reg::INVALID_REG)
|
||||
gpr_caller_save[DecodeReg(WB)] = true;
|
||||
gpr_caller_save &= CALLER_SAVED_GPRS;
|
||||
WriteBranchWatchDestInRegister(js.compilerPC, WA, inst, WC, WD, gpr_caller_save, {});
|
||||
gpr.Unlock(WC, WD);
|
||||
}
|
||||
WriteExit(WA, inst.LK_3, js.compilerPC + 4, WB);
|
||||
|
||||
if (WB != ARM64Reg::INVALID_REG)
|
||||
gpr.Unlock(WB);
|
||||
gpr.Unlock(WA);
|
||||
}
|
||||
|
||||
void JitArm64::bclrx(UGeckoInstruction inst)
|
||||
|
@ -375,85 +358,92 @@ void JitArm64::bclrx(UGeckoInstruction inst)
|
|||
bool conditional =
|
||||
(inst.BO & BO_DONT_DECREMENT_FLAG) == 0 || (inst.BO & BO_DONT_CHECK_CONDITION) == 0;
|
||||
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
ARM64Reg WB =
|
||||
conditional || inst.LK || IsDebuggingEnabled() ? gpr.GetReg() : ARM64Reg::INVALID_REG;
|
||||
ARM64Reg WC = IsDebuggingEnabled() ? gpr.GetReg() : ARM64Reg::INVALID_REG;
|
||||
|
||||
FixupBranch pCTRDontBranch;
|
||||
if ((inst.BO & BO_DONT_DECREMENT_FLAG) == 0) // Decrement and test CTR
|
||||
auto WA = gpr.GetScopedReg();
|
||||
Arm64GPRCache::ScopedARM64Reg WB;
|
||||
if (conditional || inst.LK || IsDebuggingEnabled())
|
||||
{
|
||||
LDR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF_SPR(SPR_CTR));
|
||||
SUBS(WA, WA, 1);
|
||||
STR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF_SPR(SPR_CTR));
|
||||
|
||||
if (inst.BO & BO_BRANCH_IF_CTR_0)
|
||||
pCTRDontBranch = B(CC_NEQ);
|
||||
else
|
||||
pCTRDontBranch = B(CC_EQ);
|
||||
WB = gpr.GetScopedReg();
|
||||
}
|
||||
|
||||
FixupBranch pConditionDontBranch;
|
||||
if ((inst.BO & BO_DONT_CHECK_CONDITION) == 0) // Test a CR bit
|
||||
{
|
||||
pConditionDontBranch =
|
||||
JumpIfCRFieldBit(inst.BI >> 2, 3 - (inst.BI & 3), !(inst.BO_2 & BO_BRANCH_IF_TRUE));
|
||||
}
|
||||
|
||||
LDR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF_SPR(SPR_LR));
|
||||
AND(WA, WA, LogicalImm(~0x3, GPRSize::B32));
|
||||
|
||||
if (inst.LK)
|
||||
{
|
||||
MOVI2R(WB, js.compilerPC + 4);
|
||||
STR(IndexType::Unsigned, WB, PPC_REG, PPCSTATE_OFF_SPR(SPR_LR));
|
||||
}
|
||||
|
||||
gpr.Flush(conditional ? FlushMode::MaintainState : FlushMode::All, WB);
|
||||
fpr.Flush(conditional ? FlushMode::MaintainState : FlushMode::All, ARM64Reg::INVALID_REG);
|
||||
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
BitSet32 gpr_caller_save;
|
||||
BitSet32 fpr_caller_save;
|
||||
if (conditional)
|
||||
Arm64GPRCache::ScopedARM64Reg WC;
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
gpr_caller_save = gpr.GetCallerSavedUsed() & ~BitSet32{DecodeReg(WB), DecodeReg(WC)};
|
||||
if (js.op->branchIsIdleLoop)
|
||||
gpr_caller_save[DecodeReg(WA)] = false;
|
||||
fpr_caller_save = fpr.GetCallerSavedUsed();
|
||||
WC = gpr.GetScopedReg();
|
||||
}
|
||||
|
||||
FixupBranch pCTRDontBranch;
|
||||
if ((inst.BO & BO_DONT_DECREMENT_FLAG) == 0) // Decrement and test CTR
|
||||
{
|
||||
LDR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF_SPR(SPR_CTR));
|
||||
SUBS(WA, WA, 1);
|
||||
STR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF_SPR(SPR_CTR));
|
||||
|
||||
if (inst.BO & BO_BRANCH_IF_CTR_0)
|
||||
pCTRDontBranch = B(CC_NEQ);
|
||||
else
|
||||
pCTRDontBranch = B(CC_EQ);
|
||||
}
|
||||
|
||||
FixupBranch pConditionDontBranch;
|
||||
if ((inst.BO & BO_DONT_CHECK_CONDITION) == 0) // Test a CR bit
|
||||
{
|
||||
pConditionDontBranch =
|
||||
JumpIfCRFieldBit(inst.BI >> 2, 3 - (inst.BI & 3), !(inst.BO_2 & BO_BRANCH_IF_TRUE));
|
||||
}
|
||||
|
||||
LDR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF_SPR(SPR_LR));
|
||||
AND(WA, WA, LogicalImm(~0x3, GPRSize::B32));
|
||||
|
||||
if (inst.LK)
|
||||
{
|
||||
MOVI2R(WB, js.compilerPC + 4);
|
||||
STR(IndexType::Unsigned, WB, PPC_REG, PPCSTATE_OFF_SPR(SPR_LR));
|
||||
}
|
||||
|
||||
gpr.Flush(conditional ? FlushMode::MaintainState : FlushMode::All, WB);
|
||||
fpr.Flush(conditional ? FlushMode::MaintainState : FlushMode::All, ARM64Reg::INVALID_REG);
|
||||
|
||||
if (IsDebuggingEnabled())
|
||||
{
|
||||
BitSet32 gpr_caller_save;
|
||||
BitSet32 fpr_caller_save;
|
||||
if (conditional)
|
||||
{
|
||||
gpr_caller_save = gpr.GetCallerSavedUsed() & ~BitSet32{DecodeReg(WB), DecodeReg(WC)};
|
||||
if (js.op->branchIsIdleLoop)
|
||||
gpr_caller_save[DecodeReg(WA)] = false;
|
||||
fpr_caller_save = fpr.GetCallerSavedUsed();
|
||||
}
|
||||
else
|
||||
{
|
||||
gpr_caller_save =
|
||||
js.op->branchIsIdleLoop ? BitSet32{} : BitSet32{DecodeReg(WA)} & CALLER_SAVED_GPRS;
|
||||
fpr_caller_save = {};
|
||||
}
|
||||
WriteBranchWatchDestInRegister(js.compilerPC, WA, inst, WB, WC, gpr_caller_save,
|
||||
fpr_caller_save);
|
||||
}
|
||||
if (js.op->branchIsIdleLoop)
|
||||
{
|
||||
// make idle loops go faster
|
||||
ARM64Reg XA = EncodeRegTo64(WA);
|
||||
|
||||
MOVP2R(XA, &CoreTiming::GlobalIdle);
|
||||
BLR(XA);
|
||||
|
||||
WriteExceptionExit(js.op->branchTo);
|
||||
}
|
||||
else
|
||||
{
|
||||
gpr_caller_save =
|
||||
js.op->branchIsIdleLoop ? BitSet32{} : BitSet32{DecodeReg(WA)} & CALLER_SAVED_GPRS;
|
||||
fpr_caller_save = {};
|
||||
WriteBLRExit(WA);
|
||||
}
|
||||
WriteBranchWatchDestInRegister(js.compilerPC, WA, inst, WB, WC, gpr_caller_save,
|
||||
fpr_caller_save);
|
||||
|
||||
if ((inst.BO & BO_DONT_CHECK_CONDITION) == 0)
|
||||
SetJumpTarget(pConditionDontBranch);
|
||||
if ((inst.BO & BO_DONT_DECREMENT_FLAG) == 0)
|
||||
SetJumpTarget(pCTRDontBranch);
|
||||
}
|
||||
if (js.op->branchIsIdleLoop)
|
||||
{
|
||||
// make idle loops go faster
|
||||
ARM64Reg XA = EncodeRegTo64(WA);
|
||||
|
||||
MOVP2R(XA, &CoreTiming::GlobalIdle);
|
||||
BLR(XA);
|
||||
|
||||
WriteExceptionExit(js.op->branchTo);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteBLRExit(WA);
|
||||
}
|
||||
|
||||
if ((inst.BO & BO_DONT_CHECK_CONDITION) == 0)
|
||||
SetJumpTarget(pConditionDontBranch);
|
||||
if ((inst.BO & BO_DONT_DECREMENT_FLAG) == 0)
|
||||
SetJumpTarget(pCTRDontBranch);
|
||||
|
||||
if (WC != ARM64Reg::INVALID_REG)
|
||||
gpr.Unlock(WC);
|
||||
|
||||
if (!analyzer.HasOption(PPCAnalyst::PPCAnalyzer::OPTION_CONDITIONAL_CONTINUE))
|
||||
{
|
||||
|
@ -472,8 +462,4 @@ void JitArm64::bclrx(UGeckoInstruction inst)
|
|||
WriteBranchWatch<false>(js.compilerPC, js.compilerPC + 4, inst, WA, WB, gpr_caller_save,
|
||||
fpr.GetCallerSavedUsed());
|
||||
}
|
||||
|
||||
gpr.Unlock(WA);
|
||||
if (WB != ARM64Reg::INVALID_REG)
|
||||
gpr.Unlock(WB);
|
||||
}
|
||||
|
|
|
@ -102,154 +102,151 @@ void JitArm64::fp_arith(UGeckoInstruction inst)
|
|||
const ARM64Reg VC = use_c ? reg_encoder(fpr.R(c, type)) : ARM64Reg::INVALID_REG;
|
||||
const ARM64Reg VD = reg_encoder(fpr.RW(d, type_out));
|
||||
|
||||
ARM64Reg V0Q = ARM64Reg::INVALID_REG;
|
||||
ARM64Reg V1Q = ARM64Reg::INVALID_REG;
|
||||
|
||||
ARM64Reg rounded_c_reg = VC;
|
||||
if (round_c)
|
||||
{
|
||||
ASSERT_MSG(DYNA_REC, !inputs_are_singles, "Tried to apply 25-bit precision to single");
|
||||
Arm64FPRCache::ScopedARM64Reg V0Q = ARM64Reg::INVALID_REG;
|
||||
Arm64FPRCache::ScopedARM64Reg V1Q = ARM64Reg::INVALID_REG;
|
||||
|
||||
V0Q = fpr.GetReg();
|
||||
rounded_c_reg = reg_encoder(V0Q);
|
||||
Force25BitPrecision(rounded_c_reg, VC);
|
||||
}
|
||||
|
||||
ARM64Reg inaccurate_fma_reg = VD;
|
||||
if (fma && inaccurate_fma && VD == VB)
|
||||
{
|
||||
if (V0Q == ARM64Reg::INVALID_REG)
|
||||
V0Q = fpr.GetReg();
|
||||
inaccurate_fma_reg = reg_encoder(V0Q);
|
||||
}
|
||||
|
||||
ARM64Reg result_reg = VD;
|
||||
const bool preserve_d =
|
||||
m_accurate_nans && (VD == VA || (use_b && VD == VB) || (use_c && VD == VC));
|
||||
if (preserve_d)
|
||||
{
|
||||
V1Q = fpr.GetReg();
|
||||
result_reg = reg_encoder(V1Q);
|
||||
}
|
||||
|
||||
switch (op5)
|
||||
{
|
||||
case 18:
|
||||
m_float_emit.FDIV(result_reg, VA, VB);
|
||||
break;
|
||||
case 20:
|
||||
m_float_emit.FSUB(result_reg, VA, VB);
|
||||
break;
|
||||
case 21:
|
||||
m_float_emit.FADD(result_reg, VA, VB);
|
||||
break;
|
||||
case 25:
|
||||
m_float_emit.FMUL(result_reg, VA, rounded_c_reg);
|
||||
break;
|
||||
// While it may seem like PowerPC's nmadd/nmsub map to AArch64's nmadd/msub [sic],
|
||||
// the subtly different definitions affect how signed zeroes are handled.
|
||||
// Also, PowerPC's nmadd/nmsub perform rounding before the final negation.
|
||||
// So, we negate using a separate FNEG instruction instead of using AArch64's nmadd/msub.
|
||||
case 28: // fmsub: "D = A*C - B" vs "Vd = (-Va) + Vn*Vm"
|
||||
case 30: // fnmsub: "D = -(A*C - B)" vs "Vd = -((-Va) + Vn*Vm)"
|
||||
if (inaccurate_fma)
|
||||
ARM64Reg rounded_c_reg = VC;
|
||||
if (round_c)
|
||||
{
|
||||
m_float_emit.FMUL(inaccurate_fma_reg, VA, rounded_c_reg);
|
||||
m_float_emit.FSUB(result_reg, inaccurate_fma_reg, VB);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_float_emit.FNMSUB(result_reg, VA, rounded_c_reg, VB);
|
||||
}
|
||||
break;
|
||||
case 29: // fmadd: "D = A*C + B" vs "Vd = Va + Vn*Vm"
|
||||
case 31: // fnmadd: "D = -(A*C + B)" vs "Vd = -(Va + Vn*Vm)"
|
||||
if (inaccurate_fma)
|
||||
{
|
||||
m_float_emit.FMUL(inaccurate_fma_reg, VA, rounded_c_reg);
|
||||
m_float_emit.FADD(result_reg, inaccurate_fma_reg, VB);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_float_emit.FMADD(result_reg, VA, rounded_c_reg, VB);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ASSERT_MSG(DYNA_REC, 0, "fp_arith");
|
||||
break;
|
||||
}
|
||||
ASSERT_MSG(DYNA_REC, !inputs_are_singles, "Tried to apply 25-bit precision to single");
|
||||
|
||||
Common::SmallVector<FixupBranch, 4> nan_fixups;
|
||||
if (m_accurate_nans)
|
||||
{
|
||||
// Check if we need to handle NaNs
|
||||
m_float_emit.FCMP(result_reg);
|
||||
FixupBranch no_nan = B(CCFlags::CC_VC);
|
||||
FixupBranch nan = B();
|
||||
SetJumpTarget(no_nan);
|
||||
|
||||
SwitchToFarCode();
|
||||
SetJumpTarget(nan);
|
||||
|
||||
Common::SmallVector<ARM64Reg, 3> inputs;
|
||||
inputs.push_back(VA);
|
||||
if (use_b && VA != VB)
|
||||
inputs.push_back(VB);
|
||||
if (use_c && VA != VC && (!use_b || VB != VC))
|
||||
inputs.push_back(VC);
|
||||
|
||||
// If any inputs are NaNs, pick the first NaN of them and set its quiet bit.
|
||||
// However, we can skip checking the last input, because if exactly one input is NaN, AArch64
|
||||
// arithmetic instructions automatically pick that NaN and make it quiet, just like we want.
|
||||
for (size_t i = 0; i < inputs.size() - 1; ++i)
|
||||
{
|
||||
const ARM64Reg input = inputs[i];
|
||||
|
||||
m_float_emit.FCMP(input);
|
||||
FixupBranch skip = B(CCFlags::CC_VC);
|
||||
|
||||
// Make the NaN quiet
|
||||
m_float_emit.FADD(VD, input, input);
|
||||
|
||||
nan_fixups.push_back(B());
|
||||
|
||||
SetJumpTarget(skip);
|
||||
V0Q = fpr.GetScopedReg();
|
||||
rounded_c_reg = reg_encoder(V0Q);
|
||||
Force25BitPrecision(rounded_c_reg, VC);
|
||||
}
|
||||
|
||||
std::optional<FixupBranch> nan_early_fixup;
|
||||
ARM64Reg inaccurate_fma_reg = VD;
|
||||
if (fma && inaccurate_fma && VD == VB)
|
||||
{
|
||||
if (V0Q == ARM64Reg::INVALID_REG)
|
||||
V0Q = fpr.GetScopedReg();
|
||||
inaccurate_fma_reg = reg_encoder(V0Q);
|
||||
}
|
||||
|
||||
ARM64Reg result_reg = VD;
|
||||
const bool preserve_d =
|
||||
m_accurate_nans && (VD == VA || (use_b && VD == VB) || (use_c && VD == VC));
|
||||
if (preserve_d)
|
||||
{
|
||||
V1Q = fpr.GetScopedReg();
|
||||
result_reg = reg_encoder(V1Q);
|
||||
}
|
||||
|
||||
switch (op5)
|
||||
{
|
||||
case 18:
|
||||
m_float_emit.FDIV(result_reg, VA, VB);
|
||||
break;
|
||||
case 20:
|
||||
m_float_emit.FSUB(result_reg, VA, VB);
|
||||
break;
|
||||
case 21:
|
||||
m_float_emit.FADD(result_reg, VA, VB);
|
||||
break;
|
||||
case 25:
|
||||
m_float_emit.FMUL(result_reg, VA, rounded_c_reg);
|
||||
break;
|
||||
// While it may seem like PowerPC's nmadd/nmsub map to AArch64's nmadd/msub [sic],
|
||||
// the subtly different definitions affect how signed zeroes are handled.
|
||||
// Also, PowerPC's nmadd/nmsub perform rounding before the final negation.
|
||||
// So, we negate using a separate FNEG instruction instead of using AArch64's nmadd/msub.
|
||||
case 28: // fmsub: "D = A*C - B" vs "Vd = (-Va) + Vn*Vm"
|
||||
case 30: // fnmsub: "D = -(A*C - B)" vs "Vd = -((-Va) + Vn*Vm)"
|
||||
if (inaccurate_fma)
|
||||
{
|
||||
m_float_emit.FMUL(inaccurate_fma_reg, VA, rounded_c_reg);
|
||||
m_float_emit.FSUB(result_reg, inaccurate_fma_reg, VB);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_float_emit.FNMSUB(result_reg, VA, rounded_c_reg, VB);
|
||||
}
|
||||
break;
|
||||
case 29: // fmadd: "D = A*C + B" vs "Vd = Va + Vn*Vm"
|
||||
case 31: // fnmadd: "D = -(A*C + B)" vs "Vd = -(Va + Vn*Vm)"
|
||||
if (inaccurate_fma)
|
||||
{
|
||||
m_float_emit.FMUL(inaccurate_fma_reg, VA, rounded_c_reg);
|
||||
m_float_emit.FADD(result_reg, inaccurate_fma_reg, VB);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_float_emit.FMADD(result_reg, VA, rounded_c_reg, VB);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ASSERT_MSG(DYNA_REC, 0, "fp_arith");
|
||||
break;
|
||||
}
|
||||
|
||||
Common::SmallVector<FixupBranch, 4> nan_fixups;
|
||||
if (m_accurate_nans)
|
||||
{
|
||||
// Check if we need to handle NaNs
|
||||
m_float_emit.FCMP(result_reg);
|
||||
FixupBranch no_nan = B(CCFlags::CC_VC);
|
||||
FixupBranch nan = B();
|
||||
SetJumpTarget(no_nan);
|
||||
|
||||
SwitchToFarCode();
|
||||
SetJumpTarget(nan);
|
||||
|
||||
Common::SmallVector<ARM64Reg, 3> inputs;
|
||||
inputs.push_back(VA);
|
||||
if (use_b && VA != VB)
|
||||
inputs.push_back(VB);
|
||||
if (use_c && VA != VC && (!use_b || VB != VC))
|
||||
inputs.push_back(VC);
|
||||
|
||||
// If any inputs are NaNs, pick the first NaN of them and set its quiet bit.
|
||||
// However, we can skip checking the last input, because if exactly one input is NaN, AArch64
|
||||
// arithmetic instructions automatically pick that NaN and make it quiet, just like we want.
|
||||
for (size_t i = 0; i < inputs.size() - 1; ++i)
|
||||
{
|
||||
const ARM64Reg input = inputs[i];
|
||||
|
||||
m_float_emit.FCMP(input);
|
||||
FixupBranch skip = B(CCFlags::CC_VC);
|
||||
|
||||
// Make the NaN quiet
|
||||
m_float_emit.FADD(VD, input, input);
|
||||
|
||||
nan_fixups.push_back(B());
|
||||
|
||||
SetJumpTarget(skip);
|
||||
}
|
||||
|
||||
std::optional<FixupBranch> nan_early_fixup;
|
||||
if (negate_result)
|
||||
{
|
||||
// If we have a NaN, we must not execute FNEG.
|
||||
if (result_reg != VD)
|
||||
m_float_emit.MOV(EncodeRegToDouble(VD), EncodeRegToDouble(result_reg));
|
||||
nan_fixups.push_back(B());
|
||||
}
|
||||
else
|
||||
{
|
||||
nan_early_fixup = B();
|
||||
}
|
||||
|
||||
SwitchToNearCode();
|
||||
|
||||
if (nan_early_fixup)
|
||||
SetJumpTarget(*nan_early_fixup);
|
||||
}
|
||||
|
||||
// PowerPC's nmadd/nmsub perform rounding before the final negation, which is not the case
|
||||
// for any of AArch64's FMA instructions, so we negate using a separate instruction.
|
||||
if (negate_result)
|
||||
{
|
||||
// If we have a NaN, we must not execute FNEG.
|
||||
if (result_reg != VD)
|
||||
m_float_emit.MOV(EncodeRegToDouble(VD), EncodeRegToDouble(result_reg));
|
||||
nan_fixups.push_back(B());
|
||||
}
|
||||
else
|
||||
{
|
||||
nan_early_fixup = B();
|
||||
}
|
||||
m_float_emit.FNEG(VD, result_reg);
|
||||
else if (result_reg != VD)
|
||||
m_float_emit.MOV(EncodeRegToDouble(VD), EncodeRegToDouble(result_reg));
|
||||
|
||||
SwitchToNearCode();
|
||||
|
||||
if (nan_early_fixup)
|
||||
SetJumpTarget(*nan_early_fixup);
|
||||
for (FixupBranch fixup : nan_fixups)
|
||||
SetJumpTarget(fixup);
|
||||
}
|
||||
|
||||
// PowerPC's nmadd/nmsub perform rounding before the final negation, which is not the case
|
||||
// for any of AArch64's FMA instructions, so we negate using a separate instruction.
|
||||
if (negate_result)
|
||||
m_float_emit.FNEG(VD, result_reg);
|
||||
else if (result_reg != VD)
|
||||
m_float_emit.MOV(EncodeRegToDouble(VD), EncodeRegToDouble(result_reg));
|
||||
|
||||
for (FixupBranch fixup : nan_fixups)
|
||||
SetJumpTarget(fixup);
|
||||
|
||||
if (V0Q != ARM64Reg::INVALID_REG)
|
||||
fpr.Unlock(V0Q);
|
||||
if (V1Q != ARM64Reg::INVALID_REG)
|
||||
fpr.Unlock(V1Q);
|
||||
|
||||
if (output_is_single)
|
||||
{
|
||||
ASSERT_MSG(DYNA_REC, inputs_are_singles == inputs_are_singles_func(),
|
||||
|
@ -449,43 +446,40 @@ void JitArm64::FloatCompare(UGeckoInstruction inst, bool upper)
|
|||
gpr.BindCRToRegister(crf, false);
|
||||
const ARM64Reg XA = gpr.CR(crf);
|
||||
|
||||
ARM64Reg fpscr_reg = ARM64Reg::INVALID_REG;
|
||||
Arm64GPRCache::ScopedARM64Reg fpscr_reg = ARM64Reg::INVALID_REG;
|
||||
if (fprf)
|
||||
{
|
||||
fpscr_reg = gpr.GetReg();
|
||||
fpscr_reg = gpr.GetScopedReg();
|
||||
LDR(IndexType::Unsigned, fpscr_reg, PPC_REG, PPCSTATE_OFF(fpscr));
|
||||
AND(fpscr_reg, fpscr_reg, LogicalImm(~FPCC_MASK, GPRSize::B32));
|
||||
}
|
||||
|
||||
ARM64Reg V0Q = ARM64Reg::INVALID_REG;
|
||||
ARM64Reg V1Q = ARM64Reg::INVALID_REG;
|
||||
if (upper_a)
|
||||
{
|
||||
V0Q = fpr.GetReg();
|
||||
m_float_emit.DUP(singles ? 32 : 64, paired_reg_encoder(V0Q), paired_reg_encoder(VA), 1);
|
||||
VA = reg_encoder(V0Q);
|
||||
}
|
||||
if (upper_b)
|
||||
{
|
||||
if (a == b)
|
||||
Arm64FPRCache::ScopedARM64Reg V0Q;
|
||||
Arm64FPRCache::ScopedARM64Reg V1Q;
|
||||
if (upper_a)
|
||||
{
|
||||
VB = VA;
|
||||
V0Q = fpr.GetScopedReg();
|
||||
m_float_emit.DUP(singles ? 32 : 64, paired_reg_encoder(V0Q), paired_reg_encoder(VA), 1);
|
||||
VA = reg_encoder(V0Q);
|
||||
}
|
||||
else
|
||||
if (upper_b)
|
||||
{
|
||||
V1Q = fpr.GetReg();
|
||||
m_float_emit.DUP(singles ? 32 : 64, paired_reg_encoder(V1Q), paired_reg_encoder(VB), 1);
|
||||
VB = reg_encoder(V1Q);
|
||||
if (a == b)
|
||||
{
|
||||
VB = VA;
|
||||
}
|
||||
else
|
||||
{
|
||||
V1Q = fpr.GetScopedReg();
|
||||
m_float_emit.DUP(singles ? 32 : 64, paired_reg_encoder(V1Q), paired_reg_encoder(VB), 1);
|
||||
VB = reg_encoder(V1Q);
|
||||
}
|
||||
}
|
||||
|
||||
m_float_emit.FCMP(VA, VB);
|
||||
}
|
||||
|
||||
m_float_emit.FCMP(VA, VB);
|
||||
|
||||
if (V0Q != ARM64Reg::INVALID_REG)
|
||||
fpr.Unlock(V0Q);
|
||||
if (V1Q != ARM64Reg::INVALID_REG)
|
||||
fpr.Unlock(V1Q);
|
||||
|
||||
FixupBranch pNaN, pLesser, pGreater;
|
||||
FixupBranch continue1, continue2, continue3;
|
||||
|
||||
|
@ -538,7 +532,6 @@ void JitArm64::FloatCompare(UGeckoInstruction inst, bool upper)
|
|||
if (fprf)
|
||||
{
|
||||
STR(IndexType::Unsigned, fpscr_reg, PPC_REG, PPCSTATE_OFF(fpscr));
|
||||
gpr.Unlock(fpscr_reg);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -572,7 +565,7 @@ void JitArm64::fctiwx(UGeckoInstruction inst)
|
|||
|
||||
if (single)
|
||||
{
|
||||
const ARM64Reg V0 = fpr.GetReg();
|
||||
const auto V0 = fpr.GetScopedReg();
|
||||
|
||||
if (is_fctiwzx)
|
||||
{
|
||||
|
@ -589,12 +582,10 @@ void JitArm64::fctiwx(UGeckoInstruction inst)
|
|||
m_float_emit.BIC(16, EncodeRegToDouble(V0), 0x7);
|
||||
|
||||
m_float_emit.ORR(EncodeRegToDouble(VD), EncodeRegToDouble(VD), EncodeRegToDouble(V0));
|
||||
|
||||
fpr.Unlock(V0);
|
||||
}
|
||||
else
|
||||
{
|
||||
const ARM64Reg WA = gpr.GetReg();
|
||||
const auto WA = gpr.GetScopedReg();
|
||||
|
||||
if (is_fctiwzx)
|
||||
{
|
||||
|
@ -608,8 +599,6 @@ void JitArm64::fctiwx(UGeckoInstruction inst)
|
|||
|
||||
ORR(EncodeRegTo64(WA), EncodeRegTo64(WA), LogicalImm(0xFFF8'0000'0000'0000ULL, GPRSize::B64));
|
||||
m_float_emit.FMOV(EncodeRegToDouble(VD), EncodeRegTo64(WA));
|
||||
|
||||
gpr.Unlock(WA);
|
||||
}
|
||||
|
||||
ASSERT_MSG(DYNA_REC, b == d || single == fpr.IsSingle(b, true),
|
||||
|
|
|
@ -86,10 +86,9 @@ void JitArm64::LoadCarry()
|
|||
{
|
||||
case CarryFlag::InPPCState:
|
||||
{
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
auto WA = gpr.GetScopedReg();
|
||||
LDRB(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(xer_ca));
|
||||
CMP(WA, 1);
|
||||
gpr.Unlock(WA);
|
||||
break;
|
||||
}
|
||||
case CarryFlag::InHostCarry:
|
||||
|
@ -119,18 +118,16 @@ void JitArm64::FlushCarry()
|
|||
}
|
||||
case CarryFlag::InHostCarry:
|
||||
{
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
auto WA = gpr.GetScopedReg();
|
||||
CSET(WA, CC_CS);
|
||||
STRB(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(xer_ca));
|
||||
gpr.Unlock(WA);
|
||||
break;
|
||||
}
|
||||
case CarryFlag::ConstantTrue:
|
||||
{
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
auto WA = gpr.GetScopedReg();
|
||||
MOVI2R(WA, 1);
|
||||
STRB(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(xer_ca));
|
||||
gpr.Unlock(WA);
|
||||
break;
|
||||
}
|
||||
case CarryFlag::ConstantFalse:
|
||||
|
@ -155,9 +152,10 @@ void JitArm64::reg_imm(u32 d, u32 a, u32 value, u32 (*do_op)(u32, u32),
|
|||
else
|
||||
{
|
||||
gpr.BindToRegister(d, d == a);
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
(this->*op)(gpr.R(d), gpr.R(a), value, WA);
|
||||
gpr.Unlock(WA);
|
||||
{
|
||||
auto WA = gpr.GetScopedReg();
|
||||
(this->*op)(gpr.R(d), gpr.R(a), value, WA);
|
||||
}
|
||||
|
||||
if (Rc)
|
||||
ComputeRC0(gpr.R(d));
|
||||
|
@ -245,9 +243,8 @@ void JitArm64::addix(UGeckoInstruction inst)
|
|||
{
|
||||
gpr.BindToRegister(d, d == a);
|
||||
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
auto WA = gpr.GetScopedReg();
|
||||
ADDI2R(gpr.R(d), gpr.R(a), imm, WA);
|
||||
gpr.Unlock(WA);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -544,9 +541,10 @@ void JitArm64::addx(UGeckoInstruction inst)
|
|||
int imm_value = gpr.GetImm(imm_reg);
|
||||
|
||||
gpr.BindToRegister(d, d == in_reg);
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
ADDI2R(gpr.R(d), gpr.R(in_reg), imm_value, WA);
|
||||
gpr.Unlock(WA);
|
||||
{
|
||||
auto WA = gpr.GetScopedReg();
|
||||
ADDI2R(gpr.R(d), gpr.R(in_reg), imm_value, WA);
|
||||
}
|
||||
if (inst.Rc)
|
||||
ComputeRC0(gpr.R(d));
|
||||
}
|
||||
|
@ -722,9 +720,8 @@ void JitArm64::cmpi(UGeckoInstruction inst)
|
|||
|
||||
if (B != 0)
|
||||
{
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
auto WA = gpr.GetScopedReg();
|
||||
SUBI2R(CR, CR, B, EncodeRegTo64(WA));
|
||||
gpr.Unlock(WA);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -796,10 +793,9 @@ void JitArm64::rlwinmx_internal(UGeckoInstruction inst, u32 sh)
|
|||
}
|
||||
else
|
||||
{
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
auto WA = gpr.GetScopedReg();
|
||||
MOVI2R(WA, mask);
|
||||
AND(gpr.R(a), WA, gpr.R(s), ArithOption(gpr.R(s), ShiftType::ROR, 32 - sh));
|
||||
gpr.Unlock(WA);
|
||||
}
|
||||
|
||||
if (inst.Rc)
|
||||
|
@ -829,11 +825,12 @@ void JitArm64::rlwnmx(UGeckoInstruction inst)
|
|||
const u32 mask = MakeRotationMask(inst.MB, inst.ME);
|
||||
|
||||
gpr.BindToRegister(a, a == s || a == b);
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
NEG(WA, gpr.R(b));
|
||||
RORV(gpr.R(a), gpr.R(s), WA);
|
||||
ANDI2R(gpr.R(a), gpr.R(a), mask, WA);
|
||||
gpr.Unlock(WA);
|
||||
{
|
||||
auto WA = gpr.GetScopedReg();
|
||||
NEG(WA, gpr.R(b));
|
||||
RORV(gpr.R(a), gpr.R(s), WA);
|
||||
ANDI2R(gpr.R(a), gpr.R(a), mask, WA);
|
||||
}
|
||||
|
||||
if (inst.Rc)
|
||||
ComputeRC0(gpr.R(a));
|
||||
|
@ -878,8 +875,8 @@ void JitArm64::srawix(UGeckoInstruction inst)
|
|||
|
||||
if (js.op->wantsCA)
|
||||
{
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
ARM64Reg dest = inplace_carry ? WA : ARM64Reg::WSP;
|
||||
auto WA = gpr.GetScopedReg();
|
||||
ARM64Reg dest = inplace_carry ? ARM64Reg(WA) : ARM64Reg::WSP;
|
||||
if (a != s)
|
||||
{
|
||||
ASR(RA, RS, amount);
|
||||
|
@ -901,7 +898,6 @@ void JitArm64::srawix(UGeckoInstruction inst)
|
|||
CSINC(WA, ARM64Reg::WSP, ARM64Reg::WSP, CC_EQ);
|
||||
ComputeCarry(WA);
|
||||
}
|
||||
gpr.Unlock(WA);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -936,9 +932,10 @@ void JitArm64::addic(UGeckoInstruction inst)
|
|||
else
|
||||
{
|
||||
gpr.BindToRegister(d, d == a);
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
CARRY_IF_NEEDED(ADDI2R, ADDSI2R, gpr.R(d), gpr.R(a), simm, WA);
|
||||
gpr.Unlock(WA);
|
||||
{
|
||||
auto WA = gpr.GetScopedReg();
|
||||
CARRY_IF_NEEDED(ADDI2R, ADDSI2R, gpr.R(d), gpr.R(a), simm, WA);
|
||||
}
|
||||
|
||||
ComputeCarry();
|
||||
if (rc)
|
||||
|
@ -1037,12 +1034,10 @@ void JitArm64::mulli(UGeckoInstruction inst)
|
|||
gpr.BindToRegister(d, allocate_reg);
|
||||
|
||||
// Reuse d to hold the immediate if possible, allocate a register otherwise.
|
||||
ARM64Reg WA = allocate_reg ? gpr.GetReg() : gpr.R(d);
|
||||
auto WA = allocate_reg ? gpr.GetScopedReg() : Arm64GPRCache::ScopedARM64Reg(gpr.R(d));
|
||||
|
||||
MOVI2R(WA, (u32)(s32)inst.SIMM_16);
|
||||
MUL(gpr.R(d), gpr.R(a), WA);
|
||||
if (allocate_reg)
|
||||
gpr.Unlock(WA);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1137,16 +1132,16 @@ void JitArm64::addzex(UGeckoInstruction inst)
|
|||
{
|
||||
case CarryFlag::InPPCState:
|
||||
{
|
||||
gpr.BindToRegister(d, d == a);
|
||||
ARM64Reg WA = d == a ? gpr.GetReg() : gpr.R(d);
|
||||
const bool allocate_reg = d == a;
|
||||
gpr.BindToRegister(d, allocate_reg);
|
||||
|
||||
{
|
||||
auto WA = allocate_reg ? gpr.GetScopedReg() : Arm64GPRCache::ScopedARM64Reg(gpr.R(d));
|
||||
LDRB(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(xer_ca));
|
||||
CARRY_IF_NEEDED(ADD, ADDS, gpr.R(d), gpr.R(a), WA);
|
||||
}
|
||||
|
||||
LDRB(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(xer_ca));
|
||||
CARRY_IF_NEEDED(ADD, ADDS, gpr.R(d), gpr.R(a), WA);
|
||||
ComputeCarry();
|
||||
|
||||
if (d == a)
|
||||
gpr.Unlock(WA);
|
||||
|
||||
break;
|
||||
}
|
||||
case CarryFlag::InHostCarry:
|
||||
|
@ -1229,18 +1224,16 @@ void JitArm64::subfex(UGeckoInstruction inst)
|
|||
{
|
||||
case CarryFlag::InPPCState:
|
||||
{
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
auto WA = gpr.GetScopedReg();
|
||||
LDRB(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(xer_ca));
|
||||
ADDI2R(gpr.R(d), WA, ~i + j, gpr.R(d));
|
||||
gpr.Unlock(WA);
|
||||
break;
|
||||
}
|
||||
case CarryFlag::InHostCarry:
|
||||
{
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
auto WA = gpr.GetScopedReg();
|
||||
MOVI2R(WA, ~i + j);
|
||||
ADC(gpr.R(d), WA, ARM64Reg::WZR);
|
||||
gpr.Unlock(WA);
|
||||
break;
|
||||
}
|
||||
case CarryFlag::ConstantTrue:
|
||||
|
@ -1274,23 +1267,30 @@ void JitArm64::subfex(UGeckoInstruction inst)
|
|||
else
|
||||
{
|
||||
gpr.BindToRegister(d, d == a || d == b);
|
||||
ARM64Reg RB = mex ? gpr.GetReg() : gpr.R(b);
|
||||
if (mex)
|
||||
MOVI2R(RB, -1);
|
||||
{
|
||||
Arm64GPRCache::ScopedARM64Reg RB;
|
||||
if (mex)
|
||||
{
|
||||
RB = gpr.GetScopedReg();
|
||||
MOVI2R(RB, -1);
|
||||
}
|
||||
else
|
||||
{
|
||||
RB = gpr.R(b);
|
||||
}
|
||||
|
||||
if (js.carryFlag == CarryFlag::ConstantTrue)
|
||||
{
|
||||
CARRY_IF_NEEDED(SUB, SUBS, gpr.R(d), RB, gpr.R(a));
|
||||
}
|
||||
else
|
||||
{
|
||||
LoadCarry();
|
||||
CARRY_IF_NEEDED(SBC, SBCS, gpr.R(d), RB, gpr.R(a));
|
||||
if (js.carryFlag == CarryFlag::ConstantTrue)
|
||||
{
|
||||
CARRY_IF_NEEDED(SUB, SUBS, gpr.R(d), RB, gpr.R(a));
|
||||
}
|
||||
else
|
||||
{
|
||||
LoadCarry();
|
||||
CARRY_IF_NEEDED(SBC, SBCS, gpr.R(d), RB, gpr.R(a));
|
||||
}
|
||||
}
|
||||
|
||||
ComputeCarry();
|
||||
if (mex)
|
||||
gpr.Unlock(RB);
|
||||
}
|
||||
|
||||
if (inst.Rc)
|
||||
|
@ -1343,12 +1343,13 @@ void JitArm64::subfzex(UGeckoInstruction inst)
|
|||
{
|
||||
case CarryFlag::InPPCState:
|
||||
{
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
LDRB(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(xer_ca));
|
||||
MVN(gpr.R(d), gpr.R(a));
|
||||
CARRY_IF_NEEDED(ADD, ADDS, gpr.R(d), gpr.R(d), WA);
|
||||
{
|
||||
auto WA = gpr.GetScopedReg();
|
||||
LDRB(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(xer_ca));
|
||||
MVN(gpr.R(d), gpr.R(a));
|
||||
CARRY_IF_NEEDED(ADD, ADDS, gpr.R(d), gpr.R(d), WA);
|
||||
}
|
||||
ComputeCarry();
|
||||
gpr.Unlock(WA);
|
||||
break;
|
||||
}
|
||||
case CarryFlag::InHostCarry:
|
||||
|
@ -1393,24 +1394,34 @@ void JitArm64::subfic(UGeckoInstruction inst)
|
|||
else
|
||||
{
|
||||
const bool will_read = d == a;
|
||||
const bool is_zero = imm == 0;
|
||||
const bool allocate_reg = will_read && !is_zero;
|
||||
gpr.BindToRegister(d, will_read);
|
||||
|
||||
// d = imm - a
|
||||
ARM64Reg RD = gpr.R(d);
|
||||
ARM64Reg WA = ARM64Reg::WZR;
|
||||
if (!is_zero)
|
||||
|
||||
if (imm == -1)
|
||||
{
|
||||
WA = will_read ? gpr.GetReg() : RD;
|
||||
MOVI2R(WA, imm);
|
||||
// d = -1 - a = ~a
|
||||
MVN(RD, gpr.R(a));
|
||||
// CA is always set in this case
|
||||
ComputeCarry(true);
|
||||
}
|
||||
CARRY_IF_NEEDED(SUB, SUBS, RD, WA, gpr.R(a));
|
||||
else
|
||||
{
|
||||
const bool is_zero = imm == 0;
|
||||
|
||||
if (allocate_reg)
|
||||
gpr.Unlock(WA);
|
||||
// d = imm - a
|
||||
{
|
||||
Arm64GPRCache::ScopedARM64Reg WA(ARM64Reg::WZR);
|
||||
if (!is_zero)
|
||||
{
|
||||
WA = will_read ? gpr.GetScopedReg() : Arm64GPRCache::ScopedARM64Reg(RD);
|
||||
MOVI2R(WA, imm);
|
||||
}
|
||||
|
||||
ComputeCarry();
|
||||
CARRY_IF_NEEDED(SUB, SUBS, RD, WA, gpr.R(a));
|
||||
}
|
||||
|
||||
ComputeCarry();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1433,10 +1444,9 @@ void JitArm64::addex(UGeckoInstruction inst)
|
|||
{
|
||||
case CarryFlag::InPPCState:
|
||||
{
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
auto WA = gpr.GetScopedReg();
|
||||
LDRB(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(xer_ca));
|
||||
ADDI2R(gpr.R(d), WA, i + j, gpr.R(d));
|
||||
gpr.Unlock(WA);
|
||||
break;
|
||||
}
|
||||
case CarryFlag::InHostCarry:
|
||||
|
@ -1477,23 +1487,30 @@ void JitArm64::addex(UGeckoInstruction inst)
|
|||
else
|
||||
{
|
||||
gpr.BindToRegister(d, d == a || d == b);
|
||||
ARM64Reg RB = mex ? gpr.GetReg() : gpr.R(b);
|
||||
if (mex)
|
||||
MOVI2R(RB, -1);
|
||||
{
|
||||
Arm64GPRCache::ScopedARM64Reg RB;
|
||||
if (mex)
|
||||
{
|
||||
RB = gpr.GetScopedReg();
|
||||
MOVI2R(RB, -1);
|
||||
}
|
||||
else
|
||||
{
|
||||
RB = gpr.R(b);
|
||||
}
|
||||
|
||||
if (js.carryFlag == CarryFlag::ConstantFalse)
|
||||
{
|
||||
CARRY_IF_NEEDED(ADD, ADDS, gpr.R(d), gpr.R(a), RB);
|
||||
}
|
||||
else
|
||||
{
|
||||
LoadCarry();
|
||||
CARRY_IF_NEEDED(ADC, ADCS, gpr.R(d), gpr.R(a), RB);
|
||||
if (js.carryFlag == CarryFlag::ConstantFalse)
|
||||
{
|
||||
CARRY_IF_NEEDED(ADD, ADDS, gpr.R(d), gpr.R(a), RB);
|
||||
}
|
||||
else
|
||||
{
|
||||
LoadCarry();
|
||||
CARRY_IF_NEEDED(ADC, ADCS, gpr.R(d), gpr.R(a), RB);
|
||||
}
|
||||
}
|
||||
|
||||
ComputeCarry();
|
||||
if (mex)
|
||||
gpr.Unlock(RB);
|
||||
}
|
||||
|
||||
if (inst.Rc)
|
||||
|
@ -1575,7 +1592,7 @@ void JitArm64::divwux(UGeckoInstruction inst)
|
|||
{
|
||||
UnsignedMagic m = UnsignedDivisionConstants(divisor);
|
||||
|
||||
ARM64Reg WI = allocate_reg ? gpr.GetReg() : RD;
|
||||
auto WI = allocate_reg ? gpr.GetScopedReg() : Arm64GPRCache::ScopedARM64Reg(RD);
|
||||
ARM64Reg XD = EncodeRegTo64(RD);
|
||||
|
||||
MOVI2R(WI, m.multiplier);
|
||||
|
@ -1590,9 +1607,6 @@ void JitArm64::divwux(UGeckoInstruction inst)
|
|||
}
|
||||
|
||||
LSR(XD, XD, 32 + m.shift);
|
||||
|
||||
if (allocate_reg)
|
||||
gpr.Unlock(WI);
|
||||
}
|
||||
|
||||
if (inst.Rc)
|
||||
|
@ -1719,7 +1733,7 @@ void JitArm64::divwx(UGeckoInstruction inst)
|
|||
ARM64Reg RA = gpr.R(a);
|
||||
ARM64Reg RD = gpr.R(d);
|
||||
|
||||
ARM64Reg WA = allocate_reg ? gpr.GetReg() : RD;
|
||||
auto WA = allocate_reg ? gpr.GetScopedReg() : Arm64GPRCache::ScopedARM64Reg(RD);
|
||||
|
||||
TST(RA, RA);
|
||||
ADDI2R(WA, RA, abs_val - 1, WA);
|
||||
|
@ -1729,9 +1743,6 @@ void JitArm64::divwx(UGeckoInstruction inst)
|
|||
NEG(RD, WA, ArithOption(WA, ShiftType::ASR, MathUtil::IntLog2(abs_val)));
|
||||
else
|
||||
ASR(RD, WA, MathUtil::IntLog2(abs_val));
|
||||
|
||||
if (allocate_reg)
|
||||
gpr.Unlock(WA);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1739,8 +1750,8 @@ void JitArm64::divwx(UGeckoInstruction inst)
|
|||
SignedMagic m = SignedDivisionConstants(divisor);
|
||||
|
||||
ARM64Reg RD = gpr.R(d);
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
ARM64Reg WB = allocate_reg ? gpr.GetReg() : RD;
|
||||
auto WA = gpr.GetScopedReg();
|
||||
auto WB = allocate_reg ? gpr.GetScopedReg() : Arm64GPRCache::ScopedARM64Reg(RD);
|
||||
|
||||
ARM64Reg XD = EncodeRegTo64(RD);
|
||||
ARM64Reg XA = EncodeRegTo64(WA);
|
||||
|
@ -1776,10 +1787,6 @@ void JitArm64::divwx(UGeckoInstruction inst)
|
|||
ASR(XD, XD, 32 + m.shift);
|
||||
ADD(RD, WA, RD);
|
||||
}
|
||||
|
||||
gpr.Unlock(WA);
|
||||
if (allocate_reg)
|
||||
gpr.Unlock(WB);
|
||||
}
|
||||
|
||||
if (inst.Rc)
|
||||
|
@ -1982,8 +1989,7 @@ void JitArm64::srawx(UGeckoInstruction inst)
|
|||
else
|
||||
{
|
||||
gpr.BindToRegister(a, a == s);
|
||||
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
auto WA = gpr.GetScopedReg();
|
||||
|
||||
if (a != s)
|
||||
{
|
||||
|
@ -2009,8 +2015,6 @@ void JitArm64::srawx(UGeckoInstruction inst)
|
|||
|
||||
CSET(WA, CC_NEQ);
|
||||
ComputeCarry(WA);
|
||||
|
||||
gpr.Unlock(WA);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -2018,8 +2022,8 @@ void JitArm64::srawx(UGeckoInstruction inst)
|
|||
const bool will_read = a == b || a == s;
|
||||
gpr.BindToRegister(a, will_read);
|
||||
|
||||
const bool allocate_reg = will_read || js.op->wantsCA;
|
||||
ARM64Reg WA = allocate_reg ? gpr.GetReg() : gpr.R(a);
|
||||
auto WA =
|
||||
will_read || js.op->wantsCA ? gpr.GetScopedReg() : Arm64GPRCache::ScopedARM64Reg(gpr.R(a));
|
||||
|
||||
LSL(EncodeRegTo64(WA), EncodeRegTo64(gpr.R(s)), 32);
|
||||
ASRV(EncodeRegTo64(WA), EncodeRegTo64(WA), EncodeRegTo64(gpr.R(b)));
|
||||
|
@ -2031,9 +2035,6 @@ void JitArm64::srawx(UGeckoInstruction inst)
|
|||
CSET(WA, CC_NEQ);
|
||||
ComputeCarry(WA);
|
||||
}
|
||||
|
||||
if (allocate_reg)
|
||||
gpr.Unlock(WA);
|
||||
}
|
||||
|
||||
if (inst.Rc)
|
||||
|
@ -2088,10 +2089,9 @@ void JitArm64::rlwimix(UGeckoInstruction inst)
|
|||
// No rotation
|
||||
// No mask inversion
|
||||
gpr.BindToRegister(a, true);
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
auto WA = gpr.GetScopedReg();
|
||||
UBFX(WA, gpr.R(s), lsb, width);
|
||||
BFI(gpr.R(a), WA, lsb, width);
|
||||
gpr.Unlock(WA);
|
||||
}
|
||||
else if (inst.SH && inst.MB <= inst.ME)
|
||||
{
|
||||
|
@ -2103,28 +2103,22 @@ void JitArm64::rlwimix(UGeckoInstruction inst)
|
|||
}
|
||||
else
|
||||
{
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
auto WA = gpr.GetScopedReg();
|
||||
ROR(WA, gpr.R(s), (rot_dist + lsb) % 32);
|
||||
BFI(gpr.R(a), WA, lsb, width);
|
||||
gpr.Unlock(WA);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
gpr.BindToRegister(a, true);
|
||||
const bool allocate_reg = a == s;
|
||||
ARM64Reg RA = gpr.R(a);
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
ARM64Reg WB = allocate_reg ? gpr.GetReg() : RA;
|
||||
auto WA = gpr.GetScopedReg();
|
||||
auto WB = a == s ? gpr.GetScopedReg() : Arm64GPRCache::ScopedARM64Reg(RA);
|
||||
|
||||
MOVI2R(WA, mask);
|
||||
BIC(WB, RA, WA);
|
||||
AND(WA, WA, gpr.R(s), ArithOption(gpr.R(s), ShiftType::ROR, rot_dist));
|
||||
ORR(RA, WB, WA);
|
||||
|
||||
gpr.Unlock(WA);
|
||||
if (allocate_reg)
|
||||
gpr.Unlock(WB);
|
||||
}
|
||||
|
||||
if (inst.Rc)
|
||||
|
|
|
@ -181,7 +181,8 @@ void JitArm64::SafeStoreFromReg(s32 dest, u32 value, s32 regOffset, u32 flags, s
|
|||
if (!jo.fastmem)
|
||||
gpr.Lock(ARM64Reg::W0);
|
||||
|
||||
ARM64Reg RS = gpr.R(value);
|
||||
// Don't materialize zero.
|
||||
ARM64Reg RS = gpr.IsImm(value, 0) ? ARM64Reg::WZR : gpr.R(value);
|
||||
|
||||
ARM64Reg reg_dest = ARM64Reg::INVALID_REG;
|
||||
ARM64Reg reg_off = ARM64Reg::INVALID_REG;
|
||||
|
@ -538,9 +539,12 @@ void JitArm64::lmw(UGeckoInstruction inst)
|
|||
else
|
||||
ADDI2R(addr_reg, gpr.R(a), offset, addr_reg);
|
||||
|
||||
ARM64Reg addr_base_reg = a_is_addr_base_reg ? ARM64Reg::INVALID_REG : gpr.GetReg();
|
||||
Arm64RegCache::ScopedARM64Reg addr_base_reg;
|
||||
if (!a_is_addr_base_reg)
|
||||
{
|
||||
addr_base_reg = gpr.GetScopedReg();
|
||||
MOV(addr_base_reg, addr_reg);
|
||||
}
|
||||
|
||||
BitSet32 gprs_to_discard{};
|
||||
if (!jo.memcheck)
|
||||
|
@ -628,8 +632,6 @@ void JitArm64::lmw(UGeckoInstruction inst)
|
|||
gpr.Unlock(ARM64Reg::W1, ARM64Reg::W30);
|
||||
if (jo.memcheck || !jo.fastmem)
|
||||
gpr.Unlock(ARM64Reg::W0);
|
||||
if (!a_is_addr_base_reg)
|
||||
gpr.Unlock(addr_base_reg);
|
||||
}
|
||||
|
||||
void JitArm64::stmw(UGeckoInstruction inst)
|
||||
|
@ -655,9 +657,12 @@ void JitArm64::stmw(UGeckoInstruction inst)
|
|||
else
|
||||
ADDI2R(addr_reg, gpr.R(a), offset, addr_reg);
|
||||
|
||||
ARM64Reg addr_base_reg = a_is_addr_base_reg ? ARM64Reg::INVALID_REG : gpr.GetReg();
|
||||
Arm64GPRCache::ScopedARM64Reg addr_base_reg;
|
||||
if (!a_is_addr_base_reg)
|
||||
{
|
||||
addr_base_reg = gpr.GetScopedReg();
|
||||
MOV(addr_base_reg, addr_reg);
|
||||
}
|
||||
|
||||
BitSet32 gprs_to_discard{};
|
||||
if (!jo.memcheck)
|
||||
|
@ -748,8 +753,6 @@ void JitArm64::stmw(UGeckoInstruction inst)
|
|||
gpr.Unlock(ARM64Reg::W1, ARM64Reg::W2, ARM64Reg::W30);
|
||||
if (!jo.fastmem)
|
||||
gpr.Unlock(ARM64Reg::W0);
|
||||
if (!a_is_addr_base_reg)
|
||||
gpr.Unlock(addr_base_reg);
|
||||
}
|
||||
|
||||
void JitArm64::dcbx(UGeckoInstruction inst)
|
||||
|
@ -786,8 +789,8 @@ void JitArm64::dcbx(UGeckoInstruction inst)
|
|||
// bdnz afterwards! So if we invalidate a single cache line, we don't adjust the registers at
|
||||
// all, if we invalidate 2 cachelines we adjust the registers by one step, and so on.
|
||||
|
||||
const ARM64Reg reg_cycle_count = gpr.GetReg();
|
||||
const ARM64Reg reg_downcount = gpr.GetReg();
|
||||
const auto reg_cycle_count = gpr.GetScopedReg();
|
||||
const auto reg_downcount = gpr.GetScopedReg();
|
||||
|
||||
// Figure out how many loops we want to do.
|
||||
const u8 cycle_count_per_loop =
|
||||
|
@ -855,12 +858,9 @@ void JitArm64::dcbx(UGeckoInstruction inst)
|
|||
SetJumpTarget(branch_out);
|
||||
SetJumpTarget(branch_over);
|
||||
}
|
||||
|
||||
gpr.Unlock(reg_cycle_count, reg_downcount);
|
||||
}
|
||||
|
||||
constexpr ARM64Reg effective_addr = WB;
|
||||
const ARM64Reg physical_addr = gpr.GetReg();
|
||||
|
||||
if (a)
|
||||
ADD(effective_addr, gpr.R(a), gpr.R(b));
|
||||
|
@ -874,6 +874,8 @@ void JitArm64::dcbx(UGeckoInstruction inst)
|
|||
ADD(gpr.R(b), gpr.R(b), WA, ArithOption(WA, ShiftType::LSL, 5)); // Rb += (WA * 32)
|
||||
}
|
||||
|
||||
auto physical_addr = gpr.GetScopedReg();
|
||||
|
||||
// Translate effective address to physical address.
|
||||
const u8* loop_start = GetCodePtr();
|
||||
FixupBranch bat_lookup_failed;
|
||||
|
@ -939,7 +941,7 @@ void JitArm64::dcbx(UGeckoInstruction inst)
|
|||
SwitchToNearCode();
|
||||
SetJumpTarget(near_addr);
|
||||
|
||||
gpr.Unlock(WA, WB, physical_addr);
|
||||
gpr.Unlock(WA, WB);
|
||||
if (make_loop)
|
||||
gpr.Unlock(loop_counter);
|
||||
}
|
||||
|
|
|
@ -268,14 +268,14 @@ void JitArm64::stfXX(UGeckoInstruction inst)
|
|||
|
||||
const bool have_single = fpr.IsSingle(inst.FS, true);
|
||||
|
||||
ARM64Reg V0 =
|
||||
Arm64FPRCache::ScopedARM64Reg V0 =
|
||||
fpr.R(inst.FS, want_single && have_single ? RegType::LowerPairSingle : RegType::LowerPair);
|
||||
|
||||
if (want_single && !have_single)
|
||||
{
|
||||
const ARM64Reg single_reg = fpr.GetReg();
|
||||
auto single_reg = fpr.GetScopedReg();
|
||||
ConvertDoubleToSingleLower(inst.FS, single_reg, V0);
|
||||
V0 = single_reg;
|
||||
V0 = std::move(single_reg);
|
||||
}
|
||||
|
||||
gpr.Lock(ARM64Reg::W1, ARM64Reg::W2, ARM64Reg::W30);
|
||||
|
@ -425,9 +425,6 @@ void JitArm64::stfXX(UGeckoInstruction inst)
|
|||
MOV(gpr.R(a), addr_reg);
|
||||
}
|
||||
|
||||
if (want_single && !have_single)
|
||||
fpr.Unlock(V0);
|
||||
|
||||
gpr.Unlock(ARM64Reg::W1, ARM64Reg::W2, ARM64Reg::W30);
|
||||
fpr.Unlock(ARM64Reg::Q0);
|
||||
if (!jo.fastmem)
|
||||
|
|
|
@ -173,20 +173,21 @@ void JitArm64::psq_stXX(UGeckoInstruction inst)
|
|||
|
||||
const bool have_single = fpr.IsSingle(inst.RS);
|
||||
|
||||
ARM64Reg VS = fpr.R(inst.RS, have_single ? RegType::Single : RegType::Register);
|
||||
Arm64FPRCache::ScopedARM64Reg VS =
|
||||
fpr.R(inst.RS, have_single ? RegType::Single : RegType::Register);
|
||||
|
||||
if (js.assumeNoPairedQuantize)
|
||||
{
|
||||
if (!have_single)
|
||||
{
|
||||
const ARM64Reg single_reg = fpr.GetReg();
|
||||
auto single_reg = fpr.GetScopedReg();
|
||||
|
||||
if (w)
|
||||
m_float_emit.FCVT(32, 64, EncodeRegToDouble(single_reg), EncodeRegToDouble(VS));
|
||||
else
|
||||
m_float_emit.FCVTN(32, EncodeRegToDouble(single_reg), EncodeRegToDouble(VS));
|
||||
|
||||
VS = single_reg;
|
||||
VS = std::move(single_reg);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -279,9 +280,6 @@ void JitArm64::psq_stXX(UGeckoInstruction inst)
|
|||
MOV(gpr.R(inst.RA), addr_reg);
|
||||
}
|
||||
|
||||
if (js.assumeNoPairedQuantize && !have_single)
|
||||
fpr.Unlock(VS);
|
||||
|
||||
gpr.Unlock(ARM64Reg::W1, ARM64Reg::W2, ARM64Reg::W30);
|
||||
fpr.Unlock(ARM64Reg::Q0);
|
||||
if (!js.assumeNoPairedQuantize || !jo.fastmem)
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Config/Config.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Common/Unreachable.h"
|
||||
|
||||
#include "Core/Config/SessionSettings.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
|
@ -108,201 +109,196 @@ void JitArm64::ps_arith(UGeckoInstruction inst)
|
|||
const ARM64Reg VC = use_c ? reg_encoder(fpr.R(c, type)) : ARM64Reg::INVALID_REG;
|
||||
const ARM64Reg VD = reg_encoder(fpr.RW(d, type));
|
||||
|
||||
ARM64Reg V0Q = ARM64Reg::INVALID_REG;
|
||||
ARM64Reg V1Q = ARM64Reg::INVALID_REG;
|
||||
ARM64Reg V2Q = ARM64Reg::INVALID_REG;
|
||||
|
||||
ARM64Reg rounded_c_reg = VC;
|
||||
if (round_c)
|
||||
{
|
||||
ASSERT_MSG(DYNA_REC, !singles, "Tried to apply 25-bit precision to single");
|
||||
Arm64FPRCache::ScopedARM64Reg V0Q = ARM64Reg::INVALID_REG;
|
||||
Arm64FPRCache::ScopedARM64Reg V1Q = ARM64Reg::INVALID_REG;
|
||||
Arm64FPRCache::ScopedARM64Reg V2Q = ARM64Reg::INVALID_REG;
|
||||
|
||||
V0Q = fpr.GetReg();
|
||||
rounded_c_reg = reg_encoder(V0Q);
|
||||
Force25BitPrecision(rounded_c_reg, VC);
|
||||
}
|
||||
|
||||
ARM64Reg inaccurate_fma_reg = VD;
|
||||
if (fma && inaccurate_fma && VD == VB)
|
||||
{
|
||||
if (V0Q == ARM64Reg::INVALID_REG)
|
||||
V0Q = fpr.GetReg();
|
||||
inaccurate_fma_reg = reg_encoder(V0Q);
|
||||
}
|
||||
|
||||
ARM64Reg result_reg = VD;
|
||||
const bool need_accurate_fma_reg =
|
||||
fma && !inaccurate_fma && (msub || VD != VB) && (VD == VA || VD == rounded_c_reg);
|
||||
const bool preserve_d =
|
||||
m_accurate_nans && (VD == VA || (use_b && VD == VB) || (use_c && VD == VC));
|
||||
if (need_accurate_fma_reg || preserve_d)
|
||||
{
|
||||
V1Q = fpr.GetReg();
|
||||
result_reg = reg_encoder(V1Q);
|
||||
}
|
||||
|
||||
if (m_accurate_nans)
|
||||
{
|
||||
if (V0Q == ARM64Reg::INVALID_REG)
|
||||
V0Q = fpr.GetReg();
|
||||
|
||||
if (duplicated_c || VD == result_reg)
|
||||
V2Q = fpr.GetReg();
|
||||
}
|
||||
|
||||
switch (op5)
|
||||
{
|
||||
case 12: // ps_muls0: d = a * c.ps0
|
||||
m_float_emit.FMUL(size, result_reg, VA, rounded_c_reg, 0);
|
||||
break;
|
||||
case 13: // ps_muls1: d = a * c.ps1
|
||||
m_float_emit.FMUL(size, result_reg, VA, rounded_c_reg, 1);
|
||||
break;
|
||||
case 14: // ps_madds0: d = a * c.ps0 + b
|
||||
if (inaccurate_fma)
|
||||
ARM64Reg rounded_c_reg = VC;
|
||||
if (round_c)
|
||||
{
|
||||
m_float_emit.FMUL(size, inaccurate_fma_reg, VA, rounded_c_reg, 0);
|
||||
m_float_emit.FADD(size, result_reg, inaccurate_fma_reg, VB);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (result_reg != VB)
|
||||
m_float_emit.MOV(result_reg, VB);
|
||||
m_float_emit.FMLA(size, result_reg, VA, rounded_c_reg, 0);
|
||||
}
|
||||
break;
|
||||
case 15: // ps_madds1: d = a * c.ps1 + b
|
||||
if (inaccurate_fma)
|
||||
{
|
||||
m_float_emit.FMUL(size, inaccurate_fma_reg, VA, rounded_c_reg, 1);
|
||||
m_float_emit.FADD(size, result_reg, inaccurate_fma_reg, VB);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (result_reg != VB)
|
||||
m_float_emit.MOV(result_reg, VB);
|
||||
m_float_emit.FMLA(size, result_reg, VA, rounded_c_reg, 1);
|
||||
}
|
||||
break;
|
||||
case 18: // ps_div
|
||||
m_float_emit.FDIV(size, result_reg, VA, VB);
|
||||
break;
|
||||
case 20: // ps_sub
|
||||
m_float_emit.FSUB(size, result_reg, VA, VB);
|
||||
break;
|
||||
case 21: // ps_add
|
||||
m_float_emit.FADD(size, result_reg, VA, VB);
|
||||
break;
|
||||
case 25: // ps_mul
|
||||
m_float_emit.FMUL(size, result_reg, VA, rounded_c_reg);
|
||||
break;
|
||||
case 28: // ps_msub: d = a * c - b
|
||||
case 30: // ps_nmsub: d = -(a * c - b)
|
||||
if (inaccurate_fma)
|
||||
{
|
||||
m_float_emit.FMUL(size, inaccurate_fma_reg, VA, rounded_c_reg);
|
||||
m_float_emit.FSUB(size, result_reg, inaccurate_fma_reg, VB);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_float_emit.FNEG(size, result_reg, VB);
|
||||
m_float_emit.FMLA(size, result_reg, VA, rounded_c_reg);
|
||||
}
|
||||
break;
|
||||
case 29: // ps_madd: d = a * c + b
|
||||
case 31: // ps_nmadd: d = -(a * c + b)
|
||||
if (inaccurate_fma)
|
||||
{
|
||||
m_float_emit.FMUL(size, inaccurate_fma_reg, VA, rounded_c_reg);
|
||||
m_float_emit.FADD(size, result_reg, inaccurate_fma_reg, VB);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (result_reg != VB)
|
||||
m_float_emit.MOV(result_reg, VB);
|
||||
m_float_emit.FMLA(size, result_reg, VA, rounded_c_reg);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ASSERT_MSG(DYNA_REC, 0, "ps_arith - invalid op");
|
||||
break;
|
||||
}
|
||||
ASSERT_MSG(DYNA_REC, !singles, "Tried to apply 25-bit precision to single");
|
||||
|
||||
FixupBranch nan_fixup;
|
||||
if (m_accurate_nans)
|
||||
{
|
||||
const ARM64Reg nan_temp_reg = singles ? EncodeRegToSingle(V0Q) : EncodeRegToDouble(V0Q);
|
||||
const ARM64Reg nan_temp_reg_paired = reg_encoder(V0Q);
|
||||
|
||||
// Check if we need to handle NaNs
|
||||
|
||||
m_float_emit.FMAXP(nan_temp_reg, result_reg);
|
||||
m_float_emit.FCMP(nan_temp_reg);
|
||||
FixupBranch no_nan = B(CCFlags::CC_VC);
|
||||
FixupBranch nan = B();
|
||||
SetJumpTarget(no_nan);
|
||||
|
||||
SwitchToFarCode();
|
||||
SetJumpTarget(nan);
|
||||
|
||||
// Pick the right NaNs
|
||||
|
||||
const auto check_input = [&](ARM64Reg input) {
|
||||
m_float_emit.FCMEQ(size, nan_temp_reg_paired, input, input);
|
||||
m_float_emit.BIF(result_reg, input, nan_temp_reg_paired);
|
||||
};
|
||||
|
||||
ARM64Reg c_reg_for_nan_purposes = VC;
|
||||
if (duplicated_c)
|
||||
{
|
||||
c_reg_for_nan_purposes = reg_encoder(V2Q);
|
||||
m_float_emit.DUP(size, c_reg_for_nan_purposes, VC, op5 & 0x1);
|
||||
V0Q = fpr.GetScopedReg();
|
||||
rounded_c_reg = reg_encoder(V0Q);
|
||||
Force25BitPrecision(rounded_c_reg, VC);
|
||||
}
|
||||
|
||||
if (use_c)
|
||||
check_input(c_reg_for_nan_purposes);
|
||||
ARM64Reg inaccurate_fma_reg = VD;
|
||||
if (fma && inaccurate_fma && VD == VB)
|
||||
{
|
||||
if (V0Q == ARM64Reg::INVALID_REG)
|
||||
V0Q = fpr.GetScopedReg();
|
||||
inaccurate_fma_reg = reg_encoder(V0Q);
|
||||
}
|
||||
|
||||
if (use_b && (!use_c || VB != c_reg_for_nan_purposes))
|
||||
check_input(VB);
|
||||
ARM64Reg result_reg = VD;
|
||||
const bool need_accurate_fma_reg =
|
||||
fma && !inaccurate_fma && (msub || VD != VB) && (VD == VA || VD == rounded_c_reg);
|
||||
const bool preserve_d =
|
||||
m_accurate_nans && (VD == VA || (use_b && VD == VB) || (use_c && VD == VC));
|
||||
if (need_accurate_fma_reg || preserve_d)
|
||||
{
|
||||
V1Q = fpr.GetScopedReg();
|
||||
result_reg = reg_encoder(V1Q);
|
||||
}
|
||||
|
||||
if ((!use_b || VA != VB) && (!use_c || VA != c_reg_for_nan_purposes))
|
||||
check_input(VA);
|
||||
if (m_accurate_nans)
|
||||
{
|
||||
if (V0Q == ARM64Reg::INVALID_REG)
|
||||
V0Q = fpr.GetScopedReg();
|
||||
|
||||
// Make the NaNs quiet
|
||||
if (duplicated_c || VD == result_reg)
|
||||
V2Q = fpr.GetScopedReg();
|
||||
}
|
||||
|
||||
const ARM64Reg quiet_nan_reg = VD == result_reg ? reg_encoder(V2Q) : VD;
|
||||
switch (op5)
|
||||
{
|
||||
case 12: // ps_muls0: d = a * c.ps0
|
||||
m_float_emit.FMUL(size, result_reg, VA, rounded_c_reg, 0);
|
||||
break;
|
||||
case 13: // ps_muls1: d = a * c.ps1
|
||||
m_float_emit.FMUL(size, result_reg, VA, rounded_c_reg, 1);
|
||||
break;
|
||||
case 14: // ps_madds0: d = a * c.ps0 + b
|
||||
if (inaccurate_fma)
|
||||
{
|
||||
m_float_emit.FMUL(size, inaccurate_fma_reg, VA, rounded_c_reg, 0);
|
||||
m_float_emit.FADD(size, result_reg, inaccurate_fma_reg, VB);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (result_reg != VB)
|
||||
m_float_emit.MOV(result_reg, VB);
|
||||
m_float_emit.FMLA(size, result_reg, VA, rounded_c_reg, 0);
|
||||
}
|
||||
break;
|
||||
case 15: // ps_madds1: d = a * c.ps1 + b
|
||||
if (inaccurate_fma)
|
||||
{
|
||||
m_float_emit.FMUL(size, inaccurate_fma_reg, VA, rounded_c_reg, 1);
|
||||
m_float_emit.FADD(size, result_reg, inaccurate_fma_reg, VB);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (result_reg != VB)
|
||||
m_float_emit.MOV(result_reg, VB);
|
||||
m_float_emit.FMLA(size, result_reg, VA, rounded_c_reg, 1);
|
||||
}
|
||||
break;
|
||||
case 18: // ps_div
|
||||
m_float_emit.FDIV(size, result_reg, VA, VB);
|
||||
break;
|
||||
case 20: // ps_sub
|
||||
m_float_emit.FSUB(size, result_reg, VA, VB);
|
||||
break;
|
||||
case 21: // ps_add
|
||||
m_float_emit.FADD(size, result_reg, VA, VB);
|
||||
break;
|
||||
case 25: // ps_mul
|
||||
m_float_emit.FMUL(size, result_reg, VA, rounded_c_reg);
|
||||
break;
|
||||
case 28: // ps_msub: d = a * c - b
|
||||
case 30: // ps_nmsub: d = -(a * c - b)
|
||||
if (inaccurate_fma)
|
||||
{
|
||||
m_float_emit.FMUL(size, inaccurate_fma_reg, VA, rounded_c_reg);
|
||||
m_float_emit.FSUB(size, result_reg, inaccurate_fma_reg, VB);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_float_emit.FNEG(size, result_reg, VB);
|
||||
m_float_emit.FMLA(size, result_reg, VA, rounded_c_reg);
|
||||
}
|
||||
break;
|
||||
case 29: // ps_madd: d = a * c + b
|
||||
case 31: // ps_nmadd: d = -(a * c + b)
|
||||
if (inaccurate_fma)
|
||||
{
|
||||
m_float_emit.FMUL(size, inaccurate_fma_reg, VA, rounded_c_reg);
|
||||
m_float_emit.FADD(size, result_reg, inaccurate_fma_reg, VB);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (result_reg != VB)
|
||||
m_float_emit.MOV(result_reg, VB);
|
||||
m_float_emit.FMLA(size, result_reg, VA, rounded_c_reg);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ASSERT_MSG(DYNA_REC, 0, "ps_arith - invalid op");
|
||||
break;
|
||||
}
|
||||
|
||||
m_float_emit.FADD(size, quiet_nan_reg, result_reg, result_reg);
|
||||
m_float_emit.FCMEQ(size, nan_temp_reg_paired, result_reg, result_reg);
|
||||
FixupBranch nan_fixup;
|
||||
if (m_accurate_nans)
|
||||
{
|
||||
const ARM64Reg nan_temp_reg = singles ? EncodeRegToSingle(V0Q) : EncodeRegToDouble(V0Q);
|
||||
const ARM64Reg nan_temp_reg_paired = reg_encoder(V0Q);
|
||||
|
||||
// Check if we need to handle NaNs
|
||||
|
||||
m_float_emit.FMAXP(nan_temp_reg, result_reg);
|
||||
m_float_emit.FCMP(nan_temp_reg);
|
||||
FixupBranch no_nan = B(CCFlags::CC_VC);
|
||||
FixupBranch nan = B();
|
||||
SetJumpTarget(no_nan);
|
||||
|
||||
SwitchToFarCode();
|
||||
SetJumpTarget(nan);
|
||||
|
||||
// Pick the right NaNs
|
||||
|
||||
const auto check_input = [&](ARM64Reg input) {
|
||||
m_float_emit.FCMEQ(size, nan_temp_reg_paired, input, input);
|
||||
m_float_emit.BIF(result_reg, input, nan_temp_reg_paired);
|
||||
};
|
||||
|
||||
ARM64Reg c_reg_for_nan_purposes = VC;
|
||||
if (duplicated_c)
|
||||
{
|
||||
c_reg_for_nan_purposes = reg_encoder(V2Q);
|
||||
m_float_emit.DUP(size, c_reg_for_nan_purposes, VC, op5 & 0x1);
|
||||
}
|
||||
|
||||
if (use_c)
|
||||
check_input(c_reg_for_nan_purposes);
|
||||
|
||||
if (use_b && (!use_c || VB != c_reg_for_nan_purposes))
|
||||
check_input(VB);
|
||||
|
||||
if ((!use_b || VA != VB) && (!use_c || VA != c_reg_for_nan_purposes))
|
||||
check_input(VA);
|
||||
|
||||
// Make the NaNs quiet
|
||||
|
||||
const ARM64Reg quiet_nan_reg = VD == result_reg ? reg_encoder(V2Q) : VD;
|
||||
|
||||
m_float_emit.FADD(size, quiet_nan_reg, result_reg, result_reg);
|
||||
m_float_emit.FCMEQ(size, nan_temp_reg_paired, result_reg, result_reg);
|
||||
if (negate_result)
|
||||
m_float_emit.FNEG(size, result_reg, result_reg);
|
||||
if (VD == result_reg)
|
||||
m_float_emit.BIF(VD, quiet_nan_reg, nan_temp_reg_paired);
|
||||
else // quiet_nan_reg == VD
|
||||
m_float_emit.BIT(VD, result_reg, nan_temp_reg_paired);
|
||||
|
||||
nan_fixup = B();
|
||||
|
||||
SwitchToNearCode();
|
||||
}
|
||||
|
||||
// PowerPC's nmadd/nmsub perform rounding before the final negation, which is not the case
|
||||
// for any of AArch64's FMA instructions, so we negate using a separate instruction.
|
||||
if (negate_result)
|
||||
m_float_emit.FNEG(size, result_reg, result_reg);
|
||||
if (VD == result_reg)
|
||||
m_float_emit.BIF(VD, quiet_nan_reg, nan_temp_reg_paired);
|
||||
else // quiet_nan_reg == VD
|
||||
m_float_emit.BIT(VD, result_reg, nan_temp_reg_paired);
|
||||
m_float_emit.FNEG(size, VD, result_reg);
|
||||
else if (result_reg != VD)
|
||||
m_float_emit.MOV(VD, result_reg);
|
||||
|
||||
nan_fixup = B();
|
||||
|
||||
SwitchToNearCode();
|
||||
if (m_accurate_nans)
|
||||
SetJumpTarget(nan_fixup);
|
||||
}
|
||||
|
||||
// PowerPC's nmadd/nmsub perform rounding before the final negation, which is not the case
|
||||
// for any of AArch64's FMA instructions, so we negate using a separate instruction.
|
||||
if (negate_result)
|
||||
m_float_emit.FNEG(size, VD, result_reg);
|
||||
else if (result_reg != VD)
|
||||
m_float_emit.MOV(VD, result_reg);
|
||||
|
||||
if (m_accurate_nans)
|
||||
SetJumpTarget(nan_fixup);
|
||||
|
||||
if (V0Q != ARM64Reg::INVALID_REG)
|
||||
fpr.Unlock(V0Q);
|
||||
if (V1Q != ARM64Reg::INVALID_REG)
|
||||
fpr.Unlock(V1Q);
|
||||
if (V2Q != ARM64Reg::INVALID_REG)
|
||||
fpr.Unlock(V2Q);
|
||||
|
||||
ASSERT_MSG(DYNA_REC, singles == singles_func(),
|
||||
"Register allocation turned singles into doubles in the middle of ps_arith");
|
||||
|
||||
|
@ -339,12 +335,15 @@ void JitArm64::ps_sel(UGeckoInstruction inst)
|
|||
}
|
||||
else
|
||||
{
|
||||
const ARM64Reg V0Q = fpr.GetReg();
|
||||
const auto V0Q = fpr.GetScopedReg();
|
||||
const ARM64Reg V0 = reg_encoder(V0Q);
|
||||
m_float_emit.FCMGE(size, V0, VA);
|
||||
m_float_emit.BSL(V0, VC, VB);
|
||||
m_float_emit.MOV(VD, V0);
|
||||
fpr.Unlock(V0Q);
|
||||
if (d == b)
|
||||
m_float_emit.BIT(VD, VC, V0);
|
||||
else if (d == c)
|
||||
m_float_emit.BIF(VD, VB, V0);
|
||||
else
|
||||
Common::Unreachable();
|
||||
}
|
||||
|
||||
ASSERT_MSG(DYNA_REC, singles == (fpr.IsSingle(a) && fpr.IsSingle(b) && fpr.IsSingle(c)),
|
||||
|
@ -375,41 +374,45 @@ void JitArm64::ps_sumX(UGeckoInstruction inst)
|
|||
const ARM64Reg VB = fpr.R(b, type);
|
||||
const ARM64Reg VC = fpr.R(c, type);
|
||||
const ARM64Reg VD = fpr.RW(d, type);
|
||||
const ARM64Reg V0 = fpr.GetReg();
|
||||
|
||||
m_float_emit.DUP(size, reg_encoder(V0), reg_encoder(VB), 1);
|
||||
{
|
||||
const auto V0 = fpr.GetScopedReg();
|
||||
|
||||
if (m_accurate_nans)
|
||||
{
|
||||
// If the first input is NaN, set the temp register for the second input to 0. This is because:
|
||||
//
|
||||
// - If the second input is also NaN, setting it to 0 ensures that the first NaN will be picked.
|
||||
// - If only the first input is NaN, setting the second input to 0 has no effect on the result.
|
||||
//
|
||||
// Either way, we can then do an FADD as usual, and the FADD will make the NaN quiet.
|
||||
m_float_emit.FCMP(scalar_reg_encoder(VA));
|
||||
FixupBranch a_not_nan = B(CCFlags::CC_VC);
|
||||
m_float_emit.MOVI(64, scalar_reg_encoder(V0), 0);
|
||||
SetJumpTarget(a_not_nan);
|
||||
}
|
||||
m_float_emit.DUP(size, reg_encoder(V0), reg_encoder(VB), 1);
|
||||
|
||||
if (upper)
|
||||
{
|
||||
m_float_emit.FADD(scalar_reg_encoder(V0), scalar_reg_encoder(V0), scalar_reg_encoder(VA));
|
||||
m_float_emit.TRN1(size, reg_encoder(VD), reg_encoder(VC), reg_encoder(V0));
|
||||
}
|
||||
else if (d != c)
|
||||
{
|
||||
m_float_emit.FADD(scalar_reg_encoder(VD), scalar_reg_encoder(V0), scalar_reg_encoder(VA));
|
||||
m_float_emit.INS(size, VD, 1, VC, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_float_emit.FADD(scalar_reg_encoder(V0), scalar_reg_encoder(V0), scalar_reg_encoder(VA));
|
||||
m_float_emit.INS(size, VD, 0, V0, 0);
|
||||
}
|
||||
if (m_accurate_nans)
|
||||
{
|
||||
// If the first input is NaN, set the temp register for the second input to 0. This is
|
||||
// because:
|
||||
//
|
||||
// - If the second input is also NaN, setting it to 0 ensures that the first NaN will be
|
||||
// picked.
|
||||
// - If only the first input is NaN, setting the second input to 0 has no effect on the
|
||||
// result.
|
||||
//
|
||||
// Either way, we can then do an FADD as usual, and the FADD will make the NaN quiet.
|
||||
m_float_emit.FCMP(scalar_reg_encoder(VA));
|
||||
FixupBranch a_not_nan = B(CCFlags::CC_VC);
|
||||
m_float_emit.MOVI(64, scalar_reg_encoder(V0), 0);
|
||||
SetJumpTarget(a_not_nan);
|
||||
}
|
||||
|
||||
fpr.Unlock(V0);
|
||||
if (upper)
|
||||
{
|
||||
m_float_emit.FADD(scalar_reg_encoder(V0), scalar_reg_encoder(V0), scalar_reg_encoder(VA));
|
||||
m_float_emit.TRN1(size, reg_encoder(VD), reg_encoder(VC), reg_encoder(V0));
|
||||
}
|
||||
else if (d != c)
|
||||
{
|
||||
m_float_emit.FADD(scalar_reg_encoder(VD), scalar_reg_encoder(V0), scalar_reg_encoder(VA));
|
||||
m_float_emit.INS(size, VD, 1, VC, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_float_emit.FADD(scalar_reg_encoder(V0), scalar_reg_encoder(V0), scalar_reg_encoder(VA));
|
||||
m_float_emit.INS(size, VD, 0, V0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_MSG(DYNA_REC, singles == (fpr.IsSingle(a) && fpr.IsSingle(b) && fpr.IsSingle(c)),
|
||||
"Register allocation turned singles into doubles in the middle of ps_sumX");
|
||||
|
|
|
@ -241,12 +241,16 @@ void Arm64GPRCache::FlushRegister(size_t index, FlushMode mode, ARM64Reg tmp_reg
|
|||
}
|
||||
}
|
||||
|
||||
void Arm64GPRCache::FlushRegisters(BitSet32 regs, FlushMode mode, ARM64Reg tmp_reg)
|
||||
void Arm64GPRCache::FlushRegisters(BitSet32 regs, FlushMode mode, ARM64Reg tmp_reg,
|
||||
IgnoreDiscardedRegisters ignore_discarded_registers)
|
||||
{
|
||||
for (auto iter = regs.begin(); iter != regs.end(); ++iter)
|
||||
{
|
||||
const int i = *iter;
|
||||
ASSERT_MSG(DYNA_REC, m_guest_registers[GUEST_GPR_OFFSET + i].GetType() != RegType::Discarded,
|
||||
|
||||
ASSERT_MSG(DYNA_REC,
|
||||
ignore_discarded_registers != IgnoreDiscardedRegisters::No ||
|
||||
m_guest_registers[GUEST_GPR_OFFSET + i].GetType() != RegType::Discarded,
|
||||
"Attempted to flush discarded register");
|
||||
|
||||
if (i + 1 < int(GUEST_GPR_COUNT) && regs[i + 1])
|
||||
|
@ -254,19 +258,27 @@ void Arm64GPRCache::FlushRegisters(BitSet32 regs, FlushMode mode, ARM64Reg tmp_r
|
|||
// We've got two guest registers in a row to store
|
||||
OpArg& reg1 = m_guest_registers[GUEST_GPR_OFFSET + i];
|
||||
OpArg& reg2 = m_guest_registers[GUEST_GPR_OFFSET + i + 1];
|
||||
if (reg1.IsDirty() && reg2.IsDirty() && reg1.GetType() == RegType::Register &&
|
||||
reg2.GetType() == RegType::Register)
|
||||
const bool reg1_imm = reg1.GetType() == RegType::Immediate;
|
||||
const bool reg2_imm = reg2.GetType() == RegType::Immediate;
|
||||
const bool reg1_zero = reg1_imm && reg1.GetImm() == 0;
|
||||
const bool reg2_zero = reg2_imm && reg2.GetImm() == 0;
|
||||
const bool flush_all = mode == FlushMode::All;
|
||||
if (reg1.IsDirty() && reg2.IsDirty() &&
|
||||
(reg1.GetType() == RegType::Register || (reg1_imm && (reg1_zero || flush_all))) &&
|
||||
(reg2.GetType() == RegType::Register || (reg2_imm && (reg2_zero || flush_all))))
|
||||
{
|
||||
const size_t ppc_offset = GetGuestByIndex(i).ppc_offset;
|
||||
if (ppc_offset <= 252)
|
||||
{
|
||||
ARM64Reg RX1 = R(GetGuestByIndex(i));
|
||||
ARM64Reg RX2 = R(GetGuestByIndex(i + 1));
|
||||
ARM64Reg RX1 = reg1_zero ? ARM64Reg::WZR : R(GetGuestByIndex(i));
|
||||
ARM64Reg RX2 = reg2_zero ? ARM64Reg::WZR : R(GetGuestByIndex(i + 1));
|
||||
m_emit->STP(IndexType::Signed, RX1, RX2, PPC_REG, u32(ppc_offset));
|
||||
if (mode == FlushMode::All)
|
||||
if (flush_all)
|
||||
{
|
||||
UnlockRegister(EncodeRegTo32(RX1));
|
||||
UnlockRegister(EncodeRegTo32(RX2));
|
||||
if (!reg1_zero)
|
||||
UnlockRegister(EncodeRegTo32(RX1));
|
||||
if (!reg2_zero)
|
||||
UnlockRegister(EncodeRegTo32(RX2));
|
||||
reg1.Flush();
|
||||
reg2.Flush();
|
||||
}
|
||||
|
@ -280,11 +292,14 @@ void Arm64GPRCache::FlushRegisters(BitSet32 regs, FlushMode mode, ARM64Reg tmp_r
|
|||
}
|
||||
}
|
||||
|
||||
void Arm64GPRCache::FlushCRRegisters(BitSet8 regs, FlushMode mode, ARM64Reg tmp_reg)
|
||||
void Arm64GPRCache::FlushCRRegisters(BitSet8 regs, FlushMode mode, ARM64Reg tmp_reg,
|
||||
IgnoreDiscardedRegisters ignore_discarded_registers)
|
||||
{
|
||||
for (int i : regs)
|
||||
{
|
||||
ASSERT_MSG(DYNA_REC, m_guest_registers[GUEST_CR_OFFSET + i].GetType() != RegType::Discarded,
|
||||
ASSERT_MSG(DYNA_REC,
|
||||
ignore_discarded_registers != IgnoreDiscardedRegisters::No ||
|
||||
m_guest_registers[GUEST_CR_OFFSET + i].GetType() != RegType::Discarded,
|
||||
"Attempted to flush discarded register");
|
||||
|
||||
FlushRegister(GUEST_CR_OFFSET + i, mode, tmp_reg);
|
||||
|
@ -310,10 +325,11 @@ void Arm64GPRCache::ResetCRRegisters(BitSet8 regs)
|
|||
}
|
||||
}
|
||||
|
||||
void Arm64GPRCache::Flush(FlushMode mode, ARM64Reg tmp_reg)
|
||||
void Arm64GPRCache::Flush(FlushMode mode, ARM64Reg tmp_reg,
|
||||
IgnoreDiscardedRegisters ignore_discarded_registers)
|
||||
{
|
||||
FlushRegisters(BitSet32(0xFFFFFFFF), mode, tmp_reg);
|
||||
FlushCRRegisters(BitSet8(0xFF), mode, tmp_reg);
|
||||
FlushRegisters(BitSet32(0xFFFFFFFF), mode, tmp_reg, ignore_discarded_registers);
|
||||
FlushCRRegisters(BitSet8(0xFF), mode, tmp_reg, ignore_discarded_registers);
|
||||
}
|
||||
|
||||
ARM64Reg Arm64GPRCache::R(const GuestRegInfo& guest_reg)
|
||||
|
@ -490,14 +506,19 @@ Arm64FPRCache::Arm64FPRCache() : Arm64RegCache(GUEST_FPR_COUNT)
|
|||
{
|
||||
}
|
||||
|
||||
void Arm64FPRCache::Flush(FlushMode mode, ARM64Reg tmp_reg)
|
||||
void Arm64FPRCache::Flush(FlushMode mode, ARM64Reg tmp_reg,
|
||||
IgnoreDiscardedRegisters ignore_discarded_registers)
|
||||
{
|
||||
for (size_t i = 0; i < m_guest_registers.size(); ++i)
|
||||
{
|
||||
const RegType reg_type = m_guest_registers[i].GetType();
|
||||
|
||||
if (reg_type != RegType::NotLoaded && reg_type != RegType::Discarded &&
|
||||
reg_type != RegType::Immediate)
|
||||
if (reg_type == RegType::Discarded)
|
||||
{
|
||||
ASSERT_MSG(DYNA_REC, ignore_discarded_registers != IgnoreDiscardedRegisters::No,
|
||||
"Attempted to flush discarded register");
|
||||
}
|
||||
else if (reg_type != RegType::NotLoaded && reg_type != RegType::Immediate)
|
||||
{
|
||||
FlushRegister(i, mode, tmp_reg);
|
||||
}
|
||||
|
|
|
@ -81,6 +81,12 @@ enum class FlushMode : bool
|
|||
MaintainState,
|
||||
};
|
||||
|
||||
enum class IgnoreDiscardedRegisters
|
||||
{
|
||||
No,
|
||||
Yes,
|
||||
};
|
||||
|
||||
class OpArg
|
||||
{
|
||||
public:
|
||||
|
@ -169,7 +175,8 @@ public:
|
|||
// Flushes the register cache in different ways depending on the mode.
|
||||
// A temporary register must be supplied when flushing GPRs with FlushMode::MaintainState,
|
||||
// but in other cases it can be set to ARM64Reg::INVALID_REG when convenient for the caller.
|
||||
virtual void Flush(FlushMode mode, Arm64Gen::ARM64Reg tmp_reg) = 0;
|
||||
virtual void Flush(FlushMode mode, Arm64Gen::ARM64Reg tmp_reg,
|
||||
IgnoreDiscardedRegisters ignore_discarded_registers) = 0;
|
||||
|
||||
virtual BitSet32 GetCallerSavedUsed() const = 0;
|
||||
|
||||
|
@ -177,6 +184,59 @@ public:
|
|||
// Requires unlocking after done
|
||||
Arm64Gen::ARM64Reg GetReg();
|
||||
|
||||
class ScopedARM64Reg
|
||||
{
|
||||
public:
|
||||
inline ScopedARM64Reg() = default;
|
||||
ScopedARM64Reg(const ScopedARM64Reg&) = delete;
|
||||
explicit inline ScopedARM64Reg(Arm64RegCache& cache) : m_reg(cache.GetReg()), m_gpr(&cache) {}
|
||||
inline ScopedARM64Reg(Arm64Gen::ARM64Reg reg) : m_reg(reg) {}
|
||||
inline ScopedARM64Reg(ScopedARM64Reg&& scoped_reg) { *this = std::move(scoped_reg); }
|
||||
inline ~ScopedARM64Reg() { Unlock(); }
|
||||
|
||||
inline ScopedARM64Reg& operator=(const ScopedARM64Reg&) = delete;
|
||||
inline ScopedARM64Reg& operator=(Arm64Gen::ARM64Reg reg)
|
||||
{
|
||||
Unlock();
|
||||
m_reg = reg;
|
||||
return *this;
|
||||
}
|
||||
inline ScopedARM64Reg& operator=(ScopedARM64Reg&& scoped_reg)
|
||||
{
|
||||
// Taking ownership of an existing scoped register, no need to release.
|
||||
m_reg = scoped_reg.m_reg;
|
||||
m_gpr = scoped_reg.m_gpr;
|
||||
scoped_reg.Invalidate();
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Arm64Gen::ARM64Reg GetReg() const { return m_reg; }
|
||||
inline operator Arm64Gen::ARM64Reg() const { return GetReg(); }
|
||||
inline void Unlock()
|
||||
{
|
||||
// Only unlock the register if GPR is set.
|
||||
if (m_gpr != nullptr)
|
||||
{
|
||||
m_gpr->Unlock(m_reg);
|
||||
}
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
private:
|
||||
inline void Invalidate()
|
||||
{
|
||||
m_reg = Arm64Gen::ARM64Reg::INVALID_REG;
|
||||
m_gpr = nullptr;
|
||||
}
|
||||
|
||||
Arm64Gen::ARM64Reg m_reg = Arm64Gen::ARM64Reg::INVALID_REG;
|
||||
Arm64RegCache* m_gpr = nullptr;
|
||||
};
|
||||
|
||||
// Returns a temporary register
|
||||
// Unlocking is implicitly handled through RAII
|
||||
inline ScopedARM64Reg GetScopedReg() { return ScopedARM64Reg(*this); }
|
||||
|
||||
void UpdateLastUsed(BitSet32 regs_used);
|
||||
|
||||
// Get available host registers
|
||||
|
@ -262,7 +322,9 @@ public:
|
|||
// Flushes the register cache in different ways depending on the mode.
|
||||
// A temporary register must be supplied when flushing GPRs with FlushMode::MaintainState,
|
||||
// but in other cases it can be set to ARM64Reg::INVALID_REG when convenient for the caller.
|
||||
void Flush(FlushMode mode, Arm64Gen::ARM64Reg tmp_reg) override;
|
||||
void Flush(
|
||||
FlushMode mode, Arm64Gen::ARM64Reg tmp_reg,
|
||||
IgnoreDiscardedRegisters ignore_discarded_registers = IgnoreDiscardedRegisters::No) override;
|
||||
|
||||
// Returns a guest GPR inside of a host register.
|
||||
// Will dump an immediate to the host register as well.
|
||||
|
@ -328,12 +390,12 @@ public:
|
|||
|
||||
void StoreRegisters(BitSet32 regs, Arm64Gen::ARM64Reg tmp_reg = Arm64Gen::ARM64Reg::INVALID_REG)
|
||||
{
|
||||
FlushRegisters(regs, FlushMode::All, tmp_reg);
|
||||
FlushRegisters(regs, FlushMode::All, tmp_reg, IgnoreDiscardedRegisters::No);
|
||||
}
|
||||
|
||||
void StoreCRRegisters(BitSet8 regs, Arm64Gen::ARM64Reg tmp_reg = Arm64Gen::ARM64Reg::INVALID_REG)
|
||||
{
|
||||
FlushCRRegisters(regs, FlushMode::All, tmp_reg);
|
||||
FlushCRRegisters(regs, FlushMode::All, tmp_reg, IgnoreDiscardedRegisters::No);
|
||||
}
|
||||
|
||||
void DiscardCRRegisters(BitSet8 regs);
|
||||
|
@ -368,8 +430,10 @@ private:
|
|||
void SetImmediate(const GuestRegInfo& guest_reg, u32 imm, bool dirty);
|
||||
void BindToRegister(const GuestRegInfo& guest_reg, bool will_read, bool will_write = true);
|
||||
|
||||
void FlushRegisters(BitSet32 regs, FlushMode mode, Arm64Gen::ARM64Reg tmp_reg);
|
||||
void FlushCRRegisters(BitSet8 regs, FlushMode mode, Arm64Gen::ARM64Reg tmp_reg);
|
||||
void FlushRegisters(BitSet32 regs, FlushMode mode, Arm64Gen::ARM64Reg tmp_reg,
|
||||
IgnoreDiscardedRegisters ignore_discarded_registers);
|
||||
void FlushCRRegisters(BitSet8 regs, FlushMode mode, Arm64Gen::ARM64Reg tmp_reg,
|
||||
IgnoreDiscardedRegisters ignore_discarded_registers);
|
||||
};
|
||||
|
||||
class Arm64FPRCache : public Arm64RegCache
|
||||
|
@ -379,7 +443,9 @@ public:
|
|||
|
||||
// Flushes the register cache in different ways depending on the mode.
|
||||
// The temporary register can be set to ARM64Reg::INVALID_REG when convenient for the caller.
|
||||
void Flush(FlushMode mode, Arm64Gen::ARM64Reg tmp_reg) override;
|
||||
void Flush(
|
||||
FlushMode mode, Arm64Gen::ARM64Reg tmp_reg,
|
||||
IgnoreDiscardedRegisters ignore_discarded_registers = IgnoreDiscardedRegisters::No) override;
|
||||
|
||||
// Returns a guest register inside of a host register
|
||||
// Will dump an immediate to the host register as well
|
||||
|
|
|
@ -48,17 +48,16 @@ void JitArm64::FixGTBeforeSettingCRFieldBit(Arm64Gen::ARM64Reg reg)
|
|||
// if the internal representation either has bit 63 set or has all bits set to zero.
|
||||
// If all bits are zero and we set some bit that's unrelated to GT, we need to set bit 63 so GT
|
||||
// doesn't accidentally become considered set. Gross but necessary; this can break actual games.
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
auto WA = gpr.GetScopedReg();
|
||||
ARM64Reg XA = EncodeRegTo64(WA);
|
||||
ORR(XA, reg, LogicalImm(1ULL << 63, GPRSize::B64));
|
||||
CMP(reg, ARM64Reg::ZR);
|
||||
CSEL(reg, reg, XA, CC_NEQ);
|
||||
gpr.Unlock(WA);
|
||||
}
|
||||
|
||||
void JitArm64::UpdateFPExceptionSummary(ARM64Reg fpscr)
|
||||
{
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
auto WA = gpr.GetScopedReg();
|
||||
|
||||
// fpscr.VX = (fpscr & FPSCR_VX_ANY) != 0
|
||||
MOVI2R(WA, FPSCR_VX_ANY);
|
||||
|
@ -71,8 +70,6 @@ void JitArm64::UpdateFPExceptionSummary(ARM64Reg fpscr)
|
|||
TST(WA, fpscr, ArithOption(fpscr, ShiftType::LSR, 22));
|
||||
CSET(WA, CCFlags::CC_NEQ);
|
||||
BFI(fpscr, WA, MathUtil::IntLog2(FPSCR_FEX), 1);
|
||||
|
||||
gpr.Unlock(WA);
|
||||
}
|
||||
|
||||
void JitArm64::UpdateRoundingMode()
|
||||
|
@ -135,7 +132,7 @@ void JitArm64::mcrxr(UGeckoInstruction inst)
|
|||
JITDISABLE(bJITSystemRegistersOff);
|
||||
|
||||
gpr.BindCRToRegister(inst.CRFD, false);
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
auto WA = gpr.GetScopedReg();
|
||||
ARM64Reg XA = EncodeRegTo64(WA);
|
||||
ARM64Reg XB = gpr.CR(inst.CRFD);
|
||||
ARM64Reg WB = EncodeRegTo32(XB);
|
||||
|
@ -155,8 +152,6 @@ void JitArm64::mcrxr(UGeckoInstruction inst)
|
|||
// Clear XER[0-3]
|
||||
static_assert(PPCSTATE_OFF(xer_ca) + 1 == PPCSTATE_OFF(xer_so_ov));
|
||||
STRH(IndexType::Unsigned, ARM64Reg::WZR, PPC_REG, PPCSTATE_OFF(xer_ca));
|
||||
|
||||
gpr.Unlock(WA);
|
||||
}
|
||||
|
||||
void JitArm64::mfsr(UGeckoInstruction inst)
|
||||
|
@ -186,14 +181,12 @@ void JitArm64::mfsrin(UGeckoInstruction inst)
|
|||
|
||||
ARM64Reg RB = gpr.R(b);
|
||||
ARM64Reg RD = gpr.R(d);
|
||||
ARM64Reg index = gpr.GetReg();
|
||||
auto index = gpr.GetScopedReg();
|
||||
ARM64Reg addr = EncodeRegTo64(RD);
|
||||
|
||||
UBFM(index, RB, 28, 31);
|
||||
ADDI2R(addr, PPC_REG, PPCSTATE_OFF_SR(0), addr);
|
||||
LDR(RD, addr, ArithOption(EncodeRegTo64(index), true));
|
||||
|
||||
gpr.Unlock(index);
|
||||
}
|
||||
|
||||
void JitArm64::mtsrin(UGeckoInstruction inst)
|
||||
|
@ -206,14 +199,12 @@ void JitArm64::mtsrin(UGeckoInstruction inst)
|
|||
|
||||
ARM64Reg RB = gpr.R(b);
|
||||
ARM64Reg RD = gpr.R(d);
|
||||
ARM64Reg index = gpr.GetReg();
|
||||
ARM64Reg addr = gpr.GetReg();
|
||||
auto index = gpr.GetScopedReg();
|
||||
auto addr = gpr.GetScopedReg();
|
||||
|
||||
UBFM(index, RB, 28, 31);
|
||||
ADDI2R(EncodeRegTo64(addr), PPC_REG, PPCSTATE_OFF_SR(0), EncodeRegTo64(addr));
|
||||
STR(RD, EncodeRegTo64(addr), ArithOption(EncodeRegTo64(index), true));
|
||||
|
||||
gpr.Unlock(index, addr);
|
||||
}
|
||||
|
||||
void JitArm64::twx(UGeckoInstruction inst)
|
||||
|
@ -223,7 +214,7 @@ void JitArm64::twx(UGeckoInstruction inst)
|
|||
|
||||
s32 a = inst.RA;
|
||||
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
auto WA = gpr.GetScopedReg();
|
||||
|
||||
if (inst.OPCD == 3) // twi
|
||||
{
|
||||
|
@ -278,8 +269,6 @@ void JitArm64::twx(UGeckoInstruction inst)
|
|||
fpr.Flush(FlushMode::All, ARM64Reg::INVALID_REG);
|
||||
WriteExit(js.compilerPC + 4);
|
||||
}
|
||||
|
||||
gpr.Unlock(WA);
|
||||
}
|
||||
|
||||
void JitArm64::mfspr(UGeckoInstruction inst)
|
||||
|
@ -294,19 +283,19 @@ void JitArm64::mfspr(UGeckoInstruction inst)
|
|||
case SPR_TL:
|
||||
case SPR_TU:
|
||||
{
|
||||
ARM64Reg Wg = gpr.GetReg();
|
||||
auto Wg = gpr.GetScopedReg();
|
||||
ARM64Reg Xg = EncodeRegTo64(Wg);
|
||||
|
||||
ARM64Reg Wresult = gpr.GetReg();
|
||||
auto Wresult = gpr.GetScopedReg();
|
||||
ARM64Reg Xresult = EncodeRegTo64(Wresult);
|
||||
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
ARM64Reg WB = gpr.GetReg();
|
||||
auto WA = gpr.GetScopedReg();
|
||||
auto WB = gpr.GetScopedReg();
|
||||
ARM64Reg XA = EncodeRegTo64(WA);
|
||||
ARM64Reg XB = EncodeRegTo64(WB);
|
||||
|
||||
ARM64Reg VC = fpr.GetReg();
|
||||
ARM64Reg VD = fpr.GetReg();
|
||||
auto VC = fpr.GetScopedReg();
|
||||
auto VD = fpr.GetScopedReg();
|
||||
ARM64Reg SC = EncodeRegToSingle(VC);
|
||||
ARM64Reg SD = EncodeRegToSingle(VD);
|
||||
|
||||
|
@ -371,8 +360,6 @@ void JitArm64::mfspr(UGeckoInstruction inst)
|
|||
else
|
||||
LSR(EncodeRegTo64(gpr.R(n)), Xresult, 32);
|
||||
|
||||
gpr.Unlock(Wg, Wresult, WA, WB);
|
||||
fpr.Unlock(VC, VD);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -381,22 +368,18 @@ void JitArm64::mfspr(UGeckoInstruction inst)
|
|||
LSR(EncodeRegTo64(gpr.R(d)), Xresult, 32);
|
||||
else
|
||||
MOV(gpr.R(d), Wresult);
|
||||
|
||||
gpr.Unlock(Wg, Wresult, WA, WB);
|
||||
fpr.Unlock(VC, VD);
|
||||
}
|
||||
break;
|
||||
case SPR_XER:
|
||||
{
|
||||
gpr.BindToRegister(d, false);
|
||||
ARM64Reg RD = gpr.R(d);
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
auto WA = gpr.GetScopedReg();
|
||||
LDRH(IndexType::Unsigned, RD, PPC_REG, PPCSTATE_OFF(xer_stringctrl));
|
||||
LDRB(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(xer_ca));
|
||||
ORR(RD, RD, WA, ArithOption(WA, ShiftType::LSL, XER_CA_SHIFT));
|
||||
LDRB(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(xer_so_ov));
|
||||
ORR(RD, RD, WA, ArithOption(WA, ShiftType::LSL, XER_OV_SHIFT));
|
||||
gpr.Unlock(WA);
|
||||
}
|
||||
break;
|
||||
case SPR_WPAR:
|
||||
|
@ -462,14 +445,13 @@ void JitArm64::mtspr(UGeckoInstruction inst)
|
|||
case SPR_XER:
|
||||
{
|
||||
ARM64Reg RD = gpr.R(inst.RD);
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
auto WA = gpr.GetScopedReg();
|
||||
AND(WA, RD, LogicalImm(0xFFFFFF7F, GPRSize::B32));
|
||||
STRH(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(xer_stringctrl));
|
||||
UBFM(WA, RD, XER_CA_SHIFT, XER_CA_SHIFT + 1);
|
||||
STRB(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(xer_ca));
|
||||
UBFM(WA, RD, XER_OV_SHIFT, 31); // Same as WA = RD >> XER_OV_SHIFT
|
||||
STRB(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(xer_so_ov));
|
||||
gpr.Unlock(WA);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
@ -553,114 +535,112 @@ void JitArm64::crXXX(UGeckoInstruction inst)
|
|||
return;
|
||||
}
|
||||
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
auto WA = gpr.GetScopedReg();
|
||||
ARM64Reg XA = EncodeRegTo64(WA);
|
||||
ARM64Reg WB = gpr.GetReg();
|
||||
ARM64Reg XB = EncodeRegTo64(WB);
|
||||
|
||||
// creqv or crnand or crnor
|
||||
bool negateA = inst.SUBOP10 == 289 || inst.SUBOP10 == 225 || inst.SUBOP10 == 33;
|
||||
// crandc or crorc or crnand or crnor
|
||||
bool negateB =
|
||||
inst.SUBOP10 == 129 || inst.SUBOP10 == 417 || inst.SUBOP10 == 225 || inst.SUBOP10 == 33;
|
||||
|
||||
// GetCRFieldBit
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
int field = i ? inst.CRBB >> 2 : inst.CRBA >> 2;
|
||||
int bit = i ? 3 - (inst.CRBB & 3) : 3 - (inst.CRBA & 3);
|
||||
ARM64Reg out = i ? XB : XA;
|
||||
bool negate = i ? negateB : negateA;
|
||||
auto WB = gpr.GetScopedReg();
|
||||
ARM64Reg XB = EncodeRegTo64(WB);
|
||||
|
||||
ARM64Reg XC = gpr.CR(field);
|
||||
ARM64Reg WC = EncodeRegTo32(XC);
|
||||
switch (bit)
|
||||
// creqv or crnand or crnor
|
||||
bool negateA = inst.SUBOP10 == 289 || inst.SUBOP10 == 225 || inst.SUBOP10 == 33;
|
||||
// crandc or crorc or crnand or crnor
|
||||
bool negateB =
|
||||
inst.SUBOP10 == 129 || inst.SUBOP10 == 417 || inst.SUBOP10 == 225 || inst.SUBOP10 == 33;
|
||||
|
||||
// GetCRFieldBit
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
case PowerPC::CR_SO_BIT: // check bit 59 set
|
||||
UBFX(out, XC, PowerPC::CR_EMU_SO_BIT, 1);
|
||||
if (negate)
|
||||
EOR(out, out, LogicalImm(1, GPRSize::B64));
|
||||
break;
|
||||
int field = i ? inst.CRBB >> 2 : inst.CRBA >> 2;
|
||||
int bit = i ? 3 - (inst.CRBB & 3) : 3 - (inst.CRBA & 3);
|
||||
ARM64Reg out = i ? XB : XA;
|
||||
bool negate = i ? negateB : negateA;
|
||||
|
||||
case PowerPC::CR_EQ_BIT: // check bits 31-0 == 0
|
||||
CMP(WC, ARM64Reg::WZR);
|
||||
CSET(out, negate ? CC_NEQ : CC_EQ);
|
||||
break;
|
||||
ARM64Reg XC = gpr.CR(field);
|
||||
ARM64Reg WC = EncodeRegTo32(XC);
|
||||
switch (bit)
|
||||
{
|
||||
case PowerPC::CR_SO_BIT: // check bit 59 set
|
||||
UBFX(out, XC, PowerPC::CR_EMU_SO_BIT, 1);
|
||||
if (negate)
|
||||
EOR(out, out, LogicalImm(1, GPRSize::B64));
|
||||
break;
|
||||
|
||||
case PowerPC::CR_GT_BIT: // check val > 0
|
||||
CMP(XC, ARM64Reg::ZR);
|
||||
CSET(out, negate ? CC_LE : CC_GT);
|
||||
break;
|
||||
case PowerPC::CR_EQ_BIT: // check bits 31-0 == 0
|
||||
CMP(WC, ARM64Reg::WZR);
|
||||
CSET(out, negate ? CC_NEQ : CC_EQ);
|
||||
break;
|
||||
|
||||
case PowerPC::CR_LT_BIT: // check bit 62 set
|
||||
UBFX(out, XC, PowerPC::CR_EMU_LT_BIT, 1);
|
||||
if (negate)
|
||||
EOR(out, out, LogicalImm(1, GPRSize::B64));
|
||||
break;
|
||||
case PowerPC::CR_GT_BIT: // check val > 0
|
||||
CMP(XC, ARM64Reg::ZR);
|
||||
CSET(out, negate ? CC_LE : CC_GT);
|
||||
break;
|
||||
|
||||
default:
|
||||
ASSERT_MSG(DYNA_REC, false, "Invalid CR bit");
|
||||
case PowerPC::CR_LT_BIT: // check bit 62 set
|
||||
UBFX(out, XC, PowerPC::CR_EMU_LT_BIT, 1);
|
||||
if (negate)
|
||||
EOR(out, out, LogicalImm(1, GPRSize::B64));
|
||||
break;
|
||||
|
||||
default:
|
||||
ASSERT_MSG(DYNA_REC, false, "Invalid CR bit");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute combined bit
|
||||
switch (inst.SUBOP10)
|
||||
{
|
||||
case 33: // crnor: ~(A || B) == (~A && ~B)
|
||||
case 129: // crandc: A && ~B
|
||||
case 257: // crand: A && B
|
||||
AND(XA, XA, XB);
|
||||
break;
|
||||
// Compute combined bit
|
||||
switch (inst.SUBOP10)
|
||||
{
|
||||
case 33: // crnor: ~(A || B) == (~A && ~B)
|
||||
case 129: // crandc: A && ~B
|
||||
case 257: // crand: A && B
|
||||
AND(XA, XA, XB);
|
||||
break;
|
||||
|
||||
case 193: // crxor: A ^ B
|
||||
case 289: // creqv: ~(A ^ B) = ~A ^ B
|
||||
EOR(XA, XA, XB);
|
||||
break;
|
||||
case 193: // crxor: A ^ B
|
||||
case 289: // creqv: ~(A ^ B) = ~A ^ B
|
||||
EOR(XA, XA, XB);
|
||||
break;
|
||||
|
||||
case 225: // crnand: ~(A && B) == (~A || ~B)
|
||||
case 417: // crorc: A || ~B
|
||||
case 449: // cror: A || B
|
||||
ORR(XA, XA, XB);
|
||||
break;
|
||||
case 225: // crnand: ~(A && B) == (~A || ~B)
|
||||
case 417: // crorc: A || ~B
|
||||
case 449: // cror: A || B
|
||||
ORR(XA, XA, XB);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Store result bit in CRBD
|
||||
int field = inst.CRBD >> 2;
|
||||
int bit = 3 - (inst.CRBD & 3);
|
||||
|
||||
gpr.Unlock(WB);
|
||||
WB = ARM64Reg::INVALID_REG;
|
||||
gpr.BindCRToRegister(field, true);
|
||||
XB = gpr.CR(field);
|
||||
ARM64Reg CR = gpr.CR(field);
|
||||
|
||||
if (bit != PowerPC::CR_GT_BIT)
|
||||
FixGTBeforeSettingCRFieldBit(XB);
|
||||
FixGTBeforeSettingCRFieldBit(CR);
|
||||
|
||||
switch (bit)
|
||||
{
|
||||
case PowerPC::CR_SO_BIT: // set bit 59 to input
|
||||
BFI(XB, XA, PowerPC::CR_EMU_SO_BIT, 1);
|
||||
BFI(CR, XA, PowerPC::CR_EMU_SO_BIT, 1);
|
||||
break;
|
||||
|
||||
case PowerPC::CR_EQ_BIT: // clear low 32 bits, set bit 0 to !input
|
||||
AND(XB, XB, LogicalImm(0xFFFF'FFFF'0000'0000, GPRSize::B64));
|
||||
AND(CR, CR, LogicalImm(0xFFFF'FFFF'0000'0000, GPRSize::B64));
|
||||
EOR(XA, XA, LogicalImm(1, GPRSize::B64));
|
||||
ORR(XB, XB, XA);
|
||||
ORR(CR, CR, XA);
|
||||
break;
|
||||
|
||||
case PowerPC::CR_GT_BIT: // set bit 63 to !input
|
||||
EOR(XA, XA, LogicalImm(1, GPRSize::B64));
|
||||
BFI(XB, XA, 63, 1);
|
||||
BFI(CR, XA, 63, 1);
|
||||
break;
|
||||
|
||||
case PowerPC::CR_LT_BIT: // set bit 62 to input
|
||||
BFI(XB, XA, PowerPC::CR_EMU_LT_BIT, 1);
|
||||
BFI(CR, XA, PowerPC::CR_EMU_LT_BIT, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
ORR(XB, XB, LogicalImm(1ULL << 32, GPRSize::B64));
|
||||
|
||||
gpr.Unlock(WA);
|
||||
ORR(CR, CR, LogicalImm(1ULL << 32, GPRSize::B64));
|
||||
}
|
||||
|
||||
void JitArm64::mfcr(UGeckoInstruction inst)
|
||||
|
@ -670,8 +650,8 @@ void JitArm64::mfcr(UGeckoInstruction inst)
|
|||
|
||||
gpr.BindToRegister(inst.RD, false);
|
||||
ARM64Reg WA = gpr.R(inst.RD);
|
||||
ARM64Reg WB = gpr.GetReg();
|
||||
ARM64Reg WC = gpr.GetReg();
|
||||
auto WB = gpr.GetScopedReg();
|
||||
auto WC = gpr.GetScopedReg();
|
||||
ARM64Reg XA = EncodeRegTo64(WA);
|
||||
ARM64Reg XB = EncodeRegTo64(WB);
|
||||
ARM64Reg XC = EncodeRegTo64(WC);
|
||||
|
@ -716,8 +696,6 @@ void JitArm64::mfcr(UGeckoInstruction inst)
|
|||
else if (!js.op->crInUse[i])
|
||||
gpr.StoreCRRegisters(BitSet8{i}, WC);
|
||||
}
|
||||
|
||||
gpr.Unlock(WB, WC);
|
||||
}
|
||||
|
||||
void JitArm64::mtcrf(UGeckoInstruction inst)
|
||||
|
@ -729,7 +707,7 @@ void JitArm64::mtcrf(UGeckoInstruction inst)
|
|||
if (crm != 0)
|
||||
{
|
||||
ARM64Reg RS = gpr.R(inst.RS);
|
||||
ARM64Reg WB = gpr.GetReg();
|
||||
auto WB = gpr.GetScopedReg();
|
||||
ARM64Reg XB = EncodeRegTo64(WB);
|
||||
MOVP2R(XB, PowerPC::ConditionRegister::s_crTable.data());
|
||||
for (int i = 0; i < 8; ++i)
|
||||
|
@ -753,7 +731,6 @@ void JitArm64::mtcrf(UGeckoInstruction inst)
|
|||
LDR(CR, XB, ArithOption(CR, true));
|
||||
}
|
||||
}
|
||||
gpr.Unlock(WB);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -771,7 +748,7 @@ void JitArm64::mcrfs(UGeckoInstruction inst)
|
|||
|
||||
gpr.BindCRToRegister(field, false);
|
||||
ARM64Reg CR = gpr.CR(field);
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
auto WA = gpr.GetScopedReg();
|
||||
ARM64Reg WCR = EncodeRegTo32(CR);
|
||||
ARM64Reg XA = EncodeRegTo64(WA);
|
||||
|
||||
|
@ -789,8 +766,6 @@ void JitArm64::mcrfs(UGeckoInstruction inst)
|
|||
|
||||
MOVP2R(XA, PowerPC::ConditionRegister::s_crTable.data());
|
||||
LDR(CR, XA, ArithOption(CR, true));
|
||||
|
||||
gpr.Unlock(WA);
|
||||
}
|
||||
|
||||
void JitArm64::mffsx(UGeckoInstruction inst)
|
||||
|
@ -799,7 +774,7 @@ void JitArm64::mffsx(UGeckoInstruction inst)
|
|||
JITDISABLE(bJITSystemRegistersOff);
|
||||
FALLBACK_IF(inst.Rc);
|
||||
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
auto WA = gpr.GetScopedReg();
|
||||
ARM64Reg XA = EncodeRegTo64(WA);
|
||||
|
||||
LDR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(fpscr));
|
||||
|
@ -808,8 +783,6 @@ void JitArm64::mffsx(UGeckoInstruction inst)
|
|||
|
||||
ORR(XA, XA, LogicalImm(0xFFF8'0000'0000'0000, GPRSize::B64));
|
||||
m_float_emit.FMOV(EncodeRegToDouble(VD), XA);
|
||||
|
||||
gpr.Unlock(WA);
|
||||
}
|
||||
|
||||
void JitArm64::mtfsb0x(UGeckoInstruction inst)
|
||||
|
@ -824,17 +797,17 @@ void JitArm64::mtfsb0x(UGeckoInstruction inst)
|
|||
if (mask == FPSCR_FEX || mask == FPSCR_VX)
|
||||
return;
|
||||
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
{
|
||||
auto WA = gpr.GetScopedReg();
|
||||
|
||||
LDR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(fpscr));
|
||||
LDR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(fpscr));
|
||||
|
||||
AND(WA, WA, LogicalImm(inverted_mask, GPRSize::B32));
|
||||
AND(WA, WA, LogicalImm(inverted_mask, GPRSize::B32));
|
||||
|
||||
if ((mask & (FPSCR_ANY_X | FPSCR_ANY_E)) != 0)
|
||||
UpdateFPExceptionSummary(WA);
|
||||
STR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(fpscr));
|
||||
|
||||
gpr.Unlock(WA);
|
||||
if ((mask & (FPSCR_ANY_X | FPSCR_ANY_E)) != 0)
|
||||
UpdateFPExceptionSummary(WA);
|
||||
STR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(fpscr));
|
||||
}
|
||||
|
||||
if (inst.CRBD >= 29)
|
||||
UpdateRoundingMode();
|
||||
|
@ -852,25 +825,24 @@ void JitArm64::mtfsb1x(UGeckoInstruction inst)
|
|||
if (mask == FPSCR_FEX || mask == FPSCR_VX)
|
||||
return;
|
||||
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
|
||||
LDR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(fpscr));
|
||||
|
||||
if ((mask & FPSCR_ANY_X) != 0)
|
||||
{
|
||||
ARM64Reg WB = gpr.GetReg();
|
||||
TST(WA, LogicalImm(mask, GPRSize::B32));
|
||||
ORR(WB, WA, LogicalImm(1 << 31, GPRSize::B32));
|
||||
CSEL(WA, WA, WB, CCFlags::CC_NEQ);
|
||||
gpr.Unlock(WB);
|
||||
auto WA = gpr.GetScopedReg();
|
||||
|
||||
LDR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(fpscr));
|
||||
|
||||
if ((mask & FPSCR_ANY_X) != 0)
|
||||
{
|
||||
auto WB = gpr.GetScopedReg();
|
||||
TST(WA, LogicalImm(mask, GPRSize::B32));
|
||||
ORR(WB, WA, LogicalImm(1 << 31, GPRSize::B32));
|
||||
CSEL(WA, WA, WB, CCFlags::CC_NEQ);
|
||||
}
|
||||
ORR(WA, WA, LogicalImm(mask, GPRSize::B32));
|
||||
|
||||
if ((mask & (FPSCR_ANY_X | FPSCR_ANY_E)) != 0)
|
||||
UpdateFPExceptionSummary(WA);
|
||||
STR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(fpscr));
|
||||
}
|
||||
ORR(WA, WA, LogicalImm(mask, GPRSize::B32));
|
||||
|
||||
if ((mask & (FPSCR_ANY_X | FPSCR_ANY_E)) != 0)
|
||||
UpdateFPExceptionSummary(WA);
|
||||
STR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(fpscr));
|
||||
|
||||
gpr.Unlock(WA);
|
||||
|
||||
if (inst.CRBD >= 29)
|
||||
UpdateRoundingMode();
|
||||
|
@ -887,32 +859,31 @@ void JitArm64::mtfsfix(UGeckoInstruction inst)
|
|||
u8 shift = 28 - 4 * inst.CRFD;
|
||||
u32 mask = 0xF << shift;
|
||||
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
|
||||
LDR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(fpscr));
|
||||
|
||||
if (imm == 0xF)
|
||||
{
|
||||
ORR(WA, WA, LogicalImm(mask, GPRSize::B32));
|
||||
}
|
||||
else if (imm == 0x0)
|
||||
{
|
||||
const u32 inverted_mask = ~mask;
|
||||
AND(WA, WA, LogicalImm(inverted_mask, GPRSize::B32));
|
||||
}
|
||||
else
|
||||
{
|
||||
ARM64Reg WB = gpr.GetReg();
|
||||
MOVZ(WB, imm);
|
||||
BFI(WA, WB, shift, 4);
|
||||
gpr.Unlock(WB);
|
||||
}
|
||||
auto WA = gpr.GetScopedReg();
|
||||
|
||||
if ((mask & (FPSCR_FEX | FPSCR_VX | FPSCR_ANY_X | FPSCR_ANY_E)) != 0)
|
||||
UpdateFPExceptionSummary(WA);
|
||||
STR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(fpscr));
|
||||
LDR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(fpscr));
|
||||
|
||||
gpr.Unlock(WA);
|
||||
if (imm == 0xF)
|
||||
{
|
||||
ORR(WA, WA, LogicalImm(mask, GPRSize::B32));
|
||||
}
|
||||
else if (imm == 0x0)
|
||||
{
|
||||
const u32 inverted_mask = ~mask;
|
||||
AND(WA, WA, LogicalImm(inverted_mask, GPRSize::B32));
|
||||
}
|
||||
else
|
||||
{
|
||||
auto WB = gpr.GetScopedReg();
|
||||
MOVZ(WB, imm);
|
||||
BFI(WA, WB, shift, 4);
|
||||
}
|
||||
|
||||
if ((mask & (FPSCR_FEX | FPSCR_VX | FPSCR_ANY_X | FPSCR_ANY_E)) != 0)
|
||||
UpdateFPExceptionSummary(WA);
|
||||
STR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(fpscr));
|
||||
}
|
||||
|
||||
// Field 7 contains NI and RN.
|
||||
if (inst.CRFD == 7)
|
||||
|
@ -936,49 +907,43 @@ void JitArm64::mtfsfx(UGeckoInstruction inst)
|
|||
if (mask == 0xFFFFFFFF)
|
||||
{
|
||||
ARM64Reg VB = fpr.R(inst.FB, RegType::LowerPair);
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
auto WA = gpr.GetScopedReg();
|
||||
|
||||
m_float_emit.FMOV(WA, EncodeRegToSingle(VB));
|
||||
|
||||
UpdateFPExceptionSummary(WA);
|
||||
STR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(fpscr));
|
||||
|
||||
gpr.Unlock(WA);
|
||||
}
|
||||
else if (mask != 0)
|
||||
{
|
||||
ARM64Reg VB = fpr.R(inst.FB, RegType::LowerPair);
|
||||
ARM64Reg WA = gpr.GetReg();
|
||||
ARM64Reg WB = gpr.GetReg();
|
||||
|
||||
LDR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(fpscr));
|
||||
m_float_emit.FMOV(WB, EncodeRegToSingle(VB));
|
||||
|
||||
if (LogicalImm imm = LogicalImm(mask, GPRSize::B32))
|
||||
auto WA = gpr.GetScopedReg();
|
||||
{
|
||||
const u32 inverted_mask = ~mask;
|
||||
AND(WA, WA, LogicalImm(inverted_mask, GPRSize::B32));
|
||||
AND(WB, WB, imm);
|
||||
auto WB = gpr.GetScopedReg();
|
||||
|
||||
LDR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(fpscr));
|
||||
m_float_emit.FMOV(WB, EncodeRegToSingle(VB));
|
||||
|
||||
if (LogicalImm imm = LogicalImm(mask, GPRSize::B32))
|
||||
{
|
||||
const u32 inverted_mask = ~mask;
|
||||
AND(WA, WA, LogicalImm(inverted_mask, GPRSize::B32));
|
||||
AND(WB, WB, imm);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto WC = gpr.GetScopedReg();
|
||||
|
||||
MOVI2R(WC, mask);
|
||||
BIC(WA, WA, WC);
|
||||
AND(WB, WB, WC);
|
||||
}
|
||||
ORR(WA, WA, WB);
|
||||
}
|
||||
else
|
||||
{
|
||||
ARM64Reg WC = gpr.GetReg();
|
||||
|
||||
MOVI2R(WC, mask);
|
||||
BIC(WA, WA, WC);
|
||||
AND(WB, WB, WC);
|
||||
|
||||
gpr.Unlock(WC);
|
||||
}
|
||||
ORR(WA, WA, WB);
|
||||
|
||||
gpr.Unlock(WB);
|
||||
|
||||
if ((mask & (FPSCR_FEX | FPSCR_VX | FPSCR_ANY_X | FPSCR_ANY_E)) != 0)
|
||||
UpdateFPExceptionSummary(WA);
|
||||
STR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(fpscr));
|
||||
|
||||
gpr.Unlock(WA);
|
||||
}
|
||||
|
||||
if (inst.FM & 1)
|
||||
|
|
|
@ -257,6 +257,12 @@ void ByteswapAfterLoad(ARM64XEmitter* emit, ARM64FloatEmitter* float_emit, ARM64
|
|||
ARM64Reg ByteswapBeforeStore(ARM64XEmitter* emit, ARM64FloatEmitter* float_emit, ARM64Reg tmp_reg,
|
||||
ARM64Reg src_reg, u32 flags, bool want_reversed)
|
||||
{
|
||||
// Byteswapping zero is still zero.
|
||||
// We'd typically expect a writable register to be passed in, but recognize
|
||||
// WZR for optimization purposes.
|
||||
if ((flags & BackPatchInfo::FLAG_FLOAT) == 0 && src_reg == ARM64Reg::WZR)
|
||||
return ARM64Reg::WZR;
|
||||
|
||||
ARM64Reg dst_reg = src_reg;
|
||||
|
||||
if (want_reversed == !(flags & BackPatchInfo::FLAG_REVERSE))
|
||||
|
|
|
@ -49,7 +49,8 @@ Common::Symbol* PPCSymbolDB::AddFunction(const Core::CPUThreadGuard& guard, u32
|
|||
}
|
||||
|
||||
void PPCSymbolDB::AddKnownSymbol(const Core::CPUThreadGuard& guard, u32 startAddr, u32 size,
|
||||
const std::string& name, Common::Symbol::Type type)
|
||||
const std::string& name, const std::string& object_name,
|
||||
Common::Symbol::Type type)
|
||||
{
|
||||
auto iter = m_functions.find(startAddr);
|
||||
if (iter != m_functions.end())
|
||||
|
@ -57,6 +58,7 @@ void PPCSymbolDB::AddKnownSymbol(const Core::CPUThreadGuard& guard, u32 startAdd
|
|||
// already got it, let's just update name, checksum & size to be sure.
|
||||
Common::Symbol* tempfunc = &iter->second;
|
||||
tempfunc->Rename(name);
|
||||
tempfunc->object_name = object_name;
|
||||
tempfunc->hash = HashSignatureDB::ComputeCodeChecksum(guard, startAddr, startAddr + size - 4);
|
||||
tempfunc->type = type;
|
||||
tempfunc->size = size;
|
||||
|
@ -65,6 +67,7 @@ void PPCSymbolDB::AddKnownSymbol(const Core::CPUThreadGuard& guard, u32 startAdd
|
|||
{
|
||||
// new symbol. run analyze.
|
||||
auto& new_symbol = m_functions.emplace(startAddr, name).first->second;
|
||||
new_symbol.object_name = object_name;
|
||||
new_symbol.type = type;
|
||||
new_symbol.address = startAddr;
|
||||
|
||||
|
@ -399,6 +402,13 @@ bool PPCSymbolDB::LoadMap(const Core::CPUThreadGuard& guard, const std::string&
|
|||
if (name[strlen(name) - 1] == '\r')
|
||||
name[strlen(name) - 1] = 0;
|
||||
|
||||
// Split the current name string into separate parts, and get the object name
|
||||
// if it exists.
|
||||
const std::vector<std::string> parts = SplitString(name, '\t');
|
||||
const std::string name_string(StripWhitespace(parts[0]));
|
||||
const std::string object_filename_string =
|
||||
parts.size() > 1 ? std::string(StripWhitespace(parts[1])) : "";
|
||||
|
||||
// Check if this is a valid entry.
|
||||
if (strlen(name) > 0)
|
||||
{
|
||||
|
@ -435,7 +445,7 @@ bool PPCSymbolDB::LoadMap(const Core::CPUThreadGuard& guard, const std::string&
|
|||
if (good)
|
||||
{
|
||||
++good_count;
|
||||
AddKnownSymbol(guard, vaddress, size, name, type);
|
||||
AddKnownSymbol(guard, vaddress, size, name_string, object_filename_string, type);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -473,8 +483,13 @@ bool PPCSymbolDB::SaveSymbolMap(const std::string& filename) const
|
|||
for (const auto& symbol : function_symbols)
|
||||
{
|
||||
// Write symbol address, size, virtual address, alignment, name
|
||||
f.WriteString(fmt::format("{0:08x} {1:08x} {2:08x} {3} {4}\n", symbol->address, symbol->size,
|
||||
symbol->address, 0, symbol->name));
|
||||
std::string line = fmt::format("{0:08x} {1:06x} {2:08x} {3} {4}", symbol->address, symbol->size,
|
||||
symbol->address, 0, symbol->name);
|
||||
// Also write the object name if it exists
|
||||
if (!symbol->object_name.empty())
|
||||
line += fmt::format(" \t{0}", symbol->object_name);
|
||||
line += "\n";
|
||||
f.WriteString(line);
|
||||
}
|
||||
|
||||
// Write .data section
|
||||
|
@ -482,8 +497,13 @@ bool PPCSymbolDB::SaveSymbolMap(const std::string& filename) const
|
|||
for (const auto& symbol : data_symbols)
|
||||
{
|
||||
// Write symbol address, size, virtual address, alignment, name
|
||||
f.WriteString(fmt::format("{0:08x} {1:08x} {2:08x} {3} {4}\n", symbol->address, symbol->size,
|
||||
symbol->address, 0, symbol->name));
|
||||
std::string line = fmt::format("{0:08x} {1:06x} {2:08x} {3} {4}", symbol->address, symbol->size,
|
||||
symbol->address, 0, symbol->name);
|
||||
// Also write the object name if it exists
|
||||
if (!symbol->object_name.empty())
|
||||
line += fmt::format(" \t{0}", symbol->object_name);
|
||||
line += "\n";
|
||||
f.WriteString(line);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
@ -23,7 +23,7 @@ public:
|
|||
|
||||
Common::Symbol* AddFunction(const Core::CPUThreadGuard& guard, u32 start_addr) override;
|
||||
void AddKnownSymbol(const Core::CPUThreadGuard& guard, u32 startAddr, u32 size,
|
||||
const std::string& name,
|
||||
const std::string& name, const std::string& object_name,
|
||||
Common::Symbol::Type type = Common::Symbol::Type::Function);
|
||||
|
||||
Common::Symbol* GetSymbolFromAddr(u32 addr) override;
|
||||
|
|
|
@ -240,11 +240,9 @@ bool NANDImporter::ExtractCertificates()
|
|||
|
||||
for (const PEMCertificate& certificate : certificates)
|
||||
{
|
||||
const auto search_result =
|
||||
std::search(content_bytes.begin(), content_bytes.end(), certificate.search_bytes.begin(),
|
||||
certificate.search_bytes.end());
|
||||
const auto search_result = std::ranges::search(content_bytes, certificate.search_bytes);
|
||||
|
||||
if (search_result == content_bytes.end())
|
||||
if (search_result.empty())
|
||||
{
|
||||
ERROR_LOG_FMT(DISCIO, "ExtractCertificates: Could not find offset for certficate '{}'",
|
||||
certificate.filename);
|
||||
|
@ -252,7 +250,8 @@ bool NANDImporter::ExtractCertificates()
|
|||
}
|
||||
|
||||
const std::string pem_file_path = m_nand_root + std::string(certificate.filename);
|
||||
const ptrdiff_t certificate_offset = std::distance(content_bytes.begin(), search_result);
|
||||
const ptrdiff_t certificate_offset =
|
||||
std::distance(content_bytes.begin(), search_result.begin());
|
||||
constexpr int min_offset = 2;
|
||||
if (certificate_offset < min_offset)
|
||||
{
|
||||
|
|
|
@ -61,6 +61,10 @@ void Host_PPCSymbolsChanged()
|
|||
{
|
||||
}
|
||||
|
||||
void Host_PPCBreakpointsChanged()
|
||||
{
|
||||
}
|
||||
|
||||
void Host_RefreshDSPDebuggerWindow()
|
||||
{
|
||||
}
|
||||
|
@ -86,7 +90,7 @@ void Host_UpdateDisasmDialog()
|
|||
{
|
||||
}
|
||||
|
||||
void Host_JitCacheCleared()
|
||||
void Host_JitCacheInvalidation()
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
#include "DolphinQt/Config/ControllerInterface/ControllerInterfaceWindow.h"
|
||||
#include "DolphinQt/Config/ToolTipControls/ToolTipCheckBox.h"
|
||||
#include "DolphinQt/QtUtils/ModalMessageBox.h"
|
||||
#include "DolphinQt/QtUtils/NonDefaultQPushButton.h"
|
||||
#include "DolphinQt/QtUtils/SignalBlocking.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
@ -36,7 +37,7 @@ AchievementSettingsWidget::AchievementSettingsWidget(QWidget* parent) : QWidget(
|
|||
|
||||
// If hardcore is enabled when the emulator starts, make sure it turns off what it needs to
|
||||
if (Config::Get(Config::RA_HARDCORE_ENABLED))
|
||||
ToggleHardcore();
|
||||
UpdateHardcoreMode();
|
||||
}
|
||||
|
||||
void AchievementSettingsWidget::UpdateData(int login_failed_code)
|
||||
|
@ -258,11 +259,7 @@ void AchievementSettingsWidget::ToggleRAIntegration()
|
|||
instance.Init();
|
||||
else
|
||||
instance.Shutdown();
|
||||
if (Config::Get(Config::RA_HARDCORE_ENABLED))
|
||||
{
|
||||
emit Settings::Instance().EmulationStateChanged(Core::GetState(Core::System::GetInstance()));
|
||||
emit Settings::Instance().HardcoreStateChanged();
|
||||
}
|
||||
UpdateHardcoreMode();
|
||||
}
|
||||
|
||||
void AchievementSettingsWidget::Login()
|
||||
|
@ -276,24 +273,31 @@ void AchievementSettingsWidget::Login()
|
|||
|
||||
void AchievementSettingsWidget::Logout()
|
||||
{
|
||||
AchievementManager::GetInstance().Logout();
|
||||
SaveSettings();
|
||||
auto confirm = ModalMessageBox::question(
|
||||
this, tr("Confirm Logout"), tr("Are you sure you want to log out of RetroAchievements?"),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::NoButton, Qt::ApplicationModal);
|
||||
if (confirm == QMessageBox::Yes)
|
||||
{
|
||||
AchievementManager::GetInstance().Logout();
|
||||
SaveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
void AchievementSettingsWidget::ToggleHardcore()
|
||||
{
|
||||
SaveSettings();
|
||||
AchievementManager::GetInstance().SetHardcoreMode();
|
||||
if (Config::Get(Config::RA_HARDCORE_ENABLED))
|
||||
{
|
||||
if (Config::Get(Config::MAIN_EMULATION_SPEED) < 1.0f)
|
||||
Config::SetBaseOrCurrent(Config::MAIN_EMULATION_SPEED, 1.0f);
|
||||
Config::SetBaseOrCurrent(Config::FREE_LOOK_ENABLED, false);
|
||||
Config::SetBaseOrCurrent(Config::MAIN_ENABLE_CHEATS, false);
|
||||
Settings::Instance().SetDebugModeEnabled(false);
|
||||
auto confirm = ModalMessageBox::question(
|
||||
this, tr("Confirm Hardcore Off"), tr("Are you sure you want to turn hardcore mode off?"),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::NoButton, Qt::ApplicationModal);
|
||||
if (confirm != QMessageBox::Yes)
|
||||
{
|
||||
SignalBlocking(m_common_hardcore_enabled_input)->setChecked(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
emit Settings::Instance().EmulationStateChanged(Core::GetState(Core::System::GetInstance()));
|
||||
emit Settings::Instance().HardcoreStateChanged();
|
||||
SaveSettings();
|
||||
UpdateHardcoreMode();
|
||||
}
|
||||
|
||||
void AchievementSettingsWidget::ToggleUnofficial()
|
||||
|
@ -323,4 +327,14 @@ void AchievementSettingsWidget::ToggleProgress()
|
|||
SaveSettings();
|
||||
}
|
||||
|
||||
void AchievementSettingsWidget::UpdateHardcoreMode()
|
||||
{
|
||||
if (Config::Get(Config::RA_HARDCORE_ENABLED))
|
||||
{
|
||||
Settings::Instance().SetDebugModeEnabled(false);
|
||||
}
|
||||
emit Settings::Instance().EmulationStateChanged(Core::GetState(Core::System::GetInstance()));
|
||||
emit Settings::Instance().HardcoreStateChanged();
|
||||
}
|
||||
|
||||
#endif // USE_RETRO_ACHIEVEMENTS
|
||||
|
|
|
@ -39,6 +39,8 @@ private:
|
|||
void ToggleDiscordPresence();
|
||||
void ToggleProgress();
|
||||
|
||||
void UpdateHardcoreMode();
|
||||
|
||||
QGroupBox* m_common_box;
|
||||
QVBoxLayout* m_common_layout;
|
||||
ToolTipCheckBox* m_common_integration_enabled_input;
|
||||
|
|
|
@ -52,6 +52,7 @@ add_executable(dolphin-mpn
|
|||
Config/ConfigControls/ConfigBool.h
|
||||
Config/ConfigControls/ConfigChoice.cpp
|
||||
Config/ConfigControls/ConfigChoice.h
|
||||
Config/ConfigControls/ConfigControl.h
|
||||
Config/ConfigControls/ConfigInteger.cpp
|
||||
Config/ConfigControls/ConfigInteger.h
|
||||
Config/ConfigControls/ConfigRadio.cpp
|
||||
|
@ -318,6 +319,8 @@ add_executable(dolphin-mpn
|
|||
QtUtils/ParallelProgressDialog.h
|
||||
QtUtils/PartiallyClosableTabWidget.cpp
|
||||
QtUtils/PartiallyClosableTabWidget.h
|
||||
QtUtils/QtUtils.cpp
|
||||
QtUtils/QtUtils.h
|
||||
QtUtils/SetWindowDecorations.cpp
|
||||
QtUtils/SetWindowDecorations.h
|
||||
QtUtils/SignalBlocking.h
|
||||
|
|
|
@ -115,7 +115,7 @@ void ARCodeWidget::OnItemChanged(QListWidgetItem* item)
|
|||
m_ar_codes[m_code_list->row(item)].enabled = (item->checkState() == Qt::Checked);
|
||||
|
||||
if (!m_restart_required)
|
||||
ActionReplay::ApplyCodes(m_ar_codes);
|
||||
ActionReplay::ApplyCodes(m_ar_codes, m_game_id);
|
||||
|
||||
UpdateList();
|
||||
SaveCodes();
|
||||
|
@ -140,20 +140,14 @@ void ARCodeWidget::SortAlphabetically()
|
|||
|
||||
void ARCodeWidget::SortEnabledCodesFirst()
|
||||
{
|
||||
std::stable_sort(m_ar_codes.begin(), m_ar_codes.end(), [](const auto& a, const auto& b) {
|
||||
return a.enabled && a.enabled != b.enabled;
|
||||
});
|
||||
|
||||
std::ranges::stable_partition(m_ar_codes, std::identity{}, &ActionReplay::ARCode::enabled);
|
||||
UpdateList();
|
||||
SaveCodes();
|
||||
}
|
||||
|
||||
void ARCodeWidget::SortDisabledCodesFirst()
|
||||
{
|
||||
std::stable_sort(m_ar_codes.begin(), m_ar_codes.end(), [](const auto& a, const auto& b) {
|
||||
return !a.enabled && a.enabled != b.enabled;
|
||||
});
|
||||
|
||||
std::ranges::stable_partition(m_ar_codes, std::logical_not{}, &ActionReplay::ARCode::enabled);
|
||||
UpdateList();
|
||||
SaveCodes();
|
||||
}
|
||||
|
|
|
@ -3,31 +3,28 @@
|
|||
|
||||
#include "DolphinQt/Config/ConfigControls/ConfigBool.h"
|
||||
|
||||
#include <QEvent>
|
||||
#include <QFont>
|
||||
#include <QSignalBlocker>
|
||||
|
||||
#include "Common/Config/Config.h"
|
||||
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
ConfigBool::ConfigBool(const QString& label, const Config::Info<bool>& setting, bool reverse)
|
||||
: ToolTipCheckBox(label), m_setting(setting), m_reverse(reverse)
|
||||
: ConfigBool(label, setting, nullptr, reverse)
|
||||
{
|
||||
}
|
||||
|
||||
ConfigBool::ConfigBool(const QString& label, const Config::Info<bool>& setting,
|
||||
Config::Layer* layer, bool reverse)
|
||||
: ConfigControl(label, setting.GetLocation(), layer), m_setting(setting), m_reverse(reverse)
|
||||
{
|
||||
setChecked(ReadValue(setting) ^ reverse);
|
||||
|
||||
connect(this, &QCheckBox::toggled, this, &ConfigBool::Update);
|
||||
setChecked(Config::Get(m_setting) ^ reverse);
|
||||
|
||||
connect(&Settings::Instance(), &Settings::ConfigChanged, this, [this] {
|
||||
QFont bf = font();
|
||||
bf.setBold(Config::GetActiveLayerForConfig(m_setting) != Config::LayerType::Base);
|
||||
setFont(bf);
|
||||
|
||||
const QSignalBlocker blocker(this);
|
||||
setChecked(Config::Get(m_setting) ^ m_reverse);
|
||||
});
|
||||
}
|
||||
|
||||
void ConfigBool::Update()
|
||||
{
|
||||
Config::SetBaseOrCurrent(m_setting, static_cast<bool>(isChecked() ^ m_reverse));
|
||||
const bool value = static_cast<bool>(isChecked() ^ m_reverse);
|
||||
|
||||
SaveValue(m_setting, value);
|
||||
}
|
||||
|
||||
void ConfigBool::OnConfigChanged()
|
||||
{
|
||||
setChecked(ReadValue(m_setting) ^ m_reverse);
|
||||
}
|
||||
|
|
|
@ -3,23 +3,25 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "DolphinQt/Config/ConfigControls/ConfigControl.h"
|
||||
#include "DolphinQt/Config/ToolTipControls/ToolTipCheckBox.h"
|
||||
|
||||
namespace Config
|
||||
{
|
||||
template <typename T>
|
||||
class Info;
|
||||
}
|
||||
#include "Common/Config/ConfigInfo.h"
|
||||
|
||||
class ConfigBool : public ToolTipCheckBox
|
||||
class ConfigBool final : public ConfigControl<ToolTipCheckBox>
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ConfigBool(const QString& label, const Config::Info<bool>& setting, bool reverse = false);
|
||||
ConfigBool(const QString& label, const Config::Info<bool>& setting, Config::Layer* layer,
|
||||
bool reverse = false);
|
||||
|
||||
protected:
|
||||
void OnConfigChanged() override;
|
||||
|
||||
private:
|
||||
void Update();
|
||||
|
||||
const Config::Info<bool>& m_setting;
|
||||
const Config::Info<bool> m_setting;
|
||||
bool m_reverse;
|
||||
};
|
||||
|
|
|
@ -5,85 +5,168 @@
|
|||
|
||||
#include <QSignalBlocker>
|
||||
|
||||
#include "Common/Config/Config.h"
|
||||
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
ConfigChoice::ConfigChoice(const QStringList& options, const Config::Info<int>& setting)
|
||||
: m_setting(setting)
|
||||
ConfigChoice::ConfigChoice(const QStringList& options, const Config::Info<int>& setting,
|
||||
Config::Layer* layer)
|
||||
: ConfigControl(setting.GetLocation(), layer), m_setting(setting)
|
||||
{
|
||||
addItems(options);
|
||||
setCurrentIndex(ReadValue(setting));
|
||||
|
||||
connect(this, &QComboBox::currentIndexChanged, this, &ConfigChoice::Update);
|
||||
setCurrentIndex(Config::Get(m_setting));
|
||||
|
||||
connect(&Settings::Instance(), &Settings::ConfigChanged, this, [this] {
|
||||
QFont bf = font();
|
||||
bf.setBold(Config::GetActiveLayerForConfig(m_setting) != Config::LayerType::Base);
|
||||
setFont(bf);
|
||||
|
||||
const QSignalBlocker blocker(this);
|
||||
setCurrentIndex(Config::Get(m_setting));
|
||||
});
|
||||
}
|
||||
|
||||
void ConfigChoice::Update(int choice)
|
||||
{
|
||||
Config::SetBaseOrCurrent(m_setting, choice);
|
||||
SaveValue(m_setting, choice);
|
||||
}
|
||||
|
||||
void ConfigChoice::OnConfigChanged()
|
||||
{
|
||||
setCurrentIndex(ReadValue(m_setting));
|
||||
}
|
||||
|
||||
ConfigStringChoice::ConfigStringChoice(const std::vector<std::string>& options,
|
||||
const Config::Info<std::string>& setting)
|
||||
: m_setting(setting), m_text_is_data(true)
|
||||
const Config::Info<std::string>& setting,
|
||||
Config::Layer* layer)
|
||||
: ConfigControl(setting.GetLocation(), layer), m_setting(setting), m_text_is_data(true)
|
||||
{
|
||||
for (const auto& op : options)
|
||||
addItem(QString::fromStdString(op));
|
||||
|
||||
Connect();
|
||||
Load();
|
||||
connect(this, &QComboBox::currentIndexChanged, this, &ConfigStringChoice::Update);
|
||||
}
|
||||
|
||||
ConfigStringChoice::ConfigStringChoice(const std::vector<std::pair<QString, QString>>& options,
|
||||
const Config::Info<std::string>& setting)
|
||||
: m_setting(setting), m_text_is_data(false)
|
||||
const Config::Info<std::string>& setting,
|
||||
Config::Layer* layer)
|
||||
: ConfigControl(setting.GetLocation(), layer), m_setting(setting), m_text_is_data(false)
|
||||
{
|
||||
for (const auto& [option_text, option_data] : options)
|
||||
addItem(option_text, option_data);
|
||||
|
||||
Connect();
|
||||
Load();
|
||||
}
|
||||
|
||||
void ConfigStringChoice::Connect()
|
||||
{
|
||||
const auto on_config_changed = [this]() {
|
||||
QFont bf = font();
|
||||
bf.setBold(Config::GetActiveLayerForConfig(m_setting) != Config::LayerType::Base);
|
||||
setFont(bf);
|
||||
|
||||
Load();
|
||||
};
|
||||
|
||||
connect(&Settings::Instance(), &Settings::ConfigChanged, this, on_config_changed);
|
||||
connect(this, &QComboBox::currentIndexChanged, this, &ConfigStringChoice::Update);
|
||||
Load();
|
||||
}
|
||||
|
||||
void ConfigStringChoice::Update(int index)
|
||||
{
|
||||
if (m_text_is_data)
|
||||
{
|
||||
Config::SetBaseOrCurrent(m_setting, itemText(index).toStdString());
|
||||
}
|
||||
SaveValue(m_setting, itemText(index).toStdString());
|
||||
else
|
||||
{
|
||||
Config::SetBaseOrCurrent(m_setting, itemData(index).toString().toStdString());
|
||||
}
|
||||
SaveValue(m_setting, itemData(index).toString().toStdString());
|
||||
}
|
||||
|
||||
void ConfigStringChoice::Load()
|
||||
{
|
||||
const QString setting_value = QString::fromStdString(Config::Get(m_setting));
|
||||
|
||||
const QString setting_value = QString::fromStdString(ReadValue(m_setting));
|
||||
const int index = m_text_is_data ? findText(setting_value) : findData(setting_value);
|
||||
|
||||
// This can be called publicly.
|
||||
const QSignalBlocker block(this);
|
||||
setCurrentIndex(index);
|
||||
}
|
||||
|
||||
void ConfigStringChoice::OnConfigChanged()
|
||||
{
|
||||
Load();
|
||||
}
|
||||
|
||||
ConfigComplexChoice::ConfigComplexChoice(const InfoVariant setting1, const InfoVariant setting2,
|
||||
Config::Layer* layer)
|
||||
: m_setting1(setting1), m_setting2(setting2), m_layer(layer)
|
||||
{
|
||||
connect(&Settings::Instance(), &Settings::ConfigChanged, this, &ConfigComplexChoice::Refresh);
|
||||
connect(this, &QComboBox::currentIndexChanged, this, &ConfigComplexChoice::SaveValue);
|
||||
}
|
||||
|
||||
void ConfigComplexChoice::Refresh()
|
||||
{
|
||||
auto& location = GetLocation();
|
||||
|
||||
QFont bf = font();
|
||||
if (m_layer != nullptr)
|
||||
{
|
||||
bf.setBold(m_layer->Exists(location.first) || m_layer->Exists(location.second));
|
||||
}
|
||||
else
|
||||
{
|
||||
bf.setBold(Config::GetActiveLayerForConfig(location.first) != Config::LayerType::Base ||
|
||||
Config::GetActiveLayerForConfig(location.second) != Config::LayerType::Base);
|
||||
}
|
||||
|
||||
setFont(bf);
|
||||
UpdateComboIndex();
|
||||
}
|
||||
|
||||
void ConfigComplexChoice::Add(const QString& name, const OptionVariant option1,
|
||||
const OptionVariant option2)
|
||||
{
|
||||
const QSignalBlocker blocker(this);
|
||||
addItem(name);
|
||||
m_options.push_back(std::make_pair(option1, option2));
|
||||
}
|
||||
|
||||
void ConfigComplexChoice::Reset()
|
||||
{
|
||||
clear();
|
||||
m_options.clear();
|
||||
}
|
||||
|
||||
void ConfigComplexChoice::SaveValue(int choice)
|
||||
{
|
||||
auto Set = [this, choice](auto& setting, auto& value) {
|
||||
if (m_layer != nullptr)
|
||||
{
|
||||
m_layer->Set(setting.GetLocation(), value);
|
||||
Config::OnConfigChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
Config::SetBaseOrCurrent(setting, value);
|
||||
};
|
||||
|
||||
std::visit(Set, m_setting1, m_options[choice].first);
|
||||
std::visit(Set, m_setting2, m_options[choice].second);
|
||||
}
|
||||
|
||||
void ConfigComplexChoice::UpdateComboIndex()
|
||||
{
|
||||
auto Get = [this](auto& setting) {
|
||||
if (m_layer != nullptr)
|
||||
return static_cast<OptionVariant>(m_layer->Get(setting));
|
||||
|
||||
return static_cast<OptionVariant>(Config::Get(setting));
|
||||
};
|
||||
|
||||
std::pair<OptionVariant, OptionVariant> values =
|
||||
std::make_pair(std::visit(Get, m_setting1), std::visit(Get, m_setting2));
|
||||
|
||||
auto it = std::find(m_options.begin(), m_options.end(), values);
|
||||
int index = static_cast<int>(std::distance(m_options.begin(), it));
|
||||
|
||||
const QSignalBlocker blocker(this);
|
||||
setCurrentIndex(index);
|
||||
}
|
||||
|
||||
const std::pair<Config::Location, Config::Location> ConfigComplexChoice::GetLocation() const
|
||||
{
|
||||
auto visit = [](auto& v) { return v.GetLocation(); };
|
||||
|
||||
return {std::visit(visit, m_setting1), std::visit(visit, m_setting2)};
|
||||
}
|
||||
|
||||
void ConfigComplexChoice::mousePressEvent(QMouseEvent* event)
|
||||
{
|
||||
if (event->button() == Qt::RightButton && m_layer != nullptr)
|
||||
{
|
||||
auto& location = GetLocation();
|
||||
m_layer->DeleteKey(location.first);
|
||||
m_layer->DeleteKey(location.second);
|
||||
Config::OnConfigChanged();
|
||||
}
|
||||
else
|
||||
{
|
||||
QComboBox::mousePressEvent(event);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -7,15 +7,20 @@
|
|||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "DolphinQt/Config/ConfigControls/ConfigControl.h"
|
||||
#include "DolphinQt/Config/ToolTipControls/ToolTipComboBox.h"
|
||||
|
||||
#include "Common/Config/Config.h"
|
||||
#include "Common/Config/ConfigInfo.h"
|
||||
|
||||
class ConfigChoice : public ToolTipComboBox
|
||||
class ConfigChoice final : public ConfigControl<ToolTipComboBox>
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ConfigChoice(const QStringList& options, const Config::Info<int>& setting);
|
||||
ConfigChoice(const QStringList& options, const Config::Info<int>& setting,
|
||||
Config::Layer* layer = nullptr);
|
||||
|
||||
protected:
|
||||
void OnConfigChanged() override;
|
||||
|
||||
private:
|
||||
void Update(int choice);
|
||||
|
@ -23,20 +28,49 @@ private:
|
|||
Config::Info<int> m_setting;
|
||||
};
|
||||
|
||||
class ConfigStringChoice : public ToolTipComboBox
|
||||
class ConfigStringChoice final : public ConfigControl<ToolTipComboBox>
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ConfigStringChoice(const std::vector<std::string>& options,
|
||||
const Config::Info<std::string>& setting);
|
||||
const Config::Info<std::string>& setting, Config::Layer* layer = nullptr);
|
||||
ConfigStringChoice(const std::vector<std::pair<QString, QString>>& options,
|
||||
const Config::Info<std::string>& setting);
|
||||
|
||||
private:
|
||||
void Connect();
|
||||
void Update(int index);
|
||||
const Config::Info<std::string>& setting, Config::Layer* layer = nullptr);
|
||||
void Load();
|
||||
|
||||
Config::Info<std::string> m_setting;
|
||||
protected:
|
||||
void OnConfigChanged() override;
|
||||
|
||||
private:
|
||||
void Update(int index);
|
||||
|
||||
const Config::Info<std::string> m_setting;
|
||||
bool m_text_is_data = false;
|
||||
};
|
||||
|
||||
class ConfigComplexChoice final : public ToolTipComboBox
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
using InfoVariant = std::variant<Config::Info<u32>, Config::Info<int>, Config::Info<bool>>;
|
||||
using OptionVariant = std::variant<u32, int, bool>;
|
||||
|
||||
public:
|
||||
ConfigComplexChoice(const InfoVariant setting1, const InfoVariant setting2,
|
||||
Config::Layer* layer = nullptr);
|
||||
|
||||
void Add(const QString& name, const OptionVariant option1, const OptionVariant option2);
|
||||
void Refresh();
|
||||
void Reset();
|
||||
const std::pair<Config::Location, Config::Location> GetLocation() const;
|
||||
|
||||
private:
|
||||
void SaveValue(int choice);
|
||||
void UpdateComboIndex();
|
||||
void mousePressEvent(QMouseEvent* event) override;
|
||||
|
||||
Config::Layer* m_layer;
|
||||
const InfoVariant m_setting1;
|
||||
const InfoVariant m_setting2;
|
||||
std::vector<std::pair<OptionVariant, OptionVariant>> m_options;
|
||||
};
|
||||
|
|
105
Source/Core/DolphinQt/Config/ConfigControls/ConfigControl.h
Normal file
105
Source/Core/DolphinQt/Config/ConfigControls/ConfigControl.h
Normal file
|
@ -0,0 +1,105 @@
|
|||
// Copyright 2024 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QFont>
|
||||
#include <QMouseEvent>
|
||||
#include <QSignalBlocker>
|
||||
|
||||
#include "Common/Config/Enums.h"
|
||||
#include "Common/Config/Layer.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
namespace Config
|
||||
{
|
||||
template <typename T>
|
||||
class Info;
|
||||
struct Location;
|
||||
} // namespace Config
|
||||
|
||||
template <class Derived>
|
||||
class ConfigControl : public Derived
|
||||
{
|
||||
public:
|
||||
ConfigControl(const Config::Location& location, Config::Layer* layer)
|
||||
: m_location(location), m_layer(layer)
|
||||
{
|
||||
ConnectConfig();
|
||||
}
|
||||
ConfigControl(const QString& label, const Config::Location& location, Config::Layer* layer)
|
||||
: Derived(label), m_location(location), m_layer(layer)
|
||||
{
|
||||
ConnectConfig();
|
||||
}
|
||||
ConfigControl(const Qt::Orientation& orient, const Config::Location& location,
|
||||
Config::Layer* layer)
|
||||
: Derived(orient), m_location(location), m_layer(layer)
|
||||
{
|
||||
ConnectConfig();
|
||||
}
|
||||
|
||||
const Config::Location GetLocation() const { return m_location; }
|
||||
|
||||
protected:
|
||||
void ConnectConfig()
|
||||
{
|
||||
Derived::connect(&Settings::Instance(), &Settings::ConfigChanged, this, [this] {
|
||||
QFont bf = Derived::font();
|
||||
bf.setBold(IsConfigLocal());
|
||||
Derived::setFont(bf);
|
||||
|
||||
const QSignalBlocker blocker(this);
|
||||
OnConfigChanged();
|
||||
});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void SaveValue(const Config::Info<T>& setting, const T& value)
|
||||
{
|
||||
if (m_layer != nullptr)
|
||||
{
|
||||
m_layer->Set(m_location, value);
|
||||
Config::OnConfigChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
Config::SetBaseOrCurrent(setting, value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const T ReadValue(const Config::Info<T>& setting) const
|
||||
{
|
||||
if (m_layer != nullptr)
|
||||
return m_layer->Get(setting);
|
||||
|
||||
return Config::Get(setting);
|
||||
}
|
||||
|
||||
virtual void OnConfigChanged(){};
|
||||
|
||||
private:
|
||||
bool IsConfigLocal() const
|
||||
{
|
||||
if (m_layer != nullptr)
|
||||
return m_layer->Exists(m_location);
|
||||
else
|
||||
return Config::GetActiveLayerForConfig(m_location) != Config::LayerType::Base;
|
||||
}
|
||||
|
||||
void mousePressEvent(QMouseEvent* event) override
|
||||
{
|
||||
if (m_layer != nullptr && event->button() == Qt::RightButton)
|
||||
{
|
||||
m_layer->DeleteKey(m_location);
|
||||
Config::OnConfigChanged();
|
||||
}
|
||||
else
|
||||
{
|
||||
Derived::mousePressEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
const Config::Location m_location;
|
||||
Config::Layer* m_layer;
|
||||
};
|
|
@ -3,20 +3,16 @@
|
|||
|
||||
#include "DolphinQt/Config/ConfigControls/ConfigFloatSlider.h"
|
||||
|
||||
#include <QSignalBlocker>
|
||||
|
||||
#include "Common/Config/Config.h"
|
||||
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
ConfigFloatSlider::ConfigFloatSlider(float minimum, float maximum,
|
||||
const Config::Info<float>& setting, float step)
|
||||
: ToolTipSlider(Qt::Horizontal), m_minimum(minimum), m_step(step), m_setting(setting)
|
||||
const Config::Info<float>& setting, float step,
|
||||
Config::Layer* layer)
|
||||
: ConfigControl(Qt::Horizontal, setting.GetLocation(), layer), m_minimum(minimum), m_step(step),
|
||||
m_setting(setting)
|
||||
{
|
||||
const float range = maximum - minimum;
|
||||
const int steps = std::round(range / step);
|
||||
const int interval = std::round(range / steps);
|
||||
const int current_value = std::round((Config::Get(m_setting) - minimum) / step);
|
||||
const int current_value = std::round((ReadValue(setting) - minimum) / step);
|
||||
|
||||
setMinimum(0);
|
||||
setMaximum(steps);
|
||||
|
@ -24,25 +20,21 @@ ConfigFloatSlider::ConfigFloatSlider(float minimum, float maximum,
|
|||
setValue(current_value);
|
||||
|
||||
connect(this, &ConfigFloatSlider::valueChanged, this, &ConfigFloatSlider::Update);
|
||||
|
||||
connect(&Settings::Instance(), &Settings::ConfigChanged, this, [this] {
|
||||
QFont bf = font();
|
||||
bf.setBold(Config::GetActiveLayerForConfig(m_setting) != Config::LayerType::Base);
|
||||
setFont(bf);
|
||||
|
||||
const QSignalBlocker blocker(this);
|
||||
const int value = std::round((Config::Get(m_setting) - m_minimum) / m_step);
|
||||
setValue(value);
|
||||
});
|
||||
}
|
||||
|
||||
void ConfigFloatSlider::Update(int value)
|
||||
{
|
||||
const float current_value = (m_step * value) + m_minimum;
|
||||
Config::SetBaseOrCurrent(m_setting, current_value);
|
||||
|
||||
SaveValue(m_setting, current_value);
|
||||
}
|
||||
|
||||
float ConfigFloatSlider::GetValue() const
|
||||
{
|
||||
return (m_step * value()) + m_minimum;
|
||||
}
|
||||
|
||||
void ConfigFloatSlider::OnConfigChanged()
|
||||
{
|
||||
setValue(std::round((ReadValue(m_setting) - m_minimum) / m_step));
|
||||
}
|
||||
|
|
|
@ -3,28 +3,29 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "DolphinQt/Config/ConfigControls/ConfigControl.h"
|
||||
#include "DolphinQt/Config/ToolTipControls/ToolTipSlider.h"
|
||||
|
||||
namespace Config
|
||||
{
|
||||
template <typename T>
|
||||
class Info;
|
||||
}
|
||||
#include "Common/Config/ConfigInfo.h"
|
||||
|
||||
// Automatically converts an int slider into a float one.
|
||||
// Do not read the int values or ranges directly from it.
|
||||
class ConfigFloatSlider : public ToolTipSlider
|
||||
class ConfigFloatSlider final : public ConfigControl<ToolTipSlider>
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ConfigFloatSlider(float minimum, float maximum, const Config::Info<float>& setting, float step);
|
||||
ConfigFloatSlider(float minimum, float maximum, const Config::Info<float>& setting, float step,
|
||||
Config::Layer* layer = nullptr);
|
||||
void Update(int value);
|
||||
|
||||
// Returns the adjusted float value
|
||||
float GetValue() const;
|
||||
|
||||
protected:
|
||||
void OnConfigChanged() override;
|
||||
|
||||
private:
|
||||
float m_minimum;
|
||||
float m_step;
|
||||
const Config::Info<float>& m_setting;
|
||||
const Config::Info<float> m_setting;
|
||||
};
|
||||
|
|
|
@ -3,33 +3,39 @@
|
|||
|
||||
#include "DolphinQt/Config/ConfigControls/ConfigInteger.h"
|
||||
|
||||
#include <QSignalBlocker>
|
||||
|
||||
#include "Common/Config/Config.h"
|
||||
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
ConfigInteger::ConfigInteger(int minimum, int maximum, const Config::Info<int>& setting, int step)
|
||||
: ToolTipSpinBox(), m_setting(setting)
|
||||
: ConfigInteger(minimum, maximum, setting, nullptr, step)
|
||||
{
|
||||
}
|
||||
|
||||
ConfigInteger::ConfigInteger(int minimum, int maximum, const Config::Info<int>& setting,
|
||||
Config::Layer* layer, int step)
|
||||
: ConfigControl(setting.GetLocation(), layer), m_setting(setting)
|
||||
{
|
||||
setMinimum(minimum);
|
||||
setMaximum(maximum);
|
||||
setSingleStep(step);
|
||||
|
||||
setValue(Config::Get(setting));
|
||||
setValue(ReadValue(setting));
|
||||
|
||||
connect(this, &ConfigInteger::valueChanged, this, &ConfigInteger::Update);
|
||||
connect(&Settings::Instance(), &Settings::ConfigChanged, this, [this] {
|
||||
QFont bf = font();
|
||||
bf.setBold(Config::GetActiveLayerForConfig(m_setting) != Config::LayerType::Base);
|
||||
setFont(bf);
|
||||
|
||||
const QSignalBlocker blocker(this);
|
||||
setValue(Config::Get(m_setting));
|
||||
});
|
||||
}
|
||||
|
||||
void ConfigInteger::Update(int value)
|
||||
{
|
||||
Config::SetBaseOrCurrent(m_setting, value);
|
||||
SaveValue(m_setting, value);
|
||||
}
|
||||
|
||||
void ConfigInteger::OnConfigChanged()
|
||||
{
|
||||
setValue(ReadValue(m_setting));
|
||||
}
|
||||
|
||||
ConfigIntegerLabel::ConfigIntegerLabel(const QString& text, ConfigInteger* widget)
|
||||
: QLabel(text), m_widget(QPointer<ConfigInteger>(widget))
|
||||
{
|
||||
connect(&Settings::Instance(), &Settings::ConfigChanged, this, [this]() {
|
||||
// Label shares font changes with ConfigInteger to mark game ini settings.
|
||||
if (m_widget)
|
||||
setFont(m_widget->font());
|
||||
});
|
||||
}
|
||||
|
|
|
@ -3,21 +3,38 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <QLabel>
|
||||
#include <QPointer>
|
||||
|
||||
#include "DolphinQt/Config/ConfigControls/ConfigControl.h"
|
||||
#include "DolphinQt/Config/ToolTipControls/ToolTipSpinBox.h"
|
||||
|
||||
namespace Config
|
||||
{
|
||||
template <typename T>
|
||||
class Info;
|
||||
}
|
||||
#include "Common/Config/ConfigInfo.h"
|
||||
|
||||
class ConfigInteger : public ToolTipSpinBox
|
||||
class ConfigInteger final : public ConfigControl<ToolTipSpinBox>
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ConfigInteger(int minimum, int maximum, const Config::Info<int>& setting, int step = 1);
|
||||
ConfigInteger(int minimum, int maximum, const Config::Info<int>& setting, Config::Layer* layer,
|
||||
int step = 1);
|
||||
|
||||
void Update(int value);
|
||||
|
||||
protected:
|
||||
void OnConfigChanged() override;
|
||||
|
||||
private:
|
||||
const Config::Info<int>& m_setting;
|
||||
const Config::Info<int> m_setting;
|
||||
};
|
||||
|
||||
class ConfigIntegerLabel final : public QLabel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ConfigIntegerLabel(const QString& text, ConfigInteger* widget);
|
||||
|
||||
private:
|
||||
QPointer<ConfigInteger> m_widget;
|
||||
};
|
||||
|
|
|
@ -3,33 +3,21 @@
|
|||
|
||||
#include "DolphinQt/Config/ConfigControls/ConfigRadio.h"
|
||||
|
||||
#include <QSignalBlocker>
|
||||
|
||||
#include "Common/Config/Config.h"
|
||||
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
ConfigRadioInt::ConfigRadioInt(const QString& label, const Config::Info<int>& setting, int value)
|
||||
: ToolTipRadioButton(label), m_setting(setting), m_value(value)
|
||||
ConfigRadioInt::ConfigRadioInt(const QString& label, const Config::Info<int>& setting, int value,
|
||||
Config::Layer* layer)
|
||||
: ConfigControl(label, setting.GetLocation(), layer), m_setting(setting), m_value(value)
|
||||
{
|
||||
setChecked(Config::Get(m_setting) == m_value);
|
||||
setChecked(ReadValue(setting) == value);
|
||||
|
||||
connect(this, &QRadioButton::toggled, this, &ConfigRadioInt::Update);
|
||||
|
||||
connect(&Settings::Instance(), &Settings::ConfigChanged, this, [this] {
|
||||
QFont bf = font();
|
||||
bf.setBold(Config::GetActiveLayerForConfig(m_setting) != Config::LayerType::Base);
|
||||
setFont(bf);
|
||||
|
||||
const QSignalBlocker blocker(this);
|
||||
setChecked(Config::Get(m_setting) == m_value);
|
||||
});
|
||||
}
|
||||
|
||||
void ConfigRadioInt::Update()
|
||||
{
|
||||
if (isChecked())
|
||||
{
|
||||
Config::SetBaseOrCurrent(m_setting, m_value);
|
||||
SaveValue(m_setting, m_value);
|
||||
|
||||
emit OnSelected(m_value);
|
||||
}
|
||||
else
|
||||
|
@ -37,3 +25,8 @@ void ConfigRadioInt::Update()
|
|||
emit OnDeselected(m_value);
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigRadioInt::OnConfigChanged()
|
||||
{
|
||||
setChecked(ReadValue(m_setting) == m_value);
|
||||
}
|
||||
|
|
|
@ -3,15 +3,17 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "DolphinQt/Config/ConfigControls/ConfigControl.h"
|
||||
#include "DolphinQt/Config/ToolTipControls/ToolTipRadioButton.h"
|
||||
|
||||
#include "Common/Config/Config.h"
|
||||
#include "Common/Config/ConfigInfo.h"
|
||||
|
||||
class ConfigRadioInt : public ToolTipRadioButton
|
||||
class ConfigRadioInt final : public ConfigControl<ToolTipRadioButton>
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ConfigRadioInt(const QString& label, const Config::Info<int>& setting, int value);
|
||||
ConfigRadioInt(const QString& label, const Config::Info<int>& setting, int value,
|
||||
Config::Layer* layer = nullptr);
|
||||
|
||||
signals:
|
||||
// Since selecting a new radio button deselects the old one, ::toggled will generate two signals.
|
||||
|
@ -19,9 +21,12 @@ signals:
|
|||
void OnSelected(int new_value);
|
||||
void OnDeselected(int old_value);
|
||||
|
||||
protected:
|
||||
void OnConfigChanged() override;
|
||||
|
||||
private:
|
||||
void Update();
|
||||
|
||||
Config::Info<int> m_setting;
|
||||
const Config::Info<int> m_setting;
|
||||
int m_value;
|
||||
};
|
||||
|
|
|
@ -1,36 +1,45 @@
|
|||
// Copyright 2017 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <QFont>
|
||||
|
||||
#include "DolphinQt/Config/ConfigControls/ConfigSlider.h"
|
||||
|
||||
#include <QSignalBlocker>
|
||||
|
||||
#include "Common/Config/Config.h"
|
||||
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
ConfigSlider::ConfigSlider(int minimum, int maximum, const Config::Info<int>& setting, int tick)
|
||||
: ToolTipSlider(Qt::Horizontal), m_setting(setting)
|
||||
: ConfigSlider(minimum, maximum, setting, nullptr, tick)
|
||||
{
|
||||
}
|
||||
|
||||
ConfigSlider::ConfigSlider(int minimum, int maximum, const Config::Info<int>& setting,
|
||||
Config::Layer* layer, int tick)
|
||||
: ConfigControl(Qt::Horizontal, setting.GetLocation(), layer), m_setting(setting)
|
||||
|
||||
{
|
||||
setMinimum(minimum);
|
||||
setMaximum(maximum);
|
||||
setTickInterval(tick);
|
||||
|
||||
setValue(Config::Get(setting));
|
||||
setValue(ReadValue(setting));
|
||||
|
||||
connect(this, &ConfigSlider::valueChanged, this, &ConfigSlider::Update);
|
||||
|
||||
connect(&Settings::Instance(), &Settings::ConfigChanged, this, [this] {
|
||||
QFont bf = font();
|
||||
bf.setBold(Config::GetActiveLayerForConfig(m_setting) != Config::LayerType::Base);
|
||||
setFont(bf);
|
||||
|
||||
const QSignalBlocker blocker(this);
|
||||
setValue(Config::Get(m_setting));
|
||||
});
|
||||
}
|
||||
|
||||
void ConfigSlider::Update(int value)
|
||||
{
|
||||
Config::SetBaseOrCurrent(m_setting, value);
|
||||
SaveValue(m_setting, value);
|
||||
}
|
||||
|
||||
void ConfigSlider::OnConfigChanged()
|
||||
{
|
||||
setValue(ReadValue(m_setting));
|
||||
}
|
||||
|
||||
ConfigSliderLabel::ConfigSliderLabel(const QString& text, ConfigSlider* slider)
|
||||
: QLabel(text), m_slider(QPointer<ConfigSlider>(slider))
|
||||
{
|
||||
connect(&Settings::Instance(), &Settings::ConfigChanged, this, [this]() {
|
||||
// Label shares font changes with slider to mark game ini settings.
|
||||
if (m_slider)
|
||||
setFont(m_slider->font());
|
||||
});
|
||||
}
|
||||
|
|
|
@ -3,21 +3,38 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <QLabel>
|
||||
#include <QPointer>
|
||||
|
||||
#include "DolphinQt/Config/ConfigControls/ConfigControl.h"
|
||||
#include "DolphinQt/Config/ToolTipControls/ToolTipSlider.h"
|
||||
|
||||
namespace Config
|
||||
{
|
||||
template <typename T>
|
||||
class Info;
|
||||
}
|
||||
#include "Common/Config/ConfigInfo.h"
|
||||
|
||||
class ConfigSlider : public ToolTipSlider
|
||||
class ConfigSlider final : public ConfigControl<ToolTipSlider>
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ConfigSlider(int minimum, int maximum, const Config::Info<int>& setting, int tick = 0);
|
||||
ConfigSlider(int minimum, int maximum, const Config::Info<int>& setting, Config::Layer* layer,
|
||||
int tick = 0);
|
||||
|
||||
void Update(int value);
|
||||
|
||||
protected:
|
||||
void OnConfigChanged() override;
|
||||
|
||||
private:
|
||||
const Config::Info<int>& m_setting;
|
||||
const Config::Info<int> m_setting;
|
||||
};
|
||||
|
||||
class ConfigSliderLabel final : public QLabel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ConfigSliderLabel(const QString& text, ConfigSlider* slider);
|
||||
|
||||
private:
|
||||
QPointer<ConfigSlider> m_slider;
|
||||
};
|
||||
|
|
|
@ -3,37 +3,39 @@
|
|||
|
||||
#include "DolphinQt/Config/GameConfigWidget.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QFont>
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QIcon>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QSlider>
|
||||
#include <QSpinBox>
|
||||
#include <QTabWidget>
|
||||
#include <QTimer>
|
||||
#include <QToolTip>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Common/CommonPaths.h"
|
||||
#include "Common/Config/Config.h"
|
||||
#include "Common/Config/Layer.h"
|
||||
#include "Common/FileUtil.h"
|
||||
|
||||
#include "Core/Config/GraphicsSettings.h"
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/ConfigLoaders/GameConfigLoader.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
|
||||
#include "DolphinQt/Config/ConfigControls/ConfigBool.h"
|
||||
#include "DolphinQt/Config/ConfigControls/ConfigChoice.h"
|
||||
#include "DolphinQt/Config/ConfigControls/ConfigInteger.h"
|
||||
#include "DolphinQt/Config/ConfigControls/ConfigRadio.h"
|
||||
#include "DolphinQt/Config/ConfigControls/ConfigSlider.h"
|
||||
#include "DolphinQt/Config/GameConfigEdit.h"
|
||||
#include "DolphinQt/Config/Graphics/AdvancedWidget.h"
|
||||
#include "DolphinQt/Config/Graphics/EnhancementsWidget.h"
|
||||
#include "DolphinQt/Config/Graphics/GeneralWidget.h"
|
||||
#include "DolphinQt/Config/Graphics/HacksWidget.h"
|
||||
#include "DolphinQt/QtUtils/WrapInScrollArea.h"
|
||||
|
||||
#include "UICommon/GameFile.h"
|
||||
|
||||
constexpr int DETERMINISM_NOT_SET_INDEX = 0;
|
||||
constexpr int DETERMINISM_AUTO_INDEX = 1;
|
||||
constexpr int DETERMINISM_NONE_INDEX = 2;
|
||||
constexpr int DETERMINISM_FAKE_COMPLETION_INDEX = 3;
|
||||
|
||||
constexpr const char* DETERMINISM_NOT_SET_STRING = "";
|
||||
constexpr const char* DETERMINISM_AUTO_STRING = "auto";
|
||||
constexpr const char* DETERMINISM_NONE_STRING = "none";
|
||||
constexpr const char* DETERMINISM_FAKE_COMPLETION_STRING = "fake-completion";
|
||||
|
||||
static void PopulateTab(QTabWidget* tab, const std::string& path, std::string& game_id,
|
||||
u16 revision, bool read_only)
|
||||
{
|
||||
|
@ -55,46 +57,62 @@ GameConfigWidget::GameConfigWidget(const UICommon::GameFile& game) : m_game(game
|
|||
m_gameini_local_path =
|
||||
QString::fromStdString(File::GetUserPath(D_GAMESETTINGS_IDX) + m_game_id + ".ini");
|
||||
|
||||
m_layer = std::make_unique<Config::Layer>(
|
||||
ConfigLoaders::GenerateLocalGameConfigLoader(m_game_id, m_game.GetRevision()));
|
||||
m_global_layer = std::make_unique<Config::Layer>(
|
||||
ConfigLoaders::GenerateGlobalGameConfigLoader(m_game_id, m_game.GetRevision()));
|
||||
|
||||
CreateWidgets();
|
||||
LoadSettings();
|
||||
ConnectWidgets();
|
||||
connect(&Settings::Instance(), &Settings::ConfigChanged, this, &GameConfigWidget::LoadSettings);
|
||||
|
||||
PopulateTab(m_default_tab, File::GetSysDirectory() + GAMESETTINGS_DIR DIR_SEP, m_game_id,
|
||||
m_game.GetRevision(), true);
|
||||
PopulateTab(m_local_tab, File::GetUserPath(D_GAMESETTINGS_IDX), m_game_id, m_game.GetRevision(),
|
||||
false);
|
||||
|
||||
// Always give the user the opportunity to create a new INI
|
||||
if (m_local_tab->count() == 0)
|
||||
bool game_id_tab = false;
|
||||
for (int i = 0; i < m_local_tab->count(); i++)
|
||||
{
|
||||
if (m_local_tab->tabText(i).toStdString() == m_game_id + ".ini")
|
||||
game_id_tab = true;
|
||||
}
|
||||
|
||||
if (game_id_tab == false)
|
||||
{
|
||||
// Create new local game ini tab if none exists.
|
||||
auto* edit = new GameConfigEdit(
|
||||
nullptr, QString::fromStdString(File::GetUserPath(D_GAMESETTINGS_IDX) + m_game_id + ".ini"),
|
||||
false);
|
||||
m_local_tab->addTab(edit, QString::fromStdString(m_game_id + ".ini"));
|
||||
}
|
||||
|
||||
// Fails to change font if it's directly called at this time. Is there a better workaround?
|
||||
QTimer::singleShot(100, this, [this]() {
|
||||
SetItalics();
|
||||
Config::OnConfigChanged();
|
||||
});
|
||||
}
|
||||
|
||||
void GameConfigWidget::CreateWidgets()
|
||||
{
|
||||
// General
|
||||
m_refresh_config = new QPushButton(tr("Refresh"));
|
||||
|
||||
Config::Layer* layer = m_layer.get();
|
||||
// Core
|
||||
auto* core_box = new QGroupBox(tr("Core"));
|
||||
auto* core_layout = new QGridLayout;
|
||||
core_box->setLayout(core_layout);
|
||||
|
||||
m_enable_dual_core = new QCheckBox(tr("Enable Dual Core"));
|
||||
m_enable_mmu = new QCheckBox(tr("Enable MMU"));
|
||||
m_enable_fprf = new QCheckBox(tr("Enable FPRF"));
|
||||
m_sync_gpu = new QCheckBox(tr("Synchronize GPU thread"));
|
||||
m_emulate_disc_speed = new QCheckBox(tr("Emulate Disc Speed"));
|
||||
m_use_dsp_hle = new QCheckBox(tr("DSP HLE (fast)"));
|
||||
m_manual_texture_sampling = new QCheckBox(tr("Manual Texture Sampling"));
|
||||
m_deterministic_dual_core = new QComboBox;
|
||||
m_enable_dual_core = new ConfigBool(tr("Enable Dual Core"), Config::MAIN_CPU_THREAD, layer);
|
||||
m_enable_mmu = new ConfigBool(tr("Enable MMU"), Config::MAIN_MMU, layer);
|
||||
m_enable_fprf = new ConfigBool(tr("Enable FPRF"), Config::MAIN_FPRF, layer);
|
||||
m_sync_gpu = new ConfigBool(tr("Synchronize GPU thread"), Config::MAIN_SYNC_GPU, layer);
|
||||
m_emulate_disc_speed =
|
||||
new ConfigBool(tr("Emulate Disc Speed"), Config::MAIN_FAST_DISC_SPEED, layer, true);
|
||||
m_use_dsp_hle = new ConfigBool(tr("DSP HLE (fast)"), Config::MAIN_DSP_HLE, layer);
|
||||
|
||||
for (const auto& item : {tr("Not Set"), tr("auto"), tr("none"), tr("fake-completion")})
|
||||
m_deterministic_dual_core->addItem(item);
|
||||
const std::vector<std::string> choice{tr("auto").toStdString(), tr("none").toStdString(),
|
||||
tr("fake-completion").toStdString()};
|
||||
m_deterministic_dual_core =
|
||||
new ConfigStringChoice(choice, Config::MAIN_GPU_DETERMINISM_MODE, layer);
|
||||
|
||||
m_enable_mmu->setToolTip(tr(
|
||||
"Enables the Memory Management Unit, needed for some games. (ON = Compatible, OFF = Fast)"));
|
||||
|
@ -114,24 +132,18 @@ void GameConfigWidget::CreateWidgets()
|
|||
core_layout->addWidget(m_sync_gpu, 3, 0);
|
||||
core_layout->addWidget(m_emulate_disc_speed, 4, 0);
|
||||
core_layout->addWidget(m_use_dsp_hle, 5, 0);
|
||||
core_layout->addWidget(m_manual_texture_sampling, 6, 0);
|
||||
core_layout->addWidget(new QLabel(tr("Deterministic dual core:")), 7, 0);
|
||||
core_layout->addWidget(m_deterministic_dual_core, 7, 1);
|
||||
core_layout->addWidget(new QLabel(tr("Deterministic dual core:")), 6, 0);
|
||||
core_layout->addWidget(m_deterministic_dual_core, 6, 1);
|
||||
|
||||
// Stereoscopy
|
||||
auto* stereoscopy_box = new QGroupBox(tr("Stereoscopy"));
|
||||
auto* stereoscopy_layout = new QGridLayout;
|
||||
stereoscopy_box->setLayout(stereoscopy_layout);
|
||||
|
||||
m_depth_slider = new QSlider(Qt::Horizontal);
|
||||
|
||||
m_depth_slider->setMinimum(100);
|
||||
m_depth_slider->setMaximum(200);
|
||||
|
||||
m_convergence_spin = new QSpinBox;
|
||||
m_convergence_spin->setMinimum(0);
|
||||
m_convergence_spin->setMaximum(INT32_MAX);
|
||||
m_use_monoscopic_shadows = new QCheckBox(tr("Monoscopic Shadows"));
|
||||
m_depth_slider = new ConfigSlider(100, 200, Config::GFX_STEREO_DEPTH_PERCENTAGE, layer);
|
||||
m_convergence_spin = new ConfigInteger(0, INT32_MAX, Config::GFX_STEREO_CONVERGENCE, layer);
|
||||
m_use_monoscopic_shadows =
|
||||
new ConfigBool(tr("Monoscopic Shadows"), Config::GFX_STEREO_EFB_MONO_DEPTH, layer);
|
||||
|
||||
m_depth_slider->setToolTip(
|
||||
tr("This value is multiplied with the depth set in the graphics configuration."));
|
||||
|
@ -140,37 +152,23 @@ void GameConfigWidget::CreateWidgets()
|
|||
m_use_monoscopic_shadows->setToolTip(
|
||||
tr("Use a single depth buffer for both eyes. Needed for a few games."));
|
||||
|
||||
stereoscopy_layout->addWidget(new QLabel(tr("Depth Percentage:")), 0, 0);
|
||||
stereoscopy_layout->addWidget(new ConfigSliderLabel(tr("Depth Percentage:"), m_depth_slider), 0,
|
||||
0);
|
||||
stereoscopy_layout->addWidget(m_depth_slider, 0, 1);
|
||||
stereoscopy_layout->addWidget(new QLabel(tr("Convergence:")), 1, 0);
|
||||
stereoscopy_layout->addWidget(new ConfigIntegerLabel(tr("Convergence:"), m_convergence_spin), 1,
|
||||
0);
|
||||
stereoscopy_layout->addWidget(m_convergence_spin, 1, 1);
|
||||
stereoscopy_layout->addWidget(m_use_monoscopic_shadows, 2, 0);
|
||||
|
||||
auto* settings_box = new QGroupBox(tr("Game-Specific Settings"));
|
||||
auto* settings_layout = new QVBoxLayout;
|
||||
settings_box->setLayout(settings_layout);
|
||||
|
||||
settings_layout->addWidget(
|
||||
new QLabel(tr("These settings override core Dolphin settings.\nUndetermined means the game "
|
||||
"uses Dolphin's setting.")));
|
||||
settings_layout->addWidget(core_box);
|
||||
settings_layout->addWidget(stereoscopy_box);
|
||||
|
||||
auto* general_layout = new QGridLayout;
|
||||
|
||||
general_layout->addWidget(settings_box, 0, 0, 1, -1);
|
||||
|
||||
general_layout->addWidget(m_refresh_config, 1, 0, 1, -1);
|
||||
|
||||
for (QCheckBox* item :
|
||||
{m_enable_dual_core, m_enable_mmu, m_enable_fprf, m_sync_gpu, m_emulate_disc_speed,
|
||||
m_use_dsp_hle, m_manual_texture_sampling, m_use_monoscopic_shadows})
|
||||
item->setTristate(true);
|
||||
auto* general_layout = new QVBoxLayout;
|
||||
general_layout->addWidget(core_box);
|
||||
general_layout->addWidget(stereoscopy_box);
|
||||
general_layout->addStretch();
|
||||
|
||||
auto* general_widget = new QWidget;
|
||||
general_widget->setLayout(general_layout);
|
||||
|
||||
// Advanced
|
||||
// Editor tab
|
||||
auto* advanced_layout = new QVBoxLayout;
|
||||
|
||||
auto* default_group = new QGroupBox(tr("Default Config (Read Only)"));
|
||||
|
@ -196,207 +194,186 @@ void GameConfigWidget::CreateWidgets()
|
|||
|
||||
auto* layout = new QVBoxLayout;
|
||||
auto* tab_widget = new QTabWidget;
|
||||
|
||||
tab_widget->addTab(general_widget, tr("General"));
|
||||
tab_widget->addTab(advanced_widget, tr("Editor"));
|
||||
|
||||
// GFX settings tabs. Placed in a QWidget for consistent margins.
|
||||
auto* gfx_tab_holder = new QWidget;
|
||||
auto* gfx_layout = new QVBoxLayout;
|
||||
gfx_tab_holder->setLayout(gfx_layout);
|
||||
tab_widget->addTab(gfx_tab_holder, tr("Graphics"));
|
||||
|
||||
auto* gfx_tabs = new QTabWidget;
|
||||
|
||||
gfx_tabs->addTab(GetWrappedWidget(new GeneralWidget(this, m_layer.get()), this, 125, 100),
|
||||
tr("General"));
|
||||
gfx_tabs->addTab(GetWrappedWidget(new EnhancementsWidget(this, m_layer.get()), this, 125, 100),
|
||||
tr("Enhancements"));
|
||||
gfx_tabs->addTab(GetWrappedWidget(new HacksWidget(this, m_layer.get()), this, 125, 100),
|
||||
tr("Hacks"));
|
||||
gfx_tabs->addTab(GetWrappedWidget(new AdvancedWidget(this, m_layer.get()), this, 125, 100),
|
||||
tr("Advanced"));
|
||||
const int editor_index = tab_widget->addTab(advanced_widget, tr("Editor"));
|
||||
gfx_layout->addWidget(gfx_tabs);
|
||||
|
||||
connect(tab_widget, &QTabWidget::currentChanged, this, [this, editor_index](int index) {
|
||||
// Update the ini editor after editing other tabs.
|
||||
if (index == editor_index)
|
||||
{
|
||||
// Layer only auto-saves when it is destroyed.
|
||||
m_layer->Save();
|
||||
|
||||
// There can be multiple ini loaded for a game, only replace the one related to the game
|
||||
// ini being edited.
|
||||
for (int i = 0; i < m_local_tab->count(); i++)
|
||||
{
|
||||
if (m_local_tab->tabText(i).toStdString() == m_game_id + ".ini")
|
||||
{
|
||||
m_local_tab->removeTab(i);
|
||||
|
||||
auto* edit = new GameConfigEdit(
|
||||
nullptr,
|
||||
QString::fromStdString(File::GetUserPath(D_GAMESETTINGS_IDX) + m_game_id + ".ini"),
|
||||
false);
|
||||
|
||||
m_local_tab->insertTab(i, edit, QString::fromStdString(m_game_id + ".ini"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update other tabs after using ini editor.
|
||||
if (m_prev_tab_index == editor_index)
|
||||
{
|
||||
// Load won't clear deleted keys, so everything is wiped before loading.
|
||||
m_layer->DeleteAllKeys();
|
||||
m_layer->Load();
|
||||
Config::OnConfigChanged();
|
||||
}
|
||||
|
||||
m_prev_tab_index = index;
|
||||
});
|
||||
|
||||
const QString help_msg = tr(
|
||||
"Italics mark default game settings, bold marks user settings.\nRight-click to remove user "
|
||||
"settings.\nGraphics tabs don't display the value of a default game setting.\nAnti-Aliasing "
|
||||
"settings are disabled when the global graphics backend doesn't "
|
||||
"match the game setting.");
|
||||
|
||||
auto help_icon = style()->standardIcon(QStyle::SP_MessageBoxQuestion);
|
||||
auto* help_label = new QLabel(tr("These settings override core Dolphin settings."));
|
||||
help_label->setToolTip(help_msg);
|
||||
auto help_label_icon = new QLabel();
|
||||
help_label_icon->setPixmap(help_icon.pixmap(12, 12));
|
||||
help_label_icon->setToolTip(help_msg);
|
||||
auto* help_layout = new QHBoxLayout();
|
||||
help_layout->addWidget(help_label);
|
||||
help_layout->addWidget(help_label_icon);
|
||||
help_layout->addStretch();
|
||||
|
||||
layout->addLayout(help_layout);
|
||||
layout->addWidget(tab_widget);
|
||||
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
void GameConfigWidget::ConnectWidgets()
|
||||
GameConfigWidget::~GameConfigWidget()
|
||||
{
|
||||
// Buttons
|
||||
connect(m_refresh_config, &QPushButton::clicked, this, &GameConfigWidget::LoadSettings);
|
||||
// Destructor saves the layer to file.
|
||||
m_layer.reset();
|
||||
|
||||
for (QCheckBox* box :
|
||||
{m_enable_dual_core, m_enable_mmu, m_enable_fprf, m_sync_gpu, m_emulate_disc_speed,
|
||||
m_use_dsp_hle, m_manual_texture_sampling, m_use_monoscopic_shadows})
|
||||
connect(box, &QCheckBox::stateChanged, this, &GameConfigWidget::SaveSettings);
|
||||
|
||||
connect(m_deterministic_dual_core, &QComboBox::currentIndexChanged, this,
|
||||
&GameConfigWidget::SaveSettings);
|
||||
connect(m_depth_slider, &QSlider::valueChanged, this, &GameConfigWidget::SaveSettings);
|
||||
connect(m_convergence_spin, &QSpinBox::valueChanged, this, &GameConfigWidget::SaveSettings);
|
||||
}
|
||||
|
||||
void GameConfigWidget::LoadCheckBox(QCheckBox* checkbox, const std::string& section,
|
||||
const std::string& key, bool reverse)
|
||||
{
|
||||
bool checked;
|
||||
if (m_gameini_local.GetOrCreateSection(section)->Get(key, &checked))
|
||||
return checkbox->setCheckState(checked ^ reverse ? Qt::Checked : Qt::Unchecked);
|
||||
|
||||
if (m_gameini_default.GetOrCreateSection(section)->Get(key, &checked))
|
||||
return checkbox->setCheckState(checked ^ reverse ? Qt::Checked : Qt::Unchecked);
|
||||
checkbox->setCheckState(Qt::PartiallyChecked);
|
||||
}
|
||||
|
||||
void GameConfigWidget::SaveCheckBox(QCheckBox* checkbox, const std::string& section,
|
||||
const std::string& key, bool reverse)
|
||||
{
|
||||
// Delete any existing entries from the local gameini if checkbox is undetermined.
|
||||
// Otherwise, write the current value to the local gameini if the value differs from the default
|
||||
// gameini values.
|
||||
// Delete any existing entry from the local gameini if the value does not differ from the default
|
||||
// gameini value.
|
||||
|
||||
if (checkbox->checkState() == Qt::PartiallyChecked)
|
||||
// If a game is running and the game properties window is closed, update local game layer with
|
||||
// any new changes. Not sure if doing it more frequently is safe.
|
||||
auto local_layer = Config::GetLayer(Config::LayerType::LocalGame);
|
||||
if (local_layer && SConfig::GetInstance().GetGameID() == m_game_id)
|
||||
{
|
||||
m_gameini_local.DeleteKey(section, key);
|
||||
return;
|
||||
local_layer->DeleteAllKeys();
|
||||
local_layer->Load();
|
||||
Config::OnConfigChanged();
|
||||
}
|
||||
|
||||
bool checked = (checkbox->checkState() == Qt::Checked) ^ reverse;
|
||||
|
||||
if (m_gameini_default.Exists(section, key))
|
||||
{
|
||||
bool default_value;
|
||||
m_gameini_default.GetOrCreateSection(section)->Get(key, &default_value);
|
||||
|
||||
if (default_value != checked)
|
||||
m_gameini_local.GetOrCreateSection(section)->Set(key, checked);
|
||||
else
|
||||
m_gameini_local.DeleteKey(section, key);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
m_gameini_local.GetOrCreateSection(section)->Set(key, checked);
|
||||
// Delete empty configs
|
||||
if (File::GetSize(m_gameini_local_path.toStdString()) == 0)
|
||||
File::Delete(m_gameini_local_path.toStdString());
|
||||
}
|
||||
|
||||
void GameConfigWidget::LoadSettings()
|
||||
{
|
||||
// Reload config
|
||||
m_gameini_local = SConfig::LoadLocalGameIni(m_game_id, m_game.GetRevision());
|
||||
m_gameini_default = SConfig::LoadDefaultGameIni(m_game_id, m_game.GetRevision());
|
||||
// Load globals
|
||||
auto update_bool = [this](auto config, bool reverse = false) {
|
||||
const Config::Location& setting = config->GetLocation();
|
||||
|
||||
// Load game-specific settings
|
||||
// Don't overwrite local with global
|
||||
if (m_layer->Exists(setting) || !m_global_layer->Exists(setting))
|
||||
return;
|
||||
|
||||
// Core
|
||||
LoadCheckBox(m_enable_dual_core, "Core", "CPUThread");
|
||||
LoadCheckBox(m_enable_mmu, "Core", "MMU");
|
||||
LoadCheckBox(m_enable_fprf, "Core", "FPRF");
|
||||
LoadCheckBox(m_sync_gpu, "Core", "SyncGPU");
|
||||
LoadCheckBox(m_emulate_disc_speed, "Core", "FastDiscSpeed", true);
|
||||
LoadCheckBox(m_use_dsp_hle, "Core", "DSPHLE");
|
||||
LoadCheckBox(m_manual_texture_sampling, "Video_Hacks", "FastTextureSampling", true);
|
||||
std::optional<bool> value = m_global_layer->Get<bool>(config->GetLocation());
|
||||
|
||||
std::string determinism_mode;
|
||||
if (value.has_value())
|
||||
{
|
||||
const QSignalBlocker blocker(config);
|
||||
config->setChecked(value.value() ^ reverse);
|
||||
}
|
||||
};
|
||||
|
||||
int determinism_index = DETERMINISM_NOT_SET_INDEX;
|
||||
auto update_int = [this](auto config) {
|
||||
const Config::Location& setting = config->GetLocation();
|
||||
|
||||
m_gameini_default.GetIfExists("Core", "GPUDeterminismMode", &determinism_mode);
|
||||
m_gameini_local.GetIfExists("Core", "GPUDeterminismMode", &determinism_mode);
|
||||
if (m_layer->Exists(setting) || !m_global_layer->Exists(setting))
|
||||
return;
|
||||
|
||||
if (determinism_mode == DETERMINISM_AUTO_STRING)
|
||||
std::optional<int> value = m_global_layer->Get<int>(setting);
|
||||
|
||||
if (value.has_value())
|
||||
{
|
||||
const QSignalBlocker blocker(config);
|
||||
config->setValue(value.value());
|
||||
}
|
||||
};
|
||||
|
||||
for (ConfigBool* config : {m_enable_dual_core, m_enable_mmu, m_enable_fprf, m_sync_gpu,
|
||||
m_use_dsp_hle, m_use_monoscopic_shadows})
|
||||
{
|
||||
determinism_index = DETERMINISM_AUTO_INDEX;
|
||||
}
|
||||
else if (determinism_mode == DETERMINISM_NONE_STRING)
|
||||
{
|
||||
determinism_index = DETERMINISM_NONE_INDEX;
|
||||
}
|
||||
else if (determinism_mode == DETERMINISM_FAKE_COMPLETION_STRING)
|
||||
{
|
||||
determinism_index = DETERMINISM_FAKE_COMPLETION_INDEX;
|
||||
update_bool(config);
|
||||
}
|
||||
|
||||
m_deterministic_dual_core->setCurrentIndex(determinism_index);
|
||||
update_bool(m_emulate_disc_speed, true);
|
||||
|
||||
// Stereoscopy
|
||||
int depth_percentage = 100;
|
||||
|
||||
m_gameini_default.GetIfExists("Video_Stereoscopy", "StereoDepthPercentage", &depth_percentage);
|
||||
m_gameini_local.GetIfExists("Video_Stereoscopy", "StereoDepthPercentage", &depth_percentage);
|
||||
|
||||
m_depth_slider->setValue(depth_percentage);
|
||||
|
||||
int convergence = 0;
|
||||
|
||||
m_gameini_default.GetIfExists("Video_Stereoscopy", "StereoConvergence", &convergence);
|
||||
m_gameini_local.GetIfExists("Video_Stereoscopy", "StereoConvergence", &convergence);
|
||||
|
||||
m_convergence_spin->setValue(convergence);
|
||||
|
||||
LoadCheckBox(m_use_monoscopic_shadows, "Video_Stereoscopy", "StereoEFBMonoDepth");
|
||||
update_int(m_depth_slider);
|
||||
update_int(m_convergence_spin);
|
||||
}
|
||||
|
||||
void GameConfigWidget::SaveSettings()
|
||||
void GameConfigWidget::SetItalics()
|
||||
{
|
||||
// Core
|
||||
SaveCheckBox(m_enable_dual_core, "Core", "CPUThread");
|
||||
SaveCheckBox(m_enable_mmu, "Core", "MMU");
|
||||
SaveCheckBox(m_enable_fprf, "Core", "FPRF");
|
||||
SaveCheckBox(m_sync_gpu, "Core", "SyncGPU");
|
||||
SaveCheckBox(m_emulate_disc_speed, "Core", "FastDiscSpeed", true);
|
||||
SaveCheckBox(m_use_dsp_hle, "Core", "DSPHLE");
|
||||
SaveCheckBox(m_manual_texture_sampling, "Video_Hacks", "FastTextureSampling", true);
|
||||
// Mark system game settings with italics. Called once because it should never change.
|
||||
auto italics = [this](auto config) {
|
||||
if (!m_global_layer->Exists(config->GetLocation()))
|
||||
return;
|
||||
|
||||
int determinism_num = m_deterministic_dual_core->currentIndex();
|
||||
QFont ifont = config->font();
|
||||
ifont.setItalic(true);
|
||||
config->setFont(ifont);
|
||||
};
|
||||
|
||||
std::string determinism_mode = DETERMINISM_NOT_SET_STRING;
|
||||
for (auto* config : findChildren<ConfigBool*>())
|
||||
italics(config);
|
||||
for (auto* config : findChildren<ConfigSlider*>())
|
||||
italics(config);
|
||||
for (auto* config : findChildren<ConfigInteger*>())
|
||||
italics(config);
|
||||
for (auto* config : findChildren<ConfigRadioInt*>())
|
||||
italics(config);
|
||||
for (auto* config : findChildren<ConfigChoice*>())
|
||||
italics(config);
|
||||
for (auto* config : findChildren<ConfigStringChoice*>())
|
||||
italics(config);
|
||||
|
||||
switch (determinism_num)
|
||||
for (auto* config : findChildren<ConfigComplexChoice*>())
|
||||
{
|
||||
case DETERMINISM_AUTO_INDEX:
|
||||
determinism_mode = DETERMINISM_AUTO_STRING;
|
||||
break;
|
||||
case DETERMINISM_NONE_INDEX:
|
||||
determinism_mode = DETERMINISM_NONE_STRING;
|
||||
break;
|
||||
case DETERMINISM_FAKE_COMPLETION_INDEX:
|
||||
determinism_mode = DETERMINISM_FAKE_COMPLETION_STRING;
|
||||
break;
|
||||
}
|
||||
|
||||
if (determinism_mode != DETERMINISM_NOT_SET_STRING)
|
||||
{
|
||||
std::string default_mode = DETERMINISM_NOT_SET_STRING;
|
||||
if (!(m_gameini_default.GetIfExists("Core", "GPUDeterminismMode", &default_mode) &&
|
||||
default_mode == determinism_mode))
|
||||
std::pair<Config::Location, Config::Location> location = config->GetLocation();
|
||||
if (m_global_layer->Exists(location.first) || m_global_layer->Exists(location.second))
|
||||
{
|
||||
m_gameini_local.GetOrCreateSection("Core")->Set("GPUDeterminismMode", determinism_mode);
|
||||
QFont ifont = config->font();
|
||||
ifont.setItalic(true);
|
||||
config->setFont(ifont);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_gameini_local.DeleteKey("Core", "GPUDeterminismMode");
|
||||
}
|
||||
|
||||
// Stereoscopy
|
||||
int depth_percentage = m_depth_slider->value();
|
||||
|
||||
if (depth_percentage != 100)
|
||||
{
|
||||
int default_value = 0;
|
||||
if (!(m_gameini_default.GetIfExists("Video_Stereoscopy", "StereoDepthPercentage",
|
||||
&default_value) &&
|
||||
default_value == depth_percentage))
|
||||
{
|
||||
m_gameini_local.GetOrCreateSection("Video_Stereoscopy")
|
||||
->Set("StereoDepthPercentage", depth_percentage);
|
||||
}
|
||||
}
|
||||
|
||||
int convergence = m_convergence_spin->value();
|
||||
if (convergence != 0)
|
||||
{
|
||||
int default_value = 0;
|
||||
if (!(m_gameini_default.GetIfExists("Video_Stereoscopy", "StereoConvergence", &default_value) &&
|
||||
default_value == convergence))
|
||||
{
|
||||
m_gameini_local.GetOrCreateSection("Video_Stereoscopy")
|
||||
->Set("StereoConvergence", convergence);
|
||||
}
|
||||
}
|
||||
|
||||
SaveCheckBox(m_use_monoscopic_shadows, "Video_Stereoscopy", "StereoEFBMonoDepth");
|
||||
|
||||
bool success = m_gameini_local.Save(m_gameini_local_path.toStdString());
|
||||
|
||||
// If the resulting file is empty, delete it. Kind of a hack, but meh.
|
||||
if (success && File::GetSize(m_gameini_local_path.toStdString()) == 0)
|
||||
File::Delete(m_gameini_local_path.toStdString());
|
||||
}
|
||||
|
|
|
@ -7,19 +7,21 @@
|
|||
|
||||
#include <QString>
|
||||
#include <QWidget>
|
||||
|
||||
#include "Common/IniFile.h"
|
||||
|
||||
namespace UICommon
|
||||
{
|
||||
class GameFile;
|
||||
}
|
||||
|
||||
class QCheckBox;
|
||||
class QComboBox;
|
||||
namespace Config
|
||||
{
|
||||
class Layer;
|
||||
} // namespace Config
|
||||
|
||||
class ConfigBool;
|
||||
class ConfigInteger;
|
||||
class ConfigSlider;
|
||||
class ConfigStringChoice;
|
||||
class QPushButton;
|
||||
class QSlider;
|
||||
class QSpinBox;
|
||||
class QTabWidget;
|
||||
|
||||
class GameConfigWidget : public QWidget
|
||||
|
@ -27,45 +29,33 @@ class GameConfigWidget : public QWidget
|
|||
Q_OBJECT
|
||||
public:
|
||||
explicit GameConfigWidget(const UICommon::GameFile& game);
|
||||
~GameConfigWidget();
|
||||
|
||||
private:
|
||||
void CreateWidgets();
|
||||
void ConnectWidgets();
|
||||
|
||||
void LoadSettings();
|
||||
void SaveSettings();
|
||||
void SetItalics();
|
||||
|
||||
void SaveCheckBox(QCheckBox* checkbox, const std::string& section, const std::string& key,
|
||||
bool reverse = false);
|
||||
void LoadCheckBox(QCheckBox* checkbox, const std::string& section, const std::string& key,
|
||||
bool reverse = false);
|
||||
|
||||
QString m_gameini_sys_path;
|
||||
QString m_gameini_local_path;
|
||||
|
||||
QTabWidget* m_default_tab;
|
||||
QTabWidget* m_local_tab;
|
||||
|
||||
QCheckBox* m_enable_dual_core;
|
||||
QCheckBox* m_enable_mmu;
|
||||
QCheckBox* m_enable_fprf;
|
||||
QCheckBox* m_sync_gpu;
|
||||
QCheckBox* m_emulate_disc_speed;
|
||||
QCheckBox* m_use_dsp_hle;
|
||||
QCheckBox* m_use_monoscopic_shadows;
|
||||
QCheckBox* m_manual_texture_sampling;
|
||||
ConfigBool* m_enable_dual_core;
|
||||
ConfigBool* m_enable_mmu;
|
||||
ConfigBool* m_enable_fprf;
|
||||
ConfigBool* m_sync_gpu;
|
||||
ConfigBool* m_emulate_disc_speed;
|
||||
ConfigBool* m_use_dsp_hle;
|
||||
ConfigBool* m_use_monoscopic_shadows;
|
||||
|
||||
QPushButton* m_refresh_config;
|
||||
|
||||
QComboBox* m_deterministic_dual_core;
|
||||
|
||||
QSlider* m_depth_slider;
|
||||
|
||||
QSpinBox* m_convergence_spin;
|
||||
ConfigStringChoice* m_deterministic_dual_core;
|
||||
ConfigSlider* m_depth_slider;
|
||||
ConfigInteger* m_convergence_spin;
|
||||
|
||||
const UICommon::GameFile& m_game;
|
||||
std::string m_game_id;
|
||||
|
||||
Common::IniFile m_gameini_local;
|
||||
Common::IniFile m_gameini_default;
|
||||
std::unique_ptr<Config::Layer> m_layer;
|
||||
std::unique_ptr<Config::Layer> m_global_layer;
|
||||
int m_prev_tab_index = 0;
|
||||
};
|
||||
|
|
|
@ -206,7 +206,7 @@ void GeckoCodeWidget::OnItemChanged(QListWidgetItem* item)
|
|||
m_gecko_codes[index].enabled = (item->checkState() == Qt::Checked);
|
||||
|
||||
if (!m_restart_required)
|
||||
Gecko::SetActiveCodes(m_gecko_codes);
|
||||
Gecko::SetActiveCodes(m_gecko_codes, m_game_id);
|
||||
|
||||
SaveCodes();
|
||||
}
|
||||
|
@ -318,20 +318,14 @@ void GeckoCodeWidget::SortAlphabetically()
|
|||
|
||||
void GeckoCodeWidget::SortEnabledCodesFirst()
|
||||
{
|
||||
std::stable_sort(m_gecko_codes.begin(), m_gecko_codes.end(), [](const auto& a, const auto& b) {
|
||||
return a.enabled && a.enabled != b.enabled;
|
||||
});
|
||||
|
||||
std::ranges::stable_partition(m_gecko_codes, std::identity{}, &Gecko::GeckoCode::enabled);
|
||||
UpdateList();
|
||||
SaveCodes();
|
||||
}
|
||||
|
||||
void GeckoCodeWidget::SortDisabledCodesFirst()
|
||||
{
|
||||
std::stable_sort(m_gecko_codes.begin(), m_gecko_codes.end(), [](const auto& a, const auto& b) {
|
||||
return !a.enabled && a.enabled != b.enabled;
|
||||
});
|
||||
|
||||
std::ranges::stable_partition(m_gecko_codes, std::logical_not{}, &Gecko::GeckoCode::enabled);
|
||||
UpdateList();
|
||||
SaveCodes();
|
||||
}
|
||||
|
|
|
@ -3,11 +3,9 @@
|
|||
|
||||
#include "DolphinQt/Config/Graphics/AdvancedWidget.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QGridLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QLabel>
|
||||
#include <QSpinBox>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Core/Config/GraphicsSettings.h"
|
||||
|
@ -19,6 +17,7 @@
|
|||
#include "DolphinQt/Config/ConfigControls/ConfigBool.h"
|
||||
#include "DolphinQt/Config/ConfigControls/ConfigChoice.h"
|
||||
#include "DolphinQt/Config/ConfigControls/ConfigInteger.h"
|
||||
#include "DolphinQt/Config/GameConfigWidget.h"
|
||||
#include "DolphinQt/Config/Graphics/GraphicsWindow.h"
|
||||
#include "DolphinQt/Config/ToolTipControls/ToolTipCheckBox.h"
|
||||
#include "DolphinQt/QtUtils/SignalBlocking.h"
|
||||
|
@ -29,7 +28,6 @@
|
|||
AdvancedWidget::AdvancedWidget(GraphicsWindow* parent)
|
||||
{
|
||||
CreateWidgets();
|
||||
LoadSettings();
|
||||
ConnectWidgets();
|
||||
AddDescriptions();
|
||||
|
||||
|
@ -37,17 +35,30 @@ AdvancedWidget::AdvancedWidget(GraphicsWindow* parent)
|
|||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, [this](Core::State state) {
|
||||
OnEmulationStateChanged(state != Core::State::Uninitialized);
|
||||
});
|
||||
connect(m_manual_texture_sampling, &QCheckBox::toggled, [this, parent] {
|
||||
SaveSettings();
|
||||
emit parent->UseFastTextureSamplingChanged();
|
||||
});
|
||||
connect(m_manual_texture_sampling, &QCheckBox::toggled,
|
||||
[this, parent] { emit parent->UseFastTextureSamplingChanged(); });
|
||||
|
||||
OnBackendChanged();
|
||||
OnEmulationStateChanged(!Core::IsUninitialized(Core::System::GetInstance()));
|
||||
}
|
||||
|
||||
AdvancedWidget::AdvancedWidget(GameConfigWidget* parent, Config::Layer* layer) : m_game_layer(layer)
|
||||
{
|
||||
CreateWidgets();
|
||||
ConnectWidgets();
|
||||
AddDescriptions();
|
||||
|
||||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, [this](Core::State state) {
|
||||
OnEmulationStateChanged(state != Core::State::Uninitialized);
|
||||
});
|
||||
OnEmulationStateChanged(Core::GetState(Core::System::GetInstance()) !=
|
||||
Core::State::Uninitialized);
|
||||
}
|
||||
|
||||
void AdvancedWidget::CreateWidgets()
|
||||
{
|
||||
const bool local_edit = m_game_layer != nullptr;
|
||||
|
||||
auto* main_layout = new QVBoxLayout;
|
||||
|
||||
// Performance
|
||||
|
@ -55,17 +66,19 @@ void AdvancedWidget::CreateWidgets()
|
|||
auto* performance_layout = new QGridLayout();
|
||||
performance_box->setLayout(performance_layout);
|
||||
|
||||
m_show_fps = new ConfigBool(tr("Show FPS"), Config::GFX_SHOW_FPS);
|
||||
m_show_ftimes = new ConfigBool(tr("Show Frame Times"), Config::GFX_SHOW_FTIMES);
|
||||
m_show_vps = new ConfigBool(tr("Show VPS"), Config::GFX_SHOW_VPS);
|
||||
m_show_vtimes = new ConfigBool(tr("Show VBlank Times"), Config::GFX_SHOW_VTIMES);
|
||||
m_show_graphs = new ConfigBool(tr("Show Performance Graphs"), Config::GFX_SHOW_GRAPHS);
|
||||
m_show_speed = new ConfigBool(tr("Show % Speed"), Config::GFX_SHOW_SPEED);
|
||||
m_show_speed_colors = new ConfigBool(tr("Show Speed Colors"), Config::GFX_SHOW_SPEED_COLORS);
|
||||
m_perf_samp_window = new ConfigInteger(0, 10000, Config::GFX_PERF_SAMP_WINDOW, 100);
|
||||
m_show_fps = new ConfigBool(tr("Show FPS"), Config::GFX_SHOW_FPS, m_game_layer);
|
||||
m_show_ftimes = new ConfigBool(tr("Show Frame Times"), Config::GFX_SHOW_FTIMES, m_game_layer);
|
||||
m_show_vps = new ConfigBool(tr("Show VPS"), Config::GFX_SHOW_VPS, m_game_layer);
|
||||
m_show_vtimes = new ConfigBool(tr("Show VBlank Times"), Config::GFX_SHOW_VTIMES, m_game_layer);
|
||||
m_show_graphs =
|
||||
new ConfigBool(tr("Show Performance Graphs"), Config::GFX_SHOW_GRAPHS, m_game_layer);
|
||||
m_show_speed = new ConfigBool(tr("Show % Speed"), Config::GFX_SHOW_SPEED, m_game_layer);
|
||||
m_show_speed_colors =
|
||||
new ConfigBool(tr("Show Speed Colors"), Config::GFX_SHOW_SPEED_COLORS, m_game_layer);
|
||||
m_perf_samp_window = new ConfigInteger(0, 10000, Config::GFX_PERF_SAMP_WINDOW, m_game_layer, 100);
|
||||
m_perf_samp_window->SetTitle(tr("Performance Sample Window (ms)"));
|
||||
m_log_render_time =
|
||||
new ConfigBool(tr("Log Render Time to File"), Config::GFX_LOG_RENDER_TIME_TO_FILE);
|
||||
m_log_render_time = new ConfigBool(tr("Log Render Time to File"),
|
||||
Config::GFX_LOG_RENDER_TIME_TO_FILE, m_game_layer);
|
||||
|
||||
performance_layout->addWidget(m_show_fps, 0, 0);
|
||||
performance_layout->addWidget(m_show_ftimes, 0, 1);
|
||||
|
@ -83,14 +96,16 @@ void AdvancedWidget::CreateWidgets()
|
|||
auto* debugging_layout = new QGridLayout();
|
||||
debugging_box->setLayout(debugging_layout);
|
||||
|
||||
m_enable_wireframe = new ConfigBool(tr("Enable Wireframe"), Config::GFX_ENABLE_WIREFRAME);
|
||||
m_show_statistics = new ConfigBool(tr("Show Statistics"), Config::GFX_OVERLAY_STATS);
|
||||
m_show_proj_statistics =
|
||||
new ConfigBool(tr("Show Projection Statistics"), Config::GFX_OVERLAY_PROJ_STATS);
|
||||
m_enable_wireframe =
|
||||
new ConfigBool(tr("Enable Wireframe"), Config::GFX_ENABLE_WIREFRAME, m_game_layer);
|
||||
m_show_statistics =
|
||||
new ConfigBool(tr("Show Statistics"), Config::GFX_OVERLAY_STATS, m_game_layer);
|
||||
m_show_proj_statistics = new ConfigBool(tr("Show Projection Statistics"),
|
||||
Config::GFX_OVERLAY_PROJ_STATS, m_game_layer);
|
||||
m_enable_format_overlay =
|
||||
new ConfigBool(tr("Texture Format Overlay"), Config::GFX_TEXFMT_OVERLAY_ENABLE);
|
||||
m_enable_api_validation =
|
||||
new ConfigBool(tr("Enable API Validation Layers"), Config::GFX_ENABLE_VALIDATION_LAYER);
|
||||
new ConfigBool(tr("Texture Format Overlay"), Config::GFX_TEXFMT_OVERLAY_ENABLE, m_game_layer);
|
||||
m_enable_api_validation = new ConfigBool(tr("Enable API Validation Layers"),
|
||||
Config::GFX_ENABLE_VALIDATION_LAYER, m_game_layer);
|
||||
|
||||
debugging_layout->addWidget(m_enable_wireframe, 0, 0);
|
||||
debugging_layout->addWidget(m_show_statistics, 0, 1);
|
||||
|
@ -103,14 +118,25 @@ void AdvancedWidget::CreateWidgets()
|
|||
auto* utility_layout = new QGridLayout();
|
||||
utility_box->setLayout(utility_layout);
|
||||
|
||||
m_load_custom_textures = new ConfigBool(tr("Load Custom Textures"), Config::GFX_HIRES_TEXTURES);
|
||||
m_prefetch_custom_textures =
|
||||
new ConfigBool(tr("Prefetch Custom Textures"), Config::GFX_CACHE_HIRES_TEXTURES);
|
||||
m_load_custom_textures =
|
||||
new ConfigBool(tr("Load Custom Textures"), Config::GFX_HIRES_TEXTURES, m_game_layer);
|
||||
m_prefetch_custom_textures = new ConfigBool(tr("Prefetch Custom Textures"),
|
||||
Config::GFX_CACHE_HIRES_TEXTURES, m_game_layer);
|
||||
m_prefetch_custom_textures->setEnabled(m_load_custom_textures->isChecked());
|
||||
m_dump_efb_target = new ConfigBool(tr("Dump EFB Target"), Config::GFX_DUMP_EFB_TARGET);
|
||||
m_dump_xfb_target = new ConfigBool(tr("Dump XFB Target"), Config::GFX_DUMP_XFB_TARGET);
|
||||
m_disable_vram_copies =
|
||||
new ConfigBool(tr("Disable EFB VRAM Copies"), Config::GFX_HACK_DISABLE_COPY_TO_VRAM);
|
||||
m_enable_graphics_mods = new ToolTipCheckBox(tr("Enable Graphics Mods"));
|
||||
|
||||
if (local_edit)
|
||||
{
|
||||
// It's hazardous to accidentally set these in a game ini.
|
||||
m_dump_efb_target->setEnabled(false);
|
||||
m_dump_xfb_target->setEnabled(false);
|
||||
}
|
||||
|
||||
m_disable_vram_copies = new ConfigBool(tr("Disable EFB VRAM Copies"),
|
||||
Config::GFX_HACK_DISABLE_COPY_TO_VRAM, m_game_layer);
|
||||
m_enable_graphics_mods =
|
||||
new ConfigBool(tr("Enable Graphics Mods"), Config::GFX_MODS_ENABLE, m_game_layer);
|
||||
|
||||
utility_layout->addWidget(m_load_custom_textures, 0, 0);
|
||||
utility_layout->addWidget(m_prefetch_custom_textures, 0, 1);
|
||||
|
@ -128,6 +154,16 @@ void AdvancedWidget::CreateWidgets()
|
|||
m_dump_textures = new ConfigBool(tr("Enable"), Config::GFX_DUMP_TEXTURES);
|
||||
m_dump_base_textures = new ConfigBool(tr("Dump Base Textures"), Config::GFX_DUMP_BASE_TEXTURES);
|
||||
m_dump_mip_textures = new ConfigBool(tr("Dump Mip Maps"), Config::GFX_DUMP_MIP_TEXTURES);
|
||||
m_dump_mip_textures->setEnabled(m_dump_textures->isChecked());
|
||||
m_dump_base_textures->setEnabled(m_dump_textures->isChecked());
|
||||
|
||||
if (local_edit)
|
||||
{
|
||||
// It's hazardous to accidentally set dumping in a game ini.
|
||||
m_dump_textures->setEnabled(false);
|
||||
m_dump_base_textures->setEnabled(false);
|
||||
m_dump_mip_textures->setEnabled(false);
|
||||
}
|
||||
|
||||
texture_dump_layout->addWidget(m_dump_textures, 0, 0);
|
||||
|
||||
|
@ -142,18 +178,22 @@ void AdvancedWidget::CreateWidgets()
|
|||
m_frame_dumps_resolution_type =
|
||||
new ConfigChoice({tr("Window Resolution"), tr("Aspect Ratio Corrected Internal Resolution"),
|
||||
tr("Raw Internal Resolution")},
|
||||
Config::GFX_FRAME_DUMPS_RESOLUTION_TYPE);
|
||||
m_dump_use_ffv1 = new ConfigBool(tr("Use Lossless Codec (FFV1)"), Config::GFX_USE_FFV1);
|
||||
m_dump_bitrate = new ConfigInteger(0, 1000000, Config::GFX_BITRATE_KBPS, 1000);
|
||||
m_png_compression_level = new ConfigInteger(0, 9, Config::GFX_PNG_COMPRESSION_LEVEL);
|
||||
|
||||
Config::GFX_FRAME_DUMPS_RESOLUTION_TYPE, m_game_layer);
|
||||
m_png_compression_level =
|
||||
new ConfigInteger(0, 9, Config::GFX_PNG_COMPRESSION_LEVEL, m_game_layer);
|
||||
dump_layout->addWidget(new QLabel(tr("Resolution Type:")), 0, 0);
|
||||
dump_layout->addWidget(m_frame_dumps_resolution_type, 0, 1);
|
||||
|
||||
#if defined(HAVE_FFMPEG)
|
||||
m_dump_use_ffv1 =
|
||||
new ConfigBool(tr("Use Lossless Codec (FFV1)"), Config::GFX_USE_FFV1, m_game_layer);
|
||||
m_dump_bitrate = new ConfigInteger(0, 1000000, Config::GFX_BITRATE_KBPS, m_game_layer, 1000);
|
||||
m_dump_bitrate->setEnabled(!m_dump_use_ffv1->isChecked());
|
||||
dump_layout->addWidget(m_dump_use_ffv1, 1, 0);
|
||||
dump_layout->addWidget(new QLabel(tr("Bitrate (kbps):")), 2, 0);
|
||||
dump_layout->addWidget(m_dump_bitrate, 2, 1);
|
||||
#endif
|
||||
|
||||
dump_layout->addWidget(new QLabel(tr("PNG Compression Level:")), 3, 0);
|
||||
m_png_compression_level->SetTitle(tr("PNG Compression Level"));
|
||||
dump_layout->addWidget(m_png_compression_level, 3, 1);
|
||||
|
@ -163,14 +203,16 @@ void AdvancedWidget::CreateWidgets()
|
|||
auto* misc_layout = new QGridLayout();
|
||||
misc_box->setLayout(misc_layout);
|
||||
|
||||
m_enable_cropping = new ConfigBool(tr("Crop"), Config::GFX_CROP);
|
||||
m_enable_prog_scan = new ToolTipCheckBox(tr("Enable Progressive Scan"));
|
||||
m_backend_multithreading =
|
||||
new ConfigBool(tr("Backend Multithreading"), Config::GFX_BACKEND_MULTITHREADING);
|
||||
m_enable_cropping = new ConfigBool(tr("Crop"), Config::GFX_CROP, m_game_layer);
|
||||
m_enable_prog_scan =
|
||||
new ConfigBool(tr("Enable Progressive Scan"), Config::SYSCONF_PROGRESSIVE_SCAN, m_game_layer);
|
||||
m_backend_multithreading = new ConfigBool(tr("Backend Multithreading"),
|
||||
Config::GFX_BACKEND_MULTITHREADING, m_game_layer);
|
||||
m_prefer_vs_for_point_line_expansion = new ConfigBool(
|
||||
// i18n: VS is short for vertex shaders.
|
||||
tr("Prefer VS for Point/Line Expansion"), Config::GFX_PREFER_VS_FOR_LINE_POINT_EXPANSION);
|
||||
m_cpu_cull = new ConfigBool(tr("Cull Vertices on the CPU"), Config::GFX_CPU_CULL);
|
||||
tr("Prefer VS for Point/Line Expansion"), Config::GFX_PREFER_VS_FOR_LINE_POINT_EXPANSION,
|
||||
m_game_layer);
|
||||
m_cpu_cull = new ConfigBool(tr("Cull Vertices on the CPU"), Config::GFX_CPU_CULL, m_game_layer);
|
||||
|
||||
misc_layout->addWidget(m_enable_cropping, 0, 0);
|
||||
misc_layout->addWidget(m_enable_prog_scan, 0, 1);
|
||||
|
@ -179,7 +221,7 @@ void AdvancedWidget::CreateWidgets()
|
|||
misc_layout->addWidget(m_cpu_cull, 2, 0);
|
||||
#ifdef _WIN32
|
||||
m_borderless_fullscreen =
|
||||
new ConfigBool(tr("Borderless Fullscreen"), Config::GFX_BORDERLESS_FULLSCREEN);
|
||||
new ConfigBool(tr("Borderless Fullscreen"), Config::GFX_BORDERLESS_FULLSCREEN, m_game_layer);
|
||||
|
||||
misc_layout->addWidget(m_borderless_fullscreen, 2, 1);
|
||||
#endif
|
||||
|
@ -189,10 +231,10 @@ void AdvancedWidget::CreateWidgets()
|
|||
auto* experimental_layout = new QGridLayout();
|
||||
experimental_box->setLayout(experimental_layout);
|
||||
|
||||
m_defer_efb_access_invalidation =
|
||||
new ConfigBool(tr("Defer EFB Cache Invalidation"), Config::GFX_HACK_EFB_DEFER_INVALIDATION);
|
||||
m_manual_texture_sampling =
|
||||
new ConfigBool(tr("Manual Texture Sampling"), Config::GFX_HACK_FAST_TEXTURE_SAMPLING, true);
|
||||
m_defer_efb_access_invalidation = new ConfigBool(
|
||||
tr("Defer EFB Cache Invalidation"), Config::GFX_HACK_EFB_DEFER_INVALIDATION, m_game_layer);
|
||||
m_manual_texture_sampling = new ConfigBool(
|
||||
tr("Manual Texture Sampling"), Config::GFX_HACK_FAST_TEXTURE_SAMPLING, m_game_layer, true);
|
||||
|
||||
experimental_layout->addWidget(m_defer_efb_access_invalidation, 0, 0);
|
||||
experimental_layout->addWidget(m_manual_texture_sampling, 0, 1);
|
||||
|
@ -211,34 +253,19 @@ void AdvancedWidget::CreateWidgets()
|
|||
|
||||
void AdvancedWidget::ConnectWidgets()
|
||||
{
|
||||
connect(m_load_custom_textures, &QCheckBox::toggled, this, &AdvancedWidget::SaveSettings);
|
||||
connect(m_dump_use_ffv1, &QCheckBox::toggled, this, &AdvancedWidget::SaveSettings);
|
||||
connect(m_enable_prog_scan, &QCheckBox::toggled, this, &AdvancedWidget::SaveSettings);
|
||||
connect(m_dump_textures, &QCheckBox::toggled, this, &AdvancedWidget::SaveSettings);
|
||||
connect(m_enable_graphics_mods, &QCheckBox::toggled, this, &AdvancedWidget::SaveSettings);
|
||||
}
|
||||
connect(m_load_custom_textures, &QCheckBox::toggled, this,
|
||||
[this](bool checked) { m_prefetch_custom_textures->setEnabled(checked); });
|
||||
connect(m_dump_textures, &QCheckBox::toggled, this, [this](bool checked) {
|
||||
m_dump_mip_textures->setEnabled(checked);
|
||||
m_dump_base_textures->setEnabled(checked);
|
||||
});
|
||||
connect(m_enable_graphics_mods, &QCheckBox::toggled, this,
|
||||
[this](bool checked) { emit Settings::Instance().EnableGfxModsChanged(checked); });
|
||||
|
||||
void AdvancedWidget::LoadSettings()
|
||||
{
|
||||
m_prefetch_custom_textures->setEnabled(Config::Get(Config::GFX_HIRES_TEXTURES));
|
||||
m_dump_bitrate->setEnabled(!Config::Get(Config::GFX_USE_FFV1));
|
||||
|
||||
m_enable_prog_scan->setChecked(Config::Get(Config::SYSCONF_PROGRESSIVE_SCAN));
|
||||
m_dump_mip_textures->setEnabled(Config::Get(Config::GFX_DUMP_TEXTURES));
|
||||
m_dump_base_textures->setEnabled(Config::Get(Config::GFX_DUMP_TEXTURES));
|
||||
|
||||
SignalBlocking(m_enable_graphics_mods)->setChecked(Settings::Instance().GetGraphicModsEnabled());
|
||||
}
|
||||
|
||||
void AdvancedWidget::SaveSettings()
|
||||
{
|
||||
m_prefetch_custom_textures->setEnabled(Config::Get(Config::GFX_HIRES_TEXTURES));
|
||||
m_dump_bitrate->setEnabled(!Config::Get(Config::GFX_USE_FFV1));
|
||||
|
||||
Config::SetBase(Config::SYSCONF_PROGRESSIVE_SCAN, m_enable_prog_scan->isChecked());
|
||||
m_dump_mip_textures->setEnabled(Config::Get(Config::GFX_DUMP_TEXTURES));
|
||||
m_dump_base_textures->setEnabled(Config::Get(Config::GFX_DUMP_TEXTURES));
|
||||
Settings::Instance().SetGraphicModsEnabled(m_enable_graphics_mods->isChecked());
|
||||
#if defined(HAVE_FFMPEG)
|
||||
connect(m_dump_use_ffv1, &QCheckBox::toggled, this,
|
||||
[this](bool checked) { m_dump_bitrate->setEnabled(!checked); });
|
||||
#endif
|
||||
}
|
||||
|
||||
void AdvancedWidget::OnBackendChanged()
|
||||
|
|
|
@ -8,22 +8,22 @@
|
|||
class ConfigBool;
|
||||
class ConfigChoice;
|
||||
class ConfigInteger;
|
||||
class GameConfigWidget;
|
||||
class GraphicsWindow;
|
||||
class QCheckBox;
|
||||
class QComboBox;
|
||||
class QSpinBox;
|
||||
class ToolTipCheckBox;
|
||||
|
||||
namespace Config
|
||||
{
|
||||
class Layer;
|
||||
} // namespace Config
|
||||
|
||||
class AdvancedWidget final : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit AdvancedWidget(GraphicsWindow* parent);
|
||||
AdvancedWidget(GameConfigWidget* parent, Config::Layer* layer);
|
||||
|
||||
private:
|
||||
void LoadSettings();
|
||||
void SaveSettings();
|
||||
|
||||
void CreateWidgets();
|
||||
void ConnectWidgets();
|
||||
void AddDescriptions();
|
||||
|
@ -52,7 +52,7 @@ private:
|
|||
ConfigBool* m_dump_xfb_target;
|
||||
ConfigBool* m_disable_vram_copies;
|
||||
ConfigBool* m_load_custom_textures;
|
||||
ToolTipCheckBox* m_enable_graphics_mods;
|
||||
ConfigBool* m_enable_graphics_mods;
|
||||
|
||||
// Texture dumping
|
||||
ConfigBool* m_dump_textures;
|
||||
|
@ -67,7 +67,7 @@ private:
|
|||
|
||||
// Misc
|
||||
ConfigBool* m_enable_cropping;
|
||||
ToolTipCheckBox* m_enable_prog_scan;
|
||||
ConfigBool* m_enable_prog_scan;
|
||||
ConfigBool* m_backend_multithreading;
|
||||
ConfigBool* m_prefer_vs_for_point_line_expansion;
|
||||
ConfigBool* m_cpu_cull;
|
||||
|
@ -76,4 +76,6 @@ private:
|
|||
// Experimental
|
||||
ConfigBool* m_defer_efb_access_invalidation;
|
||||
ConfigBool* m_manual_texture_sampling;
|
||||
|
||||
Config::Layer* m_game_layer = nullptr;
|
||||
};
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
#include "Core/Config/GraphicsSettings.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
|
||||
|
@ -18,6 +20,7 @@
|
|||
#include "DolphinQt/Config/ConfigControls/ConfigChoice.h"
|
||||
#include "DolphinQt/Config/ConfigControls/ConfigRadio.h"
|
||||
#include "DolphinQt/Config/ConfigControls/ConfigSlider.h"
|
||||
#include "DolphinQt/Config/GameConfigWidget.h"
|
||||
#include "DolphinQt/Config/Graphics/ColorCorrectionConfigWindow.h"
|
||||
#include "DolphinQt/Config/Graphics/GraphicsWindow.h"
|
||||
#include "DolphinQt/Config/Graphics/PostProcessingConfigWindow.h"
|
||||
|
@ -31,31 +34,43 @@
|
|||
#include "VideoCommon/VideoCommon.h"
|
||||
#include "VideoCommon/VideoConfig.h"
|
||||
|
||||
EnhancementsWidget::EnhancementsWidget(GraphicsWindow* parent) : m_block_save(false)
|
||||
EnhancementsWidget::EnhancementsWidget(GraphicsWindow* parent)
|
||||
{
|
||||
CreateWidgets();
|
||||
LoadSettings();
|
||||
LoadPPShaders();
|
||||
ConnectWidgets();
|
||||
AddDescriptions();
|
||||
connect(parent, &GraphicsWindow::BackendChanged,
|
||||
[this](const QString& backend) { LoadSettings(); });
|
||||
connect(parent, &GraphicsWindow::UseFastTextureSamplingChanged, this,
|
||||
&EnhancementsWidget::LoadSettings);
|
||||
connect(parent, &GraphicsWindow::UseGPUTextureDecodingChanged, this,
|
||||
&EnhancementsWidget::LoadSettings);
|
||||
|
||||
// BackendChanged is called by parent on window creation.
|
||||
connect(parent, &GraphicsWindow::BackendChanged, this, &EnhancementsWidget::OnBackendChanged);
|
||||
connect(parent, &GraphicsWindow::UseFastTextureSamplingChanged, this, [this]() {
|
||||
m_texture_filtering_combo->setEnabled(ReadSetting(Config::GFX_HACK_FAST_TEXTURE_SAMPLING));
|
||||
});
|
||||
connect(parent, &GraphicsWindow::UseGPUTextureDecodingChanged, this, [this]() {
|
||||
m_arbitrary_mipmap_detection->setEnabled(!ReadSetting(Config::GFX_ENABLE_GPU_TEXTURE_DECODING));
|
||||
});
|
||||
}
|
||||
|
||||
constexpr int TEXTURE_FILTERING_DEFAULT = 0;
|
||||
constexpr int TEXTURE_FILTERING_ANISO_2X = 1;
|
||||
constexpr int TEXTURE_FILTERING_ANISO_4X = 2;
|
||||
constexpr int TEXTURE_FILTERING_ANISO_8X = 3;
|
||||
constexpr int TEXTURE_FILTERING_ANISO_16X = 4;
|
||||
constexpr int TEXTURE_FILTERING_FORCE_NEAREST = 5;
|
||||
constexpr int TEXTURE_FILTERING_FORCE_LINEAR = 6;
|
||||
constexpr int TEXTURE_FILTERING_FORCE_LINEAR_ANISO_2X = 7;
|
||||
constexpr int TEXTURE_FILTERING_FORCE_LINEAR_ANISO_4X = 8;
|
||||
constexpr int TEXTURE_FILTERING_FORCE_LINEAR_ANISO_8X = 9;
|
||||
constexpr int TEXTURE_FILTERING_FORCE_LINEAR_ANISO_16X = 10;
|
||||
EnhancementsWidget::EnhancementsWidget(GameConfigWidget* parent, Config::Layer* layer)
|
||||
: m_game_layer(layer)
|
||||
{
|
||||
CreateWidgets();
|
||||
LoadPPShaders();
|
||||
ConnectWidgets();
|
||||
AddDescriptions();
|
||||
|
||||
connect(&Settings::Instance(), &Settings::ConfigChanged, this,
|
||||
&EnhancementsWidget::OnConfigChanged);
|
||||
}
|
||||
|
||||
constexpr int ANISO_DEFAULT = 0;
|
||||
constexpr int ANISO_2X = 1;
|
||||
constexpr int ANISO_4X = 2;
|
||||
constexpr int ANISO_8X = 3;
|
||||
constexpr int ANISO_16X = 4;
|
||||
constexpr int FILTERING_DEFAULT = 0;
|
||||
constexpr int FILTERING_NEAREST = 1;
|
||||
constexpr int FILTERING_LINEAR = 2;
|
||||
|
||||
void EnhancementsWidget::CreateWidgets()
|
||||
{
|
||||
|
@ -83,7 +98,7 @@ void EnhancementsWidget::CreateWidgets()
|
|||
// If the current scale is greater than the max scale in the ini, add sufficient options so that
|
||||
// when the settings are saved we don't lose the user-modified value from the ini.
|
||||
const int max_efb_scale =
|
||||
std::max(Config::Get(Config::GFX_EFB_SCALE), Config::Get(Config::GFX_MAX_EFB_SCALE));
|
||||
std::max(ReadSetting(Config::GFX_EFB_SCALE), ReadSetting(Config::GFX_MAX_EFB_SCALE));
|
||||
for (int scale = static_cast<int>(resolution_options.size()); scale <= max_efb_scale; scale++)
|
||||
{
|
||||
const QString scale_text = QString::number(scale);
|
||||
|
@ -104,61 +119,60 @@ void EnhancementsWidget::CreateWidgets()
|
|||
}
|
||||
}
|
||||
|
||||
m_ir_combo = new ConfigChoice(resolution_options, Config::GFX_EFB_SCALE);
|
||||
m_ir_combo = new ConfigChoice(resolution_options, Config::GFX_EFB_SCALE, m_game_layer);
|
||||
m_ir_combo->setMaxVisibleItems(visible_resolution_option_count);
|
||||
|
||||
m_aa_combo = new ToolTipComboBox();
|
||||
m_aa_combo = new ConfigComplexChoice(Config::GFX_MSAA, Config::GFX_SSAA, m_game_layer);
|
||||
m_aa_combo->Add(tr("None"), (u32)1, false);
|
||||
|
||||
m_texture_filtering_combo = new ToolTipComboBox();
|
||||
m_texture_filtering_combo->addItem(tr("Default"), TEXTURE_FILTERING_DEFAULT);
|
||||
m_texture_filtering_combo->addItem(tr("2x Anisotropic"), TEXTURE_FILTERING_ANISO_2X);
|
||||
m_texture_filtering_combo->addItem(tr("4x Anisotropic"), TEXTURE_FILTERING_ANISO_4X);
|
||||
m_texture_filtering_combo->addItem(tr("8x Anisotropic"), TEXTURE_FILTERING_ANISO_8X);
|
||||
m_texture_filtering_combo->addItem(tr("16x Anisotropic"), TEXTURE_FILTERING_ANISO_16X);
|
||||
m_texture_filtering_combo->addItem(tr("Force Nearest"), TEXTURE_FILTERING_FORCE_NEAREST);
|
||||
m_texture_filtering_combo->addItem(tr("Force Linear"), TEXTURE_FILTERING_FORCE_LINEAR);
|
||||
m_texture_filtering_combo->addItem(tr("Force Linear and 2x Anisotropic"),
|
||||
TEXTURE_FILTERING_FORCE_LINEAR_ANISO_2X);
|
||||
m_texture_filtering_combo->addItem(tr("Force Linear and 4x Anisotropic"),
|
||||
TEXTURE_FILTERING_FORCE_LINEAR_ANISO_4X);
|
||||
m_texture_filtering_combo->addItem(tr("Force Linear and 8x Anisotropic"),
|
||||
TEXTURE_FILTERING_FORCE_LINEAR_ANISO_8X);
|
||||
m_texture_filtering_combo->addItem(tr("Force Linear and 16x Anisotropic"),
|
||||
TEXTURE_FILTERING_FORCE_LINEAR_ANISO_16X);
|
||||
m_texture_filtering_combo =
|
||||
new ConfigComplexChoice(Config::GFX_ENHANCE_MAX_ANISOTROPY,
|
||||
Config::GFX_ENHANCE_FORCE_TEXTURE_FILTERING, m_game_layer);
|
||||
|
||||
m_output_resampling_combo = new ToolTipComboBox();
|
||||
m_output_resampling_combo->addItem(tr("Default"),
|
||||
static_cast<int>(OutputResamplingMode::Default));
|
||||
m_output_resampling_combo->addItem(tr("Bilinear"),
|
||||
static_cast<int>(OutputResamplingMode::Bilinear));
|
||||
m_output_resampling_combo->addItem(tr("Bicubic: B-Spline"),
|
||||
static_cast<int>(OutputResamplingMode::BSpline));
|
||||
m_output_resampling_combo->addItem(tr("Bicubic: Mitchell-Netravali"),
|
||||
static_cast<int>(OutputResamplingMode::MitchellNetravali));
|
||||
m_output_resampling_combo->addItem(tr("Bicubic: Catmull-Rom"),
|
||||
static_cast<int>(OutputResamplingMode::CatmullRom));
|
||||
m_output_resampling_combo->addItem(tr("Sharp Bilinear"),
|
||||
static_cast<int>(OutputResamplingMode::SharpBilinear));
|
||||
m_output_resampling_combo->addItem(tr("Area Sampling"),
|
||||
static_cast<int>(OutputResamplingMode::AreaSampling));
|
||||
m_texture_filtering_combo->Add(tr("Default"), ANISO_DEFAULT, FILTERING_DEFAULT);
|
||||
m_texture_filtering_combo->Add(tr("2x Anisotropic"), ANISO_2X, FILTERING_DEFAULT);
|
||||
m_texture_filtering_combo->Add(tr("4x Anisotropic"), ANISO_4X, FILTERING_DEFAULT);
|
||||
m_texture_filtering_combo->Add(tr("8x Anisotropic"), ANISO_8X, FILTERING_DEFAULT);
|
||||
m_texture_filtering_combo->Add(tr("16x Anisotropic"), ANISO_16X, FILTERING_DEFAULT);
|
||||
m_texture_filtering_combo->Add(tr("Force Nearest"), ANISO_DEFAULT, FILTERING_NEAREST);
|
||||
m_texture_filtering_combo->Add(tr("Force Linear"), ANISO_DEFAULT, FILTERING_LINEAR);
|
||||
m_texture_filtering_combo->Add(tr("Force Linear and 2x Anisotropic"), ANISO_2X, FILTERING_LINEAR);
|
||||
m_texture_filtering_combo->Add(tr("Force Linear and 4x Anisotropic"), ANISO_4X, FILTERING_LINEAR);
|
||||
m_texture_filtering_combo->Add(tr("Force Linear and 8x Anisotropic"), ANISO_8X, FILTERING_LINEAR);
|
||||
m_texture_filtering_combo->Add(tr("Force Linear and 16x Anisotropic"), ANISO_16X,
|
||||
FILTERING_LINEAR);
|
||||
m_texture_filtering_combo->Refresh();
|
||||
m_texture_filtering_combo->setEnabled(ReadSetting(Config::GFX_HACK_FAST_TEXTURE_SAMPLING));
|
||||
|
||||
m_output_resampling_combo = new ConfigChoice(
|
||||
{tr("Default"), tr("Bilinear"), tr("Bicubic: B-Spline"), tr("Bicubic: Mitchell-Netravali"),
|
||||
tr("Bicubic: Catmull-Rom"), tr("Sharp Bilinear"), tr("Area Sampling")},
|
||||
Config::GFX_ENHANCE_OUTPUT_RESAMPLING, m_game_layer);
|
||||
|
||||
m_configure_color_correction = new ToolTipPushButton(tr("Configure"));
|
||||
|
||||
m_pp_effect = new ToolTipComboBox();
|
||||
m_pp_effect = new ConfigStringChoice(VideoCommon::PostProcessing::GetShaderList(),
|
||||
Config::GFX_ENHANCE_POST_SHADER, m_game_layer);
|
||||
m_configure_pp_effect = new NonDefaultQPushButton(tr("Configure"));
|
||||
m_scaled_efb_copy = new ConfigBool(tr("Scaled EFB Copy"), Config::GFX_HACK_COPY_EFB_SCALED);
|
||||
m_per_pixel_lighting =
|
||||
new ConfigBool(tr("Per-Pixel Lighting"), Config::GFX_ENABLE_PIXEL_LIGHTING);
|
||||
m_configure_pp_effect->setDisabled(true);
|
||||
|
||||
m_widescreen_hack = new ConfigBool(tr("Widescreen Hack"), Config::GFX_WIDESCREEN_HACK);
|
||||
m_disable_fog = new ConfigBool(tr("Disable Fog"), Config::GFX_DISABLE_FOG);
|
||||
m_scaled_efb_copy =
|
||||
new ConfigBool(tr("Scaled EFB Copy"), Config::GFX_HACK_COPY_EFB_SCALED, m_game_layer);
|
||||
m_per_pixel_lighting =
|
||||
new ConfigBool(tr("Per-Pixel Lighting"), Config::GFX_ENABLE_PIXEL_LIGHTING, m_game_layer);
|
||||
|
||||
m_widescreen_hack =
|
||||
new ConfigBool(tr("Widescreen Hack"), Config::GFX_WIDESCREEN_HACK, m_game_layer);
|
||||
m_disable_fog = new ConfigBool(tr("Disable Fog"), Config::GFX_DISABLE_FOG, m_game_layer);
|
||||
m_force_24bit_color =
|
||||
new ConfigBool(tr("Force 24-Bit Color"), Config::GFX_ENHANCE_FORCE_TRUE_COLOR);
|
||||
m_disable_copy_filter =
|
||||
new ConfigBool(tr("Disable Copy Filter"), Config::GFX_ENHANCE_DISABLE_COPY_FILTER);
|
||||
m_arbitrary_mipmap_detection = new ConfigBool(tr("Arbitrary Mipmap Detection"),
|
||||
Config::GFX_ENHANCE_ARBITRARY_MIPMAP_DETECTION);
|
||||
m_hdr = new ConfigBool(tr("HDR Post-Processing"), Config::GFX_ENHANCE_HDR_OUTPUT);
|
||||
new ConfigBool(tr("Force 24-Bit Color"), Config::GFX_ENHANCE_FORCE_TRUE_COLOR, m_game_layer);
|
||||
m_disable_copy_filter = new ConfigBool(tr("Disable Copy Filter"),
|
||||
Config::GFX_ENHANCE_DISABLE_COPY_FILTER, m_game_layer);
|
||||
m_arbitrary_mipmap_detection =
|
||||
new ConfigBool(tr("Arbitrary Mipmap Detection"),
|
||||
Config::GFX_ENHANCE_ARBITRARY_MIPMAP_DETECTION, m_game_layer);
|
||||
m_arbitrary_mipmap_detection->setEnabled(!ReadSetting(Config::GFX_ENABLE_GPU_TEXTURE_DECODING));
|
||||
m_hdr = new ConfigBool(tr("HDR Post-Processing"), Config::GFX_ENHANCE_HDR_OUTPUT, m_game_layer);
|
||||
|
||||
int row = 0;
|
||||
enhancements_layout->addWidget(new QLabel(tr("Internal Resolution:")), row, 0);
|
||||
|
@ -209,26 +223,27 @@ void EnhancementsWidget::CreateWidgets()
|
|||
|
||||
m_3d_mode = new ConfigChoice({tr("Off"), tr("Side-by-Side"), tr("Top-and-Bottom"), tr("Anaglyph"),
|
||||
tr("HDMI 3D"), tr("Passive")},
|
||||
Config::GFX_STEREO_MODE);
|
||||
m_3d_depth = new ConfigSlider(0, Config::GFX_STEREO_DEPTH_MAXIMUM, Config::GFX_STEREO_DEPTH);
|
||||
Config::GFX_STEREO_MODE, m_game_layer);
|
||||
m_3d_depth =
|
||||
new ConfigSlider(0, Config::GFX_STEREO_DEPTH_MAXIMUM, Config::GFX_STEREO_DEPTH, m_game_layer);
|
||||
m_3d_convergence = new ConfigSlider(0, Config::GFX_STEREO_CONVERGENCE_MAXIMUM,
|
||||
Config::GFX_STEREO_CONVERGENCE, 100);
|
||||
Config::GFX_STEREO_CONVERGENCE, m_game_layer, 100);
|
||||
|
||||
m_3d_swap_eyes = new ConfigBool(tr("Swap Eyes"), Config::GFX_STEREO_SWAP_EYES);
|
||||
m_3d_swap_eyes = new ConfigBool(tr("Swap Eyes"), Config::GFX_STEREO_SWAP_EYES, m_game_layer);
|
||||
|
||||
m_3d_per_eye_resolution =
|
||||
new ConfigBool(tr("Use Full Resolution Per Eye"), Config::GFX_STEREO_PER_EYE_RESOLUTION_FULL);
|
||||
m_3d_per_eye_resolution = new ConfigBool(
|
||||
tr("Use Full Resolution Per Eye"), Config::GFX_STEREO_PER_EYE_RESOLUTION_FULL, m_game_layer);
|
||||
|
||||
stereoscopy_layout->addWidget(new QLabel(tr("Stereoscopic 3D Mode:")), 0, 0);
|
||||
stereoscopy_layout->addWidget(m_3d_mode, 0, 1);
|
||||
stereoscopy_layout->addWidget(new QLabel(tr("Depth:")), 1, 0);
|
||||
stereoscopy_layout->addWidget(new ConfigSliderLabel(tr("Depth:"), m_3d_depth), 1, 0);
|
||||
stereoscopy_layout->addWidget(m_3d_depth, 1, 1);
|
||||
stereoscopy_layout->addWidget(new QLabel(tr("Convergence:")), 2, 0);
|
||||
stereoscopy_layout->addWidget(new ConfigSliderLabel(tr("Convergence:"), m_3d_convergence), 2, 0);
|
||||
stereoscopy_layout->addWidget(m_3d_convergence, 2, 1);
|
||||
stereoscopy_layout->addWidget(m_3d_swap_eyes, 3, 0);
|
||||
stereoscopy_layout->addWidget(m_3d_per_eye_resolution, 4, 0);
|
||||
|
||||
auto current_stereo_mode = Config::Get(Config::GFX_STEREO_MODE);
|
||||
auto current_stereo_mode = ReadSetting(Config::GFX_STEREO_MODE);
|
||||
if (current_stereo_mode != StereoMode::SBS && current_stereo_mode != StereoMode::TAB)
|
||||
m_3d_per_eye_resolution->hide();
|
||||
|
||||
|
@ -241,58 +256,53 @@ void EnhancementsWidget::CreateWidgets()
|
|||
|
||||
void EnhancementsWidget::ConnectWidgets()
|
||||
{
|
||||
connect(m_aa_combo, &QComboBox::currentIndexChanged, [this](int) { SaveSettings(); });
|
||||
connect(m_texture_filtering_combo, &QComboBox::currentIndexChanged,
|
||||
[this](int) { SaveSettings(); });
|
||||
connect(m_output_resampling_combo, &QComboBox::currentIndexChanged,
|
||||
[this](int) { SaveSettings(); });
|
||||
connect(m_pp_effect, &QComboBox::currentIndexChanged, [this](int) { SaveSettings(); });
|
||||
connect(m_3d_mode, &QComboBox::currentIndexChanged, [this] {
|
||||
m_block_save = true;
|
||||
m_configure_color_correction->setEnabled(g_Config.backend_info.bSupportsPostProcessing);
|
||||
|
||||
auto current_stereo_mode = Config::Get(Config::GFX_STEREO_MODE);
|
||||
LoadPPShaders(current_stereo_mode);
|
||||
auto current_stereo_mode = ReadSetting(Config::GFX_STEREO_MODE);
|
||||
LoadPPShaders();
|
||||
|
||||
if (current_stereo_mode == StereoMode::SBS || current_stereo_mode == StereoMode::TAB)
|
||||
m_3d_per_eye_resolution->show();
|
||||
else
|
||||
m_3d_per_eye_resolution->hide();
|
||||
|
||||
m_block_save = false;
|
||||
SaveSettings();
|
||||
});
|
||||
|
||||
connect(m_pp_effect, &QComboBox::currentIndexChanged, this, &EnhancementsWidget::ShaderChanged);
|
||||
|
||||
connect(m_configure_color_correction, &QPushButton::clicked, this,
|
||||
&EnhancementsWidget::ConfigureColorCorrection);
|
||||
connect(m_configure_pp_effect, &QPushButton::clicked, this,
|
||||
&EnhancementsWidget::ConfigurePostProcessingShader);
|
||||
|
||||
connect(&Settings::Instance(), &Settings::ConfigChanged, this, [this] {
|
||||
const QSignalBlocker blocker(this);
|
||||
m_block_save = true;
|
||||
LoadPPShaders(Config::Get(Config::GFX_STEREO_MODE));
|
||||
m_block_save = false;
|
||||
});
|
||||
}
|
||||
|
||||
void EnhancementsWidget::LoadPPShaders(StereoMode stereo_mode)
|
||||
template <typename T>
|
||||
T EnhancementsWidget::ReadSetting(const Config::Info<T>& setting) const
|
||||
{
|
||||
std::vector<std::string> shaders = VideoCommon::PostProcessing::GetShaderList();
|
||||
if (stereo_mode == StereoMode::Anaglyph)
|
||||
{
|
||||
shaders = VideoCommon::PostProcessing::GetAnaglyphShaderList();
|
||||
}
|
||||
else if (stereo_mode == StereoMode::Passive)
|
||||
{
|
||||
shaders = VideoCommon::PostProcessing::GetPassiveShaderList();
|
||||
}
|
||||
if (m_game_layer != nullptr)
|
||||
return m_game_layer->Get(setting);
|
||||
else
|
||||
return Config::Get(setting);
|
||||
}
|
||||
|
||||
void EnhancementsWidget::LoadPPShaders()
|
||||
{
|
||||
auto stereo_mode = ReadSetting(Config::GFX_STEREO_MODE);
|
||||
|
||||
const QSignalBlocker blocker(m_pp_effect);
|
||||
m_pp_effect->clear();
|
||||
|
||||
// Get shader list
|
||||
std::vector<std::string> shaders = VideoCommon::PostProcessing::GetShaderList();
|
||||
|
||||
if (stereo_mode == StereoMode::Anaglyph)
|
||||
shaders = VideoCommon::PostProcessing::GetAnaglyphShaderList();
|
||||
else if (stereo_mode == StereoMode::Passive)
|
||||
shaders = VideoCommon::PostProcessing::GetPassiveShaderList();
|
||||
|
||||
// Populate widget
|
||||
if (stereo_mode != StereoMode::Anaglyph && stereo_mode != StereoMode::Passive)
|
||||
m_pp_effect->addItem(tr("(off)"));
|
||||
|
||||
auto selected_shader = Config::Get(Config::GFX_ENHANCE_POST_SHADER);
|
||||
auto selected_shader = ReadSetting(Config::GFX_ENHANCE_POST_SHADER);
|
||||
|
||||
bool found = false;
|
||||
|
||||
|
@ -306,6 +316,7 @@ void EnhancementsWidget::LoadPPShaders(StereoMode stereo_mode)
|
|||
}
|
||||
}
|
||||
|
||||
// Force a shader for StereoModes that require it.
|
||||
if (!found)
|
||||
{
|
||||
if (stereo_mode == StereoMode::Anaglyph)
|
||||
|
@ -321,103 +332,20 @@ void EnhancementsWidget::LoadPPShaders(StereoMode stereo_mode)
|
|||
else
|
||||
m_pp_effect->setCurrentIndex(0);
|
||||
|
||||
Config::SetBaseOrCurrent(Config::GFX_ENHANCE_POST_SHADER, selected_shader);
|
||||
// Save forced shader, but avoid forcing an option into a game ini layer.
|
||||
if (m_game_layer == nullptr && ReadSetting(Config::GFX_ENHANCE_POST_SHADER) != selected_shader)
|
||||
Config::SetBaseOrCurrent(Config::GFX_ENHANCE_POST_SHADER, selected_shader);
|
||||
}
|
||||
|
||||
const bool supports_postprocessing = g_Config.backend_info.bSupportsPostProcessing;
|
||||
m_pp_effect->setEnabled(supports_postprocessing);
|
||||
|
||||
m_pp_effect->setToolTip(supports_postprocessing ?
|
||||
QString{} :
|
||||
tr("%1 doesn't support this feature.")
|
||||
.arg(tr(g_video_backend->GetDisplayName().c_str())));
|
||||
|
||||
VideoCommon::PostProcessingConfiguration pp_shader;
|
||||
if (selected_shader != "" && supports_postprocessing)
|
||||
{
|
||||
pp_shader.LoadShader(selected_shader);
|
||||
m_configure_pp_effect->setEnabled(pp_shader.HasOptions());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_configure_pp_effect->setEnabled(false);
|
||||
}
|
||||
m_pp_effect->Load();
|
||||
ShaderChanged();
|
||||
}
|
||||
|
||||
void EnhancementsWidget::LoadSettings()
|
||||
void EnhancementsWidget::OnBackendChanged()
|
||||
{
|
||||
m_block_save = true;
|
||||
m_texture_filtering_combo->setEnabled(Config::Get(Config::GFX_HACK_FAST_TEXTURE_SAMPLING));
|
||||
m_arbitrary_mipmap_detection->setEnabled(!Config::Get(Config::GFX_ENABLE_GPU_TEXTURE_DECODING));
|
||||
|
||||
// Anti-Aliasing
|
||||
|
||||
const u32 aa_selection = Config::Get(Config::GFX_MSAA);
|
||||
const bool ssaa = Config::Get(Config::GFX_SSAA);
|
||||
const int aniso = Config::Get(Config::GFX_ENHANCE_MAX_ANISOTROPY);
|
||||
const TextureFilteringMode tex_filter_mode =
|
||||
Config::Get(Config::GFX_ENHANCE_FORCE_TEXTURE_FILTERING);
|
||||
|
||||
m_aa_combo->clear();
|
||||
|
||||
for (const u32 aa_mode : g_Config.backend_info.AAModes)
|
||||
{
|
||||
if (aa_mode == 1)
|
||||
m_aa_combo->addItem(tr("None"), 1);
|
||||
else
|
||||
m_aa_combo->addItem(tr("%1x MSAA").arg(aa_mode), static_cast<int>(aa_mode));
|
||||
|
||||
if (aa_mode == aa_selection && !ssaa)
|
||||
m_aa_combo->setCurrentIndex(m_aa_combo->count() - 1);
|
||||
}
|
||||
if (g_Config.backend_info.bSupportsSSAA)
|
||||
{
|
||||
for (const u32 aa_mode : g_Config.backend_info.AAModes)
|
||||
{
|
||||
if (aa_mode != 1) // don't show "None" twice
|
||||
{
|
||||
// Mark SSAA using negative values in the variant
|
||||
m_aa_combo->addItem(tr("%1x SSAA").arg(aa_mode), -static_cast<int>(aa_mode));
|
||||
if (aa_mode == aa_selection && ssaa)
|
||||
m_aa_combo->setCurrentIndex(m_aa_combo->count() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_aa_combo->setEnabled(m_aa_combo->count() > 1);
|
||||
|
||||
switch (tex_filter_mode)
|
||||
{
|
||||
case TextureFilteringMode::Default:
|
||||
if (aniso >= 0 && aniso <= 4)
|
||||
m_texture_filtering_combo->setCurrentIndex(aniso);
|
||||
else
|
||||
m_texture_filtering_combo->setCurrentIndex(TEXTURE_FILTERING_DEFAULT);
|
||||
break;
|
||||
case TextureFilteringMode::Nearest:
|
||||
m_texture_filtering_combo->setCurrentIndex(TEXTURE_FILTERING_FORCE_NEAREST);
|
||||
break;
|
||||
case TextureFilteringMode::Linear:
|
||||
if (aniso >= 0 && aniso <= 4)
|
||||
m_texture_filtering_combo->setCurrentIndex(TEXTURE_FILTERING_FORCE_LINEAR + aniso);
|
||||
else
|
||||
m_texture_filtering_combo->setCurrentIndex(TEXTURE_FILTERING_FORCE_LINEAR);
|
||||
break;
|
||||
}
|
||||
|
||||
// Resampling
|
||||
const OutputResamplingMode output_resampling_mode =
|
||||
Config::Get(Config::GFX_ENHANCE_OUTPUT_RESAMPLING);
|
||||
m_output_resampling_combo->setCurrentIndex(
|
||||
m_output_resampling_combo->findData(static_cast<int>(output_resampling_mode)));
|
||||
|
||||
m_output_resampling_combo->setEnabled(g_Config.backend_info.bSupportsPostProcessing);
|
||||
|
||||
// Color Correction
|
||||
m_configure_color_correction->setEnabled(g_Config.backend_info.bSupportsPostProcessing);
|
||||
|
||||
// Post Processing Shader
|
||||
LoadPPShaders(Config::Get(Config::GFX_STEREO_MODE));
|
||||
m_hdr->setEnabled(g_Config.backend_info.bSupportsHDROutput);
|
||||
|
||||
// Stereoscopy
|
||||
const bool supports_stereoscopy = g_Config.backend_info.bSupportsGeometryShaders;
|
||||
|
@ -426,105 +354,98 @@ void EnhancementsWidget::LoadSettings()
|
|||
m_3d_depth->setEnabled(supports_stereoscopy);
|
||||
m_3d_swap_eyes->setEnabled(supports_stereoscopy);
|
||||
|
||||
m_hdr->setEnabled(g_Config.backend_info.bSupportsHDROutput);
|
||||
|
||||
m_block_save = false;
|
||||
}
|
||||
|
||||
void EnhancementsWidget::SaveSettings()
|
||||
{
|
||||
if (m_block_save)
|
||||
return;
|
||||
|
||||
const u32 aa_value = static_cast<u32>(std::abs(m_aa_combo->currentData().toInt()));
|
||||
const bool is_ssaa = m_aa_combo->currentData().toInt() < 0;
|
||||
|
||||
Config::SetBaseOrCurrent(Config::GFX_MSAA, aa_value);
|
||||
Config::SetBaseOrCurrent(Config::GFX_SSAA, is_ssaa);
|
||||
|
||||
const int texture_filtering_selection = m_texture_filtering_combo->currentData().toInt();
|
||||
switch (texture_filtering_selection)
|
||||
// PostProcessing
|
||||
const bool supports_postprocessing = g_Config.backend_info.bSupportsPostProcessing;
|
||||
if (!supports_postprocessing)
|
||||
{
|
||||
case TEXTURE_FILTERING_DEFAULT:
|
||||
Config::SetBaseOrCurrent(Config::GFX_ENHANCE_MAX_ANISOTROPY, 0);
|
||||
Config::SetBaseOrCurrent(Config::GFX_ENHANCE_FORCE_TEXTURE_FILTERING,
|
||||
TextureFilteringMode::Default);
|
||||
break;
|
||||
case TEXTURE_FILTERING_ANISO_2X:
|
||||
Config::SetBaseOrCurrent(Config::GFX_ENHANCE_MAX_ANISOTROPY, 1);
|
||||
Config::SetBaseOrCurrent(Config::GFX_ENHANCE_FORCE_TEXTURE_FILTERING,
|
||||
TextureFilteringMode::Default);
|
||||
break;
|
||||
case TEXTURE_FILTERING_ANISO_4X:
|
||||
Config::SetBaseOrCurrent(Config::GFX_ENHANCE_MAX_ANISOTROPY, 2);
|
||||
Config::SetBaseOrCurrent(Config::GFX_ENHANCE_FORCE_TEXTURE_FILTERING,
|
||||
TextureFilteringMode::Default);
|
||||
break;
|
||||
case TEXTURE_FILTERING_ANISO_8X:
|
||||
Config::SetBaseOrCurrent(Config::GFX_ENHANCE_MAX_ANISOTROPY, 3);
|
||||
Config::SetBaseOrCurrent(Config::GFX_ENHANCE_FORCE_TEXTURE_FILTERING,
|
||||
TextureFilteringMode::Default);
|
||||
break;
|
||||
case TEXTURE_FILTERING_ANISO_16X:
|
||||
Config::SetBaseOrCurrent(Config::GFX_ENHANCE_MAX_ANISOTROPY, 4);
|
||||
Config::SetBaseOrCurrent(Config::GFX_ENHANCE_FORCE_TEXTURE_FILTERING,
|
||||
TextureFilteringMode::Default);
|
||||
break;
|
||||
case TEXTURE_FILTERING_FORCE_NEAREST:
|
||||
Config::SetBaseOrCurrent(Config::GFX_ENHANCE_MAX_ANISOTROPY, 0);
|
||||
Config::SetBaseOrCurrent(Config::GFX_ENHANCE_FORCE_TEXTURE_FILTERING,
|
||||
TextureFilteringMode::Nearest);
|
||||
break;
|
||||
case TEXTURE_FILTERING_FORCE_LINEAR:
|
||||
Config::SetBaseOrCurrent(Config::GFX_ENHANCE_MAX_ANISOTROPY, 0);
|
||||
Config::SetBaseOrCurrent(Config::GFX_ENHANCE_FORCE_TEXTURE_FILTERING,
|
||||
TextureFilteringMode::Linear);
|
||||
break;
|
||||
case TEXTURE_FILTERING_FORCE_LINEAR_ANISO_2X:
|
||||
Config::SetBaseOrCurrent(Config::GFX_ENHANCE_MAX_ANISOTROPY, 1);
|
||||
Config::SetBaseOrCurrent(Config::GFX_ENHANCE_FORCE_TEXTURE_FILTERING,
|
||||
TextureFilteringMode::Linear);
|
||||
break;
|
||||
case TEXTURE_FILTERING_FORCE_LINEAR_ANISO_4X:
|
||||
Config::SetBaseOrCurrent(Config::GFX_ENHANCE_MAX_ANISOTROPY, 2);
|
||||
Config::SetBaseOrCurrent(Config::GFX_ENHANCE_FORCE_TEXTURE_FILTERING,
|
||||
TextureFilteringMode::Linear);
|
||||
break;
|
||||
case TEXTURE_FILTERING_FORCE_LINEAR_ANISO_8X:
|
||||
Config::SetBaseOrCurrent(Config::GFX_ENHANCE_MAX_ANISOTROPY, 3);
|
||||
Config::SetBaseOrCurrent(Config::GFX_ENHANCE_FORCE_TEXTURE_FILTERING,
|
||||
TextureFilteringMode::Linear);
|
||||
break;
|
||||
case TEXTURE_FILTERING_FORCE_LINEAR_ANISO_16X:
|
||||
Config::SetBaseOrCurrent(Config::GFX_ENHANCE_MAX_ANISOTROPY, 4);
|
||||
Config::SetBaseOrCurrent(Config::GFX_ENHANCE_FORCE_TEXTURE_FILTERING,
|
||||
TextureFilteringMode::Linear);
|
||||
break;
|
||||
m_configure_pp_effect->setEnabled(false);
|
||||
m_pp_effect->setEnabled(false);
|
||||
m_pp_effect->setToolTip(
|
||||
tr("%1 doesn't support this feature.").arg(tr(g_video_backend->GetDisplayName().c_str())));
|
||||
}
|
||||
else if (!m_pp_effect->isEnabled() && supports_postprocessing)
|
||||
{
|
||||
m_configure_pp_effect->setEnabled(true);
|
||||
m_pp_effect->setEnabled(true);
|
||||
m_pp_effect->setToolTip(QString{});
|
||||
LoadPPShaders();
|
||||
}
|
||||
|
||||
const int output_resampling_selection = m_output_resampling_combo->currentData().toInt();
|
||||
Config::SetBaseOrCurrent(Config::GFX_ENHANCE_OUTPUT_RESAMPLING,
|
||||
static_cast<OutputResamplingMode>(output_resampling_selection));
|
||||
UpdateAAOptions();
|
||||
}
|
||||
|
||||
const bool anaglyph = g_Config.stereo_mode == StereoMode::Anaglyph;
|
||||
const bool passive = g_Config.stereo_mode == StereoMode::Passive;
|
||||
Config::SetBaseOrCurrent(Config::GFX_ENHANCE_POST_SHADER,
|
||||
(!anaglyph && !passive && m_pp_effect->currentIndex() == 0) ?
|
||||
"" :
|
||||
m_pp_effect->currentText().toStdString());
|
||||
void EnhancementsWidget::ShaderChanged()
|
||||
{
|
||||
auto shader = ReadSetting(Config::GFX_ENHANCE_POST_SHADER);
|
||||
|
||||
VideoCommon::PostProcessingConfiguration pp_shader;
|
||||
if (Config::Get(Config::GFX_ENHANCE_POST_SHADER) != "")
|
||||
if (shader == "(off)" || shader == "")
|
||||
{
|
||||
pp_shader.LoadShader(Config::Get(Config::GFX_ENHANCE_POST_SHADER));
|
||||
shader = "";
|
||||
|
||||
// Setting a shader to null in a game ini could be confusing, as it won't be bolded. Remove it
|
||||
// instead.
|
||||
if (m_game_layer != nullptr)
|
||||
m_game_layer->DeleteKey(Config::GFX_ENHANCE_POST_SHADER.GetLocation());
|
||||
else
|
||||
Config::SetBaseOrCurrent(Config::GFX_ENHANCE_POST_SHADER, shader);
|
||||
}
|
||||
|
||||
if (shader != "" && m_pp_effect->isEnabled())
|
||||
{
|
||||
VideoCommon::PostProcessingConfiguration pp_shader;
|
||||
pp_shader.LoadShader(shader);
|
||||
m_configure_pp_effect->setEnabled(pp_shader.HasOptions());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_configure_pp_effect->setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
LoadSettings();
|
||||
void EnhancementsWidget::OnConfigChanged()
|
||||
{
|
||||
// Only used for the GameConfigWidget. Bypasses graphics window signals and backend info due to it
|
||||
// being global.
|
||||
m_texture_filtering_combo->setEnabled(ReadSetting(Config::GFX_HACK_FAST_TEXTURE_SAMPLING));
|
||||
m_arbitrary_mipmap_detection->setEnabled(!ReadSetting(Config::GFX_ENABLE_GPU_TEXTURE_DECODING));
|
||||
UpdateAAOptions();
|
||||
|
||||
// Needs to update after deleting a key for 3d settings.
|
||||
LoadPPShaders();
|
||||
}
|
||||
|
||||
void EnhancementsWidget::UpdateAAOptions()
|
||||
{
|
||||
const QSignalBlocker blocker_aa(m_aa_combo);
|
||||
|
||||
m_aa_combo->Reset();
|
||||
m_aa_combo->Add(tr("None"), (u32)1, false);
|
||||
|
||||
std::vector<u32> aa_modes = g_Config.backend_info.AAModes;
|
||||
for (const u32 aa_mode : aa_modes)
|
||||
{
|
||||
if (aa_mode > 1)
|
||||
m_aa_combo->Add(tr("%1x MSAA").arg(aa_mode), aa_mode, false);
|
||||
}
|
||||
|
||||
if (g_Config.backend_info.bSupportsSSAA)
|
||||
{
|
||||
for (const u32 aa_mode : aa_modes)
|
||||
{
|
||||
if (aa_mode > 1)
|
||||
m_aa_combo->Add(tr("%1x SSAA").arg(aa_mode), aa_mode, true);
|
||||
}
|
||||
}
|
||||
|
||||
m_aa_combo->Refresh();
|
||||
|
||||
// Backend info can't be populated in the local game settings window. Only enable local game AA
|
||||
// edits when the backend info is correct - global and local have the same backend.
|
||||
const bool good_info =
|
||||
m_game_layer == nullptr || !m_game_layer->Exists(Config::MAIN_GFX_BACKEND.GetLocation()) ||
|
||||
Config::Get(Config::MAIN_GFX_BACKEND) == m_game_layer->Get(Config::MAIN_GFX_BACKEND);
|
||||
|
||||
m_aa_combo->setEnabled(m_aa_combo->count() > 1 && good_info);
|
||||
}
|
||||
|
||||
void EnhancementsWidget::AddDescriptions()
|
||||
|
@ -713,7 +634,7 @@ void EnhancementsWidget::ConfigureColorCorrection()
|
|||
|
||||
void EnhancementsWidget::ConfigurePostProcessingShader()
|
||||
{
|
||||
const std::string shader = Config::Get(Config::GFX_ENHANCE_POST_SHADER);
|
||||
const std::string shader = ReadSetting(Config::GFX_ENHANCE_POST_SHADER);
|
||||
PostProcessingConfigWindow dialog(this, shader);
|
||||
SetQWidgetWindowDecorations(&dialog);
|
||||
dialog.exec();
|
||||
|
|
|
@ -9,39 +9,52 @@
|
|||
|
||||
class ConfigBool;
|
||||
class ConfigChoice;
|
||||
class ConfigComplexChoice;
|
||||
class ConfigStringChoice;
|
||||
class ConfigSlider;
|
||||
class GameConfigWidget;
|
||||
class GraphicsWindow;
|
||||
class QCheckBox;
|
||||
class QComboBox;
|
||||
class QPushButton;
|
||||
class QSlider;
|
||||
class ToolTipComboBox;
|
||||
class ToolTipPushButton;
|
||||
enum class StereoMode : int;
|
||||
|
||||
namespace Config
|
||||
{
|
||||
template <typename T>
|
||||
class Info;
|
||||
class Layer;
|
||||
} // namespace Config
|
||||
|
||||
class EnhancementsWidget final : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit EnhancementsWidget(GraphicsWindow* parent);
|
||||
EnhancementsWidget(GameConfigWidget* parent, Config::Layer* layer);
|
||||
|
||||
private:
|
||||
void LoadSettings();
|
||||
void SaveSettings();
|
||||
template <typename T>
|
||||
T ReadSetting(const Config::Info<T>& setting) const;
|
||||
|
||||
void CreateWidgets();
|
||||
void ConnectWidgets();
|
||||
void AddDescriptions();
|
||||
|
||||
void OnBackendChanged();
|
||||
void UpdateAAOptions();
|
||||
void LoadPPShaders();
|
||||
void ShaderChanged();
|
||||
void OnConfigChanged();
|
||||
|
||||
void ConfigureColorCorrection();
|
||||
void ConfigurePostProcessingShader();
|
||||
void LoadPPShaders(StereoMode stereo_mode);
|
||||
|
||||
// Enhancements
|
||||
ConfigChoice* m_ir_combo;
|
||||
ToolTipComboBox* m_aa_combo;
|
||||
ToolTipComboBox* m_texture_filtering_combo;
|
||||
ToolTipComboBox* m_output_resampling_combo;
|
||||
ToolTipComboBox* m_pp_effect;
|
||||
ConfigComplexChoice* m_aa_combo;
|
||||
ConfigComplexChoice* m_texture_filtering_combo;
|
||||
ConfigChoice* m_output_resampling_combo;
|
||||
ConfigStringChoice* m_pp_effect;
|
||||
ToolTipPushButton* m_configure_color_correction;
|
||||
QPushButton* m_configure_pp_effect;
|
||||
ConfigBool* m_scaled_efb_copy;
|
||||
|
@ -60,6 +73,5 @@ private:
|
|||
ConfigBool* m_3d_swap_eyes;
|
||||
ConfigBool* m_3d_per_eye_resolution;
|
||||
|
||||
int m_msaa_modes;
|
||||
bool m_block_save;
|
||||
Config::Layer* m_game_layer = nullptr;
|
||||
};
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include "DolphinQt/Config/ConfigControls/ConfigChoice.h"
|
||||
#include "DolphinQt/Config/ConfigControls/ConfigInteger.h"
|
||||
#include "DolphinQt/Config/ConfigControls/ConfigRadio.h"
|
||||
#include "DolphinQt/Config/GameConfigWidget.h"
|
||||
#include "DolphinQt/Config/Graphics/GraphicsWindow.h"
|
||||
#include "DolphinQt/Config/ToolTipControls/ToolTipComboBox.h"
|
||||
#include "DolphinQt/QtUtils/ModalMessageBox.h"
|
||||
|
@ -38,7 +39,6 @@ GeneralWidget::GeneralWidget(GraphicsWindow* parent)
|
|||
LoadSettings();
|
||||
ConnectWidgets();
|
||||
AddDescriptions();
|
||||
emit BackendChanged(QString::fromStdString(Config::Get(Config::MAIN_GFX_BACKEND)));
|
||||
|
||||
connect(parent, &GraphicsWindow::BackendChanged, this, &GeneralWidget::OnBackendChanged);
|
||||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, [this](Core::State state) {
|
||||
|
@ -47,6 +47,14 @@ GeneralWidget::GeneralWidget(GraphicsWindow* parent)
|
|||
OnEmulationStateChanged(!Core::IsUninitialized(Core::System::GetInstance()));
|
||||
}
|
||||
|
||||
GeneralWidget::GeneralWidget(GameConfigWidget* parent, Config::Layer* layer) : m_game_layer(layer)
|
||||
{
|
||||
CreateWidgets();
|
||||
LoadSettings();
|
||||
ConnectWidgets();
|
||||
AddDescriptions();
|
||||
}
|
||||
|
||||
void GeneralWidget::CreateWidgets()
|
||||
{
|
||||
auto* main_layout = new QVBoxLayout;
|
||||
|
@ -55,33 +63,36 @@ void GeneralWidget::CreateWidgets()
|
|||
auto* m_video_box = new QGroupBox(tr("Basic"));
|
||||
m_video_layout = new QGridLayout();
|
||||
|
||||
m_backend_combo = new ToolTipComboBox();
|
||||
std::vector<std::pair<QString, QString>> options;
|
||||
for (auto& backend : VideoBackendBase::GetAvailableBackends())
|
||||
{
|
||||
options.push_back(std::make_pair(tr(backend->GetDisplayName().c_str()),
|
||||
QString::fromStdString(backend->GetName())));
|
||||
}
|
||||
m_backend_combo = new ConfigStringChoice(options, Config::MAIN_GFX_BACKEND, m_game_layer);
|
||||
m_previous_backend = m_backend_combo->currentIndex();
|
||||
|
||||
m_aspect_combo = new ConfigChoice({tr("Auto"), tr("Force 16:9"), tr("Force 4:3"),
|
||||
tr("Stretch to Window"), tr("Custom"), tr("Custom (Stretch)")},
|
||||
Config::GFX_ASPECT_RATIO);
|
||||
Config::GFX_ASPECT_RATIO, m_game_layer);
|
||||
m_custom_aspect_label = new QLabel(tr("Custom Aspect Ratio:"));
|
||||
m_custom_aspect_label->setHidden(true);
|
||||
constexpr int MAX_CUSTOM_ASPECT_RATIO_RESOLUTION = 10000;
|
||||
m_custom_aspect_width = new ConfigInteger(1, MAX_CUSTOM_ASPECT_RATIO_RESOLUTION,
|
||||
Config::GFX_CUSTOM_ASPECT_RATIO_WIDTH);
|
||||
Config::GFX_CUSTOM_ASPECT_RATIO_WIDTH, m_game_layer);
|
||||
m_custom_aspect_width->setEnabled(false);
|
||||
m_custom_aspect_width->setHidden(true);
|
||||
m_custom_aspect_height = new ConfigInteger(1, MAX_CUSTOM_ASPECT_RATIO_RESOLUTION,
|
||||
Config::GFX_CUSTOM_ASPECT_RATIO_HEIGHT);
|
||||
Config::GFX_CUSTOM_ASPECT_RATIO_HEIGHT, m_game_layer);
|
||||
m_custom_aspect_height->setEnabled(false);
|
||||
m_custom_aspect_height->setHidden(true);
|
||||
m_adapter_combo = new ToolTipComboBox;
|
||||
m_enable_vsync = new ConfigBool(tr("V-Sync"), Config::GFX_VSYNC);
|
||||
m_enable_fullscreen = new ConfigBool(tr("Start in Fullscreen"), Config::MAIN_FULLSCREEN);
|
||||
m_enable_vsync = new ConfigBool(tr("V-Sync"), Config::GFX_VSYNC, m_game_layer);
|
||||
m_enable_fullscreen =
|
||||
new ConfigBool(tr("Start in Fullscreen"), Config::MAIN_FULLSCREEN, m_game_layer);
|
||||
|
||||
m_video_box->setLayout(m_video_layout);
|
||||
|
||||
for (auto& backend : VideoBackendBase::GetAvailableBackends())
|
||||
{
|
||||
m_backend_combo->addItem(tr(backend->GetDisplayName().c_str()),
|
||||
QVariant(QString::fromStdString(backend->GetName())));
|
||||
}
|
||||
|
||||
m_video_layout->addWidget(new QLabel(tr("Backend:")), 0, 0);
|
||||
m_video_layout->addWidget(m_backend_combo, 0, 1, 1, -1);
|
||||
|
||||
|
@ -102,12 +113,15 @@ void GeneralWidget::CreateWidgets()
|
|||
auto* m_options_box = new QGroupBox(tr("Other"));
|
||||
auto* m_options_layout = new QGridLayout();
|
||||
|
||||
m_show_ping = new ConfigBool(tr("Show NetPlay Ping"), Config::GFX_SHOW_NETPLAY_PING);
|
||||
m_show_turn_count = new ConfigBool(tr("Show MP Turn"), Config::GFX_SHOW_MP_TURN);
|
||||
m_autoadjust_window_size =
|
||||
new ConfigBool(tr("Auto-Adjust Window Size"), Config::MAIN_RENDER_WINDOW_AUTOSIZE);
|
||||
m_show_messages = new ConfigBool(tr("Show NetPlay Messages"), Config::GFX_SHOW_NETPLAY_MESSAGES);
|
||||
m_render_main_window = new ConfigBool(tr("Render to Main Window"), Config::MAIN_RENDER_TO_MAIN);
|
||||
m_show_ping =
|
||||
new ConfigBool(tr("Show NetPlay Ping"), Config::GFX_SHOW_NETPLAY_PING, m_game_layer);
|
||||
m_autoadjust_window_size = new ConfigBool(tr("Auto-Adjust Window Size"),
|
||||
Config::MAIN_RENDER_WINDOW_AUTOSIZE, m_game_layer);
|
||||
m_show_turn_count = new ConfigBool(tr("Show MP Turn"), Config::GFX_SHOW_MP_TURN. m_game_layer);
|
||||
m_show_messages =
|
||||
new ConfigBool(tr("Show NetPlay Messages"), Config::GFX_SHOW_NETPLAY_MESSAGES, m_game_layer);
|
||||
m_render_main_window =
|
||||
new ConfigBool(tr("Render to Main Window"), Config::MAIN_RENDER_TO_MAIN, m_game_layer);
|
||||
|
||||
m_options_box->setLayout(m_options_layout);
|
||||
|
||||
|
@ -130,13 +144,13 @@ void GeneralWidget::CreateWidgets()
|
|||
}};
|
||||
for (size_t i = 0; i < modes.size(); i++)
|
||||
{
|
||||
m_shader_compilation_mode[i] =
|
||||
new ConfigRadioInt(tr(modes[i]), Config::GFX_SHADER_COMPILATION_MODE, static_cast<int>(i));
|
||||
m_shader_compilation_mode[i] = new ConfigRadioInt(
|
||||
tr(modes[i]), Config::GFX_SHADER_COMPILATION_MODE, static_cast<int>(i), m_game_layer);
|
||||
shader_compilation_layout->addWidget(m_shader_compilation_mode[i], static_cast<int>(i / 2),
|
||||
static_cast<int>(i % 2));
|
||||
}
|
||||
m_wait_for_shaders = new ConfigBool(tr("Compile Shaders Before Starting"),
|
||||
Config::GFX_WAIT_FOR_SHADERS_BEFORE_STARTING);
|
||||
Config::GFX_WAIT_FOR_SHADERS_BEFORE_STARTING, m_game_layer);
|
||||
shader_compilation_layout->addWidget(m_wait_for_shaders);
|
||||
shader_compilation_box->setLayout(shader_compilation_layout);
|
||||
|
||||
|
@ -151,7 +165,7 @@ void GeneralWidget::CreateWidgets()
|
|||
void GeneralWidget::ConnectWidgets()
|
||||
{
|
||||
// Video Backend
|
||||
connect(m_backend_combo, &QComboBox::currentIndexChanged, this, &GeneralWidget::SaveSettings);
|
||||
connect(m_backend_combo, &QComboBox::currentIndexChanged, this, &GeneralWidget::BackendWarning);
|
||||
connect(m_adapter_combo, &QComboBox::currentIndexChanged, this, [&](int index) {
|
||||
g_Config.iAdapter = index;
|
||||
Config::SetBaseOrCurrent(Config::GFX_ADAPTER, index);
|
||||
|
@ -170,10 +184,6 @@ void GeneralWidget::ConnectWidgets()
|
|||
|
||||
void GeneralWidget::LoadSettings()
|
||||
{
|
||||
// Video Backend
|
||||
m_backend_combo->setCurrentIndex(m_backend_combo->findData(
|
||||
QVariant(QString::fromStdString(Config::Get(Config::MAIN_GFX_BACKEND)))));
|
||||
|
||||
const bool is_custom_aspect_ratio =
|
||||
(Config::Get(Config::GFX_ASPECT_RATIO) == AspectMode::Custom) ||
|
||||
(Config::Get(Config::GFX_ASPECT_RATIO) == AspectMode::CustomStretch);
|
||||
|
@ -184,13 +194,8 @@ void GeneralWidget::LoadSettings()
|
|||
m_custom_aspect_height->setHidden(!is_custom_aspect_ratio);
|
||||
}
|
||||
|
||||
void GeneralWidget::SaveSettings()
|
||||
void GeneralWidget::BackendWarning()
|
||||
{
|
||||
// Video Backend
|
||||
const auto current_backend = m_backend_combo->currentData().toString().toStdString();
|
||||
if (Config::Get(Config::MAIN_GFX_BACKEND) == current_backend)
|
||||
return;
|
||||
|
||||
if (Config::GetActiveLayerForConfig(Config::MAIN_GFX_BACKEND) == Config::LayerType::Base)
|
||||
{
|
||||
auto warningMessage = VideoBackendBase::GetAvailableBackends()[m_backend_combo->currentIndex()]
|
||||
|
@ -207,15 +212,14 @@ void GeneralWidget::SaveSettings()
|
|||
SetQWidgetWindowDecorations(&confirm_sw);
|
||||
if (confirm_sw.exec() != QMessageBox::Yes)
|
||||
{
|
||||
m_backend_combo->setCurrentIndex(m_backend_combo->findData(
|
||||
QVariant(QString::fromStdString(Config::Get(Config::MAIN_GFX_BACKEND)))));
|
||||
m_backend_combo->setCurrentIndex(m_previous_backend);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Config::SetBaseOrCurrent(Config::MAIN_GFX_BACKEND, current_backend);
|
||||
emit BackendChanged(QString::fromStdString(current_backend));
|
||||
m_previous_backend = m_backend_combo->currentIndex();
|
||||
emit BackendChanged(m_backend_combo->currentData().toString());
|
||||
}
|
||||
|
||||
void GeneralWidget::OnEmulationStateChanged(bool running)
|
||||
|
@ -229,7 +233,10 @@ void GeneralWidget::OnEmulationStateChanged(bool running)
|
|||
|
||||
std::string current_backend = m_backend_combo->currentData().toString().toStdString();
|
||||
if (Config::Get(Config::MAIN_GFX_BACKEND) != current_backend)
|
||||
{
|
||||
m_backend_combo->Load();
|
||||
emit BackendChanged(QString::fromStdString(Config::Get(Config::MAIN_GFX_BACKEND)));
|
||||
}
|
||||
}
|
||||
|
||||
void GeneralWidget::AddDescriptions()
|
||||
|
@ -241,7 +248,7 @@ void GeneralWidget::AddDescriptions()
|
|||
"recommended. Different games and different GPUs will behave differently on each "
|
||||
"backend, so for the best emulation experience it is recommended to try each and "
|
||||
"select the backend that is least problematic.<br><br><dolphin_emphasis>If unsure, "
|
||||
"select OpenGL.</dolphin_emphasis>");
|
||||
"select %1.</dolphin_emphasis>");
|
||||
static const char TR_FULLSCREEN_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Uses the entire screen for rendering.<br><br>If disabled, a "
|
||||
"render window will be created instead.<br><br><dolphin_emphasis>If "
|
||||
|
@ -313,7 +320,9 @@ void GeneralWidget::AddDescriptions()
|
|||
"unsure, leave this unchecked.</dolphin_emphasis>");
|
||||
|
||||
m_backend_combo->SetTitle(tr("Backend"));
|
||||
m_backend_combo->SetDescription(tr(TR_BACKEND_DESCRIPTION));
|
||||
m_backend_combo->SetDescription(
|
||||
tr(TR_BACKEND_DESCRIPTION)
|
||||
.arg(QString::fromStdString(VideoBackendBase::GetDefaultBackendDisplayName())));
|
||||
|
||||
m_adapter_combo->SetTitle(tr("Adapter"));
|
||||
|
||||
|
@ -348,8 +357,6 @@ void GeneralWidget::AddDescriptions()
|
|||
|
||||
void GeneralWidget::OnBackendChanged(const QString& backend_name)
|
||||
{
|
||||
m_backend_combo->setCurrentIndex(m_backend_combo->findData(QVariant(backend_name)));
|
||||
|
||||
const QSignalBlocker blocker(m_adapter_combo);
|
||||
|
||||
m_adapter_combo->clear();
|
||||
|
|
|
@ -11,6 +11,8 @@ class ConfigBool;
|
|||
class ConfigChoice;
|
||||
class ConfigInteger;
|
||||
class ConfigRadioInt;
|
||||
class ConfigStringChoice;
|
||||
class GameConfigWidget;
|
||||
class GraphicsWindow;
|
||||
class QCheckBox;
|
||||
class QComboBox;
|
||||
|
@ -19,17 +21,24 @@ class QRadioButton;
|
|||
class QGridLayout;
|
||||
class ToolTipComboBox;
|
||||
|
||||
namespace Config
|
||||
{
|
||||
class Layer;
|
||||
} // namespace Config
|
||||
|
||||
class GeneralWidget final : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit GeneralWidget(GraphicsWindow* parent);
|
||||
GeneralWidget(GameConfigWidget* parent, Config::Layer* layer);
|
||||
|
||||
signals:
|
||||
void BackendChanged(const QString& backend);
|
||||
|
||||
private:
|
||||
void LoadSettings();
|
||||
void SaveSettings();
|
||||
void BackendWarning();
|
||||
|
||||
void CreateWidgets();
|
||||
void ConnectWidgets();
|
||||
|
@ -40,7 +49,7 @@ private:
|
|||
|
||||
// Video
|
||||
QGridLayout* m_video_layout;
|
||||
ToolTipComboBox* m_backend_combo;
|
||||
ConfigStringChoice* m_backend_combo;
|
||||
ToolTipComboBox* m_adapter_combo;
|
||||
ConfigChoice* m_aspect_combo;
|
||||
QLabel* m_custom_aspect_label;
|
||||
|
@ -57,4 +66,6 @@ private:
|
|||
ConfigBool* m_render_main_window;
|
||||
std::array<ConfigRadioInt*, 4> m_shader_compilation_mode{};
|
||||
ConfigBool* m_wait_for_shaders;
|
||||
int m_previous_backend = 0;
|
||||
Config::Layer* m_game_layer = nullptr;
|
||||
};
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
#include "DolphinQt/Config/ConfigControls/ConfigBool.h"
|
||||
#include "DolphinQt/Config/ConfigControls/ConfigSlider.h"
|
||||
#include "DolphinQt/Config/GameConfigWidget.h"
|
||||
#include "DolphinQt/Config/Graphics/GraphicsWindow.h"
|
||||
#include "DolphinQt/Config/ToolTipControls/ToolTipSlider.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
@ -37,6 +38,14 @@ HacksWidget::HacksWidget(GraphicsWindow* parent)
|
|||
});
|
||||
}
|
||||
|
||||
HacksWidget::HacksWidget(GameConfigWidget* parent, Config::Layer* layer) : m_game_layer(layer)
|
||||
{
|
||||
CreateWidgets();
|
||||
LoadSettings();
|
||||
ConnectWidgets();
|
||||
AddDescriptions();
|
||||
}
|
||||
|
||||
void HacksWidget::CreateWidgets()
|
||||
{
|
||||
auto* main_layout = new QVBoxLayout;
|
||||
|
@ -45,14 +54,14 @@ void HacksWidget::CreateWidgets()
|
|||
auto* efb_box = new QGroupBox(tr("Embedded Frame Buffer (EFB)"));
|
||||
auto* efb_layout = new QGridLayout();
|
||||
efb_box->setLayout(efb_layout);
|
||||
m_skip_efb_cpu =
|
||||
new ConfigBool(tr("Skip EFB Access from CPU"), Config::GFX_HACK_EFB_ACCESS_ENABLE, true);
|
||||
m_ignore_format_changes = new ConfigBool(tr("Ignore Format Changes"),
|
||||
Config::GFX_HACK_EFB_EMULATE_FORMAT_CHANGES, true);
|
||||
m_store_efb_copies =
|
||||
new ConfigBool(tr("Store EFB Copies to Texture Only"), Config::GFX_HACK_SKIP_EFB_COPY_TO_RAM);
|
||||
m_defer_efb_copies =
|
||||
new ConfigBool(tr("Defer EFB Copies to RAM"), Config::GFX_HACK_DEFER_EFB_COPIES);
|
||||
m_skip_efb_cpu = new ConfigBool(tr("Skip EFB Access from CPU"),
|
||||
Config::GFX_HACK_EFB_ACCESS_ENABLE, m_game_layer, true);
|
||||
m_ignore_format_changes = new ConfigBool(
|
||||
tr("Ignore Format Changes"), Config::GFX_HACK_EFB_EMULATE_FORMAT_CHANGES, m_game_layer, true);
|
||||
m_store_efb_copies = new ConfigBool(tr("Store EFB Copies to Texture Only"),
|
||||
Config::GFX_HACK_SKIP_EFB_COPY_TO_RAM, m_game_layer);
|
||||
m_defer_efb_copies = new ConfigBool(tr("Defer EFB Copies to RAM"),
|
||||
Config::GFX_HACK_DEFER_EFB_COPIES, m_game_layer);
|
||||
|
||||
efb_layout->addWidget(m_skip_efb_cpu, 0, 0);
|
||||
efb_layout->addWidget(m_ignore_format_changes, 0, 1);
|
||||
|
@ -69,8 +78,8 @@ void HacksWidget::CreateWidgets()
|
|||
m_accuracy->setMaximum(2);
|
||||
m_accuracy->setPageStep(1);
|
||||
m_accuracy->setTickPosition(QSlider::TicksBelow);
|
||||
m_gpu_texture_decoding =
|
||||
new ConfigBool(tr("GPU Texture Decoding"), Config::GFX_ENABLE_GPU_TEXTURE_DECODING);
|
||||
m_gpu_texture_decoding = new ConfigBool(tr("GPU Texture Decoding"),
|
||||
Config::GFX_ENABLE_GPU_TEXTURE_DECODING, m_game_layer);
|
||||
|
||||
auto* safe_label = new QLabel(tr("Safe"));
|
||||
safe_label->setAlignment(Qt::AlignRight);
|
||||
|
@ -88,11 +97,12 @@ void HacksWidget::CreateWidgets()
|
|||
auto* xfb_layout = new QVBoxLayout();
|
||||
xfb_box->setLayout(xfb_layout);
|
||||
|
||||
m_store_xfb_copies =
|
||||
new ConfigBool(tr("Store XFB Copies to Texture Only"), Config::GFX_HACK_SKIP_XFB_COPY_TO_RAM);
|
||||
m_immediate_xfb = new ConfigBool(tr("Immediately Present XFB"), Config::GFX_HACK_IMMEDIATE_XFB);
|
||||
m_skip_duplicate_xfbs =
|
||||
new ConfigBool(tr("Skip Presenting Duplicate Frames"), Config::GFX_HACK_SKIP_DUPLICATE_XFBS);
|
||||
m_store_xfb_copies = new ConfigBool(tr("Store XFB Copies to Texture Only"),
|
||||
Config::GFX_HACK_SKIP_XFB_COPY_TO_RAM, m_game_layer);
|
||||
m_immediate_xfb =
|
||||
new ConfigBool(tr("Immediately Present XFB"), Config::GFX_HACK_IMMEDIATE_XFB, m_game_layer);
|
||||
m_skip_duplicate_xfbs = new ConfigBool(tr("Skip Presenting Duplicate Frames"),
|
||||
Config::GFX_HACK_SKIP_DUPLICATE_XFBS, m_game_layer);
|
||||
|
||||
xfb_layout->addWidget(m_store_xfb_copies);
|
||||
xfb_layout->addWidget(m_immediate_xfb);
|
||||
|
@ -104,13 +114,14 @@ void HacksWidget::CreateWidgets()
|
|||
other_box->setLayout(other_layout);
|
||||
|
||||
m_fast_depth_calculation =
|
||||
new ConfigBool(tr("Fast Depth Calculation"), Config::GFX_FAST_DEPTH_CALC);
|
||||
new ConfigBool(tr("Fast Depth Calculation"), Config::GFX_FAST_DEPTH_CALC, m_game_layer);
|
||||
m_disable_bounding_box =
|
||||
new ConfigBool(tr("Disable Bounding Box"), Config::GFX_HACK_BBOX_ENABLE, true);
|
||||
m_vertex_rounding = new ConfigBool(tr("Vertex Rounding"), Config::GFX_HACK_VERTEX_ROUNDING);
|
||||
m_save_texture_cache_state =
|
||||
new ConfigBool(tr("Save Texture Cache to State"), Config::GFX_SAVE_TEXTURE_CACHE_TO_STATE);
|
||||
m_vi_skip = new ConfigBool(tr("VBI Skip"), Config::GFX_HACK_VI_SKIP);
|
||||
new ConfigBool(tr("Disable Bounding Box"), Config::GFX_HACK_BBOX_ENABLE, m_game_layer, true);
|
||||
m_vertex_rounding =
|
||||
new ConfigBool(tr("Vertex Rounding"), Config::GFX_HACK_VERTEX_ROUNDING, m_game_layer);
|
||||
m_save_texture_cache_state = new ConfigBool(
|
||||
tr("Save Texture Cache to State"), Config::GFX_SAVE_TEXTURE_CACHE_TO_STATE, m_game_layer);
|
||||
m_vi_skip = new ConfigBool(tr("VBI Skip"), Config::GFX_HACK_VI_SKIP, m_game_layer);
|
||||
|
||||
other_layout->addWidget(m_fast_depth_calculation, 0, 0);
|
||||
other_layout->addWidget(m_disable_bounding_box, 0, 1);
|
||||
|
@ -223,7 +234,7 @@ void HacksWidget::AddDescriptions()
|
|||
"Ignores any requests from the CPU to read from or write to the EFB. "
|
||||
"<br><br>Improves performance in some games, but will disable all EFB-based "
|
||||
"graphical effects or gameplay-related features.<br><br><dolphin_emphasis>If unsure, "
|
||||
"leave this unchecked.</dolphin_emphasis>");
|
||||
"leave this checked.</dolphin_emphasis>");
|
||||
static const char TR_IGNORE_FORMAT_CHANGE_DESCRIPTION[] = QT_TR_NOOP(
|
||||
"Ignores any changes to the EFB format.<br><br>Improves performance in many games "
|
||||
"without "
|
||||
|
|
|
@ -6,15 +6,22 @@
|
|||
#include <QWidget>
|
||||
|
||||
class ConfigBool;
|
||||
class GameConfigWidget;
|
||||
class GraphicsWindow;
|
||||
class QLabel;
|
||||
class ToolTipSlider;
|
||||
|
||||
namespace Config
|
||||
{
|
||||
class Layer;
|
||||
} // namespace Config
|
||||
|
||||
class HacksWidget final : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit HacksWidget(GraphicsWindow* parent);
|
||||
HacksWidget(GameConfigWidget* parent, Config::Layer* layer);
|
||||
|
||||
private:
|
||||
void LoadSettings();
|
||||
|
@ -45,6 +52,8 @@ private:
|
|||
ConfigBool* m_vi_skip;
|
||||
ConfigBool* m_save_texture_cache_state;
|
||||
|
||||
Config::Layer* m_game_layer = nullptr;
|
||||
|
||||
void CreateWidgets();
|
||||
void ConnectWidgets();
|
||||
void AddDescriptions();
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue