Compare commits
43 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f325fcb131 | ||
|
|
de4afde065 | ||
|
|
7f0d0dd177 | ||
|
|
4e9adae5da | ||
|
|
f39d2cf78b | ||
|
|
d8ff939edc | ||
|
|
eec3184bb0 | ||
|
|
67e0d38152 | ||
|
|
e09756b2df | ||
|
|
8f00c59462 | ||
|
|
1cdd2d5204 | ||
|
|
ccfdb7c1af | ||
|
|
6f0f7f1547 | ||
|
|
316a2c1715 | ||
|
|
d867ae5ab6 | ||
|
|
c4d91488d9 | ||
|
|
72bff8ba11 | ||
|
|
84d15c7f47 | ||
|
|
dce242858a | ||
|
|
8ce6256722 | ||
|
|
3e6840a74c | ||
|
|
25d53e66d1 | ||
|
|
20118075c5 | ||
|
|
2cdfbbc07d | ||
|
|
cdb9fe978f | ||
|
|
86a3a0b1b4 | ||
|
|
f6e7cae62c | ||
|
|
2dd6a2352d | ||
|
|
e0ec9ffc36 | ||
|
|
041eb5bf57 | ||
|
|
8b4d5aeb4f | ||
|
|
d8e3380ea5 | ||
|
|
e59bd6c335 | ||
|
|
77803d96be | ||
|
|
fa9b7db76f | ||
|
|
165bce3c2d | ||
|
|
1a378a7769 | ||
|
|
1689e0a71f | ||
|
|
2ccbf5abdd | ||
|
|
3e0aaeba98 | ||
|
|
82fdfb33ac | ||
|
|
7f66050f0c | ||
|
|
470e89a8ed |
@@ -56,7 +56,7 @@ Result System::IsConfigValid(const std::string_view device_name,
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result System::Initialize(std::string& device_name, const AudioInParameter& in_params,
|
||||
Result System::Initialize(std::string device_name, const AudioInParameter& in_params,
|
||||
const u32 handle_, const u64 applet_resource_user_id_) {
|
||||
auto result{IsConfigValid(device_name, in_params)};
|
||||
if (result.IsError()) {
|
||||
|
||||
@@ -97,7 +97,7 @@ public:
|
||||
* @param applet_resource_user_id - Unused.
|
||||
* @return Result code.
|
||||
*/
|
||||
Result Initialize(std::string& device_name, const AudioInParameter& in_params, u32 handle,
|
||||
Result Initialize(std::string device_name, const AudioInParameter& in_params, u32 handle,
|
||||
u64 applet_resource_user_id);
|
||||
|
||||
/**
|
||||
|
||||
@@ -49,8 +49,8 @@ Result System::IsConfigValid(std::string_view device_name,
|
||||
return Service::Audio::ERR_INVALID_CHANNEL_COUNT;
|
||||
}
|
||||
|
||||
Result System::Initialize(std::string& device_name, const AudioOutParameter& in_params, u32 handle_,
|
||||
u64& applet_resource_user_id_) {
|
||||
Result System::Initialize(std::string device_name, const AudioOutParameter& in_params, u32 handle_,
|
||||
u64 applet_resource_user_id_) {
|
||||
auto result = IsConfigValid(device_name, in_params);
|
||||
if (result.IsError()) {
|
||||
return result;
|
||||
|
||||
@@ -88,8 +88,8 @@ public:
|
||||
* @param applet_resource_user_id - Unused.
|
||||
* @return Result code.
|
||||
*/
|
||||
Result Initialize(std::string& device_name, const AudioOutParameter& in_params, u32 handle,
|
||||
u64& applet_resource_user_id);
|
||||
Result Initialize(std::string device_name, const AudioOutParameter& in_params, u32 handle,
|
||||
u64 applet_resource_user_id);
|
||||
|
||||
/**
|
||||
* Start this system.
|
||||
|
||||
@@ -3,24 +3,14 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iterator>
|
||||
#include <type_traits>
|
||||
|
||||
namespace Common {
|
||||
|
||||
// Check if type is like an STL container
|
||||
// Check if type satisfies the ContiguousContainer named requirement.
|
||||
template <typename T>
|
||||
concept IsSTLContainer = requires(T t) {
|
||||
typename T::value_type;
|
||||
typename T::iterator;
|
||||
typename T::const_iterator;
|
||||
// TODO(ogniK): Replace below is std::same_as<void> when MSVC supports it.
|
||||
t.begin();
|
||||
t.end();
|
||||
t.cbegin();
|
||||
t.cend();
|
||||
t.data();
|
||||
t.size();
|
||||
};
|
||||
concept IsContiguousContainer = std::contiguous_iterator<typename T::iterator>;
|
||||
|
||||
// TODO: Replace with std::derived_from when the <concepts> header
|
||||
// is available on all supported platforms.
|
||||
|
||||
@@ -209,8 +209,8 @@ public:
|
||||
|
||||
/**
|
||||
* Helper function which deduces the value type of a contiguous STL container used in ReadSpan.
|
||||
* If T is not a contiguous STL container as defined by the concept IsSTLContainer, this calls
|
||||
* ReadObject and T must be a trivially copyable object.
|
||||
* If T is not a contiguous container as defined by the concept IsContiguousContainer, this
|
||||
* calls ReadObject and T must be a trivially copyable object.
|
||||
*
|
||||
* See ReadSpan for more details if T is a contiguous container.
|
||||
* See ReadObject for more details if T is a trivially copyable object.
|
||||
@@ -223,7 +223,7 @@ public:
|
||||
*/
|
||||
template <typename T>
|
||||
[[nodiscard]] size_t Read(T& data) const {
|
||||
if constexpr (IsSTLContainer<T>) {
|
||||
if constexpr (IsContiguousContainer<T>) {
|
||||
using ContiguousType = typename T::value_type;
|
||||
static_assert(std::is_trivially_copyable_v<ContiguousType>,
|
||||
"Data type must be trivially copyable.");
|
||||
@@ -235,8 +235,8 @@ public:
|
||||
|
||||
/**
|
||||
* Helper function which deduces the value type of a contiguous STL container used in WriteSpan.
|
||||
* If T is not a contiguous STL container as defined by the concept IsSTLContainer, this calls
|
||||
* WriteObject and T must be a trivially copyable object.
|
||||
* If T is not a contiguous STL container as defined by the concept IsContiguousContainer, this
|
||||
* calls WriteObject and T must be a trivially copyable object.
|
||||
*
|
||||
* See WriteSpan for more details if T is a contiguous container.
|
||||
* See WriteObject for more details if T is a trivially copyable object.
|
||||
@@ -249,7 +249,7 @@ public:
|
||||
*/
|
||||
template <typename T>
|
||||
[[nodiscard]] size_t Write(const T& data) const {
|
||||
if constexpr (IsSTLContainer<T>) {
|
||||
if constexpr (IsContiguousContainer<T>) {
|
||||
using ContiguousType = typename T::value_type;
|
||||
static_assert(std::is_trivially_copyable_v<ContiguousType>,
|
||||
"Data type must be trivially copyable.");
|
||||
|
||||
@@ -100,7 +100,6 @@ enum class CameraError {
|
||||
enum class VibrationAmplificationType {
|
||||
Linear,
|
||||
Exponential,
|
||||
Test,
|
||||
};
|
||||
|
||||
// Analog properties for calibration
|
||||
@@ -325,6 +324,10 @@ public:
|
||||
return VibrationError::NotSupported;
|
||||
}
|
||||
|
||||
virtual bool IsVibrationEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual PollingError SetPollingMode([[maybe_unused]] PollingMode polling_mode) {
|
||||
return PollingError::NotSupported;
|
||||
}
|
||||
|
||||
@@ -450,7 +450,7 @@ std::vector<ARM_Interface::BacktraceEntry> ARM_Dynarmic_32::GetBacktrace(Core::S
|
||||
// Frame records are two words long:
|
||||
// fp+0 : pointer to previous frame record
|
||||
// fp+4 : value of lr for frame
|
||||
while (true) {
|
||||
for (size_t i = 0; i < 256; i++) {
|
||||
out.push_back({"", 0, lr, 0, ""});
|
||||
if (!fp || (fp % 4 != 0) || !memory.IsValidVirtualAddressRange(fp, 8)) {
|
||||
break;
|
||||
|
||||
@@ -517,7 +517,7 @@ std::vector<ARM_Interface::BacktraceEntry> ARM_Dynarmic_64::GetBacktrace(Core::S
|
||||
// Frame records are two words long:
|
||||
// fp+0 : pointer to previous frame record
|
||||
// fp+8 : value of lr for frame
|
||||
while (true) {
|
||||
for (size_t i = 0; i < 256; i++) {
|
||||
out.push_back({"", 0, lr, 0, ""});
|
||||
if (!fp || (fp % 4 != 0) || !memory.IsValidVirtualAddressRange(fp, 16)) {
|
||||
break;
|
||||
|
||||
@@ -137,6 +137,7 @@ struct System::Impl {
|
||||
device_memory = std::make_unique<Core::DeviceMemory>();
|
||||
|
||||
is_multicore = Settings::values.use_multi_core.GetValue();
|
||||
extended_memory_layout = Settings::values.use_extended_memory_layout.GetValue();
|
||||
|
||||
core_timing.SetMulticore(is_multicore);
|
||||
core_timing.Initialize([&system]() { system.RegisterHostThread(); });
|
||||
@@ -166,13 +167,18 @@ struct System::Impl {
|
||||
}
|
||||
|
||||
void ReinitializeIfNecessary(System& system) {
|
||||
if (is_multicore == Settings::values.use_multi_core.GetValue()) {
|
||||
const bool must_reinitialize =
|
||||
is_multicore != Settings::values.use_multi_core.GetValue() ||
|
||||
extended_memory_layout != Settings::values.use_extended_memory_layout.GetValue();
|
||||
|
||||
if (!must_reinitialize) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Kernel, "Re-initializing");
|
||||
|
||||
is_multicore = Settings::values.use_multi_core.GetValue();
|
||||
extended_memory_layout = Settings::values.use_extended_memory_layout.GetValue();
|
||||
|
||||
Initialize(system);
|
||||
}
|
||||
@@ -521,6 +527,7 @@ struct System::Impl {
|
||||
|
||||
bool is_multicore{};
|
||||
bool is_async_gpu{};
|
||||
bool extended_memory_layout{};
|
||||
|
||||
ExecuteProgramCallback execute_program_callback;
|
||||
ExitCallback exit_callback;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/settings.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
@@ -37,6 +38,27 @@ std::string LanguageEntry::GetDeveloperName() const {
|
||||
developer_name.size());
|
||||
}
|
||||
|
||||
constexpr std::array<Language, 18> language_to_codes = {{
|
||||
Language::Japanese,
|
||||
Language::AmericanEnglish,
|
||||
Language::French,
|
||||
Language::German,
|
||||
Language::Italian,
|
||||
Language::Spanish,
|
||||
Language::Chinese,
|
||||
Language::Korean,
|
||||
Language::Dutch,
|
||||
Language::Portuguese,
|
||||
Language::Russian,
|
||||
Language::Taiwanese,
|
||||
Language::BritishEnglish,
|
||||
Language::CanadianFrench,
|
||||
Language::LatinAmericanSpanish,
|
||||
Language::Chinese,
|
||||
Language::Taiwanese,
|
||||
Language::BrazilianPortuguese,
|
||||
}};
|
||||
|
||||
NACP::NACP() = default;
|
||||
|
||||
NACP::NACP(VirtualFile file) {
|
||||
@@ -45,9 +67,13 @@ NACP::NACP(VirtualFile file) {
|
||||
|
||||
NACP::~NACP() = default;
|
||||
|
||||
const LanguageEntry& NACP::GetLanguageEntry(Language language) const {
|
||||
if (language != Language::Default) {
|
||||
return raw.language_entries.at(static_cast<u8>(language));
|
||||
const LanguageEntry& NACP::GetLanguageEntry() const {
|
||||
Language language = language_to_codes[Settings::values.language_index.GetValue()];
|
||||
|
||||
{
|
||||
const auto& language_entry = raw.language_entries.at(static_cast<u8>(language));
|
||||
if (!language_entry.GetApplicationName().empty())
|
||||
return language_entry;
|
||||
}
|
||||
|
||||
for (const auto& language_entry : raw.language_entries) {
|
||||
@@ -55,16 +81,15 @@ const LanguageEntry& NACP::GetLanguageEntry(Language language) const {
|
||||
return language_entry;
|
||||
}
|
||||
|
||||
// Fallback to English
|
||||
return GetLanguageEntry(Language::AmericanEnglish);
|
||||
return raw.language_entries.at(static_cast<u8>(Language::AmericanEnglish));
|
||||
}
|
||||
|
||||
std::string NACP::GetApplicationName(Language language) const {
|
||||
return GetLanguageEntry(language).GetApplicationName();
|
||||
std::string NACP::GetApplicationName() const {
|
||||
return GetLanguageEntry().GetApplicationName();
|
||||
}
|
||||
|
||||
std::string NACP::GetDeveloperName(Language language) const {
|
||||
return GetLanguageEntry(language).GetDeveloperName();
|
||||
std::string NACP::GetDeveloperName() const {
|
||||
return GetLanguageEntry().GetDeveloperName();
|
||||
}
|
||||
|
||||
u64 NACP::GetTitleId() const {
|
||||
|
||||
@@ -101,9 +101,9 @@ public:
|
||||
explicit NACP(VirtualFile file);
|
||||
~NACP();
|
||||
|
||||
const LanguageEntry& GetLanguageEntry(Language language = Language::Default) const;
|
||||
std::string GetApplicationName(Language language = Language::Default) const;
|
||||
std::string GetDeveloperName(Language language = Language::Default) const;
|
||||
const LanguageEntry& GetLanguageEntry() const;
|
||||
std::string GetApplicationName() const;
|
||||
std::string GetDeveloperName() const;
|
||||
u64 GetTitleId() const;
|
||||
u64 GetDLCBaseTitleId() const;
|
||||
std::string GetVersionString() const;
|
||||
|
||||
@@ -970,14 +970,7 @@ bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue v
|
||||
Common::Input::VibrationError::None;
|
||||
}
|
||||
|
||||
bool EmulatedController::TestVibration(std::size_t device_index) {
|
||||
if (device_index >= output_devices.size()) {
|
||||
return false;
|
||||
}
|
||||
if (!output_devices[device_index]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EmulatedController::IsVibrationEnabled(std::size_t device_index) {
|
||||
const auto player_index = NpadIdTypeToIndex(npad_id_type);
|
||||
const auto& player = Settings::values.players.GetValue()[player_index];
|
||||
|
||||
@@ -985,31 +978,15 @@ bool EmulatedController::TestVibration(std::size_t device_index) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Common::Input::VibrationStatus test_vibration = {
|
||||
.low_amplitude = 0.001f,
|
||||
.low_frequency = DEFAULT_VIBRATION_VALUE.low_frequency,
|
||||
.high_amplitude = 0.001f,
|
||||
.high_frequency = DEFAULT_VIBRATION_VALUE.high_frequency,
|
||||
.type = Common::Input::VibrationAmplificationType::Test,
|
||||
};
|
||||
if (device_index >= output_devices.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Common::Input::VibrationStatus zero_vibration = {
|
||||
.low_amplitude = DEFAULT_VIBRATION_VALUE.low_amplitude,
|
||||
.low_frequency = DEFAULT_VIBRATION_VALUE.low_frequency,
|
||||
.high_amplitude = DEFAULT_VIBRATION_VALUE.high_amplitude,
|
||||
.high_frequency = DEFAULT_VIBRATION_VALUE.high_frequency,
|
||||
.type = Common::Input::VibrationAmplificationType::Test,
|
||||
};
|
||||
if (!output_devices[device_index]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Send a slight vibration to test for rumble support
|
||||
output_devices[device_index]->SetVibration(test_vibration);
|
||||
|
||||
// Wait for about 15ms to ensure the controller is ready for the stop command
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(15));
|
||||
|
||||
// Stop any vibration and return the result
|
||||
return output_devices[device_index]->SetVibration(zero_vibration) ==
|
||||
Common::Input::VibrationError::None;
|
||||
return output_devices[device_index]->IsVibrationEnabled();
|
||||
}
|
||||
|
||||
bool EmulatedController::SetPollingMode(Common::Input::PollingMode polling_mode) {
|
||||
@@ -1048,6 +1025,7 @@ bool EmulatedController::HasNfc() const {
|
||||
case NpadStyleIndex::JoyconRight:
|
||||
case NpadStyleIndex::JoyconDual:
|
||||
case NpadStyleIndex::ProController:
|
||||
case NpadStyleIndex::Handheld:
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
@@ -1234,12 +1212,6 @@ bool EmulatedController::IsConnected(bool get_temporary_value) const {
|
||||
return is_connected;
|
||||
}
|
||||
|
||||
bool EmulatedController::IsVibrationEnabled() const {
|
||||
const auto player_index = NpadIdTypeToIndex(npad_id_type);
|
||||
const auto& player = Settings::values.players.GetValue()[player_index];
|
||||
return player.vibration_enabled;
|
||||
}
|
||||
|
||||
NpadIdType EmulatedController::GetNpadIdType() const {
|
||||
std::scoped_lock lock{mutex};
|
||||
return npad_id_type;
|
||||
|
||||
@@ -206,9 +206,6 @@ public:
|
||||
*/
|
||||
bool IsConnected(bool get_temporary_value = false) const;
|
||||
|
||||
/// Returns true if vibration is enabled
|
||||
bool IsVibrationEnabled() const;
|
||||
|
||||
/// Removes all callbacks created from input devices
|
||||
void UnloadInput();
|
||||
|
||||
@@ -339,7 +336,7 @@ public:
|
||||
* Sends a small vibration to the output device
|
||||
* @return true if SetVibration was successfull
|
||||
*/
|
||||
bool TestVibration(std::size_t device_index);
|
||||
bool IsVibrationEnabled(std::size_t device_index);
|
||||
|
||||
/**
|
||||
* Sets the desired data to be polled from a controller
|
||||
|
||||
@@ -49,4 +49,26 @@ bool GlobalSchedulerContext::IsLocked() const {
|
||||
return scheduler_lock.IsLockedByCurrentThread();
|
||||
}
|
||||
|
||||
void GlobalSchedulerContext::RegisterDummyThreadForWakeup(KThread* thread) {
|
||||
ASSERT(IsLocked());
|
||||
|
||||
woken_dummy_threads.insert(thread);
|
||||
}
|
||||
|
||||
void GlobalSchedulerContext::UnregisterDummyThreadForWakeup(KThread* thread) {
|
||||
ASSERT(IsLocked());
|
||||
|
||||
woken_dummy_threads.erase(thread);
|
||||
}
|
||||
|
||||
void GlobalSchedulerContext::WakeupWaitingDummyThreads() {
|
||||
ASSERT(IsLocked());
|
||||
|
||||
for (auto* thread : woken_dummy_threads) {
|
||||
thread->DummyThreadEndWait();
|
||||
}
|
||||
|
||||
woken_dummy_threads.clear();
|
||||
}
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
@@ -58,6 +59,10 @@ public:
|
||||
/// Returns true if the global scheduler lock is acquired
|
||||
bool IsLocked() const;
|
||||
|
||||
void UnregisterDummyThreadForWakeup(KThread* thread);
|
||||
void RegisterDummyThreadForWakeup(KThread* thread);
|
||||
void WakeupWaitingDummyThreads();
|
||||
|
||||
[[nodiscard]] LockType& SchedulerLock() {
|
||||
return scheduler_lock;
|
||||
}
|
||||
@@ -76,6 +81,9 @@ private:
|
||||
KSchedulerPriorityQueue priority_queue;
|
||||
LockType scheduler_lock;
|
||||
|
||||
/// Lists dummy threads pending wakeup on lock release
|
||||
std::set<KThread*> woken_dummy_threads;
|
||||
|
||||
/// Lists all thread ids that aren't deleted/etc.
|
||||
std::vector<KThread*> thread_list;
|
||||
std::mutex global_list_guard;
|
||||
|
||||
@@ -304,7 +304,7 @@ public:
|
||||
*/
|
||||
template <typename T, typename = std::enable_if_t<!std::is_pointer_v<T>>>
|
||||
std::size_t WriteBuffer(const T& data, std::size_t buffer_index = 0) const {
|
||||
if constexpr (Common::IsSTLContainer<T>) {
|
||||
if constexpr (Common::IsContiguousContainer<T>) {
|
||||
using ContiguousType = typename T::value_type;
|
||||
static_assert(std::is_trivially_copyable_v<ContiguousType>,
|
||||
"Container to WriteBuffer must contain trivially copyable objects");
|
||||
|
||||
@@ -81,8 +81,8 @@ void KScheduler::RescheduleCurrentHLEThread(KernelCore& kernel) {
|
||||
// HACK: we cannot schedule from this thread, it is not a core thread
|
||||
ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() == 1);
|
||||
|
||||
// Special case to ensure dummy threads that are waiting block
|
||||
GetCurrentThread(kernel).IfDummyThreadTryWait();
|
||||
// Ensure dummy threads that are waiting block.
|
||||
GetCurrentThread(kernel).DummyThreadBeginWait();
|
||||
|
||||
ASSERT(GetCurrentThread(kernel).GetState() != ThreadState::Waiting);
|
||||
GetCurrentThread(kernel).EnableDispatch();
|
||||
@@ -314,6 +314,16 @@ u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) {
|
||||
idle_cores &= ~(1ULL << core_id);
|
||||
}
|
||||
|
||||
// HACK: any waiting dummy threads can wake up now.
|
||||
kernel.GlobalSchedulerContext().WakeupWaitingDummyThreads();
|
||||
|
||||
// HACK: if we are a dummy thread, and we need to go sleep, indicate
|
||||
// that for when the lock is released.
|
||||
KThread* const cur_thread = GetCurrentThreadPointer(kernel);
|
||||
if (cur_thread->IsDummyThread() && cur_thread->GetState() != ThreadState::Runnable) {
|
||||
cur_thread->RequestDummyThreadWait();
|
||||
}
|
||||
|
||||
return cores_needing_scheduling;
|
||||
}
|
||||
|
||||
@@ -531,11 +541,23 @@ void KScheduler::OnThreadStateChanged(KernelCore& kernel, KThread* thread, Threa
|
||||
GetPriorityQueue(kernel).Remove(thread);
|
||||
IncrementScheduledCount(thread);
|
||||
SetSchedulerUpdateNeeded(kernel);
|
||||
|
||||
if (thread->IsDummyThread()) {
|
||||
// HACK: if this is a dummy thread, it should no longer wake up when the
|
||||
// scheduler lock is released.
|
||||
kernel.GlobalSchedulerContext().UnregisterDummyThreadForWakeup(thread);
|
||||
}
|
||||
} else if (cur_state == ThreadState::Runnable) {
|
||||
// If we're now runnable, then we weren't previously, and we should add.
|
||||
GetPriorityQueue(kernel).PushBack(thread);
|
||||
IncrementScheduledCount(thread);
|
||||
SetSchedulerUpdateNeeded(kernel);
|
||||
|
||||
if (thread->IsDummyThread()) {
|
||||
// HACK: if this is a dummy thread, it should wake up when the scheduler
|
||||
// lock is released.
|
||||
kernel.GlobalSchedulerContext().RegisterDummyThreadForWakeup(thread);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ private:
|
||||
|
||||
/// List of threads which are pending a reply.
|
||||
boost::intrusive::list<KSessionRequest> m_request_list;
|
||||
KSessionRequest* m_current_request;
|
||||
KSessionRequest* m_current_request{};
|
||||
|
||||
KLightLock m_lock;
|
||||
};
|
||||
|
||||
@@ -148,7 +148,9 @@ Result KThread::Initialize(KThreadFunction func, uintptr_t arg, VAddr user_stack
|
||||
physical_affinity_mask.SetAffinity(phys_core, true);
|
||||
|
||||
// Set the thread state.
|
||||
thread_state = (type == ThreadType::Main) ? ThreadState::Runnable : ThreadState::Initialized;
|
||||
thread_state = (type == ThreadType::Main || type == ThreadType::Dummy)
|
||||
? ThreadState::Runnable
|
||||
: ThreadState::Initialized;
|
||||
|
||||
// Set TLS address.
|
||||
tls_address = 0;
|
||||
@@ -1174,30 +1176,31 @@ Result KThread::Sleep(s64 timeout) {
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void KThread::IfDummyThreadTryWait() {
|
||||
if (!IsDummyThread()) {
|
||||
return;
|
||||
}
|
||||
void KThread::RequestDummyThreadWait() {
|
||||
ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(kernel));
|
||||
ASSERT(this->IsDummyThread());
|
||||
|
||||
if (GetState() != ThreadState::Waiting) {
|
||||
return;
|
||||
}
|
||||
|
||||
ASSERT(!kernel.IsPhantomModeForSingleCore());
|
||||
|
||||
// Block until we are no longer waiting.
|
||||
std::unique_lock lk(dummy_wait_lock);
|
||||
dummy_wait_cv.wait(
|
||||
lk, [&] { return GetState() != ThreadState::Waiting || kernel.IsShuttingDown(); });
|
||||
// We will block when the scheduler lock is released.
|
||||
dummy_thread_runnable.store(false);
|
||||
}
|
||||
|
||||
void KThread::IfDummyThreadEndWait() {
|
||||
if (!IsDummyThread()) {
|
||||
void KThread::DummyThreadBeginWait() {
|
||||
if (!this->IsDummyThread() || kernel.IsPhantomModeForSingleCore()) {
|
||||
// Occurs in single core mode.
|
||||
return;
|
||||
}
|
||||
|
||||
// Block until runnable is no longer false.
|
||||
dummy_thread_runnable.wait(false);
|
||||
}
|
||||
|
||||
void KThread::DummyThreadEndWait() {
|
||||
ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(kernel));
|
||||
ASSERT(this->IsDummyThread());
|
||||
|
||||
// Wake up the waiting thread.
|
||||
dummy_wait_cv.notify_one();
|
||||
dummy_thread_runnable.store(true);
|
||||
dummy_thread_runnable.notify_one();
|
||||
}
|
||||
|
||||
void KThread::BeginWait(KThreadQueue* queue) {
|
||||
@@ -1231,9 +1234,6 @@ void KThread::EndWait(Result wait_result_) {
|
||||
}
|
||||
|
||||
wait_queue->EndWait(this, wait_result_);
|
||||
|
||||
// Special case for dummy threads to wakeup if necessary.
|
||||
IfDummyThreadEndWait();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -643,8 +643,9 @@ public:
|
||||
// therefore will not block on guest kernel synchronization primitives. These methods handle
|
||||
// blocking as needed.
|
||||
|
||||
void IfDummyThreadTryWait();
|
||||
void IfDummyThreadEndWait();
|
||||
void RequestDummyThreadWait();
|
||||
void DummyThreadBeginWait();
|
||||
void DummyThreadEndWait();
|
||||
|
||||
[[nodiscard]] uintptr_t GetArgument() const {
|
||||
return argument;
|
||||
@@ -777,8 +778,7 @@ private:
|
||||
bool is_single_core{};
|
||||
ThreadType thread_type{};
|
||||
StepState step_state{};
|
||||
std::mutex dummy_wait_lock;
|
||||
std::condition_variable dummy_wait_cv;
|
||||
std::atomic<bool> dummy_thread_runnable{true};
|
||||
|
||||
// For debugging
|
||||
std::vector<KSynchronizationObject*> wait_objects_for_debugging;
|
||||
|
||||
@@ -299,7 +299,7 @@ ISelfController::ISelfController(Core::System& system_, NVFlinger::NVFlinger& nv
|
||||
{100, &ISelfController::SetAlbumImageTakenNotificationEnabled, "SetAlbumImageTakenNotificationEnabled"},
|
||||
{110, nullptr, "SetApplicationAlbumUserData"},
|
||||
{120, &ISelfController::SaveCurrentScreenshot, "SaveCurrentScreenshot"},
|
||||
{130, nullptr, "SetRecordVolumeMuted"},
|
||||
{130, &ISelfController::SetRecordVolumeMuted, "SetRecordVolumeMuted"},
|
||||
{1000, nullptr, "GetDebugStorageChannel"},
|
||||
};
|
||||
// clang-format on
|
||||
@@ -597,6 +597,17 @@ void ISelfController::SaveCurrentScreenshot(Kernel::HLERequestContext& ctx) {
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
|
||||
void ISelfController::SetRecordVolumeMuted(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
const auto is_record_volume_muted = rp.Pop<bool>();
|
||||
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called. is_record_volume_muted={}", is_record_volume_muted);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
|
||||
AppletMessageQueue::AppletMessageQueue(Core::System& system)
|
||||
: service_context{system, "AppletMessageQueue"} {
|
||||
on_new_message = service_context.CreateEvent("AMMessageQueue:OnMessageReceived");
|
||||
|
||||
@@ -182,6 +182,7 @@ private:
|
||||
void GetAccumulatedSuspendedTickChangedEvent(Kernel::HLERequestContext& ctx);
|
||||
void SetAlbumImageTakenNotificationEnabled(Kernel::HLERequestContext& ctx);
|
||||
void SaveCurrentScreenshot(Kernel::HLERequestContext& ctx);
|
||||
void SetRecordVolumeMuted(Kernel::HLERequestContext& ctx);
|
||||
|
||||
enum class ScreenshotPermission : u32 {
|
||||
Inherit = 0,
|
||||
|
||||
@@ -17,7 +17,7 @@ using namespace AudioCore::AudioIn;
|
||||
class IAudioIn final : public ServiceFramework<IAudioIn> {
|
||||
public:
|
||||
explicit IAudioIn(Core::System& system_, Manager& manager, size_t session_id,
|
||||
std::string& device_name, const AudioInParameter& in_params, u32 handle,
|
||||
const std::string& device_name, const AudioInParameter& in_params, u32 handle,
|
||||
u64 applet_resource_user_id)
|
||||
: ServiceFramework{system_, "IAudioIn"},
|
||||
service_context{system_, "IAudioIn"}, event{service_context.CreateEvent("AudioInEvent")},
|
||||
|
||||
@@ -24,7 +24,7 @@ using namespace AudioCore::AudioOut;
|
||||
class IAudioOut final : public ServiceFramework<IAudioOut> {
|
||||
public:
|
||||
explicit IAudioOut(Core::System& system_, AudioCore::AudioOut::Manager& manager,
|
||||
size_t session_id, std::string& device_name,
|
||||
size_t session_id, const std::string& device_name,
|
||||
const AudioOutParameter& in_params, u32 handle, u64 applet_resource_user_id)
|
||||
: ServiceFramework{system_, "IAudioOut", ServiceThreadType::CreateNew},
|
||||
service_context{system_, "IAudioOut"}, event{service_context.CreateEvent(
|
||||
|
||||
@@ -868,7 +868,7 @@ bool Controller_NPad::VibrateControllerAtIndex(Core::HID::NpadIdType npad_id,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!controller.device->IsVibrationEnabled()) {
|
||||
if (!controller.device->IsVibrationEnabled(device_index)) {
|
||||
if (controller.vibration[device_index].latest_vibration_value.low_amplitude != 0.0f ||
|
||||
controller.vibration[device_index].latest_vibration_value.high_amplitude != 0.0f) {
|
||||
// Send an empty vibration to stop any vibrations.
|
||||
@@ -1001,7 +1001,7 @@ void Controller_NPad::InitializeVibrationDeviceAtIndex(Core::HID::NpadIdType npa
|
||||
}
|
||||
|
||||
controller.vibration[device_index].device_mounted =
|
||||
controller.device->TestVibration(device_index);
|
||||
controller.device->IsVibrationEnabled(device_index);
|
||||
}
|
||||
|
||||
void Controller_NPad::SetPermitVibrationSession(bool permit_vibration_session) {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <mbedtls/hmac_drbg.h>
|
||||
|
||||
#include "common/fs/file.h"
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/hle/service/mii/mii_manager.h"
|
||||
@@ -279,7 +280,7 @@ bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info) {
|
||||
Common::FS::FileType::BinaryFile};
|
||||
|
||||
if (!keys_file.IsOpen()) {
|
||||
LOG_ERROR(Service_NFP, "No keys detected");
|
||||
LOG_ERROR(Service_NFP, "Failed to open key file");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -295,6 +296,11 @@ bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsKeyAvailable() {
|
||||
const auto yuzu_keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir);
|
||||
return Common::FS::Exists(yuzu_keys_dir / "key_retail.bin");
|
||||
}
|
||||
|
||||
bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& tag_data) {
|
||||
InternalKey locked_secret{};
|
||||
InternalKey unfixed_info{};
|
||||
|
||||
@@ -91,6 +91,9 @@ void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& ou
|
||||
/// Loads both amiibo keys from key_retail.bin
|
||||
bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info);
|
||||
|
||||
/// Returns true if key_retail.bin exist
|
||||
bool IsKeyAvailable();
|
||||
|
||||
/// Decodes encripted amiibo data returns true if output is valid
|
||||
bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& tag_data);
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/k_event.h"
|
||||
#include "core/hle/service/mii/mii_manager.h"
|
||||
#include "core/hle/service/mii/types.h"
|
||||
#include "core/hle/service/nfp/amiibo_crypto.h"
|
||||
#include "core/hle/service/nfp/nfp.h"
|
||||
#include "core/hle/service/nfp/nfp_device.h"
|
||||
@@ -233,6 +234,14 @@ Result NfpDevice::Mount(MountTarget mount_target_) {
|
||||
return NotAnAmiibo;
|
||||
}
|
||||
|
||||
// Mark amiibos as read only when keys are missing
|
||||
if (!AmiiboCrypto::IsKeyAvailable()) {
|
||||
LOG_ERROR(Service_NFP, "No keys detected");
|
||||
device_state = DeviceState::TagMounted;
|
||||
mount_target = MountTarget::Rom;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
if (!AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) {
|
||||
LOG_ERROR(Service_NFP, "Can't decode amiibo {}", device_state);
|
||||
return CorruptedData;
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "core/hle/service/kernel_helpers.h"
|
||||
#include "core/hle/service/mii/types.h"
|
||||
#include "core/hle/service/nfp/nfp_types.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
|
||||
@@ -17,11 +17,6 @@ enum class ServiceType : u32 {
|
||||
System,
|
||||
};
|
||||
|
||||
enum class State : u32 {
|
||||
NonInitialized,
|
||||
Initialized,
|
||||
};
|
||||
|
||||
enum class DeviceState : u32 {
|
||||
Initialized,
|
||||
SearchingForTag,
|
||||
|
||||
@@ -6,12 +6,9 @@
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hid/emulated_controller.h"
|
||||
#include "core/hid/hid_core.h"
|
||||
#include "core/hid/hid_types.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/k_event.h"
|
||||
#include "core/hle/service/mii/mii_manager.h"
|
||||
#include "core/hle/service/nfp/nfp_device.h"
|
||||
#include "core/hle/service/nfp/nfp_result.h"
|
||||
#include "core/hle/service/nfp/nfp_user.h"
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/kernel_helpers.h"
|
||||
#include "core/hle/service/nfp/nfp.h"
|
||||
#include "core/hle/service/nfp/nfp_types.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Service::NFP {
|
||||
class NfpDevice;
|
||||
@@ -15,6 +14,11 @@ public:
|
||||
explicit IUser(Core::System& system_);
|
||||
|
||||
private:
|
||||
enum class State : u32 {
|
||||
NonInitialized,
|
||||
Initialized,
|
||||
};
|
||||
|
||||
void Initialize(Kernel::HLERequestContext& ctx);
|
||||
void Finalize(Kernel::HLERequestContext& ctx);
|
||||
void ListDevices(Kernel::HLERequestContext& ctx);
|
||||
|
||||
@@ -255,15 +255,16 @@ std::optional<NvMap::FreeInfo> NvMap::FreeHandle(Handle::Id handle, bool interna
|
||||
.address = handle_description->address,
|
||||
.size = handle_description->size,
|
||||
.was_uncached = handle_description->flags.map_uncached.Value() != 0,
|
||||
.can_unlock = true,
|
||||
};
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Handle hasn't been freed from memory, set address to 0 to mark that the handle wasn't freed
|
||||
// If the handle hasn't been freed from memory, mark that
|
||||
if (!hWeak.expired()) {
|
||||
LOG_DEBUG(Service_NVDRV, "nvmap handle: {} wasn't freed as it is still in use", handle);
|
||||
freeInfo.address = 0;
|
||||
freeInfo.can_unlock = false;
|
||||
}
|
||||
|
||||
return freeInfo;
|
||||
|
||||
@@ -105,6 +105,7 @@ public:
|
||||
u64 address; //!< Address the handle referred to before deletion
|
||||
u64 size; //!< Page-aligned handle size
|
||||
bool was_uncached; //!< If the handle was allocated as uncached
|
||||
bool can_unlock; //!< If the address region is ready to be unlocked
|
||||
};
|
||||
|
||||
explicit NvMap(Tegra::Host1x::Host1x& host1x);
|
||||
|
||||
@@ -251,10 +251,12 @@ NvResult nvmap::IocFree(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
}
|
||||
|
||||
if (auto freeInfo{file.FreeHandle(params.handle, false)}) {
|
||||
ASSERT(system.CurrentProcess()
|
||||
->PageTable()
|
||||
.UnlockForDeviceAddressSpace(freeInfo->address, freeInfo->size)
|
||||
.IsSuccess());
|
||||
if (freeInfo->can_unlock) {
|
||||
ASSERT(system.CurrentProcess()
|
||||
->PageTable()
|
||||
.UnlockForDeviceAddressSpace(freeInfo->address, freeInfo->size)
|
||||
.IsSuccess());
|
||||
}
|
||||
params.address = freeInfo->address;
|
||||
params.size = static_cast<u32>(freeInfo->size);
|
||||
params.flags.raw = 0;
|
||||
|
||||
@@ -742,6 +742,13 @@ Status BufferQueueProducer::Disconnect(NativeWindowApi api) {
|
||||
return Status::NoError;
|
||||
}
|
||||
|
||||
// HACK: We are not Android. Remove handle for items in queue, and clear queue.
|
||||
// Allows synchronous destruction of nvmap handles.
|
||||
for (auto& item : core->queue) {
|
||||
nvmap.FreeHandle(item.graphic_buffer->BufferId(), true);
|
||||
}
|
||||
core->queue.clear();
|
||||
|
||||
switch (api) {
|
||||
case NativeWindowApi::Egl:
|
||||
case NativeWindowApi::Cpu:
|
||||
|
||||
@@ -138,6 +138,19 @@ std::optional<u64> NVFlinger::OpenDisplay(std::string_view name) {
|
||||
return itr->GetID();
|
||||
}
|
||||
|
||||
bool NVFlinger::CloseDisplay(u64 display_id) {
|
||||
const auto lock_guard = Lock();
|
||||
auto* const display = FindDisplay(display_id);
|
||||
|
||||
if (display == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
display->Reset();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<u64> NVFlinger::CreateLayer(u64 display_id) {
|
||||
const auto lock_guard = Lock();
|
||||
auto* const display = FindDisplay(display_id);
|
||||
|
||||
@@ -58,6 +58,11 @@ public:
|
||||
/// If an invalid display name is provided, then an empty optional is returned.
|
||||
[[nodiscard]] std::optional<u64> OpenDisplay(std::string_view name);
|
||||
|
||||
/// Closes the specified display by its ID.
|
||||
///
|
||||
/// Returns false if an invalid display ID is provided.
|
||||
[[nodiscard]] bool CloseDisplay(u64 display_id);
|
||||
|
||||
/// Creates a layer on the specified display and returns the layer ID.
|
||||
///
|
||||
/// If an invalid display ID is specified, then an empty optional is returned.
|
||||
|
||||
@@ -80,7 +80,6 @@ ResultVal<Kernel::KPort*> ServiceManager::GetServicePort(const std::string& name
|
||||
}
|
||||
|
||||
auto* port = Kernel::KPort::Create(kernel);
|
||||
SCOPE_EXIT({ port->Close(); });
|
||||
|
||||
port->Initialize(ServerSessionCountMax, false, name);
|
||||
auto handler = it->second;
|
||||
@@ -150,9 +149,10 @@ ResultVal<Kernel::KClientSession*> SM::GetServiceImpl(Kernel::HLERequestContext&
|
||||
return port_result.Code();
|
||||
}
|
||||
auto& port = port_result.Unwrap();
|
||||
SCOPE_EXIT({ port->GetClientPort().Close(); });
|
||||
|
||||
kernel.RegisterServerObject(&port->GetServerPort());
|
||||
SCOPE_EXIT({
|
||||
port->GetClientPort().Close();
|
||||
port->GetServerPort().Close();
|
||||
});
|
||||
|
||||
// Create a new session.
|
||||
Kernel::KClientSession* session{};
|
||||
|
||||
@@ -28,23 +28,36 @@ void Controller::ConvertCurrentObjectToDomain(Kernel::HLERequestContext& ctx) {
|
||||
void Controller::CloneCurrentObject(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service, "called");
|
||||
|
||||
auto& process = *ctx.GetThread().GetOwnerProcess();
|
||||
auto& parent_session = *ctx.Session()->GetParent();
|
||||
auto& parent_port = parent_session.GetParent()->GetParent()->GetClientPort();
|
||||
auto& session_manager = parent_session.GetServerSession().GetSessionRequestManager();
|
||||
auto& session_handler = session_manager->SessionHandler();
|
||||
|
||||
// Create a session.
|
||||
Kernel::KClientSession* session{};
|
||||
const Result result = parent_port.CreateSession(std::addressof(session), session_manager);
|
||||
if (result.IsError()) {
|
||||
LOG_CRITICAL(Service, "CreateSession failed with error 0x{:08X}", result.raw);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(result);
|
||||
}
|
||||
// FIXME: this is duplicated from the SVC, it should just call it instead
|
||||
// once this is a proper process
|
||||
|
||||
// Reserve a new session from the process resource limit.
|
||||
Kernel::KScopedResourceReservation session_reservation(&process,
|
||||
Kernel::LimitableResource::Sessions);
|
||||
ASSERT(session_reservation.Succeeded());
|
||||
|
||||
// Create the session.
|
||||
Kernel::KSession* session = Kernel::KSession::Create(system.Kernel());
|
||||
ASSERT(session != nullptr);
|
||||
|
||||
// Initialize the session.
|
||||
session->Initialize(nullptr, parent_session.GetName(), session_manager);
|
||||
|
||||
// Commit the session reservation.
|
||||
session_reservation.Commit();
|
||||
|
||||
// Register the session.
|
||||
session_handler.ClientConnected(&session->GetServerSession());
|
||||
|
||||
// We succeeded.
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushMoveObjects(session);
|
||||
rb.PushMoveObjects(session->GetClientSession());
|
||||
}
|
||||
|
||||
void Controller::CloneCurrentObjectEx(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
@@ -106,6 +106,12 @@ public:
|
||||
///
|
||||
void CloseLayer(u64 layer_id);
|
||||
|
||||
/// Resets the display for a new connection.
|
||||
void Reset() {
|
||||
layers.clear();
|
||||
got_vsync_event = false;
|
||||
}
|
||||
|
||||
/// Attempts to find a layer with the given ID.
|
||||
///
|
||||
/// @param layer_id The layer ID.
|
||||
|
||||
@@ -324,10 +324,10 @@ private:
|
||||
IPC::RequestParser rp{ctx};
|
||||
const u64 display = rp.Pop<u64>();
|
||||
|
||||
LOG_WARNING(Service_VI, "(STUBBED) called. display=0x{:016X}", display);
|
||||
const Result rc = nv_flinger.CloseDisplay(display) ? ResultSuccess : ResultUnknown;
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push(rc);
|
||||
}
|
||||
|
||||
void CreateManagedLayer(Kernel::HLERequestContext& ctx) {
|
||||
@@ -508,10 +508,10 @@ private:
|
||||
IPC::RequestParser rp{ctx};
|
||||
const u64 display_id = rp.Pop<u64>();
|
||||
|
||||
LOG_WARNING(Service_VI, "(STUBBED) called. display_id=0x{:016X}", display_id);
|
||||
const Result rc = nv_flinger.CloseDisplay(display_id) ? ResultSuccess : ResultUnknown;
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push(rc);
|
||||
}
|
||||
|
||||
// This literally does nothing internally in the actual service itself,
|
||||
|
||||
@@ -324,7 +324,7 @@ bool GCAdapter::GetGCEndpoint(libusb_device* device) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Common::Input::VibrationError GCAdapter::SetRumble(
|
||||
Common::Input::VibrationError GCAdapter::SetVibration(
|
||||
const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) {
|
||||
const auto mean_amplitude = (vibration.low_amplitude + vibration.high_amplitude) * 0.5f;
|
||||
const auto processed_amplitude =
|
||||
@@ -338,6 +338,10 @@ Common::Input::VibrationError GCAdapter::SetRumble(
|
||||
return Common::Input::VibrationError::None;
|
||||
}
|
||||
|
||||
bool GCAdapter::IsVibrationEnabled([[maybe_unused]] const PadIdentifier& identifier) {
|
||||
return rumble_enabled;
|
||||
}
|
||||
|
||||
void GCAdapter::UpdateVibrations() {
|
||||
// Use 8 states to keep the switching between on/off fast enough for
|
||||
// a human to feel different vibration strenght
|
||||
|
||||
@@ -25,9 +25,11 @@ public:
|
||||
explicit GCAdapter(std::string input_engine_);
|
||||
~GCAdapter() override;
|
||||
|
||||
Common::Input::VibrationError SetRumble(
|
||||
Common::Input::VibrationError SetVibration(
|
||||
const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;
|
||||
|
||||
bool IsVibrationEnabled(const PadIdentifier& identifier) override;
|
||||
|
||||
/// Used for automapping features
|
||||
std::vector<Common::ParamPackage> GetInputDevices() const override;
|
||||
ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override;
|
||||
|
||||
@@ -114,6 +114,20 @@ public:
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void EnableVibration(bool is_enabled) {
|
||||
has_vibration = is_enabled;
|
||||
is_vibration_tested = true;
|
||||
}
|
||||
|
||||
bool HasVibration() const {
|
||||
return has_vibration;
|
||||
}
|
||||
|
||||
bool IsVibrationTested() const {
|
||||
return is_vibration_tested;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Pad identifier of the joystick
|
||||
*/
|
||||
@@ -236,6 +250,8 @@ private:
|
||||
u64 last_motion_update{};
|
||||
bool has_gyro{false};
|
||||
bool has_accel{false};
|
||||
bool has_vibration{false};
|
||||
bool is_vibration_tested{false};
|
||||
BasicMotion motion;
|
||||
};
|
||||
|
||||
@@ -517,7 +533,7 @@ std::vector<Common::ParamPackage> SDLDriver::GetInputDevices() const {
|
||||
return devices;
|
||||
}
|
||||
|
||||
Common::Input::VibrationError SDLDriver::SetRumble(
|
||||
Common::Input::VibrationError SDLDriver::SetVibration(
|
||||
const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) {
|
||||
const auto joystick =
|
||||
GetSDLJoystickByGUID(identifier.guid.RawString(), static_cast<int>(identifier.port));
|
||||
@@ -546,13 +562,6 @@ Common::Input::VibrationError SDLDriver::SetRumble(
|
||||
.type = Common::Input::VibrationAmplificationType::Exponential,
|
||||
};
|
||||
|
||||
if (vibration.type == Common::Input::VibrationAmplificationType::Test) {
|
||||
if (!joystick->RumblePlay(new_vibration)) {
|
||||
return Common::Input::VibrationError::Unknown;
|
||||
}
|
||||
return Common::Input::VibrationError::None;
|
||||
}
|
||||
|
||||
vibration_queue.Push(VibrationRequest{
|
||||
.identifier = identifier,
|
||||
.vibration = new_vibration,
|
||||
@@ -561,6 +570,45 @@ Common::Input::VibrationError SDLDriver::SetRumble(
|
||||
return Common::Input::VibrationError::None;
|
||||
}
|
||||
|
||||
bool SDLDriver::IsVibrationEnabled(const PadIdentifier& identifier) {
|
||||
const auto joystick =
|
||||
GetSDLJoystickByGUID(identifier.guid.RawString(), static_cast<int>(identifier.port));
|
||||
|
||||
constexpr Common::Input::VibrationStatus test_vibration{
|
||||
.low_amplitude = 1,
|
||||
.low_frequency = 160.0f,
|
||||
.high_amplitude = 1,
|
||||
.high_frequency = 320.0f,
|
||||
.type = Common::Input::VibrationAmplificationType::Exponential,
|
||||
};
|
||||
|
||||
constexpr Common::Input::VibrationStatus zero_vibration{
|
||||
.low_amplitude = 0,
|
||||
.low_frequency = 160.0f,
|
||||
.high_amplitude = 0,
|
||||
.high_frequency = 320.0f,
|
||||
.type = Common::Input::VibrationAmplificationType::Exponential,
|
||||
};
|
||||
|
||||
if (joystick->IsVibrationTested()) {
|
||||
return joystick->HasVibration();
|
||||
}
|
||||
|
||||
// First vibration might fail
|
||||
joystick->RumblePlay(test_vibration);
|
||||
|
||||
// Wait for about 15ms to ensure the controller is ready for the stop command
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(15));
|
||||
|
||||
if (!joystick->RumblePlay(zero_vibration)) {
|
||||
joystick->EnableVibration(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
joystick->EnableVibration(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SDLDriver::SendVibrations() {
|
||||
while (!vibration_queue.Empty()) {
|
||||
VibrationRequest request;
|
||||
|
||||
@@ -61,9 +61,11 @@ public:
|
||||
|
||||
bool IsStickInverted(const Common::ParamPackage& params) override;
|
||||
|
||||
Common::Input::VibrationError SetRumble(
|
||||
Common::Input::VibrationError SetVibration(
|
||||
const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;
|
||||
|
||||
bool IsVibrationEnabled(const PadIdentifier& identifier) override;
|
||||
|
||||
private:
|
||||
struct VibrationRequest {
|
||||
PadIdentifier identifier;
|
||||
|
||||
@@ -108,12 +108,17 @@ public:
|
||||
[[maybe_unused]] const Common::Input::LedStatus& led_status) {}
|
||||
|
||||
// Sets rumble to a controller
|
||||
virtual Common::Input::VibrationError SetRumble(
|
||||
virtual Common::Input::VibrationError SetVibration(
|
||||
[[maybe_unused]] const PadIdentifier& identifier,
|
||||
[[maybe_unused]] const Common::Input::VibrationStatus& vibration) {
|
||||
return Common::Input::VibrationError::NotSupported;
|
||||
}
|
||||
|
||||
// Returns true if device supports vibrations
|
||||
virtual bool IsVibrationEnabled([[maybe_unused]] const PadIdentifier& identifier) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sets polling mode to a controller
|
||||
virtual Common::Input::PollingError SetPollingMode(
|
||||
[[maybe_unused]] const PadIdentifier& identifier,
|
||||
|
||||
@@ -763,7 +763,11 @@ public:
|
||||
|
||||
Common::Input::VibrationError SetVibration(
|
||||
const Common::Input::VibrationStatus& vibration_status) override {
|
||||
return input_engine->SetRumble(identifier, vibration_status);
|
||||
return input_engine->SetVibration(identifier, vibration_status);
|
||||
}
|
||||
|
||||
bool IsVibrationEnabled() override {
|
||||
return input_engine->IsVibrationEnabled(identifier);
|
||||
}
|
||||
|
||||
Common::Input::PollingError SetPollingMode(Common::Input::PollingMode polling_mode) override {
|
||||
|
||||
@@ -325,11 +325,6 @@ void Inst::AddPhiOperand(Block* predecessor, const Value& value) {
|
||||
phi_args.emplace_back(predecessor, value);
|
||||
}
|
||||
|
||||
void Inst::ErasePhiOperand(size_t index) {
|
||||
const auto operand_it{phi_args.begin() + static_cast<ptrdiff_t>(index)};
|
||||
phi_args.erase(operand_it);
|
||||
}
|
||||
|
||||
void Inst::OrderPhiArgs() {
|
||||
if (op != Opcode::Phi) {
|
||||
throw LogicError("{} is not a Phi instruction", op);
|
||||
|
||||
@@ -178,13 +178,9 @@ public:
|
||||
|
||||
/// Get a pointer to the block of a phi argument.
|
||||
[[nodiscard]] Block* PhiBlock(size_t index) const;
|
||||
|
||||
/// Add phi operand to a phi instruction.
|
||||
void AddPhiOperand(Block* predecessor, const Value& value);
|
||||
|
||||
// Erase the phi operand at the given index.
|
||||
void ErasePhiOperand(size_t index);
|
||||
|
||||
/// Orders the Phi arguments from farthest away to nearest.
|
||||
void OrderPhiArgs();
|
||||
|
||||
|
||||
@@ -1,104 +1,24 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
#include "shader_recompiler/frontend/ir/basic_block.h"
|
||||
#include "shader_recompiler/frontend/ir/value.h"
|
||||
#include "shader_recompiler/ir_opt/passes.h"
|
||||
|
||||
namespace Shader::Optimization {
|
||||
namespace {
|
||||
template <bool TEST_USES>
|
||||
void DeadInstElimination(IR::Block* const block) {
|
||||
// We iterate over the instructions in reverse order.
|
||||
// This is because removing an instruction reduces the number of uses for earlier instructions.
|
||||
auto it{block->end()};
|
||||
while (it != block->begin()) {
|
||||
--it;
|
||||
if constexpr (TEST_USES) {
|
||||
if (it->HasUses() || it->MayHaveSideEffects()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
it->Invalidate();
|
||||
it = block->Instructions().erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void DeletedPhiArgElimination(IR::Program& program, std::span<const IR::Block*> dead_blocks) {
|
||||
for (IR::Block* const block : program.blocks) {
|
||||
for (IR::Inst& phi : *block) {
|
||||
if (!IR::IsPhi(phi)) {
|
||||
continue;
|
||||
}
|
||||
for (size_t i = 0; i < phi.NumArgs(); ++i) {
|
||||
if (std::ranges::find(dead_blocks, phi.PhiBlock(i)) == dead_blocks.end()) {
|
||||
continue;
|
||||
}
|
||||
// Phi operand at this index is an unreachable block
|
||||
phi.ErasePhiOperand(i);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DeadBranchElimination(IR::Program& program) {
|
||||
boost::container::small_vector<const IR::Block*, 3> dead_blocks;
|
||||
const auto begin_it{program.syntax_list.begin()};
|
||||
for (auto node_it = begin_it; node_it != program.syntax_list.end(); ++node_it) {
|
||||
if (node_it->type != IR::AbstractSyntaxNode::Type::If) {
|
||||
continue;
|
||||
}
|
||||
IR::Inst* const cond_ref{node_it->data.if_node.cond.Inst()};
|
||||
const IR::U1 cond{cond_ref->Arg(0)};
|
||||
if (!cond.IsImmediate()) {
|
||||
continue;
|
||||
}
|
||||
if (cond.U1()) {
|
||||
continue;
|
||||
}
|
||||
// False immediate condition. Remove condition ref, erase the entire branch.
|
||||
cond_ref->Invalidate();
|
||||
// Account for nested if-statements within the if(false) branch
|
||||
u32 nested_ifs{1u};
|
||||
while (node_it->type != IR::AbstractSyntaxNode::Type::EndIf || nested_ifs > 0) {
|
||||
node_it = program.syntax_list.erase(node_it);
|
||||
switch (node_it->type) {
|
||||
case IR::AbstractSyntaxNode::Type::If:
|
||||
++nested_ifs;
|
||||
break;
|
||||
case IR::AbstractSyntaxNode::Type::EndIf:
|
||||
--nested_ifs;
|
||||
break;
|
||||
case IR::AbstractSyntaxNode::Type::Block: {
|
||||
IR::Block* const block{node_it->data.block};
|
||||
DeadInstElimination<false>(block);
|
||||
dead_blocks.push_back(block);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Erase EndIf node of the if(false) branch
|
||||
node_it = program.syntax_list.erase(node_it);
|
||||
// Account for loop increment
|
||||
--node_it;
|
||||
}
|
||||
if (!dead_blocks.empty()) {
|
||||
DeletedPhiArgElimination(program, std::span(dead_blocks.data(), dead_blocks.size()));
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void DeadCodeEliminationPass(IR::Program& program) {
|
||||
DeadBranchElimination(program);
|
||||
// We iterate over the instructions in reverse order.
|
||||
// This is because removing an instruction reduces the number of uses for earlier instructions.
|
||||
for (IR::Block* const block : program.post_order_blocks) {
|
||||
DeadInstElimination<true>(block);
|
||||
auto it{block->end()};
|
||||
while (it != block->begin()) {
|
||||
--it;
|
||||
if (!it->HasUses() && !it->MayHaveSideEffects()) {
|
||||
it->Invalidate();
|
||||
it = block->Instructions().erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ public:
|
||||
|
||||
[[nodiscard]] unsigned Count() const noexcept {
|
||||
unsigned count = 0;
|
||||
for (const auto [index, value] : page_table) {
|
||||
for (const auto& [index, value] : page_table) {
|
||||
count += value;
|
||||
}
|
||||
return count;
|
||||
|
||||
@@ -123,9 +123,6 @@ void Maxwell3D::InitializeRegisterDefaults() {
|
||||
draw_command[MAXWELL3D_REG_INDEX(vertex_buffer.count)] = true;
|
||||
draw_command[MAXWELL3D_REG_INDEX(index_buffer.first)] = true;
|
||||
draw_command[MAXWELL3D_REG_INDEX(index_buffer.count)] = true;
|
||||
draw_command[MAXWELL3D_REG_INDEX(index_buffer32_first)] = true;
|
||||
draw_command[MAXWELL3D_REG_INDEX(index_buffer16_first)] = true;
|
||||
draw_command[MAXWELL3D_REG_INDEX(index_buffer8_first)] = true;
|
||||
draw_command[MAXWELL3D_REG_INDEX(draw_inline_index)] = true;
|
||||
draw_command[MAXWELL3D_REG_INDEX(inline_index_2x16.even)] = true;
|
||||
draw_command[MAXWELL3D_REG_INDEX(inline_index_4x8.index0)] = true;
|
||||
@@ -216,6 +213,21 @@ void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argume
|
||||
return ProcessCBBind(3);
|
||||
case MAXWELL3D_REG_INDEX(bind_groups[4].raw_config):
|
||||
return ProcessCBBind(4);
|
||||
case MAXWELL3D_REG_INDEX(index_buffer32_first):
|
||||
regs.index_buffer.count = regs.index_buffer32_first.count;
|
||||
regs.index_buffer.first = regs.index_buffer32_first.first;
|
||||
dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
|
||||
return ProcessDraw();
|
||||
case MAXWELL3D_REG_INDEX(index_buffer16_first):
|
||||
regs.index_buffer.count = regs.index_buffer16_first.count;
|
||||
regs.index_buffer.first = regs.index_buffer16_first.first;
|
||||
dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
|
||||
return ProcessDraw();
|
||||
case MAXWELL3D_REG_INDEX(index_buffer8_first):
|
||||
regs.index_buffer.count = regs.index_buffer8_first.count;
|
||||
regs.index_buffer.first = regs.index_buffer8_first.first;
|
||||
dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
|
||||
return ProcessDraw();
|
||||
case MAXWELL3D_REG_INDEX(topology_override):
|
||||
use_topology_override = true;
|
||||
return;
|
||||
@@ -583,70 +595,7 @@ void Maxwell3D::ProcessClearBuffers() {
|
||||
rasterizer->Clear();
|
||||
}
|
||||
|
||||
void Maxwell3D::ProcessDeferredDraw() {
|
||||
if (deferred_draw_method.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
enum class DrawMode {
|
||||
Undefined,
|
||||
General,
|
||||
Instance,
|
||||
};
|
||||
DrawMode draw_mode{DrawMode::Undefined};
|
||||
u32 instance_count = 1;
|
||||
|
||||
auto first_method = deferred_draw_method[0];
|
||||
if (MAXWELL3D_REG_INDEX(draw.begin) == first_method) {
|
||||
// The minimum number of methods for drawing must be greater than or equal to
|
||||
// 3[draw.begin->vertex(index)count->draw.end] to avoid errors in index mode drawing
|
||||
if (deferred_draw_method.size() < 3) {
|
||||
return;
|
||||
}
|
||||
draw_mode = (regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::Subsequent) ||
|
||||
(regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::Unchanged)
|
||||
? DrawMode::Instance
|
||||
: DrawMode::General;
|
||||
} else if (MAXWELL3D_REG_INDEX(index_buffer32_first) == first_method ||
|
||||
MAXWELL3D_REG_INDEX(index_buffer16_first) == first_method ||
|
||||
MAXWELL3D_REG_INDEX(index_buffer8_first) == first_method) {
|
||||
draw_mode = DrawMode::General;
|
||||
}
|
||||
|
||||
// Drawing will only begin with draw.begin or index_buffer method, other methods directly
|
||||
// clear
|
||||
if (draw_mode == DrawMode::Undefined) {
|
||||
deferred_draw_method.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (draw_mode == DrawMode::Instance) {
|
||||
ASSERT_MSG(deferred_draw_method.size() % 4 == 0, "Instance mode method size error");
|
||||
instance_count = static_cast<u32>(deferred_draw_method.size()) / 4;
|
||||
} else {
|
||||
if (MAXWELL3D_REG_INDEX(index_buffer32_first) == first_method) {
|
||||
regs.index_buffer.count = regs.index_buffer32_first.count;
|
||||
regs.index_buffer.first = regs.index_buffer32_first.first;
|
||||
dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
|
||||
} else if (MAXWELL3D_REG_INDEX(index_buffer32_first) == first_method) {
|
||||
regs.index_buffer.count = regs.index_buffer16_first.count;
|
||||
regs.index_buffer.first = regs.index_buffer16_first.first;
|
||||
dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
|
||||
} else if (MAXWELL3D_REG_INDEX(index_buffer32_first) == first_method) {
|
||||
regs.index_buffer.count = regs.index_buffer8_first.count;
|
||||
regs.index_buffer.first = regs.index_buffer8_first.first;
|
||||
dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
|
||||
} else {
|
||||
auto second_method = deferred_draw_method[1];
|
||||
if (MAXWELL3D_REG_INDEX(draw_inline_index) == second_method ||
|
||||
MAXWELL3D_REG_INDEX(inline_index_2x16.even) == second_method ||
|
||||
MAXWELL3D_REG_INDEX(inline_index_4x8.index0) == second_method) {
|
||||
regs.index_buffer.count = static_cast<u32>(inline_index_draw_indexes.size() / 4);
|
||||
regs.index_buffer.format = Regs::IndexFormat::UnsignedInt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Maxwell3D::ProcessDraw(u32 instance_count) {
|
||||
LOG_TRACE(HW_GPU, "called, topology={}, count={}", regs.draw.topology.Value(),
|
||||
regs.vertex_buffer.count);
|
||||
|
||||
@@ -669,6 +618,64 @@ void Maxwell3D::ProcessDeferredDraw() {
|
||||
} else {
|
||||
regs.vertex_buffer.count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Maxwell3D::ProcessDeferredDraw() {
|
||||
if (deferred_draw_method.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
enum class DrawMode {
|
||||
Undefined,
|
||||
General,
|
||||
Instance,
|
||||
};
|
||||
DrawMode draw_mode{DrawMode::Undefined};
|
||||
u32 instance_count = 1;
|
||||
|
||||
u32 index = 0;
|
||||
u32 method = 0;
|
||||
u32 method_count = static_cast<u32>(deferred_draw_method.size());
|
||||
for (; index < method_count &&
|
||||
(method = deferred_draw_method[index]) != MAXWELL3D_REG_INDEX(draw.begin);
|
||||
++index)
|
||||
;
|
||||
|
||||
if (MAXWELL3D_REG_INDEX(draw.begin) != method) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The minimum number of methods for drawing must be greater than or equal to
|
||||
// 3[draw.begin->vertex(index)count(first)->draw.end] to avoid errors in index mode drawing
|
||||
if ((method_count - index) < 3) {
|
||||
return;
|
||||
}
|
||||
draw_mode = (regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::Subsequent) ||
|
||||
(regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::Unchanged)
|
||||
? DrawMode::Instance
|
||||
: DrawMode::General;
|
||||
|
||||
// Drawing will only begin with draw.begin or index_buffer method, other methods directly
|
||||
// clear
|
||||
if (draw_mode == DrawMode::Undefined) {
|
||||
deferred_draw_method.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (draw_mode == DrawMode::Instance) {
|
||||
ASSERT_MSG(deferred_draw_method.size() % 4 == 0, "Instance mode method size error");
|
||||
instance_count = static_cast<u32>(method_count - index) / 4;
|
||||
} else {
|
||||
method = deferred_draw_method[index + 1];
|
||||
if (MAXWELL3D_REG_INDEX(draw_inline_index) == method ||
|
||||
MAXWELL3D_REG_INDEX(inline_index_2x16.even) == method ||
|
||||
MAXWELL3D_REG_INDEX(inline_index_4x8.index0) == method) {
|
||||
regs.index_buffer.count = static_cast<u32>(inline_index_draw_indexes.size() / 4);
|
||||
regs.index_buffer.format = Regs::IndexFormat::UnsignedInt;
|
||||
}
|
||||
}
|
||||
|
||||
ProcessDraw(instance_count);
|
||||
|
||||
deferred_draw_method.clear();
|
||||
inline_index_draw_indexes.clear();
|
||||
|
||||
@@ -3143,6 +3143,8 @@ private:
|
||||
/// Handles use of topology overrides (e.g., to avoid using a topology assigned from a macro)
|
||||
void ProcessTopologyOverride();
|
||||
|
||||
void ProcessDraw(u32 instance_count = 1);
|
||||
|
||||
void ProcessDeferredDraw();
|
||||
|
||||
/// Returns a query's value or an empty object if the value will be deferred through a cache.
|
||||
|
||||
@@ -144,7 +144,6 @@ private:
|
||||
using FuncType = TypedCommand<T>;
|
||||
static_assert(sizeof(FuncType) < sizeof(data), "Lambda is too large");
|
||||
|
||||
recorded_counts++;
|
||||
command_offset = Common::AlignUp(command_offset, alignof(FuncType));
|
||||
if (command_offset > sizeof(data) - sizeof(FuncType)) {
|
||||
return false;
|
||||
@@ -166,7 +165,7 @@ private:
|
||||
}
|
||||
|
||||
bool Empty() const {
|
||||
return recorded_counts == 0;
|
||||
return command_offset == 0;
|
||||
}
|
||||
|
||||
bool HasSubmit() const {
|
||||
@@ -177,7 +176,6 @@ private:
|
||||
Command* first = nullptr;
|
||||
Command* last = nullptr;
|
||||
|
||||
size_t recorded_counts = 0;
|
||||
size_t command_offset = 0;
|
||||
bool submit = false;
|
||||
alignas(std::max_align_t) std::array<u8, 0x8000> data{};
|
||||
|
||||
@@ -1782,17 +1782,17 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime,
|
||||
|
||||
const auto& resolution = runtime.resolution;
|
||||
|
||||
u32 width = 0;
|
||||
u32 height = 0;
|
||||
u32 width = std::numeric_limits<u32>::max();
|
||||
u32 height = std::numeric_limits<u32>::max();
|
||||
for (size_t index = 0; index < NUM_RT; ++index) {
|
||||
const ImageView* const color_buffer = color_buffers[index];
|
||||
if (!color_buffer) {
|
||||
renderpass_key.color_formats[index] = PixelFormat::Invalid;
|
||||
continue;
|
||||
}
|
||||
width = std::max(width, is_rescaled ? resolution.ScaleUp(color_buffer->size.width)
|
||||
width = std::min(width, is_rescaled ? resolution.ScaleUp(color_buffer->size.width)
|
||||
: color_buffer->size.width);
|
||||
height = std::max(height, is_rescaled ? resolution.ScaleUp(color_buffer->size.height)
|
||||
height = std::min(height, is_rescaled ? resolution.ScaleUp(color_buffer->size.height)
|
||||
: color_buffer->size.height);
|
||||
attachments.push_back(color_buffer->RenderTarget());
|
||||
renderpass_key.color_formats[index] = color_buffer->format;
|
||||
@@ -1804,9 +1804,9 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime,
|
||||
}
|
||||
const size_t num_colors = attachments.size();
|
||||
if (depth_buffer) {
|
||||
width = std::max(width, is_rescaled ? resolution.ScaleUp(depth_buffer->size.width)
|
||||
width = std::min(width, is_rescaled ? resolution.ScaleUp(depth_buffer->size.width)
|
||||
: depth_buffer->size.width);
|
||||
height = std::max(height, is_rescaled ? resolution.ScaleUp(depth_buffer->size.height)
|
||||
height = std::min(height, is_rescaled ? resolution.ScaleUp(depth_buffer->size.height)
|
||||
: depth_buffer->size.height);
|
||||
attachments.push_back(depth_buffer->RenderTarget());
|
||||
renderpass_key.depth_format = depth_buffer->format;
|
||||
|
||||
@@ -819,6 +819,7 @@ void Config::ReadUIGamelistValues() {
|
||||
qt_config->beginGroup(QStringLiteral("UIGameList"));
|
||||
|
||||
ReadBasicSetting(UISettings::values.show_add_ons);
|
||||
ReadBasicSetting(UISettings::values.show_compat);
|
||||
ReadBasicSetting(UISettings::values.game_icon_size);
|
||||
ReadBasicSetting(UISettings::values.folder_icon_size);
|
||||
ReadBasicSetting(UISettings::values.row_1_text_id);
|
||||
@@ -1414,6 +1415,7 @@ void Config::SaveUIGamelistValues() {
|
||||
qt_config->beginGroup(QStringLiteral("UIGameList"));
|
||||
|
||||
WriteBasicSetting(UISettings::values.show_add_ons);
|
||||
WriteBasicSetting(UISettings::values.show_compat);
|
||||
WriteBasicSetting(UISettings::values.game_icon_size);
|
||||
WriteBasicSetting(UISettings::values.folder_icon_size);
|
||||
WriteBasicSetting(UISettings::values.row_1_text_id);
|
||||
|
||||
@@ -72,6 +72,7 @@ ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent)
|
||||
|
||||
// Force game list reload if any of the relevant settings are changed.
|
||||
connect(ui->show_add_ons, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
|
||||
connect(ui->show_compat, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
|
||||
connect(ui->game_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||
&ConfigureUi::RequestGameListUpdate);
|
||||
connect(ui->folder_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
||||
@@ -109,6 +110,7 @@ void ConfigureUi::ApplyConfiguration() {
|
||||
UISettings::values.theme =
|
||||
ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString();
|
||||
UISettings::values.show_add_ons = ui->show_add_ons->isChecked();
|
||||
UISettings::values.show_compat = ui->show_compat->isChecked();
|
||||
UISettings::values.game_icon_size = ui->game_icon_size_combobox->currentData().toUInt();
|
||||
UISettings::values.folder_icon_size = ui->folder_icon_size_combobox->currentData().toUInt();
|
||||
UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt();
|
||||
@@ -129,6 +131,7 @@ void ConfigureUi::SetConfiguration() {
|
||||
ui->language_combobox->setCurrentIndex(
|
||||
ui->language_combobox->findData(UISettings::values.language));
|
||||
ui->show_add_ons->setChecked(UISettings::values.show_add_ons.GetValue());
|
||||
ui->show_compat->setChecked(UISettings::values.show_compat.GetValue());
|
||||
ui->game_icon_size_combobox->setCurrentIndex(
|
||||
ui->game_icon_size_combobox->findData(UISettings::values.game_icon_size.GetValue()));
|
||||
ui->folder_icon_size_combobox->setCurrentIndex(
|
||||
|
||||
@@ -76,6 +76,13 @@
|
||||
<layout class="QHBoxLayout" name="GameListHorizontalLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="GeneralVerticalLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="show_compat">
|
||||
<property name="text">
|
||||
<string>Show Compatibility List</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="show_add_ons">
|
||||
<property name="text">
|
||||
|
||||
@@ -335,6 +335,7 @@ GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvid
|
||||
RetranslateUI();
|
||||
|
||||
tree_view->setColumnHidden(COLUMN_ADD_ONS, !UISettings::values.show_add_ons);
|
||||
tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat);
|
||||
item_model->setSortRole(GameListItemPath::SortRole);
|
||||
|
||||
connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::OnUpdateThemedIcons);
|
||||
@@ -786,6 +787,7 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
|
||||
|
||||
// Update the columns in case UISettings has changed
|
||||
tree_view->setColumnHidden(COLUMN_ADD_ONS, !UISettings::values.show_add_ons);
|
||||
tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat);
|
||||
|
||||
// Delete any rows that might already exist if we're repopulating
|
||||
item_model->removeRows(0, item_model->rowCount());
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
<addaction name="separator"/>
|
||||
<addaction name="menu_recent_files"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="action_Load_Amiibo"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="action_Open_yuzu_Folder"/>
|
||||
|
||||
@@ -129,6 +129,9 @@ struct Values {
|
||||
Settings::Setting<bool> favorites_expanded{true, "favorites_expanded"};
|
||||
QVector<u64> favorited_ids;
|
||||
|
||||
// Compatibility List
|
||||
Settings::Setting<bool> show_compat{false, "show_compat"};
|
||||
|
||||
bool configuration_applied;
|
||||
bool reset_to_defaults;
|
||||
Settings::Setting<bool> disable_web_applet{true, "disable_web_applet"};
|
||||
|
||||
Reference in New Issue
Block a user