diff options
Diffstat (limited to 'src')
98 files changed, 3833 insertions, 1216 deletions
diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp index c4e0e30fe..41bf5cd4d 100644 --- a/src/audio_core/cubeb_sink.cpp +++ b/src/audio_core/cubeb_sink.cpp @@ -193,7 +193,7 @@ long CubebSinkStream::DataCallback(cubeb_stream* stream, void* user_data, const const std::size_t samples_to_write = num_channels * num_frames; std::size_t samples_written; - if (Settings::values.enable_audio_stretching) { + if (Settings::values.enable_audio_stretching.GetValue()) { const std::vector<s16> in{impl->queue.Pop()}; const std::size_t num_in{in.size() / num_channels}; s16* const out{reinterpret_cast<s16*>(buffer)}; diff --git a/src/audio_core/stream.cpp b/src/audio_core/stream.cpp index dfc4805d9..aab3e979a 100644 --- a/src/audio_core/stream.cpp +++ b/src/audio_core/stream.cpp @@ -38,7 +38,7 @@ Stream::Stream(Core::Timing::CoreTiming& core_timing, u32 sample_rate, Format fo sink_stream{sink_stream}, core_timing{core_timing}, name{std::move(name_)} { release_event = Core::Timing::CreateEvent( - name, [this](u64 userdata, s64 cycles_late) { ReleaseActiveBuffer(); }); + name, [this](u64 userdata, s64 cycles_late) { ReleaseActiveBuffer(cycles_late); }); } void Stream::Play() { @@ -66,15 +66,6 @@ s64 Stream::GetBufferReleaseNS(const Buffer& buffer) const { return ns.count(); } -s64 Stream::GetBufferReleaseNSHostTiming(const Buffer& buffer) const { - const std::size_t num_samples{buffer.GetSamples().size() / GetNumChannels()}; - /// DSP signals before playing the last sample, in HLE we emulate this in this way - s64 base_samples = std::max<s64>(static_cast<s64>(num_samples) - 1, 0); - const auto ns = - std::chrono::nanoseconds((static_cast<u64>(base_samples) * 1000000000ULL) / sample_rate); - return ns.count(); -} - static void VolumeAdjustSamples(std::vector<s16>& samples, float game_volume) { const float volume{std::clamp(Settings::Volume() - (1.0f - game_volume), 0.0f, 1.0f)}; @@ -89,7 +80,7 @@ static void VolumeAdjustSamples(std::vector<s16>& samples, float game_volume) { } } -void Stream::PlayNextBuffer() { +void Stream::PlayNextBuffer(s64 cycles_late) { if (!IsPlaying()) { // Ensure we are in playing state before playing the next buffer sink_stream.Flush(); @@ -114,18 +105,17 @@ void Stream::PlayNextBuffer() { sink_stream.EnqueueSamples(GetNumChannels(), active_buffer->GetSamples()); - if (core_timing.IsHostTiming()) { - core_timing.ScheduleEvent(GetBufferReleaseNSHostTiming(*active_buffer), release_event, {}); - } else { - core_timing.ScheduleEvent(GetBufferReleaseNS(*active_buffer), release_event, {}); - } + core_timing.ScheduleEvent( + GetBufferReleaseNS(*active_buffer) - + (Settings::values.enable_audio_stretching.GetValue() ? 0 : cycles_late), + release_event, {}); } -void Stream::ReleaseActiveBuffer() { +void Stream::ReleaseActiveBuffer(s64 cycles_late) { ASSERT(active_buffer); released_buffers.push(std::move(active_buffer)); release_callback(); - PlayNextBuffer(); + PlayNextBuffer(cycles_late); } bool Stream::QueueBuffer(BufferPtr&& buffer) { diff --git a/src/audio_core/stream.h b/src/audio_core/stream.h index e309d60fe..524376257 100644 --- a/src/audio_core/stream.h +++ b/src/audio_core/stream.h @@ -90,10 +90,10 @@ public: private: /// Plays the next queued buffer in the audio stream, starting playback if necessary - void PlayNextBuffer(); + void PlayNextBuffer(s64 cycles_late = 0); /// Releases the actively playing buffer, signalling that it has been completed - void ReleaseActiveBuffer(); + void ReleaseActiveBuffer(s64 cycles_late = 0); /// Gets the number of core cycles when the specified buffer will be released s64 GetBufferReleaseNS(const Buffer& buffer) const; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index f87d67db5..d1f173f42 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -614,7 +614,7 @@ endif() create_target_directory_groups(core) target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) -target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::Opus unicorn zip) +target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls opus unicorn zip) if (YUZU_ENABLE_BOXCAT) target_compile_definitions(core PRIVATE -DYUZU_ENABLE_BOXCAT) diff --git a/src/core/arm/cpu_interrupt_handler.cpp b/src/core/arm/cpu_interrupt_handler.cpp index 2f1a1a269..df0350881 100644 --- a/src/core/arm/cpu_interrupt_handler.cpp +++ b/src/core/arm/cpu_interrupt_handler.cpp @@ -2,8 +2,6 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#pragma once - #include "common/thread.h" #include "core/arm/cpu_interrupt_handler.h" diff --git a/src/core/core.cpp b/src/core/core.cpp index 1a243c515..69a1aa0a5 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -147,8 +147,8 @@ struct System::Impl { device_memory = std::make_unique<Core::DeviceMemory>(system); - is_multicore = Settings::values.use_multi_core; - is_async_gpu = is_multicore || Settings::values.use_asynchronous_gpu_emulation; + is_multicore = Settings::values.use_multi_core.GetValue(); + is_async_gpu = is_multicore || Settings::values.use_asynchronous_gpu_emulation.GetValue(); kernel.SetMulticore(is_multicore); cpu_manager.SetMulticore(is_multicore); @@ -162,7 +162,7 @@ struct System::Impl { const auto current_time = std::chrono::duration_cast<std::chrono::seconds>( std::chrono::system_clock::now().time_since_epoch()); Settings::values.custom_rtc_differential = - Settings::values.custom_rtc.value_or(current_time) - current_time; + Settings::values.custom_rtc.GetValue().value_or(current_time) - current_time; // Create a default fs if one doesn't already exist. if (virtual_filesystem == nullptr) diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index 5c83c41a4..a63e60461 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -172,7 +172,7 @@ void CoreTiming::ClearPendingEvents() { } void CoreTiming::RemoveEvent(const std::shared_ptr<EventType>& event_type) { - basic_lock.lock(); + std::scoped_lock lock{basic_lock}; const auto itr = std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) { return e.type.lock().get() == event_type.get(); @@ -183,12 +183,10 @@ void CoreTiming::RemoveEvent(const std::shared_ptr<EventType>& event_type) { event_queue.erase(itr, event_queue.end()); std::make_heap(event_queue.begin(), event_queue.end(), std::greater<>()); } - basic_lock.unlock(); } std::optional<s64> CoreTiming::Advance() { - std::scoped_lock advance_scope{advance_lock}; - std::scoped_lock basic_scope{basic_lock}; + std::scoped_lock lock{advance_lock, basic_lock}; global_timer = GetGlobalTimeNs().count(); while (!event_queue.empty() && event_queue.front().time <= global_timer) { diff --git a/src/core/frontend/framebuffer_layout.cpp b/src/core/frontend/framebuffer_layout.cpp index d0c43447c..c1fbc235b 100644 --- a/src/core/frontend/framebuffer_layout.cpp +++ b/src/core/frontend/framebuffer_layout.cpp @@ -29,7 +29,7 @@ FramebufferLayout DefaultFrameLayout(u32 width, u32 height) { const float window_aspect_ratio = static_cast<float>(height) / width; const float emulation_aspect_ratio = EmulationAspectRatio( - static_cast<AspectRatio>(Settings::values.aspect_ratio), window_aspect_ratio); + static_cast<AspectRatio>(Settings::values.aspect_ratio.GetValue()), window_aspect_ratio); const Common::Rectangle<u32> screen_window_area{0, 0, width, height}; Common::Rectangle<u32> screen = MaxRectangle(screen_window_area, emulation_aspect_ratio); diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index f9d7c024d..c6fcb56ad 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -123,7 +123,7 @@ std::shared_ptr<Process> Process::Create(Core::System& system, std::string name, : kernel.CreateNewUserProcessID(); process->capabilities.InitializeForMetadatalessProcess(); - std::mt19937 rng(Settings::values.rng_seed.value_or(0)); + std::mt19937 rng(Settings::values.rng_seed.GetValue().value_or(0)); std::uniform_int_distribution<u64> distribution; std::generate(process->random_entropy.begin(), process->random_entropy.end(), [&] { return distribution(rng); }); diff --git a/src/core/hle/kernel/scheduler.cpp b/src/core/hle/kernel/scheduler.cpp index 2b12c0dbf..7b929781c 100644 --- a/src/core/hle/kernel/scheduler.cpp +++ b/src/core/hle/kernel/scheduler.cpp @@ -6,6 +6,7 @@ // licensed under GPLv2 or later under exception provided by the author. #include <algorithm> +#include <mutex> #include <set> #include <unordered_set> #include <utility> @@ -31,22 +32,20 @@ GlobalScheduler::GlobalScheduler(KernelCore& kernel) : kernel{kernel} {} GlobalScheduler::~GlobalScheduler() = default; void GlobalScheduler::AddThread(std::shared_ptr<Thread> thread) { - global_list_guard.lock(); + std::scoped_lock lock{global_list_guard}; thread_list.push_back(std::move(thread)); - global_list_guard.unlock(); } void GlobalScheduler::RemoveThread(std::shared_ptr<Thread> thread) { - global_list_guard.lock(); + std::scoped_lock lock{global_list_guard}; thread_list.erase(std::remove(thread_list.begin(), thread_list.end(), thread), thread_list.end()); - global_list_guard.unlock(); } u32 GlobalScheduler::SelectThreads() { ASSERT(is_locked); const auto update_thread = [](Thread* thread, Scheduler& sched) { - sched.guard.lock(); + std::scoped_lock lock{sched.guard}; if (thread != sched.selected_thread_set.get()) { if (thread == nullptr) { ++sched.idle_selection_count; @@ -57,7 +56,6 @@ u32 GlobalScheduler::SelectThreads() { sched.is_context_switch_pending || (sched.selected_thread_set != sched.current_thread); sched.is_context_switch_pending = reschedule_pending; std::atomic_thread_fence(std::memory_order_seq_cst); - sched.guard.unlock(); return reschedule_pending; }; if (!is_reselection_pending.load()) { @@ -757,11 +755,12 @@ void Scheduler::OnSwitch(void* this_scheduler) { void Scheduler::SwitchToCurrent() { while (true) { - guard.lock(); - selected_thread = selected_thread_set; - current_thread = selected_thread; - is_context_switch_pending = false; - guard.unlock(); + { + std::scoped_lock lock{guard}; + selected_thread = selected_thread_set; + current_thread = selected_thread; + is_context_switch_pending = false; + } while (!is_context_switch_pending) { if (current_thread != nullptr && !current_thread->IsHLEThread()) { current_thread->context_guard.lock(); diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp index 94d8c1fc6..8ac856ec3 100644 --- a/src/core/hle/service/acc/acc.cpp +++ b/src/core/hle/service/acc/acc.cpp @@ -776,6 +776,15 @@ void Module::Interface::ListQualifiedUsers(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); } +void Module::Interface::ListOpenContextStoredUsers(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_ACC, "(STUBBED) called"); + + // TODO(ogniK): Handle open contexts + ctx.WriteBuffer(profile_manager->GetOpenUsers()); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + void Module::Interface::TrySelectUserWithoutInteraction(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_ACC, "called"); // A u8 is passed into this function which we can safely ignore. It's to determine if we have diff --git a/src/core/hle/service/acc/acc.h b/src/core/hle/service/acc/acc.h index 74ca39d6e..d4c6395c6 100644 --- a/src/core/hle/service/acc/acc.h +++ b/src/core/hle/service/acc/acc.h @@ -34,6 +34,7 @@ public: void IsUserAccountSwitchLocked(Kernel::HLERequestContext& ctx); void GetProfileEditor(Kernel::HLERequestContext& ctx); void ListQualifiedUsers(Kernel::HLERequestContext& ctx); + void ListOpenContextStoredUsers(Kernel::HLERequestContext& ctx); private: ResultCode InitializeApplicationInfoBase(); diff --git a/src/core/hle/service/acc/acc_su.cpp b/src/core/hle/service/acc/acc_su.cpp index 85620bde3..d2bb8c2c8 100644 --- a/src/core/hle/service/acc/acc_su.cpp +++ b/src/core/hle/service/acc/acc_su.cpp @@ -20,7 +20,7 @@ ACC_SU::ACC_SU(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p {6, nullptr, "GetProfileDigest"}, // 3.0.0+ {50, &ACC_SU::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"}, {51, &ACC_SU::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"}, - {60, nullptr, "ListOpenContextStoredUsers"}, // 5.0.0 - 5.1.0 + {60, &ACC_SU::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, // 5.0.0 - 5.1.0 {99, nullptr, "DebugActivateOpenContextRetention"}, // 6.0.0+ {100, nullptr, "GetUserRegistrationNotifier"}, {101, nullptr, "GetUserStateChangeNotifier"}, diff --git a/src/core/hle/service/acc/acc_u0.cpp b/src/core/hle/service/acc/acc_u0.cpp index 49f6e20f1..cb44e06b7 100644 --- a/src/core/hle/service/acc/acc_u0.cpp +++ b/src/core/hle/service/acc/acc_u0.cpp @@ -20,7 +20,7 @@ ACC_U0::ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p {6, nullptr, "GetProfileDigest"}, // 3.0.0+ {50, &ACC_U0::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"}, {51, &ACC_U0::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"}, - {60, nullptr, "ListOpenContextStoredUsers"}, // 5.0.0 - 5.1.0 + {60, &ACC_U0::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, // 5.0.0 - 5.1.0 {99, nullptr, "DebugActivateOpenContextRetention"}, // 6.0.0+ {100, &ACC_U0::InitializeApplicationInfo, "InitializeApplicationInfo"}, {101, &ACC_U0::GetBaasAccountManagerForApplication, "GetBaasAccountManagerForApplication"}, @@ -30,7 +30,7 @@ ACC_U0::ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p {111, nullptr, "ClearSaveDataThumbnail"}, {120, nullptr, "CreateGuestLoginRequest"}, {130, nullptr, "LoadOpenContext"}, // 5.0.0+ - {131, nullptr, "ListOpenContextStoredUsers"}, // 6.0.0+ + {131, &ACC_U0::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, // 6.0.0+ {140, &ACC_U0::InitializeApplicationInfoRestricted, "InitializeApplicationInfoRestricted"}, // 6.0.0+ {141, &ACC_U0::ListQualifiedUsers, "ListQualifiedUsers"}, // 6.0.0+ {150, &ACC_U0::IsUserAccountSwitchLocked, "IsUserAccountSwitchLocked"}, // 6.0.0+ diff --git a/src/core/hle/service/acc/acc_u1.cpp b/src/core/hle/service/acc/acc_u1.cpp index f47004f84..a4aa5316a 100644 --- a/src/core/hle/service/acc/acc_u1.cpp +++ b/src/core/hle/service/acc/acc_u1.cpp @@ -20,7 +20,7 @@ ACC_U1::ACC_U1(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p {6, nullptr, "GetProfileDigest"}, // 3.0.0+ {50, &ACC_U1::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"}, {51, &ACC_U1::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"}, - {60, nullptr, "ListOpenContextStoredUsers"}, // 5.0.0 - 5.1.0 + {60, &ACC_U1::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, // 5.0.0 - 5.1.0 {99, nullptr, "DebugActivateOpenContextRetention"}, // 6.0.0+ {100, nullptr, "GetUserRegistrationNotifier"}, {101, nullptr, "GetUserStateChangeNotifier"}, diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 20f366635..256449aa7 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -10,6 +10,7 @@ #include "core/core.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" +#include "core/file_sys/registered_cache.h" #include "core/file_sys/savedata_factory.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/kernel.h" @@ -271,7 +272,7 @@ ISelfController::ISelfController(Core::System& system, {41, nullptr, "IsSystemBufferSharingEnabled"}, {42, nullptr, "GetSystemSharedLayerHandle"}, {43, nullptr, "GetSystemSharedBufferHandle"}, - {44, nullptr, "CreateManagedDisplaySeparableLayer"}, + {44, &ISelfController::CreateManagedDisplaySeparableLayer, "CreateManagedDisplaySeparableLayer"}, {45, nullptr, "SetManagedDisplayLayerSeparationMode"}, {50, &ISelfController::SetHandlesRequestToDisplay, "SetHandlesRequestToDisplay"}, {51, nullptr, "ApproveToDisplay"}, @@ -461,6 +462,24 @@ void ISelfController::CreateManagedDisplayLayer(Kernel::HLERequestContext& ctx) rb.Push(*layer_id); } +void ISelfController::CreateManagedDisplaySeparableLayer(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + + // TODO(Subv): Find out how AM determines the display to use, for now just + // create the layer in the Default display. + // This calls nn::vi::CreateRecordingLayer() which creates another layer. + // Currently we do not support more than 1 layer per display, output 1 layer id for now. + // Outputting 1 layer id instead of the expected 2 has not been observed to cause any adverse + // side effects. + // TODO: Support multiple layers + const auto display_id = nvflinger->OpenDisplay("Default"); + const auto layer_id = nvflinger->CreateLayer(*display_id); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push(*layer_id); +} + void ISelfController::SetHandlesRequestToDisplay(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_AM, "(STUBBED) called"); @@ -730,14 +749,14 @@ void ICommonStateGetter::GetDefaultDisplayResolution(Kernel::HLERequestContext& if (Settings::values.use_docked_mode) { rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth) * - static_cast<u32>(Settings::values.resolution_factor)); + static_cast<u32>(Settings::values.resolution_factor.GetValue())); rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight) * - static_cast<u32>(Settings::values.resolution_factor)); + static_cast<u32>(Settings::values.resolution_factor.GetValue())); } else { rb.Push(static_cast<u32>(Service::VI::DisplayResolution::UndockedWidth) * - static_cast<u32>(Settings::values.resolution_factor)); + static_cast<u32>(Settings::values.resolution_factor.GetValue())); rb.Push(static_cast<u32>(Service::VI::DisplayResolution::UndockedHeight) * - static_cast<u32>(Settings::values.resolution_factor)); + static_cast<u32>(Settings::values.resolution_factor.GetValue())); } } @@ -841,7 +860,7 @@ public: {110, nullptr, "NeedsToExitProcess"}, {120, nullptr, "GetLibraryAppletInfo"}, {150, nullptr, "RequestForAppletToGetForeground"}, - {160, nullptr, "GetIndirectLayerConsumerHandle"}, + {160, &ILibraryAppletAccessor::GetIndirectLayerConsumerHandle, "GetIndirectLayerConsumerHandle"}, }; // clang-format on @@ -960,6 +979,18 @@ private: rb.PushCopyObjects(applet->GetBroker().GetInteractiveDataEvent()); } + void GetIndirectLayerConsumerHandle(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + + // We require a non-zero handle to be valid. Using 0xdeadbeef allows us to trace if this is + // actually used anywhere + constexpr u64 handle = 0xdeadbeef; + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push(handle); + } + std::shared_ptr<Applets::Applet> applet; }; @@ -1341,14 +1372,25 @@ void IApplicationFunctions::GetDisplayVersion(Kernel::HLERequestContext& ctx) { std::array<u8, 0x10> version_string{}; - FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID()}; - const auto res = pm.GetControlMetadata(); + const auto res = [this] { + const auto title_id = system.CurrentProcess()->GetTitleID(); + + FileSys::PatchManager pm{title_id}; + auto res = pm.GetControlMetadata(); + if (res.first != nullptr) { + return res; + } + + FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(title_id)}; + return pm_update.GetControlMetadata(); + }(); + if (res.first != nullptr) { const auto& version = res.first->GetVersionString(); std::copy(version.begin(), version.end(), version_string.begin()); } else { - constexpr u128 default_version = {1, 0}; - std::memcpy(version_string.data(), default_version.data(), sizeof(u128)); + constexpr char default_version[]{"1.0.0"}; + std::memcpy(version_string.data(), default_version, sizeof(default_version)); } IPC::ResponseBuilder rb{ctx, 6}; diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index 2f69466ec..6cfb11b48 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -140,6 +140,7 @@ private: void SetOutOfFocusSuspendingEnabled(Kernel::HLERequestContext& ctx); void SetAlbumImageOrientation(Kernel::HLERequestContext& ctx); void CreateManagedDisplayLayer(Kernel::HLERequestContext& ctx); + void CreateManagedDisplaySeparableLayer(Kernel::HLERequestContext& ctx); void SetHandlesRequestToDisplay(Kernel::HLERequestContext& ctx); void SetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx); void GetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp index 7e5ceccdb..6cfa9666d 100644 --- a/src/core/hle/service/ns/ns.cpp +++ b/src/core/hle/service/ns/ns.cpp @@ -366,7 +366,8 @@ ResultVal<u8> IApplicationManagerInterface::GetApplicationDesiredLanguage( LOG_DEBUG(Service_NS, "called with supported_languages={:08X}", supported_languages); // Get language code from settings - const auto language_code = Set::GetLanguageCodeFromIndex(Settings::values.language_index); + const auto language_code = + Set::GetLanguageCodeFromIndex(Settings::values.language_index.GetValue()); // Convert to application language, get priority list const auto application_language = ConvertToApplicationLanguage(language_code); diff --git a/src/core/hle/service/set/set.cpp b/src/core/hle/service/set/set.cpp index f3b4b286c..34fe2fd82 100644 --- a/src/core/hle/service/set/set.cpp +++ b/src/core/hle/service/set/set.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include <algorithm> +#include <array> #include <chrono> #include "common/logging/log.h" #include "core/hle/ipc_helpers.h" @@ -31,6 +32,44 @@ constexpr std::array<LanguageCode, 17> available_language_codes = {{ LanguageCode::ZH_HANT, }}; +enum class KeyboardLayout : u64 { + Japanese = 0, + EnglishUs = 1, + EnglishUsInternational = 2, + EnglishUk = 3, + French = 4, + FrenchCa = 5, + Spanish = 6, + SpanishLatin = 7, + German = 8, + Italian = 9, + Portuguese = 10, + Russian = 11, + Korean = 12, + ChineseSimplified = 13, + ChineseTraditional = 14, +}; + +constexpr std::array<std::pair<LanguageCode, KeyboardLayout>, 17> language_to_layout{{ + {LanguageCode::JA, KeyboardLayout::Japanese}, + {LanguageCode::EN_US, KeyboardLayout::EnglishUs}, + {LanguageCode::FR, KeyboardLayout::French}, + {LanguageCode::DE, KeyboardLayout::German}, + {LanguageCode::IT, KeyboardLayout::Italian}, + {LanguageCode::ES, KeyboardLayout::Spanish}, + {LanguageCode::ZH_CN, KeyboardLayout::ChineseSimplified}, + {LanguageCode::KO, KeyboardLayout::Korean}, + {LanguageCode::NL, KeyboardLayout::EnglishUsInternational}, + {LanguageCode::PT, KeyboardLayout::Portuguese}, + {LanguageCode::RU, KeyboardLayout::Russian}, + {LanguageCode::ZH_TW, KeyboardLayout::ChineseTraditional}, + {LanguageCode::EN_GB, KeyboardLayout::EnglishUk}, + {LanguageCode::FR_CA, KeyboardLayout::FrenchCa}, + {LanguageCode::ES_419, KeyboardLayout::SpanishLatin}, + {LanguageCode::ZH_HANS, KeyboardLayout::ChineseSimplified}, + {LanguageCode::ZH_HANT, KeyboardLayout::ChineseTraditional}, +}}; + constexpr std::size_t pre4_0_0_max_entries = 15; constexpr std::size_t post4_0_0_max_entries = 17; @@ -50,6 +89,25 @@ void GetAvailableLanguageCodesImpl(Kernel::HLERequestContext& ctx, std::size_t m ctx.WriteBuffer(available_language_codes.data(), copy_size); PushResponseLanguageCode(ctx, copy_amount); } + +void GetKeyCodeMapImpl(Kernel::HLERequestContext& ctx) { + const auto language_code = available_language_codes[Settings::values.language_index.GetValue()]; + const auto key_code = + std::find_if(language_to_layout.cbegin(), language_to_layout.cend(), + [=](const auto& element) { return element.first == language_code; }); + KeyboardLayout layout = KeyboardLayout::EnglishUs; + if (key_code == language_to_layout.cend()) { + LOG_ERROR(Service_SET, + "Could not find keyboard layout for language index {}, defaulting to English us", + Settings::values.language_index.GetValue()); + } else { + layout = key_code->second; + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + ctx.WriteBuffer(&layout, sizeof(KeyboardLayout)); +} } // Anonymous namespace LanguageCode GetLanguageCodeFromIndex(std::size_t index) { @@ -105,11 +163,11 @@ void SET::GetQuestFlag(Kernel::HLERequestContext& ctx) { } void SET::GetLanguageCode(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_SET, "called {}", Settings::values.language_index); + LOG_DEBUG(Service_SET, "called {}", Settings::values.language_index.GetValue()); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); - rb.PushEnum(available_language_codes[Settings::values.language_index]); + rb.PushEnum(available_language_codes[Settings::values.language_index.GetValue()]); } void SET::GetRegionCode(Kernel::HLERequestContext& ctx) { @@ -117,7 +175,17 @@ void SET::GetRegionCode(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push(Settings::values.region_index); + rb.Push(Settings::values.region_index.GetValue()); +} + +void SET::GetKeyCodeMap(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_SET, "Called {}", ctx.Description()); + GetKeyCodeMapImpl(ctx); +} + +void SET::GetKeyCodeMap2(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_SET, "Called {}", ctx.Description()); + GetKeyCodeMapImpl(ctx); } SET::SET() : ServiceFramework("set") { @@ -130,9 +198,9 @@ SET::SET() : ServiceFramework("set") { {4, &SET::GetRegionCode, "GetRegionCode"}, {5, &SET::GetAvailableLanguageCodes2, "GetAvailableLanguageCodes2"}, {6, &SET::GetAvailableLanguageCodeCount2, "GetAvailableLanguageCodeCount2"}, - {7, nullptr, "GetKeyCodeMap"}, + {7, &SET::GetKeyCodeMap, "GetKeyCodeMap"}, {8, &SET::GetQuestFlag, "GetQuestFlag"}, - {9, nullptr, "GetKeyCodeMap2"}, + {9, &SET::GetKeyCodeMap2, "GetKeyCodeMap2"}, {10, nullptr, "GetFirmwareVersionForDebug"}, }; // clang-format on diff --git a/src/core/hle/service/set/set.h b/src/core/hle/service/set/set.h index 6084b345d..8ac9c169d 100644 --- a/src/core/hle/service/set/set.h +++ b/src/core/hle/service/set/set.h @@ -44,6 +44,8 @@ private: void GetAvailableLanguageCodeCount2(Kernel::HLERequestContext& ctx); void GetQuestFlag(Kernel::HLERequestContext& ctx); void GetRegionCode(Kernel::HLERequestContext& ctx); + void GetKeyCodeMap(Kernel::HLERequestContext& ctx); + void GetKeyCodeMap2(Kernel::HLERequestContext& ctx); }; } // namespace Service::Set diff --git a/src/core/hle/service/spl/module.cpp b/src/core/hle/service/spl/module.cpp index e724d4ab8..865ed3b91 100644 --- a/src/core/hle/service/spl/module.cpp +++ b/src/core/hle/service/spl/module.cpp @@ -19,7 +19,7 @@ namespace Service::SPL { Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) : ServiceFramework(name), module(std::move(module)), - rng(Settings::values.rng_seed.value_or(std::time(nullptr))) {} + rng(Settings::values.rng_seed.GetValue().value_or(std::time(nullptr))) {} Module::Interface::~Interface() = default; diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp index 157092074..552a5e4ef 100644 --- a/src/core/hle/service/vi/vi.cpp +++ b/src/core/hle/service/vi/vi.cpp @@ -519,9 +519,9 @@ private: IGBPConnectRequestParcel request{ctx.ReadBuffer()}; IGBPConnectResponseParcel response{ static_cast<u32>(static_cast<u32>(DisplayResolution::UndockedWidth) * - Settings::values.resolution_factor), + Settings::values.resolution_factor.GetValue()), static_cast<u32>(static_cast<u32>(DisplayResolution::UndockedHeight) * - Settings::values.resolution_factor)}; + Settings::values.resolution_factor.GetValue())}; ctx.WriteBuffer(response.Serialize()); break; } @@ -748,14 +748,14 @@ private: if (Settings::values.use_docked_mode) { rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth) * - static_cast<u32>(Settings::values.resolution_factor)); + static_cast<u32>(Settings::values.resolution_factor.GetValue())); rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight) * - static_cast<u32>(Settings::values.resolution_factor)); + static_cast<u32>(Settings::values.resolution_factor.GetValue())); } else { rb.Push(static_cast<u32>(Service::VI::DisplayResolution::UndockedWidth) * - static_cast<u32>(Settings::values.resolution_factor)); + static_cast<u32>(Settings::values.resolution_factor.GetValue())); rb.Push(static_cast<u32>(Service::VI::DisplayResolution::UndockedHeight) * - static_cast<u32>(Settings::values.resolution_factor)); + static_cast<u32>(Settings::values.resolution_factor.GetValue())); } rb.PushRaw<float>(60.0f); // This wouldn't seem to be correct for 30 fps games. @@ -1029,9 +1029,9 @@ private: // between docked and undocked dimensions. We take the liberty of applying // the resolution scaling factor here. rb.Push(static_cast<u64>(DisplayResolution::UndockedWidth) * - static_cast<u32>(Settings::values.resolution_factor)); + static_cast<u32>(Settings::values.resolution_factor.GetValue())); rb.Push(static_cast<u64>(DisplayResolution::UndockedHeight) * - static_cast<u32>(Settings::values.resolution_factor)); + static_cast<u32>(Settings::values.resolution_factor.GetValue())); } void SetLayerScalingMode(Kernel::HLERequestContext& ctx) { @@ -1064,8 +1064,8 @@ private: LOG_WARNING(Service_VI, "(STUBBED) called"); DisplayInfo display_info; - display_info.width *= static_cast<u64>(Settings::values.resolution_factor); - display_info.height *= static_cast<u64>(Settings::values.resolution_factor); + display_info.width *= static_cast<u64>(Settings::values.resolution_factor.GetValue()); + display_info.height *= static_cast<u64>(Settings::values.resolution_factor.GetValue()); ctx.WriteBuffer(&display_info, sizeof(DisplayInfo)); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 7def00768..2c5588933 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -548,9 +548,9 @@ struct Memory::Impl { // longer exist, and we should just leave the pagetable entry blank. page_type = Common::PageType::Unmapped; } else { - page_type = Common::PageType::Memory; current_page_table->pointers[vaddr >> PAGE_BITS] = pointer - (vaddr & ~PAGE_MASK); + page_type = Common::PageType::Memory; } break; } @@ -591,9 +591,12 @@ struct Memory::Impl { base + page_table.pointers.size()); if (!target) { + ASSERT_MSG(type != Common::PageType::Memory, + "Mapping memory page without a pointer @ {:016x}", base * PAGE_SIZE); + while (base != end) { - page_table.pointers[base] = nullptr; page_table.attributes[base] = type; + page_table.pointers[base] = nullptr; page_table.backing_addr[base] = 0; base += 1; diff --git a/src/core/perf_stats.cpp b/src/core/perf_stats.cpp index 9f3a6b811..29339ead7 100644 --- a/src/core/perf_stats.cpp +++ b/src/core/perf_stats.cpp @@ -119,13 +119,14 @@ double PerfStats::GetLastFrameTimeScale() { } void FrameLimiter::DoFrameLimiting(microseconds current_system_time_us) { - if (!Settings::values.use_frame_limit || Settings::values.use_multi_core) { + if (!Settings::values.use_frame_limit.GetValue() || + Settings::values.use_multi_core.GetValue()) { return; } auto now = Clock::now(); - const double sleep_scale = Settings::values.frame_limit / 100.0; + const double sleep_scale = Settings::values.frame_limit.GetValue() / 100.0; // Max lag caused by slow frames. Shouldn't be more than the length of a frame at the current // speed percent or it will clamp too much and prevent this from properly limiting to that diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 56df5e925..d3886c4ec 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -62,6 +62,7 @@ const std::array<const char*, NumMouseButtons> mapping = {{ } Values values = {}; +bool configuring_global = true; std::string GetTimeZoneString() { static constexpr std::array<const char*, 46> timezones{{ @@ -73,9 +74,9 @@ std::string GetTimeZoneString() { "UCT", "Universal", "UTC", "W-SU", "WET", "Zulu", }}; - ASSERT(Settings::values.time_zone_index < timezones.size()); + ASSERT(Settings::values.time_zone_index.GetValue() < timezones.size()); - return timezones[Settings::values.time_zone_index]; + return timezones[Settings::values.time_zone_index.GetValue()]; } void Apply() { @@ -97,25 +98,25 @@ void LogSetting(const std::string& name, const T& value) { void LogSettings() { LOG_INFO(Config, "yuzu Configuration:"); - LogSetting("System_UseDockedMode", Settings::values.use_docked_mode); - LogSetting("System_RngSeed", Settings::values.rng_seed.value_or(0)); + LogSetting("Controls_UseDockedMode", Settings::values.use_docked_mode); + LogSetting("System_RngSeed", Settings::values.rng_seed.GetValue().value_or(0)); LogSetting("System_CurrentUser", Settings::values.current_user); - LogSetting("System_LanguageIndex", Settings::values.language_index); - LogSetting("System_RegionIndex", Settings::values.region_index); - LogSetting("System_TimeZoneIndex", Settings::values.time_zone_index); - LogSetting("Core_UseMultiCore", Settings::values.use_multi_core); - LogSetting("Renderer_UseResolutionFactor", Settings::values.resolution_factor); - LogSetting("Renderer_UseFrameLimit", Settings::values.use_frame_limit); - LogSetting("Renderer_FrameLimit", Settings::values.frame_limit); - LogSetting("Renderer_UseDiskShaderCache", Settings::values.use_disk_shader_cache); - LogSetting("Renderer_GPUAccuracyLevel", Settings::values.gpu_accuracy); + LogSetting("System_LanguageIndex", Settings::values.language_index.GetValue()); + LogSetting("System_RegionIndex", Settings::values.region_index.GetValue()); + LogSetting("System_TimeZoneIndex", Settings::values.time_zone_index.GetValue()); + LogSetting("Core_UseMultiCore", Settings::values.use_multi_core.GetValue()); + LogSetting("Renderer_UseResolutionFactor", Settings::values.resolution_factor.GetValue()); + LogSetting("Renderer_UseFrameLimit", Settings::values.use_frame_limit.GetValue()); + LogSetting("Renderer_FrameLimit", Settings::values.frame_limit.GetValue()); + LogSetting("Renderer_UseDiskShaderCache", Settings::values.use_disk_shader_cache.GetValue()); + LogSetting("Renderer_GPUAccuracyLevel", Settings::values.gpu_accuracy.GetValue()); LogSetting("Renderer_UseAsynchronousGpuEmulation", - Settings::values.use_asynchronous_gpu_emulation); - LogSetting("Renderer_UseVsync", Settings::values.use_vsync); - LogSetting("Renderer_UseAssemblyShaders", Settings::values.use_assembly_shaders); - LogSetting("Renderer_AnisotropicFilteringLevel", Settings::values.max_anisotropy); + Settings::values.use_asynchronous_gpu_emulation.GetValue()); + LogSetting("Renderer_UseVsync", Settings::values.use_vsync.GetValue()); + LogSetting("Renderer_UseAssemblyShaders", Settings::values.use_assembly_shaders.GetValue()); + LogSetting("Renderer_AnisotropicFilteringLevel", Settings::values.max_anisotropy.GetValue()); LogSetting("Audio_OutputEngine", Settings::values.sink_id); - LogSetting("Audio_EnableAudioStretching", Settings::values.enable_audio_stretching); + LogSetting("Audio_EnableAudioStretching", Settings::values.enable_audio_stretching.GetValue()); LogSetting("Audio_OutputDevice", Settings::values.audio_device_id); LogSetting("DataStorage_UseVirtualSd", Settings::values.use_virtual_sd); LogSetting("DataStorage_NandDir", FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)); @@ -131,15 +132,56 @@ float Volume() { if (values.audio_muted) { return 0.0f; } - return values.volume; + return values.volume.GetValue(); } bool IsGPULevelExtreme() { - return values.gpu_accuracy == GPUAccuracy::Extreme; + return values.gpu_accuracy.GetValue() == GPUAccuracy::Extreme; } bool IsGPULevelHigh() { - return values.gpu_accuracy == GPUAccuracy::Extreme || values.gpu_accuracy == GPUAccuracy::High; + return values.gpu_accuracy.GetValue() == GPUAccuracy::Extreme || + values.gpu_accuracy.GetValue() == GPUAccuracy::High; +} + +void RestoreGlobalState() { + // If a game is running, DO NOT restore the global settings state + if (Core::System::GetInstance().IsPoweredOn()) { + return; + } + + // Audio + values.enable_audio_stretching.SetGlobal(true); + values.volume.SetGlobal(true); + + // Core + values.use_multi_core.SetGlobal(true); + + // Renderer + values.renderer_backend.SetGlobal(true); + values.vulkan_device.SetGlobal(true); + values.aspect_ratio.SetGlobal(true); + values.max_anisotropy.SetGlobal(true); + values.use_frame_limit.SetGlobal(true); + values.frame_limit.SetGlobal(true); + values.use_disk_shader_cache.SetGlobal(true); + values.gpu_accuracy.SetGlobal(true); + values.use_asynchronous_gpu_emulation.SetGlobal(true); + values.use_vsync.SetGlobal(true); + values.use_assembly_shaders.SetGlobal(true); + values.use_fast_gpu_time.SetGlobal(true); + values.force_30fps_mode.SetGlobal(true); + values.bg_red.SetGlobal(true); + values.bg_green.SetGlobal(true); + values.bg_blue.SetGlobal(true); + + // System + values.language_index.SetGlobal(true); + values.region_index.SetGlobal(true); + values.time_zone_index.SetGlobal(true); + values.rng_seed.SetGlobal(true); + values.custom_rtc.SetGlobal(true); + values.sound_index.SetGlobal(true); } } // namespace Settings diff --git a/src/core/settings.h b/src/core/settings.h index a598ccbc1..850ca4072 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -382,20 +382,85 @@ enum class GPUAccuracy : u32 { Extreme = 2, }; +extern bool configuring_global; + +template <typename Type> +class Setting final { +public: + Setting() = default; + explicit Setting(Type val) : global{val} {} + ~Setting() = default; + void SetGlobal(bool to_global) { + use_global = to_global; + } + bool UsingGlobal() const { + return use_global; + } + Type GetValue(bool need_global = false) const { + if (use_global || need_global) { + return global; + } + return local; + } + void SetValue(const Type& value) { + if (use_global) { + global = value; + } else { + local = value; + } + } + +private: + bool use_global = true; + Type global{}; + Type local{}; +}; + struct Values { + // Audio + std::string audio_device_id; + std::string sink_id; + bool audio_muted; + Setting<bool> enable_audio_stretching; + Setting<float> volume; + + // Core + Setting<bool> use_multi_core; + + // Renderer + Setting<RendererBackend> renderer_backend; + bool renderer_debug; + Setting<int> vulkan_device; + + Setting<u16> resolution_factor = Setting(static_cast<u16>(1)); + Setting<int> aspect_ratio; + Setting<int> max_anisotropy; + Setting<bool> use_frame_limit; + Setting<u16> frame_limit; + Setting<bool> use_disk_shader_cache; + Setting<GPUAccuracy> gpu_accuracy; + Setting<bool> use_asynchronous_gpu_emulation; + Setting<bool> use_vsync; + Setting<bool> use_assembly_shaders; + Setting<bool> force_30fps_mode; + Setting<bool> use_fast_gpu_time; + + Setting<float> bg_red; + Setting<float> bg_green; + Setting<float> bg_blue; + // System - bool use_docked_mode; - std::optional<u32> rng_seed; + Setting<std::optional<u32>> rng_seed; // Measured in seconds since epoch - std::optional<std::chrono::seconds> custom_rtc; + Setting<std::optional<std::chrono::seconds>> custom_rtc; // Set on game boot, reset on stop. Seconds difference between current time and `custom_rtc` std::chrono::seconds custom_rtc_differential; s32 current_user; - s32 language_index; - s32 region_index; - s32 time_zone_index; - s32 sound_index; + Setting<s32> language_index; + Setting<s32> region_index; + Setting<s32> time_zone_index; + Setting<s32> sound_index; // Controls std::array<PlayerInput, 10> players; @@ -419,8 +484,7 @@ struct Values { u16 udp_input_port; u8 udp_pad_index; - // Core - bool use_multi_core; + bool use_docked_mode; // Data Storage bool use_virtual_sd; @@ -432,39 +496,6 @@ struct Values { NANDUserSize nand_user_size; SDMCSize sdmc_size; - // Renderer - RendererBackend renderer_backend; - bool renderer_debug; - int vulkan_device; - - u16 resolution_factor{1}; - int aspect_ratio; - int max_anisotropy; - bool use_frame_limit; - u16 frame_limit; - bool use_disk_shader_cache; - GPUAccuracy gpu_accuracy; - bool use_asynchronous_gpu_emulation; - bool use_vsync; - bool use_assembly_shaders; - bool force_30fps_mode; - bool use_fast_gpu_time; - - float bg_red; - float bg_green; - float bg_blue; - - std::string log_filter; - - bool use_dev_keys; - - // Audio - bool audio_muted; - std::string sink_id; - bool enable_audio_stretching; - std::string audio_device_id; - float volume; - // Debugging bool record_frame_times; bool use_gdbstub; @@ -477,7 +508,11 @@ struct Values { bool disable_cpu_opt; bool disable_macro_jit; - // BCAT + // Misceallaneous + std::string log_filter; + bool use_dev_keys; + + // Services std::string bcat_backend; bool bcat_boxcat_local; @@ -501,4 +536,7 @@ std::string GetTimeZoneString(); void Apply(); void LogSettings(); +// Restore the global state of all applicable settings in the Values struct +void RestoreGlobalState(); + } // namespace Settings diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp index c781b3cfc..78915e6db 100644 --- a/src/core/telemetry_session.cpp +++ b/src/core/telemetry_session.cpp @@ -189,19 +189,24 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader) { // Log user configuration information constexpr auto field_type = Telemetry::FieldType::UserConfig; AddField(field_type, "Audio_SinkId", Settings::values.sink_id); - AddField(field_type, "Audio_EnableAudioStretching", Settings::values.enable_audio_stretching); - AddField(field_type, "Core_UseMultiCore", Settings::values.use_multi_core); - AddField(field_type, "Renderer_Backend", TranslateRenderer(Settings::values.renderer_backend)); - AddField(field_type, "Renderer_ResolutionFactor", Settings::values.resolution_factor); - AddField(field_type, "Renderer_UseFrameLimit", Settings::values.use_frame_limit); - AddField(field_type, "Renderer_FrameLimit", Settings::values.frame_limit); - AddField(field_type, "Renderer_UseDiskShaderCache", Settings::values.use_disk_shader_cache); + AddField(field_type, "Audio_EnableAudioStretching", + Settings::values.enable_audio_stretching.GetValue()); + AddField(field_type, "Core_UseMultiCore", Settings::values.use_multi_core.GetValue()); + AddField(field_type, "Renderer_Backend", + TranslateRenderer(Settings::values.renderer_backend.GetValue())); + AddField(field_type, "Renderer_ResolutionFactor", + Settings::values.resolution_factor.GetValue()); + AddField(field_type, "Renderer_UseFrameLimit", Settings::values.use_frame_limit.GetValue()); + AddField(field_type, "Renderer_FrameLimit", Settings::values.frame_limit.GetValue()); + AddField(field_type, "Renderer_UseDiskShaderCache", + Settings::values.use_disk_shader_cache.GetValue()); AddField(field_type, "Renderer_GPUAccuracyLevel", - TranslateGPUAccuracyLevel(Settings::values.gpu_accuracy)); + TranslateGPUAccuracyLevel(Settings::values.gpu_accuracy.GetValue())); AddField(field_type, "Renderer_UseAsynchronousGpuEmulation", - Settings::values.use_asynchronous_gpu_emulation); - AddField(field_type, "Renderer_UseVsync", Settings::values.use_vsync); - AddField(field_type, "Renderer_UseAssemblyShaders", Settings::values.use_assembly_shaders); + Settings::values.use_asynchronous_gpu_emulation.GetValue()); + AddField(field_type, "Renderer_UseVsync", Settings::values.use_vsync.GetValue()); + AddField(field_type, "Renderer_UseAssemblyShaders", + Settings::values.use_assembly_shaders.GetValue()); AddField(field_type, "System_UseDockedMode", Settings::values.use_docked_mode); } diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index a9c2392b1..3bd76dd23 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -7,6 +7,10 @@ add_library(input_common STATIC main.h motion_emu.cpp motion_emu.h + gcadapter/gc_adapter.cpp + gcadapter/gc_adapter.h + gcadapter/gc_poller.cpp + gcadapter/gc_poller.h sdl/sdl.cpp sdl/sdl.h udp/client.cpp @@ -26,5 +30,7 @@ if(SDL2_FOUND) target_compile_definitions(input_common PRIVATE HAVE_SDL2) endif() +target_link_libraries(input_common PUBLIC ${LIBUSB_LIBRARIES}) + create_target_directory_groups(input_common) target_link_libraries(input_common PUBLIC core PRIVATE common Boost::boost) diff --git a/src/input_common/gcadapter/gc_adapter.cpp b/src/input_common/gcadapter/gc_adapter.cpp new file mode 100644 index 000000000..6d9f4d9eb --- /dev/null +++ b/src/input_common/gcadapter/gc_adapter.cpp @@ -0,0 +1,398 @@ +// Copyright 2014 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include <chrono> +#include <thread> +#include "common/logging/log.h" +#include "input_common/gcadapter/gc_adapter.h" + +namespace GCAdapter { + +/// Used to loop through and assign button in poller +constexpr std::array<PadButton, 12> PadButtonArray{ + PadButton::PAD_BUTTON_LEFT, PadButton::PAD_BUTTON_RIGHT, PadButton::PAD_BUTTON_DOWN, + PadButton::PAD_BUTTON_UP, PadButton::PAD_TRIGGER_Z, PadButton::PAD_TRIGGER_R, + PadButton::PAD_TRIGGER_L, PadButton::PAD_BUTTON_A, PadButton::PAD_BUTTON_B, + PadButton::PAD_BUTTON_X, PadButton::PAD_BUTTON_Y, PadButton::PAD_BUTTON_START, +}; + +Adapter::Adapter() { + if (usb_adapter_handle != nullptr) { + return; + } + LOG_INFO(Input, "GC Adapter Initialization started"); + + current_status = NO_ADAPTER_DETECTED; + + const int init_res = libusb_init(&libusb_ctx); + if (init_res == LIBUSB_SUCCESS) { + StartScanThread(); + } else { + LOG_ERROR(Input, "libusb could not be initialized. failed with error = {}", init_res); + } +} + +GCPadStatus Adapter::GetPadStatus(int port, const std::array<u8, 37>& adapter_payload) { + GCPadStatus pad = {}; + bool get_origin = false; + + ControllerTypes type = ControllerTypes(adapter_payload[1 + (9 * port)] >> 4); + if (type != ControllerTypes::None) { + get_origin = true; + } + + adapter_controllers_status[port] = type; + + static constexpr std::array<PadButton, 8> b1_buttons{ + PadButton::PAD_BUTTON_A, PadButton::PAD_BUTTON_B, PadButton::PAD_BUTTON_X, + PadButton::PAD_BUTTON_Y, PadButton::PAD_BUTTON_LEFT, PadButton::PAD_BUTTON_RIGHT, + PadButton::PAD_BUTTON_DOWN, PadButton::PAD_BUTTON_UP, + }; + + static constexpr std::array<PadButton, 4> b2_buttons{ + PadButton::PAD_BUTTON_START, + PadButton::PAD_TRIGGER_Z, + PadButton::PAD_TRIGGER_R, + PadButton::PAD_TRIGGER_L, + }; + + if (adapter_controllers_status[port] != ControllerTypes::None) { + const u8 b1 = adapter_payload[1 + (9 * port) + 1]; + const u8 b2 = adapter_payload[1 + (9 * port) + 2]; + + for (std::size_t i = 0; i < b1_buttons.size(); ++i) { + if ((b1 & (1U << i)) != 0) { + pad.button |= static_cast<u16>(b1_buttons[i]); + } + } + + for (std::size_t j = 0; j < b2_buttons.size(); ++j) { + if ((b2 & (1U << j)) != 0) { + pad.button |= static_cast<u16>(b2_buttons[j]); + } + } + + if (get_origin) { + pad.button |= PAD_GET_ORIGIN; + } + + pad.stick_x = adapter_payload[1 + (9 * port) + 3]; + pad.stick_y = adapter_payload[1 + (9 * port) + 4]; + pad.substick_x = adapter_payload[1 + (9 * port) + 5]; + pad.substick_y = adapter_payload[1 + (9 * port) + 6]; + pad.trigger_left = adapter_payload[1 + (9 * port) + 7]; + pad.trigger_right = adapter_payload[1 + (9 * port) + 8]; + } + return pad; +} + +void Adapter::PadToState(const GCPadStatus& pad, GCState& state) { + for (const auto& button : PadButtonArray) { + const u16 button_value = static_cast<u16>(button); + state.buttons.insert_or_assign(button_value, pad.button & button_value); + } + + state.axes.insert_or_assign(static_cast<u8>(PadAxes::StickX), pad.stick_x); + state.axes.insert_or_assign(static_cast<u8>(PadAxes::StickY), pad.stick_y); + state.axes.insert_or_assign(static_cast<u8>(PadAxes::SubstickX), pad.substick_x); + state.axes.insert_or_assign(static_cast<u8>(PadAxes::SubstickY), pad.substick_y); + state.axes.insert_or_assign(static_cast<u8>(PadAxes::TriggerLeft), pad.trigger_left); + state.axes.insert_or_assign(static_cast<u8>(PadAxes::TriggerRight), pad.trigger_right); +} + +void Adapter::Read() { + LOG_DEBUG(Input, "GC Adapter Read() thread started"); + + int payload_size_in, payload_size_copy; + std::array<u8, 37> adapter_payload; + std::array<u8, 37> adapter_payload_copy; + std::array<GCPadStatus, 4> pads; + + while (adapter_thread_running) { + libusb_interrupt_transfer(usb_adapter_handle, input_endpoint, adapter_payload.data(), + sizeof(adapter_payload), &payload_size_in, 16); + payload_size_copy = 0; + // this mutex might be redundant? + { + std::lock_guard<std::mutex> lk(s_mutex); + std::copy(std::begin(adapter_payload), std::end(adapter_payload), + std::begin(adapter_payload_copy)); + payload_size_copy = payload_size_in; + } + + if (payload_size_copy != sizeof(adapter_payload_copy) || + adapter_payload_copy[0] != LIBUSB_DT_HID) { + LOG_ERROR(Input, "error reading payload (size: {}, type: {:02x})", payload_size_copy, + adapter_payload_copy[0]); + adapter_thread_running = false; // error reading from adapter, stop reading. + break; + } + for (std::size_t port = 0; port < pads.size(); ++port) { + pads[port] = GetPadStatus(port, adapter_payload_copy); + if (DeviceConnected(port) && configuring) { + if (pads[port].button != PAD_GET_ORIGIN) { + pad_queue[port].Push(pads[port]); + } + + // Accounting for a threshold here because of some controller variance + if (pads[port].stick_x > pads[port].MAIN_STICK_CENTER_X + pads[port].THRESHOLD || + pads[port].stick_x < pads[port].MAIN_STICK_CENTER_X - pads[port].THRESHOLD) { + pads[port].axis = GCAdapter::PadAxes::StickX; + pads[port].axis_value = pads[port].stick_x; + pad_queue[port].Push(pads[port]); + } + if (pads[port].stick_y > pads[port].MAIN_STICK_CENTER_Y + pads[port].THRESHOLD || + pads[port].stick_y < pads[port].MAIN_STICK_CENTER_Y - pads[port].THRESHOLD) { + pads[port].axis = GCAdapter::PadAxes::StickY; + pads[port].axis_value = pads[port].stick_y; + pad_queue[port].Push(pads[port]); + } + if (pads[port].substick_x > pads[port].C_STICK_CENTER_X + pads[port].THRESHOLD || + pads[port].substick_x < pads[port].C_STICK_CENTER_X - pads[port].THRESHOLD) { + pads[port].axis = GCAdapter::PadAxes::SubstickX; + pads[port].axis_value = pads[port].substick_x; + pad_queue[port].Push(pads[port]); + } + if (pads[port].substick_y > pads[port].C_STICK_CENTER_Y + pads[port].THRESHOLD || + pads[port].substick_y < pads[port].C_STICK_CENTER_Y - pads[port].THRESHOLD) { + pads[port].axis = GCAdapter::PadAxes::SubstickY; + pads[port].axis_value = pads[port].substick_y; + pad_queue[port].Push(pads[port]); + } + if (pads[port].trigger_left > pads[port].TRIGGER_THRESHOLD) { + pads[port].axis = GCAdapter::PadAxes::TriggerLeft; + pads[port].axis_value = pads[port].trigger_left; + pad_queue[port].Push(pads[port]); + } + if (pads[port].trigger_right > pads[port].TRIGGER_THRESHOLD) { + pads[port].axis = GCAdapter::PadAxes::TriggerRight; + pads[port].axis_value = pads[port].trigger_right; + pad_queue[port].Push(pads[port]); + } + } + PadToState(pads[port], state[port]); + } + std::this_thread::yield(); + } +} + +void Adapter::ScanThreadFunc() { + LOG_INFO(Input, "GC Adapter scanning thread started"); + + while (detect_thread_running) { + if (usb_adapter_handle == nullptr) { + std::lock_guard<std::mutex> lk(initialization_mutex); + Setup(); + } + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } +} + +void Adapter::StartScanThread() { + if (detect_thread_running) { + return; + } + if (!libusb_ctx) { + return; + } + + detect_thread_running = true; + detect_thread = std::thread([=] { ScanThreadFunc(); }); +} + +void Adapter::StopScanThread() { + detect_thread_running = false; + detect_thread.join(); +} + +void Adapter::Setup() { + // Reset the error status in case the adapter gets unplugged + if (current_status < 0) { + current_status = NO_ADAPTER_DETECTED; + } + + adapter_controllers_status.fill(ControllerTypes::None); + + // pointer to list of connected usb devices + libusb_device** devices{}; + + // populate the list of devices, get the count + const ssize_t device_count = libusb_get_device_list(libusb_ctx, &devices); + if (device_count < 0) { + LOG_ERROR(Input, "libusb_get_device_list failed with error: {}", device_count); + detect_thread_running = false; // Stop the loop constantly checking for gc adapter + // TODO: For hotplug+gc adapter checkbox implementation, revert this. + return; + } + + if (devices != nullptr) { + for (std::size_t index = 0; index < device_count; ++index) { + if (CheckDeviceAccess(devices[index])) { + // GC Adapter found and accessible, registering it + GetGCEndpoint(devices[index]); + break; + } + } + libusb_free_device_list(devices, 1); + } +} + +bool Adapter::CheckDeviceAccess(libusb_device* device) { + libusb_device_descriptor desc; + const int get_descriptor_error = libusb_get_device_descriptor(device, &desc); + if (get_descriptor_error) { + // could not acquire the descriptor, no point in trying to use it. + LOG_ERROR(Input, "libusb_get_device_descriptor failed with error: {}", + get_descriptor_error); + return false; + } + + if (desc.idVendor != 0x057e || desc.idProduct != 0x0337) { + // This isn't the device we are looking for. + return false; + } + const int open_error = libusb_open(device, &usb_adapter_handle); + + if (open_error == LIBUSB_ERROR_ACCESS) { + LOG_ERROR(Input, "Yuzu can not gain access to this device: ID {:04X}:{:04X}.", + desc.idVendor, desc.idProduct); + return false; + } + if (open_error) { + LOG_ERROR(Input, "libusb_open failed to open device with error = {}", open_error); + return false; + } + + int kernel_driver_error = libusb_kernel_driver_active(usb_adapter_handle, 0); + if (kernel_driver_error == 1) { + kernel_driver_error = libusb_detach_kernel_driver(usb_adapter_handle, 0); + if (kernel_driver_error != 0 && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) { + LOG_ERROR(Input, "libusb_detach_kernel_driver failed with error = {}", + kernel_driver_error); + } + } + + if (kernel_driver_error && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) { + libusb_close(usb_adapter_handle); + usb_adapter_handle = nullptr; + return false; + } + + const int interface_claim_error = libusb_claim_interface(usb_adapter_handle, 0); + if (interface_claim_error) { + LOG_ERROR(Input, "libusb_claim_interface failed with error = {}", interface_claim_error); + libusb_close(usb_adapter_handle); + usb_adapter_handle = nullptr; + return false; + } + + return true; +} + +void Adapter::GetGCEndpoint(libusb_device* device) { + libusb_config_descriptor* config = nullptr; + const int config_descriptor_return = libusb_get_config_descriptor(device, 0, &config); + if (config_descriptor_return != LIBUSB_SUCCESS) { + LOG_ERROR(Input, "libusb_get_config_descriptor failed with error = {}", + config_descriptor_return); + return; + } + + for (u8 ic = 0; ic < config->bNumInterfaces; ic++) { + const libusb_interface* interfaceContainer = &config->interface[ic]; + for (int i = 0; i < interfaceContainer->num_altsetting; i++) { + const libusb_interface_descriptor* interface = &interfaceContainer->altsetting[i]; + for (u8 e = 0; e < interface->bNumEndpoints; e++) { + const libusb_endpoint_descriptor* endpoint = &interface->endpoint[e]; + if (endpoint->bEndpointAddress & LIBUSB_ENDPOINT_IN) { + input_endpoint = endpoint->bEndpointAddress; + } else { + output_endpoint = endpoint->bEndpointAddress; + } + } + } + } + // This transfer seems to be responsible for clearing the state of the adapter + // Used to clear the "busy" state of when the device is unexpectedly unplugged + unsigned char clear_payload = 0x13; + libusb_interrupt_transfer(usb_adapter_handle, output_endpoint, &clear_payload, + sizeof(clear_payload), nullptr, 16); + + adapter_thread_running = true; + current_status = ADAPTER_DETECTED; + adapter_input_thread = std::thread([=] { Read(); }); // Read input +} + +Adapter::~Adapter() { + StopScanThread(); + Reset(); +} + +void Adapter::Reset() { + std::unique_lock<std::mutex> lock(initialization_mutex, std::defer_lock); + if (!lock.try_lock()) { + return; + } + if (current_status != ADAPTER_DETECTED) { + return; + } + + if (adapter_thread_running) { + adapter_thread_running = false; + } + adapter_input_thread.join(); + + adapter_controllers_status.fill(ControllerTypes::None); + current_status = NO_ADAPTER_DETECTED; + + if (usb_adapter_handle) { + libusb_release_interface(usb_adapter_handle, 1); + libusb_close(usb_adapter_handle); + usb_adapter_handle = nullptr; + } + + if (libusb_ctx) { + libusb_exit(libusb_ctx); + } +} + +bool Adapter::DeviceConnected(int port) { + return adapter_controllers_status[port] != ControllerTypes::None; +} + +void Adapter::ResetDeviceType(int port) { + adapter_controllers_status[port] = ControllerTypes::None; +} + +void Adapter::BeginConfiguration() { + for (auto& pq : pad_queue) { + pq.Clear(); + } + configuring = true; +} + +void Adapter::EndConfiguration() { + for (auto& pq : pad_queue) { + pq.Clear(); + } + configuring = false; +} + +std::array<Common::SPSCQueue<GCPadStatus>, 4>& Adapter::GetPadQueue() { + return pad_queue; +} + +const std::array<Common::SPSCQueue<GCPadStatus>, 4>& Adapter::GetPadQueue() const { + return pad_queue; +} + +std::array<GCState, 4>& Adapter::GetPadState() { + return state; +} + +const std::array<GCState, 4>& Adapter::GetPadState() const { + return state; +} + +} // namespace GCAdapter diff --git a/src/input_common/gcadapter/gc_adapter.h b/src/input_common/gcadapter/gc_adapter.h new file mode 100644 index 000000000..b1c2a1958 --- /dev/null +++ b/src/input_common/gcadapter/gc_adapter.h @@ -0,0 +1,161 @@ +// Copyright 2014 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once +#include <algorithm> +#include <functional> +#include <mutex> +#include <thread> +#include <unordered_map> +#include <libusb.h> +#include "common/common_types.h" +#include "common/threadsafe_queue.h" + +namespace GCAdapter { + +enum { + PAD_USE_ORIGIN = 0x0080, + PAD_GET_ORIGIN = 0x2000, + PAD_ERR_STATUS = 0x8000, +}; + +enum class PadButton { + PAD_BUTTON_LEFT = 0x0001, + PAD_BUTTON_RIGHT = 0x0002, + PAD_BUTTON_DOWN = 0x0004, + PAD_BUTTON_UP = 0x0008, + PAD_TRIGGER_Z = 0x0010, + PAD_TRIGGER_R = 0x0020, + PAD_TRIGGER_L = 0x0040, + PAD_BUTTON_A = 0x0100, + PAD_BUTTON_B = 0x0200, + PAD_BUTTON_X = 0x0400, + PAD_BUTTON_Y = 0x0800, + PAD_BUTTON_START = 0x1000, + // Below is for compatibility with "AxisButton" type + PAD_STICK = 0x2000, +}; + +extern const std::array<PadButton, 12> PadButtonArray; + +enum class PadAxes : u8 { + StickX, + StickY, + SubstickX, + SubstickY, + TriggerLeft, + TriggerRight, + Undefined, +}; + +struct GCPadStatus { + u16 button{}; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits + u8 stick_x{}; // 0 <= stick_x <= 255 + u8 stick_y{}; // 0 <= stick_y <= 255 + u8 substick_x{}; // 0 <= substick_x <= 255 + u8 substick_y{}; // 0 <= substick_y <= 255 + u8 trigger_left{}; // 0 <= trigger_left <= 255 + u8 trigger_right{}; // 0 <= trigger_right <= 255 + + static constexpr u8 MAIN_STICK_CENTER_X = 0x80; + static constexpr u8 MAIN_STICK_CENTER_Y = 0x80; + static constexpr u8 MAIN_STICK_RADIUS = 0x7f; + static constexpr u8 C_STICK_CENTER_X = 0x80; + static constexpr u8 C_STICK_CENTER_Y = 0x80; + static constexpr u8 C_STICK_RADIUS = 0x7f; + static constexpr u8 THRESHOLD = 10; + + // 256/4, at least a quarter press to count as a press. For polling mostly + static constexpr u8 TRIGGER_THRESHOLD = 64; + + u8 port{}; + PadAxes axis{PadAxes::Undefined}; + u8 axis_value{255}; +}; + +struct GCState { + std::unordered_map<int, bool> buttons; + std::unordered_map<int, u16> axes; +}; + +enum class ControllerTypes { None, Wired, Wireless }; + +enum { + NO_ADAPTER_DETECTED = 0, + ADAPTER_DETECTED = 1, +}; + +class Adapter { +public: + /// Initialize the GC Adapter capture and read sequence + Adapter(); + + /// Close the adapter read thread and release the adapter + ~Adapter(); + /// Used for polling + void BeginConfiguration(); + void EndConfiguration(); + + std::array<Common::SPSCQueue<GCPadStatus>, 4>& GetPadQueue(); + const std::array<Common::SPSCQueue<GCPadStatus>, 4>& GetPadQueue() const; + + std::array<GCState, 4>& GetPadState(); + const std::array<GCState, 4>& GetPadState() const; + +private: + GCPadStatus GetPadStatus(int port, const std::array<u8, 37>& adapter_payload); + + void PadToState(const GCPadStatus& pad, GCState& state); + + void Read(); + void ScanThreadFunc(); + /// Begin scanning for the GC Adapter. + void StartScanThread(); + + /// Stop scanning for the adapter + void StopScanThread(); + + /// Returns true if there is a device connected to port + bool DeviceConnected(int port); + + /// Resets status of device connected to port + void ResetDeviceType(int port); + + /// Returns true if we successfully gain access to GC Adapter + bool CheckDeviceAccess(libusb_device* device); + + /// Captures GC Adapter endpoint address, + void GetGCEndpoint(libusb_device* device); + + /// For shutting down, clear all data, join all threads, release usb + void Reset(); + + /// For use in initialization, querying devices to find the adapter + void Setup(); + + int current_status = NO_ADAPTER_DETECTED; + libusb_device_handle* usb_adapter_handle = nullptr; + std::array<ControllerTypes, 4> adapter_controllers_status{}; + + std::mutex s_mutex; + + std::thread adapter_input_thread; + bool adapter_thread_running; + + std::mutex initialization_mutex; + std::thread detect_thread; + bool detect_thread_running = false; + + libusb_context* libusb_ctx; + + u8 input_endpoint = 0; + u8 output_endpoint = 0; + + bool configuring = false; + + std::array<Common::SPSCQueue<GCPadStatus>, 4> pad_queue; + std::array<GCState, 4> state; +}; + +} // namespace GCAdapter diff --git a/src/input_common/gcadapter/gc_poller.cpp b/src/input_common/gcadapter/gc_poller.cpp new file mode 100644 index 000000000..385ce8430 --- /dev/null +++ b/src/input_common/gcadapter/gc_poller.cpp @@ -0,0 +1,272 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <atomic> +#include <list> +#include <mutex> +#include <utility> +#include "common/threadsafe_queue.h" +#include "input_common/gcadapter/gc_adapter.h" +#include "input_common/gcadapter/gc_poller.h" + +namespace InputCommon { + +class GCButton final : public Input::ButtonDevice { +public: + explicit GCButton(int port_, int button_, GCAdapter::Adapter* adapter) + : port(port_), button(button_), gcadapter(adapter) {} + + ~GCButton() override; + + bool GetStatus() const override { + return gcadapter->GetPadState()[port].buttons.at(button); + } + +private: + const int port; + const int button; + GCAdapter::Adapter* gcadapter; +}; + +class GCAxisButton final : public Input::ButtonDevice { +public: + explicit GCAxisButton(int port_, int axis_, float threshold_, bool trigger_if_greater_, + GCAdapter::Adapter* adapter) + : port(port_), axis(axis_), threshold(threshold_), trigger_if_greater(trigger_if_greater_), + gcadapter(adapter) { + // L/R triggers range is only in positive direction beginning near 0 + // 0.0 threshold equates to near half trigger press, but threshold accounts for variability. + if (axis > 3) { + threshold *= -0.5; + } + } + + bool GetStatus() const override { + const float axis_value = (gcadapter->GetPadState()[port].axes.at(axis) - 128.0f) / 128.0f; + if (trigger_if_greater) { + // TODO: Might be worthwile to set a slider for the trigger threshold. It is currently + // always set to 0.5 in configure_input_player.cpp ZL/ZR HandleClick + return axis_value > threshold; + } + return axis_value < -threshold; + } + +private: + const int port; + const int axis; + float threshold; + bool trigger_if_greater; + GCAdapter::Adapter* gcadapter; +}; + +GCButtonFactory::GCButtonFactory(std::shared_ptr<GCAdapter::Adapter> adapter_) + : adapter(std::move(adapter_)) {} + +GCButton::~GCButton() = default; + +std::unique_ptr<Input::ButtonDevice> GCButtonFactory::Create(const Common::ParamPackage& params) { + const int button_id = params.Get("button", 0); + const int port = params.Get("port", 0); + + constexpr int PAD_STICK_ID = static_cast<u16>(GCAdapter::PadButton::PAD_STICK); + + // button is not an axis/stick button + if (button_id != PAD_STICK_ID) { + auto button = std::make_unique<GCButton>(port, button_id, adapter.get()); + return std::move(button); + } + + // For Axis buttons, used by the binary sticks. + if (button_id == PAD_STICK_ID) { + const int axis = params.Get("axis", 0); + const float threshold = params.Get("threshold", 0.25f); + const std::string direction_name = params.Get("direction", ""); + bool trigger_if_greater; + if (direction_name == "+") { + trigger_if_greater = true; + } else if (direction_name == "-") { + trigger_if_greater = false; + } else { + trigger_if_greater = true; + LOG_ERROR(Input, "Unknown direction {}", direction_name); + } + return std::make_unique<GCAxisButton>(port, axis, threshold, trigger_if_greater, + adapter.get()); + } +} + +Common::ParamPackage GCButtonFactory::GetNextInput() { + Common::ParamPackage params; + GCAdapter::GCPadStatus pad; + auto& queue = adapter->GetPadQueue(); + for (std::size_t port = 0; port < queue.size(); ++port) { + while (queue[port].Pop(pad)) { + // This while loop will break on the earliest detected button + params.Set("engine", "gcpad"); + params.Set("port", static_cast<int>(port)); + for (const auto& button : GCAdapter::PadButtonArray) { + const u16 button_value = static_cast<u16>(button); + if (pad.button & button_value) { + params.Set("button", button_value); + break; + } + } + + // For Axis button implementation + if (pad.axis != GCAdapter::PadAxes::Undefined) { + params.Set("axis", static_cast<u8>(pad.axis)); + params.Set("button", static_cast<u16>(GCAdapter::PadButton::PAD_STICK)); + if (pad.axis_value > 128) { + params.Set("direction", "+"); + params.Set("threshold", "0.25"); + } else { + params.Set("direction", "-"); + params.Set("threshold", "-0.25"); + } + break; + } + } + } + return params; +} + +void GCButtonFactory::BeginConfiguration() { + polling = true; + adapter->BeginConfiguration(); +} + +void GCButtonFactory::EndConfiguration() { + polling = false; + adapter->EndConfiguration(); +} + +class GCAnalog final : public Input::AnalogDevice { +public: + GCAnalog(int port_, int axis_x_, int axis_y_, float deadzone_, GCAdapter::Adapter* adapter) + : port(port_), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_), gcadapter(adapter) {} + + float GetAxis(int axis) const { + std::lock_guard lock{mutex}; + // division is not by a perfect 128 to account for some variance in center location + // e.g. my device idled at 131 in X, 120 in Y, and full range of motion was in range + // [20-230] + return (gcadapter->GetPadState()[port].axes.at(axis) - 128.0f) / 95.0f; + } + + std::pair<float, float> GetAnalog(int axis_x, int axis_y) const { + float x = GetAxis(axis_x); + float y = GetAxis(axis_y); + + // Make sure the coordinates are in the unit circle, + // otherwise normalize it. + float r = x * x + y * y; + if (r > 1.0f) { + r = std::sqrt(r); + x /= r; + y /= r; + } + + return {x, y}; + } + + std::tuple<float, float> GetStatus() const override { + const auto [x, y] = GetAnalog(axis_x, axis_y); + const float r = std::sqrt((x * x) + (y * y)); + if (r > deadzone) { + return {x / r * (r - deadzone) / (1 - deadzone), + y / r * (r - deadzone) / (1 - deadzone)}; + } + return {0.0f, 0.0f}; + } + + bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override { + const auto [x, y] = GetStatus(); + const float directional_deadzone = 0.4f; + switch (direction) { + case Input::AnalogDirection::RIGHT: + return x > directional_deadzone; + case Input::AnalogDirection::LEFT: + return x < -directional_deadzone; + case Input::AnalogDirection::UP: + return y > directional_deadzone; + case Input::AnalogDirection::DOWN: + return y < -directional_deadzone; + } + return false; + } + +private: + const int port; + const int axis_x; + const int axis_y; + const float deadzone; + mutable std::mutex mutex; + GCAdapter::Adapter* gcadapter; +}; + +/// An analog device factory that creates analog devices from GC Adapter +GCAnalogFactory::GCAnalogFactory(std::shared_ptr<GCAdapter::Adapter> adapter_) + : adapter(std::move(adapter_)) {} + +/** + * Creates analog device from joystick axes + * @param params contains parameters for creating the device: + * - "port": the nth gcpad on the adapter + * - "axis_x": the index of the axis to be bind as x-axis + * - "axis_y": the index of the axis to be bind as y-axis + */ +std::unique_ptr<Input::AnalogDevice> GCAnalogFactory::Create(const Common::ParamPackage& params) { + const int port = params.Get("port", 0); + const int axis_x = params.Get("axis_x", 0); + const int axis_y = params.Get("axis_y", 1); + const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, .99f); + + return std::make_unique<GCAnalog>(port, axis_x, axis_y, deadzone, adapter.get()); +} + +void GCAnalogFactory::BeginConfiguration() { + polling = true; + adapter->BeginConfiguration(); +} + +void GCAnalogFactory::EndConfiguration() { + polling = false; + adapter->EndConfiguration(); +} + +Common::ParamPackage GCAnalogFactory::GetNextInput() { + GCAdapter::GCPadStatus pad; + auto& queue = adapter->GetPadQueue(); + for (std::size_t port = 0; port < queue.size(); ++port) { + while (queue[port].Pop(pad)) { + if (pad.axis == GCAdapter::PadAxes::Undefined || + std::abs((pad.axis_value - 128.0f) / 128.0f) < 0.1) { + continue; + } + // An analog device needs two axes, so we need to store the axis for later and wait for + // a second input event. The axes also must be from the same joystick. + const u8 axis = static_cast<u8>(pad.axis); + if (analog_x_axis == -1) { + analog_x_axis = axis; + controller_number = port; + } else if (analog_y_axis == -1 && analog_x_axis != axis && controller_number == port) { + analog_y_axis = axis; + } + } + } + Common::ParamPackage params; + if (analog_x_axis != -1 && analog_y_axis != -1) { + params.Set("engine", "gcpad"); + params.Set("port", controller_number); + params.Set("axis_x", analog_x_axis); + params.Set("axis_y", analog_y_axis); + analog_x_axis = -1; + analog_y_axis = -1; + controller_number = -1; + return params; + } + return params; +} + +} // namespace InputCommon diff --git a/src/input_common/gcadapter/gc_poller.h b/src/input_common/gcadapter/gc_poller.h new file mode 100644 index 000000000..e96af7d51 --- /dev/null +++ b/src/input_common/gcadapter/gc_poller.h @@ -0,0 +1,67 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include "core/frontend/input.h" +#include "input_common/gcadapter/gc_adapter.h" + +namespace InputCommon { + +/** + * A button device factory representing a gcpad. It receives gcpad events and forward them + * to all button devices it created. + */ +class GCButtonFactory final : public Input::Factory<Input::ButtonDevice> { +public: + explicit GCButtonFactory(std::shared_ptr<GCAdapter::Adapter> adapter_); + + /** + * Creates a button device from a button press + * @param params contains parameters for creating the device: + * - "code": the code of the key to bind with the button + */ + std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override; + + Common::ParamPackage GetNextInput(); + + /// For device input configuration/polling + void BeginConfiguration(); + void EndConfiguration(); + + bool IsPolling() const { + return polling; + } + +private: + std::shared_ptr<GCAdapter::Adapter> adapter; + bool polling = false; +}; + +/// An analog device factory that creates analog devices from GC Adapter +class GCAnalogFactory final : public Input::Factory<Input::AnalogDevice> { +public: + explicit GCAnalogFactory(std::shared_ptr<GCAdapter::Adapter> adapter_); + + std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override; + Common::ParamPackage GetNextInput(); + + /// For device input configuration/polling + void BeginConfiguration(); + void EndConfiguration(); + + bool IsPolling() const { + return polling; + } + +private: + std::shared_ptr<GCAdapter::Adapter> adapter; + int analog_x_axis = -1; + int analog_y_axis = -1; + int controller_number = -1; + bool polling = false; +}; + +} // namespace InputCommon diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index 95e351e24..fd0af1019 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -4,8 +4,11 @@ #include <memory> #include <thread> +#include <libusb.h> #include "common/param_package.h" #include "input_common/analog_from_button.h" +#include "input_common/gcadapter/gc_adapter.h" +#include "input_common/gcadapter/gc_poller.h" #include "input_common/keyboard.h" #include "input_common/main.h" #include "input_common/motion_emu.h" @@ -22,8 +25,16 @@ static std::shared_ptr<MotionEmu> motion_emu; static std::unique_ptr<SDL::State> sdl; #endif static std::unique_ptr<CemuhookUDP::State> udp; +static std::shared_ptr<GCButtonFactory> gcbuttons; +static std::shared_ptr<GCAnalogFactory> gcanalog; void Init() { + auto gcadapter = std::make_shared<GCAdapter::Adapter>(); + gcbuttons = std::make_shared<GCButtonFactory>(gcadapter); + Input::RegisterFactory<Input::ButtonDevice>("gcpad", gcbuttons); + gcanalog = std::make_shared<GCAnalogFactory>(gcadapter); + Input::RegisterFactory<Input::AnalogDevice>("gcpad", gcanalog); + keyboard = std::make_shared<Keyboard>(); Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard); Input::RegisterFactory<Input::AnalogDevice>("analog_from_button", @@ -48,6 +59,11 @@ void Shutdown() { sdl.reset(); #endif udp.reset(); + Input::UnregisterFactory<Input::ButtonDevice>("gcpad"); + Input::UnregisterFactory<Input::AnalogDevice>("gcpad"); + + gcbuttons.reset(); + gcanalog.reset(); } Keyboard* GetKeyboard() { @@ -58,6 +74,14 @@ MotionEmu* GetMotionEmu() { return motion_emu.get(); } +GCButtonFactory* GetGCButtons() { + return gcbuttons.get(); +} + +GCAnalogFactory* GetGCAnalogs() { + return gcanalog.get(); +} + std::string GenerateKeyboardParam(int key_code) { Common::ParamPackage param{ {"engine", "keyboard"}, diff --git a/src/input_common/main.h b/src/input_common/main.h index 77a0ce90b..0e32856f6 100644 --- a/src/input_common/main.h +++ b/src/input_common/main.h @@ -7,6 +7,7 @@ #include <memory> #include <string> #include <vector> +#include "input_common/gcadapter/gc_poller.h" namespace Common { class ParamPackage; @@ -30,6 +31,10 @@ class MotionEmu; /// Gets the motion emulation factory. MotionEmu* GetMotionEmu(); +GCButtonFactory* GetGCButtons(); + +GCAnalogFactory* GetGCAnalogs(); + /// Generates a serialized param package for creating a keyboard button device std::string GenerateKeyboardParam(int key_code); diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index cf8bdd021..dd7ce8c99 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -96,7 +96,8 @@ public: } if (is_written) { map->MarkAsModified(true, GetModifiedTicks()); - if (Settings::IsGPULevelHigh() && Settings::values.use_asynchronous_gpu_emulation) { + if (Settings::IsGPULevelHigh() && + Settings::values.use_asynchronous_gpu_emulation.GetValue()) { MarkForAsyncFlush(map); } if (!map->is_written) { @@ -322,8 +323,7 @@ protected: } private: - MapInterval* MapAddress(const Buffer* block, GPUVAddr gpu_addr, VAddr cpu_addr, - std::size_t size) { + MapInterval* MapAddress(Buffer* block, GPUVAddr gpu_addr, VAddr cpu_addr, std::size_t size) { const VectorMapInterval overlaps = GetMapsInRange(cpu_addr, size); if (overlaps.empty()) { auto& memory_manager = system.GPU().MemoryManager(); @@ -370,15 +370,15 @@ private: } if (modified_inheritance) { map->MarkAsModified(true, GetModifiedTicks()); - if (Settings::IsGPULevelHigh() && Settings::values.use_asynchronous_gpu_emulation) { + if (Settings::IsGPULevelHigh() && + Settings::values.use_asynchronous_gpu_emulation.GetValue()) { MarkForAsyncFlush(map); } } return map; } - void UpdateBlock(const Buffer* block, VAddr start, VAddr end, - const VectorMapInterval& overlaps) { + void UpdateBlock(Buffer* block, VAddr start, VAddr end, const VectorMapInterval& overlaps) { const IntervalType base_interval{start, end}; IntervalSet interval_set{}; interval_set.add(base_interval); diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index 482e49711..758bfe148 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp @@ -157,7 +157,7 @@ u64 GPU::GetTicks() const { constexpr u64 gpu_ticks_den = 625; u64 nanoseconds = system.CoreTiming().GetGlobalTimeNs().count(); - if (Settings::values.use_fast_gpu_time) { + if (Settings::values.use_fast_gpu_time.GetValue()) { nanoseconds /= 256; } const u64 nanoseconds_num = nanoseconds / gpu_ticks_den; diff --git a/src/video_core/query_cache.h b/src/video_core/query_cache.h index e12dab899..0d3a88765 100644 --- a/src/video_core/query_cache.h +++ b/src/video_core/query_cache.h @@ -132,7 +132,7 @@ public: } query->BindCounter(Stream(type).Current(), timestamp); - if (Settings::values.use_asynchronous_gpu_emulation) { + if (Settings::values.use_asynchronous_gpu_emulation.GetValue()) { AsyncFlushQuery(cpu_addr); } } diff --git a/src/video_core/renderer_base.cpp b/src/video_core/renderer_base.cpp index 919d1f2d4..dfb06e87e 100644 --- a/src/video_core/renderer_base.cpp +++ b/src/video_core/renderer_base.cpp @@ -18,7 +18,7 @@ RendererBase::~RendererBase() = default; void RendererBase::RefreshBaseSettings() { UpdateCurrentFramebufferLayout(); - renderer_settings.use_framelimiter = Settings::values.use_frame_limit; + renderer_settings.use_framelimiter = Settings::values.use_frame_limit.GetValue(); renderer_settings.set_background_color = true; } diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp index d9f7b4cc6..e461e4c70 100644 --- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp @@ -34,20 +34,27 @@ Buffer::Buffer(const Device& device, VAddr cpu_addr, std::size_t size) Buffer::~Buffer() = default; -void Buffer::Upload(std::size_t offset, std::size_t size, const u8* data) const { +void Buffer::Upload(std::size_t offset, std::size_t size, const u8* data) { glNamedBufferSubData(Handle(), static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size), data); } -void Buffer::Download(std::size_t offset, std::size_t size, u8* data) const { +void Buffer::Download(std::size_t offset, std::size_t size, u8* data) { MICROPROFILE_SCOPE(OpenGL_Buffer_Download); + const GLsizeiptr gl_size = static_cast<GLsizeiptr>(size); + const GLintptr gl_offset = static_cast<GLintptr>(offset); + if (read_buffer.handle == 0) { + read_buffer.Create(); + glNamedBufferData(read_buffer.handle, static_cast<GLsizeiptr>(Size()), nullptr, + GL_STREAM_READ); + } glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT); - glGetNamedBufferSubData(Handle(), static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size), - data); + glCopyNamedBufferSubData(gl_buffer.handle, read_buffer.handle, gl_offset, gl_offset, gl_size); + glGetNamedBufferSubData(read_buffer.handle, gl_offset, gl_size, data); } void Buffer::CopyFrom(const Buffer& src, std::size_t src_offset, std::size_t dst_offset, - std::size_t size) const { + std::size_t size) { glCopyNamedBufferSubData(src.Handle(), Handle(), static_cast<GLintptr>(src_offset), static_cast<GLintptr>(dst_offset), static_cast<GLsizeiptr>(size)); } diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h index 59d95adbc..88fdc0536 100644 --- a/src/video_core/renderer_opengl/gl_buffer_cache.h +++ b/src/video_core/renderer_opengl/gl_buffer_cache.h @@ -28,12 +28,12 @@ public: explicit Buffer(const Device& device, VAddr cpu_addr, std::size_t size); ~Buffer(); - void Upload(std::size_t offset, std::size_t size, const u8* data) const; + void Upload(std::size_t offset, std::size_t size, const u8* data); - void Download(std::size_t offset, std::size_t size, u8* data) const; + void Download(std::size_t offset, std::size_t size, u8* data); void CopyFrom(const Buffer& src, std::size_t src_offset, std::size_t dst_offset, - std::size_t size) const; + std::size_t size); GLuint Handle() const noexcept { return gl_buffer.handle; @@ -45,6 +45,7 @@ public: private: OGLBuffer gl_buffer; + OGLBuffer read_buffer; u64 gpu_address = 0; }; diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp index 208fc6167..c1f20f0ab 100644 --- a/src/video_core/renderer_opengl/gl_device.cpp +++ b/src/video_core/renderer_opengl/gl_device.cpp @@ -229,15 +229,15 @@ Device::Device() // uniform buffers as "push constants" has_fast_buffer_sub_data = is_nvidia && !disable_fast_buffer_sub_data; - use_assembly_shaders = Settings::values.use_assembly_shaders && GLAD_GL_NV_gpu_program5 && - GLAD_GL_NV_compute_program5 && GLAD_GL_NV_transform_feedback && - GLAD_GL_NV_transform_feedback2; + use_assembly_shaders = Settings::values.use_assembly_shaders.GetValue() && + GLAD_GL_NV_gpu_program5 && GLAD_GL_NV_compute_program5 && + GLAD_GL_NV_transform_feedback && GLAD_GL_NV_transform_feedback2; LOG_INFO(Render_OpenGL, "Renderer_VariableAOFFI: {}", has_variable_aoffi); LOG_INFO(Render_OpenGL, "Renderer_ComponentIndexingBug: {}", has_component_indexing_bug); LOG_INFO(Render_OpenGL, "Renderer_PreciseBug: {}", has_precise_bug); - if (Settings::values.use_assembly_shaders && !use_assembly_shaders) { + if (Settings::values.use_assembly_shaders.GetValue() && !use_assembly_shaders) { LOG_ERROR(Render_OpenGL, "Assembly shaders enabled but not supported"); } } diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp index 653c3f2f9..2dcc2b0eb 100644 --- a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp @@ -213,7 +213,7 @@ ShaderDiskCacheOpenGL::~ShaderDiskCacheOpenGL() = default; std::optional<std::vector<ShaderDiskCacheEntry>> ShaderDiskCacheOpenGL::LoadTransferable() { // Skip games without title id const bool has_title_id = system.CurrentProcess()->GetTitleID() != 0; - if (!Settings::values.use_disk_shader_cache || !has_title_id) { + if (!Settings::values.use_disk_shader_cache.GetValue() || !has_title_id) { return {}; } diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h index 774e70a5b..fe9bd4b5a 100644 --- a/src/video_core/renderer_opengl/maxwell_to_gl.h +++ b/src/video_core/renderer_opengl/maxwell_to_gl.h @@ -191,6 +191,12 @@ inline GLenum WrapMode(Tegra::Texture::WrapMode wrap_mode) { } else { return GL_MIRROR_CLAMP_TO_EDGE; } + case Tegra::Texture::WrapMode::MirrorOnceClampOGL: + if (GL_EXT_texture_mirror_clamp) { + return GL_MIRROR_CLAMP_EXT; + } else { + return GL_MIRROR_CLAMP_TO_EDGE; + } } UNIMPLEMENTED_MSG("Unimplemented texture wrap mode={}", static_cast<u32>(wrap_mode)); return GL_REPEAT; diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index c40adb6e7..e66cdc083 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -455,8 +455,8 @@ void RendererOpenGL::LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color void RendererOpenGL::InitOpenGLObjects() { frame_mailbox = std::make_unique<FrameMailbox>(); - glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue, - 0.0f); + glClearColor(Settings::values.bg_red.GetValue(), Settings::values.bg_green.GetValue(), + Settings::values.bg_blue.GetValue(), 0.0f); // Create shader programs OGLShader vertex_shader; @@ -561,8 +561,8 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture, void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) { if (renderer_settings.set_background_color) { // Update background color before drawing - glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue, - 0.0f); + glClearColor(Settings::values.bg_red.GetValue(), Settings::values.bg_green.GetValue(), + Settings::values.bg_blue.GetValue(), 0.0f); } // Set projection matrix diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp index 424278816..d1f0ea932 100644 --- a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp +++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp @@ -39,52 +39,18 @@ constexpr std::array POLYGON_OFFSET_ENABLE_LUT = { } // Anonymous namespace -void FixedPipelineState::DepthStencil::Fill(const Maxwell& regs) noexcept { - raw = 0; - front.action_stencil_fail.Assign(PackStencilOp(regs.stencil_front_op_fail)); - front.action_depth_fail.Assign(PackStencilOp(regs.stencil_front_op_zfail)); - front.action_depth_pass.Assign(PackStencilOp(regs.stencil_front_op_zpass)); - front.test_func.Assign(PackComparisonOp(regs.stencil_front_func_func)); - if (regs.stencil_two_side_enable) { - back.action_stencil_fail.Assign(PackStencilOp(regs.stencil_back_op_fail)); - back.action_depth_fail.Assign(PackStencilOp(regs.stencil_back_op_zfail)); - back.action_depth_pass.Assign(PackStencilOp(regs.stencil_back_op_zpass)); - back.test_func.Assign(PackComparisonOp(regs.stencil_back_func_func)); - } else { - back.action_stencil_fail.Assign(front.action_stencil_fail); - back.action_depth_fail.Assign(front.action_depth_fail); - back.action_depth_pass.Assign(front.action_depth_pass); - back.test_func.Assign(front.test_func); - } - depth_test_enable.Assign(regs.depth_test_enable); - depth_write_enable.Assign(regs.depth_write_enabled); - depth_bounds_enable.Assign(regs.depth_bounds_enable); - stencil_enable.Assign(regs.stencil_enable); - depth_test_func.Assign(PackComparisonOp(regs.depth_test_func)); -} - -void FixedPipelineState::Rasterizer::Fill(const Maxwell& regs) noexcept { +void FixedPipelineState::Fill(const Maxwell& regs, bool has_extended_dynamic_state) { const auto& clip = regs.view_volume_clip_control; const std::array enabled_lut = {regs.polygon_offset_point_enable, regs.polygon_offset_line_enable, regs.polygon_offset_fill_enable}; const u32 topology_index = static_cast<u32>(regs.draw.topology.Value()); - u32 packed_front_face = PackFrontFace(regs.front_face); - if (regs.screen_y_control.triangle_rast_flip != 0) { - // Flip front face - packed_front_face = 1 - packed_front_face; - } - raw = 0; - topology.Assign(topology_index); primitive_restart_enable.Assign(regs.primitive_restart.enabled != 0 ? 1 : 0); - cull_enable.Assign(regs.cull_test_enabled != 0 ? 1 : 0); depth_bias_enable.Assign(enabled_lut[POLYGON_OFFSET_ENABLE_LUT[topology_index]] != 0 ? 1 : 0); depth_clamp_disabled.Assign(regs.view_volume_clip_control.depth_clamp_disabled.Value()); ndc_minus_one_to_one.Assign(regs.depth_mode == Maxwell::DepthMode::MinusOneToOne ? 1 : 0); - cull_face.Assign(PackCullFace(regs.cull_face)); - front_face.Assign(packed_front_face); polygon_mode.Assign(PackPolygonMode(regs.polygon_mode_front)); patch_control_points_minus_one.Assign(regs.patch_vertices - 1); tessellation_primitive.Assign(static_cast<u32>(regs.tess_mode.prim.Value())); @@ -93,19 +59,37 @@ void FixedPipelineState::Rasterizer::Fill(const Maxwell& regs) noexcept { logic_op_enable.Assign(regs.logic_op.enable != 0 ? 1 : 0); logic_op.Assign(PackLogicOp(regs.logic_op.operation)); rasterize_enable.Assign(regs.rasterize_enable != 0 ? 1 : 0); + std::memcpy(&point_size, ®s.point_size, sizeof(point_size)); // TODO: C++20 std::bit_cast -} -void FixedPipelineState::ColorBlending::Fill(const Maxwell& regs) noexcept { + for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) { + binding_divisors[index] = + regs.instanced_arrays.IsInstancingEnabled(index) ? regs.vertex_array[index].divisor : 0; + } + + for (std::size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) { + const auto& input = regs.vertex_attrib_format[index]; + auto& attribute = attributes[index]; + attribute.raw = 0; + attribute.enabled.Assign(input.IsConstant() ? 0 : 1); + attribute.buffer.Assign(input.buffer); + attribute.offset.Assign(input.offset); + attribute.type.Assign(static_cast<u32>(input.type.Value())); + attribute.size.Assign(static_cast<u32>(input.size.Value())); + } + for (std::size_t index = 0; index < std::size(attachments); ++index) { attachments[index].Fill(regs, index); } -} -void FixedPipelineState::ViewportSwizzles::Fill(const Maxwell& regs) noexcept { const auto& transform = regs.viewport_transform; - std::transform(transform.begin(), transform.end(), swizzles.begin(), + std::transform(transform.begin(), transform.end(), viewport_swizzles.begin(), [](const auto& viewport) { return static_cast<u16>(viewport.swizzle.raw); }); + + if (!has_extended_dynamic_state) { + no_extended_dynamic_state.Assign(1); + dynamic_state.Fill(regs); + } } void FixedPipelineState::BlendingAttachment::Fill(const Maxwell& regs, std::size_t index) { @@ -147,20 +131,57 @@ void FixedPipelineState::BlendingAttachment::Fill(const Maxwell& regs, std::size enable.Assign(1); } -void FixedPipelineState::Fill(const Maxwell& regs) { - rasterizer.Fill(regs); - depth_stencil.Fill(regs); - color_blending.Fill(regs); - viewport_swizzles.Fill(regs); +void FixedPipelineState::DynamicState::Fill(const Maxwell& regs) { + const u32 topology_index = static_cast<u32>(regs.draw.topology.Value()); + u32 packed_front_face = PackFrontFace(regs.front_face); + if (regs.screen_y_control.triangle_rast_flip != 0) { + // Flip front face + packed_front_face = 1 - packed_front_face; + } + + raw1 = 0; + raw2 = 0; + front.action_stencil_fail.Assign(PackStencilOp(regs.stencil_front_op_fail)); + front.action_depth_fail.Assign(PackStencilOp(regs.stencil_front_op_zfail)); + front.action_depth_pass.Assign(PackStencilOp(regs.stencil_front_op_zpass)); + front.test_func.Assign(PackComparisonOp(regs.stencil_front_func_func)); + if (regs.stencil_two_side_enable) { + back.action_stencil_fail.Assign(PackStencilOp(regs.stencil_back_op_fail)); + back.action_depth_fail.Assign(PackStencilOp(regs.stencil_back_op_zfail)); + back.action_depth_pass.Assign(PackStencilOp(regs.stencil_back_op_zpass)); + back.test_func.Assign(PackComparisonOp(regs.stencil_back_func_func)); + } else { + back.action_stencil_fail.Assign(front.action_stencil_fail); + back.action_depth_fail.Assign(front.action_depth_fail); + back.action_depth_pass.Assign(front.action_depth_pass); + back.test_func.Assign(front.test_func); + } + stencil_enable.Assign(regs.stencil_enable); + depth_write_enable.Assign(regs.depth_write_enabled); + depth_bounds_enable.Assign(regs.depth_bounds_enable); + depth_test_enable.Assign(regs.depth_test_enable); + front_face.Assign(packed_front_face); + depth_test_func.Assign(PackComparisonOp(regs.depth_test_func)); + topology.Assign(topology_index); + cull_face.Assign(PackCullFace(regs.cull_face)); + cull_enable.Assign(regs.cull_test_enabled != 0 ? 1 : 0); + + for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) { + const auto& input = regs.vertex_array[index]; + VertexBinding& binding = vertex_bindings[index]; + binding.raw = 0; + binding.enabled.Assign(input.IsEnabled() ? 1 : 0); + binding.stride.Assign(static_cast<u16>(input.stride.Value())); + } } std::size_t FixedPipelineState::Hash() const noexcept { - const u64 hash = Common::CityHash64(reinterpret_cast<const char*>(this), sizeof *this); + const u64 hash = Common::CityHash64(reinterpret_cast<const char*>(this), Size()); return static_cast<std::size_t>(hash); } bool FixedPipelineState::operator==(const FixedPipelineState& rhs) const noexcept { - return std::memcmp(this, &rhs, sizeof *this) == 0; + return std::memcmp(this, &rhs, Size()) == 0; } u32 FixedPipelineState::PackComparisonOp(Maxwell::ComparisonOp op) noexcept { diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.h b/src/video_core/renderer_vulkan/fixed_pipeline_state.h index 31a6398f2..cdcbb65f5 100644 --- a/src/video_core/renderer_vulkan/fixed_pipeline_state.h +++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.h @@ -60,14 +60,6 @@ struct FixedPipelineState { void Fill(const Maxwell& regs, std::size_t index); - std::size_t Hash() const noexcept; - - bool operator==(const BlendingAttachment& rhs) const noexcept; - - bool operator!=(const BlendingAttachment& rhs) const noexcept { - return !operator==(rhs); - } - constexpr std::array<bool, 4> Mask() const noexcept { return {mask_r != 0, mask_g != 0, mask_b != 0, mask_a != 0}; } @@ -97,156 +89,116 @@ struct FixedPipelineState { } }; - struct VertexInput { - union Binding { - u16 raw; - BitField<0, 1, u16> enabled; - BitField<1, 12, u16> stride; - }; + union VertexAttribute { + u32 raw; + BitField<0, 1, u32> enabled; + BitField<1, 5, u32> buffer; + BitField<6, 14, u32> offset; + BitField<20, 3, u32> type; + BitField<23, 6, u32> size; - union Attribute { - u32 raw; - BitField<0, 1, u32> enabled; - BitField<1, 5, u32> buffer; - BitField<6, 14, u32> offset; - BitField<20, 3, u32> type; - BitField<23, 6, u32> size; - - constexpr Maxwell::VertexAttribute::Type Type() const noexcept { - return static_cast<Maxwell::VertexAttribute::Type>(type.Value()); - } - - constexpr Maxwell::VertexAttribute::Size Size() const noexcept { - return static_cast<Maxwell::VertexAttribute::Size>(size.Value()); - } - }; - - std::array<Binding, Maxwell::NumVertexArrays> bindings; - std::array<u32, Maxwell::NumVertexArrays> binding_divisors; - std::array<Attribute, Maxwell::NumVertexAttributes> attributes; - - void SetBinding(std::size_t index, bool enabled, u32 stride, u32 divisor) noexcept { - auto& binding = bindings[index]; - binding.raw = 0; - binding.enabled.Assign(enabled ? 1 : 0); - binding.stride.Assign(static_cast<u16>(stride)); - binding_divisors[index] = divisor; + constexpr Maxwell::VertexAttribute::Type Type() const noexcept { + return static_cast<Maxwell::VertexAttribute::Type>(type.Value()); } - void SetAttribute(std::size_t index, bool enabled, u32 buffer, u32 offset, - Maxwell::VertexAttribute::Type type, - Maxwell::VertexAttribute::Size size) noexcept { - auto& attribute = attributes[index]; - attribute.raw = 0; - attribute.enabled.Assign(enabled ? 1 : 0); - attribute.buffer.Assign(buffer); - attribute.offset.Assign(offset); - attribute.type.Assign(static_cast<u32>(type)); - attribute.size.Assign(static_cast<u32>(size)); + constexpr Maxwell::VertexAttribute::Size Size() const noexcept { + return static_cast<Maxwell::VertexAttribute::Size>(size.Value()); } }; - struct Rasterizer { - union { - u32 raw; - BitField<0, 4, u32> topology; - BitField<4, 1, u32> primitive_restart_enable; - BitField<5, 1, u32> cull_enable; - BitField<6, 1, u32> depth_bias_enable; - BitField<7, 1, u32> depth_clamp_disabled; - BitField<8, 1, u32> ndc_minus_one_to_one; - BitField<9, 2, u32> cull_face; - BitField<11, 1, u32> front_face; - BitField<12, 2, u32> polygon_mode; - BitField<14, 5, u32> patch_control_points_minus_one; - BitField<19, 2, u32> tessellation_primitive; - BitField<21, 2, u32> tessellation_spacing; - BitField<23, 1, u32> tessellation_clockwise; - BitField<24, 1, u32> logic_op_enable; - BitField<25, 4, u32> logic_op; - BitField<29, 1, u32> rasterize_enable; - }; - - // TODO(Rodrigo): Move this to push constants - u32 point_size; + template <std::size_t Position> + union StencilFace { + BitField<Position + 0, 3, u32> action_stencil_fail; + BitField<Position + 3, 3, u32> action_depth_fail; + BitField<Position + 6, 3, u32> action_depth_pass; + BitField<Position + 9, 3, u32> test_func; - void Fill(const Maxwell& regs) noexcept; + Maxwell::StencilOp ActionStencilFail() const noexcept { + return UnpackStencilOp(action_stencil_fail); + } - constexpr Maxwell::PrimitiveTopology Topology() const noexcept { - return static_cast<Maxwell::PrimitiveTopology>(topology.Value()); + Maxwell::StencilOp ActionDepthFail() const noexcept { + return UnpackStencilOp(action_depth_fail); } - Maxwell::CullFace CullFace() const noexcept { - return UnpackCullFace(cull_face.Value()); + Maxwell::StencilOp ActionDepthPass() const noexcept { + return UnpackStencilOp(action_depth_pass); } - Maxwell::FrontFace FrontFace() const noexcept { - return UnpackFrontFace(front_face.Value()); + Maxwell::ComparisonOp TestFunc() const noexcept { + return UnpackComparisonOp(test_func); } }; - struct DepthStencil { - template <std::size_t Position> - union StencilFace { - BitField<Position + 0, 3, u32> action_stencil_fail; - BitField<Position + 3, 3, u32> action_depth_fail; - BitField<Position + 6, 3, u32> action_depth_pass; - BitField<Position + 9, 3, u32> test_func; - - Maxwell::StencilOp ActionStencilFail() const noexcept { - return UnpackStencilOp(action_stencil_fail); - } - - Maxwell::StencilOp ActionDepthFail() const noexcept { - return UnpackStencilOp(action_depth_fail); - } - - Maxwell::StencilOp ActionDepthPass() const noexcept { - return UnpackStencilOp(action_depth_pass); - } - - Maxwell::ComparisonOp TestFunc() const noexcept { - return UnpackComparisonOp(test_func); - } - }; + union VertexBinding { + u16 raw; + BitField<0, 12, u16> stride; + BitField<12, 1, u16> enabled; + }; + struct DynamicState { union { - u32 raw; + u32 raw1; StencilFace<0> front; StencilFace<12> back; - BitField<24, 1, u32> depth_test_enable; + BitField<24, 1, u32> stencil_enable; BitField<25, 1, u32> depth_write_enable; BitField<26, 1, u32> depth_bounds_enable; - BitField<27, 1, u32> stencil_enable; - BitField<28, 3, u32> depth_test_func; + BitField<27, 1, u32> depth_test_enable; + BitField<28, 1, u32> front_face; + BitField<29, 3, u32> depth_test_func; + }; + union { + u32 raw2; + BitField<0, 4, u32> topology; + BitField<4, 2, u32> cull_face; + BitField<6, 1, u32> cull_enable; }; + std::array<VertexBinding, Maxwell::NumVertexArrays> vertex_bindings; - void Fill(const Maxwell& regs) noexcept; + void Fill(const Maxwell& regs); Maxwell::ComparisonOp DepthTestFunc() const noexcept { return UnpackComparisonOp(depth_test_func); } - }; - - struct ColorBlending { - std::array<BlendingAttachment, Maxwell::NumRenderTargets> attachments; - void Fill(const Maxwell& regs) noexcept; - }; + Maxwell::CullFace CullFace() const noexcept { + return UnpackCullFace(cull_face.Value()); + } - struct ViewportSwizzles { - std::array<u16, Maxwell::NumViewports> swizzles; + Maxwell::FrontFace FrontFace() const noexcept { + return UnpackFrontFace(front_face.Value()); + } - void Fill(const Maxwell& regs) noexcept; + constexpr Maxwell::PrimitiveTopology Topology() const noexcept { + return static_cast<Maxwell::PrimitiveTopology>(topology.Value()); + } }; - VertexInput vertex_input; - Rasterizer rasterizer; - DepthStencil depth_stencil; - ColorBlending color_blending; - ViewportSwizzles viewport_swizzles; + union { + u32 raw; + BitField<0, 1, u32> no_extended_dynamic_state; + BitField<2, 1, u32> primitive_restart_enable; + BitField<3, 1, u32> depth_bias_enable; + BitField<4, 1, u32> depth_clamp_disabled; + BitField<5, 1, u32> ndc_minus_one_to_one; + BitField<6, 2, u32> polygon_mode; + BitField<8, 5, u32> patch_control_points_minus_one; + BitField<13, 2, u32> tessellation_primitive; + BitField<15, 2, u32> tessellation_spacing; + BitField<17, 1, u32> tessellation_clockwise; + BitField<18, 1, u32> logic_op_enable; + BitField<19, 4, u32> logic_op; + BitField<23, 1, u32> rasterize_enable; + }; + u32 point_size; + std::array<u32, Maxwell::NumVertexArrays> binding_divisors; + std::array<VertexAttribute, Maxwell::NumVertexAttributes> attributes; + std::array<BlendingAttachment, Maxwell::NumRenderTargets> attachments; + std::array<u16, Maxwell::NumViewports> viewport_swizzles; + DynamicState dynamic_state; - void Fill(const Maxwell& regs); + void Fill(const Maxwell& regs, bool has_extended_dynamic_state); std::size_t Hash() const noexcept; @@ -255,6 +207,11 @@ struct FixedPipelineState { bool operator!=(const FixedPipelineState& rhs) const noexcept { return !operator==(rhs); } + + std::size_t Size() const noexcept { + const std::size_t total_size = sizeof *this; + return total_size - (no_extended_dynamic_state != 0 ? 0 : sizeof(DynamicState)); + } }; static_assert(std::has_unique_object_representations_v<FixedPipelineState>); static_assert(std::is_trivially_copyable_v<FixedPipelineState>); diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index 2d9b18ed9..2258479f5 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -409,7 +409,7 @@ bool RendererVulkan::PickDevices() { return false; } - const s32 device_index = Settings::values.vulkan_device; + const s32 device_index = Settings::values.vulkan_device.GetValue(); if (device_index < 0 || device_index >= static_cast<s32>(devices->size())) { LOG_ERROR(Render_Vulkan, "Invalid device index {}!", device_index); return false; diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp index f10f96cd8..2be38d419 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp @@ -56,7 +56,7 @@ Buffer::Buffer(const VKDevice& device, VKMemoryManager& memory_manager, VKSchedu Buffer::~Buffer() = default; -void Buffer::Upload(std::size_t offset, std::size_t size, const u8* data) const { +void Buffer::Upload(std::size_t offset, std::size_t size, const u8* data) { const auto& staging = staging_pool.GetUnusedBuffer(size, true); std::memcpy(staging.commit->Map(size), data, size); @@ -81,7 +81,7 @@ void Buffer::Upload(std::size_t offset, std::size_t size, const u8* data) const }); } -void Buffer::Download(std::size_t offset, std::size_t size, u8* data) const { +void Buffer::Download(std::size_t offset, std::size_t size, u8* data) { const auto& staging = staging_pool.GetUnusedBuffer(size, true); scheduler.RequestOutsideRenderPassOperationContext(); @@ -110,7 +110,7 @@ void Buffer::Download(std::size_t offset, std::size_t size, u8* data) const { } void Buffer::CopyFrom(const Buffer& src, std::size_t src_offset, std::size_t dst_offset, - std::size_t size) const { + std::size_t size) { scheduler.RequestOutsideRenderPassOperationContext(); const VkBuffer dst_buffer = Handle(); diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h index 3630aca77..991ee451c 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.h +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h @@ -29,12 +29,12 @@ public: VKStagingBufferPool& staging_pool, VAddr cpu_addr, std::size_t size); ~Buffer(); - void Upload(std::size_t offset, std::size_t size, const u8* data) const; + void Upload(std::size_t offset, std::size_t size, const u8* data); - void Download(std::size_t offset, std::size_t size, u8* data) const; + void Download(std::size_t offset, std::size_t size, u8* data); void CopyFrom(const Buffer& src, std::size_t src_offset, std::size_t dst_offset, - std::size_t size) const; + std::size_t size); VkBuffer Handle() const { return *buffer.handle; diff --git a/src/video_core/renderer_vulkan/vk_device.cpp b/src/video_core/renderer_vulkan/vk_device.cpp index 9fd8ac3f6..fdaea4210 100644 --- a/src/video_core/renderer_vulkan/vk_device.cpp +++ b/src/video_core/renderer_vulkan/vk_device.cpp @@ -313,6 +313,16 @@ bool VKDevice::Create() { LOG_INFO(Render_Vulkan, "Device doesn't support custom border colors"); } + VkPhysicalDeviceExtendedDynamicStateFeaturesEXT dynamic_state; + if (ext_extended_dynamic_state) { + dynamic_state.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT; + dynamic_state.pNext = nullptr; + dynamic_state.extendedDynamicState = VK_TRUE; + SetNext(next, dynamic_state); + } else { + LOG_INFO(Render_Vulkan, "Device doesn't support extended dynamic state"); + } + if (!ext_depth_range_unrestricted) { LOG_INFO(Render_Vulkan, "Device doesn't support depth range unrestricted"); } @@ -541,6 +551,7 @@ std::vector<const char*> VKDevice::LoadExtensions() { bool has_ext_subgroup_size_control{}; bool has_ext_transform_feedback{}; bool has_ext_custom_border_color{}; + bool has_ext_extended_dynamic_state{}; for (const auto& extension : physical.EnumerateDeviceExtensionProperties()) { Test(extension, nv_viewport_swizzle, VK_NV_VIEWPORT_SWIZZLE_EXTENSION_NAME, true); Test(extension, khr_uniform_buffer_standard_layout, @@ -558,6 +569,8 @@ std::vector<const char*> VKDevice::LoadExtensions() { false); Test(extension, has_ext_custom_border_color, VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME, false); + Test(extension, has_ext_extended_dynamic_state, + VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME, false); if (Settings::values.renderer_debug) { Test(extension, nv_device_diagnostics_config, VK_NV_DEVICE_DIAGNOSTICS_CONFIG_EXTENSION_NAME, true); @@ -643,6 +656,19 @@ std::vector<const char*> VKDevice::LoadExtensions() { } } + if (has_ext_extended_dynamic_state) { + VkPhysicalDeviceExtendedDynamicStateFeaturesEXT dynamic_state; + dynamic_state.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT; + dynamic_state.pNext = nullptr; + features.pNext = &dynamic_state; + physical.GetFeatures2KHR(features); + + if (dynamic_state.extendedDynamicState) { + extensions.push_back(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); + ext_extended_dynamic_state = true; + } + } + return extensions; } diff --git a/src/video_core/renderer_vulkan/vk_device.h b/src/video_core/renderer_vulkan/vk_device.h index 6b9227b09..ae5c21baa 100644 --- a/src/video_core/renderer_vulkan/vk_device.h +++ b/src/video_core/renderer_vulkan/vk_device.h @@ -182,6 +182,11 @@ public: return ext_custom_border_color; } + /// Returns true if the device supports VK_EXT_extended_dynamic_state. + bool IsExtExtendedDynamicStateSupported() const { + return ext_extended_dynamic_state; + } + /// Returns the vendor name reported from Vulkan. std::string_view GetVendorName() const { return vendor_name; @@ -239,6 +244,7 @@ private: bool ext_shader_viewport_index_layer{}; ///< Support for VK_EXT_shader_viewport_index_layer. bool ext_transform_feedback{}; ///< Support for VK_EXT_transform_feedback. bool ext_custom_border_color{}; ///< Support for VK_EXT_custom_border_color. + bool ext_extended_dynamic_state{}; ///< Support for VK_EXT_extended_dynamic_state. bool nv_device_diagnostics_config{}; ///< Support for VK_NV_device_diagnostics_config. // Telemetry parameters diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 69b6bba00..844445105 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -176,20 +176,32 @@ std::vector<vk::ShaderModule> VKGraphicsPipeline::CreateShaderModules( vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpass_params, const SPIRVProgram& program) const { - const auto& vi = fixed_state.vertex_input; - const auto& ds = fixed_state.depth_stencil; - const auto& cd = fixed_state.color_blending; - const auto& rs = fixed_state.rasterizer; - const auto& viewport_swizzles = fixed_state.viewport_swizzles.swizzles; + const auto& state = fixed_state; + const auto& viewport_swizzles = state.viewport_swizzles; + + FixedPipelineState::DynamicState dynamic; + if (device.IsExtExtendedDynamicStateSupported()) { + // Insert dummy values, as long as they are valid they don't matter as extended dynamic + // state is ignored + dynamic.raw1 = 0; + dynamic.raw2 = 0; + for (FixedPipelineState::VertexBinding& binding : dynamic.vertex_bindings) { + // Enable all vertex bindings + binding.raw = 0; + binding.enabled.Assign(1); + } + } else { + dynamic = state.dynamic_state; + } std::vector<VkVertexInputBindingDescription> vertex_bindings; std::vector<VkVertexInputBindingDivisorDescriptionEXT> vertex_binding_divisors; - for (std::size_t index = 0; index < std::size(vi.bindings); ++index) { - const auto& binding = vi.bindings[index]; + for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) { + const auto& binding = dynamic.vertex_bindings[index]; if (!binding.enabled) { continue; } - const bool instanced = vi.binding_divisors[index] != 0; + const bool instanced = state.binding_divisors[index] != 0; const auto rate = instanced ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX; auto& vertex_binding = vertex_bindings.emplace_back(); @@ -200,14 +212,14 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa if (instanced) { auto& binding_divisor = vertex_binding_divisors.emplace_back(); binding_divisor.binding = static_cast<u32>(index); - binding_divisor.divisor = vi.binding_divisors[index]; + binding_divisor.divisor = state.binding_divisors[index]; } } std::vector<VkVertexInputAttributeDescription> vertex_attributes; const auto& input_attributes = program[0]->entries.attributes; - for (std::size_t index = 0; index < std::size(vi.attributes); ++index) { - const auto& attribute = vi.attributes[index]; + for (std::size_t index = 0; index < state.attributes.size(); ++index) { + const auto& attribute = state.attributes[index]; if (!attribute.enabled) { continue; } @@ -244,15 +256,15 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa input_assembly_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; input_assembly_ci.pNext = nullptr; input_assembly_ci.flags = 0; - input_assembly_ci.topology = MaxwellToVK::PrimitiveTopology(device, rs.Topology()); + input_assembly_ci.topology = MaxwellToVK::PrimitiveTopology(device, dynamic.Topology()); input_assembly_ci.primitiveRestartEnable = - rs.primitive_restart_enable != 0 && SupportsPrimitiveRestart(input_assembly_ci.topology); + state.primitive_restart_enable != 0 && SupportsPrimitiveRestart(input_assembly_ci.topology); VkPipelineTessellationStateCreateInfo tessellation_ci; tessellation_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO; tessellation_ci.pNext = nullptr; tessellation_ci.flags = 0; - tessellation_ci.patchControlPoints = rs.patch_control_points_minus_one.Value() + 1; + tessellation_ci.patchControlPoints = state.patch_control_points_minus_one.Value() + 1; VkPipelineViewportStateCreateInfo viewport_ci; viewport_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; @@ -280,13 +292,13 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa rasterization_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rasterization_ci.pNext = nullptr; rasterization_ci.flags = 0; - rasterization_ci.depthClampEnable = rs.depth_clamp_disabled == 0 ? VK_TRUE : VK_FALSE; - rasterization_ci.rasterizerDiscardEnable = rs.rasterize_enable == 0 ? VK_TRUE : VK_FALSE; + rasterization_ci.depthClampEnable = state.depth_clamp_disabled == 0 ? VK_TRUE : VK_FALSE; + rasterization_ci.rasterizerDiscardEnable = state.rasterize_enable == 0 ? VK_TRUE : VK_FALSE; rasterization_ci.polygonMode = VK_POLYGON_MODE_FILL; rasterization_ci.cullMode = - rs.cull_enable ? MaxwellToVK::CullFace(rs.CullFace()) : VK_CULL_MODE_NONE; - rasterization_ci.frontFace = MaxwellToVK::FrontFace(rs.FrontFace()); - rasterization_ci.depthBiasEnable = rs.depth_bias_enable; + dynamic.cull_enable ? MaxwellToVK::CullFace(dynamic.CullFace()) : VK_CULL_MODE_NONE; + rasterization_ci.frontFace = MaxwellToVK::FrontFace(dynamic.FrontFace()); + rasterization_ci.depthBiasEnable = state.depth_bias_enable; rasterization_ci.depthBiasConstantFactor = 0.0f; rasterization_ci.depthBiasClamp = 0.0f; rasterization_ci.depthBiasSlopeFactor = 0.0f; @@ -307,14 +319,15 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa depth_stencil_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; depth_stencil_ci.pNext = nullptr; depth_stencil_ci.flags = 0; - depth_stencil_ci.depthTestEnable = ds.depth_test_enable; - depth_stencil_ci.depthWriteEnable = ds.depth_write_enable; - depth_stencil_ci.depthCompareOp = - ds.depth_test_enable ? MaxwellToVK::ComparisonOp(ds.DepthTestFunc()) : VK_COMPARE_OP_ALWAYS; - depth_stencil_ci.depthBoundsTestEnable = ds.depth_bounds_enable; - depth_stencil_ci.stencilTestEnable = ds.stencil_enable; - depth_stencil_ci.front = GetStencilFaceState(ds.front); - depth_stencil_ci.back = GetStencilFaceState(ds.back); + depth_stencil_ci.depthTestEnable = dynamic.depth_test_enable; + depth_stencil_ci.depthWriteEnable = dynamic.depth_write_enable; + depth_stencil_ci.depthCompareOp = dynamic.depth_test_enable + ? MaxwellToVK::ComparisonOp(dynamic.DepthTestFunc()) + : VK_COMPARE_OP_ALWAYS; + depth_stencil_ci.depthBoundsTestEnable = dynamic.depth_bounds_enable; + depth_stencil_ci.stencilTestEnable = dynamic.stencil_enable; + depth_stencil_ci.front = GetStencilFaceState(dynamic.front); + depth_stencil_ci.back = GetStencilFaceState(dynamic.back); depth_stencil_ci.minDepthBounds = 0.0f; depth_stencil_ci.maxDepthBounds = 0.0f; @@ -324,7 +337,7 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa static constexpr std::array COMPONENT_TABLE = { VK_COLOR_COMPONENT_R_BIT, VK_COLOR_COMPONENT_G_BIT, VK_COLOR_COMPONENT_B_BIT, VK_COLOR_COMPONENT_A_BIT}; - const auto& blend = cd.attachments[index]; + const auto& blend = state.attachments[index]; VkColorComponentFlags color_components = 0; for (std::size_t i = 0; i < COMPONENT_TABLE.size(); ++i) { @@ -354,11 +367,27 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa color_blend_ci.pAttachments = cb_attachments.data(); std::memset(color_blend_ci.blendConstants, 0, sizeof(color_blend_ci.blendConstants)); - static constexpr std::array dynamic_states = { + std::vector dynamic_states = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, VK_DYNAMIC_STATE_DEPTH_BIAS, VK_DYNAMIC_STATE_BLEND_CONSTANTS, VK_DYNAMIC_STATE_DEPTH_BOUNDS, VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK, - VK_DYNAMIC_STATE_STENCIL_WRITE_MASK, VK_DYNAMIC_STATE_STENCIL_REFERENCE}; + VK_DYNAMIC_STATE_STENCIL_WRITE_MASK, VK_DYNAMIC_STATE_STENCIL_REFERENCE, + }; + if (device.IsExtExtendedDynamicStateSupported()) { + static constexpr std::array extended = { + VK_DYNAMIC_STATE_CULL_MODE_EXT, + VK_DYNAMIC_STATE_FRONT_FACE_EXT, + VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY_EXT, + VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT, + VK_DYNAMIC_STATE_DEPTH_TEST_ENABLE_EXT, + VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE_EXT, + VK_DYNAMIC_STATE_DEPTH_COMPARE_OP_EXT, + VK_DYNAMIC_STATE_DEPTH_BOUNDS_TEST_ENABLE_EXT, + VK_DYNAMIC_STATE_STENCIL_TEST_ENABLE_EXT, + VK_DYNAMIC_STATE_STENCIL_OP_EXT, + }; + dynamic_states.insert(dynamic_states.end(), extended.begin(), extended.end()); + } VkPipelineDynamicStateCreateInfo dynamic_state_ci; dynamic_state_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index ea66e621e..3da835324 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -116,12 +116,12 @@ u32 FillDescriptorLayout(const ShaderEntries& entries, } // Anonymous namespace std::size_t GraphicsPipelineCacheKey::Hash() const noexcept { - const u64 hash = Common::CityHash64(reinterpret_cast<const char*>(this), sizeof *this); + const u64 hash = Common::CityHash64(reinterpret_cast<const char*>(this), Size()); return static_cast<std::size_t>(hash); } bool GraphicsPipelineCacheKey::operator==(const GraphicsPipelineCacheKey& rhs) const noexcept { - return std::memcmp(&rhs, this, sizeof *this) == 0; + return std::memcmp(&rhs, this, Size()) == 0; } std::size_t ComputePipelineCacheKey::Hash() const noexcept { @@ -312,18 +312,19 @@ VKPipelineCache::DecompileShaders(const GraphicsPipelineCacheKey& key) { const auto& gpu = system.GPU().Maxwell3D(); Specialization specialization; - if (fixed_state.rasterizer.Topology() == Maxwell::PrimitiveTopology::Points) { + if (fixed_state.dynamic_state.Topology() == Maxwell::PrimitiveTopology::Points || + device.IsExtExtendedDynamicStateSupported()) { float point_size; - std::memcpy(&point_size, &fixed_state.rasterizer.point_size, sizeof(float)); + std::memcpy(&point_size, &fixed_state.point_size, sizeof(float)); specialization.point_size = point_size; ASSERT(point_size != 0.0f); } for (std::size_t i = 0; i < Maxwell::NumVertexAttributes; ++i) { - const auto& attribute = fixed_state.vertex_input.attributes[i]; + const auto& attribute = fixed_state.attributes[i]; specialization.enabled_attributes[i] = attribute.enabled.Value() != 0; specialization.attribute_types[i] = attribute.Type(); } - specialization.ndc_minus_one_to_one = fixed_state.rasterizer.ndc_minus_one_to_one; + specialization.ndc_minus_one_to_one = fixed_state.ndc_minus_one_to_one; SPIRVProgram program; std::vector<VkDescriptorSetLayoutBinding> bindings; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h index 0a36e5112..0a3fe65fb 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.h +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h @@ -44,10 +44,10 @@ class VKUpdateDescriptorQueue; using Maxwell = Tegra::Engines::Maxwell3D::Regs; struct GraphicsPipelineCacheKey { - FixedPipelineState fixed_state; RenderPassParams renderpass_params; + u32 padding; std::array<GPUVAddr, Maxwell::MaxShaderProgram> shaders; - u64 padding; // This is necessary for unique object representations + FixedPipelineState fixed_state; std::size_t Hash() const noexcept; @@ -56,6 +56,10 @@ struct GraphicsPipelineCacheKey { bool operator!=(const GraphicsPipelineCacheKey& rhs) const noexcept { return !operator==(rhs); } + + std::size_t Size() const noexcept { + return sizeof(renderpass_params) + sizeof(padding) + sizeof(shaders) + fixed_state.Size(); + } }; static_assert(std::has_unique_object_representations_v<GraphicsPipelineCacheKey>); static_assert(std::is_trivially_copyable_v<GraphicsPipelineCacheKey>); diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index a8d94eac3..380ed532b 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -186,13 +186,22 @@ bool HasToPreserveDepthContents(bool is_clear, const Maxwell& regs) { scissor.max_y < regs.zeta_height; } +template <std::size_t N> +std::array<VkDeviceSize, N> ExpandStrides(const std::array<u16, N>& strides) { + std::array<VkDeviceSize, N> expanded; + std::copy(strides.begin(), strides.end(), expanded.begin()); + return expanded; +} + } // Anonymous namespace class BufferBindings final { public: - void AddVertexBinding(VkBuffer buffer, VkDeviceSize offset) { + void AddVertexBinding(VkBuffer buffer, VkDeviceSize offset, VkDeviceSize size, u32 stride) { vertex.buffers[vertex.num_buffers] = buffer; vertex.offsets[vertex.num_buffers] = offset; + vertex.sizes[vertex.num_buffers] = size; + vertex.strides[vertex.num_buffers] = static_cast<u16>(stride); ++vertex.num_buffers; } @@ -202,76 +211,76 @@ public: index.type = type; } - void Bind(VKScheduler& scheduler) const { + void Bind(const VKDevice& device, VKScheduler& scheduler) const { // Use this large switch case to avoid dispatching more memory in the record lambda than // what we need. It looks horrible, but it's the best we can do on standard C++. switch (vertex.num_buffers) { case 0: - return BindStatic<0>(scheduler); + return BindStatic<0>(device, scheduler); case 1: - return BindStatic<1>(scheduler); + return BindStatic<1>(device, scheduler); case 2: - return BindStatic<2>(scheduler); + return BindStatic<2>(device, scheduler); case 3: - return BindStatic<3>(scheduler); + return BindStatic<3>(device, scheduler); case 4: - return BindStatic<4>(scheduler); + return BindStatic<4>(device, scheduler); case 5: - return BindStatic<5>(scheduler); + return BindStatic<5>(device, scheduler); case 6: - return BindStatic<6>(scheduler); + return BindStatic<6>(device, scheduler); case 7: - return BindStatic<7>(scheduler); + return BindStatic<7>(device, scheduler); case 8: - return BindStatic<8>(scheduler); + return BindStatic<8>(device, scheduler); case 9: - return BindStatic<9>(scheduler); + return BindStatic<9>(device, scheduler); case 10: - return BindStatic<10>(scheduler); + return BindStatic<10>(device, scheduler); case 11: - return BindStatic<11>(scheduler); + return BindStatic<11>(device, scheduler); case 12: - return BindStatic<12>(scheduler); + return BindStatic<12>(device, scheduler); case 13: - return BindStatic<13>(scheduler); + return BindStatic<13>(device, scheduler); case 14: - return BindStatic<14>(scheduler); + return BindStatic<14>(device, scheduler); case 15: - return BindStatic<15>(scheduler); + return BindStatic<15>(device, scheduler); case 16: - return BindStatic<16>(scheduler); + return BindStatic<16>(device, scheduler); case 17: - return BindStatic<17>(scheduler); + return BindStatic<17>(device, scheduler); case 18: - return BindStatic<18>(scheduler); + return BindStatic<18>(device, scheduler); case 19: - return BindStatic<19>(scheduler); + return BindStatic<19>(device, scheduler); case 20: - return BindStatic<20>(scheduler); + return BindStatic<20>(device, scheduler); case 21: - return BindStatic<21>(scheduler); + return BindStatic<21>(device, scheduler); case 22: - return BindStatic<22>(scheduler); + return BindStatic<22>(device, scheduler); case 23: - return BindStatic<23>(scheduler); + return BindStatic<23>(device, scheduler); case 24: - return BindStatic<24>(scheduler); + return BindStatic<24>(device, scheduler); case 25: - return BindStatic<25>(scheduler); + return BindStatic<25>(device, scheduler); case 26: - return BindStatic<26>(scheduler); + return BindStatic<26>(device, scheduler); case 27: - return BindStatic<27>(scheduler); + return BindStatic<27>(device, scheduler); case 28: - return BindStatic<28>(scheduler); + return BindStatic<28>(device, scheduler); case 29: - return BindStatic<29>(scheduler); + return BindStatic<29>(device, scheduler); case 30: - return BindStatic<30>(scheduler); + return BindStatic<30>(device, scheduler); case 31: - return BindStatic<31>(scheduler); + return BindStatic<31>(device, scheduler); case 32: - return BindStatic<32>(scheduler); + return BindStatic<32>(device, scheduler); } UNREACHABLE(); } @@ -282,6 +291,8 @@ private: std::size_t num_buffers = 0; std::array<VkBuffer, Maxwell::NumVertexArrays> buffers; std::array<VkDeviceSize, Maxwell::NumVertexArrays> offsets; + std::array<VkDeviceSize, Maxwell::NumVertexArrays> sizes; + std::array<u16, Maxwell::NumVertexArrays> strides; } vertex; struct { @@ -291,15 +302,23 @@ private: } index; template <std::size_t N> - void BindStatic(VKScheduler& scheduler) const { - if (index.buffer) { - BindStatic<N, true>(scheduler); + void BindStatic(const VKDevice& device, VKScheduler& scheduler) const { + if (device.IsExtExtendedDynamicStateSupported()) { + if (index.buffer) { + BindStatic<N, true, true>(scheduler); + } else { + BindStatic<N, false, true>(scheduler); + } } else { - BindStatic<N, false>(scheduler); + if (index.buffer) { + BindStatic<N, true, false>(scheduler); + } else { + BindStatic<N, false, false>(scheduler); + } } } - template <std::size_t N, bool is_indexed> + template <std::size_t N, bool is_indexed, bool has_extended_dynamic_state> void BindStatic(VKScheduler& scheduler) const { static_assert(N <= Maxwell::NumVertexArrays); if constexpr (N == 0) { @@ -311,6 +330,31 @@ private: std::copy(vertex.buffers.begin(), vertex.buffers.begin() + N, buffers.begin()); std::copy(vertex.offsets.begin(), vertex.offsets.begin() + N, offsets.begin()); + if constexpr (has_extended_dynamic_state) { + // With extended dynamic states we can specify the length and stride of a vertex buffer + // std::array<VkDeviceSize, N> sizes; + std::array<u16, N> strides; + // std::copy(vertex.sizes.begin(), vertex.sizes.begin() + N, sizes.begin()); + std::copy(vertex.strides.begin(), vertex.strides.begin() + N, strides.begin()); + + if constexpr (is_indexed) { + scheduler.Record( + [buffers, offsets, strides, index = index](vk::CommandBuffer cmdbuf) { + cmdbuf.BindIndexBuffer(index.buffer, index.offset, index.type); + cmdbuf.BindVertexBuffers2EXT(0, static_cast<u32>(N), buffers.data(), + offsets.data(), nullptr, + ExpandStrides(strides).data()); + }); + } else { + scheduler.Record([buffers, offsets, strides](vk::CommandBuffer cmdbuf) { + cmdbuf.BindVertexBuffers2EXT(0, static_cast<u32>(N), buffers.data(), + offsets.data(), nullptr, + ExpandStrides(strides).data()); + }); + } + return; + } + if constexpr (is_indexed) { // Indexed draw scheduler.Record([buffers, offsets, index = index](vk::CommandBuffer cmdbuf) { @@ -369,7 +413,7 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) { const auto& gpu = system.GPU().Maxwell3D(); GraphicsPipelineCacheKey key; - key.fixed_state.Fill(gpu.regs); + key.fixed_state.Fill(gpu.regs, device.IsExtExtendedDynamicStateSupported()); buffer_cache.Map(CalculateGraphicsStreamBufferSize(is_indexed)); @@ -402,7 +446,7 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) { UpdateDynamicStates(); - buffer_bindings.Bind(scheduler); + buffer_bindings.Bind(device, scheduler); BeginTransformFeedback(); @@ -822,7 +866,7 @@ RasterizerVulkan::DrawParameters RasterizerVulkan::SetupGeometry(FixedPipelineSt const auto& gpu = system.GPU().Maxwell3D(); const auto& regs = gpu.regs; - SetupVertexArrays(fixed_state.vertex_input, buffer_bindings); + SetupVertexArrays(buffer_bindings); const u32 base_instance = regs.vb_base_instance; const u32 num_instances = is_instanced ? gpu.mme_draw.instance_count : 1; @@ -893,6 +937,17 @@ void RasterizerVulkan::UpdateDynamicStates() { UpdateBlendConstants(regs); UpdateDepthBounds(regs); UpdateStencilFaces(regs); + if (device.IsExtExtendedDynamicStateSupported()) { + UpdateCullMode(regs); + UpdateDepthBoundsTestEnable(regs); + UpdateDepthTestEnable(regs); + UpdateDepthWriteEnable(regs); + UpdateDepthCompareOp(regs); + UpdateFrontFace(regs); + UpdatePrimitiveTopology(regs); + UpdateStencilOp(regs); + UpdateStencilTestEnable(regs); + } } void RasterizerVulkan::BeginTransformFeedback() { @@ -940,41 +995,25 @@ void RasterizerVulkan::EndTransformFeedback() { [](vk::CommandBuffer cmdbuf) { cmdbuf.EndTransformFeedbackEXT(0, 0, nullptr, nullptr); }); } -void RasterizerVulkan::SetupVertexArrays(FixedPipelineState::VertexInput& vertex_input, - BufferBindings& buffer_bindings) { +void RasterizerVulkan::SetupVertexArrays(BufferBindings& buffer_bindings) { const auto& regs = system.GPU().Maxwell3D().regs; - for (std::size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) { - const auto& attrib = regs.vertex_attrib_format[index]; - if (attrib.IsConstant()) { - vertex_input.SetAttribute(index, false, 0, 0, {}, {}); - continue; - } - vertex_input.SetAttribute(index, true, attrib.buffer, attrib.offset, attrib.type.Value(), - attrib.size.Value()); - } - for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) { const auto& vertex_array = regs.vertex_array[index]; if (!vertex_array.IsEnabled()) { - vertex_input.SetBinding(index, false, 0, 0); continue; } - vertex_input.SetBinding( - index, true, vertex_array.stride, - regs.instanced_arrays.IsInstancingEnabled(index) ? vertex_array.divisor : 0); - const GPUVAddr start{vertex_array.StartAddress()}; const GPUVAddr end{regs.vertex_array_limit[index].LimitAddress()}; ASSERT(end >= start); - const std::size_t size{end - start}; + const std::size_t size = end - start; if (size == 0) { - buffer_bindings.AddVertexBinding(DefaultBuffer(), 0); + buffer_bindings.AddVertexBinding(DefaultBuffer(), 0, DEFAULT_BUFFER_SIZE, 0); continue; } const auto info = buffer_cache.UploadMemory(start, size); - buffer_bindings.AddVertexBinding(info.handle, info.offset); + buffer_bindings.AddVertexBinding(info.handle, info.offset, size, vertex_array.stride); } } @@ -1326,6 +1365,117 @@ void RasterizerVulkan::UpdateStencilFaces(Tegra::Engines::Maxwell3D::Regs& regs) } } +void RasterizerVulkan::UpdateCullMode(Tegra::Engines::Maxwell3D::Regs& regs) { + if (!state_tracker.TouchCullMode()) { + return; + } + scheduler.Record( + [enabled = regs.cull_test_enabled, cull_face = regs.cull_face](vk::CommandBuffer cmdbuf) { + cmdbuf.SetCullModeEXT(enabled ? MaxwellToVK::CullFace(cull_face) : VK_CULL_MODE_NONE); + }); +} + +void RasterizerVulkan::UpdateDepthBoundsTestEnable(Tegra::Engines::Maxwell3D::Regs& regs) { + if (!state_tracker.TouchDepthBoundsTestEnable()) { + return; + } + scheduler.Record([enable = regs.depth_bounds_enable](vk::CommandBuffer cmdbuf) { + cmdbuf.SetDepthBoundsTestEnableEXT(enable); + }); +} + +void RasterizerVulkan::UpdateDepthTestEnable(Tegra::Engines::Maxwell3D::Regs& regs) { + if (!state_tracker.TouchDepthTestEnable()) { + return; + } + scheduler.Record([enable = regs.depth_test_enable](vk::CommandBuffer cmdbuf) { + cmdbuf.SetDepthTestEnableEXT(enable); + }); +} + +void RasterizerVulkan::UpdateDepthWriteEnable(Tegra::Engines::Maxwell3D::Regs& regs) { + if (!state_tracker.TouchDepthWriteEnable()) { + return; + } + scheduler.Record([enable = regs.depth_write_enabled](vk::CommandBuffer cmdbuf) { + cmdbuf.SetDepthWriteEnableEXT(enable); + }); +} + +void RasterizerVulkan::UpdateDepthCompareOp(Tegra::Engines::Maxwell3D::Regs& regs) { + if (!state_tracker.TouchDepthCompareOp()) { + return; + } + scheduler.Record([func = regs.depth_test_func](vk::CommandBuffer cmdbuf) { + cmdbuf.SetDepthCompareOpEXT(MaxwellToVK::ComparisonOp(func)); + }); +} + +void RasterizerVulkan::UpdateFrontFace(Tegra::Engines::Maxwell3D::Regs& regs) { + if (!state_tracker.TouchFrontFace()) { + return; + } + + VkFrontFace front_face = MaxwellToVK::FrontFace(regs.front_face); + if (regs.screen_y_control.triangle_rast_flip != 0) { + front_face = front_face == VK_FRONT_FACE_CLOCKWISE ? VK_FRONT_FACE_COUNTER_CLOCKWISE + : VK_FRONT_FACE_CLOCKWISE; + } + scheduler.Record( + [front_face](vk::CommandBuffer cmdbuf) { cmdbuf.SetFrontFaceEXT(front_face); }); +} + +void RasterizerVulkan::UpdatePrimitiveTopology(Tegra::Engines::Maxwell3D::Regs& regs) { + if (!state_tracker.TouchPrimitiveTopology()) { + return; + } + const Maxwell::PrimitiveTopology primitive_topology = regs.draw.topology.Value(); + scheduler.Record([this, primitive_topology](vk::CommandBuffer cmdbuf) { + cmdbuf.SetPrimitiveTopologyEXT(MaxwellToVK::PrimitiveTopology(device, primitive_topology)); + }); +} + +void RasterizerVulkan::UpdateStencilOp(Tegra::Engines::Maxwell3D::Regs& regs) { + if (!state_tracker.TouchStencilOp()) { + return; + } + const Maxwell::StencilOp fail = regs.stencil_front_op_fail; + const Maxwell::StencilOp zfail = regs.stencil_front_op_zfail; + const Maxwell::StencilOp zpass = regs.stencil_front_op_zpass; + const Maxwell::ComparisonOp compare = regs.stencil_front_func_func; + if (regs.stencil_two_side_enable) { + scheduler.Record([fail, zfail, zpass, compare](vk::CommandBuffer cmdbuf) { + cmdbuf.SetStencilOpEXT(VK_STENCIL_FACE_FRONT_AND_BACK, MaxwellToVK::StencilOp(fail), + MaxwellToVK::StencilOp(zpass), MaxwellToVK::StencilOp(zfail), + MaxwellToVK::ComparisonOp(compare)); + }); + } else { + const Maxwell::StencilOp back_fail = regs.stencil_back_op_fail; + const Maxwell::StencilOp back_zfail = regs.stencil_back_op_zfail; + const Maxwell::StencilOp back_zpass = regs.stencil_back_op_zpass; + const Maxwell::ComparisonOp back_compare = regs.stencil_back_func_func; + scheduler.Record([fail, zfail, zpass, compare, back_fail, back_zfail, back_zpass, + back_compare](vk::CommandBuffer cmdbuf) { + cmdbuf.SetStencilOpEXT(VK_STENCIL_FACE_FRONT_BIT, MaxwellToVK::StencilOp(fail), + MaxwellToVK::StencilOp(zpass), MaxwellToVK::StencilOp(zfail), + MaxwellToVK::ComparisonOp(compare)); + cmdbuf.SetStencilOpEXT(VK_STENCIL_FACE_BACK_BIT, MaxwellToVK::StencilOp(back_fail), + MaxwellToVK::StencilOp(back_zpass), + MaxwellToVK::StencilOp(back_zfail), + MaxwellToVK::ComparisonOp(back_compare)); + }); + } +} + +void RasterizerVulkan::UpdateStencilTestEnable(Tegra::Engines::Maxwell3D::Regs& regs) { + if (!state_tracker.TouchStencilTestEnable()) { + return; + } + scheduler.Record([enable = regs.stencil_enable](vk::CommandBuffer cmdbuf) { + cmdbuf.SetStencilTestEnableEXT(enable); + }); +} + std::size_t RasterizerVulkan::CalculateGraphicsStreamBufferSize(bool is_indexed) const { std::size_t size = CalculateVertexArraysSize(); if (is_indexed) { diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index 83e00e7e9..923178b0b 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -185,8 +185,7 @@ private: bool WalkAttachmentOverlaps(const CachedSurfaceView& attachment); - void SetupVertexArrays(FixedPipelineState::VertexInput& vertex_input, - BufferBindings& buffer_bindings); + void SetupVertexArrays(BufferBindings& buffer_bindings); void SetupIndexBuffer(BufferBindings& buffer_bindings, DrawParameters& params, bool is_indexed); @@ -246,6 +245,16 @@ private: void UpdateDepthBounds(Tegra::Engines::Maxwell3D::Regs& regs); void UpdateStencilFaces(Tegra::Engines::Maxwell3D::Regs& regs); + void UpdateCullMode(Tegra::Engines::Maxwell3D::Regs& regs); + void UpdateDepthBoundsTestEnable(Tegra::Engines::Maxwell3D::Regs& regs); + void UpdateDepthTestEnable(Tegra::Engines::Maxwell3D::Regs& regs); + void UpdateDepthWriteEnable(Tegra::Engines::Maxwell3D::Regs& regs); + void UpdateDepthCompareOp(Tegra::Engines::Maxwell3D::Regs& regs); + void UpdateFrontFace(Tegra::Engines::Maxwell3D::Regs& regs); + void UpdatePrimitiveTopology(Tegra::Engines::Maxwell3D::Regs& regs); + void UpdateStencilOp(Tegra::Engines::Maxwell3D::Regs& regs); + void UpdateStencilTestEnable(Tegra::Engines::Maxwell3D::Regs& regs); + std::size_t CalculateGraphicsStreamBufferSize(bool is_indexed) const; std::size_t CalculateComputeStreamBufferSize() const; diff --git a/src/video_core/renderer_vulkan/vk_state_tracker.cpp b/src/video_core/renderer_vulkan/vk_state_tracker.cpp index 94a89e388..e5a583dd5 100644 --- a/src/video_core/renderer_vulkan/vk_state_tracker.cpp +++ b/src/video_core/renderer_vulkan/vk_state_tracker.cpp @@ -36,6 +36,15 @@ Flags MakeInvalidationFlags() { flags[BlendConstants] = true; flags[DepthBounds] = true; flags[StencilProperties] = true; + flags[CullMode] = true; + flags[DepthBoundsEnable] = true; + flags[DepthTestEnable] = true; + flags[DepthWriteEnable] = true; + flags[DepthCompareOp] = true; + flags[FrontFace] = true; + flags[PrimitiveTopology] = true; + flags[StencilOp] = true; + flags[StencilTestEnable] = true; return flags; } @@ -75,6 +84,57 @@ void SetupDirtyStencilProperties(Tables& tables) { table[OFF(stencil_back_func_mask)] = StencilProperties; } +void SetupDirtyCullMode(Tables& tables) { + auto& table = tables[0]; + table[OFF(cull_face)] = CullMode; + table[OFF(cull_test_enabled)] = CullMode; +} + +void SetupDirtyDepthBoundsEnable(Tables& tables) { + tables[0][OFF(depth_bounds_enable)] = DepthBoundsEnable; +} + +void SetupDirtyDepthTestEnable(Tables& tables) { + tables[0][OFF(depth_test_enable)] = DepthTestEnable; +} + +void SetupDirtyDepthWriteEnable(Tables& tables) { + tables[0][OFF(depth_write_enabled)] = DepthWriteEnable; +} + +void SetupDirtyDepthCompareOp(Tables& tables) { + tables[0][OFF(depth_test_func)] = DepthCompareOp; +} + +void SetupDirtyFrontFace(Tables& tables) { + auto& table = tables[0]; + table[OFF(front_face)] = FrontFace; + table[OFF(screen_y_control)] = FrontFace; +} + +void SetupDirtyPrimitiveTopology(Tables& tables) { + tables[0][OFF(draw.topology)] = PrimitiveTopology; +} + +void SetupDirtyStencilOp(Tables& tables) { + auto& table = tables[0]; + table[OFF(stencil_front_op_fail)] = StencilOp; + table[OFF(stencil_front_op_zfail)] = StencilOp; + table[OFF(stencil_front_op_zpass)] = StencilOp; + table[OFF(stencil_front_func_func)] = StencilOp; + table[OFF(stencil_back_op_fail)] = StencilOp; + table[OFF(stencil_back_op_zfail)] = StencilOp; + table[OFF(stencil_back_op_zpass)] = StencilOp; + table[OFF(stencil_back_func_func)] = StencilOp; + + // Table 0 is used by StencilProperties + tables[1][OFF(stencil_two_side_enable)] = StencilOp; +} + +void SetupDirtyStencilTestEnable(Tables& tables) { + tables[0][OFF(stencil_enable)] = StencilTestEnable; +} + } // Anonymous namespace StateTracker::StateTracker(Core::System& system) @@ -90,6 +150,14 @@ void StateTracker::Initialize() { SetupDirtyBlendConstants(tables); SetupDirtyDepthBounds(tables); SetupDirtyStencilProperties(tables); + SetupDirtyCullMode(tables); + SetupDirtyDepthBoundsEnable(tables); + SetupDirtyDepthTestEnable(tables); + SetupDirtyDepthWriteEnable(tables); + SetupDirtyDepthCompareOp(tables); + SetupDirtyFrontFace(tables); + SetupDirtyPrimitiveTopology(tables); + SetupDirtyStencilOp(tables); } void StateTracker::InvalidateCommandBufferState() { diff --git a/src/video_core/renderer_vulkan/vk_state_tracker.h b/src/video_core/renderer_vulkan/vk_state_tracker.h index 03bc415b2..54ca0d6c6 100644 --- a/src/video_core/renderer_vulkan/vk_state_tracker.h +++ b/src/video_core/renderer_vulkan/vk_state_tracker.h @@ -26,6 +26,16 @@ enum : u8 { DepthBounds, StencilProperties, + CullMode, + DepthBoundsEnable, + DepthTestEnable, + DepthWriteEnable, + DepthCompareOp, + FrontFace, + PrimitiveTopology, + StencilOp, + StencilTestEnable, + Last }; static_assert(Last <= std::numeric_limits<u8>::max()); @@ -64,6 +74,46 @@ public: return Exchange(Dirty::StencilProperties, false); } + bool TouchCullMode() { + return Exchange(Dirty::CullMode, false); + } + + bool TouchDepthBoundsTestEnable() { + return Exchange(Dirty::DepthBoundsEnable, false); + } + + bool TouchDepthTestEnable() { + return Exchange(Dirty::DepthTestEnable, false); + } + + bool TouchDepthBoundsEnable() { + return Exchange(Dirty::DepthBoundsEnable, false); + } + + bool TouchDepthWriteEnable() { + return Exchange(Dirty::DepthWriteEnable, false); + } + + bool TouchDepthCompareOp() { + return Exchange(Dirty::DepthCompareOp, false); + } + + bool TouchFrontFace() { + return Exchange(Dirty::FrontFace, false); + } + + bool TouchPrimitiveTopology() { + return Exchange(Dirty::PrimitiveTopology, false); + } + + bool TouchStencilOp() { + return Exchange(Dirty::StencilOp, false); + } + + bool TouchStencilTestEnable() { + return Exchange(Dirty::StencilTestEnable, false); + } + private: bool Exchange(std::size_t id, bool new_value) const noexcept { auto& flags = system.GPU().Maxwell3D().dirty.flags; diff --git a/src/video_core/renderer_vulkan/vk_stream_buffer.cpp b/src/video_core/renderer_vulkan/vk_stream_buffer.cpp index 868447af2..2d28a6c47 100644 --- a/src/video_core/renderer_vulkan/vk_stream_buffer.cpp +++ b/src/video_core/renderer_vulkan/vk_stream_buffer.cpp @@ -121,7 +121,7 @@ void VKStreamBuffer::CreateBuffers(VkBufferUsageFlags usage) { // Substract from the preferred heap size some bytes to avoid getting out of memory. const VkDeviceSize heap_size = memory_properties.memoryHeaps[preferred_heap].size; - const VkDeviceSize allocable_size = heap_size - 4 * 1024 * 1024; + const VkDeviceSize allocable_size = heap_size - 9 * 1024 * 1024; VkBufferCreateInfo buffer_ci; buffer_ci.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; diff --git a/src/video_core/renderer_vulkan/wrapper.cpp b/src/video_core/renderer_vulkan/wrapper.cpp index 0d485a662..051298cc8 100644 --- a/src/video_core/renderer_vulkan/wrapper.cpp +++ b/src/video_core/renderer_vulkan/wrapper.cpp @@ -88,6 +88,16 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { X(vkCmdSetStencilWriteMask); X(vkCmdSetViewport); X(vkCmdWaitEvents); + X(vkCmdBindVertexBuffers2EXT); + X(vkCmdSetCullModeEXT); + X(vkCmdSetDepthBoundsTestEnableEXT); + X(vkCmdSetDepthCompareOpEXT); + X(vkCmdSetDepthTestEnableEXT); + X(vkCmdSetDepthWriteEnableEXT); + X(vkCmdSetFrontFaceEXT); + X(vkCmdSetPrimitiveTopologyEXT); + X(vkCmdSetStencilOpEXT); + X(vkCmdSetStencilTestEnableEXT); X(vkCreateBuffer); X(vkCreateBufferView); X(vkCreateCommandPool); diff --git a/src/video_core/renderer_vulkan/wrapper.h b/src/video_core/renderer_vulkan/wrapper.h index d56fdb3f9..71daac9d7 100644 --- a/src/video_core/renderer_vulkan/wrapper.h +++ b/src/video_core/renderer_vulkan/wrapper.h @@ -207,6 +207,16 @@ struct DeviceDispatch : public InstanceDispatch { PFN_vkCmdSetStencilWriteMask vkCmdSetStencilWriteMask; PFN_vkCmdSetViewport vkCmdSetViewport; PFN_vkCmdWaitEvents vkCmdWaitEvents; + PFN_vkCmdBindVertexBuffers2EXT vkCmdBindVertexBuffers2EXT; + PFN_vkCmdSetCullModeEXT vkCmdSetCullModeEXT; + PFN_vkCmdSetDepthBoundsTestEnableEXT vkCmdSetDepthBoundsTestEnableEXT; + PFN_vkCmdSetDepthCompareOpEXT vkCmdSetDepthCompareOpEXT; + PFN_vkCmdSetDepthTestEnableEXT vkCmdSetDepthTestEnableEXT; + PFN_vkCmdSetDepthWriteEnableEXT vkCmdSetDepthWriteEnableEXT; + PFN_vkCmdSetFrontFaceEXT vkCmdSetFrontFaceEXT; + PFN_vkCmdSetPrimitiveTopologyEXT vkCmdSetPrimitiveTopologyEXT; + PFN_vkCmdSetStencilOpEXT vkCmdSetStencilOpEXT; + PFN_vkCmdSetStencilTestEnableEXT vkCmdSetStencilTestEnableEXT; PFN_vkCreateBuffer vkCreateBuffer; PFN_vkCreateBufferView vkCreateBufferView; PFN_vkCreateCommandPool vkCreateCommandPool; @@ -969,6 +979,50 @@ public: buffer_barriers.data(), image_barriers.size(), image_barriers.data()); } + void BindVertexBuffers2EXT(u32 first_binding, u32 binding_count, const VkBuffer* buffers, + const VkDeviceSize* offsets, const VkDeviceSize* sizes, + const VkDeviceSize* strides) const noexcept { + dld->vkCmdBindVertexBuffers2EXT(handle, first_binding, binding_count, buffers, offsets, + sizes, strides); + } + + void SetCullModeEXT(VkCullModeFlags cull_mode) const noexcept { + dld->vkCmdSetCullModeEXT(handle, cull_mode); + } + + void SetDepthBoundsTestEnableEXT(bool enable) const noexcept { + dld->vkCmdSetDepthBoundsTestEnableEXT(handle, enable ? VK_TRUE : VK_FALSE); + } + + void SetDepthCompareOpEXT(VkCompareOp compare_op) const noexcept { + dld->vkCmdSetDepthCompareOpEXT(handle, compare_op); + } + + void SetDepthTestEnableEXT(bool enable) const noexcept { + dld->vkCmdSetDepthTestEnableEXT(handle, enable ? VK_TRUE : VK_FALSE); + } + + void SetDepthWriteEnableEXT(bool enable) const noexcept { + dld->vkCmdSetDepthWriteEnableEXT(handle, enable ? VK_TRUE : VK_FALSE); + } + + void SetFrontFaceEXT(VkFrontFace front_face) const noexcept { + dld->vkCmdSetFrontFaceEXT(handle, front_face); + } + + void SetPrimitiveTopologyEXT(VkPrimitiveTopology primitive_topology) const noexcept { + dld->vkCmdSetPrimitiveTopologyEXT(handle, primitive_topology); + } + + void SetStencilOpEXT(VkStencilFaceFlags face_mask, VkStencilOp fail_op, VkStencilOp pass_op, + VkStencilOp depth_fail_op, VkCompareOp compare_op) const noexcept { + dld->vkCmdSetStencilOpEXT(handle, face_mask, fail_op, pass_op, depth_fail_op, compare_op); + } + + void SetStencilTestEnableEXT(bool enable) const noexcept { + dld->vkCmdSetStencilTestEnableEXT(handle, enable ? VK_TRUE : VK_FALSE); + } + void BindTransformFeedbackBuffersEXT(u32 first, u32 count, const VkBuffer* buffers, const VkDeviceSize* offsets, const VkDeviceSize* sizes) const noexcept { diff --git a/src/video_core/shader_cache.h b/src/video_core/shader_cache.h index 2dd270e99..b7608fc7b 100644 --- a/src/video_core/shader_cache.h +++ b/src/video_core/shader_cache.h @@ -20,6 +20,7 @@ namespace VideoCommon { template <class T> class ShaderCache { static constexpr u64 PAGE_BITS = 14; + static constexpr u64 PAGE_SIZE = u64(1) << PAGE_BITS; struct Entry { VAddr addr_start; @@ -87,8 +88,8 @@ protected: const VAddr addr_end = addr + size; Entry* const entry = NewEntry(addr, addr_end, data.get()); - const u64 page_end = addr_end >> PAGE_BITS; - for (u64 page = addr >> PAGE_BITS; page <= page_end; ++page) { + const u64 page_end = (addr_end + PAGE_SIZE - 1) >> PAGE_BITS; + for (u64 page = addr >> PAGE_BITS; page < page_end; ++page) { invalidation_cache[page].push_back(entry); } @@ -108,20 +109,13 @@ private: /// @pre invalidation_mutex is locked void InvalidatePagesInRegion(VAddr addr, std::size_t size) { const VAddr addr_end = addr + size; - const u64 page_end = addr_end >> PAGE_BITS; - for (u64 page = addr >> PAGE_BITS; page <= page_end; ++page) { - const auto it = invalidation_cache.find(page); + const u64 page_end = (addr_end + PAGE_SIZE - 1) >> PAGE_BITS; + for (u64 page = addr >> PAGE_BITS; page < page_end; ++page) { + auto it = invalidation_cache.find(page); if (it == invalidation_cache.end()) { continue; } - - std::vector<Entry*>& entries = it->second; - InvalidatePageEntries(entries, addr, addr_end); - - // If there's nothing else in this page, remove it to avoid overpopulating the hash map. - if (entries.empty()) { - invalidation_cache.erase(it); - } + InvalidatePageEntries(it->second, addr, addr_end); } } @@ -131,15 +125,22 @@ private: if (marked_for_removal.empty()) { return; } - std::scoped_lock lock{lookup_mutex}; + // Remove duplicates + std::sort(marked_for_removal.begin(), marked_for_removal.end()); + marked_for_removal.erase(std::unique(marked_for_removal.begin(), marked_for_removal.end()), + marked_for_removal.end()); std::vector<T*> removed_shaders; removed_shaders.reserve(marked_for_removal.size()); + std::scoped_lock lock{lookup_mutex}; + for (Entry* const entry : marked_for_removal) { - if (lookup_cache.erase(entry->addr_start) > 0) { - removed_shaders.push_back(entry->data); - } + removed_shaders.push_back(entry->data); + + const auto it = lookup_cache.find(entry->addr_start); + ASSERT(it != lookup_cache.end()); + lookup_cache.erase(it); } marked_for_removal.clear(); @@ -154,17 +155,33 @@ private: /// @param addr_end Non-inclusive end address of the invalidation /// @pre invalidation_mutex is locked void InvalidatePageEntries(std::vector<Entry*>& entries, VAddr addr, VAddr addr_end) { - auto it = entries.begin(); - while (it != entries.end()) { - Entry* const entry = *it; + std::size_t index = 0; + while (index < entries.size()) { + Entry* const entry = entries[index]; if (!entry->Overlaps(addr, addr_end)) { - ++it; + ++index; continue; } + UnmarkMemory(entry); + RemoveEntryFromInvalidationCache(entry); marked_for_removal.push_back(entry); + } + } - it = entries.erase(it); + /// @brief Removes all references to an entry in the invalidation cache + /// @param entry Entry to remove from the invalidation cache + /// @pre invalidation_mutex is locked + void RemoveEntryFromInvalidationCache(const Entry* entry) { + const u64 page_end = (entry->addr_end + PAGE_SIZE - 1) >> PAGE_BITS; + for (u64 page = entry->addr_start >> PAGE_BITS; page < page_end; ++page) { + const auto entries_it = invalidation_cache.find(page); + ASSERT(entries_it != invalidation_cache.end()); + std::vector<Entry*>& entries = entries_it->second; + + const auto entry_it = std::find(entries.begin(), entries.end(), entry); + ASSERT(entry_it != entries.end()); + entries.erase(entry_it); } } @@ -182,16 +199,11 @@ private: } /// @brief Removes a vector of shaders from a list - /// @param removed_shaders Shaders to be removed from the storage, it can contain duplicates + /// @param removed_shaders Shaders to be removed from the storage /// @pre invalidation_mutex is locked /// @pre lookup_mutex is locked void RemoveShadersFromStorage(std::vector<T*> removed_shaders) { - // Remove duplicates - std::sort(removed_shaders.begin(), removed_shaders.end()); - removed_shaders.erase(std::unique(removed_shaders.begin(), removed_shaders.end()), - removed_shaders.end()); - - // Now that there are no duplicates, we can notify removals + // Notify removals for (T* const shader : removed_shaders) { OnShaderRemoval(shader); } diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 6207d8dfe..cdcddb225 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -249,7 +249,7 @@ public: auto& surface = render_targets[index].target; surface->MarkAsRenderTarget(false, NO_RT); const auto& cr_params = surface->GetSurfaceParams(); - if (!cr_params.is_tiled && Settings::values.use_asynchronous_gpu_emulation) { + if (!cr_params.is_tiled && Settings::values.use_asynchronous_gpu_emulation.GetValue()) { AsyncFlushSurface(surface); } } diff --git a/src/video_core/textures/texture.cpp b/src/video_core/textures/texture.cpp index d1939d744..4171e3ef2 100644 --- a/src/video_core/textures/texture.cpp +++ b/src/video_core/textures/texture.cpp @@ -48,7 +48,7 @@ constexpr std::array<float, 256> SRGB_CONVERSION_LUT = { }; unsigned SettingsMinimumAnisotropy() noexcept { - switch (static_cast<Anisotropy>(Settings::values.max_anisotropy)) { + switch (static_cast<Anisotropy>(Settings::values.max_anisotropy.GetValue())) { default: case Anisotropy::Default: return 1U; diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp index f60bdc60a..45f360bdd 100644 --- a/src/video_core/video_core.cpp +++ b/src/video_core/video_core.cpp @@ -19,7 +19,7 @@ namespace { std::unique_ptr<VideoCore::RendererBase> CreateRenderer(Core::Frontend::EmuWindow& emu_window, Core::System& system, Core::Frontend::GraphicsContext& context) { - switch (Settings::values.renderer_backend) { + switch (Settings::values.renderer_backend.GetValue()) { case Settings::RendererBackend::OpenGL: return std::make_unique<OpenGL::RendererOpenGL>(emu_window, system, context); #ifdef HAS_VULKAN @@ -42,7 +42,7 @@ std::unique_ptr<Tegra::GPU> CreateGPU(Core::Frontend::EmuWindow& emu_window, Cor return nullptr; } - if (Settings::values.use_asynchronous_gpu_emulation) { + if (Settings::values.use_asynchronous_gpu_emulation.GetValue()) { return std::make_unique<VideoCommon::GPUAsynch>(system, std::move(renderer), std::move(context)); } @@ -51,8 +51,8 @@ std::unique_ptr<Tegra::GPU> CreateGPU(Core::Frontend::EmuWindow& emu_window, Cor u16 GetResolutionScaleFactor(const RendererBase& renderer) { return static_cast<u16>( - Settings::values.resolution_factor != 0 - ? Settings::values.resolution_factor + Settings::values.resolution_factor.GetValue() != 0 + ? Settings::values.resolution_factor.GetValue() : renderer.GetRenderWindow().GetFramebufferLayout().GetScalingRatio()); } diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index c08a10f29..6b25a7fa0 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -24,6 +24,8 @@ add_executable(yuzu compatibility_list.h configuration/config.cpp configuration/config.h + configuration/configuration_shared.cpp + configuration/configuration_shared.h configuration/configure.ui configuration/configure_audio.cpp configuration/configure_audio.h @@ -60,9 +62,12 @@ add_executable(yuzu configuration/configure_mouse_advanced.cpp configuration/configure_mouse_advanced.h configuration/configure_mouse_advanced.ui - configuration/configure_per_general.cpp - configuration/configure_per_general.h - configuration/configure_per_general.ui + configuration/configure_per_game.cpp + configuration/configure_per_game.h + configuration/configure_per_game.ui + configuration/configure_per_game_addons.cpp + configuration/configure_per_game_addons.h + configuration/configure_per_game_addons.ui configuration/configure_profile_manager.cpp configuration/configure_profile_manager.h configuration/configure_profile_manager.ui diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 4bfce48a4..5738787ac 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -145,7 +145,7 @@ public: // disable vsync for any shared contexts auto format = share_context->format(); - format.setSwapInterval(main_surface ? Settings::values.use_vsync : 0); + format.setSwapInterval(main_surface ? Settings::values.use_vsync.GetValue() : 0); context = std::make_unique<QOpenGLContext>(); context->setShareContext(share_context); @@ -495,7 +495,7 @@ void GRenderWindow::resizeEvent(QResizeEvent* event) { std::unique_ptr<Core::Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const { #ifdef HAS_OPENGL - if (Settings::values.renderer_backend == Settings::RendererBackend::OpenGL) { + if (Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL) { auto c = static_cast<OpenGLSharedContext*>(main_context.get()); // Bind the shared contexts to the main surface in case the backend wants to take over // presentation @@ -511,7 +511,7 @@ bool GRenderWindow::InitRenderTarget() { first_frame = false; - switch (Settings::values.renderer_backend) { + switch (Settings::values.renderer_backend.GetValue()) { case Settings::RendererBackend::OpenGL: if (!InitializeOpenGL()) { return false; @@ -538,7 +538,7 @@ bool GRenderWindow::InitRenderTarget() { OnFramebufferSizeChanged(); BackupGeometry(); - if (Settings::values.renderer_backend == Settings::RendererBackend::OpenGL) { + if (Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL) { if (!LoadOpenGL()) { return false; } diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index bbbd96113..1b2b1b2bb 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -13,17 +13,20 @@ #include "input_common/udp/client.h" #include "yuzu/configuration/config.h" -Config::Config() { +Config::Config(const std::string& config_file, bool is_global) { // TODO: Don't hardcode the path; let the frontend decide where to put the config files. - qt_config_loc = FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "qt-config.ini"; + qt_config_loc = FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + config_file; FileUtil::CreateFullPath(qt_config_loc); qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat); + global = is_global; Reload(); } Config::~Config() { - Save(); + if (global) { + Save(); + } } const std::array<int, Settings::NativeButton::NumButtons> Config::default_buttons = { @@ -212,7 +215,7 @@ const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> Config::default // UISetting::values.shortcuts, which is alphabetically ordered. // clang-format off const std::array<UISettings::Shortcut, 16> Config::default_hotkeys{{ - {QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::ApplicationShortcut}}, + {QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::WidgetWithChildrenShortcut}}, {QStringLiteral("Change Docked Mode"), QStringLiteral("Main Window"), {QStringLiteral("F10"), Qt::ApplicationShortcut}}, {QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}}, {QStringLiteral("Decrease Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("-"), Qt::ApplicationShortcut}}, @@ -220,8 +223,8 @@ const std::array<UISettings::Shortcut, 16> Config::default_hotkeys{{ {QStringLiteral("Exit yuzu"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Q"), Qt::WindowShortcut}}, {QStringLiteral("Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("F11"), Qt::WindowShortcut}}, {QStringLiteral("Increase Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("+"), Qt::ApplicationShortcut}}, - {QStringLiteral("Load Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), Qt::ApplicationShortcut}}, - {QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), Qt::WindowShortcut}}, + {QStringLiteral("Load Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), Qt::WidgetWithChildrenShortcut}}, + {QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), Qt::WidgetWithChildrenShortcut}}, {QStringLiteral("Mute Audio"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), Qt::WindowShortcut}}, {QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}}, {QStringLiteral("Stop Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F5"), Qt::WindowShortcut}}, @@ -402,16 +405,19 @@ void Config::ApplyDefaultProfileIfInputInvalid() { void Config::ReadAudioValues() { qt_config->beginGroup(QStringLiteral("Audio")); - Settings::values.sink_id = ReadSetting(QStringLiteral("output_engine"), QStringLiteral("auto")) - .toString() - .toStdString(); - Settings::values.enable_audio_stretching = - ReadSetting(QStringLiteral("enable_audio_stretching"), true).toBool(); - Settings::values.audio_device_id = - ReadSetting(QStringLiteral("output_device"), QStringLiteral("auto")) - .toString() - .toStdString(); - Settings::values.volume = ReadSetting(QStringLiteral("volume"), 1).toFloat(); + if (global) { + Settings::values.sink_id = + ReadSetting(QStringLiteral("output_engine"), QStringLiteral("auto")) + .toString() + .toStdString(); + Settings::values.audio_device_id = + ReadSetting(QStringLiteral("output_device"), QStringLiteral("auto")) + .toString() + .toStdString(); + } + ReadSettingGlobal(Settings::values.enable_audio_stretching, + QStringLiteral("enable_audio_stretching"), true); + ReadSettingGlobal(Settings::values.volume, QStringLiteral("volume"), 1); qt_config->endGroup(); } @@ -440,6 +446,8 @@ void Config::ReadControlValues() { .toInt()); Settings::values.udp_pad_index = static_cast<u8>(ReadSetting(QStringLiteral("udp_pad_index"), 0).toUInt()); + Settings::values.use_docked_mode = + ReadSetting(QStringLiteral("use_docked_mode"), false).toBool(); qt_config->endGroup(); } @@ -447,7 +455,7 @@ void Config::ReadControlValues() { void Config::ReadCoreValues() { qt_config->beginGroup(QStringLiteral("Core")); - Settings::values.use_multi_core = ReadSetting(QStringLiteral("use_multi_core"), false).toBool(); + ReadSettingGlobal(Settings::values.use_multi_core, QStringLiteral("use_multi_core"), false); qt_config->endGroup(); } @@ -628,32 +636,28 @@ void Config::ReadPathValues() { void Config::ReadRendererValues() { qt_config->beginGroup(QStringLiteral("Renderer")); - Settings::values.renderer_backend = - static_cast<Settings::RendererBackend>(ReadSetting(QStringLiteral("backend"), 0).toInt()); - Settings::values.renderer_debug = ReadSetting(QStringLiteral("debug"), false).toBool(); - Settings::values.vulkan_device = ReadSetting(QStringLiteral("vulkan_device"), 0).toInt(); - Settings::values.aspect_ratio = ReadSetting(QStringLiteral("aspect_ratio"), 0).toInt(); - Settings::values.max_anisotropy = ReadSetting(QStringLiteral("max_anisotropy"), 0).toInt(); - Settings::values.use_frame_limit = - ReadSetting(QStringLiteral("use_frame_limit"), true).toBool(); - Settings::values.frame_limit = ReadSetting(QStringLiteral("frame_limit"), 100).toUInt(); - Settings::values.use_disk_shader_cache = - ReadSetting(QStringLiteral("use_disk_shader_cache"), true).toBool(); - const int gpu_accuracy_level = ReadSetting(QStringLiteral("gpu_accuracy"), 0).toInt(); - Settings::values.gpu_accuracy = static_cast<Settings::GPUAccuracy>(gpu_accuracy_level); - Settings::values.use_asynchronous_gpu_emulation = - ReadSetting(QStringLiteral("use_asynchronous_gpu_emulation"), false).toBool(); - Settings::values.use_vsync = ReadSetting(QStringLiteral("use_vsync"), true).toBool(); - Settings::values.use_assembly_shaders = - ReadSetting(QStringLiteral("use_assembly_shaders"), false).toBool(); - Settings::values.use_fast_gpu_time = - ReadSetting(QStringLiteral("use_fast_gpu_time"), true).toBool(); - Settings::values.force_30fps_mode = - ReadSetting(QStringLiteral("force_30fps_mode"), false).toBool(); - - Settings::values.bg_red = ReadSetting(QStringLiteral("bg_red"), 0.0).toFloat(); - Settings::values.bg_green = ReadSetting(QStringLiteral("bg_green"), 0.0).toFloat(); - Settings::values.bg_blue = ReadSetting(QStringLiteral("bg_blue"), 0.0).toFloat(); + ReadSettingGlobal(Settings::values.renderer_backend, QStringLiteral("backend"), 0); + ReadSettingGlobal(Settings::values.renderer_debug, QStringLiteral("debug"), false); + ReadSettingGlobal(Settings::values.vulkan_device, QStringLiteral("vulkan_device"), 0); + ReadSettingGlobal(Settings::values.aspect_ratio, QStringLiteral("aspect_ratio"), 0); + ReadSettingGlobal(Settings::values.max_anisotropy, QStringLiteral("max_anisotropy"), 0); + ReadSettingGlobal(Settings::values.use_frame_limit, QStringLiteral("use_frame_limit"), true); + ReadSettingGlobal(Settings::values.frame_limit, QStringLiteral("frame_limit"), 100); + ReadSettingGlobal(Settings::values.use_disk_shader_cache, + QStringLiteral("use_disk_shader_cache"), true); + ReadSettingGlobal(Settings::values.gpu_accuracy, QStringLiteral("gpu_accuracy"), 0); + ReadSettingGlobal(Settings::values.use_asynchronous_gpu_emulation, + QStringLiteral("use_asynchronous_gpu_emulation"), false); + ReadSettingGlobal(Settings::values.use_vsync, QStringLiteral("use_vsync"), true); + ReadSettingGlobal(Settings::values.use_assembly_shaders, QStringLiteral("use_assembly_shaders"), + false); + ReadSettingGlobal(Settings::values.use_fast_gpu_time, QStringLiteral("use_fast_gpu_time"), + true); + ReadSettingGlobal(Settings::values.force_30fps_mode, QStringLiteral("force_30fps_mode"), false); + + ReadSettingGlobal(Settings::values.bg_red, QStringLiteral("bg_red"), 0.0); + ReadSettingGlobal(Settings::values.bg_green, QStringLiteral("bg_green"), 0.0); + ReadSettingGlobal(Settings::values.bg_blue, QStringLiteral("bg_blue"), 0.0); qt_config->endGroup(); } @@ -665,11 +669,13 @@ void Config::ReadShortcutValues() { const auto& [keyseq, context] = shortcut; qt_config->beginGroup(group); qt_config->beginGroup(name); + // No longer using ReadSetting for shortcut.second as it innacurately returns a value of 1 + // for WidgetWithChildrenShortcut which is a value of 3. Needed to fix shortcuts the open + // a file dialog in windowed mode UISettings::values.shortcuts.push_back( {name, group, - {ReadSetting(QStringLiteral("KeySeq"), keyseq).toString(), - ReadSetting(QStringLiteral("Context"), context).toInt()}}); + {ReadSetting(QStringLiteral("KeySeq"), keyseq).toString(), shortcut.second}}); qt_config->endGroup(); qt_config->endGroup(); } @@ -680,35 +686,45 @@ void Config::ReadShortcutValues() { void Config::ReadSystemValues() { qt_config->beginGroup(QStringLiteral("System")); - Settings::values.use_docked_mode = - ReadSetting(QStringLiteral("use_docked_mode"), false).toBool(); - - Settings::values.current_user = std::clamp<int>( - ReadSetting(QStringLiteral("current_user"), 0).toInt(), 0, Service::Account::MAX_USERS - 1); + ReadSettingGlobal(Settings::values.current_user, QStringLiteral("current_user"), 0); + Settings::values.current_user = + std::clamp<int>(Settings::values.current_user, 0, Service::Account::MAX_USERS - 1); - Settings::values.language_index = ReadSetting(QStringLiteral("language_index"), 1).toInt(); + ReadSettingGlobal(Settings::values.language_index, QStringLiteral("language_index"), 1); - Settings::values.region_index = ReadSetting(QStringLiteral("region_index"), 1).toInt(); + ReadSettingGlobal(Settings::values.region_index, QStringLiteral("region_index"), 1); - Settings::values.time_zone_index = ReadSetting(QStringLiteral("time_zone_index"), 0).toInt(); + ReadSettingGlobal(Settings::values.time_zone_index, QStringLiteral("time_zone_index"), 0); - const auto rng_seed_enabled = ReadSetting(QStringLiteral("rng_seed_enabled"), false).toBool(); - if (rng_seed_enabled) { - Settings::values.rng_seed = ReadSetting(QStringLiteral("rng_seed"), 0).toULongLong(); - } else { - Settings::values.rng_seed = std::nullopt; + bool rng_seed_enabled; + ReadSettingGlobal(rng_seed_enabled, QStringLiteral("rng_seed_enabled"), false); + bool rng_seed_global = + global || qt_config->value(QStringLiteral("rng_seed/use_global"), true).toBool(); + Settings::values.rng_seed.SetGlobal(rng_seed_global); + if (global || !rng_seed_global) { + if (rng_seed_enabled) { + Settings::values.rng_seed.SetValue( + ReadSetting(QStringLiteral("rng_seed"), 0).toULongLong()); + } else { + Settings::values.rng_seed.SetValue(std::nullopt); + } } - const auto custom_rtc_enabled = - ReadSetting(QStringLiteral("custom_rtc_enabled"), false).toBool(); - if (custom_rtc_enabled) { - Settings::values.custom_rtc = - std::chrono::seconds(ReadSetting(QStringLiteral("custom_rtc"), 0).toULongLong()); - } else { - Settings::values.custom_rtc = std::nullopt; + bool custom_rtc_enabled; + ReadSettingGlobal(custom_rtc_enabled, QStringLiteral("custom_rtc_enabled"), false); + bool custom_rtc_global = + global || qt_config->value(QStringLiteral("custom_rtc/use_global"), true).toBool(); + Settings::values.custom_rtc.SetGlobal(custom_rtc_global); + if (global || !custom_rtc_global) { + if (custom_rtc_enabled) { + Settings::values.custom_rtc.SetValue( + std::chrono::seconds(ReadSetting(QStringLiteral("custom_rtc"), 0).toULongLong())); + } else { + Settings::values.custom_rtc.SetValue(std::nullopt); + } } - Settings::values.sound_index = ReadSetting(QStringLiteral("sound_index"), 1).toInt(); + ReadSettingGlobal(Settings::values.sound_index, QStringLiteral("sound_index"), 1); qt_config->endGroup(); } @@ -802,18 +818,20 @@ void Config::ReadWebServiceValues() { } void Config::ReadValues() { - ReadControlValues(); + if (global) { + ReadControlValues(); + ReadDataStorageValues(); + ReadDebuggingValues(); + ReadDisabledAddOnValues(); + ReadServiceValues(); + ReadUIValues(); + ReadWebServiceValues(); + ReadMiscellaneousValues(); + } ReadCoreValues(); ReadRendererValues(); ReadAudioValues(); - ReadDataStorageValues(); ReadSystemValues(); - ReadMiscellaneousValues(); - ReadDebuggingValues(); - ReadWebServiceValues(); - ReadServiceValues(); - ReadDisabledAddOnValues(); - ReadUIValues(); } void Config::SavePlayerValues() { @@ -900,30 +918,35 @@ void Config::SaveTouchscreenValues() { } void Config::SaveValues() { - SaveControlValues(); + if (global) { + SaveControlValues(); + SaveDataStorageValues(); + SaveDebuggingValues(); + SaveDisabledAddOnValues(); + SaveServiceValues(); + SaveUIValues(); + SaveWebServiceValues(); + SaveMiscellaneousValues(); + } SaveCoreValues(); SaveRendererValues(); SaveAudioValues(); - SaveDataStorageValues(); SaveSystemValues(); - SaveMiscellaneousValues(); - SaveDebuggingValues(); - SaveWebServiceValues(); - SaveServiceValues(); - SaveDisabledAddOnValues(); - SaveUIValues(); } void Config::SaveAudioValues() { qt_config->beginGroup(QStringLiteral("Audio")); - WriteSetting(QStringLiteral("output_engine"), QString::fromStdString(Settings::values.sink_id), - QStringLiteral("auto")); - WriteSetting(QStringLiteral("enable_audio_stretching"), - Settings::values.enable_audio_stretching, true); - WriteSetting(QStringLiteral("output_device"), - QString::fromStdString(Settings::values.audio_device_id), QStringLiteral("auto")); - WriteSetting(QStringLiteral("volume"), Settings::values.volume, 1.0f); + if (global) { + WriteSetting(QStringLiteral("output_engine"), + QString::fromStdString(Settings::values.sink_id), QStringLiteral("auto")); + WriteSetting(QStringLiteral("output_device"), + QString::fromStdString(Settings::values.audio_device_id), + QStringLiteral("auto")); + } + WriteSettingGlobal(QStringLiteral("enable_audio_stretching"), + Settings::values.enable_audio_stretching, true); + WriteSettingGlobal(QStringLiteral("volume"), Settings::values.volume, 1.0f); qt_config->endGroup(); } @@ -946,6 +969,7 @@ void Config::SaveControlValues() { WriteSetting(QStringLiteral("udp_input_port"), Settings::values.udp_input_port, InputCommon::CemuhookUDP::DEFAULT_PORT); WriteSetting(QStringLiteral("udp_pad_index"), Settings::values.udp_pad_index, 0); + WriteSetting(QStringLiteral("use_docked_mode"), Settings::values.use_docked_mode, false); qt_config->endGroup(); } @@ -953,7 +977,7 @@ void Config::SaveControlValues() { void Config::SaveCoreValues() { qt_config->beginGroup(QStringLiteral("Core")); - WriteSetting(QStringLiteral("use_multi_core"), Settings::values.use_multi_core, false); + WriteSettingGlobal(QStringLiteral("use_multi_core"), Settings::values.use_multi_core, false); qt_config->endGroup(); } @@ -1076,29 +1100,34 @@ void Config::SavePathValues() { void Config::SaveRendererValues() { qt_config->beginGroup(QStringLiteral("Renderer")); - WriteSetting(QStringLiteral("backend"), static_cast<int>(Settings::values.renderer_backend), 0); + WriteSettingGlobal(QStringLiteral("backend"), + static_cast<int>(Settings::values.renderer_backend.GetValue(global)), + Settings::values.renderer_backend.UsingGlobal(), 0); WriteSetting(QStringLiteral("debug"), Settings::values.renderer_debug, false); - WriteSetting(QStringLiteral("vulkan_device"), Settings::values.vulkan_device, 0); - WriteSetting(QStringLiteral("aspect_ratio"), Settings::values.aspect_ratio, 0); - WriteSetting(QStringLiteral("max_anisotropy"), Settings::values.max_anisotropy, 0); - WriteSetting(QStringLiteral("use_frame_limit"), Settings::values.use_frame_limit, true); - WriteSetting(QStringLiteral("frame_limit"), Settings::values.frame_limit, 100); - WriteSetting(QStringLiteral("use_disk_shader_cache"), Settings::values.use_disk_shader_cache, - true); - WriteSetting(QStringLiteral("gpu_accuracy"), static_cast<int>(Settings::values.gpu_accuracy), - 0); - WriteSetting(QStringLiteral("use_asynchronous_gpu_emulation"), - Settings::values.use_asynchronous_gpu_emulation, false); - WriteSetting(QStringLiteral("use_vsync"), Settings::values.use_vsync, true); - WriteSetting(QStringLiteral("use_assembly_shaders"), Settings::values.use_assembly_shaders, - false); - WriteSetting(QStringLiteral("use_fast_gpu_time"), Settings::values.use_fast_gpu_time, true); - WriteSetting(QStringLiteral("force_30fps_mode"), Settings::values.force_30fps_mode, false); + WriteSettingGlobal(QStringLiteral("vulkan_device"), Settings::values.vulkan_device, 0); + WriteSettingGlobal(QStringLiteral("aspect_ratio"), Settings::values.aspect_ratio, 0); + WriteSettingGlobal(QStringLiteral("max_anisotropy"), Settings::values.max_anisotropy, 0); + WriteSettingGlobal(QStringLiteral("use_frame_limit"), Settings::values.use_frame_limit, true); + WriteSettingGlobal(QStringLiteral("frame_limit"), Settings::values.frame_limit, 100); + WriteSettingGlobal(QStringLiteral("use_disk_shader_cache"), + Settings::values.use_disk_shader_cache, true); + WriteSettingGlobal(QStringLiteral("gpu_accuracy"), + static_cast<int>(Settings::values.gpu_accuracy.GetValue(global)), + Settings::values.gpu_accuracy.UsingGlobal(), 0); + WriteSettingGlobal(QStringLiteral("use_asynchronous_gpu_emulation"), + Settings::values.use_asynchronous_gpu_emulation, false); + WriteSettingGlobal(QStringLiteral("use_vsync"), Settings::values.use_vsync, true); + WriteSettingGlobal(QStringLiteral("use_assembly_shaders"), + Settings::values.use_assembly_shaders, false); + WriteSettingGlobal(QStringLiteral("use_fast_gpu_time"), Settings::values.use_fast_gpu_time, + true); + WriteSettingGlobal(QStringLiteral("force_30fps_mode"), Settings::values.force_30fps_mode, + false); // Cast to double because Qt's written float values are not human-readable - WriteSetting(QStringLiteral("bg_red"), static_cast<double>(Settings::values.bg_red), 0.0); - WriteSetting(QStringLiteral("bg_green"), static_cast<double>(Settings::values.bg_green), 0.0); - WriteSetting(QStringLiteral("bg_blue"), static_cast<double>(Settings::values.bg_blue), 0.0); + WriteSettingGlobal(QStringLiteral("bg_red"), Settings::values.bg_red, 0.0); + WriteSettingGlobal(QStringLiteral("bg_green"), Settings::values.bg_green, 0.0); + WriteSettingGlobal(QStringLiteral("bg_blue"), Settings::values.bg_blue, 0.0); qt_config->endGroup(); } @@ -1126,23 +1155,28 @@ void Config::SaveShortcutValues() { void Config::SaveSystemValues() { qt_config->beginGroup(QStringLiteral("System")); - WriteSetting(QStringLiteral("use_docked_mode"), Settings::values.use_docked_mode, false); WriteSetting(QStringLiteral("current_user"), Settings::values.current_user, 0); - WriteSetting(QStringLiteral("language_index"), Settings::values.language_index, 1); - WriteSetting(QStringLiteral("region_index"), Settings::values.region_index, 1); - WriteSetting(QStringLiteral("time_zone_index"), Settings::values.time_zone_index, 0); - - WriteSetting(QStringLiteral("rng_seed_enabled"), Settings::values.rng_seed.has_value(), false); - WriteSetting(QStringLiteral("rng_seed"), Settings::values.rng_seed.value_or(0), 0); - - WriteSetting(QStringLiteral("custom_rtc_enabled"), Settings::values.custom_rtc.has_value(), - false); - WriteSetting(QStringLiteral("custom_rtc"), - QVariant::fromValue<long long>( - Settings::values.custom_rtc.value_or(std::chrono::seconds{}).count()), - 0); - - WriteSetting(QStringLiteral("sound_index"), Settings::values.sound_index, 1); + WriteSettingGlobal(QStringLiteral("language_index"), Settings::values.language_index, 1); + WriteSettingGlobal(QStringLiteral("region_index"), Settings::values.region_index, 1); + WriteSettingGlobal(QStringLiteral("time_zone_index"), Settings::values.time_zone_index, 0); + + WriteSettingGlobal(QStringLiteral("rng_seed_enabled"), + Settings::values.rng_seed.GetValue(global).has_value(), + Settings::values.rng_seed.UsingGlobal(), false); + WriteSettingGlobal(QStringLiteral("rng_seed"), + Settings::values.rng_seed.GetValue(global).value_or(0), + Settings::values.rng_seed.UsingGlobal(), 0); + + WriteSettingGlobal(QStringLiteral("custom_rtc_enabled"), + Settings::values.custom_rtc.GetValue(global).has_value(), + Settings::values.custom_rtc.UsingGlobal(), false); + WriteSettingGlobal( + QStringLiteral("custom_rtc"), + QVariant::fromValue<long long>( + Settings::values.custom_rtc.GetValue(global).value_or(std::chrono::seconds{}).count()), + Settings::values.custom_rtc.UsingGlobal(), 0); + + WriteSettingGlobal(QStringLiteral("sound_index"), Settings::values.sound_index, 1); qt_config->endGroup(); } @@ -1234,6 +1268,34 @@ QVariant Config::ReadSetting(const QString& name, const QVariant& default_value) return result; } +template <typename Type> +void Config::ReadSettingGlobal(Settings::Setting<Type>& setting, const QString& name) { + const bool use_global = qt_config->value(name + QStringLiteral("/use_global"), true).toBool(); + setting.SetGlobal(use_global); + if (global || !use_global) { + setting.SetValue(ReadSetting(name).value<Type>()); + } +} + +template <typename Type> +void Config::ReadSettingGlobal(Settings::Setting<Type>& setting, const QString& name, + const QVariant& default_value) { + const bool use_global = qt_config->value(name + QStringLiteral("/use_global"), true).toBool(); + setting.SetGlobal(use_global); + if (global || !use_global) { + setting.SetValue(ReadSetting(name, default_value).value<Type>()); + } +} + +template <typename Type> +void Config::ReadSettingGlobal(Type& setting, const QString& name, + const QVariant& default_value) const { + const bool use_global = qt_config->value(name + QStringLiteral("/use_global"), true).toBool(); + if (global || !use_global) { + setting = ReadSetting(name, default_value).value<Type>(); + } +} + void Config::WriteSetting(const QString& name, const QVariant& value) { qt_config->setValue(name, value); } @@ -1244,6 +1306,40 @@ void Config::WriteSetting(const QString& name, const QVariant& value, qt_config->setValue(name, value); } +template <typename Type> +void Config::WriteSettingGlobal(const QString& name, const Settings::Setting<Type>& setting) { + if (!global) { + qt_config->setValue(name + QStringLiteral("/use_global"), setting.UsingGlobal()); + } + if (global || !setting.UsingGlobal()) { + qt_config->setValue(name, setting.GetValue(global)); + } +} + +template <typename Type> +void Config::WriteSettingGlobal(const QString& name, const Settings::Setting<Type>& setting, + const QVariant& default_value) { + if (!global) { + qt_config->setValue(name + QStringLiteral("/use_global"), setting.UsingGlobal()); + } + if (global || !setting.UsingGlobal()) { + qt_config->setValue(name + QStringLiteral("/default"), + setting.GetValue(global) == default_value.value<Type>()); + qt_config->setValue(name, setting.GetValue(global)); + } +} + +void Config::WriteSettingGlobal(const QString& name, const QVariant& value, bool use_global, + const QVariant& default_value) { + if (!global) { + qt_config->setValue(name + QStringLiteral("/use_global"), use_global); + } + if (global || !use_global) { + qt_config->setValue(name + QStringLiteral("/default"), value == default_value); + qt_config->setValue(name, value); + } +} + void Config::Reload() { ReadValues(); // To apply default value changes diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h index 09316382c..681f0bca5 100644 --- a/src/yuzu/configuration/config.h +++ b/src/yuzu/configuration/config.h @@ -7,6 +7,7 @@ #include <array> #include <memory> #include <string> +#include <QMetaType> #include <QVariant> #include "core/settings.h" #include "yuzu/uisettings.h" @@ -15,7 +16,7 @@ class QSettings; class Config { public: - Config(); + explicit Config(const std::string& config_loc = "qt-config.ini", bool is_global = true); ~Config(); void Reload(); @@ -82,9 +83,33 @@ private: QVariant ReadSetting(const QString& name) const; QVariant ReadSetting(const QString& name, const QVariant& default_value) const; + // Templated ReadSettingGlobal functions will also look for the use_global setting and set + // both the value and the global state properly + template <typename Type> + void ReadSettingGlobal(Settings::Setting<Type>& setting, const QString& name); + template <typename Type> + void ReadSettingGlobal(Settings::Setting<Type>& setting, const QString& name, + const QVariant& default_value); + template <typename Type> + void ReadSettingGlobal(Type& setting, const QString& name, const QVariant& default_value) const; + // Templated WriteSettingGlobal functions will also write the global state if needed and will + // skip writing the actual setting if it defers to the global value void WriteSetting(const QString& name, const QVariant& value); void WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value); + template <typename Type> + void WriteSettingGlobal(const QString& name, const Settings::Setting<Type>& setting); + template <typename Type> + void WriteSettingGlobal(const QString& name, const Settings::Setting<Type>& setting, + const QVariant& default_value); + void WriteSettingGlobal(const QString& name, const QVariant& value, bool use_global, + const QVariant& default_value); std::unique_ptr<QSettings> qt_config; std::string qt_config_loc; + + bool global; }; + +// These metatype declarations cannot be in core/settings.h because core is devoid of QT +Q_DECLARE_METATYPE(Settings::RendererBackend); +Q_DECLARE_METATYPE(Settings::GPUAccuracy); diff --git a/src/yuzu/configuration/configuration_shared.cpp b/src/yuzu/configuration/configuration_shared.cpp new file mode 100644 index 000000000..bb47c3933 --- /dev/null +++ b/src/yuzu/configuration/configuration_shared.cpp @@ -0,0 +1,76 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <QCheckBox> +#include <QComboBox> +#include "core/settings.h" +#include "yuzu/configuration/configuration_shared.h" +#include "yuzu/configuration/configure_per_game.h" + +void ConfigurationShared::ApplyPerGameSetting(Settings::Setting<bool>* setting, + const QCheckBox* checkbox) { + if (checkbox->checkState() == Qt::PartiallyChecked) { + setting->SetGlobal(true); + } else { + setting->SetGlobal(false); + setting->SetValue(checkbox->checkState() == Qt::Checked); + } +} + +void ConfigurationShared::ApplyPerGameSetting(Settings::Setting<int>* setting, + const QComboBox* combobox) { + if (combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { + setting->SetGlobal(true); + } else { + setting->SetGlobal(false); + setting->SetValue(combobox->currentIndex() - ConfigurationShared::USE_GLOBAL_OFFSET); + } +} + +void ConfigurationShared::ApplyPerGameSetting(Settings::Setting<Settings::RendererBackend>* setting, + const QComboBox* combobox) { + if (combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { + setting->SetGlobal(true); + } else { + setting->SetGlobal(false); + setting->SetValue(static_cast<Settings::RendererBackend>( + combobox->currentIndex() - ConfigurationShared::USE_GLOBAL_OFFSET)); + } +} + +void ConfigurationShared::SetPerGameSetting(QCheckBox* checkbox, + const Settings::Setting<bool>* setting) { + if (setting->UsingGlobal()) { + checkbox->setCheckState(Qt::PartiallyChecked); + } else { + checkbox->setCheckState(setting->GetValue() ? Qt::Checked : Qt::Unchecked); + } +} + +void ConfigurationShared::SetPerGameSetting(QComboBox* combobox, + const Settings::Setting<int>* setting) { + combobox->setCurrentIndex(setting->UsingGlobal() + ? ConfigurationShared::USE_GLOBAL_INDEX + : setting->GetValue() + ConfigurationShared::USE_GLOBAL_OFFSET); +} + +void ConfigurationShared::SetPerGameSetting( + QComboBox* combobox, const Settings::Setting<Settings::RendererBackend>* setting) { + combobox->setCurrentIndex(setting->UsingGlobal() ? ConfigurationShared::USE_GLOBAL_INDEX + : static_cast<int>(setting->GetValue()) + + ConfigurationShared::USE_GLOBAL_OFFSET); +} + +void ConfigurationShared::SetPerGameSetting( + QComboBox* combobox, const Settings::Setting<Settings::GPUAccuracy>* setting) { + combobox->setCurrentIndex(setting->UsingGlobal() ? ConfigurationShared::USE_GLOBAL_INDEX + : static_cast<int>(setting->GetValue()) + + ConfigurationShared::USE_GLOBAL_OFFSET); +} + +void ConfigurationShared::InsertGlobalItem(QComboBox* combobox) { + const QString use_global_text = ConfigurePerGame::tr("Use global configuration"); + combobox->insertItem(ConfigurationShared::USE_GLOBAL_INDEX, use_global_text); + combobox->insertSeparator(ConfigurationShared::USE_GLOBAL_SEPARATOR_INDEX); +} diff --git a/src/yuzu/configuration/configuration_shared.h b/src/yuzu/configuration/configuration_shared.h new file mode 100644 index 000000000..b11b1b950 --- /dev/null +++ b/src/yuzu/configuration/configuration_shared.h @@ -0,0 +1,36 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <QCheckBox> +#include <QComboBox> +#include <QString> +#include "core/settings.h" + +namespace ConfigurationShared { + +constexpr int USE_GLOBAL_INDEX = 0; +constexpr int USE_GLOBAL_SEPARATOR_INDEX = 1; +constexpr int USE_GLOBAL_OFFSET = 2; + +// Global-aware apply and set functions + +void ApplyPerGameSetting(Settings::Setting<bool>* setting, const QCheckBox* checkbox); +void ApplyPerGameSetting(Settings::Setting<int>* setting, const QComboBox* combobox); +void ApplyPerGameSetting(Settings::Setting<Settings::RendererBackend>* setting, + const QComboBox* combobox); +void ApplyPerGameSetting(Settings::Setting<Settings::GPUAccuracy>* setting, + const QComboBox* combobox); + +void SetPerGameSetting(QCheckBox* checkbox, const Settings::Setting<bool>* setting); +void SetPerGameSetting(QComboBox* combobox, const Settings::Setting<int>* setting); +void SetPerGameSetting(QComboBox* combobox, + const Settings::Setting<Settings::RendererBackend>* setting); +void SetPerGameSetting(QComboBox* combobox, + const Settings::Setting<Settings::GPUAccuracy>* setting); + +void InsertGlobalItem(QComboBox* combobox); + +} // namespace ConfigurationShared diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp index f370c690f..cc021beec 100644 --- a/src/yuzu/configuration/configure_audio.cpp +++ b/src/yuzu/configuration/configure_audio.cpp @@ -11,6 +11,7 @@ #include "core/core.h" #include "core/settings.h" #include "ui_configure_audio.h" +#include "yuzu/configuration/configuration_shared.h" #include "yuzu/configuration/configure_audio.h" ConfigureAudio::ConfigureAudio(QWidget* parent) @@ -24,6 +25,11 @@ ConfigureAudio::ConfigureAudio(QWidget* parent) connect(ui->output_sink_combo_box, qOverload<int>(&QComboBox::currentIndexChanged), this, &ConfigureAudio::UpdateAudioDevices); + ui->volume_label->setVisible(Settings::configuring_global); + ui->volume_combo_box->setVisible(!Settings::configuring_global); + + SetupPerGameUI(); + SetConfiguration(); const bool is_powered_on = Core::System::GetInstance().IsPoweredOn(); @@ -41,8 +47,22 @@ void ConfigureAudio::SetConfiguration() { SetAudioDeviceFromDeviceID(); - ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching); - ui->volume_slider->setValue(Settings::values.volume * ui->volume_slider->maximum()); + ui->volume_slider->setValue(Settings::values.volume.GetValue() * ui->volume_slider->maximum()); + + if (Settings::configuring_global) { + ui->toggle_audio_stretching->setChecked( + Settings::values.enable_audio_stretching.GetValue()); + } else { + ConfigurationShared::SetPerGameSetting(ui->toggle_audio_stretching, + &Settings::values.enable_audio_stretching); + if (Settings::values.volume.UsingGlobal()) { + ui->volume_combo_box->setCurrentIndex(0); + ui->volume_slider->setEnabled(false); + } else { + ui->volume_combo_box->setCurrentIndex(1); + ui->volume_slider->setEnabled(true); + } + } SetVolumeIndicatorText(ui->volume_slider->sliderPosition()); } @@ -80,15 +100,36 @@ void ConfigureAudio::SetVolumeIndicatorText(int percentage) { } void ConfigureAudio::ApplyConfiguration() { - Settings::values.sink_id = - ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex()) - .toStdString(); - Settings::values.enable_audio_stretching = ui->toggle_audio_stretching->isChecked(); - Settings::values.audio_device_id = - ui->audio_device_combo_box->itemText(ui->audio_device_combo_box->currentIndex()) - .toStdString(); - Settings::values.volume = - static_cast<float>(ui->volume_slider->sliderPosition()) / ui->volume_slider->maximum(); + if (Settings::configuring_global) { + Settings::values.sink_id = + ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex()) + .toStdString(); + Settings::values.audio_device_id = + ui->audio_device_combo_box->itemText(ui->audio_device_combo_box->currentIndex()) + .toStdString(); + + // Guard if during game and set to game-specific value + if (Settings::values.enable_audio_stretching.UsingGlobal()) { + Settings::values.enable_audio_stretching.SetValue( + ui->toggle_audio_stretching->isChecked()); + } + if (Settings::values.volume.UsingGlobal()) { + Settings::values.volume.SetValue( + static_cast<float>(ui->volume_slider->sliderPosition()) / + ui->volume_slider->maximum()); + } + } else { + ConfigurationShared::ApplyPerGameSetting(&Settings::values.enable_audio_stretching, + ui->toggle_audio_stretching); + if (ui->volume_combo_box->currentIndex() == 0) { + Settings::values.volume.SetGlobal(true); + } else { + Settings::values.volume.SetGlobal(false); + Settings::values.volume.SetValue( + static_cast<float>(ui->volume_slider->sliderPosition()) / + ui->volume_slider->maximum()); + } + } } void ConfigureAudio::changeEvent(QEvent* event) { @@ -122,3 +163,22 @@ void ConfigureAudio::RetranslateUI() { ui->retranslateUi(this); SetVolumeIndicatorText(ui->volume_slider->sliderPosition()); } + +void ConfigureAudio::SetupPerGameUI() { + if (Settings::configuring_global) { + ui->volume_slider->setEnabled(Settings::values.volume.UsingGlobal()); + ui->toggle_audio_stretching->setEnabled( + Settings::values.enable_audio_stretching.UsingGlobal()); + + return; + } + + ui->toggle_audio_stretching->setTristate(true); + connect(ui->volume_combo_box, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), + this, [this](int index) { ui->volume_slider->setEnabled(index == 1); }); + + ui->output_sink_combo_box->setVisible(false); + ui->output_sink_label->setVisible(false); + ui->audio_device_combo_box->setVisible(false); + ui->audio_device_label->setVisible(false); +} diff --git a/src/yuzu/configuration/configure_audio.h b/src/yuzu/configuration/configure_audio.h index ea83bd72d..d84f4a682 100644 --- a/src/yuzu/configuration/configure_audio.h +++ b/src/yuzu/configuration/configure_audio.h @@ -34,5 +34,7 @@ private: void SetAudioDeviceFromDeviceID(); void SetVolumeIndicatorText(int percentage); + void SetupPerGameUI(); + std::unique_ptr<Ui::ConfigureAudio> ui; }; diff --git a/src/yuzu/configuration/configure_audio.ui b/src/yuzu/configuration/configure_audio.ui index a098b9acc..862ccb988 100644 --- a/src/yuzu/configuration/configure_audio.ui +++ b/src/yuzu/configuration/configure_audio.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>188</width> - <height>246</height> + <width>367</width> + <height>368</height> </rect> </property> <layout class="QVBoxLayout"> @@ -18,9 +18,9 @@ </property> <layout class="QVBoxLayout"> <item> - <layout class="QHBoxLayout"> + <layout class="QHBoxLayout" name="_3"> <item> - <widget class="QLabel" name="label_1"> + <widget class="QLabel" name="output_sink_label"> <property name="text"> <string>Output Engine:</string> </property> @@ -31,20 +31,20 @@ </item> </layout> </item> - <item> - <widget class="QCheckBox" name="toggle_audio_stretching"> - <property name="toolTip"> - <string>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</string> - </property> - <property name="text"> - <string>Enable audio stretching</string> - </property> - </widget> - </item> <item> - <layout class="QHBoxLayout"> + <widget class="QCheckBox" name="toggle_audio_stretching"> + <property name="toolTip"> + <string>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</string> + </property> + <property name="text"> + <string>Enable audio stretching</string> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="_2"> <item> - <widget class="QLabel" name="label_2"> + <widget class="QLabel" name="audio_device_label"> <property name="text"> <string>Audio Device:</string> </property> @@ -61,7 +61,21 @@ <number>0</number> </property> <item> - <widget class="QLabel" name="label_3"> + <widget class="QComboBox" name="volume_combo_box"> + <item> + <property name="text"> + <string>Use global volume</string> + </property> + </item> + <item> + <property name="text"> + <string>Set volume:</string> + </property> + </item> + </widget> + </item> + <item> + <widget class="QLabel" name="volume_label"> <property name="text"> <string>Volume:</string> </property> @@ -74,7 +88,7 @@ </property> <property name="sizeHint" stdset="0"> <size> - <width>40</width> + <width>30</width> <height>20</height> </size> </property> diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index df4473b46..5918e9972 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp @@ -14,6 +14,8 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry) : QDialog(parent), ui(new Ui::ConfigureDialog), registry(registry) { + Settings::configuring_global = true; + ui->setupUi(this); ui->hotkeysTab->Populate(registry); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp index 74b2ad537..1fb62d1cf 100644 --- a/src/yuzu/configuration/configure_general.cpp +++ b/src/yuzu/configuration/configure_general.cpp @@ -7,17 +7,21 @@ #include "core/core.h" #include "core/settings.h" #include "ui_configure_general.h" +#include "yuzu/configuration/configuration_shared.h" #include "yuzu/configuration/configure_general.h" #include "yuzu/uisettings.h" ConfigureGeneral::ConfigureGeneral(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureGeneral) { - ui->setupUi(this); + SetupPerGameUI(); + SetConfiguration(); - connect(ui->toggle_frame_limit, &QCheckBox::toggled, ui->frame_limit, &QSpinBox::setEnabled); + connect(ui->toggle_frame_limit, &QCheckBox::stateChanged, ui->frame_limit, [this]() { + ui->frame_limit->setEnabled(ui->toggle_frame_limit->checkState() == Qt::Checked); + }); } ConfigureGeneral::~ConfigureGeneral() = default; @@ -26,27 +30,56 @@ void ConfigureGeneral::SetConfiguration() { const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn(); ui->use_multi_core->setEnabled(runtime_lock); - ui->use_multi_core->setChecked(Settings::values.use_multi_core); + ui->use_multi_core->setChecked(Settings::values.use_multi_core.GetValue()); ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing); ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot); ui->toggle_background_pause->setChecked(UISettings::values.pause_when_in_background); ui->toggle_hide_mouse->setChecked(UISettings::values.hide_mouse); - ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit); - ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked()); - ui->frame_limit->setValue(Settings::values.frame_limit); + ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit.GetValue()); + ui->frame_limit->setValue(Settings::values.frame_limit.GetValue()); + + if (!Settings::configuring_global) { + if (Settings::values.use_multi_core.UsingGlobal()) { + ui->use_multi_core->setCheckState(Qt::PartiallyChecked); + } + if (Settings::values.use_frame_limit.UsingGlobal()) { + ui->toggle_frame_limit->setCheckState(Qt::PartiallyChecked); + } + } + + ui->frame_limit->setEnabled(ui->toggle_frame_limit->checkState() == Qt::Checked && + ui->toggle_frame_limit->isEnabled()); } void ConfigureGeneral::ApplyConfiguration() { - UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked(); - UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked(); - UISettings::values.pause_when_in_background = ui->toggle_background_pause->isChecked(); - UISettings::values.hide_mouse = ui->toggle_hide_mouse->isChecked(); - - Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked(); - Settings::values.frame_limit = ui->frame_limit->value(); - Settings::values.use_multi_core = ui->use_multi_core->isChecked(); + if (Settings::configuring_global) { + UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked(); + UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked(); + UISettings::values.pause_when_in_background = ui->toggle_background_pause->isChecked(); + UISettings::values.hide_mouse = ui->toggle_hide_mouse->isChecked(); + + // Guard if during game and set to game-specific value + if (Settings::values.use_frame_limit.UsingGlobal()) { + Settings::values.use_frame_limit.SetValue(ui->toggle_frame_limit->checkState() == + Qt::Checked); + Settings::values.frame_limit.SetValue(ui->frame_limit->value()); + Settings::values.use_multi_core.SetValue(ui->use_multi_core->isChecked()); + } + } else { + ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_multi_core, + ui->use_multi_core); + + bool global_frame_limit = ui->toggle_frame_limit->checkState() == Qt::PartiallyChecked; + Settings::values.use_frame_limit.SetGlobal(global_frame_limit); + Settings::values.frame_limit.SetGlobal(global_frame_limit); + if (!global_frame_limit) { + Settings::values.use_frame_limit.SetValue(ui->toggle_frame_limit->checkState() == + Qt::Checked); + Settings::values.frame_limit.SetValue(ui->frame_limit->value()); + } + } } void ConfigureGeneral::changeEvent(QEvent* event) { @@ -60,3 +93,20 @@ void ConfigureGeneral::changeEvent(QEvent* event) { void ConfigureGeneral::RetranslateUI() { ui->retranslateUi(this); } + +void ConfigureGeneral::SetupPerGameUI() { + if (Settings::configuring_global) { + ui->toggle_frame_limit->setEnabled(Settings::values.use_frame_limit.UsingGlobal()); + ui->frame_limit->setEnabled(Settings::values.frame_limit.UsingGlobal()); + + return; + } + + ui->toggle_check_exit->setVisible(false); + ui->toggle_user_on_boot->setVisible(false); + ui->toggle_background_pause->setVisible(false); + ui->toggle_hide_mouse->setVisible(false); + + ui->toggle_frame_limit->setTristate(true); + ui->use_multi_core->setTristate(true); +} diff --git a/src/yuzu/configuration/configure_general.h b/src/yuzu/configuration/configure_general.h index ef05ce065..9c785c22e 100644 --- a/src/yuzu/configuration/configure_general.h +++ b/src/yuzu/configuration/configure_general.h @@ -28,5 +28,7 @@ private: void SetConfiguration(); + void SetupPerGameUI(); + std::unique_ptr<Ui::ConfigureGeneral> ui; }; diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index 304625cd7..cb4706bd6 100644 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp @@ -13,6 +13,7 @@ #include "core/core.h" #include "core/settings.h" #include "ui_configure_graphics.h" +#include "yuzu/configuration/configuration_shared.h" #include "yuzu/configuration/configure_graphics.h" #ifdef HAS_VULKAN @@ -21,16 +22,18 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureGraphics) { - vulkan_device = Settings::values.vulkan_device; + vulkan_device = Settings::values.vulkan_device.GetValue(); RetrieveVulkanDevices(); ui->setupUi(this); + SetupPerGameUI(); + SetConfiguration(); - connect(ui->api, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, + connect(ui->api, qOverload<int>(&QComboBox::currentIndexChanged), this, [this] { UpdateDeviceComboBox(); }); - connect(ui->device, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, + connect(ui->device, qOverload<int>(&QComboBox::activated), this, [this](int device) { UpdateDeviceSelection(device); }); connect(ui->bg_button, &QPushButton::clicked, this, [this] { @@ -40,6 +43,9 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent) } UpdateBackgroundColorButton(new_bg_color); }); + + ui->bg_label->setVisible(Settings::configuring_global); + ui->bg_combobox->setVisible(!Settings::configuring_global); } void ConfigureGraphics::UpdateDeviceSelection(int device) { @@ -57,27 +63,95 @@ void ConfigureGraphics::SetConfiguration() { const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn(); ui->api->setEnabled(runtime_lock); - ui->api->setCurrentIndex(static_cast<int>(Settings::values.renderer_backend)); - ui->aspect_ratio_combobox->setCurrentIndex(Settings::values.aspect_ratio); - ui->use_disk_shader_cache->setEnabled(runtime_lock); - ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache); ui->use_asynchronous_gpu_emulation->setEnabled(runtime_lock); - ui->use_asynchronous_gpu_emulation->setChecked(Settings::values.use_asynchronous_gpu_emulation); - UpdateBackgroundColorButton(QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green, - Settings::values.bg_blue)); + ui->use_disk_shader_cache->setEnabled(runtime_lock); + + if (Settings::configuring_global) { + ui->api->setCurrentIndex(static_cast<int>(Settings::values.renderer_backend.GetValue())); + ui->aspect_ratio_combobox->setCurrentIndex(Settings::values.aspect_ratio.GetValue()); + ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache.GetValue()); + ui->use_asynchronous_gpu_emulation->setChecked( + Settings::values.use_asynchronous_gpu_emulation.GetValue()); + } else { + ConfigurationShared::SetPerGameSetting(ui->use_disk_shader_cache, + &Settings::values.use_disk_shader_cache); + ConfigurationShared::SetPerGameSetting(ui->use_asynchronous_gpu_emulation, + &Settings::values.use_asynchronous_gpu_emulation); + + ConfigurationShared::SetPerGameSetting(ui->api, &Settings::values.renderer_backend); + ConfigurationShared::SetPerGameSetting(ui->aspect_ratio_combobox, + &Settings::values.aspect_ratio); + + ui->bg_combobox->setCurrentIndex(Settings::values.bg_red.UsingGlobal() ? 0 : 1); + ui->bg_button->setEnabled(!Settings::values.bg_red.UsingGlobal()); + } + + UpdateBackgroundColorButton(QColor::fromRgbF(Settings::values.bg_red.GetValue(), + Settings::values.bg_green.GetValue(), + Settings::values.bg_blue.GetValue())); UpdateDeviceComboBox(); } void ConfigureGraphics::ApplyConfiguration() { - Settings::values.renderer_backend = GetCurrentGraphicsBackend(); - Settings::values.vulkan_device = vulkan_device; - Settings::values.aspect_ratio = ui->aspect_ratio_combobox->currentIndex(); - Settings::values.use_disk_shader_cache = ui->use_disk_shader_cache->isChecked(); - Settings::values.use_asynchronous_gpu_emulation = - ui->use_asynchronous_gpu_emulation->isChecked(); - Settings::values.bg_red = static_cast<float>(bg_color.redF()); - Settings::values.bg_green = static_cast<float>(bg_color.greenF()); - Settings::values.bg_blue = static_cast<float>(bg_color.blueF()); + if (Settings::configuring_global) { + // Guard if during game and set to game-specific value + if (Settings::values.renderer_backend.UsingGlobal()) { + Settings::values.renderer_backend.SetValue(GetCurrentGraphicsBackend()); + } + if (Settings::values.vulkan_device.UsingGlobal()) { + Settings::values.vulkan_device.SetValue(vulkan_device); + } + if (Settings::values.aspect_ratio.UsingGlobal()) { + Settings::values.aspect_ratio.SetValue(ui->aspect_ratio_combobox->currentIndex()); + } + if (Settings::values.use_disk_shader_cache.UsingGlobal()) { + Settings::values.use_disk_shader_cache.SetValue(ui->use_disk_shader_cache->isChecked()); + } + if (Settings::values.use_asynchronous_gpu_emulation.UsingGlobal()) { + Settings::values.use_asynchronous_gpu_emulation.SetValue( + ui->use_asynchronous_gpu_emulation->isChecked()); + } + if (Settings::values.bg_red.UsingGlobal()) { + Settings::values.bg_red.SetValue(static_cast<float>(bg_color.redF())); + Settings::values.bg_green.SetValue(static_cast<float>(bg_color.greenF())); + Settings::values.bg_blue.SetValue(static_cast<float>(bg_color.blueF())); + } + } else { + if (ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { + Settings::values.renderer_backend.SetGlobal(true); + Settings::values.vulkan_device.SetGlobal(true); + } else { + Settings::values.renderer_backend.SetGlobal(false); + Settings::values.renderer_backend.SetValue(GetCurrentGraphicsBackend()); + if (GetCurrentGraphicsBackend() == Settings::RendererBackend::Vulkan) { + Settings::values.vulkan_device.SetGlobal(false); + Settings::values.vulkan_device.SetValue(vulkan_device); + } else { + Settings::values.vulkan_device.SetGlobal(true); + } + } + + ConfigurationShared::ApplyPerGameSetting(&Settings::values.aspect_ratio, + ui->aspect_ratio_combobox); + + ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_disk_shader_cache, + ui->use_disk_shader_cache); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_asynchronous_gpu_emulation, + ui->use_asynchronous_gpu_emulation); + + if (ui->bg_combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { + Settings::values.bg_red.SetGlobal(true); + Settings::values.bg_green.SetGlobal(true); + Settings::values.bg_blue.SetGlobal(true); + } else { + Settings::values.bg_red.SetGlobal(false); + Settings::values.bg_green.SetGlobal(false); + Settings::values.bg_blue.SetGlobal(false); + Settings::values.bg_red.SetValue(static_cast<float>(bg_color.redF())); + Settings::values.bg_green.SetValue(static_cast<float>(bg_color.greenF())); + Settings::values.bg_blue.SetValue(static_cast<float>(bg_color.blueF())); + } + } } void ConfigureGraphics::changeEvent(QEvent* event) { @@ -106,19 +180,27 @@ void ConfigureGraphics::UpdateDeviceComboBox() { ui->device->clear(); bool enabled = false; + + if (!Settings::configuring_global && + ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { + vulkan_device = Settings::values.vulkan_device.GetValue(); + } switch (GetCurrentGraphicsBackend()) { case Settings::RendererBackend::OpenGL: ui->device->addItem(tr("OpenGL Graphics Device")); enabled = false; break; case Settings::RendererBackend::Vulkan: - for (const auto device : vulkan_devices) { + for (const auto& device : vulkan_devices) { ui->device->addItem(device); } ui->device->setCurrentIndex(vulkan_device); enabled = !vulkan_devices.empty(); break; } + // If in per-game config and use global is selected, don't enable. + enabled &= !(!Settings::configuring_global && + ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX); ui->device->setEnabled(enabled && !Core::System::GetInstance().IsPoweredOn()); } @@ -132,5 +214,37 @@ void ConfigureGraphics::RetrieveVulkanDevices() { } Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const { - return static_cast<Settings::RendererBackend>(ui->api->currentIndex()); + if (Settings::configuring_global) { + return static_cast<Settings::RendererBackend>(ui->api->currentIndex()); + } + + if (ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { + Settings::values.renderer_backend.SetGlobal(true); + return Settings::values.renderer_backend.GetValue(); + } + Settings::values.renderer_backend.SetGlobal(false); + return static_cast<Settings::RendererBackend>(ui->api->currentIndex() - + ConfigurationShared::USE_GLOBAL_OFFSET); +} + +void ConfigureGraphics::SetupPerGameUI() { + if (Settings::configuring_global) { + ui->api->setEnabled(Settings::values.renderer_backend.UsingGlobal()); + ui->device->setEnabled(Settings::values.renderer_backend.UsingGlobal()); + ui->aspect_ratio_combobox->setEnabled(Settings::values.aspect_ratio.UsingGlobal()); + ui->use_asynchronous_gpu_emulation->setEnabled( + Settings::values.use_asynchronous_gpu_emulation.UsingGlobal()); + ui->use_disk_shader_cache->setEnabled(Settings::values.use_disk_shader_cache.UsingGlobal()); + ui->bg_button->setEnabled(Settings::values.bg_red.UsingGlobal()); + + return; + } + + connect(ui->bg_combobox, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, + [this](int index) { ui->bg_button->setEnabled(index == 1); }); + + ui->use_disk_shader_cache->setTristate(true); + ui->use_asynchronous_gpu_emulation->setTristate(true); + ConfigurationShared::InsertGlobalItem(ui->aspect_ratio_combobox); + ConfigurationShared::InsertGlobalItem(ui->api); } diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h index 7e0596d9c..24f01c739 100644 --- a/src/yuzu/configuration/configure_graphics.h +++ b/src/yuzu/configuration/configure_graphics.h @@ -35,6 +35,8 @@ private: void RetrieveVulkanDevices(); + void SetupPerGameUI(); + Settings::RendererBackend GetCurrentGraphicsBackend() const; std::unique_ptr<Ui::ConfigureGraphics> ui; diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui index 6e75447a5..62418fc14 100644 --- a/src/yuzu/configuration/configure_graphics.ui +++ b/src/yuzu/configuration/configure_graphics.ui @@ -122,6 +122,29 @@ <item> <layout class="QHBoxLayout" name="horizontalLayout_3"> <item> + <widget class="QComboBox" name="bg_combobox"> + <property name="currentText"> + <string>Use global background color</string> + </property> + <property name="currentIndex"> + <number>0</number> + </property> + <property name="maxVisibleItems"> + <number>10</number> + </property> + <item> + <property name="text"> + <string>Use global background color</string> + </property> + </item> + <item> + <property name="text"> + <string>Set background color:</string> + </property> + </item> + </widget> + </item> + <item> <widget class="QLabel" name="bg_label"> <property name="text"> <string>Background Color:</string> @@ -129,6 +152,19 @@ </widget> </item> <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> <widget class="QPushButton" name="bg_button"> <property name="maximumSize"> <size> diff --git a/src/yuzu/configuration/configure_graphics_advanced.cpp b/src/yuzu/configuration/configure_graphics_advanced.cpp index be5006ad3..7c0fa7ec5 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.cpp +++ b/src/yuzu/configuration/configure_graphics_advanced.cpp @@ -5,6 +5,7 @@ #include "core/core.h" #include "core/settings.h" #include "ui_configure_graphics_advanced.h" +#include "yuzu/configuration/configuration_shared.h" #include "yuzu/configuration/configure_graphics_advanced.h" ConfigureGraphicsAdvanced::ConfigureGraphicsAdvanced(QWidget* parent) @@ -12,6 +13,8 @@ ConfigureGraphicsAdvanced::ConfigureGraphicsAdvanced(QWidget* parent) ui->setupUi(this); + SetupPerGameUI(); + SetConfiguration(); } @@ -19,26 +22,81 @@ ConfigureGraphicsAdvanced::~ConfigureGraphicsAdvanced() = default; void ConfigureGraphicsAdvanced::SetConfiguration() { const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn(); - ui->gpu_accuracy->setCurrentIndex(static_cast<int>(Settings::values.gpu_accuracy)); ui->use_vsync->setEnabled(runtime_lock); - ui->use_vsync->setChecked(Settings::values.use_vsync); ui->use_assembly_shaders->setEnabled(runtime_lock); - ui->use_assembly_shaders->setChecked(Settings::values.use_assembly_shaders); - ui->use_fast_gpu_time->setChecked(Settings::values.use_fast_gpu_time); ui->force_30fps_mode->setEnabled(runtime_lock); - ui->force_30fps_mode->setChecked(Settings::values.force_30fps_mode); ui->anisotropic_filtering_combobox->setEnabled(runtime_lock); - ui->anisotropic_filtering_combobox->setCurrentIndex(Settings::values.max_anisotropy); + + if (Settings::configuring_global) { + ui->gpu_accuracy->setCurrentIndex( + static_cast<int>(Settings::values.gpu_accuracy.GetValue())); + ui->use_vsync->setChecked(Settings::values.use_vsync.GetValue()); + ui->use_assembly_shaders->setChecked(Settings::values.use_assembly_shaders.GetValue()); + ui->use_fast_gpu_time->setChecked(Settings::values.use_fast_gpu_time.GetValue()); + ui->force_30fps_mode->setChecked(Settings::values.force_30fps_mode.GetValue()); + ui->anisotropic_filtering_combobox->setCurrentIndex( + Settings::values.max_anisotropy.GetValue()); + } else { + ConfigurationShared::SetPerGameSetting(ui->gpu_accuracy, &Settings::values.gpu_accuracy); + ConfigurationShared::SetPerGameSetting(ui->use_vsync, &Settings::values.use_vsync); + ConfigurationShared::SetPerGameSetting(ui->use_assembly_shaders, + &Settings::values.use_assembly_shaders); + ConfigurationShared::SetPerGameSetting(ui->use_fast_gpu_time, + &Settings::values.use_fast_gpu_time); + ConfigurationShared::SetPerGameSetting(ui->force_30fps_mode, + &Settings::values.force_30fps_mode); + ConfigurationShared::SetPerGameSetting(ui->anisotropic_filtering_combobox, + &Settings::values.max_anisotropy); + } } void ConfigureGraphicsAdvanced::ApplyConfiguration() { - auto gpu_accuracy = static_cast<Settings::GPUAccuracy>(ui->gpu_accuracy->currentIndex()); - Settings::values.gpu_accuracy = gpu_accuracy; - Settings::values.use_vsync = ui->use_vsync->isChecked(); - Settings::values.use_assembly_shaders = ui->use_assembly_shaders->isChecked(); - Settings::values.use_fast_gpu_time = ui->use_fast_gpu_time->isChecked(); - Settings::values.force_30fps_mode = ui->force_30fps_mode->isChecked(); - Settings::values.max_anisotropy = ui->anisotropic_filtering_combobox->currentIndex(); + // Subtract 2 if configuring per-game (separator and "use global configuration" take 2 slots) + const auto gpu_accuracy = static_cast<Settings::GPUAccuracy>( + ui->gpu_accuracy->currentIndex() - + ((Settings::configuring_global) ? 0 : ConfigurationShared::USE_GLOBAL_OFFSET)); + + if (Settings::configuring_global) { + // Must guard in case of a during-game configuration when set to be game-specific. + if (Settings::values.gpu_accuracy.UsingGlobal()) { + Settings::values.gpu_accuracy.SetValue(gpu_accuracy); + } + if (Settings::values.use_vsync.UsingGlobal()) { + Settings::values.use_vsync.SetValue(ui->use_vsync->isChecked()); + } + if (Settings::values.use_assembly_shaders.UsingGlobal()) { + Settings::values.use_assembly_shaders.SetValue(ui->use_assembly_shaders->isChecked()); + } + if (Settings::values.use_fast_gpu_time.UsingGlobal()) { + Settings::values.use_fast_gpu_time.SetValue(ui->use_fast_gpu_time->isChecked()); + } + if (Settings::values.force_30fps_mode.UsingGlobal()) { + Settings::values.force_30fps_mode.SetValue(ui->force_30fps_mode->isChecked()); + } + if (Settings::values.max_anisotropy.UsingGlobal()) { + Settings::values.max_anisotropy.SetValue( + ui->anisotropic_filtering_combobox->currentIndex()); + } + } else { + ConfigurationShared::ApplyPerGameSetting(&Settings::values.max_anisotropy, + ui->anisotropic_filtering_combobox); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vsync, ui->use_vsync); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_assembly_shaders, + ui->use_assembly_shaders); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_fast_gpu_time, + ui->use_fast_gpu_time); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.force_30fps_mode, + ui->force_30fps_mode); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.max_anisotropy, + ui->anisotropic_filtering_combobox); + + if (ui->gpu_accuracy->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { + Settings::values.gpu_accuracy.SetGlobal(true); + } else { + Settings::values.gpu_accuracy.SetGlobal(false); + Settings::values.gpu_accuracy.SetValue(gpu_accuracy); + } + } } void ConfigureGraphicsAdvanced::changeEvent(QEvent* event) { @@ -52,3 +110,25 @@ void ConfigureGraphicsAdvanced::changeEvent(QEvent* event) { void ConfigureGraphicsAdvanced::RetranslateUI() { ui->retranslateUi(this); } + +void ConfigureGraphicsAdvanced::SetupPerGameUI() { + // Disable if not global (only happens during game) + if (Settings::configuring_global) { + ui->gpu_accuracy->setEnabled(Settings::values.gpu_accuracy.UsingGlobal()); + ui->use_vsync->setEnabled(Settings::values.use_vsync.UsingGlobal()); + ui->use_assembly_shaders->setEnabled(Settings::values.use_assembly_shaders.UsingGlobal()); + ui->use_fast_gpu_time->setEnabled(Settings::values.use_fast_gpu_time.UsingGlobal()); + ui->force_30fps_mode->setEnabled(Settings::values.force_30fps_mode.UsingGlobal()); + ui->anisotropic_filtering_combobox->setEnabled( + Settings::values.max_anisotropy.UsingGlobal()); + + return; + } + + ConfigurationShared::InsertGlobalItem(ui->gpu_accuracy); + ui->use_vsync->setTristate(true); + ui->use_assembly_shaders->setTristate(true); + ui->use_fast_gpu_time->setTristate(true); + ui->force_30fps_mode->setTristate(true); + ConfigurationShared::InsertGlobalItem(ui->anisotropic_filtering_combobox); +} diff --git a/src/yuzu/configuration/configure_graphics_advanced.h b/src/yuzu/configuration/configure_graphics_advanced.h index bbc9d4355..c043588ff 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.h +++ b/src/yuzu/configuration/configure_graphics_advanced.h @@ -26,5 +26,7 @@ private: void SetConfiguration(); + void SetupPerGameUI(); + std::unique_ptr<Ui::ConfigureGraphicsAdvanced> ui; }; diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index a05fa64ba..00433926d 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -70,6 +70,20 @@ static QString ButtonToText(const Common::ParamPackage& param) { return GetKeyName(param.Get("code", 0)); } + if (param.Get("engine", "") == "gcpad") { + if (param.Has("axis")) { + const QString axis_str = QString::fromStdString(param.Get("axis", "")); + const QString direction_str = QString::fromStdString(param.Get("direction", "")); + + return QObject::tr("GC Axis %1%2").arg(axis_str, direction_str); + } + if (param.Has("button")) { + const QString button_str = QString::number(int(std::log2(param.Get("button", 0)))); + return QObject::tr("GC Button %1").arg(button_str); + } + return GetKeyName(param.Get("code", 0)); + } + if (param.Get("engine", "") == "sdl") { if (param.Has("hat")) { const QString hat_str = QString::fromStdString(param.Get("hat", "")); @@ -126,6 +140,25 @@ static QString AnalogToText(const Common::ParamPackage& param, const std::string return {}; } + if (param.Get("engine", "") == "gcpad") { + if (dir == "modifier") { + return QObject::tr("[unused]"); + } + + if (dir == "left" || dir == "right") { + const QString axis_x_str = QString::fromStdString(param.Get("axis_x", "")); + + return QObject::tr("GC Axis %1").arg(axis_x_str); + } + + if (dir == "up" || dir == "down") { + const QString axis_y_str = QString::fromStdString(param.Get("axis_y", "")); + + return QObject::tr("GC Axis %1").arg(axis_y_str); + } + + return {}; + } return QObject::tr("[unknown]"); } @@ -332,7 +365,8 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i connect(analog_map_deadzone_and_modifier_slider[analog_id], &QSlider::valueChanged, [=] { const float slider_value = analog_map_deadzone_and_modifier_slider[analog_id]->value(); - if (analogs_param[analog_id].Get("engine", "") == "sdl") { + if (analogs_param[analog_id].Get("engine", "") == "sdl" || + analogs_param[analog_id].Get("engine", "") == "gcpad") { analog_map_deadzone_and_modifier_slider_label[analog_id]->setText( tr("Deadzone: %1%").arg(slider_value)); analogs_param[analog_id].Set("deadzone", slider_value / 100.0f); @@ -352,6 +386,20 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i connect(poll_timer.get(), &QTimer::timeout, [this] { Common::ParamPackage params; + if (InputCommon::GetGCButtons()->IsPolling()) { + params = InputCommon::GetGCButtons()->GetNextInput(); + if (params.Has("engine")) { + SetPollingResult(params, false); + return; + } + } + if (InputCommon::GetGCAnalogs()->IsPolling()) { + params = InputCommon::GetGCAnalogs()->GetNextInput(); + if (params.Has("engine")) { + SetPollingResult(params, false); + return; + } + } for (auto& poller : device_pollers) { params = poller->GetNextInput(); if (params.Has("engine")) { @@ -534,7 +582,7 @@ void ConfigureInputPlayer::UpdateButtonLabels() { analog_map_deadzone_and_modifier_slider_label[analog_id]; if (param.Has("engine")) { - if (param.Get("engine", "") == "sdl") { + if (param.Get("engine", "") == "sdl" || param.Get("engine", "") == "gcpad") { if (!param.Has("deadzone")) { param.Set("deadzone", 0.1f); } @@ -583,6 +631,11 @@ void ConfigureInputPlayer::HandleClick( grabKeyboard(); grabMouse(); + if (type == InputCommon::Polling::DeviceType::Button) { + InputCommon::GetGCButtons()->BeginConfiguration(); + } else { + InputCommon::GetGCAnalogs()->BeginConfiguration(); + } timeout_timer->start(5000); // Cancel after 5 seconds poll_timer->start(200); // Check for new inputs every 200ms } @@ -596,6 +649,9 @@ void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params, poller->Stop(); } + InputCommon::GetGCButtons()->EndConfiguration(); + InputCommon::GetGCAnalogs()->EndConfiguration(); + if (!abort) { (*input_setter)(params); } diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp new file mode 100644 index 000000000..1e49f0787 --- /dev/null +++ b/src/yuzu/configuration/configure_per_game.cpp @@ -0,0 +1,140 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <memory> +#include <utility> + +#include <QCheckBox> +#include <QHeaderView> +#include <QMenu> +#include <QStandardItemModel> +#include <QString> +#include <QTimer> +#include <QTreeView> + +#include "common/common_paths.h" +#include "common/file_util.h" +#include "core/file_sys/control_metadata.h" +#include "core/file_sys/patch_manager.h" +#include "core/file_sys/xts_archive.h" +#include "core/loader/loader.h" +#include "ui_configure_per_game.h" +#include "yuzu/configuration/config.h" +#include "yuzu/configuration/configure_input.h" +#include "yuzu/configuration/configure_per_game.h" +#include "yuzu/uisettings.h" +#include "yuzu/util/util.h" + +ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id) + : QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGame>()), title_id(title_id) { + game_config = std::make_unique<Config>(fmt::format("{:016X}.ini", title_id), false); + + Settings::configuring_global = false; + + ui->setupUi(this); + setFocusPolicy(Qt::ClickFocus); + setWindowTitle(tr("Properties")); + + ui->addonsTab->SetTitleId(title_id); + + scene = new QGraphicsScene; + ui->icon_view->setScene(scene); + + LoadConfiguration(); +} + +ConfigurePerGame::~ConfigurePerGame() = default; + +void ConfigurePerGame::ApplyConfiguration() { + ui->addonsTab->ApplyConfiguration(); + ui->generalTab->ApplyConfiguration(); + ui->systemTab->ApplyConfiguration(); + ui->graphicsTab->ApplyConfiguration(); + ui->graphicsAdvancedTab->ApplyConfiguration(); + ui->audioTab->ApplyConfiguration(); + + Settings::Apply(); + Settings::LogSettings(); + + game_config->Save(); +} + +void ConfigurePerGame::changeEvent(QEvent* event) { + if (event->type() == QEvent::LanguageChange) { + RetranslateUI(); + } + + QDialog::changeEvent(event); +} + +void ConfigurePerGame::RetranslateUI() { + ui->retranslateUi(this); +} + +void ConfigurePerGame::LoadFromFile(FileSys::VirtualFile file) { + this->file = std::move(file); + LoadConfiguration(); +} + +void ConfigurePerGame::LoadConfiguration() { + if (file == nullptr) { + return; + } + + ui->addonsTab->LoadFromFile(file); + + ui->display_title_id->setText( + QStringLiteral("%1").arg(title_id, 16, 16, QLatin1Char{'0'}).toUpper()); + + FileSys::PatchManager pm{title_id}; + const auto control = pm.GetControlMetadata(); + const auto loader = Loader::GetLoader(file); + + if (control.first != nullptr) { + ui->display_version->setText(QString::fromStdString(control.first->GetVersionString())); + ui->display_name->setText(QString::fromStdString(control.first->GetApplicationName())); + ui->display_developer->setText(QString::fromStdString(control.first->GetDeveloperName())); + } else { + std::string title; + if (loader->ReadTitle(title) == Loader::ResultStatus::Success) + ui->display_name->setText(QString::fromStdString(title)); + + FileSys::NACP nacp; + if (loader->ReadControlData(nacp) == Loader::ResultStatus::Success) + ui->display_developer->setText(QString::fromStdString(nacp.GetDeveloperName())); + + ui->display_version->setText(QStringLiteral("1.0.0")); + } + + if (control.second != nullptr) { + scene->clear(); + + QPixmap map; + const auto bytes = control.second->ReadAllBytes(); + map.loadFromData(bytes.data(), static_cast<u32>(bytes.size())); + + scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(), + Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + } else { + std::vector<u8> bytes; + if (loader->ReadIcon(bytes) == Loader::ResultStatus::Success) { + scene->clear(); + + QPixmap map; + map.loadFromData(bytes.data(), static_cast<u32>(bytes.size())); + + scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(), + Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + } + } + + ui->display_filename->setText(QString::fromStdString(file->GetName())); + + ui->display_format->setText( + QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))); + + const auto valueText = ReadableByteSize(file->GetSize()); + ui->display_size->setText(valueText); +} diff --git a/src/yuzu/configuration/configure_per_game.h b/src/yuzu/configuration/configure_per_game.h new file mode 100644 index 000000000..5f9a08cef --- /dev/null +++ b/src/yuzu/configuration/configure_per_game.h @@ -0,0 +1,51 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <vector> + +#include <QDialog> +#include <QList> + +#include "core/file_sys/vfs_types.h" +#include "yuzu/configuration/config.h" + +class QGraphicsScene; +class QStandardItem; +class QStandardItemModel; +class QTreeView; +class QVBoxLayout; + +namespace Ui { +class ConfigurePerGame; +} + +class ConfigurePerGame : public QDialog { + Q_OBJECT + +public: + explicit ConfigurePerGame(QWidget* parent, u64 title_id); + ~ConfigurePerGame() override; + + /// Save all button configurations to settings file + void ApplyConfiguration(); + + void LoadFromFile(FileSys::VirtualFile file); + +private: + void changeEvent(QEvent* event) override; + void RetranslateUI(); + + void LoadConfiguration(); + + std::unique_ptr<Ui::ConfigurePerGame> ui; + FileSys::VirtualFile file; + u64 title_id; + + QGraphicsScene* scene; + + std::unique_ptr<Config> game_config; +}; diff --git a/src/yuzu/configuration/configure_per_game.ui b/src/yuzu/configuration/configure_per_game.ui new file mode 100644 index 000000000..d2057c4ab --- /dev/null +++ b/src/yuzu/configuration/configure_per_game.ui @@ -0,0 +1,350 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigurePerGame</class> + <widget class="QDialog" name="ConfigurePerGame"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>800</width> + <height>600</height> + </rect> + </property> + <property name="windowTitle"> + <string>Dialog</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Info</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item alignment="Qt::AlignHCenter"> + <widget class="QGraphicsView" name="icon_view"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>256</width> + <height>256</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>256</width> + <height>256</height> + </size> + </property> + <property name="verticalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + <property name="horizontalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + <property name="interactive"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="6" column="1"> + <widget class="QLineEdit" name="display_size"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QLineEdit" name="display_version"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Name</string> + </property> + </widget> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Title ID</string> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QLineEdit" name="display_title_id"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="7" column="1"> + <widget class="QLineEdit" name="display_filename"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="5" column="1"> + <widget class="QLineEdit" name="display_format"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="7" column="0"> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string>Filename</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="display_name"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLineEdit" name="display_developer"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="5" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Format</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Version</string> + </property> + </widget> + </item> + <item row="6" column="0"> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string>Size</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Developer</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item> + <layout class="QVBoxLayout" name="VerticalLayout"> + <item> + <layout class="QVBoxLayout" name="verticalLayout_2"/> + </item> + <item> + <widget class="QTabWidget" name="tabWidget"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="currentIndex"> + <number>0</number> + </property> + <property name="usesScrollButtons"> + <bool>true</bool> + </property> + <property name="documentMode"> + <bool>false</bool> + </property> + <property name="tabsClosable"> + <bool>false</bool> + </property> + <widget class="ConfigurePerGameAddons" name="addonsTab"> + <attribute name="title"> + <string>Add-Ons</string> + </attribute> + </widget> + <widget class="ConfigureGeneral" name="generalTab"> + <attribute name="title"> + <string>General</string> + </attribute> + </widget> + <widget class="ConfigureSystem" name="systemTab"> + <attribute name="title"> + <string>System</string> + </attribute> + </widget> + <widget class="ConfigureGraphics" name="graphicsTab"> + <attribute name="title"> + <string>Graphics</string> + </attribute> + </widget> + <widget class="ConfigureGraphicsAdvanced" name="graphicsAdvancedTab"> + <attribute name="title"> + <string>Adv. Graphics</string> + </attribute> + </widget> + <widget class="ConfigureAudio" name="audioTab"> + <attribute name="title"> + <string>Audio</string> + </attribute> + </widget> + </widget> + </item> + </layout> + </item> + </layout> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>ConfigureGeneral</class> + <extends>QWidget</extends> + <header>configuration/configure_general.h</header> + <container>1</container> + </customwidget> + <customwidget> + <class>ConfigureSystem</class> + <extends>QWidget</extends> + <header>configuration/configure_system.h</header> + <container>1</container> + </customwidget> + <customwidget> + <class>ConfigureAudio</class> + <extends>QWidget</extends> + <header>configuration/configure_audio.h</header> + <container>1</container> + </customwidget> + <customwidget> + <class>ConfigureGraphics</class> + <extends>QWidget</extends> + <header>configuration/configure_graphics.h</header> + <container>1</container> + </customwidget> + <customwidget> + <class>ConfigureGraphicsAdvanced</class> + <extends>QWidget</extends> + <header>configuration/configure_graphics_advanced.h</header> + <container>1</container> + </customwidget> + <customwidget> + <class>ConfigurePerGameAddons</class> + <extends>QWidget</extends> + <header>configuration/configure_per_game_addons.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>ConfigurePerGame</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>ConfigurePerGame</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/yuzu/configuration/configure_per_general.cpp b/src/yuzu/configuration/configure_per_game_addons.cpp index d7f259f12..478d5d3a1 100644 --- a/src/yuzu/configuration/configure_per_general.cpp +++ b/src/yuzu/configuration/configure_per_game_addons.cpp @@ -15,23 +15,20 @@ #include "common/common_paths.h" #include "common/file_util.h" -#include "core/file_sys/control_metadata.h" +#include "core/core.h" #include "core/file_sys/patch_manager.h" #include "core/file_sys/xts_archive.h" #include "core/loader/loader.h" -#include "ui_configure_per_general.h" +#include "ui_configure_per_game_addons.h" #include "yuzu/configuration/config.h" #include "yuzu/configuration/configure_input.h" -#include "yuzu/configuration/configure_per_general.h" +#include "yuzu/configuration/configure_per_game_addons.h" #include "yuzu/uisettings.h" #include "yuzu/util/util.h" -ConfigurePerGameGeneral::ConfigurePerGameGeneral(QWidget* parent, u64 title_id) - : QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGameGeneral>()), title_id(title_id) { - +ConfigurePerGameAddons::ConfigurePerGameAddons(QWidget* parent) + : QWidget(parent), ui(new Ui::ConfigurePerGameAddons) { ui->setupUi(this); - setFocusPolicy(Qt::ClickFocus); - setWindowTitle(tr("Properties")); layout = new QVBoxLayout; tree_view = new QTreeView; @@ -52,7 +49,7 @@ ConfigurePerGameGeneral::ConfigurePerGameGeneral(QWidget* parent, u64 title_id) item_model->setHeaderData(1, Qt::Horizontal, tr("Version")); // We must register all custom types with the Qt Automoc system so that we are able to use it - // with signals/slots. In this case, QList falls under the umbrells of custom types. + // with signals/slots. In this case, QList falls under the umbrella of custom types. qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); layout->setContentsMargins(0, 0, 0, 0); @@ -61,18 +58,15 @@ ConfigurePerGameGeneral::ConfigurePerGameGeneral(QWidget* parent, u64 title_id) ui->scrollArea->setLayout(layout); - scene = new QGraphicsScene; - ui->icon_view->setScene(scene); + ui->scrollArea->setEnabled(!Core::System::GetInstance().IsPoweredOn()); connect(item_model, &QStandardItemModel::itemChanged, [] { UISettings::values.is_game_list_reload_pending.exchange(true); }); - - LoadConfiguration(); } -ConfigurePerGameGeneral::~ConfigurePerGameGeneral() = default; +ConfigurePerGameAddons::~ConfigurePerGameAddons() = default; -void ConfigurePerGameGeneral::ApplyConfiguration() { +void ConfigurePerGameAddons::ApplyConfiguration() { std::vector<std::string> disabled_addons; for (const auto& item : list_items) { @@ -92,72 +86,35 @@ void ConfigurePerGameGeneral::ApplyConfiguration() { Settings::values.disabled_addons[title_id] = disabled_addons; } -void ConfigurePerGameGeneral::changeEvent(QEvent* event) { +void ConfigurePerGameAddons::LoadFromFile(FileSys::VirtualFile file) { + this->file = std::move(file); + LoadConfiguration(); +} + +void ConfigurePerGameAddons::SetTitleId(u64 id) { + this->title_id = id; +} + +void ConfigurePerGameAddons::changeEvent(QEvent* event) { if (event->type() == QEvent::LanguageChange) { RetranslateUI(); } - QDialog::changeEvent(event); + QWidget::changeEvent(event); } -void ConfigurePerGameGeneral::RetranslateUI() { +void ConfigurePerGameAddons::RetranslateUI() { ui->retranslateUi(this); } -void ConfigurePerGameGeneral::LoadFromFile(FileSys::VirtualFile file) { - this->file = std::move(file); - LoadConfiguration(); -} - -void ConfigurePerGameGeneral::LoadConfiguration() { +void ConfigurePerGameAddons::LoadConfiguration() { if (file == nullptr) { return; } - ui->display_title_id->setText(QString::fromStdString(fmt::format("{:016X}", title_id))); - FileSys::PatchManager pm{title_id}; - const auto control = pm.GetControlMetadata(); const auto loader = Loader::GetLoader(file); - if (control.first != nullptr) { - ui->display_version->setText(QString::fromStdString(control.first->GetVersionString())); - ui->display_name->setText(QString::fromStdString(control.first->GetApplicationName())); - ui->display_developer->setText(QString::fromStdString(control.first->GetDeveloperName())); - } else { - std::string title; - if (loader->ReadTitle(title) == Loader::ResultStatus::Success) - ui->display_name->setText(QString::fromStdString(title)); - - FileSys::NACP nacp; - if (loader->ReadControlData(nacp) == Loader::ResultStatus::Success) - ui->display_developer->setText(QString::fromStdString(nacp.GetDeveloperName())); - - ui->display_version->setText(QStringLiteral("1.0.0")); - } - - if (control.second != nullptr) { - scene->clear(); - - QPixmap map; - const auto bytes = control.second->ReadAllBytes(); - map.loadFromData(bytes.data(), static_cast<u32>(bytes.size())); - - scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(), - Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); - } else { - std::vector<u8> bytes; - if (loader->ReadIcon(bytes) == Loader::ResultStatus::Success) { - scene->clear(); - - QPixmap map; - map.loadFromData(bytes.data(), static_cast<u32>(bytes.size())); - - scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(), - Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); - } - } - FileSys::VirtualFile update_raw; loader->ReadUpdateRaw(update_raw); @@ -182,12 +139,4 @@ void ConfigurePerGameGeneral::LoadConfiguration() { } tree_view->setColumnWidth(0, 5 * tree_view->width() / 16); - - ui->display_filename->setText(QString::fromStdString(file->GetName())); - - ui->display_format->setText( - QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))); - - const auto valueText = ReadableByteSize(file->GetSize()); - ui->display_size->setText(valueText); } diff --git a/src/yuzu/configuration/configure_per_general.h b/src/yuzu/configuration/configure_per_game_addons.h index a3b2cdeff..a00ec3539 100644 --- a/src/yuzu/configuration/configure_per_general.h +++ b/src/yuzu/configuration/configure_per_game_addons.h @@ -1,4 +1,4 @@ -// Copyright 2016 Citra Emulator Project +// Copyright 2016 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -7,7 +7,6 @@ #include <memory> #include <vector> -#include <QDialog> #include <QList> #include "core/file_sys/vfs_types.h" @@ -19,35 +18,36 @@ class QTreeView; class QVBoxLayout; namespace Ui { -class ConfigurePerGameGeneral; +class ConfigurePerGameAddons; } -class ConfigurePerGameGeneral : public QDialog { +class ConfigurePerGameAddons : public QWidget { Q_OBJECT public: - explicit ConfigurePerGameGeneral(QWidget* parent, u64 title_id); - ~ConfigurePerGameGeneral() override; + explicit ConfigurePerGameAddons(QWidget* parent = nullptr); + ~ConfigurePerGameAddons() override; /// Save all button configurations to settings file void ApplyConfiguration(); void LoadFromFile(FileSys::VirtualFile file); + void SetTitleId(u64 id); + private: void changeEvent(QEvent* event) override; void RetranslateUI(); void LoadConfiguration(); - std::unique_ptr<Ui::ConfigurePerGameGeneral> ui; + std::unique_ptr<Ui::ConfigurePerGameAddons> ui; FileSys::VirtualFile file; u64 title_id; QVBoxLayout* layout; QTreeView* tree_view; QStandardItemModel* item_model; - QGraphicsScene* scene; std::vector<QList<QStandardItem*>> list_items; }; diff --git a/src/yuzu/configuration/configure_per_game_addons.ui b/src/yuzu/configuration/configure_per_game_addons.ui new file mode 100644 index 000000000..aefdebfcd --- /dev/null +++ b/src/yuzu/configuration/configure_per_game_addons.ui @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigurePerGameAddons</class> + <widget class="QWidget" name="ConfigurePerGameAddons"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>300</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QScrollArea" name="scrollArea"> + <property name="widgetResizable"> + <bool>true</bool> + </property> + <widget class="QWidget" name="scrollAreaWidgetContents"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>380</width> + <height>280</height> + </rect> + </property> + </widget> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/yuzu/configuration/configure_per_general.ui b/src/yuzu/configuration/configure_per_general.ui deleted file mode 100644 index 8fdd96fa4..000000000 --- a/src/yuzu/configuration/configure_per_general.ui +++ /dev/null @@ -1,276 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>ConfigurePerGameGeneral</class> - <widget class="QDialog" name="ConfigurePerGameGeneral"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>400</width> - <height>520</height> - </rect> - </property> - <property name="windowTitle"> - <string>ConfigurePerGameGeneral</string> - </property> - <layout class="QHBoxLayout" name="HorizontalLayout"> - <item> - <layout class="QVBoxLayout" name="VerticalLayout"> - <item> - <widget class="QGroupBox" name="GeneralGroupBox"> - <property name="title"> - <string>Info</string> - </property> - <layout class="QHBoxLayout" name="GeneralHorizontalLayout"> - <item> - <layout class="QGridLayout" name="gridLayout_2"> - <item row="6" column="1" colspan="2"> - <widget class="QLineEdit" name="display_filename"> - <property name="enabled"> - <bool>true</bool> - </property> - <property name="readOnly"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="0" column="1"> - <widget class="QLineEdit" name="display_name"> - <property name="enabled"> - <bool>true</bool> - </property> - <property name="readOnly"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="label_2"> - <property name="text"> - <string>Developer</string> - </property> - </widget> - </item> - <item row="5" column="1" colspan="2"> - <widget class="QLineEdit" name="display_size"> - <property name="enabled"> - <bool>true</bool> - </property> - <property name="readOnly"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="0" column="0"> - <widget class="QLabel" name="label"> - <property name="text"> - <string>Name</string> - </property> - </widget> - </item> - <item row="6" column="0"> - <widget class="QLabel" name="label_7"> - <property name="text"> - <string>Filename</string> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="label_3"> - <property name="text"> - <string>Version</string> - </property> - </widget> - </item> - <item row="4" column="0"> - <widget class="QLabel" name="label_5"> - <property name="text"> - <string>Format</string> - </property> - </widget> - </item> - <item row="2" column="1"> - <widget class="QLineEdit" name="display_version"> - <property name="enabled"> - <bool>true</bool> - </property> - <property name="readOnly"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="4" column="1"> - <widget class="QLineEdit" name="display_format"> - <property name="enabled"> - <bool>true</bool> - </property> - <property name="readOnly"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="5" column="0"> - <widget class="QLabel" name="label_6"> - <property name="text"> - <string>Size</string> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QLineEdit" name="display_developer"> - <property name="enabled"> - <bool>true</bool> - </property> - <property name="readOnly"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="3" column="0"> - <widget class="QLabel" name="label_4"> - <property name="text"> - <string>Title ID</string> - </property> - </widget> - </item> - <item row="3" column="1"> - <widget class="QLineEdit" name="display_title_id"> - <property name="enabled"> - <bool>true</bool> - </property> - <property name="readOnly"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="0" column="2" rowspan="5"> - <widget class="QGraphicsView" name="icon_view"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="minimumSize"> - <size> - <width>128</width> - <height>128</height> - </size> - </property> - <property name="maximumSize"> - <size> - <width>128</width> - <height>128</height> - </size> - </property> - <property name="verticalScrollBarPolicy"> - <enum>Qt::ScrollBarAlwaysOff</enum> - </property> - <property name="horizontalScrollBarPolicy"> - <enum>Qt::ScrollBarAlwaysOff</enum> - </property> - <property name="sizeAdjustPolicy"> - <enum>QAbstractScrollArea::AdjustToContents</enum> - </property> - <property name="interactive"> - <bool>false</bool> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </widget> - </item> - <item> - <widget class="QGroupBox" name="PerformanceGroupBox"> - <property name="title"> - <string>Add-Ons</string> - </property> - <layout class="QHBoxLayout" name="PerformanceHorizontalLayout"> - <item> - <widget class="QScrollArea" name="scrollArea"> - <property name="widgetResizable"> - <bool>true</bool> - </property> - <widget class="QWidget" name="scrollAreaWidgetContents"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>350</width> - <height>169</height> - </rect> - </property> - </widget> - </widget> - </item> - <item> - <layout class="QVBoxLayout" name="PerformanceVerticalLayout"/> - </item> - </layout> - </widget> - </item> - <item> - <spacer name="verticalSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeType"> - <enum>QSizePolicy::Fixed</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QDialogButtonBox" name="buttonBox"> - <property name="standardButtons"> - <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </widget> - <resources/> - <connections> - <connection> - <sender>buttonBox</sender> - <signal>accepted()</signal> - <receiver>ConfigurePerGameGeneral</receiver> - <slot>accept()</slot> - <hints> - <hint type="sourcelabel"> - <x>269</x> - <y>567</y> - </hint> - <hint type="destinationlabel"> - <x>269</x> - <y>294</y> - </hint> - </hints> - </connection> - <connection> - <sender>buttonBox</sender> - <signal>rejected()</signal> - <receiver>ConfigurePerGameGeneral</receiver> - <slot>reject()</slot> - <hints> - <hint type="sourcelabel"> - <x>269</x> - <y>567</y> - </hint> - <hint type="destinationlabel"> - <x>269</x> - <y>294</y> - </hint> - </hints> - </connection> - </connections> -</ui> diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp index 10315e7a6..68e02738b 100644 --- a/src/yuzu/configuration/configure_system.cpp +++ b/src/yuzu/configuration/configure_system.cpp @@ -14,6 +14,7 @@ #include "core/core.h" #include "core/settings.h" #include "ui_configure_system.h" +#include "yuzu/configuration/configuration_shared.h" #include "yuzu/configuration/configure_system.h" ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureSystem) { @@ -21,20 +22,25 @@ ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui:: connect(ui->button_regenerate_console_id, &QPushButton::clicked, this, &ConfigureSystem::RefreshConsoleID); - connect(ui->rng_seed_checkbox, &QCheckBox::stateChanged, this, [this](bool checked) { - ui->rng_seed_edit->setEnabled(checked); - if (!checked) { + connect(ui->rng_seed_checkbox, &QCheckBox::stateChanged, this, [this](int state) { + ui->rng_seed_edit->setEnabled(state == Qt::Checked); + if (state != Qt::Checked) { ui->rng_seed_edit->setText(QStringLiteral("00000000")); } }); - connect(ui->custom_rtc_checkbox, &QCheckBox::stateChanged, this, [this](bool checked) { - ui->custom_rtc_edit->setEnabled(checked); - if (!checked) { + connect(ui->custom_rtc_checkbox, &QCheckBox::stateChanged, this, [this](int state) { + ui->custom_rtc_edit->setEnabled(state == Qt::Checked); + if (state != Qt::Checked) { ui->custom_rtc_edit->setDateTime(QDateTime::currentDateTime()); } }); + ui->label_console_id->setVisible(Settings::configuring_global); + ui->button_regenerate_console_id->setVisible(Settings::configuring_global); + + SetupPerGameUI(); + SetConfiguration(); } @@ -54,26 +60,58 @@ void ConfigureSystem::RetranslateUI() { void ConfigureSystem::SetConfiguration() { enabled = !Core::System::GetInstance().IsPoweredOn(); + const auto rng_seed = + QStringLiteral("%1") + .arg(Settings::values.rng_seed.GetValue().value_or(0), 8, 16, QLatin1Char{'0'}) + .toUpper(); + const auto rtc_time = Settings::values.custom_rtc.GetValue().value_or( + std::chrono::seconds(QDateTime::currentSecsSinceEpoch())); - ui->combo_language->setCurrentIndex(Settings::values.language_index); - ui->combo_region->setCurrentIndex(Settings::values.region_index); - ui->combo_time_zone->setCurrentIndex(Settings::values.time_zone_index); - ui->combo_sound->setCurrentIndex(Settings::values.sound_index); - - ui->rng_seed_checkbox->setChecked(Settings::values.rng_seed.has_value()); - ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.has_value()); - - const auto rng_seed = QStringLiteral("%1") - .arg(Settings::values.rng_seed.value_or(0), 8, 16, QLatin1Char{'0'}) - .toUpper(); - ui->rng_seed_edit->setText(rng_seed); - - ui->custom_rtc_checkbox->setChecked(Settings::values.custom_rtc.has_value()); - ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.has_value()); + if (Settings::configuring_global) { + ui->combo_language->setCurrentIndex(Settings::values.language_index.GetValue()); + ui->combo_region->setCurrentIndex(Settings::values.region_index.GetValue()); + ui->combo_time_zone->setCurrentIndex(Settings::values.time_zone_index.GetValue()); + ui->combo_sound->setCurrentIndex(Settings::values.sound_index.GetValue()); + + ui->rng_seed_checkbox->setChecked(Settings::values.rng_seed.GetValue().has_value()); + ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.GetValue().has_value() && + Settings::values.rng_seed.UsingGlobal()); + ui->rng_seed_edit->setText(rng_seed); + + ui->custom_rtc_checkbox->setChecked(Settings::values.custom_rtc.GetValue().has_value()); + ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.GetValue().has_value() && + Settings::values.rng_seed.UsingGlobal()); + ui->custom_rtc_edit->setDateTime(QDateTime::fromSecsSinceEpoch(rtc_time.count())); + } else { + ConfigurationShared::SetPerGameSetting(ui->combo_language, + &Settings::values.language_index); + ConfigurationShared::SetPerGameSetting(ui->combo_region, &Settings::values.region_index); + ConfigurationShared::SetPerGameSetting(ui->combo_time_zone, + &Settings::values.time_zone_index); + ConfigurationShared::SetPerGameSetting(ui->combo_sound, &Settings::values.sound_index); + + if (Settings::values.rng_seed.UsingGlobal()) { + ui->rng_seed_checkbox->setCheckState(Qt::PartiallyChecked); + } else { + ui->rng_seed_checkbox->setCheckState( + Settings::values.rng_seed.GetValue().has_value() ? Qt::Checked : Qt::Unchecked); + ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.GetValue().has_value()); + if (Settings::values.rng_seed.GetValue().has_value()) { + ui->rng_seed_edit->setText(rng_seed); + } + } - const auto rtc_time = Settings::values.custom_rtc.value_or( - std::chrono::seconds(QDateTime::currentSecsSinceEpoch())); - ui->custom_rtc_edit->setDateTime(QDateTime::fromSecsSinceEpoch(rtc_time.count())); + if (Settings::values.custom_rtc.UsingGlobal()) { + ui->custom_rtc_checkbox->setCheckState(Qt::PartiallyChecked); + } else { + ui->custom_rtc_checkbox->setCheckState( + Settings::values.custom_rtc.GetValue().has_value() ? Qt::Checked : Qt::Unchecked); + ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.GetValue().has_value()); + if (Settings::values.custom_rtc.GetValue().has_value()) { + ui->custom_rtc_edit->setDateTime(QDateTime::fromSecsSinceEpoch(rtc_time.count())); + } + } + } } void ConfigureSystem::ReadSystemSettings() {} @@ -83,22 +121,78 @@ void ConfigureSystem::ApplyConfiguration() { return; } - Settings::values.language_index = ui->combo_language->currentIndex(); - Settings::values.region_index = ui->combo_region->currentIndex(); - Settings::values.time_zone_index = ui->combo_time_zone->currentIndex(); - Settings::values.sound_index = ui->combo_sound->currentIndex(); + if (Settings::configuring_global) { + // Guard if during game and set to game-specific value + if (Settings::values.language_index.UsingGlobal()) { + Settings::values.language_index.SetValue(ui->combo_language->currentIndex()); + } + if (Settings::values.region_index.UsingGlobal()) { + Settings::values.region_index.SetValue(ui->combo_region->currentIndex()); + } + if (Settings::values.time_zone_index.UsingGlobal()) { + Settings::values.time_zone_index.SetValue(ui->combo_time_zone->currentIndex()); + } + if (Settings::values.sound_index.UsingGlobal()) { + Settings::values.sound_index.SetValue(ui->combo_sound->currentIndex()); + } + + if (Settings::values.rng_seed.UsingGlobal()) { + if (ui->rng_seed_checkbox->isChecked()) { + Settings::values.rng_seed.SetValue( + ui->rng_seed_edit->text().toULongLong(nullptr, 16)); + } else { + Settings::values.rng_seed.SetValue(std::nullopt); + } + } - if (ui->rng_seed_checkbox->isChecked()) { - Settings::values.rng_seed = ui->rng_seed_edit->text().toULongLong(nullptr, 16); + if (Settings::values.custom_rtc.UsingGlobal()) { + if (ui->custom_rtc_checkbox->isChecked()) { + Settings::values.custom_rtc.SetValue( + std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch())); + } else { + Settings::values.custom_rtc.SetValue(std::nullopt); + } + } } else { - Settings::values.rng_seed = std::nullopt; - } + ConfigurationShared::ApplyPerGameSetting(&Settings::values.language_index, + ui->combo_language); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.region_index, ui->combo_region); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.time_zone_index, + ui->combo_time_zone); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.sound_index, ui->combo_sound); + + switch (ui->rng_seed_checkbox->checkState()) { + case Qt::Checked: + Settings::values.rng_seed.SetGlobal(false); + Settings::values.rng_seed.SetValue(ui->rng_seed_edit->text().toULongLong(nullptr, 16)); + break; + case Qt::Unchecked: + Settings::values.rng_seed.SetGlobal(false); + Settings::values.rng_seed.SetValue(std::nullopt); + break; + case Qt::PartiallyChecked: + Settings::values.rng_seed.SetGlobal(false); + Settings::values.rng_seed.SetValue(std::nullopt); + Settings::values.rng_seed.SetGlobal(true); + break; + } - if (ui->custom_rtc_checkbox->isChecked()) { - Settings::values.custom_rtc = - std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch()); - } else { - Settings::values.custom_rtc = std::nullopt; + switch (ui->custom_rtc_checkbox->checkState()) { + case Qt::Checked: + Settings::values.custom_rtc.SetGlobal(false); + Settings::values.custom_rtc.SetValue( + std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch())); + break; + case Qt::Unchecked: + Settings::values.custom_rtc.SetGlobal(false); + Settings::values.custom_rtc.SetValue(std::nullopt); + break; + case Qt::PartiallyChecked: + Settings::values.custom_rtc.SetGlobal(false); + Settings::values.custom_rtc.SetValue(std::nullopt); + Settings::values.custom_rtc.SetGlobal(true); + break; + } } Settings::Apply(); @@ -120,3 +214,25 @@ void ConfigureSystem::RefreshConsoleID() { ui->label_console_id->setText( tr("Console ID: 0x%1").arg(QString::number(console_id, 16).toUpper())); } + +void ConfigureSystem::SetupPerGameUI() { + if (Settings::configuring_global) { + ui->combo_language->setEnabled(Settings::values.language_index.UsingGlobal()); + ui->combo_region->setEnabled(Settings::values.region_index.UsingGlobal()); + ui->combo_time_zone->setEnabled(Settings::values.time_zone_index.UsingGlobal()); + ui->combo_sound->setEnabled(Settings::values.sound_index.UsingGlobal()); + ui->rng_seed_checkbox->setEnabled(Settings::values.rng_seed.UsingGlobal()); + ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.UsingGlobal()); + ui->custom_rtc_checkbox->setEnabled(Settings::values.custom_rtc.UsingGlobal()); + ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.UsingGlobal()); + + return; + } + + ConfigurationShared::InsertGlobalItem(ui->combo_language); + ConfigurationShared::InsertGlobalItem(ui->combo_region); + ConfigurationShared::InsertGlobalItem(ui->combo_time_zone); + ConfigurationShared::InsertGlobalItem(ui->combo_sound); + ui->rng_seed_checkbox->setTristate(true); + ui->custom_rtc_checkbox->setTristate(true); +} diff --git a/src/yuzu/configuration/configure_system.h b/src/yuzu/configuration/configure_system.h index 26d42d5c5..f317ef8b5 100644 --- a/src/yuzu/configuration/configure_system.h +++ b/src/yuzu/configuration/configure_system.h @@ -32,6 +32,8 @@ private: void RefreshConsoleID(); + void SetupPerGameUI(); + std::unique_ptr<Ui::ConfigureSystem> ui; bool enabled = false; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index fb299a39b..4d501a8f9 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -16,7 +16,7 @@ #include "applets/software_keyboard.h" #include "applets/web_browser.h" #include "configuration/configure_input.h" -#include "configuration/configure_per_general.h" +#include "configuration/configure_per_game.h" #include "core/file_sys/vfs.h" #include "core/file_sys/vfs_real.h" #include "core/frontend/applets/general_frontend.h" @@ -534,15 +534,15 @@ void GMainWindow::InitializeWidgets() { if (emulation_running) { return; } - bool is_async = - !Settings::values.use_asynchronous_gpu_emulation || Settings::values.use_multi_core; - Settings::values.use_asynchronous_gpu_emulation = is_async; - async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation); + bool is_async = !Settings::values.use_asynchronous_gpu_emulation.GetValue() || + Settings::values.use_multi_core.GetValue(); + Settings::values.use_asynchronous_gpu_emulation.SetValue(is_async); + async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation.GetValue()); Settings::Apply(); }); async_status_button->setText(tr("ASYNC")); async_status_button->setCheckable(true); - async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation); + async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation.GetValue()); // Setup Multicore button multicore_status_button = new QPushButton(); @@ -552,17 +552,17 @@ void GMainWindow::InitializeWidgets() { if (emulation_running) { return; } - Settings::values.use_multi_core = !Settings::values.use_multi_core; - bool is_async = - Settings::values.use_asynchronous_gpu_emulation || Settings::values.use_multi_core; - Settings::values.use_asynchronous_gpu_emulation = is_async; - async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation); - multicore_status_button->setChecked(Settings::values.use_multi_core); + Settings::values.use_multi_core.SetValue(!Settings::values.use_multi_core.GetValue()); + bool is_async = Settings::values.use_asynchronous_gpu_emulation.GetValue() || + Settings::values.use_multi_core.GetValue(); + Settings::values.use_asynchronous_gpu_emulation.SetValue(is_async); + async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation.GetValue()); + multicore_status_button->setChecked(Settings::values.use_multi_core.GetValue()); Settings::Apply(); }); multicore_status_button->setText(tr("MULTICORE")); multicore_status_button->setCheckable(true); - multicore_status_button->setChecked(Settings::values.use_multi_core); + multicore_status_button->setChecked(Settings::values.use_multi_core.GetValue()); statusBar()->insertPermanentWidget(0, multicore_status_button); statusBar()->insertPermanentWidget(0, async_status_button); @@ -581,16 +581,16 @@ void GMainWindow::InitializeWidgets() { renderer_status_button->setCheckable(false); renderer_status_button->setDisabled(true); #else - renderer_status_button->setChecked(Settings::values.renderer_backend == + renderer_status_button->setChecked(Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::Vulkan); connect(renderer_status_button, &QPushButton::clicked, [=] { if (emulation_running) { return; } if (renderer_status_button->isChecked()) { - Settings::values.renderer_backend = Settings::RendererBackend::Vulkan; + Settings::values.renderer_backend.SetValue(Settings::RendererBackend::Vulkan); } else { - Settings::values.renderer_backend = Settings::RendererBackend::OpenGL; + Settings::values.renderer_backend.SetValue(Settings::RendererBackend::OpenGL); } Settings::Apply(); @@ -727,21 +727,24 @@ void GMainWindow::InitializeHotkeys() { }); connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Toggle Speed Limit"), this), &QShortcut::activated, this, [&] { - Settings::values.use_frame_limit = !Settings::values.use_frame_limit; + Settings::values.use_frame_limit.SetValue( + !Settings::values.use_frame_limit.GetValue()); UpdateStatusBar(); }); constexpr u16 SPEED_LIMIT_STEP = 5; connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Increase Speed Limit"), this), &QShortcut::activated, this, [&] { - if (Settings::values.frame_limit < 9999 - SPEED_LIMIT_STEP) { - Settings::values.frame_limit += SPEED_LIMIT_STEP; + if (Settings::values.frame_limit.GetValue() < 9999 - SPEED_LIMIT_STEP) { + Settings::values.frame_limit.SetValue(SPEED_LIMIT_STEP + + Settings::values.frame_limit.GetValue()); UpdateStatusBar(); } }); connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Decrease Speed Limit"), this), &QShortcut::activated, this, [&] { - if (Settings::values.frame_limit > SPEED_LIMIT_STEP) { - Settings::values.frame_limit -= SPEED_LIMIT_STEP; + if (Settings::values.frame_limit.GetValue() > SPEED_LIMIT_STEP) { + Settings::values.frame_limit.SetValue(Settings::values.frame_limit.GetValue() - + SPEED_LIMIT_STEP); UpdateStatusBar(); } }); @@ -753,7 +756,7 @@ void GMainWindow::InitializeHotkeys() { }); connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Capture Screenshot"), this), &QShortcut::activated, this, [&] { - if (emu_thread->IsRunning()) { + if (emu_thread != nullptr && emu_thread->IsRunning()) { OnCaptureScreenshot(); } }); @@ -1039,6 +1042,17 @@ void GMainWindow::BootGame(const QString& filename) { LOG_INFO(Frontend, "yuzu starting..."); StoreRecentFile(filename); // Put the filename on top of the list + u64 title_id{0}; + + const auto v_file = Core::GetGameFileFromPath(vfs, filename.toUtf8().constData()); + const auto loader = Loader::GetLoader(v_file); + if (!(loader == nullptr || loader->ReadProgramId(title_id) != Loader::ResultStatus::Success)) { + // Load per game settings + Config per_game_config(fmt::format("{:016X}.ini", title_id), false); + } + + Settings::LogSettings(); + if (UISettings::values.select_user_on_boot) { SelectAndSetCurrentUser(); } @@ -1063,6 +1077,7 @@ void GMainWindow::BootGame(const QString& filename) { &LoadingScreen::OnLoadProgress, Qt::QueuedConnection); // Update the GUI + UpdateStatusButtons(); if (ui.action_Single_Window_Mode->isChecked()) { game_list->hide(); game_list_placeholder->hide(); @@ -1078,8 +1093,6 @@ void GMainWindow::BootGame(const QString& filename) { ui.centralwidget->setMouseTracking(true); } - const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID(); - std::string title_name; std::string title_version; const auto res = Core::System::GetInstance().GetGameName(title_name); @@ -1521,7 +1534,7 @@ void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) { return; } - ConfigurePerGameGeneral dialog(this, title_id); + ConfigurePerGame dialog(this, title_id); dialog.LoadFromFile(v_file); auto result = dialog.exec(); if (result == QDialog::Accepted) { @@ -1532,7 +1545,14 @@ void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) { game_list->PopulateAsync(UISettings::values.game_dirs); } - config->Save(); + // Do not cause the global config to write local settings into the config file + Settings::RestoreGlobalState(); + + if (!Core::System::GetInstance().IsPoweredOn()) { + config->Save(); + } + } else { + Settings::RestoreGlobalState(); } } @@ -1819,6 +1839,9 @@ void GMainWindow::OnStopGame() { } ShutdownGame(); + + Settings::RestoreGlobalState(); + UpdateStatusButtons(); } void GMainWindow::OnLoadComplete() { @@ -1926,7 +1949,7 @@ void GMainWindow::ToggleWindowMode() { void GMainWindow::ResetWindowSize() { const auto aspect_ratio = Layout::EmulationAspectRatio( - static_cast<Layout::AspectRatio>(Settings::values.aspect_ratio), + static_cast<Layout::AspectRatio>(Settings::values.aspect_ratio.GetValue()), static_cast<float>(Layout::ScreenUndocked::Height) / Layout::ScreenUndocked::Width); if (!ui.action_Single_Window_Mode->isChecked()) { render_window->resize(Layout::ScreenUndocked::Height / aspect_ratio, @@ -1974,16 +1997,7 @@ void GMainWindow::OnConfigure() { ui.centralwidget->setMouseTracking(false); } - dock_status_button->setChecked(Settings::values.use_docked_mode); - multicore_status_button->setChecked(Settings::values.use_multi_core); - Settings::values.use_asynchronous_gpu_emulation = - Settings::values.use_asynchronous_gpu_emulation || Settings::values.use_multi_core; - async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation); - -#ifdef HAS_VULKAN - renderer_status_button->setChecked(Settings::values.renderer_backend == - Settings::RendererBackend::Vulkan); -#endif + UpdateStatusButtons(); } void GMainWindow::OnLoadAmiibo() { @@ -2097,21 +2111,34 @@ void GMainWindow::UpdateStatusBar() { auto results = Core::System::GetInstance().GetAndResetPerfStats(); - if (Settings::values.use_frame_limit) { + if (Settings::values.use_frame_limit.GetValue()) { emu_speed_label->setText(tr("Speed: %1% / %2%") .arg(results.emulation_speed * 100.0, 0, 'f', 0) - .arg(Settings::values.frame_limit)); + .arg(Settings::values.frame_limit.GetValue())); } else { emu_speed_label->setText(tr("Speed: %1%").arg(results.emulation_speed * 100.0, 0, 'f', 0)); } game_fps_label->setText(tr("Game: %1 FPS").arg(results.game_fps, 0, 'f', 0)); emu_frametime_label->setText(tr("Frame: %1 ms").arg(results.frametime * 1000.0, 0, 'f', 2)); - emu_speed_label->setVisible(!Settings::values.use_multi_core); + emu_speed_label->setVisible(!Settings::values.use_multi_core.GetValue()); game_fps_label->setVisible(true); emu_frametime_label->setVisible(true); } +void GMainWindow::UpdateStatusButtons() { + dock_status_button->setChecked(Settings::values.use_docked_mode); + multicore_status_button->setChecked(Settings::values.use_multi_core.GetValue()); + Settings::values.use_asynchronous_gpu_emulation.SetValue( + Settings::values.use_asynchronous_gpu_emulation.GetValue() || + Settings::values.use_multi_core.GetValue()); + async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation.GetValue()); +#ifdef HAS_VULKAN + renderer_status_button->setChecked(Settings::values.renderer_backend.GetValue() == + Settings::RendererBackend::Vulkan); +#endif +} + void GMainWindow::HideMouseCursor() { if (emu_thread == nullptr || UISettings::values.hide_mouse == false) { mouse_hide_timer.stop(); @@ -2195,6 +2222,9 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det if (answer == QMessageBox::Yes) { if (emu_thread) { ShutdownGame(); + + Settings::RestoreGlobalState(); + UpdateStatusButtons(); } } else { // Only show the message if the game is still running. @@ -2357,9 +2387,13 @@ void GMainWindow::closeEvent(QCloseEvent* event) { hotkey_registry.SaveHotkeys(); // Shutdown session if the emu thread is active... - if (emu_thread != nullptr) + if (emu_thread != nullptr) { ShutdownGame(); + Settings::RestoreGlobalState(); + UpdateStatusButtons(); + } + render_window->close(); QWidget::closeEvent(event); @@ -2539,8 +2573,6 @@ int main(int argc, char* argv[]) { QObject::connect(&app, &QGuiApplication::applicationStateChanged, &main_window, &GMainWindow::OnAppFocusStateChanged); - Settings::LogSettings(); - int result = app.exec(); detached_tasks.WaitForAllTasks(); return result; diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 66c84e5c0..8e3d39c38 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -221,6 +221,7 @@ private: void UpdateWindowTitle(const std::string& title_name = {}, const std::string& title_version = {}); void UpdateStatusBar(); + void UpdateStatusButtons(); void HideMouseCursor(); void ShowMouseCursor(); void OpenURL(const QUrl& url); diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index 659b9f701..23763144f 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -354,63 +354,72 @@ void Config::ReadValues() { const auto rng_seed_enabled = sdl2_config->GetBoolean("System", "rng_seed_enabled", false); if (rng_seed_enabled) { - Settings::values.rng_seed = sdl2_config->GetInteger("System", "rng_seed", 0); + Settings::values.rng_seed.SetValue(sdl2_config->GetInteger("System", "rng_seed", 0)); } else { - Settings::values.rng_seed = std::nullopt; + Settings::values.rng_seed.SetValue(std::nullopt); } const auto custom_rtc_enabled = sdl2_config->GetBoolean("System", "custom_rtc_enabled", false); if (custom_rtc_enabled) { - Settings::values.custom_rtc = - std::chrono::seconds(sdl2_config->GetInteger("System", "custom_rtc", 0)); + Settings::values.custom_rtc.SetValue( + std::chrono::seconds(sdl2_config->GetInteger("System", "custom_rtc", 0))); } else { - Settings::values.custom_rtc = std::nullopt; + Settings::values.custom_rtc.SetValue(std::nullopt); } - Settings::values.language_index = sdl2_config->GetInteger("System", "language_index", 1); - Settings::values.time_zone_index = sdl2_config->GetInteger("System", "time_zone_index", 0); + Settings::values.language_index.SetValue( + sdl2_config->GetInteger("System", "language_index", 1)); + Settings::values.time_zone_index.SetValue( + sdl2_config->GetInteger("System", "time_zone_index", 0)); // Core - Settings::values.use_multi_core = sdl2_config->GetBoolean("Core", "use_multi_core", false); + Settings::values.use_multi_core.SetValue( + sdl2_config->GetBoolean("Core", "use_multi_core", false)); // Renderer const int renderer_backend = sdl2_config->GetInteger( "Renderer", "backend", static_cast<int>(Settings::RendererBackend::OpenGL)); - Settings::values.renderer_backend = static_cast<Settings::RendererBackend>(renderer_backend); + Settings::values.renderer_backend.SetValue( + static_cast<Settings::RendererBackend>(renderer_backend)); Settings::values.renderer_debug = sdl2_config->GetBoolean("Renderer", "debug", false); - Settings::values.vulkan_device = sdl2_config->GetInteger("Renderer", "vulkan_device", 0); - - Settings::values.aspect_ratio = - static_cast<int>(sdl2_config->GetInteger("Renderer", "aspect_ratio", 0)); - Settings::values.max_anisotropy = - static_cast<int>(sdl2_config->GetInteger("Renderer", "max_anisotropy", 0)); - Settings::values.use_frame_limit = sdl2_config->GetBoolean("Renderer", "use_frame_limit", true); - Settings::values.frame_limit = - static_cast<u16>(sdl2_config->GetInteger("Renderer", "frame_limit", 100)); - Settings::values.use_disk_shader_cache = - sdl2_config->GetBoolean("Renderer", "use_disk_shader_cache", false); + Settings::values.vulkan_device.SetValue( + sdl2_config->GetInteger("Renderer", "vulkan_device", 0)); + + Settings::values.aspect_ratio.SetValue( + static_cast<int>(sdl2_config->GetInteger("Renderer", "aspect_ratio", 0))); + Settings::values.max_anisotropy.SetValue( + static_cast<int>(sdl2_config->GetInteger("Renderer", "max_anisotropy", 0))); + Settings::values.use_frame_limit.SetValue( + sdl2_config->GetBoolean("Renderer", "use_frame_limit", true)); + Settings::values.frame_limit.SetValue( + static_cast<u16>(sdl2_config->GetInteger("Renderer", "frame_limit", 100))); + Settings::values.use_disk_shader_cache.SetValue( + sdl2_config->GetBoolean("Renderer", "use_disk_shader_cache", false)); const int gpu_accuracy_level = sdl2_config->GetInteger("Renderer", "gpu_accuracy", 0); - Settings::values.gpu_accuracy = static_cast<Settings::GPUAccuracy>(gpu_accuracy_level); - Settings::values.use_asynchronous_gpu_emulation = - sdl2_config->GetBoolean("Renderer", "use_asynchronous_gpu_emulation", false); - Settings::values.use_vsync = - static_cast<u16>(sdl2_config->GetInteger("Renderer", "use_vsync", 1)); - Settings::values.use_assembly_shaders = - sdl2_config->GetBoolean("Renderer", "use_assembly_shaders", false); - Settings::values.use_fast_gpu_time = - sdl2_config->GetBoolean("Renderer", "use_fast_gpu_time", true); - - Settings::values.bg_red = static_cast<float>(sdl2_config->GetReal("Renderer", "bg_red", 0.0)); - Settings::values.bg_green = - static_cast<float>(sdl2_config->GetReal("Renderer", "bg_green", 0.0)); - Settings::values.bg_blue = static_cast<float>(sdl2_config->GetReal("Renderer", "bg_blue", 0.0)); + Settings::values.gpu_accuracy.SetValue(static_cast<Settings::GPUAccuracy>(gpu_accuracy_level)); + Settings::values.use_asynchronous_gpu_emulation.SetValue( + sdl2_config->GetBoolean("Renderer", "use_asynchronous_gpu_emulation", false)); + Settings::values.use_vsync.SetValue( + static_cast<u16>(sdl2_config->GetInteger("Renderer", "use_vsync", 1))); + Settings::values.use_assembly_shaders.SetValue( + sdl2_config->GetBoolean("Renderer", "use_assembly_shaders", false)); + Settings::values.use_fast_gpu_time.SetValue( + sdl2_config->GetBoolean("Renderer", "use_fast_gpu_time", true)); + + Settings::values.bg_red.SetValue( + static_cast<float>(sdl2_config->GetReal("Renderer", "bg_red", 0.0))); + Settings::values.bg_green.SetValue( + static_cast<float>(sdl2_config->GetReal("Renderer", "bg_green", 0.0))); + Settings::values.bg_blue.SetValue( + static_cast<float>(sdl2_config->GetReal("Renderer", "bg_blue", 0.0))); // Audio Settings::values.sink_id = sdl2_config->Get("Audio", "output_engine", "auto"); - Settings::values.enable_audio_stretching = - sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true); + Settings::values.enable_audio_stretching.SetValue( + sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true)); Settings::values.audio_device_id = sdl2_config->Get("Audio", "output_device", "auto"); - Settings::values.volume = static_cast<float>(sdl2_config->GetReal("Audio", "volume", 1)); + Settings::values.volume.SetValue( + static_cast<float>(sdl2_config->GetReal("Audio", "volume", 1))); // Miscellaneous Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Trace"); diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp index 09cc0a3b5..e78025737 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp @@ -165,7 +165,7 @@ std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_GL::CreateShared void EmuWindow_SDL2_GL::Present() { SDL_GL_MakeCurrent(render_window, window_context); - SDL_GL_SetSwapInterval(Settings::values.use_vsync ? 1 : 0); + SDL_GL_SetSwapInterval(Settings::values.use_vsync.GetValue() ? 1 : 0); while (IsOpen()) { system.Renderer().TryPresent(100); SDL_GL_SwapWindow(render_window); diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index e6c6a839d..512b060a7 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -181,7 +181,7 @@ int main(int argc, char** argv) { Core::System& system{Core::System::GetInstance()}; std::unique_ptr<EmuWindow_SDL2> emu_window; - switch (Settings::values.renderer_backend) { + switch (Settings::values.renderer_backend.GetValue()) { case Settings::RendererBackend::OpenGL: emu_window = std::make_unique<EmuWindow_SDL2_GL>(system, fullscreen); break; diff --git a/src/yuzu_tester/config.cpp b/src/yuzu_tester/config.cpp index 1566c2e3f..acb22885e 100644 --- a/src/yuzu_tester/config.cpp +++ b/src/yuzu_tester/config.cpp @@ -81,6 +81,9 @@ void Config::ReadValues() { Settings::values.touchscreen.diameter_x = 15; Settings::values.touchscreen.diameter_y = 15; + Settings::values.use_docked_mode = + sdl2_config->GetBoolean("Controls", "use_docked_mode", false); + // Data Storage Settings::values.use_virtual_sd = sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true); @@ -92,57 +95,59 @@ void Config::ReadValues() { FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir))); // System - Settings::values.use_docked_mode = sdl2_config->GetBoolean("System", "use_docked_mode", false); - Settings::values.current_user = std::clamp<int>( sdl2_config->GetInteger("System", "current_user", 0), 0, Service::Account::MAX_USERS - 1); const auto rng_seed_enabled = sdl2_config->GetBoolean("System", "rng_seed_enabled", false); if (rng_seed_enabled) { - Settings::values.rng_seed = sdl2_config->GetInteger("System", "rng_seed", 0); + Settings::values.rng_seed.SetValue(sdl2_config->GetInteger("System", "rng_seed", 0)); } else { - Settings::values.rng_seed = std::nullopt; + Settings::values.rng_seed.SetValue(std::nullopt); } const auto custom_rtc_enabled = sdl2_config->GetBoolean("System", "custom_rtc_enabled", false); if (custom_rtc_enabled) { - Settings::values.custom_rtc = - std::chrono::seconds(sdl2_config->GetInteger("System", "custom_rtc", 0)); + Settings::values.custom_rtc.SetValue( + std::chrono::seconds(sdl2_config->GetInteger("System", "custom_rtc", 0))); } else { - Settings::values.custom_rtc = std::nullopt; + Settings::values.custom_rtc.SetValue(std::nullopt); } // Core - Settings::values.use_multi_core = sdl2_config->GetBoolean("Core", "use_multi_core", false); + Settings::values.use_multi_core.SetValue( + sdl2_config->GetBoolean("Core", "use_multi_core", false)); // Renderer - Settings::values.aspect_ratio = - static_cast<int>(sdl2_config->GetInteger("Renderer", "aspect_ratio", 0)); - Settings::values.max_anisotropy = - static_cast<int>(sdl2_config->GetInteger("Renderer", "max_anisotropy", 0)); - Settings::values.use_frame_limit = false; - Settings::values.frame_limit = 100; - Settings::values.use_disk_shader_cache = - sdl2_config->GetBoolean("Renderer", "use_disk_shader_cache", false); + Settings::values.aspect_ratio.SetValue( + static_cast<int>(sdl2_config->GetInteger("Renderer", "aspect_ratio", 0))); + Settings::values.max_anisotropy.SetValue( + static_cast<int>(sdl2_config->GetInteger("Renderer", "max_anisotropy", 0))); + Settings::values.use_frame_limit.SetValue(false); + Settings::values.frame_limit.SetValue(100); + Settings::values.use_disk_shader_cache.SetValue( + sdl2_config->GetBoolean("Renderer", "use_disk_shader_cache", false)); const int gpu_accuracy_level = sdl2_config->GetInteger("Renderer", "gpu_accuracy", 0); - Settings::values.gpu_accuracy = static_cast<Settings::GPUAccuracy>(gpu_accuracy_level); - Settings::values.use_asynchronous_gpu_emulation = - sdl2_config->GetBoolean("Renderer", "use_asynchronous_gpu_emulation", false); - Settings::values.use_fast_gpu_time = - sdl2_config->GetBoolean("Renderer", "use_fast_gpu_time", true); - - Settings::values.bg_red = static_cast<float>(sdl2_config->GetReal("Renderer", "bg_red", 0.0)); - Settings::values.bg_green = - static_cast<float>(sdl2_config->GetReal("Renderer", "bg_green", 0.0)); - Settings::values.bg_blue = static_cast<float>(sdl2_config->GetReal("Renderer", "bg_blue", 0.0)); + Settings::values.gpu_accuracy.SetValue(static_cast<Settings::GPUAccuracy>(gpu_accuracy_level)); + Settings::values.use_asynchronous_gpu_emulation.SetValue( + sdl2_config->GetBoolean("Renderer", "use_asynchronous_gpu_emulation", false)); + Settings::values.use_fast_gpu_time.SetValue( + sdl2_config->GetBoolean("Renderer", "use_fast_gpu_time", true)); + + Settings::values.bg_red.SetValue( + static_cast<float>(sdl2_config->GetReal("Renderer", "bg_red", 0.0))); + Settings::values.bg_green.SetValue( + static_cast<float>(sdl2_config->GetReal("Renderer", "bg_green", 0.0))); + Settings::values.bg_blue.SetValue( + static_cast<float>(sdl2_config->GetReal("Renderer", "bg_blue", 0.0))); // Audio Settings::values.sink_id = "null"; - Settings::values.enable_audio_stretching = false; + Settings::values.enable_audio_stretching.SetValue(false); Settings::values.audio_device_id = "auto"; - Settings::values.volume = 0; + Settings::values.volume.SetValue(0); - Settings::values.language_index = sdl2_config->GetInteger("System", "language_index", 1); + Settings::values.language_index.SetValue( + sdl2_config->GetInteger("System", "language_index", 1)); // Miscellaneous Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Trace"); |