diff options
Diffstat (limited to 'src')
167 files changed, 3017 insertions, 1852 deletions
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 57922b51c..b18a2a2f5 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -53,6 +53,8 @@ add_library(common STATIC div_ceil.h dynamic_library.cpp dynamic_library.h + error.cpp + error.h fiber.cpp fiber.h fs/file.cpp @@ -88,7 +90,6 @@ add_library(common STATIC microprofile.cpp microprofile.h microprofileui.h - misc.cpp nvidia_flags.cpp nvidia_flags.h page_table.cpp diff --git a/src/common/alignment.h b/src/common/alignment.h index 32d796ffa..1b56569d1 100644 --- a/src/common/alignment.h +++ b/src/common/alignment.h @@ -9,41 +9,48 @@ namespace Common { template <typename T> -requires std::is_unsigned_v<T>[[nodiscard]] constexpr T AlignUp(T value, size_t size) { +requires std::is_unsigned_v<T> +[[nodiscard]] constexpr T AlignUp(T value, size_t size) { auto mod{static_cast<T>(value % size)}; value -= mod; return static_cast<T>(mod == T{0} ? value : value + size); } template <typename T> -requires std::is_unsigned_v<T>[[nodiscard]] constexpr T AlignUpLog2(T value, size_t align_log2) { +requires std::is_unsigned_v<T> +[[nodiscard]] constexpr T AlignUpLog2(T value, size_t align_log2) { return static_cast<T>((value + ((1ULL << align_log2) - 1)) >> align_log2 << align_log2); } template <typename T> -requires std::is_unsigned_v<T>[[nodiscard]] constexpr T AlignDown(T value, size_t size) { +requires std::is_unsigned_v<T> +[[nodiscard]] constexpr T AlignDown(T value, size_t size) { return static_cast<T>(value - value % size); } template <typename T> -requires std::is_unsigned_v<T>[[nodiscard]] constexpr bool Is4KBAligned(T value) { +requires std::is_unsigned_v<T> +[[nodiscard]] constexpr bool Is4KBAligned(T value) { return (value & 0xFFF) == 0; } template <typename T> -requires std::is_unsigned_v<T>[[nodiscard]] constexpr bool IsWordAligned(T value) { +requires std::is_unsigned_v<T> +[[nodiscard]] constexpr bool IsWordAligned(T value) { return (value & 0b11) == 0; } template <typename T> -requires std::is_integral_v<T>[[nodiscard]] constexpr bool IsAligned(T value, size_t alignment) { +requires std::is_integral_v<T> +[[nodiscard]] constexpr bool IsAligned(T value, size_t alignment) { using U = typename std::make_unsigned_t<T>; const U mask = static_cast<U>(alignment - 1); return (value & mask) == 0; } template <typename T, typename U> -requires std::is_integral_v<T>[[nodiscard]] constexpr T DivideUp(T x, U y) { +requires std::is_integral_v<T> +[[nodiscard]] constexpr T DivideUp(T x, U y) { return (x + (y - 1)) / y; } diff --git a/src/common/common_funcs.h b/src/common/common_funcs.h index 53bd7da60..4c1e29de6 100644 --- a/src/common/common_funcs.h +++ b/src/common/common_funcs.h @@ -4,9 +4,8 @@ #pragma once -#include <algorithm> #include <array> -#include <string> +#include <iterator> #if !defined(ARCHITECTURE_x86_64) #include <cstdlib> // for exit @@ -49,16 +48,6 @@ __declspec(dllimport) void __stdcall DebugBreak(void); #endif // _MSC_VER ndef -// Generic function to get last error message. -// Call directly after the command or use the error num. -// This function might change the error code. -// Defined in misc.cpp. -[[nodiscard]] std::string GetLastErrorMsg(); - -// Like GetLastErrorMsg(), but passing an explicit error code. -// Defined in misc.cpp. -[[nodiscard]] std::string NativeErrorToString(int e); - #define DECLARE_ENUM_FLAG_OPERATORS(type) \ [[nodiscard]] constexpr type operator|(type a, type b) noexcept { \ using T = std::underlying_type_t<type>; \ @@ -72,6 +61,14 @@ __declspec(dllimport) void __stdcall DebugBreak(void); using T = std::underlying_type_t<type>; \ return static_cast<type>(static_cast<T>(a) ^ static_cast<T>(b)); \ } \ + [[nodiscard]] constexpr type operator<<(type a, type b) noexcept { \ + using T = std::underlying_type_t<type>; \ + return static_cast<type>(static_cast<T>(a) << static_cast<T>(b)); \ + } \ + [[nodiscard]] constexpr type operator>>(type a, type b) noexcept { \ + using T = std::underlying_type_t<type>; \ + return static_cast<type>(static_cast<T>(a) >> static_cast<T>(b)); \ + } \ constexpr type& operator|=(type& a, type b) noexcept { \ a = a | b; \ return a; \ @@ -84,6 +81,14 @@ __declspec(dllimport) void __stdcall DebugBreak(void); a = a ^ b; \ return a; \ } \ + constexpr type& operator<<=(type& a, type b) noexcept { \ + a = a << b; \ + return a; \ + } \ + constexpr type& operator>>=(type& a, type b) noexcept { \ + a = a >> b; \ + return a; \ + } \ [[nodiscard]] constexpr type operator~(type key) noexcept { \ using T = std::underlying_type_t<type>; \ return static_cast<type>(~static_cast<T>(key)); \ diff --git a/src/common/div_ceil.h b/src/common/div_ceil.h index 95e1489a9..e1db35464 100644 --- a/src/common/div_ceil.h +++ b/src/common/div_ceil.h @@ -11,15 +11,15 @@ namespace Common { /// Ceiled integer division. template <typename N, typename D> -requires std::is_integral_v<N>&& std::is_unsigned_v<D>[[nodiscard]] constexpr N DivCeil(N number, - D divisor) { +requires std::is_integral_v<N> && std::is_unsigned_v<D> +[[nodiscard]] constexpr N DivCeil(N number, D divisor) { return static_cast<N>((static_cast<D>(number) + divisor - 1) / divisor); } /// Ceiled integer division with logarithmic divisor in base 2 template <typename N, typename D> -requires std::is_integral_v<N>&& std::is_unsigned_v<D>[[nodiscard]] constexpr N DivCeilLog2( - N value, D alignment_log2) { +requires std::is_integral_v<N> && std::is_unsigned_v<D> +[[nodiscard]] constexpr N DivCeilLog2(N value, D alignment_log2) { return static_cast<N>((static_cast<D>(value) + (D(1) << alignment_log2) - 1) >> alignment_log2); } diff --git a/src/common/misc.cpp b/src/common/error.cpp index 495385b9e..d4455e310 100644 --- a/src/common/misc.cpp +++ b/src/common/error.cpp @@ -10,7 +10,9 @@ #include <cstring> #endif -#include "common/common_funcs.h" +#include "common/error.h" + +namespace Common { std::string NativeErrorToString(int e) { #ifdef _WIN32 @@ -50,3 +52,5 @@ std::string GetLastErrorMsg() { return NativeErrorToString(errno); #endif } + +} // namespace Common diff --git a/src/common/error.h b/src/common/error.h new file mode 100644 index 000000000..e084d4b0f --- /dev/null +++ b/src/common/error.h @@ -0,0 +1,21 @@ +// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <string> + +namespace Common { + +// Generic function to get last error message. +// Call directly after the command or use the error num. +// This function might change the error code. +// Defined in error.cpp. +[[nodiscard]] std::string GetLastErrorMsg(); + +// Like GetLastErrorMsg(), but passing an explicit error code. +// Defined in error.cpp. +[[nodiscard]] std::string NativeErrorToString(int e); + +} // namespace Common diff --git a/src/common/fs/fs_paths.h b/src/common/fs/fs_paths.h index b32614797..5d447f108 100644 --- a/src/common/fs/fs_paths.h +++ b/src/common/fs/fs_paths.h @@ -21,6 +21,7 @@ #define SCREENSHOTS_DIR "screenshots" #define SDMC_DIR "sdmc" #define SHADER_DIR "shader" +#define TAS_DIR "tas" // yuzu-specific files diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp index 6cdd14f13..43b79bd6d 100644 --- a/src/common/fs/path_util.cpp +++ b/src/common/fs/path_util.cpp @@ -116,6 +116,7 @@ private: GenerateYuzuPath(YuzuPath::ScreenshotsDir, yuzu_path / SCREENSHOTS_DIR); GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR); GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR); + GenerateYuzuPath(YuzuPath::TASDir, yuzu_path / TAS_DIR); } ~PathManagerImpl() = default; diff --git a/src/common/fs/path_util.h b/src/common/fs/path_util.h index f956ac9a2..0a9e3a145 100644 --- a/src/common/fs/path_util.h +++ b/src/common/fs/path_util.h @@ -23,6 +23,7 @@ enum class YuzuPath { ScreenshotsDir, // Where yuzu screenshots are stored. SDMCDir, // Where the emulated SDMC is stored. ShaderDir, // Where shaders are stored. + TASDir, // Where TAS scripts are stored. }; /** diff --git a/src/common/host_memory.cpp b/src/common/host_memory.cpp index 6661244cf..b44a44949 100644 --- a/src/common/host_memory.cpp +++ b/src/common/host_memory.cpp @@ -314,8 +314,8 @@ private: } void UntrackPlaceholder(boost::icl::separate_interval_set<size_t>::iterator it) { - placeholders.erase(it); placeholder_host_pointers.erase(it->lower()); + placeholders.erase(it); } /// Return true when a given memory region is a "nieche" and the placeholders don't have to be diff --git a/src/common/intrusive_red_black_tree.h b/src/common/intrusive_red_black_tree.h index 1f696fe80..3173cc449 100644 --- a/src/common/intrusive_red_black_tree.h +++ b/src/common/intrusive_red_black_tree.h @@ -235,20 +235,19 @@ public: template <typename T> concept HasLightCompareType = requires { - { std::is_same<typename T::LightCompareType, void>::value } - ->std::convertible_to<bool>; + { std::is_same<typename T::LightCompareType, void>::value } -> std::convertible_to<bool>; }; namespace impl { -template <typename T, typename Default> -consteval auto* GetLightCompareType() { - if constexpr (HasLightCompareType<T>) { - return static_cast<typename T::LightCompareType*>(nullptr); - } else { - return static_cast<Default*>(nullptr); + template <typename T, typename Default> + consteval auto* GetLightCompareType() { + if constexpr (HasLightCompareType<T>) { + return static_cast<typename T::LightCompareType*>(nullptr); + } else { + return static_cast<Default*>(nullptr); + } } -} } // namespace impl diff --git a/src/common/settings.cpp b/src/common/settings.cpp index fd3b639cd..9dd5e3efb 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -54,14 +54,13 @@ void LogSettings() { log_setting("Renderer_GPUAccuracyLevel", values.gpu_accuracy.GetValue()); log_setting("Renderer_UseAsynchronousGpuEmulation", values.use_asynchronous_gpu_emulation.GetValue()); - log_setting("Renderer_UseNvdecEmulation", values.use_nvdec_emulation.GetValue()); + log_setting("Renderer_NvdecEmulation", values.nvdec_emulation.GetValue()); log_setting("Renderer_AccelerateASTC", values.accelerate_astc.GetValue()); log_setting("Renderer_UseVsync", values.use_vsync.GetValue()); log_setting("Renderer_ShaderBackend", values.shader_backend.GetValue()); log_setting("Renderer_UseAsynchronousShaders", values.use_asynchronous_shaders.GetValue()); log_setting("Renderer_AnisotropicFilteringLevel", values.max_anisotropy.GetValue()); log_setting("Audio_OutputEngine", values.sink_id.GetValue()); - log_setting("Audio_EnableAudioStretching", values.enable_audio_stretching.GetValue()); log_setting("Audio_OutputDevice", values.audio_device_id.GetValue()); log_setting("DataStorage_UseVirtualSd", values.use_virtual_sd.GetValue()); log_path("DataStorage_CacheDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir)); @@ -70,8 +69,9 @@ void LogSettings() { log_path("DataStorage_NANDDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir)); log_path("DataStorage_SDMCDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir)); log_setting("Debugging_ProgramArgs", values.program_args.GetValue()); - log_setting("Services_BCATBackend", values.bcat_backend.GetValue()); - log_setting("Services_BCATBoxcatLocal", values.bcat_boxcat_local.GetValue()); + log_setting("Input_EnableMotion", values.motion_enabled.GetValue()); + log_setting("Input_EnableVibration", values.vibration_enabled.GetValue()); + log_setting("Input_EnableRawInput", values.enable_raw_input.GetValue()); } bool IsConfiguringGlobal() { @@ -112,7 +112,6 @@ void RestoreGlobalState(bool is_powered_on) { } // Audio - values.enable_audio_stretching.SetGlobal(true); values.volume.SetGlobal(true); // Core @@ -136,7 +135,7 @@ void RestoreGlobalState(bool is_powered_on) { values.use_disk_shader_cache.SetGlobal(true); values.gpu_accuracy.SetGlobal(true); values.use_asynchronous_gpu_emulation.SetGlobal(true); - values.use_nvdec_emulation.SetGlobal(true); + values.nvdec_emulation.SetGlobal(true); values.accelerate_astc.SetGlobal(true); values.use_vsync.SetGlobal(true); values.shader_backend.SetGlobal(true); diff --git a/src/common/settings.h b/src/common/settings.h index ec4d381e8..402339443 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -16,7 +16,6 @@ #include "common/common_types.h" #include "common/settings_input.h" -#include "input_common/udp/client.h" namespace Settings { @@ -48,6 +47,12 @@ enum class FullscreenMode : u32 { Exclusive = 1, }; +enum class NvdecEmulation : u32 { + Off = 0, + CPU = 1, + GPU = 2, +}; + /** The BasicSetting class is a simple resource manager. It defines a label and default value * alongside the actual value of the setting for simpler and less-error prone use with frontend * configurations. Setting a default value and label is required, though subclasses may deviate from @@ -409,7 +414,6 @@ struct Values { BasicSetting<std::string> audio_device_id{"auto", "output_device"}; BasicSetting<std::string> sink_id{"auto", "output_engine"}; BasicSetting<bool> audio_muted{false, "audio_muted"}; - Setting<bool> enable_audio_stretching{true, "enable_audio_stretching"}; RangedSetting<u8> volume{100, 0, 100, "volume"}; // Core @@ -466,7 +470,7 @@ struct Values { RangedSetting<GPUAccuracy> gpu_accuracy{GPUAccuracy::High, GPUAccuracy::Normal, GPUAccuracy::Extreme, "gpu_accuracy"}; Setting<bool> use_asynchronous_gpu_emulation{true, "use_asynchronous_gpu_emulation"}; - Setting<bool> use_nvdec_emulation{true, "use_nvdec_emulation"}; + Setting<NvdecEmulation> nvdec_emulation{NvdecEmulation::GPU, "nvdec_emulation"}; Setting<bool> accelerate_astc{true, "accelerate_astc"}; Setting<bool> use_vsync{true, "use_vsync"}; BasicRangedSetting<u16> fps_cap{1000, 1, 1000, "fps_cap"}; @@ -498,14 +502,20 @@ struct Values { Setting<bool> use_docked_mode{true, "use_docked_mode"}; + BasicSetting<bool> enable_raw_input{false, "enable_raw_input"}; + Setting<bool> vibration_enabled{true, "vibration_enabled"}; Setting<bool> enable_accurate_vibrations{false, "enable_accurate_vibrations"}; Setting<bool> motion_enabled{true, "motion_enabled"}; BasicSetting<std::string> motion_device{"engine:motion_emu,update_period:100,sensitivity:0.01", "motion_device"}; - BasicSetting<std::string> udp_input_servers{InputCommon::CemuhookUDP::DEFAULT_SRV, - "udp_input_servers"}; + BasicSetting<std::string> udp_input_servers{"127.0.0.1:26760", "udp_input_servers"}; + + BasicSetting<bool> pause_tas_on_load{true, "pause_tas_on_load"}; + BasicSetting<bool> tas_enable{false, "tas_enable"}; + BasicSetting<bool> tas_loop{false, "tas_loop"}; + BasicSetting<bool> tas_swap_controllers{true, "tas_swap_controllers"}; BasicSetting<bool> mouse_panning{false, "mouse_panning"}; BasicRangedSetting<u8> mouse_panning_sensitivity{10, 1, 100, "mouse_panning_sensitivity"}; @@ -558,8 +568,6 @@ struct Values { BasicSetting<bool> use_dev_keys{false, "use_dev_keys"}; // Network - BasicSetting<std::string> bcat_backend{"none", "bcat_backend"}; - BasicSetting<bool> bcat_boxcat_local{false, "bcat_boxcat_local"}; BasicSetting<std::string> network_interface{std::string(), "network_interface"}; // WebService diff --git a/src/common/thread.cpp b/src/common/thread.cpp index d2c1ac60d..946a1114d 100644 --- a/src/common/thread.cpp +++ b/src/common/thread.cpp @@ -2,7 +2,9 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "common/common_funcs.h" +#include <string> + +#include "common/error.h" #include "common/logging/log.h" #include "common/thread.h" #ifdef __APPLE__ @@ -21,8 +23,6 @@ #include <unistd.h> #endif -#include <string> - #ifdef __FreeBSD__ #define cpu_set_t cpuset_t #endif diff --git a/src/common/threadsafe_queue.h b/src/common/threadsafe_queue.h index 8430b9778..2c8c2b90e 100644 --- a/src/common/threadsafe_queue.h +++ b/src/common/threadsafe_queue.h @@ -14,7 +14,7 @@ #include <utility> namespace Common { -template <typename T> +template <typename T, bool with_stop_token = false> class SPSCQueue { public: SPSCQueue() { @@ -84,7 +84,7 @@ public: void Wait() { if (Empty()) { std::unique_lock lock{cv_mutex}; - cv.wait(lock, [this]() { return !Empty(); }); + cv.wait(lock, [this] { return !Empty(); }); } } @@ -95,6 +95,19 @@ public: return t; } + T PopWait(std::stop_token stop_token) { + if (Empty()) { + std::unique_lock lock{cv_mutex}; + cv.wait(lock, stop_token, [this] { return !Empty(); }); + } + if (stop_token.stop_requested()) { + return T{}; + } + T t; + Pop(t); + return t; + } + // not thread-safe void Clear() { size.store(0); @@ -123,13 +136,13 @@ private: ElementPtr* read_ptr; std::atomic_size_t size{0}; std::mutex cv_mutex; - std::condition_variable cv; + std::conditional_t<with_stop_token, std::condition_variable_any, std::condition_variable> cv; }; // a simple thread-safe, // single reader, multiple writer queue -template <typename T> +template <typename T, bool with_stop_token = false> class MPSCQueue { public: [[nodiscard]] std::size_t Size() const { @@ -166,13 +179,17 @@ public: return spsc_queue.PopWait(); } + T PopWait(std::stop_token stop_token) { + return spsc_queue.PopWait(stop_token); + } + // not thread-safe void Clear() { spsc_queue.Clear(); } private: - SPSCQueue<T> spsc_queue; + SPSCQueue<T, with_stop_token> spsc_queue; std::mutex write_lock; }; } // namespace Common diff --git a/src/common/uuid.h b/src/common/uuid.h index 2353179d8..8ea01f8da 100644 --- a/src/common/uuid.h +++ b/src/common/uuid.h @@ -58,6 +58,13 @@ struct UUID { uuid = INVALID_UUID; } + [[nodiscard]] constexpr bool IsInvalid() const { + return uuid == INVALID_UUID; + } + [[nodiscard]] constexpr bool IsValid() const { + return !IsInvalid(); + } + // TODO(ogniK): Properly generate a Nintendo ID [[nodiscard]] constexpr u64 GetNintendoID() const { return uuid[0]; diff --git a/src/common/vector_math.h b/src/common/vector_math.h index 22dba3c2d..ba7c363c1 100644 --- a/src/common/vector_math.h +++ b/src/common/vector_math.h @@ -667,8 +667,8 @@ template <typename T> // linear interpolation via float: 0.0=begin, 1.0=end template <typename X> -[[nodiscard]] constexpr decltype(X{} * float{} + X{} * float{}) Lerp(const X& begin, const X& end, - const float t) { +[[nodiscard]] constexpr decltype(X{} * float{} + X{} * float{}) + Lerp(const X& begin, const X& end, const float t) { return begin * (1.f - t) + end * t; } diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 87d47e2e5..aa3b26628 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -106,8 +106,6 @@ add_library(core STATIC file_sys/vfs_concat.h file_sys/vfs_layered.cpp file_sys/vfs_layered.h - file_sys/vfs_libzip.cpp - file_sys/vfs_libzip.h file_sys/vfs_offset.cpp file_sys/vfs_offset.h file_sys/vfs_real.cpp @@ -263,6 +261,8 @@ add_library(core STATIC hle/service/acc/acc_u0.h hle/service/acc/acc_u1.cpp hle/service/acc/acc_u1.h + hle/service/acc/async_context.cpp + hle/service/acc/async_context.h hle/service/acc/errors.h hle/service/acc/profile_manager.cpp hle/service/acc/profile_manager.h @@ -651,13 +651,6 @@ add_library(core STATIC tools/freezer.h ) -if (YUZU_ENABLE_BOXCAT) - target_sources(core PRIVATE - hle/service/bcat/backend/boxcat.cpp - hle/service/bcat/backend/boxcat.h - ) -endif() - if (MSVC) target_compile_options(core PRIVATE /we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data @@ -688,12 +681,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 zip) - -if (YUZU_ENABLE_BOXCAT) - target_compile_definitions(core PRIVATE -DYUZU_ENABLE_BOXCAT) - target_link_libraries(core PRIVATE httplib nlohmann_json::nlohmann_json) -endif() +target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::Opus) if (ENABLE_WEB_SERVICE) target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE) diff --git a/src/core/core.cpp b/src/core/core.cpp index ba4629993..bb268a319 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -83,6 +83,12 @@ FileSys::StorageId GetStorageIdForFrontendSlot( } } +void KProcessDeleter(Kernel::KProcess* process) { + process->Destroy(); +} + +using KProcessPtr = std::unique_ptr<Kernel::KProcess, decltype(&KProcessDeleter)>; + } // Anonymous namespace FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs, @@ -233,8 +239,8 @@ struct System::Impl { } telemetry_session->AddInitialInfo(*app_loader, fs_controller, *content_provider); - auto main_process = Kernel::KProcess::Create(system.Kernel()); - ASSERT(Kernel::KProcess::Initialize(main_process, system, "main", + main_process = KProcessPtr{Kernel::KProcess::Create(system.Kernel()), KProcessDeleter}; + ASSERT(Kernel::KProcess::Initialize(main_process.get(), system, "main", Kernel::KProcess::ProcessType::Userland) .IsSuccess()); main_process->Open(); @@ -247,7 +253,7 @@ struct System::Impl { static_cast<u32>(load_result)); } AddGlueRegistrationForProcess(*app_loader, *main_process); - kernel.MakeCurrentProcess(main_process); + kernel.MakeCurrentProcess(main_process.get()); kernel.InitializeCores(); // Initialize cheat engine @@ -299,10 +305,6 @@ struct System::Impl { is_powered_on = false; exit_lock = false; - if (gpu_core) { - gpu_core->ShutDown(); - } - services.reset(); service_manager.reset(); cheat_engine.reset(); @@ -311,11 +313,13 @@ struct System::Impl { time_manager.Shutdown(); core_timing.Shutdown(); app_loader.reset(); - gpu_core.reset(); perf_stats.reset(); + gpu_core.reset(); kernel.Shutdown(); memory.Reset(); applet_manager.ClearAll(); + // TODO: The main process should be freed based on KAutoObject ref counting. + main_process.reset(); LOG_DEBUG(Core, "Shutdown OK"); } @@ -374,6 +378,7 @@ struct System::Impl { std::unique_ptr<Tegra::GPU> gpu_core; std::unique_ptr<Hardware::InterruptManager> interrupt_manager; std::unique_ptr<Core::DeviceMemory> device_memory; + KProcessPtr main_process{nullptr, KProcessDeleter}; Core::Memory::Memory memory; CpuManager cpu_manager; std::atomic_bool is_powered_on{}; @@ -416,6 +421,7 @@ struct System::Impl { bool is_async_gpu{}; ExecuteProgramCallback execute_program_callback; + ExitCallback exit_callback; std::array<u64, Core::Hardware::NUM_CPU_CORES> dynarmic_ticks{}; std::array<MicroProfileToken, Core::Hardware::NUM_CPU_CORES> microprofile_dynarmic{}; @@ -793,6 +799,18 @@ void System::ExecuteProgram(std::size_t program_index) { } } +void System::RegisterExitCallback(ExitCallback&& callback) { + impl->exit_callback = std::move(callback); +} + +void System::Exit() { + if (impl->exit_callback) { + impl->exit_callback(); + } else { + LOG_CRITICAL(Core, "exit_callback must be initialized by the frontend"); + } +} + void System::ApplySettings() { if (IsPoweredOn()) { Renderer().RefreshBaseSettings(); diff --git a/src/core/core.h b/src/core/core.h index 715ab88e7..a796472b2 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -387,6 +387,18 @@ public: */ void ExecuteProgram(std::size_t program_index); + /// Type used for the frontend to designate a callback for System to exit the application. + using ExitCallback = std::function<void()>; + + /** + * Registers a callback from the frontend for System to exit the application. + * @param callback Callback from the frontend to exit the application. + */ + void RegisterExitCallback(ExitCallback&& callback); + + /// Instructs the frontend to exit the application. + void Exit(); + /// Applies any changes to settings to this core instance. void ApplySettings(); diff --git a/src/core/file_sys/kernel_executable.h b/src/core/file_sys/kernel_executable.h index 044c554d3..79ca82f8b 100644 --- a/src/core/file_sys/kernel_executable.h +++ b/src/core/file_sys/kernel_executable.h @@ -5,6 +5,7 @@ #pragma once #include <array> +#include <string> #include <vector> #include "common/common_funcs.h" diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp index 01ae1a567..35a53d36c 100644 --- a/src/core/file_sys/program_metadata.cpp +++ b/src/core/file_sys/program_metadata.cpp @@ -77,7 +77,7 @@ void ProgramMetadata::LoadManual(bool is_64_bit, ProgramAddressSpaceType address aci_header.title_id = title_id; aci_file_access.permissions = filesystem_permissions; npdm_header.system_resource_size = system_resource_size; - aci_kernel_capabilities = std ::move(capabilities); + aci_kernel_capabilities = std::move(capabilities); } bool ProgramMetadata::Is64BitProgram() const { diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp index 368419eca..f5ad10b15 100644 --- a/src/core/file_sys/vfs.cpp +++ b/src/core/file_sys/vfs.cpp @@ -273,6 +273,10 @@ VirtualFile VfsDirectory::GetFile(std::string_view name) const { return iter == files.end() ? nullptr : *iter; } +FileTimeStampRaw VfsDirectory::GetFileTimeStamp([[maybe_unused]] std::string_view path) const { + return {}; +} + VirtualDir VfsDirectory::GetSubdirectory(std::string_view name) const { const auto& subs = GetSubdirectories(); const auto iter = std::find_if(subs.begin(), subs.end(), diff --git a/src/core/file_sys/vfs.h b/src/core/file_sys/vfs.h index afd64e95c..ff6935da6 100644 --- a/src/core/file_sys/vfs.h +++ b/src/core/file_sys/vfs.h @@ -199,6 +199,9 @@ public: // file with name. virtual VirtualFile GetFile(std::string_view name) const; + // Returns a struct containing the file's timestamp. + virtual FileTimeStampRaw GetFileTimeStamp(std::string_view path) const; + // Returns a vector containing all of the subdirectories in this directory. virtual std::vector<VirtualDir> GetSubdirectories() const = 0; // Returns the directory with name matching name. Returns nullptr if directory dosen't have a diff --git a/src/core/file_sys/vfs_libzip.cpp b/src/core/file_sys/vfs_libzip.cpp deleted file mode 100644 index 00e256779..000000000 --- a/src/core/file_sys/vfs_libzip.cpp +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2019 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <string> - -#ifdef __GNUC__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wshadow" -#endif -#include <zip.h> -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif - -#include "common/fs/path_util.h" -#include "core/file_sys/vfs.h" -#include "core/file_sys/vfs_libzip.h" -#include "core/file_sys/vfs_vector.h" - -namespace FileSys { - -VirtualDir ExtractZIP(VirtualFile file) { - zip_error_t error{}; - - const auto data = file->ReadAllBytes(); - std::unique_ptr<zip_source_t, decltype(&zip_source_close)> src{ - zip_source_buffer_create(data.data(), data.size(), 0, &error), zip_source_close}; - if (src == nullptr) - return nullptr; - - std::unique_ptr<zip_t, decltype(&zip_close)> zip{zip_open_from_source(src.get(), 0, &error), - zip_close}; - if (zip == nullptr) - return nullptr; - - std::shared_ptr<VectorVfsDirectory> out = std::make_shared<VectorVfsDirectory>(); - - const auto num_entries = static_cast<std::size_t>(zip_get_num_entries(zip.get(), 0)); - - zip_stat_t stat{}; - zip_stat_init(&stat); - - for (std::size_t i = 0; i < num_entries; ++i) { - const auto stat_res = zip_stat_index(zip.get(), i, 0, &stat); - if (stat_res == -1) - return nullptr; - - const std::string name(stat.name); - if (name.empty()) - continue; - - if (name.back() != '/') { - std::unique_ptr<zip_file_t, decltype(&zip_fclose)> file2{ - zip_fopen_index(zip.get(), i, 0), zip_fclose}; - - std::vector<u8> buf(stat.size); - if (zip_fread(file2.get(), buf.data(), buf.size()) != s64(buf.size())) - return nullptr; - - const auto parts = Common::FS::SplitPathComponents(stat.name); - const auto new_file = std::make_shared<VectorVfsFile>(buf, parts.back()); - - std::shared_ptr<VectorVfsDirectory> dtrv = out; - for (std::size_t j = 0; j < parts.size() - 1; ++j) { - if (dtrv == nullptr) - return nullptr; - const auto subdir = dtrv->GetSubdirectory(parts[j]); - if (subdir == nullptr) { - const auto temp = std::make_shared<VectorVfsDirectory>( - std::vector<VirtualFile>{}, std::vector<VirtualDir>{}, parts[j]); - dtrv->AddDirectory(temp); - dtrv = temp; - } else { - dtrv = std::dynamic_pointer_cast<VectorVfsDirectory>(subdir); - } - } - - if (dtrv == nullptr) - return nullptr; - dtrv->AddFile(new_file); - } - } - - return out; -} - -} // namespace FileSys diff --git a/src/core/file_sys/vfs_libzip.h b/src/core/file_sys/vfs_libzip.h deleted file mode 100644 index f68af576a..000000000 --- a/src/core/file_sys/vfs_libzip.h +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2019 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include "core/file_sys/vfs_types.h" - -namespace FileSys { - -VirtualDir ExtractZIP(VirtualFile zip); - -} // namespace FileSys diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp index 3dad54f49..f4073b76a 100644 --- a/src/core/file_sys/vfs_real.cpp +++ b/src/core/file_sys/vfs_real.cpp @@ -13,6 +13,13 @@ #include "common/logging/log.h" #include "core/file_sys/vfs_real.h" +// For FileTimeStampRaw +#include <sys/stat.h> + +#ifdef _MSC_VER +#define stat _stat64 +#endif + namespace FileSys { namespace FS = Common::FS; @@ -392,6 +399,28 @@ std::vector<VirtualFile> RealVfsDirectory::GetFiles() const { return IterateEntries<RealVfsFile, VfsFile>(); } +FileTimeStampRaw RealVfsDirectory::GetFileTimeStamp(std::string_view path_) const { + const auto full_path = FS::SanitizePath(path + '/' + std::string(path_)); + const auto fs_path = std::filesystem::path{FS::ToU8String(full_path)}; + struct stat file_status; + +#ifdef _WIN32 + const auto stat_result = _wstat64(fs_path.c_str(), &file_status); +#else + const auto stat_result = stat(fs_path.c_str(), &file_status); +#endif + + if (stat_result != 0) { + return {}; + } + + return { + .created{static_cast<u64>(file_status.st_ctime)}, + .accessed{static_cast<u64>(file_status.st_atime)}, + .modified{static_cast<u64>(file_status.st_mtime)}, + }; +} + std::vector<VirtualDir> RealVfsDirectory::GetSubdirectories() const { return IterateEntries<RealVfsDirectory, VfsDirectory>(); } diff --git a/src/core/file_sys/vfs_real.h b/src/core/file_sys/vfs_real.h index e4d1bba79..746e624cb 100644 --- a/src/core/file_sys/vfs_real.h +++ b/src/core/file_sys/vfs_real.h @@ -86,6 +86,7 @@ public: VirtualDir CreateDirectoryRelative(std::string_view relative_path) override; bool DeleteSubdirectoryRecursive(std::string_view name) override; std::vector<VirtualFile> GetFiles() const override; + FileTimeStampRaw GetFileTimeStamp(std::string_view path) const override; std::vector<VirtualDir> GetSubdirectories() const override; bool IsWritable() const override; bool IsReadable() const override; diff --git a/src/core/file_sys/vfs_types.h b/src/core/file_sys/vfs_types.h index 6215ed7af..ed0724717 100644 --- a/src/core/file_sys/vfs_types.h +++ b/src/core/file_sys/vfs_types.h @@ -6,6 +6,8 @@ #include <memory> +#include "common/common_types.h" + namespace FileSys { class VfsDirectory; @@ -18,4 +20,11 @@ using VirtualDir = std::shared_ptr<VfsDirectory>; using VirtualFile = std::shared_ptr<VfsFile>; using VirtualFilesystem = std::shared_ptr<VfsFilesystem>; +struct FileTimeStampRaw { + u64 created{}; + u64 accessed{}; + u64 modified{}; + u64 padding{}; +}; + } // namespace FileSys diff --git a/src/core/frontend/applets/profile_select.cpp b/src/core/frontend/applets/profile_select.cpp index 4c58c310f..3e4f90be2 100644 --- a/src/core/frontend/applets/profile_select.cpp +++ b/src/core/frontend/applets/profile_select.cpp @@ -13,7 +13,8 @@ ProfileSelectApplet::~ProfileSelectApplet() = default; void DefaultProfileSelectApplet::SelectProfile( std::function<void(std::optional<Common::UUID>)> callback) const { Service::Account::ProfileManager manager; - callback(manager.GetUser(Settings::values.current_user.GetValue()).value_or(Common::UUID{})); + callback(manager.GetUser(Settings::values.current_user.GetValue()) + .value_or(Common::UUID{Common::INVALID_UUID})); LOG_INFO(Service_ACC, "called, selecting current user instead of prompting..."); } diff --git a/src/core/hle/api_version.h b/src/core/hle/api_version.h index 43d5670a9..626e30753 100644 --- a/src/core/hle/api_version.h +++ b/src/core/hle/api_version.h @@ -28,13 +28,20 @@ constexpr char DISPLAY_TITLE[] = "NintendoSDK Firmware for NX 12.1.0-1.0"; // Atmosphere version constants. -constexpr u8 ATMOSPHERE_RELEASE_VERSION_MAJOR = 0; -constexpr u8 ATMOSPHERE_RELEASE_VERSION_MINOR = 19; -constexpr u8 ATMOSPHERE_RELEASE_VERSION_MICRO = 5; +constexpr u8 ATMOSPHERE_RELEASE_VERSION_MAJOR = 1; +constexpr u8 ATMOSPHERE_RELEASE_VERSION_MINOR = 0; +constexpr u8 ATMOSPHERE_RELEASE_VERSION_MICRO = 0; + +constexpr u32 AtmosphereTargetFirmwareWithRevision(u8 major, u8 minor, u8 micro, u8 rev) { + return u32{major} << 24 | u32{minor} << 16 | u32{micro} << 8 | u32{rev}; +} + +constexpr u32 AtmosphereTargetFirmware(u8 major, u8 minor, u8 micro) { + return AtmosphereTargetFirmwareWithRevision(major, minor, micro, 0); +} constexpr u32 GetTargetFirmware() { - return u32{HOS_VERSION_MAJOR} << 24 | u32{HOS_VERSION_MINOR} << 16 | - u32{HOS_VERSION_MICRO} << 8 | 0U; + return AtmosphereTargetFirmware(HOS_VERSION_MAJOR, HOS_VERSION_MINOR, HOS_VERSION_MICRO); } } // namespace HLE::ApiVersion diff --git a/src/core/hle/kernel/k_handle_table.cpp b/src/core/hle/kernel/k_handle_table.cpp index 6a420d5b0..44d13169f 100644 --- a/src/core/hle/kernel/k_handle_table.cpp +++ b/src/core/hle/kernel/k_handle_table.cpp @@ -7,7 +7,7 @@ namespace Kernel { KHandleTable::KHandleTable(KernelCore& kernel_) : kernel{kernel_} {} -KHandleTable ::~KHandleTable() = default; +KHandleTable::~KHandleTable() = default; ResultCode KHandleTable::Finalize() { // Get the table and clear our record of it. diff --git a/src/core/hle/kernel/k_priority_queue.h b/src/core/hle/kernel/k_priority_queue.h index 4aa669d95..f4d71ad7e 100644 --- a/src/core/hle/kernel/k_priority_queue.h +++ b/src/core/hle/kernel/k_priority_queue.h @@ -22,12 +22,10 @@ class KThread; template <typename T> concept KPriorityQueueAffinityMask = !std::is_reference_v<T> && requires(T & t) { - { t.GetAffinityMask() } - ->Common::ConvertibleTo<u64>; + { t.GetAffinityMask() } -> Common::ConvertibleTo<u64>; {t.SetAffinityMask(0)}; - { t.GetAffinity(0) } - ->std::same_as<bool>; + { t.GetAffinity(0) } -> std::same_as<bool>; {t.SetAffinity(0, false)}; {t.SetAll()}; }; @@ -38,25 +36,20 @@ concept KPriorityQueueMember = !std::is_reference_v<T> && requires(T & t) { {(typename T::QueueEntry()).Initialize()}; {(typename T::QueueEntry()).SetPrev(std::addressof(t))}; {(typename T::QueueEntry()).SetNext(std::addressof(t))}; - { (typename T::QueueEntry()).GetNext() } - ->std::same_as<T*>; - { (typename T::QueueEntry()).GetPrev() } - ->std::same_as<T*>; - { t.GetPriorityQueueEntry(0) } - ->std::same_as<typename T::QueueEntry&>; + { (typename T::QueueEntry()).GetNext() } -> std::same_as<T*>; + { (typename T::QueueEntry()).GetPrev() } -> std::same_as<T*>; + { t.GetPriorityQueueEntry(0) } -> std::same_as<typename T::QueueEntry&>; {t.GetAffinityMask()}; - { std::remove_cvref_t<decltype(t.GetAffinityMask())>() } - ->KPriorityQueueAffinityMask; + { std::remove_cvref_t<decltype(t.GetAffinityMask())>() } -> KPriorityQueueAffinityMask; - { t.GetActiveCore() } - ->Common::ConvertibleTo<s32>; - { t.GetPriority() } - ->Common::ConvertibleTo<s32>; + { t.GetActiveCore() } -> Common::ConvertibleTo<s32>; + { t.GetPriority() } -> Common::ConvertibleTo<s32>; }; template <typename Member, size_t NumCores_, int LowestPriority, int HighestPriority> -requires KPriorityQueueMember<Member> class KPriorityQueue { +requires KPriorityQueueMember<Member> +class KPriorityQueue { public: using AffinityMaskType = std::remove_cv_t< std::remove_reference_t<decltype(std::declval<Member>().GetAffinityMask())>>; diff --git a/src/core/hle/kernel/k_scheduler.h b/src/core/hle/kernel/k_scheduler.h index 12cfae919..c8ccc1ae4 100644 --- a/src/core/hle/kernel/k_scheduler.h +++ b/src/core/hle/kernel/k_scheduler.h @@ -197,7 +197,7 @@ private: class [[nodiscard]] KScopedSchedulerLock : KScopedLock<GlobalSchedulerContext::LockType> { public: - explicit KScopedSchedulerLock(KernelCore & kernel); + explicit KScopedSchedulerLock(KernelCore& kernel); ~KScopedSchedulerLock(); }; diff --git a/src/core/hle/kernel/k_scoped_lock.h b/src/core/hle/kernel/k_scoped_lock.h index 72c3b0252..4fb180fc6 100644 --- a/src/core/hle/kernel/k_scoped_lock.h +++ b/src/core/hle/kernel/k_scoped_lock.h @@ -13,19 +13,18 @@ namespace Kernel { template <typename T> concept KLockable = !std::is_reference_v<T> && requires(T & t) { - { t.Lock() } - ->std::same_as<void>; - { t.Unlock() } - ->std::same_as<void>; + { t.Lock() } -> std::same_as<void>; + { t.Unlock() } -> std::same_as<void>; }; template <typename T> -requires KLockable<T> class [[nodiscard]] KScopedLock { +requires KLockable<T> +class [[nodiscard]] KScopedLock { public: - explicit KScopedLock(T * l) : lock_ptr(l) { + explicit KScopedLock(T* l) : lock_ptr(l) { this->lock_ptr->Lock(); } - explicit KScopedLock(T & l) : KScopedLock(std::addressof(l)) {} + explicit KScopedLock(T& l) : KScopedLock(std::addressof(l)) {} ~KScopedLock() { this->lock_ptr->Unlock(); @@ -34,7 +33,7 @@ public: KScopedLock(const KScopedLock&) = delete; KScopedLock& operator=(const KScopedLock&) = delete; - KScopedLock(KScopedLock &&) = delete; + KScopedLock(KScopedLock&&) = delete; KScopedLock& operator=(KScopedLock&&) = delete; private: diff --git a/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h b/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h index a86af56dd..f6c75f2d9 100644 --- a/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h +++ b/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h @@ -17,7 +17,7 @@ namespace Kernel { class [[nodiscard]] KScopedSchedulerLockAndSleep { public: - explicit KScopedSchedulerLockAndSleep(KernelCore & kernel_, KThread * t, s64 timeout) + explicit KScopedSchedulerLockAndSleep(KernelCore& kernel_, KThread* t, s64 timeout) : kernel(kernel_), thread(t), timeout_tick(timeout) { // Lock the scheduler. kernel.GlobalSchedulerContext().scheduler_lock.Lock(); diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 3a6db0b1c..901d43da9 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -5,6 +5,7 @@ #pragma once #include <array> +#include <functional> #include <memory> #include <string> #include <unordered_map> diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp index 882fc1492..689b36056 100644 --- a/src/core/hle/service/acc/acc.cpp +++ b/src/core/hle/service/acc/acc.cpp @@ -23,6 +23,7 @@ #include "core/hle/service/acc/acc_su.h" #include "core/hle/service/acc/acc_u0.h" #include "core/hle/service/acc/acc_u1.h" +#include "core/hle/service/acc/async_context.h" #include "core/hle/service/acc/errors.h" #include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/glue/arp.h" @@ -454,22 +455,6 @@ public: : IProfileCommon{system_, "IProfileEditor", true, user_id_, profile_manager_} {} }; -class IAsyncContext final : public ServiceFramework<IAsyncContext> { -public: - explicit IAsyncContext(Core::System& system_) : ServiceFramework{system_, "IAsyncContext"} { - // clang-format off - static const FunctionInfo functions[] = { - {0, nullptr, "GetSystemEvent"}, - {1, nullptr, "Cancel"}, - {2, nullptr, "HasDone"}, - {3, nullptr, "GetResult"}, - }; - // clang-format on - - RegisterHandlers(functions); - } -}; - class ISessionObject final : public ServiceFramework<ISessionObject> { public: explicit ISessionObject(Core::System& system_, Common::UUID) @@ -504,16 +489,44 @@ public: } }; +class EnsureTokenIdCacheAsyncInterface final : public IAsyncContext { +public: + explicit EnsureTokenIdCacheAsyncInterface(Core::System& system_) : IAsyncContext{system_} { + MarkComplete(); + } + ~EnsureTokenIdCacheAsyncInterface() = default; + + void LoadIdTokenCache(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_ACC, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + +protected: + bool IsComplete() const override { + return true; + } + + void Cancel() override {} + + ResultCode GetResult() const override { + return ResultSuccess; + } +}; + class IManagerForApplication final : public ServiceFramework<IManagerForApplication> { public: explicit IManagerForApplication(Core::System& system_, Common::UUID user_id_) - : ServiceFramework{system_, "IManagerForApplication"}, user_id{user_id_} { + : ServiceFramework{system_, "IManagerForApplication"}, + ensure_token_id{std::make_shared<EnsureTokenIdCacheAsyncInterface>(system)}, + user_id{user_id_} { // clang-format off static const FunctionInfo functions[] = { {0, &IManagerForApplication::CheckAvailability, "CheckAvailability"}, {1, &IManagerForApplication::GetAccountId, "GetAccountId"}, - {2, nullptr, "EnsureIdTokenCacheAsync"}, - {3, nullptr, "LoadIdTokenCache"}, + {2, &IManagerForApplication::EnsureIdTokenCacheAsync, "EnsureIdTokenCacheAsync"}, + {3, &IManagerForApplication::LoadIdTokenCache, "LoadIdTokenCache"}, {130, &IManagerForApplication::GetNintendoAccountUserResourceCacheForApplication, "GetNintendoAccountUserResourceCacheForApplication"}, {150, nullptr, "CreateAuthorizationRequest"}, {160, &IManagerForApplication::StoreOpenContext, "StoreOpenContext"}, @@ -540,6 +553,20 @@ private: rb.PushRaw<u64>(user_id.GetNintendoID()); } + void EnsureIdTokenCacheAsync(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_ACC, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface(ensure_token_id); + } + + void LoadIdTokenCache(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_ACC, "(STUBBED) called"); + + ensure_token_id->LoadIdTokenCache(ctx); + } + void GetNintendoAccountUserResourceCacheForApplication(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_ACC, "(STUBBED) called"); @@ -562,6 +589,7 @@ private: rb.Push(ResultSuccess); } + std::shared_ptr<EnsureTokenIdCacheAsyncInterface> ensure_token_id{}; Common::UUID user_id{Common::INVALID_UUID}; }; @@ -901,8 +929,7 @@ void Module::Interface::TrySelectUserWithoutInteraction(Kernel::HLERequestContex } const auto user_list = profile_manager->GetAllUsers(); - if (std::all_of(user_list.begin(), user_list.end(), - [](const auto& user) { return user.uuid == Common::INVALID_UUID; })) { + if (std::ranges::all_of(user_list, [](const auto& user) { return user.IsInvalid(); })) { rb.Push(ResultUnknown); // TODO(ogniK): Find the correct error code rb.PushRaw<u128>(Common::INVALID_UUID); return; diff --git a/src/core/hle/service/acc/async_context.cpp b/src/core/hle/service/acc/async_context.cpp new file mode 100644 index 000000000..459323132 --- /dev/null +++ b/src/core/hle/service/acc/async_context.cpp @@ -0,0 +1,68 @@ +// Copyright 2021 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/core.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/service/acc/async_context.h" + +namespace Service::Account { +IAsyncContext::IAsyncContext(Core::System& system_) + : ServiceFramework{system_, "IAsyncContext"}, compeletion_event{system_.Kernel()} { + + Kernel::KAutoObject::Create(std::addressof(compeletion_event)); + compeletion_event.Initialize("IAsyncContext:CompletionEvent"); + + // clang-format off + static const FunctionInfo functions[] = { + {0, &IAsyncContext::GetSystemEvent, "GetSystemEvent"}, + {1, &IAsyncContext::Cancel, "Cancel"}, + {2, &IAsyncContext::HasDone, "HasDone"}, + {3, &IAsyncContext::GetResult, "GetResult"}, + }; + // clang-format on + + RegisterHandlers(functions); +} + +void IAsyncContext::GetSystemEvent(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_ACC, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(compeletion_event.GetReadableEvent()); +} + +void IAsyncContext::Cancel(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_ACC, "called"); + + Cancel(); + MarkComplete(); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + +void IAsyncContext::HasDone(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_ACC, "called"); + + is_complete.store(IsComplete()); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(is_complete.load()); +} + +void IAsyncContext::GetResult(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_ACC, "called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(GetResult()); +} + +void IAsyncContext::MarkComplete() { + is_complete.store(true); + compeletion_event.GetWritableEvent().Signal(); +} + +} // namespace Service::Account diff --git a/src/core/hle/service/acc/async_context.h b/src/core/hle/service/acc/async_context.h new file mode 100644 index 000000000..c694b4946 --- /dev/null +++ b/src/core/hle/service/acc/async_context.h @@ -0,0 +1,37 @@ +// Copyright 2021 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <atomic> +#include "core/hle/kernel/k_event.h" +#include "core/hle/service/service.h" + +namespace Core { +class System; +} + +namespace Service::Account { + +class IAsyncContext : public ServiceFramework<IAsyncContext> { +public: + explicit IAsyncContext(Core::System& system_); + + void GetSystemEvent(Kernel::HLERequestContext& ctx); + void Cancel(Kernel::HLERequestContext& ctx); + void HasDone(Kernel::HLERequestContext& ctx); + void GetResult(Kernel::HLERequestContext& ctx); + +protected: + virtual bool IsComplete() const = 0; + virtual void Cancel() = 0; + virtual ResultCode GetResult() const = 0; + + void MarkComplete(); + + std::atomic<bool> is_complete{false}; + Kernel::KEvent compeletion_event; +}; + +} // namespace Service::Account diff --git a/src/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp index 24a1c9157..568303ced 100644 --- a/src/core/hle/service/acc/profile_manager.cpp +++ b/src/core/hle/service/acc/profile_manager.cpp @@ -208,9 +208,10 @@ bool ProfileManager::UserExists(UUID uuid) const { } bool ProfileManager::UserExistsIndex(std::size_t index) const { - if (index >= MAX_USERS) + if (index >= MAX_USERS) { return false; - return profiles[index].user_uuid.uuid != Common::INVALID_UUID; + } + return profiles[index].user_uuid.IsValid(); } /// Opens a specific user @@ -304,7 +305,7 @@ bool ProfileManager::RemoveUser(UUID uuid) { bool ProfileManager::SetProfileBase(UUID uuid, const ProfileBase& profile_new) { const auto index = GetUserIndex(uuid); - if (!index || profile_new.user_uuid == UUID(Common::INVALID_UUID)) { + if (!index || profile_new.user_uuid.IsInvalid()) { return false; } @@ -346,7 +347,7 @@ void ProfileManager::ParseUserSaveFile() { } for (const auto& user : data.users) { - if (user.uuid == UUID(Common::INVALID_UUID)) { + if (user.uuid.IsInvalid()) { continue; } diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index a538f82e3..49e9787a4 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -275,12 +275,14 @@ ISelfController::ISelfController(Core::System& system_, NVFlinger::NVFlinger& nv {18, nullptr, "SetRequiresCaptureButtonShortPressedMessage"}, {19, &ISelfController::SetAlbumImageOrientation, "SetAlbumImageOrientation"}, {20, nullptr, "SetDesirableKeyboardLayout"}, + {21, nullptr, "GetScreenShotProgramId"}, {40, &ISelfController::CreateManagedDisplayLayer, "CreateManagedDisplayLayer"}, {41, nullptr, "IsSystemBufferSharingEnabled"}, {42, nullptr, "GetSystemSharedLayerHandle"}, {43, nullptr, "GetSystemSharedBufferHandle"}, {44, &ISelfController::CreateManagedDisplaySeparableLayer, "CreateManagedDisplaySeparableLayer"}, {45, nullptr, "SetManagedDisplayLayerSeparationMode"}, + {46, nullptr, "SetRecordingLayerCompositionEnabled"}, {50, &ISelfController::SetHandlesRequestToDisplay, "SetHandlesRequestToDisplay"}, {51, nullptr, "ApproveToDisplay"}, {60, nullptr, "OverrideAutoSleepTimeAndDimmingTime"}, @@ -302,6 +304,7 @@ ISelfController::ISelfController(Core::System& system_, NVFlinger::NVFlinger& nv {100, &ISelfController::SetAlbumImageTakenNotificationEnabled, "SetAlbumImageTakenNotificationEnabled"}, {110, nullptr, "SetApplicationAlbumUserData"}, {120, nullptr, "SaveCurrentScreenshot"}, + {130, nullptr, "SetRecordVolumeMuted"}, {1000, nullptr, "GetDebugStorageChannel"}, }; // clang-format on @@ -329,10 +332,10 @@ ISelfController::~ISelfController() = default; void ISelfController::Exit(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_AM, "called"); - system.Shutdown(); - IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); + + system.Exit(); } void ISelfController::LockExit(Kernel::HLERequestContext& ctx) { @@ -683,6 +686,7 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_, {91, nullptr, "GetCurrentPerformanceConfiguration"}, {100, nullptr, "SetHandlingHomeButtonShortPressedEnabled"}, {110, nullptr, "OpenMyGpuErrorHandler"}, + {120, nullptr, "GetAppletLaunchedHistory"}, {200, nullptr, "GetOperationModeSystemInfo"}, {300, nullptr, "GetSettingsPlatformRegion"}, {400, nullptr, "ActivateMigrationService"}, @@ -1270,7 +1274,8 @@ void ILibraryAppletCreator::CreateHandleStorage(Kernel::HLERequestContext& ctx) IApplicationFunctions::IApplicationFunctions(Core::System& system_) : ServiceFramework{system_, "IApplicationFunctions"}, gpu_error_detected_event{system.Kernel()}, friend_invitation_storage_channel_event{system.Kernel()}, - health_warning_disappeared_system_event{system.Kernel()} { + notification_storage_channel_event{system.Kernel()}, health_warning_disappeared_system_event{ + system.Kernel()} { // clang-format off static const FunctionInfo functions[] = { {1, &IApplicationFunctions::PopLaunchParameter, "PopLaunchParameter"}, @@ -1322,7 +1327,7 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_) {131, nullptr, "SetDelayTimeToAbortOnGpuError"}, {140, &IApplicationFunctions::GetFriendInvitationStorageChannelEvent, "GetFriendInvitationStorageChannelEvent"}, {141, &IApplicationFunctions::TryPopFromFriendInvitationStorageChannel, "TryPopFromFriendInvitationStorageChannel"}, - {150, nullptr, "GetNotificationStorageChannelEvent"}, + {150, &IApplicationFunctions::GetNotificationStorageChannelEvent, "GetNotificationStorageChannelEvent"}, {151, nullptr, "TryPopFromNotificationStorageChannel"}, {160, &IApplicationFunctions::GetHealthWarningDisappearedSystemEvent, "GetHealthWarningDisappearedSystemEvent"}, {170, nullptr, "SetHdcpAuthenticationActivated"}, @@ -1340,11 +1345,14 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_) Kernel::KAutoObject::Create(std::addressof(gpu_error_detected_event)); Kernel::KAutoObject::Create(std::addressof(friend_invitation_storage_channel_event)); + Kernel::KAutoObject::Create(std::addressof(notification_storage_channel_event)); Kernel::KAutoObject::Create(std::addressof(health_warning_disappeared_system_event)); gpu_error_detected_event.Initialize("IApplicationFunctions:GpuErrorDetectedSystemEvent"); friend_invitation_storage_channel_event.Initialize( "IApplicationFunctions:FriendInvitationStorageChannelEvent"); + notification_storage_channel_event.Initialize( + "IApplicationFunctions:NotificationStorageChannelEvent"); health_warning_disappeared_system_event.Initialize( "IApplicationFunctions:HealthWarningDisappearedSystemEvent"); } @@ -1762,6 +1770,14 @@ void IApplicationFunctions::TryPopFromFriendInvitationStorageChannel( rb.Push(ERR_NO_DATA_IN_CHANNEL); } +void IApplicationFunctions::GetNotificationStorageChannelEvent(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(notification_storage_channel_event.GetReadableEvent()); +} + void IApplicationFunctions::GetHealthWarningDisappearedSystemEvent(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_AM, "called"); diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index 184030a8e..c13aa5787 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -295,6 +295,7 @@ private: void GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx); void GetFriendInvitationStorageChannelEvent(Kernel::HLERequestContext& ctx); void TryPopFromFriendInvitationStorageChannel(Kernel::HLERequestContext& ctx); + void GetNotificationStorageChannelEvent(Kernel::HLERequestContext& ctx); void GetHealthWarningDisappearedSystemEvent(Kernel::HLERequestContext& ctx); bool launch_popped_application_specific = false; @@ -302,6 +303,7 @@ private: s32 previous_program_index{-1}; Kernel::KEvent gpu_error_detected_event; Kernel::KEvent friend_invitation_storage_channel_event; + Kernel::KEvent notification_storage_channel_event; Kernel::KEvent health_warning_disappeared_system_event; }; diff --git a/src/core/hle/service/am/applets/applet_profile_select.cpp b/src/core/hle/service/am/applets/applet_profile_select.cpp index bdc21778e..a6e891944 100644 --- a/src/core/hle/service/am/applets/applet_profile_select.cpp +++ b/src/core/hle/service/am/applets/applet_profile_select.cpp @@ -60,7 +60,7 @@ void ProfileSelect::Execute() { void ProfileSelect::SelectionComplete(std::optional<Common::UUID> uuid) { UserSelectionOutput output{}; - if (uuid.has_value() && uuid->uuid != Common::INVALID_UUID) { + if (uuid.has_value() && uuid->IsValid()) { output.result = 0; output.uuid_selected = uuid->uuid; } else { diff --git a/src/core/hle/service/audio/audctl.cpp b/src/core/hle/service/audio/audctl.cpp index 8c4c49b85..2e46e7161 100644 --- a/src/core/hle/service/audio/audctl.cpp +++ b/src/core/hle/service/audio/audctl.cpp @@ -41,6 +41,14 @@ AudCtl::AudCtl(Core::System& system_) : ServiceFramework{system_, "audctl"} { {27, nullptr, "SetVolumeMappingTableForDev"}, {28, nullptr, "GetAudioOutputChannelCountForPlayReport"}, {29, nullptr, "BindAudioOutputChannelCountUpdateEventForPlayReport"}, + {30, nullptr, "Unknown30"}, + {31, nullptr, "Unknown31"}, + {32, nullptr, "Unknown32"}, + {33, nullptr, "Unknown33"}, + {34, nullptr, "Unknown34"}, + {10000, nullptr, "Unknown10000"}, + {10001, nullptr, "Unknown10001"}, + {10002, nullptr, "Unknown10002"}, }; // clang-format on diff --git a/src/core/hle/service/audio/audin_u.cpp b/src/core/hle/service/audio/audin_u.cpp index 3e7fd6024..570525019 100644 --- a/src/core/hle/service/audio/audin_u.cpp +++ b/src/core/hle/service/audio/audin_u.cpp @@ -3,38 +3,65 @@ // Refer to the license.txt file included. #include "common/logging/log.h" +#include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/hle_ipc.h" +#include "core/hle/kernel/k_event.h" #include "core/hle/service/audio/audin_u.h" namespace Service::Audio { -class IAudioIn final : public ServiceFramework<IAudioIn> { -public: - explicit IAudioIn(Core::System& system_) : ServiceFramework{system_, "IAudioIn"} { - // clang-format off - static const FunctionInfo functions[] = { - {0, nullptr, "GetAudioInState"}, - {1, nullptr, "Start"}, - {2, nullptr, "Stop"}, - {3, nullptr, "AppendAudioInBuffer"}, - {4, nullptr, "RegisterBufferEvent"}, - {5, nullptr, "GetReleasedAudioInBuffer"}, - {6, nullptr, "ContainsAudioInBuffer"}, - {7, nullptr, "AppendUacInBuffer"}, - {8, nullptr, "AppendAudioInBufferAuto"}, - {9, nullptr, "GetReleasedAudioInBuffersAuto"}, - {10, nullptr, "AppendUacInBufferAuto"}, - {11, nullptr, "GetAudioInBufferCount"}, - {12, nullptr, "SetDeviceGain"}, - {13, nullptr, "GetDeviceGain"}, - {14, nullptr, "FlushAudioInBuffers"}, - }; - // clang-format on - - RegisterHandlers(functions); - } -}; +IAudioIn::IAudioIn(Core::System& system_) + : ServiceFramework{system_, "IAudioIn"}, buffer_event{system_.Kernel()} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "GetAudioInState"}, + {1, &IAudioIn::Start, "Start"}, + {2, nullptr, "Stop"}, + {3, nullptr, "AppendAudioInBuffer"}, + {4, &IAudioIn::RegisterBufferEvent, "RegisterBufferEvent"}, + {5, nullptr, "GetReleasedAudioInBuffer"}, + {6, nullptr, "ContainsAudioInBuffer"}, + {7, nullptr, "AppendUacInBuffer"}, + {8, &IAudioIn::AppendAudioInBufferAuto, "AppendAudioInBufferAuto"}, + {9, nullptr, "GetReleasedAudioInBuffersAuto"}, + {10, nullptr, "AppendUacInBufferAuto"}, + {11, nullptr, "GetAudioInBufferCount"}, + {12, nullptr, "SetDeviceGain"}, + {13, nullptr, "GetDeviceGain"}, + {14, nullptr, "FlushAudioInBuffers"}, + }; + // clang-format on + + RegisterHandlers(functions); + + Kernel::KAutoObject::Create(std::addressof(buffer_event)); + buffer_event.Initialize("IAudioIn:BufferEvent"); +} + +IAudioIn::~IAudioIn() = default; + +void IAudioIn::Start(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_Audio, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + +void IAudioIn::RegisterBufferEvent(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_Audio, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(buffer_event.GetReadableEvent()); +} + +void IAudioIn::AppendAudioInBufferAuto(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_Audio, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} AudInU::AudInU(Core::System& system_) : ServiceFramework{system_, "audin:u"} { // clang-format off diff --git a/src/core/hle/service/audio/audin_u.h b/src/core/hle/service/audio/audin_u.h index 0d75ae5ac..f2f7f9932 100644 --- a/src/core/hle/service/audio/audin_u.h +++ b/src/core/hle/service/audio/audin_u.h @@ -4,6 +4,7 @@ #pragma once +#include "core/hle/kernel/k_event.h" #include "core/hle/service/service.h" namespace Core { @@ -16,6 +17,19 @@ class HLERequestContext; namespace Service::Audio { +class IAudioIn final : public ServiceFramework<IAudioIn> { +public: + explicit IAudioIn(Core::System& system_); + ~IAudioIn() override; + +private: + void Start(Kernel::HLERequestContext& ctx); + void RegisterBufferEvent(Kernel::HLERequestContext& ctx); + void AppendAudioInBufferAuto(Kernel::HLERequestContext& ctx); + + Kernel::KEvent buffer_event; +}; + class AudInU final : public ServiceFramework<AudInU> { public: explicit AudInU(Core::System& system_); diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp index b769fe959..1a91719f5 100644 --- a/src/core/hle/service/audio/audren_u.cpp +++ b/src/core/hle/service/audio/audren_u.cpp @@ -187,7 +187,8 @@ public: {10, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceNameAuto"}, {11, &IAudioDevice::QueryAudioDeviceInputEvent, "QueryAudioDeviceInputEvent"}, {12, &IAudioDevice::QueryAudioDeviceOutputEvent, "QueryAudioDeviceOutputEvent"}, - {13, nullptr, "GetAudioSystemMasterVolumeSetting"}, + {13, nullptr, "GetActiveAudioOutputDeviceName"}, + {14, nullptr, "ListAudioOutputDeviceName"}, }; RegisterHandlers(functions); } diff --git a/src/core/hle/service/bcat/backend/boxcat.cpp b/src/core/hle/service/bcat/backend/boxcat.cpp deleted file mode 100644 index 7ca7f2aac..000000000 --- a/src/core/hle/service/bcat/backend/boxcat.cpp +++ /dev/null @@ -1,548 +0,0 @@ -// Copyright 2019 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <fmt/ostream.h> - -#ifdef __GNUC__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wshadow" -#ifndef __clang__ -#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" -#endif -#endif -#include <httplib.h> -#include <mbedtls/sha256.h> -#include <nlohmann/json.hpp> -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif - -#include "common/fs/file.h" -#include "common/fs/fs.h" -#include "common/fs/path_util.h" -#include "common/hex_util.h" -#include "common/logging/log.h" -#include "common/settings.h" -#include "core/core.h" -#include "core/file_sys/vfs.h" -#include "core/file_sys/vfs_libzip.h" -#include "core/file_sys/vfs_vector.h" -#include "core/frontend/applets/error.h" -#include "core/hle/service/am/applets/applets.h" -#include "core/hle/service/bcat/backend/boxcat.h" - -namespace Service::BCAT { -namespace { - -// Prevents conflicts with windows macro called CreateFile -FileSys::VirtualFile VfsCreateFileWrap(FileSys::VirtualDir dir, std::string_view name) { - return dir->CreateFile(name); -} - -// Prevents conflicts with windows macro called DeleteFile -bool VfsDeleteFileWrap(FileSys::VirtualDir dir, std::string_view name) { - return dir->DeleteFile(name); -} - -constexpr ResultCode ERROR_GENERAL_BCAT_FAILURE{ErrorModule::BCAT, 1}; - -constexpr char BOXCAT_HOSTNAME[] = "api.yuzu-emu.org"; - -// Formatted using fmt with arg[0] = hex title id -constexpr char BOXCAT_PATHNAME_DATA[] = "/game-assets/{:016X}/boxcat"; -constexpr char BOXCAT_PATHNAME_LAUNCHPARAM[] = "/game-assets/{:016X}/launchparam"; - -constexpr char BOXCAT_PATHNAME_EVENTS[] = "/game-assets/boxcat/events"; - -constexpr char BOXCAT_API_VERSION[] = "1"; -constexpr char BOXCAT_CLIENT_TYPE[] = "yuzu"; - -// HTTP status codes for Boxcat -enum class ResponseStatus { - Ok = 200, ///< Operation completed successfully. - BadClientVersion = 301, ///< The Boxcat-Client-Version doesn't match the server. - NoUpdate = 304, ///< The digest provided would match the new data, no need to update. - NoMatchTitleId = 404, ///< The title ID provided doesn't have a boxcat implementation. - NoMatchBuildId = 406, ///< The build ID provided is blacklisted (potentially because of format - ///< issues or whatnot) and has no data. -}; - -enum class DownloadResult { - Success = 0, - NoResponse, - GeneralWebError, - NoMatchTitleId, - NoMatchBuildId, - InvalidContentType, - GeneralFSError, - BadClientVersion, -}; - -constexpr std::array<const char*, 8> DOWNLOAD_RESULT_LOG_MESSAGES{ - "Success", - "There was no response from the server.", - "There was a general web error code returned from the server.", - "The title ID of the current game doesn't have a boxcat implementation. If you believe an " - "implementation should be added, contact yuzu support.", - "The build ID of the current version of the game is marked as incompatible with the current " - "BCAT distribution. Try upgrading or downgrading your game version or contacting yuzu support.", - "The content type of the web response was invalid.", - "There was a general filesystem error while saving the zip file.", - "The server is either too new or too old to serve the request. Try using the latest version of " - "an official release of yuzu.", -}; - -std::ostream& operator<<(std::ostream& os, DownloadResult result) { - return os << DOWNLOAD_RESULT_LOG_MESSAGES.at(static_cast<std::size_t>(result)); -} - -constexpr u32 PORT = 443; -constexpr u32 TIMEOUT_SECONDS = 30; -[[maybe_unused]] constexpr u64 VFS_COPY_BLOCK_SIZE = 1ULL << 24; // 4MB - -std::filesystem::path GetBINFilePath(u64 title_id) { - return Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / "bcat" / - fmt::format("{:016X}/launchparam.bin", title_id); -} - -std::filesystem::path GetZIPFilePath(u64 title_id) { - return Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / "bcat" / - fmt::format("{:016X}/data.zip", title_id); -} - -// If the error is something the user should know about (build ID mismatch, bad client version), -// display an error. -void HandleDownloadDisplayResult(const AM::Applets::AppletManager& applet_manager, - DownloadResult res) { - if (res == DownloadResult::Success || res == DownloadResult::NoResponse || - res == DownloadResult::GeneralWebError || res == DownloadResult::GeneralFSError || - res == DownloadResult::NoMatchTitleId || res == DownloadResult::InvalidContentType) { - return; - } - - const auto& frontend{applet_manager.GetAppletFrontendSet()}; - frontend.error->ShowCustomErrorText( - ResultUnknown, "There was an error while attempting to use Boxcat.", - DOWNLOAD_RESULT_LOG_MESSAGES[static_cast<std::size_t>(res)], [] {}); -} - -bool VfsRawCopyProgress(FileSys::VirtualFile src, FileSys::VirtualFile dest, - std::string_view dir_name, ProgressServiceBackend& progress, - std::size_t block_size = 0x1000) { - if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable()) - return false; - if (!dest->Resize(src->GetSize())) - return false; - - progress.StartDownloadingFile(dir_name, src->GetName(), src->GetSize()); - - std::vector<u8> temp(std::min(block_size, src->GetSize())); - for (std::size_t i = 0; i < src->GetSize(); i += block_size) { - const auto read = std::min(block_size, src->GetSize() - i); - - if (src->Read(temp.data(), read, i) != read) { - return false; - } - - if (dest->Write(temp.data(), read, i) != read) { - return false; - } - - progress.UpdateFileProgress(i); - } - - progress.FinishDownloadingFile(); - - return true; -} - -bool VfsRawCopyDProgressSingle(FileSys::VirtualDir src, FileSys::VirtualDir dest, - ProgressServiceBackend& progress, std::size_t block_size = 0x1000) { - if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable()) - return false; - - for (const auto& file : src->GetFiles()) { - const auto out_file = VfsCreateFileWrap(dest, file->GetName()); - if (!VfsRawCopyProgress(file, out_file, src->GetName(), progress, block_size)) { - return false; - } - } - progress.CommitDirectory(src->GetName()); - - return true; -} - -bool VfsRawCopyDProgress(FileSys::VirtualDir src, FileSys::VirtualDir dest, - ProgressServiceBackend& progress, std::size_t block_size = 0x1000) { - if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable()) - return false; - - for (const auto& dir : src->GetSubdirectories()) { - const auto out = dest->CreateSubdirectory(dir->GetName()); - if (!VfsRawCopyDProgressSingle(dir, out, progress, block_size)) { - return false; - } - } - - return true; -} - -} // Anonymous namespace - -class Boxcat::Client { -public: - Client(std::filesystem::path path_, u64 title_id_, u64 build_id_) - : path(std::move(path_)), title_id(title_id_), build_id(build_id_) {} - - DownloadResult DownloadDataZip() { - return DownloadInternal(fmt::format(BOXCAT_PATHNAME_DATA, title_id), TIMEOUT_SECONDS, - "application/zip"); - } - - DownloadResult DownloadLaunchParam() { - return DownloadInternal(fmt::format(BOXCAT_PATHNAME_LAUNCHPARAM, title_id), - TIMEOUT_SECONDS / 3, "application/octet-stream"); - } - -private: - DownloadResult DownloadInternal(const std::string& resolved_path, u32 timeout_seconds, - const std::string& content_type_name) { - if (client == nullptr) { - client = std::make_unique<httplib::SSLClient>(BOXCAT_HOSTNAME, PORT); - client->set_connection_timeout(timeout_seconds); - client->set_read_timeout(timeout_seconds); - client->set_write_timeout(timeout_seconds); - } - - httplib::Headers headers{ - {std::string("Game-Assets-API-Version"), std::string(BOXCAT_API_VERSION)}, - {std::string("Boxcat-Client-Type"), std::string(BOXCAT_CLIENT_TYPE)}, - {std::string("Game-Build-Id"), fmt::format("{:016X}", build_id)}, - }; - - if (Common::FS::Exists(path)) { - Common::FS::IOFile file{path, Common::FS::FileAccessMode::Read, - Common::FS::FileType::BinaryFile}; - if (file.IsOpen()) { - std::vector<u8> bytes(file.GetSize()); - void(file.Read(bytes)); - const auto digest = DigestFile(bytes); - headers.insert({std::string("If-None-Match"), Common::HexToString(digest, false)}); - } - } - - const auto response = client->Get(resolved_path.c_str(), headers); - if (response == nullptr) - return DownloadResult::NoResponse; - - if (response->status == static_cast<int>(ResponseStatus::NoUpdate)) - return DownloadResult::Success; - if (response->status == static_cast<int>(ResponseStatus::BadClientVersion)) - return DownloadResult::BadClientVersion; - if (response->status == static_cast<int>(ResponseStatus::NoMatchTitleId)) - return DownloadResult::NoMatchTitleId; - if (response->status == static_cast<int>(ResponseStatus::NoMatchBuildId)) - return DownloadResult::NoMatchBuildId; - if (response->status != static_cast<int>(ResponseStatus::Ok)) - return DownloadResult::GeneralWebError; - - const auto content_type = response->headers.find("content-type"); - if (content_type == response->headers.end() || - content_type->second.find(content_type_name) == std::string::npos) { - return DownloadResult::InvalidContentType; - } - - if (!Common::FS::CreateDirs(path)) { - return DownloadResult::GeneralFSError; - } - - Common::FS::IOFile file{path, Common::FS::FileAccessMode::Append, - Common::FS::FileType::BinaryFile}; - if (!file.IsOpen()) { - return DownloadResult::GeneralFSError; - } - - if (!file.SetSize(response->body.size())) { - return DownloadResult::GeneralFSError; - } - - if (file.Write(response->body) != response->body.size()) { - return DownloadResult::GeneralFSError; - } - - return DownloadResult::Success; - } - - using Digest = std::array<u8, 0x20>; - static Digest DigestFile(std::vector<u8> bytes) { - Digest out{}; - mbedtls_sha256_ret(bytes.data(), bytes.size(), out.data(), 0); - return out; - } - - std::unique_ptr<httplib::SSLClient> client; - std::filesystem::path path; - u64 title_id; - u64 build_id; -}; - -Boxcat::Boxcat(AM::Applets::AppletManager& applet_manager_, DirectoryGetter getter) - : Backend(std::move(getter)), applet_manager{applet_manager_} {} - -Boxcat::~Boxcat() = default; - -void SynchronizeInternal(AM::Applets::AppletManager& applet_manager, DirectoryGetter dir_getter, - TitleIDVersion title, ProgressServiceBackend& progress, - std::optional<std::string> dir_name = {}) { - progress.SetNeedHLELock(true); - - if (Settings::values.bcat_boxcat_local) { - LOG_INFO(Service_BCAT, "Boxcat using local data by override, skipping download."); - const auto dir = dir_getter(title.title_id); - if (dir) - progress.SetTotalSize(dir->GetSize()); - progress.FinishDownload(ResultSuccess); - return; - } - - const auto zip_path = GetZIPFilePath(title.title_id); - Boxcat::Client client{zip_path, title.title_id, title.build_id}; - - progress.StartConnecting(); - - const auto res = client.DownloadDataZip(); - if (res != DownloadResult::Success) { - LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res); - - if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) { - Common::FS::RemoveFile(zip_path); - } - - HandleDownloadDisplayResult(applet_manager, res); - progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE); - return; - } - - progress.StartProcessingDataList(); - - Common::FS::IOFile zip{zip_path, Common::FS::FileAccessMode::Read, - Common::FS::FileType::BinaryFile}; - const auto size = zip.GetSize(); - std::vector<u8> bytes(size); - if (!zip.IsOpen() || size == 0 || zip.Read(bytes) != bytes.size()) { - LOG_ERROR(Service_BCAT, "Boxcat failed to read ZIP file at path '{}'!", - Common::FS::PathToUTF8String(zip_path)); - progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE); - return; - } - - const auto extracted = FileSys::ExtractZIP(std::make_shared<FileSys::VectorVfsFile>(bytes)); - if (extracted == nullptr) { - LOG_ERROR(Service_BCAT, "Boxcat failed to extract ZIP file!"); - progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE); - return; - } - - if (dir_name == std::nullopt) { - progress.SetTotalSize(extracted->GetSize()); - - const auto target_dir = dir_getter(title.title_id); - if (target_dir == nullptr || !VfsRawCopyDProgress(extracted, target_dir, progress)) { - LOG_ERROR(Service_BCAT, "Boxcat failed to copy extracted ZIP to target directory!"); - progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE); - return; - } - } else { - const auto target_dir = dir_getter(title.title_id); - if (target_dir == nullptr) { - LOG_ERROR(Service_BCAT, "Boxcat failed to get directory for title ID!"); - progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE); - return; - } - - const auto target_sub = target_dir->GetSubdirectory(*dir_name); - const auto source_sub = extracted->GetSubdirectory(*dir_name); - - progress.SetTotalSize(source_sub->GetSize()); - - std::vector<std::string> filenames; - { - const auto files = target_sub->GetFiles(); - std::transform(files.begin(), files.end(), std::back_inserter(filenames), - [](const auto& vfile) { return vfile->GetName(); }); - } - - for (const auto& filename : filenames) { - VfsDeleteFileWrap(target_sub, filename); - } - - if (target_sub == nullptr || source_sub == nullptr || - !VfsRawCopyDProgressSingle(source_sub, target_sub, progress)) { - LOG_ERROR(Service_BCAT, "Boxcat failed to copy extracted ZIP to target directory!"); - progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE); - return; - } - } - - progress.FinishDownload(ResultSuccess); -} - -bool Boxcat::Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) { - is_syncing.exchange(true); - - std::thread([this, title, &progress] { - SynchronizeInternal(applet_manager, dir_getter, title, progress); - }).detach(); - - return true; -} - -bool Boxcat::SynchronizeDirectory(TitleIDVersion title, std::string name, - ProgressServiceBackend& progress) { - is_syncing.exchange(true); - - std::thread([this, title, name, &progress] { - SynchronizeInternal(applet_manager, dir_getter, title, progress, name); - }).detach(); - - return true; -} - -bool Boxcat::Clear(u64 title_id) { - if (Settings::values.bcat_boxcat_local) { - LOG_INFO(Service_BCAT, "Boxcat using local data by override, skipping clear."); - return true; - } - - const auto dir = dir_getter(title_id); - - std::vector<std::string> dirnames; - - for (const auto& subdir : dir->GetSubdirectories()) - dirnames.push_back(subdir->GetName()); - - for (const auto& subdir : dirnames) { - if (!dir->DeleteSubdirectoryRecursive(subdir)) - return false; - } - - return true; -} - -void Boxcat::SetPassphrase(u64 title_id, const Passphrase& passphrase) { - LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase={}", title_id, - Common::HexToString(passphrase)); -} - -std::optional<std::vector<u8>> Boxcat::GetLaunchParameter(TitleIDVersion title) { - const auto bin_file_path = GetBINFilePath(title.title_id); - - if (Settings::values.bcat_boxcat_local) { - LOG_INFO(Service_BCAT, "Boxcat using local data by override, skipping download."); - } else { - Client launch_client{bin_file_path, title.title_id, title.build_id}; - - const auto res = launch_client.DownloadLaunchParam(); - if (res != DownloadResult::Success) { - LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res); - - if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) { - Common::FS::RemoveFile(bin_file_path); - } - - HandleDownloadDisplayResult(applet_manager, res); - return std::nullopt; - } - } - - Common::FS::IOFile bin{bin_file_path, Common::FS::FileAccessMode::Read, - Common::FS::FileType::BinaryFile}; - const auto size = bin.GetSize(); - std::vector<u8> bytes(size); - if (!bin.IsOpen() || size == 0 || bin.Read(bytes) != bytes.size()) { - LOG_ERROR(Service_BCAT, "Boxcat failed to read launch parameter binary at path '{}'!", - Common::FS::PathToUTF8String(bin_file_path)); - return std::nullopt; - } - - return bytes; -} - -Boxcat::StatusResult Boxcat::GetStatus(std::optional<std::string>& global, - std::map<std::string, EventStatus>& games) { - httplib::SSLClient client{BOXCAT_HOSTNAME, static_cast<int>(PORT)}; - client.set_connection_timeout(static_cast<int>(TIMEOUT_SECONDS)); - client.set_read_timeout(static_cast<int>(TIMEOUT_SECONDS)); - client.set_write_timeout(static_cast<int>(TIMEOUT_SECONDS)); - - httplib::Headers headers{ - {std::string("Game-Assets-API-Version"), std::string(BOXCAT_API_VERSION)}, - {std::string("Boxcat-Client-Type"), std::string(BOXCAT_CLIENT_TYPE)}, - }; - - if (!client.is_valid()) { - LOG_ERROR(Service_BCAT, "Client is invalid, going offline!"); - return StatusResult::Offline; - } - - if (!client.is_socket_open()) { - LOG_ERROR(Service_BCAT, "Failed to open socket, going offline!"); - return StatusResult::Offline; - } - - const auto response = client.Get(BOXCAT_PATHNAME_EVENTS, headers); - if (response == nullptr) - return StatusResult::Offline; - - if (response->status == static_cast<int>(ResponseStatus::BadClientVersion)) - return StatusResult::BadClientVersion; - - try { - nlohmann::json json = nlohmann::json::parse(response->body); - - if (!json["online"].get<bool>()) - return StatusResult::Offline; - - if (json["global"].is_null()) - global = std::nullopt; - else - global = json["global"].get<std::string>(); - - if (json["games"].is_array()) { - for (const auto& object : json["games"]) { - if (object.is_object() && object.find("name") != object.end()) { - EventStatus detail{}; - if (object["header"].is_string()) { - detail.header = object["header"].get<std::string>(); - } else { - detail.header = std::nullopt; - } - - if (object["footer"].is_string()) { - detail.footer = object["footer"].get<std::string>(); - } else { - detail.footer = std::nullopt; - } - - if (object["events"].is_array()) { - for (const auto& event : object["events"]) { - if (!event.is_string()) - continue; - detail.events.push_back(event.get<std::string>()); - } - } - - games.insert_or_assign(object["name"], std::move(detail)); - } - } - } - - return StatusResult::Success; - } catch (const nlohmann::json::parse_error& error) { - LOG_ERROR(Service_BCAT, "{}", error.what()); - return StatusResult::ParseError; - } -} - -} // namespace Service::BCAT diff --git a/src/core/hle/service/bcat/backend/boxcat.h b/src/core/hle/service/bcat/backend/boxcat.h deleted file mode 100644 index d65b42e58..000000000 --- a/src/core/hle/service/bcat/backend/boxcat.h +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2019 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <atomic> -#include <map> -#include <optional> -#include "core/hle/service/bcat/backend/backend.h" - -namespace Service::AM::Applets { -class AppletManager; -} - -namespace Service::BCAT { - -struct EventStatus { - std::optional<std::string> header; - std::optional<std::string> footer; - std::vector<std::string> events; -}; - -/// Boxcat is yuzu's custom backend implementation of Nintendo's BCAT service. It is free to use and -/// doesn't require a switch or nintendo account. The content is controlled by the yuzu team. -class Boxcat final : public Backend { - friend void SynchronizeInternal(AM::Applets::AppletManager& applet_manager, - DirectoryGetter dir_getter, TitleIDVersion title, - ProgressServiceBackend& progress, - std::optional<std::string> dir_name); - -public: - explicit Boxcat(AM::Applets::AppletManager& applet_manager_, DirectoryGetter getter); - ~Boxcat() override; - - bool Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) override; - bool SynchronizeDirectory(TitleIDVersion title, std::string name, - ProgressServiceBackend& progress) override; - - bool Clear(u64 title_id) override; - - void SetPassphrase(u64 title_id, const Passphrase& passphrase) override; - - std::optional<std::vector<u8>> GetLaunchParameter(TitleIDVersion title) override; - - enum class StatusResult { - Success, - Offline, - ParseError, - BadClientVersion, - }; - - static StatusResult GetStatus(std::optional<std::string>& global, - std::map<std::string, EventStatus>& games); - -private: - std::atomic_bool is_syncing{false}; - - class Client; - std::unique_ptr<Client> client; - AM::Applets::AppletManager& applet_manager; -}; - -} // namespace Service::BCAT diff --git a/src/core/hle/service/bcat/bcat_module.cpp b/src/core/hle/service/bcat/bcat_module.cpp index 72294eb2e..701f634f8 100644 --- a/src/core/hle/service/bcat/bcat_module.cpp +++ b/src/core/hle/service/bcat/bcat_module.cpp @@ -4,7 +4,6 @@ #include <cctype> #include <mbedtls/md5.h> -#include "backend/boxcat.h" #include "common/hex_util.h" #include "common/logging/log.h" #include "common/settings.h" @@ -578,12 +577,6 @@ void Module::Interface::CreateDeliveryCacheStorageServiceWithApplicationId( std::unique_ptr<Backend> CreateBackendFromSettings([[maybe_unused]] Core::System& system, DirectoryGetter getter) { -#ifdef YUZU_ENABLE_BOXCAT - if (Settings::values.bcat_backend.GetValue() == "boxcat") { - return std::make_unique<Boxcat>(system.GetAppletManager(), std::move(getter)); - } -#endif - return std::make_unique<NullBackend>(std::move(getter)); } diff --git a/src/core/hle/service/btdrv/btdrv.cpp b/src/core/hle/service/btdrv/btdrv.cpp index 46da438ef..acf791de2 100644 --- a/src/core/hle/service/btdrv/btdrv.cpp +++ b/src/core/hle/service/btdrv/btdrv.cpp @@ -175,6 +175,10 @@ public: {143, nullptr, "GetAudioControlInputState"}, {144, nullptr, "AcquireAudioConnectionStateChangedEvent"}, {145, nullptr, "GetConnectedAudioDevice"}, + {146, nullptr, "CloseAudioControlInput"}, + {147, nullptr, "RegisterAudioControlNotification"}, + {148, nullptr, "SendAudioControlPassthroughCommand"}, + {149, nullptr, "SendAudioControlSetAbsoluteVolumeCommand"}, {256, nullptr, "IsManufacturingMode"}, {257, nullptr, "EmulateBluetoothCrash"}, {258, nullptr, "GetBleChannelMap"}, diff --git a/src/core/hle/service/caps/caps_ss.cpp b/src/core/hle/service/caps/caps_ss.cpp index 2b5314691..33a976ddf 100644 --- a/src/core/hle/service/caps/caps_ss.cpp +++ b/src/core/hle/service/caps/caps_ss.cpp @@ -15,6 +15,7 @@ CAPS_SS::CAPS_SS(Core::System& system_) : ServiceFramework{system_, "caps:ss"} { {204, nullptr, "SaveEditedScreenShotEx0"}, {206, nullptr, "Unknown206"}, {208, nullptr, "SaveScreenShotOfMovieEx1"}, + {1000, nullptr, "Unknown1000"}, }; // clang-format on diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp index 110c7cb1c..f6184acc9 100644 --- a/src/core/hle/service/es/es.cpp +++ b/src/core/hle/service/es/es.cpp @@ -55,6 +55,8 @@ public: {36, nullptr, "DeleteAllInactiveELicenseRequiredPersonalizedTicket"}, {37, nullptr, "OwnTicket2"}, {38, nullptr, "OwnTicket3"}, + {39, nullptr, "DeleteAllInactivePersonalizedTicket"}, + {40, nullptr, "DeletePrepurchaseRecordByNintendoAccountId"}, {501, nullptr, "Unknown501"}, {502, nullptr, "Unknown502"}, {503, nullptr, "GetTitleKey"}, @@ -88,11 +90,15 @@ public: {1503, nullptr, "Unknown1503"}, {1504, nullptr, "Unknown1504"}, {1505, nullptr, "Unknown1505"}, + {1506, nullptr, "Unknown1506"}, {2000, nullptr, "Unknown2000"}, {2001, nullptr, "Unknown2001"}, + {2002, nullptr, "Unknown2002"}, + {2003, nullptr, "Unknown2003"}, {2100, nullptr, "Unknown2100"}, {2501, nullptr, "Unknown2501"}, {2502, nullptr, "Unknown2502"}, + {2601, nullptr, "Unknown2601"}, {3001, nullptr, "Unknown3001"}, {3002, nullptr, "Unknown3002"}, }; diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index 4a9b13e45..f8f9e32f7 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -97,14 +97,24 @@ ResultCode VfsDirectoryServiceWrapper::DeleteFile(const std::string& path_) cons ResultCode VfsDirectoryServiceWrapper::CreateDirectory(const std::string& path_) const { std::string path(Common::FS::SanitizePath(path_)); - auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path)); - if (dir == nullptr || Common::FS::GetFilename(Common::FS::GetParentPath(path)).empty()) { - dir = backing; - } - auto new_dir = dir->CreateSubdirectory(Common::FS::GetFilename(path)); - if (new_dir == nullptr) { - // TODO(DarkLordZach): Find a better error code for this - return ResultUnknown; + + // NOTE: This is inaccurate behavior. CreateDirectory is not recursive. + // CreateDirectory should return PathNotFound if the parent directory does not exist. + // This is here temporarily in order to have UMM "work" in the meantime. + // TODO (Morph): Remove this when a hardware test verifies the correct behavior. + const auto components = Common::FS::SplitPathComponents(path); + std::string relative_path; + for (const auto& component : components) { + // Skip empty path components + if (component.empty()) { + continue; + } + relative_path = Common::FS::SanitizePath(relative_path + '/' + component); + auto new_dir = backing->CreateSubdirectory(relative_path); + if (new_dir == nullptr) { + // TODO(DarkLordZach): Find a better error code for this + return ResultUnknown; + } } return ResultSuccess; } @@ -251,6 +261,18 @@ ResultVal<FileSys::EntryType> VfsDirectoryServiceWrapper::GetEntryType( return FileSys::ERROR_PATH_NOT_FOUND; } +ResultVal<FileSys::FileTimeStampRaw> VfsDirectoryServiceWrapper::GetFileTimeStampRaw( + const std::string& path) const { + auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path)); + if (dir == nullptr) { + return FileSys::ERROR_PATH_NOT_FOUND; + } + if (GetEntryType(path).Failed()) { + return FileSys::ERROR_PATH_NOT_FOUND; + } + return MakeResult(dir->GetFileTimeStamp(Common::FS::GetFilename(path))); +} + FileSystemController::FileSystemController(Core::System& system_) : system{system_} {} FileSystemController::~FileSystemController() = default; diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h index d387af3cb..b155e0811 100644 --- a/src/core/hle/service/filesystem/filesystem.h +++ b/src/core/hle/service/filesystem/filesystem.h @@ -240,6 +240,12 @@ public: */ ResultVal<FileSys::EntryType> GetEntryType(const std::string& path) const; + /** + * Get the timestamp of the specified path + * @return The timestamp of the specified path or error code + */ + ResultVal<FileSys::FileTimeStampRaw> GetFileTimeStampRaw(const std::string& path) const; + private: FileSys::VirtualDir backing; }; diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp index db4d44c12..50c788dd6 100644 --- a/src/core/hle/service/filesystem/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp_srv.cpp @@ -326,7 +326,7 @@ public: {11, &IFileSystem::GetFreeSpaceSize, "GetFreeSpaceSize"}, {12, &IFileSystem::GetTotalSpaceSize, "GetTotalSpaceSize"}, {13, &IFileSystem::CleanDirectoryRecursively, "CleanDirectoryRecursively"}, - {14, nullptr, "GetFileTimeStampRaw"}, + {14, &IFileSystem::GetFileTimeStampRaw, "GetFileTimeStampRaw"}, {15, nullptr, "QueryEntry"}, }; RegisterHandlers(functions); @@ -501,6 +501,24 @@ public: rb.Push(size.get_total_size()); } + void GetFileTimeStampRaw(Kernel::HLERequestContext& ctx) { + const auto file_buffer = ctx.ReadBuffer(); + const std::string name = Common::StringFromBuffer(file_buffer); + + LOG_WARNING(Service_FS, "(Partial Implementation) called. file={}", name); + + auto result = backend.GetFileTimeStampRaw(name); + if (result.Failed()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result.Code()); + return; + } + + IPC::ResponseBuilder rb{ctx, 10}; + rb.Push(ResultSuccess); + rb.PushRaw(*result); + } + private: VfsDirectoryServiceWrapper backend; SizeGetter size; diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h index 4fcc6f93a..9ee146caf 100644 --- a/src/core/hle/service/hid/controllers/npad.h +++ b/src/core/hle/service/hid/controllers/npad.h @@ -507,6 +507,7 @@ private: LarkNesRight = 18, Lucia = 19, Verification = 20, + Lagon = 21, }; struct NPadEntry { diff --git a/src/core/hle/service/hid/controllers/touchscreen.h b/src/core/hle/service/hid/controllers/touchscreen.h index ef2becefd..8e9b40c0a 100644 --- a/src/core/hle/service/hid/controllers/touchscreen.h +++ b/src/core/hle/service/hid/controllers/touchscreen.h @@ -15,6 +15,20 @@ namespace Service::HID { class Controller_Touchscreen final : public ControllerBase { public: + enum class TouchScreenModeForNx : u8 { + UseSystemSetting, + Finger, + Heat2, + }; + + struct TouchScreenConfigurationForNx { + TouchScreenModeForNx mode; + INSERT_PADDING_BYTES_NOINIT(0x7); + INSERT_PADDING_BYTES_NOINIT(0xF); // Reserved + }; + static_assert(sizeof(TouchScreenConfigurationForNx) == 0x17, + "TouchScreenConfigurationForNx is an invalid size"); + explicit Controller_Touchscreen(Core::System& system_); ~Controller_Touchscreen() override; diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index b8b80570d..8c363142c 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -106,7 +106,7 @@ void IAppletResource::DeactivateController(HidController controller) { controllers[static_cast<size_t>(controller)]->DeactivateController(); } -IAppletResource ::~IAppletResource() { +IAppletResource::~IAppletResource() { system.CoreTiming().UnscheduleEvent(pad_update_event, 0); system.CoreTiming().UnscheduleEvent(motion_update_event, 0); } @@ -239,6 +239,12 @@ Hid::Hid(Core::System& system_) {81, &Hid::ResetGyroscopeZeroDriftMode, "ResetGyroscopeZeroDriftMode"}, {82, &Hid::IsSixAxisSensorAtRest, "IsSixAxisSensorAtRest"}, {83, &Hid::IsFirmwareUpdateAvailableForSixAxisSensor, "IsFirmwareUpdateAvailableForSixAxisSensor"}, + {84, nullptr, "EnableSixAxisSensorUnalteredPassthrough"}, + {85, nullptr, "IsSixAxisSensorUnalteredPassthroughEnabled"}, + {86, nullptr, "StoreSixAxisSensorCalibrationParameter"}, + {87, nullptr, "LoadSixAxisSensorCalibrationParameter"}, + {88, nullptr, "GetSixAxisSensorIcInformation"}, + {89, nullptr, "ResetIsSixAxisSensorDeviceNewlyAssigned"}, {91, &Hid::ActivateGesture, "ActivateGesture"}, {100, &Hid::SetSupportedNpadStyleSet, "SetSupportedNpadStyleSet"}, {101, &Hid::GetSupportedNpadStyleSet, "GetSupportedNpadStyleSet"}, @@ -331,7 +337,7 @@ Hid::Hid(Core::System& system_) {529, nullptr, "SetDisallowedPalmaConnection"}, {1000, &Hid::SetNpadCommunicationMode, "SetNpadCommunicationMode"}, {1001, &Hid::GetNpadCommunicationMode, "GetNpadCommunicationMode"}, - {1002, nullptr, "SetTouchScreenConfiguration"}, + {1002, &Hid::SetTouchScreenConfiguration, "SetTouchScreenConfiguration"}, {1003, nullptr, "IsFirmwareUpdateNeededForNotification"}, {2000, nullptr, "ActivateDigitizer"}, }; @@ -1631,6 +1637,18 @@ void Hid::GetNpadCommunicationMode(Kernel::HLERequestContext& ctx) { .GetNpadCommunicationMode()); } +void Hid::SetTouchScreenConfiguration(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto touchscreen_mode{rp.PopRaw<Controller_Touchscreen::TouchScreenConfigurationForNx>()}; + const auto applet_resource_user_id{rp.Pop<u64>()}; + + LOG_WARNING(Service_HID, "(STUBBED) called, touchscreen_mode={}, applet_resource_user_id={}", + touchscreen_mode.mode, applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + class HidDbg final : public ServiceFramework<HidDbg> { public: explicit HidDbg(Core::System& system_) : ServiceFramework{system_, "hid:dbg"} { @@ -1644,6 +1662,9 @@ public: {12, nullptr, "UnsetTouchScreenAutoPilotState"}, {13, nullptr, "GetTouchScreenConfiguration"}, {14, nullptr, "ProcessTouchScreenAutoTune"}, + {15, nullptr, "ForceStopTouchScreenManagement"}, + {16, nullptr, "ForceRestartTouchScreenManagement"}, + {17, nullptr, "IsTouchScreenManaged"}, {20, nullptr, "DeactivateMouse"}, {21, nullptr, "SetMouseAutoPilotState"}, {22, nullptr, "UnsetMouseAutoPilotState"}, diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index 9c5c7f252..b1fe75e94 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -159,6 +159,7 @@ private: void SetPalmaBoostMode(Kernel::HLERequestContext& ctx); void SetNpadCommunicationMode(Kernel::HLERequestContext& ctx); void GetNpadCommunicationMode(Kernel::HLERequestContext& ctx); + void SetTouchScreenConfiguration(Kernel::HLERequestContext& ctx); enum class VibrationDeviceType : u32 { Unknown = 0, diff --git a/src/core/hle/service/ngct/ngct.cpp b/src/core/hle/service/ngct/ngct.cpp index deb3abb28..8ec7d5266 100644 --- a/src/core/hle/service/ngct/ngct.cpp +++ b/src/core/hle/service/ngct/ngct.cpp @@ -15,7 +15,7 @@ public: explicit IService(Core::System& system_) : ServiceFramework{system_, "ngct:u"} { // clang-format off static const FunctionInfo functions[] = { - {0, nullptr, "Match"}, + {0, &IService::Match, "Match"}, {1, &IService::Filter, "Filter"}, }; // clang-format on @@ -24,6 +24,19 @@ public: } private: + void Match(Kernel::HLERequestContext& ctx) { + const auto buffer = ctx.ReadBuffer(); + const auto text = Common::StringFromFixedZeroTerminatedBuffer( + reinterpret_cast<const char*>(buffer.data()), buffer.size()); + + LOG_WARNING(Service_NGCT, "(STUBBED) called, text={}", text); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + // Return false since we don't censor anything + rb.Push(false); + } + void Filter(Kernel::HLERequestContext& ctx) { const auto buffer = ctx.ReadBuffer(); const auto text = Common::StringFromFixedZeroTerminatedBuffer( diff --git a/src/core/hle/service/npns/npns.cpp b/src/core/hle/service/npns/npns.cpp index e4c703da4..32533cd94 100644 --- a/src/core/hle/service/npns/npns.cpp +++ b/src/core/hle/service/npns/npns.cpp @@ -31,6 +31,7 @@ public: {24, nullptr, "DestroyTokenWithApplicationId"}, {25, nullptr, "QueryIsTokenValid"}, {26, nullptr, "ListenToMyApplicationId"}, + {27, nullptr, "DestroyTokenAll"}, {31, nullptr, "UploadTokenToBaaS"}, {32, nullptr, "DestroyTokenForBaaS"}, {33, nullptr, "CreateTokenForBaaS"}, diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp index ce6065db2..789000294 100644 --- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp +++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp @@ -16,7 +16,7 @@ namespace Service::Nvidia::Devices { nvdisp_disp0::nvdisp_disp0(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_) : nvdevice{system_}, nvmap_dev{std::move(nvmap_dev_)} {} -nvdisp_disp0 ::~nvdisp_disp0() = default; +nvdisp_disp0::~nvdisp_disp0() = default; NvResult nvdisp_disp0::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) { @@ -42,15 +42,14 @@ void nvdisp_disp0::OnClose(DeviceFD fd) {} void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u32 height, u32 stride, NVFlinger::BufferQueue::BufferTransformFlags transform, const Common::Rectangle<int>& crop_rect) { - VAddr addr = nvmap_dev->GetObjectAddress(buffer_handle); + const VAddr addr = nvmap_dev->GetObjectAddress(buffer_handle); LOG_TRACE(Service, "Drawing from address {:X} offset {:08X} Width {} Height {} Stride {} Format {}", addr, offset, width, height, stride, format); - using PixelFormat = Tegra::FramebufferConfig::PixelFormat; - const Tegra::FramebufferConfig framebuffer{ - addr, offset, width, height, stride, static_cast<PixelFormat>(format), - transform, crop_rect}; + const auto pixel_format = static_cast<Tegra::FramebufferConfig::PixelFormat>(format); + const Tegra::FramebufferConfig framebuffer{addr, offset, width, height, + stride, pixel_format, transform, crop_rect}; system.GetPerfStats().EndSystemFrame(); system.GPU().SwapBuffers(&framebuffer); diff --git a/src/core/hle/service/nvflinger/buffer_queue.cpp b/src/core/hle/service/nvflinger/buffer_queue.cpp index 59ddf6298..b4c3a6099 100644 --- a/src/core/hle/service/nvflinger/buffer_queue.cpp +++ b/src/core/hle/service/nvflinger/buffer_queue.cpp @@ -9,17 +9,20 @@ #include "core/core.h" #include "core/hle/kernel/k_writable_event.h" #include "core/hle/kernel/kernel.h" +#include "core/hle/service/kernel_helpers.h" #include "core/hle/service/nvflinger/buffer_queue.h" namespace Service::NVFlinger { -BufferQueue::BufferQueue(Kernel::KernelCore& kernel, u32 id_, u64 layer_id_) - : id(id_), layer_id(layer_id_), buffer_wait_event{kernel} { - Kernel::KAutoObject::Create(std::addressof(buffer_wait_event)); - buffer_wait_event.Initialize("BufferQueue:WaitEvent"); +BufferQueue::BufferQueue(Kernel::KernelCore& kernel, u32 id_, u64 layer_id_, + KernelHelpers::ServiceContext& service_context_) + : id(id_), layer_id(layer_id_), service_context{service_context_} { + buffer_wait_event = service_context.CreateEvent("BufferQueue:WaitEvent"); } -BufferQueue::~BufferQueue() = default; +BufferQueue::~BufferQueue() { + service_context.CloseEvent(buffer_wait_event); +} void BufferQueue::SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer) { ASSERT(slot < buffer_slots); @@ -41,7 +44,7 @@ void BufferQueue::SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer) .multi_fence = {}, }; - buffer_wait_event.GetWritableEvent().Signal(); + buffer_wait_event->GetWritableEvent().Signal(); } std::optional<std::pair<u32, Service::Nvidia::MultiFence*>> BufferQueue::DequeueBuffer(u32 width, @@ -119,7 +122,7 @@ void BufferQueue::CancelBuffer(u32 slot, const Service::Nvidia::MultiFence& mult } free_buffers_condition.notify_one(); - buffer_wait_event.GetWritableEvent().Signal(); + buffer_wait_event->GetWritableEvent().Signal(); } std::optional<std::reference_wrapper<const BufferQueue::Buffer>> BufferQueue::AcquireBuffer() { @@ -154,7 +157,7 @@ void BufferQueue::ReleaseBuffer(u32 slot) { } free_buffers_condition.notify_one(); - buffer_wait_event.GetWritableEvent().Signal(); + buffer_wait_event->GetWritableEvent().Signal(); } void BufferQueue::Connect() { @@ -169,7 +172,7 @@ void BufferQueue::Disconnect() { std::unique_lock lock{queue_sequence_mutex}; queue_sequence.clear(); } - buffer_wait_event.GetWritableEvent().Signal(); + buffer_wait_event->GetWritableEvent().Signal(); is_connect = false; free_buffers_condition.notify_one(); } @@ -189,11 +192,11 @@ u32 BufferQueue::Query(QueryType type) { } Kernel::KWritableEvent& BufferQueue::GetWritableBufferWaitEvent() { - return buffer_wait_event.GetWritableEvent(); + return buffer_wait_event->GetWritableEvent(); } Kernel::KReadableEvent& BufferQueue::GetBufferWaitEvent() { - return buffer_wait_event.GetReadableEvent(); + return buffer_wait_event->GetReadableEvent(); } } // namespace Service::NVFlinger diff --git a/src/core/hle/service/nvflinger/buffer_queue.h b/src/core/hle/service/nvflinger/buffer_queue.h index 61e337ac5..78de3f354 100644 --- a/src/core/hle/service/nvflinger/buffer_queue.h +++ b/src/core/hle/service/nvflinger/buffer_queue.h @@ -24,6 +24,10 @@ class KReadableEvent; class KWritableEvent; } // namespace Kernel +namespace Service::KernelHelpers { +class ServiceContext; +} // namespace Service::KernelHelpers + namespace Service::NVFlinger { constexpr u32 buffer_slots = 0x40; @@ -38,7 +42,9 @@ struct IGBPBuffer { u32_le index; INSERT_PADDING_WORDS(3); u32_le gpu_buffer_id; - INSERT_PADDING_WORDS(17); + INSERT_PADDING_WORDS(6); + u32_le external_format; + INSERT_PADDING_WORDS(10); u32_le nvmap_handle; u32_le offset; INSERT_PADDING_WORDS(60); @@ -54,7 +60,8 @@ public: NativeWindowFormat = 2, }; - explicit BufferQueue(Kernel::KernelCore& kernel, u32 id_, u64 layer_id_); + explicit BufferQueue(Kernel::KernelCore& kernel, u32 id_, u64 layer_id_, + KernelHelpers::ServiceContext& service_context_); ~BufferQueue(); enum class BufferTransformFlags : u32 { @@ -130,12 +137,14 @@ private: std::list<u32> free_buffers; std::array<Buffer, buffer_slots> buffers; std::list<u32> queue_sequence; - Kernel::KEvent buffer_wait_event; + Kernel::KEvent* buffer_wait_event{}; std::mutex free_buffers_mutex; std::condition_variable free_buffers_condition; std::mutex queue_sequence_mutex; + + KernelHelpers::ServiceContext& service_context; }; } // namespace Service::NVFlinger diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp index 32d4e360a..3ead813b0 100644 --- a/src/core/hle/service/nvflinger/nvflinger.cpp +++ b/src/core/hle/service/nvflinger/nvflinger.cpp @@ -147,7 +147,7 @@ std::optional<u64> NVFlinger::CreateLayer(u64 display_id) { void NVFlinger::CreateLayerAtId(VI::Display& display, u64 layer_id) { const u32 buffer_queue_id = next_buffer_queue_id++; buffer_queues.emplace_back( - std::make_unique<BufferQueue>(system.Kernel(), buffer_queue_id, layer_id)); + std::make_unique<BufferQueue>(system.Kernel(), buffer_queue_id, layer_id, service_context)); display.CreateLayer(layer_id, *buffer_queues.back()); } @@ -298,7 +298,7 @@ void NVFlinger::Compose() { auto nvdisp = nvdrv->GetDevice<Nvidia::Devices::nvdisp_disp0>("/dev/nvdisp_disp0"); ASSERT(nvdisp); - nvdisp->flip(igbp_buffer.gpu_buffer_id, igbp_buffer.offset, igbp_buffer.format, + nvdisp->flip(igbp_buffer.gpu_buffer_id, igbp_buffer.offset, igbp_buffer.external_format, igbp_buffer.width, igbp_buffer.height, igbp_buffer.stride, buffer->get().transform, buffer->get().crop_rect); diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp index 7d85ecb6a..b9e765f1d 100644 --- a/src/core/hle/service/sockets/bsd.cpp +++ b/src/core/hle/service/sockets/bsd.cpp @@ -415,6 +415,18 @@ void BSD::Write(Kernel::HLERequestContext& ctx) { }); } +void BSD::Read(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const s32 fd = rp.Pop<s32>(); + + LOG_WARNING(Service, "(STUBBED) called. fd={} len={}", fd, ctx.GetWriteBufferSize()); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(ResultSuccess); + rb.Push<u32>(0); // ret + rb.Push<u32>(0); // bsd errno +} + void BSD::Close(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const s32 fd = rp.Pop<s32>(); @@ -855,7 +867,7 @@ BSD::BSD(Core::System& system_, const char* name) : ServiceFramework{system_, na {22, &BSD::Shutdown, "Shutdown"}, {23, nullptr, "ShutdownAllSockets"}, {24, &BSD::Write, "Write"}, - {25, nullptr, "Read"}, + {25, &BSD::Read, "Read"}, {26, &BSD::Close, "Close"}, {27, nullptr, "DuplicateSocket"}, {28, nullptr, "GetResourceStatistics"}, diff --git a/src/core/hle/service/sockets/bsd.h b/src/core/hle/service/sockets/bsd.h index 1d2df9c61..d68beef5c 100644 --- a/src/core/hle/service/sockets/bsd.h +++ b/src/core/hle/service/sockets/bsd.h @@ -135,6 +135,7 @@ private: void Send(Kernel::HLERequestContext& ctx); void SendTo(Kernel::HLERequestContext& ctx); void Write(Kernel::HLERequestContext& ctx); + void Read(Kernel::HLERequestContext& ctx); void Close(Kernel::HLERequestContext& ctx); void EventFd(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/time/system_clock_core.cpp b/src/core/hle/service/time/system_clock_core.cpp index bd334bbef..5c2354cdd 100644 --- a/src/core/hle/service/time/system_clock_core.cpp +++ b/src/core/hle/service/time/system_clock_core.cpp @@ -13,7 +13,7 @@ SystemClockCore::SystemClockCore(SteadyClockCore& steady_clock_core_) context.steady_time_point.clock_source_id = steady_clock_core.GetClockSourceId(); } -SystemClockCore ::~SystemClockCore() = default; +SystemClockCore::~SystemClockCore() = default; ResultCode SystemClockCore::GetCurrentTime(Core::System& system, s64& posix_time) const { posix_time = 0; diff --git a/src/core/hle/service/time/time_zone_service.cpp b/src/core/hle/service/time/time_zone_service.cpp index 5c3108768..3871e7316 100644 --- a/src/core/hle/service/time/time_zone_service.cpp +++ b/src/core/hle/service/time/time_zone_service.cpp @@ -10,8 +10,8 @@ namespace Service::Time { -ITimeZoneService ::ITimeZoneService(Core::System& system_, - TimeZone::TimeZoneContentManager& time_zone_manager_) +ITimeZoneService::ITimeZoneService(Core::System& system_, + TimeZone::TimeZoneContentManager& time_zone_manager_) : ServiceFramework{system_, "ITimeZoneService"}, time_zone_content_manager{time_zone_manager_} { static const FunctionInfo functions[] = { {0, &ITimeZoneService::GetDeviceLocationName, "GetDeviceLocationName"}, diff --git a/src/core/hle/service/usb/usb.cpp b/src/core/hle/service/usb/usb.cpp index 7f436c3bb..2ee103b37 100644 --- a/src/core/hle/service/usb/usb.cpp +++ b/src/core/hle/service/usb/usb.cpp @@ -97,7 +97,7 @@ public: {3, nullptr, "GetAlternateInterface"}, {4, nullptr, "GetCurrentFrame"}, {5, nullptr, "CtrlXferAsync"}, - {6, nullptr, "Unknown6"}, + {6, nullptr, "GetCtrlXferCompletionEvent"}, {7, nullptr, "GetCtrlXferReport"}, {8, nullptr, "ResetDevice"}, {9, nullptr, "OpenUsbEp"}, @@ -183,8 +183,8 @@ public: {4, nullptr, "GetHostPdcFirmwareRevision"}, {5, nullptr, "GetHostPdcManufactureId"}, {6, nullptr, "GetHostPdcDeviceId"}, - {7, nullptr, "AwakeCradle"}, - {8, nullptr, "SleepCradle"}, + {7, nullptr, "EnableCradleRecovery"}, + {8, nullptr, "DisableCradleRecovery"}, }; // clang-format on diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp index 8e8fc40ca..be3d52d54 100644 --- a/src/core/hle/service/vi/vi.cpp +++ b/src/core/hle/service/vi/vi.cpp @@ -831,6 +831,7 @@ public: {6010, nullptr, "GetLayerPresentationAllFencesExpiredEvent"}, {6011, nullptr, "EnableLayerAutoClearTransitionBuffer"}, {6012, nullptr, "DisableLayerAutoClearTransitionBuffer"}, + {6013, nullptr, "SetLayerOpacity"}, {7000, nullptr, "SetContentVisibility"}, {8000, nullptr, "SetConductorLayer"}, {8001, nullptr, "SetTimestampTracking"}, diff --git a/src/core/network/network.cpp b/src/core/network/network.cpp index 4732d4485..72eea52f0 100644 --- a/src/core/network/network.cpp +++ b/src/core/network/network.cpp @@ -7,7 +7,8 @@ #include <limits> #include <utility> #include <vector> -#include "common/common_funcs.h" + +#include "common/error.h" #ifdef _WIN32 #include <winsock2.h> @@ -223,7 +224,7 @@ Errno GetAndLogLastError() { if (err == Errno::AGAIN) { return err; } - LOG_ERROR(Network, "Socket operation error: {}", NativeErrorToString(e)); + LOG_ERROR(Network, "Socket operation error: {}", Common::NativeErrorToString(e)); return err; } diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp index 5a8cfd301..191475f71 100644 --- a/src/core/telemetry_session.cpp +++ b/src/core/telemetry_session.cpp @@ -72,6 +72,18 @@ static const char* TranslateGPUAccuracyLevel(Settings::GPUAccuracy backend) { return "Unknown"; } +static const char* TranslateNvdecEmulation(Settings::NvdecEmulation backend) { + switch (backend) { + case Settings::NvdecEmulation::Off: + return "Off"; + case Settings::NvdecEmulation::CPU: + return "CPU"; + case Settings::NvdecEmulation::GPU: + return "GPU"; + } + return "Unknown"; +} + u64 GetTelemetryId() { u64 telemetry_id{}; const auto filename = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir) / "telemetry_id"; @@ -214,8 +226,6 @@ 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.GetValue()); - 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())); @@ -229,8 +239,8 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader, TranslateGPUAccuracyLevel(Settings::values.gpu_accuracy.GetValue())); AddField(field_type, "Renderer_UseAsynchronousGpuEmulation", Settings::values.use_asynchronous_gpu_emulation.GetValue()); - AddField(field_type, "Renderer_UseNvdecEmulation", - Settings::values.use_nvdec_emulation.GetValue()); + AddField(field_type, "Renderer_NvdecEmulation", + TranslateNvdecEmulation(Settings::values.nvdec_emulation.GetValue())); AddField(field_type, "Renderer_AccelerateASTC", Settings::values.accelerate_astc.GetValue()); AddField(field_type, "Renderer_UseVsync", Settings::values.use_vsync.GetValue()); AddField(field_type, "Renderer_ShaderBackend", diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index c4283a952..dd13d948f 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -21,6 +21,10 @@ add_library(input_common STATIC mouse/mouse_poller.h sdl/sdl.cpp sdl/sdl.h + tas/tas_input.cpp + tas/tas_input.h + tas/tas_poller.cpp + tas/tas_poller.h udp/client.cpp udp/client.h udp/protocol.cpp diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index ff23230f0..f3907c65a 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -5,6 +5,7 @@ #include <memory> #include <thread> #include "common/param_package.h" +#include "common/settings.h" #include "input_common/analog_from_button.h" #include "input_common/gcadapter/gc_adapter.h" #include "input_common/gcadapter/gc_poller.h" @@ -13,6 +14,8 @@ #include "input_common/motion_from_button.h" #include "input_common/mouse/mouse_input.h" #include "input_common/mouse/mouse_poller.h" +#include "input_common/tas/tas_input.h" +#include "input_common/tas/tas_poller.h" #include "input_common/touch_from_button.h" #include "input_common/udp/client.h" #include "input_common/udp/udp.h" @@ -60,6 +63,12 @@ struct InputSubsystem::Impl { Input::RegisterFactory<Input::MotionDevice>("mouse", mousemotion); mousetouch = std::make_shared<MouseTouchFactory>(mouse); Input::RegisterFactory<Input::TouchDevice>("mouse", mousetouch); + + tas = std::make_shared<TasInput::Tas>(); + tasbuttons = std::make_shared<TasButtonFactory>(tas); + Input::RegisterFactory<Input::ButtonDevice>("tas", tasbuttons); + tasanalog = std::make_shared<TasAnalogFactory>(tas); + Input::RegisterFactory<Input::AnalogDevice>("tas", tasanalog); } void Shutdown() { @@ -94,6 +103,12 @@ struct InputSubsystem::Impl { mouseanalog.reset(); mousemotion.reset(); mousetouch.reset(); + + Input::UnregisterFactory<Input::ButtonDevice>("tas"); + Input::UnregisterFactory<Input::AnalogDevice>("tas"); + + tasbuttons.reset(); + tasanalog.reset(); } [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const { @@ -101,6 +116,10 @@ struct InputSubsystem::Impl { Common::ParamPackage{{"display", "Any"}, {"class", "any"}}, Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "keyboard"}}, }; + if (Settings::values.tas_enable) { + devices.emplace_back( + Common::ParamPackage{{"display", "TAS Controller"}, {"class", "tas"}}); + } #ifdef HAVE_SDL2 auto sdl_devices = sdl->GetInputDevices(); devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end()); @@ -120,6 +139,9 @@ struct InputSubsystem::Impl { if (params.Get("class", "") == "gcpad") { return gcadapter->GetAnalogMappingForDevice(params); } + if (params.Get("class", "") == "tas") { + return tas->GetAnalogMappingForDevice(params); + } #ifdef HAVE_SDL2 if (params.Get("class", "") == "sdl") { return sdl->GetAnalogMappingForDevice(params); @@ -136,6 +158,9 @@ struct InputSubsystem::Impl { if (params.Get("class", "") == "gcpad") { return gcadapter->GetButtonMappingForDevice(params); } + if (params.Get("class", "") == "tas") { + return tas->GetButtonMappingForDevice(params); + } #ifdef HAVE_SDL2 if (params.Get("class", "") == "sdl") { return sdl->GetButtonMappingForDevice(params); @@ -174,9 +199,12 @@ struct InputSubsystem::Impl { std::shared_ptr<MouseAnalogFactory> mouseanalog; std::shared_ptr<MouseMotionFactory> mousemotion; std::shared_ptr<MouseTouchFactory> mousetouch; + std::shared_ptr<TasButtonFactory> tasbuttons; + std::shared_ptr<TasAnalogFactory> tasanalog; std::shared_ptr<CemuhookUDP::Client> udp; std::shared_ptr<GCAdapter::Adapter> gcadapter; std::shared_ptr<MouseInput::Mouse> mouse; + std::shared_ptr<TasInput::Tas> tas; }; InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {} @@ -207,6 +235,14 @@ const MouseInput::Mouse* InputSubsystem::GetMouse() const { return impl->mouse.get(); } +TasInput::Tas* InputSubsystem::GetTas() { + return impl->tas.get(); +} + +const TasInput::Tas* InputSubsystem::GetTas() const { + return impl->tas.get(); +} + std::vector<Common::ParamPackage> InputSubsystem::GetInputDevices() const { return impl->GetInputDevices(); } @@ -287,6 +323,22 @@ const MouseTouchFactory* InputSubsystem::GetMouseTouch() const { return impl->mousetouch.get(); } +TasButtonFactory* InputSubsystem::GetTasButtons() { + return impl->tasbuttons.get(); +} + +const TasButtonFactory* InputSubsystem::GetTasButtons() const { + return impl->tasbuttons.get(); +} + +TasAnalogFactory* InputSubsystem::GetTasAnalogs() { + return impl->tasanalog.get(); +} + +const TasAnalogFactory* InputSubsystem::GetTasAnalogs() const { + return impl->tasanalog.get(); +} + void InputSubsystem::ReloadInputDevices() { if (!impl->udp) { return; @@ -294,8 +346,8 @@ void InputSubsystem::ReloadInputDevices() { impl->udp->ReloadSockets(); } -std::vector<std::unique_ptr<Polling::DevicePoller>> InputSubsystem::GetPollers([ - [maybe_unused]] Polling::DeviceType type) const { +std::vector<std::unique_ptr<Polling::DevicePoller>> InputSubsystem::GetPollers( + [[maybe_unused]] Polling::DeviceType type) const { #ifdef HAVE_SDL2 return impl->sdl->GetPollers(type); #else diff --git a/src/input_common/main.h b/src/input_common/main.h index 5d6f26385..6390d3f09 100644 --- a/src/input_common/main.h +++ b/src/input_common/main.h @@ -29,6 +29,10 @@ namespace MouseInput { class Mouse; } +namespace TasInput { +class Tas; +} + namespace InputCommon { namespace Polling { @@ -64,6 +68,8 @@ class MouseButtonFactory; class MouseAnalogFactory; class MouseMotionFactory; class MouseTouchFactory; +class TasButtonFactory; +class TasAnalogFactory; class Keyboard; /** @@ -103,6 +109,11 @@ public: /// Retrieves the underlying mouse device. [[nodiscard]] const MouseInput::Mouse* GetMouse() const; + /// Retrieves the underlying tas device. + [[nodiscard]] TasInput::Tas* GetTas(); + + /// Retrieves the underlying tas device. + [[nodiscard]] const TasInput::Tas* GetTas() const; /** * Returns all available input devices that this Factory can create a new device with. * Each returned ParamPackage should have a `display` field used for display, a class field for @@ -144,30 +155,42 @@ public: /// Retrieves the underlying udp touch handler. [[nodiscard]] const UDPTouchFactory* GetUDPTouch() const; - /// Retrieves the underlying GameCube button handler. + /// Retrieves the underlying mouse button handler. [[nodiscard]] MouseButtonFactory* GetMouseButtons(); - /// Retrieves the underlying GameCube button handler. + /// Retrieves the underlying mouse button handler. [[nodiscard]] const MouseButtonFactory* GetMouseButtons() const; - /// Retrieves the underlying udp touch handler. + /// Retrieves the underlying mouse analog handler. [[nodiscard]] MouseAnalogFactory* GetMouseAnalogs(); - /// Retrieves the underlying udp touch handler. + /// Retrieves the underlying mouse analog handler. [[nodiscard]] const MouseAnalogFactory* GetMouseAnalogs() const; - /// Retrieves the underlying udp motion handler. + /// Retrieves the underlying mouse motion handler. [[nodiscard]] MouseMotionFactory* GetMouseMotions(); - /// Retrieves the underlying udp motion handler. + /// Retrieves the underlying mouse motion handler. [[nodiscard]] const MouseMotionFactory* GetMouseMotions() const; - /// Retrieves the underlying udp touch handler. + /// Retrieves the underlying mouse touch handler. [[nodiscard]] MouseTouchFactory* GetMouseTouch(); - /// Retrieves the underlying udp touch handler. + /// Retrieves the underlying mouse touch handler. [[nodiscard]] const MouseTouchFactory* GetMouseTouch() const; + /// Retrieves the underlying tas button handler. + [[nodiscard]] TasButtonFactory* GetTasButtons(); + + /// Retrieves the underlying tas button handler. + [[nodiscard]] const TasButtonFactory* GetTasButtons() const; + + /// Retrieves the underlying tas analogs handler. + [[nodiscard]] TasAnalogFactory* GetTasAnalogs(); + + /// Retrieves the underlying tas analogs handler. + [[nodiscard]] const TasAnalogFactory* GetTasAnalogs() const; + /// Reloads the input devices void ReloadInputDevices(); diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp index 8723acc31..ab6211b29 100644 --- a/src/input_common/sdl/sdl_impl.cpp +++ b/src/input_common/sdl/sdl_impl.cpp @@ -21,7 +21,7 @@ #include "common/logging/log.h" #include "common/math_util.h" #include "common/param_package.h" -#include "common/settings_input.h" +#include "common/settings.h" #include "common/threadsafe_queue.h" #include "core/frontend/input.h" #include "input_common/motion_input.h" @@ -903,8 +903,10 @@ SDLState::SDLState() { RegisterFactory<VibrationDevice>("sdl", vibration_factory); RegisterFactory<MotionDevice>("sdl", motion_factory); - // Disable raw input. When enabled this setting causes SDL to die when a web applet opens - SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, "0"); + if (!Settings::values.enable_raw_input) { + // Disable raw input. When enabled this setting causes SDL to die when a web applet opens + SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, "0"); + } // Enable HIDAPI rumble. This prevents SDL from disabling motion on PS4 and PS5 controllers SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); @@ -912,10 +914,10 @@ SDLState::SDLState() { // Tell SDL2 to use the hidapi driver. This will allow joycons to be detected as a // GameController and not a generic one - SDL_SetHint("SDL_JOYSTICK_HIDAPI_JOY_CONS", "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1"); // Turn off Pro controller home led - SDL_SetHint("SDL_JOYSTICK_HIDAPI_SWITCH_HOME_LED", "0"); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_HOME_LED, "0"); // If the frontend is going to manage the event loop, then we don't start one here start_thread = SDL_WasInit(SDL_INIT_JOYSTICK) == 0; diff --git a/src/input_common/tas/tas_input.cpp b/src/input_common/tas/tas_input.cpp new file mode 100644 index 000000000..1598092b6 --- /dev/null +++ b/src/input_common/tas/tas_input.cpp @@ -0,0 +1,455 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include <cstring> +#include <regex> + +#include "common/fs/file.h" +#include "common/fs/fs_types.h" +#include "common/fs/path_util.h" +#include "common/logging/log.h" +#include "common/settings.h" +#include "input_common/tas/tas_input.h" + +namespace TasInput { + +// Supported keywords and buttons from a TAS file +constexpr std::array<std::pair<std::string_view, TasButton>, 20> text_to_tas_button = { + std::pair{"KEY_A", TasButton::BUTTON_A}, + {"KEY_B", TasButton::BUTTON_B}, + {"KEY_X", TasButton::BUTTON_X}, + {"KEY_Y", TasButton::BUTTON_Y}, + {"KEY_LSTICK", TasButton::STICK_L}, + {"KEY_RSTICK", TasButton::STICK_R}, + {"KEY_L", TasButton::TRIGGER_L}, + {"KEY_R", TasButton::TRIGGER_R}, + {"KEY_PLUS", TasButton::BUTTON_PLUS}, + {"KEY_MINUS", TasButton::BUTTON_MINUS}, + {"KEY_DLEFT", TasButton::BUTTON_LEFT}, + {"KEY_DUP", TasButton::BUTTON_UP}, + {"KEY_DRIGHT", TasButton::BUTTON_RIGHT}, + {"KEY_DDOWN", TasButton::BUTTON_DOWN}, + {"KEY_SL", TasButton::BUTTON_SL}, + {"KEY_SR", TasButton::BUTTON_SR}, + {"KEY_CAPTURE", TasButton::BUTTON_CAPTURE}, + {"KEY_HOME", TasButton::BUTTON_HOME}, + {"KEY_ZL", TasButton::TRIGGER_ZL}, + {"KEY_ZR", TasButton::TRIGGER_ZR}, +}; + +Tas::Tas() { + if (!Settings::values.tas_enable) { + needs_reset = true; + return; + } + LoadTasFiles(); +} + +Tas::~Tas() { + Stop(); +}; + +void Tas::LoadTasFiles() { + script_length = 0; + for (size_t i = 0; i < commands.size(); i++) { + LoadTasFile(i); + if (commands[i].size() > script_length) { + script_length = commands[i].size(); + } + } +} + +void Tas::LoadTasFile(size_t player_index) { + if (!commands[player_index].empty()) { + commands[player_index].clear(); + } + std::string file = + Common::FS::ReadStringFromFile(Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) / + fmt::format("script0-{}.txt", player_index + 1), + Common::FS::FileType::BinaryFile); + std::stringstream command_line(file); + std::string line; + int frame_no = 0; + while (std::getline(command_line, line, '\n')) { + if (line.empty()) { + continue; + } + LOG_DEBUG(Input, "Loading line: {}", line); + std::smatch m; + + std::stringstream linestream(line); + std::string segment; + std::vector<std::string> seglist; + + while (std::getline(linestream, segment, ' ')) { + seglist.push_back(segment); + } + + if (seglist.size() < 4) { + continue; + } + + while (frame_no < std::stoi(seglist.at(0))) { + commands[player_index].push_back({}); + frame_no++; + } + + TASCommand command = { + .buttons = ReadCommandButtons(seglist.at(1)), + .l_axis = ReadCommandAxis(seglist.at(2)), + .r_axis = ReadCommandAxis(seglist.at(3)), + }; + commands[player_index].push_back(command); + frame_no++; + } + LOG_INFO(Input, "TAS file loaded! {} frames", frame_no); +} + +void Tas::WriteTasFile(std::u8string file_name) { + std::string output_text; + for (size_t frame = 0; frame < record_commands.size(); frame++) { + if (!output_text.empty()) { + output_text += "\n"; + } + const TASCommand& line = record_commands[frame]; + output_text += std::to_string(frame) + " " + WriteCommandButtons(line.buttons) + " " + + WriteCommandAxis(line.l_axis) + " " + WriteCommandAxis(line.r_axis); + } + const auto bytes_written = Common::FS::WriteStringToFile( + Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) / file_name, + Common::FS::FileType::TextFile, output_text); + if (bytes_written == output_text.size()) { + LOG_INFO(Input, "TAS file written to file!"); + } else { + LOG_ERROR(Input, "Writing the TAS-file has failed! {} / {} bytes written", bytes_written, + output_text.size()); + } +} + +std::pair<float, float> Tas::FlipAxisY(std::pair<float, float> old) { + auto [x, y] = old; + return {x, -y}; +} + +void Tas::RecordInput(u32 buttons, const std::array<std::pair<float, float>, 2>& axes) { + last_input = {buttons, FlipAxisY(axes[0]), FlipAxisY(axes[1])}; +} + +std::tuple<TasState, size_t, size_t> Tas::GetStatus() const { + TasState state; + if (is_recording) { + return {TasState::Recording, 0, record_commands.size()}; + } + + if (is_running) { + state = TasState::Running; + } else { + state = TasState::Stopped; + } + + return {state, current_command, script_length}; +} + +std::string Tas::DebugButtons(u32 buttons) const { + return fmt::format("{{ {} }}", TasInput::Tas::ButtonsToString(buttons)); +} + +std::string Tas::DebugJoystick(float x, float y) const { + return fmt::format("[ {} , {} ]", std::to_string(x), std::to_string(y)); +} + +std::string Tas::DebugInput(const TasData& data) const { + return fmt::format("{{ {} , {} , {} }}", DebugButtons(data.buttons), + DebugJoystick(data.axis[0], data.axis[1]), + DebugJoystick(data.axis[2], data.axis[3])); +} + +std::string Tas::DebugInputs(const std::array<TasData, PLAYER_NUMBER>& arr) const { + std::string returns = "[ "; + for (size_t i = 0; i < arr.size(); i++) { + returns += DebugInput(arr[i]); + if (i != arr.size() - 1) { + returns += " , "; + } + } + return returns + "]"; +} + +std::string Tas::ButtonsToString(u32 button) const { + std::string returns; + for (auto [text_button, tas_button] : text_to_tas_button) { + if ((button & static_cast<u32>(tas_button)) != 0) + returns += fmt::format(", {}", text_button.substr(4)); + } + return returns.empty() ? "" : returns.substr(2); +} + +void Tas::UpdateThread() { + if (!Settings::values.tas_enable) { + if (is_running) { + Stop(); + } + return; + } + + if (is_recording) { + record_commands.push_back(last_input); + } + if (needs_reset) { + current_command = 0; + needs_reset = false; + LoadTasFiles(); + LOG_DEBUG(Input, "tas_reset done"); + } + + if (!is_running) { + tas_data.fill({}); + return; + } + if (current_command < script_length) { + LOG_DEBUG(Input, "Playing TAS {}/{}", current_command, script_length); + size_t frame = current_command++; + for (size_t i = 0; i < commands.size(); i++) { + if (frame < commands[i].size()) { + TASCommand command = commands[i][frame]; + tas_data[i].buttons = command.buttons; + auto [l_axis_x, l_axis_y] = command.l_axis; + tas_data[i].axis[0] = l_axis_x; + tas_data[i].axis[1] = l_axis_y; + auto [r_axis_x, r_axis_y] = command.r_axis; + tas_data[i].axis[2] = r_axis_x; + tas_data[i].axis[3] = r_axis_y; + } else { + tas_data[i] = {}; + } + } + } else { + is_running = Settings::values.tas_loop.GetValue(); + current_command = 0; + tas_data.fill({}); + if (!is_running) { + SwapToStoredController(); + } + } + LOG_DEBUG(Input, "TAS inputs: {}", DebugInputs(tas_data)); +} + +TasAnalog Tas::ReadCommandAxis(const std::string& line) const { + std::stringstream linestream(line); + std::string segment; + std::vector<std::string> seglist; + + while (std::getline(linestream, segment, ';')) { + seglist.push_back(segment); + } + + const float x = std::stof(seglist.at(0)) / 32767.0f; + const float y = std::stof(seglist.at(1)) / 32767.0f; + + return {x, y}; +} + +u32 Tas::ReadCommandButtons(const std::string& data) const { + std::stringstream button_text(data); + std::string line; + u32 buttons = 0; + while (std::getline(button_text, line, ';')) { + for (auto [text, tas_button] : text_to_tas_button) { + if (text == line) { + buttons |= static_cast<u32>(tas_button); + break; + } + } + } + return buttons; +} + +std::string Tas::WriteCommandAxis(TasAnalog data) const { + auto [x, y] = data; + std::string line; + line += std::to_string(static_cast<int>(x * 32767)); + line += ";"; + line += std::to_string(static_cast<int>(y * 32767)); + return line; +} + +std::string Tas::WriteCommandButtons(u32 data) const { + if (data == 0) { + return "NONE"; + } + + std::string line; + u32 index = 0; + while (data > 0) { + if ((data & 1) == 1) { + for (auto [text, tas_button] : text_to_tas_button) { + if (tas_button == static_cast<TasButton>(1 << index)) { + if (line.size() > 0) { + line += ";"; + } + line += text; + break; + } + } + } + index++; + data >>= 1; + } + return line; +} + +void Tas::StartStop() { + if (!Settings::values.tas_enable) { + return; + } + if (is_running) { + Stop(); + } else { + is_running = true; + SwapToTasController(); + } +} + +void Tas::Stop() { + is_running = false; + SwapToStoredController(); +} + +void Tas::SwapToTasController() { + if (!Settings::values.tas_swap_controllers) { + return; + } + auto& players = Settings::values.players.GetValue(); + for (std::size_t index = 0; index < players.size(); index++) { + auto& player = players[index]; + player_mappings[index] = player; + + // Only swap active controllers + if (!player.connected) { + continue; + } + + Common::ParamPackage tas_param; + tas_param.Set("pad", static_cast<u8>(index)); + auto button_mapping = GetButtonMappingForDevice(tas_param); + auto analog_mapping = GetAnalogMappingForDevice(tas_param); + auto& buttons = player.buttons; + auto& analogs = player.analogs; + + for (std::size_t i = 0; i < buttons.size(); ++i) { + buttons[i] = button_mapping[static_cast<Settings::NativeButton::Values>(i)].Serialize(); + } + for (std::size_t i = 0; i < analogs.size(); ++i) { + analogs[i] = analog_mapping[static_cast<Settings::NativeAnalog::Values>(i)].Serialize(); + } + } + is_old_input_saved = true; + Settings::values.is_device_reload_pending.store(true); +} + +void Tas::SwapToStoredController() { + if (!is_old_input_saved) { + return; + } + auto& players = Settings::values.players.GetValue(); + for (std::size_t index = 0; index < players.size(); index++) { + players[index] = player_mappings[index]; + } + is_old_input_saved = false; + Settings::values.is_device_reload_pending.store(true); +} + +void Tas::Reset() { + if (!Settings::values.tas_enable) { + return; + } + needs_reset = true; +} + +bool Tas::Record() { + if (!Settings::values.tas_enable) { + return true; + } + is_recording = !is_recording; + return is_recording; +} + +void Tas::SaveRecording(bool overwrite_file) { + if (is_recording) { + return; + } + if (record_commands.empty()) { + return; + } + WriteTasFile(u8"record.txt"); + if (overwrite_file) { + WriteTasFile(u8"script0-1.txt"); + } + needs_reset = true; + record_commands.clear(); +} + +InputCommon::ButtonMapping Tas::GetButtonMappingForDevice( + const Common::ParamPackage& params) const { + // This list is missing ZL/ZR since those are not considered buttons. + // We will add those afterwards + // This list also excludes any button that can't be really mapped + static constexpr std::array<std::pair<Settings::NativeButton::Values, TasButton>, 20> + switch_to_tas_button = { + std::pair{Settings::NativeButton::A, TasButton::BUTTON_A}, + {Settings::NativeButton::B, TasButton::BUTTON_B}, + {Settings::NativeButton::X, TasButton::BUTTON_X}, + {Settings::NativeButton::Y, TasButton::BUTTON_Y}, + {Settings::NativeButton::LStick, TasButton::STICK_L}, + {Settings::NativeButton::RStick, TasButton::STICK_R}, + {Settings::NativeButton::L, TasButton::TRIGGER_L}, + {Settings::NativeButton::R, TasButton::TRIGGER_R}, + {Settings::NativeButton::Plus, TasButton::BUTTON_PLUS}, + {Settings::NativeButton::Minus, TasButton::BUTTON_MINUS}, + {Settings::NativeButton::DLeft, TasButton::BUTTON_LEFT}, + {Settings::NativeButton::DUp, TasButton::BUTTON_UP}, + {Settings::NativeButton::DRight, TasButton::BUTTON_RIGHT}, + {Settings::NativeButton::DDown, TasButton::BUTTON_DOWN}, + {Settings::NativeButton::SL, TasButton::BUTTON_SL}, + {Settings::NativeButton::SR, TasButton::BUTTON_SR}, + {Settings::NativeButton::Screenshot, TasButton::BUTTON_CAPTURE}, + {Settings::NativeButton::Home, TasButton::BUTTON_HOME}, + {Settings::NativeButton::ZL, TasButton::TRIGGER_ZL}, + {Settings::NativeButton::ZR, TasButton::TRIGGER_ZR}, + }; + + InputCommon::ButtonMapping mapping{}; + for (const auto& [switch_button, tas_button] : switch_to_tas_button) { + Common::ParamPackage button_params({{"engine", "tas"}}); + button_params.Set("pad", params.Get("pad", 0)); + button_params.Set("button", static_cast<int>(tas_button)); + mapping.insert_or_assign(switch_button, std::move(button_params)); + } + + return mapping; +} + +InputCommon::AnalogMapping Tas::GetAnalogMappingForDevice( + const Common::ParamPackage& params) const { + + InputCommon::AnalogMapping mapping = {}; + Common::ParamPackage left_analog_params; + left_analog_params.Set("engine", "tas"); + left_analog_params.Set("pad", params.Get("pad", 0)); + left_analog_params.Set("axis_x", static_cast<int>(TasAxes::StickX)); + left_analog_params.Set("axis_y", static_cast<int>(TasAxes::StickY)); + mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params)); + Common::ParamPackage right_analog_params; + right_analog_params.Set("engine", "tas"); + right_analog_params.Set("pad", params.Get("pad", 0)); + right_analog_params.Set("axis_x", static_cast<int>(TasAxes::SubstickX)); + right_analog_params.Set("axis_y", static_cast<int>(TasAxes::SubstickY)); + mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params)); + return mapping; +} + +const TasData& Tas::GetTasState(std::size_t pad) const { + return tas_data[pad]; +} +} // namespace TasInput diff --git a/src/input_common/tas/tas_input.h b/src/input_common/tas/tas_input.h new file mode 100644 index 000000000..3e2db8f00 --- /dev/null +++ b/src/input_common/tas/tas_input.h @@ -0,0 +1,237 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> + +#include "common/common_types.h" +#include "common/settings_input.h" +#include "core/frontend/input.h" +#include "input_common/main.h" + +/* +To play back TAS scripts on Yuzu, select the folder with scripts in the configuration menu below +Tools -> Configure TAS. The file itself has normal text format and has to be called script0-1.txt +for controller 1, script0-2.txt for controller 2 and so forth (with max. 8 players). + +A script file has the same format as TAS-nx uses, so final files will look like this: + +1 KEY_B 0;0 0;0 +6 KEY_ZL 0;0 0;0 +41 KEY_ZL;KEY_Y 0;0 0;0 +43 KEY_X;KEY_A 32767;0 0;0 +44 KEY_A 32767;0 0;0 +45 KEY_A 32767;0 0;0 +46 KEY_A 32767;0 0;0 +47 KEY_A 32767;0 0;0 + +After placing the file at the correct location, it can be read into Yuzu with the (default) hotkey +CTRL+F6 (refresh). In the bottom left corner, it will display the amount of frames the script file +has. Playback can be started or stopped using CTRL+F5. + +However, for playback to actually work, the correct input device has to be selected: In the Controls +menu, select TAS from the device list for the controller that the script should be played on. + +Recording a new script file is really simple: Just make sure that the proper device (not TAS) is +connected on P1, and press CTRL+F7 to start recording. When done, just press the same keystroke +again (CTRL+F7). The new script will be saved at the location previously selected, as the filename +record.txt. + +For debugging purposes, the common controller debugger can be used (View -> Debugging -> Controller +P1). +*/ + +namespace TasInput { + +constexpr size_t PLAYER_NUMBER = 8; + +using TasAnalog = std::pair<float, float>; + +enum class TasState { + Running, + Recording, + Stopped, +}; + +enum class TasButton : u32 { + BUTTON_A = 1U << 0, + BUTTON_B = 1U << 1, + BUTTON_X = 1U << 2, + BUTTON_Y = 1U << 3, + STICK_L = 1U << 4, + STICK_R = 1U << 5, + TRIGGER_L = 1U << 6, + TRIGGER_R = 1U << 7, + TRIGGER_ZL = 1U << 8, + TRIGGER_ZR = 1U << 9, + BUTTON_PLUS = 1U << 10, + BUTTON_MINUS = 1U << 11, + BUTTON_LEFT = 1U << 12, + BUTTON_UP = 1U << 13, + BUTTON_RIGHT = 1U << 14, + BUTTON_DOWN = 1U << 15, + BUTTON_SL = 1U << 16, + BUTTON_SR = 1U << 17, + BUTTON_HOME = 1U << 18, + BUTTON_CAPTURE = 1U << 19, +}; + +enum class TasAxes : u8 { + StickX, + StickY, + SubstickX, + SubstickY, + Undefined, +}; + +struct TasData { + u32 buttons{}; + std::array<float, 4> axis{}; +}; + +class Tas { +public: + Tas(); + ~Tas(); + + // Changes the input status that will be stored in each frame + void RecordInput(u32 buttons, const std::array<std::pair<float, float>, 2>& axes); + + // Main loop that records or executes input + void UpdateThread(); + + // Sets the flag to start or stop the TAS command excecution and swaps controllers profiles + void StartStop(); + + // Stop the TAS and reverts any controller profile + void Stop(); + + // Sets the flag to reload the file and start from the begining in the next update + void Reset(); + + /** + * Sets the flag to enable or disable recording of inputs + * @return Returns true if the current recording status is enabled + */ + bool Record(); + + // Saves contents of record_commands on a file if overwrite is enabled player 1 will be + // overwritten with the recorded commands + void SaveRecording(bool overwrite_file); + + /** + * Returns the current status values of TAS playback/recording + * @return Tuple of + * TasState indicating the current state out of Running, Recording or Stopped ; + * Current playback progress or amount of frames (so far) for Recording ; + * Total length of script file currently loaded or amount of frames (so far) for Recording + */ + std::tuple<TasState, size_t, size_t> GetStatus() const; + + // Retuns an array of the default button mappings + InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const; + + // Retuns an array of the default analog mappings + InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const; + [[nodiscard]] const TasData& GetTasState(std::size_t pad) const; + +private: + struct TASCommand { + u32 buttons{}; + TasAnalog l_axis{}; + TasAnalog r_axis{}; + }; + + // Loads TAS files from all players + void LoadTasFiles(); + + // Loads TAS file from the specified player + void LoadTasFile(size_t player_index); + + // Writes a TAS file from the recorded commands + void WriteTasFile(std::u8string file_name); + + /** + * Parses a string containing the axis values with the following format "x;y" + * X and Y have a range from -32767 to 32767 + * @return Returns a TAS analog object with axis values with range from -1.0 to 1.0 + */ + TasAnalog ReadCommandAxis(const std::string& line) const; + + /** + * Parses a string containing the button values with the following format "a;b;c;d..." + * Each button is represented by it's text format specified in text_to_tas_button array + * @return Returns a u32 with each bit representing the status of a button + */ + u32 ReadCommandButtons(const std::string& line) const; + + /** + * Converts an u32 containing the button status into the text equivalent + * @return Returns a string with the name of the buttons to be written to the file + */ + std::string WriteCommandButtons(u32 data) const; + + /** + * Converts an TAS analog object containing the axis status into the text equivalent + * @return Returns a string with the value of the axis to be written to the file + */ + std::string WriteCommandAxis(TasAnalog data) const; + + // Inverts the Y axis polarity + std::pair<float, float> FlipAxisY(std::pair<float, float> old); + + /** + * Converts an u32 containing the button status into the text equivalent + * @return Returns a string with the name of the buttons to be printed on console + */ + std::string DebugButtons(u32 buttons) const; + + /** + * Converts an TAS analog object containing the axis status into the text equivalent + * @return Returns a string with the value of the axis to be printed on console + */ + std::string DebugJoystick(float x, float y) const; + + /** + * Converts the given TAS status into the text equivalent + * @return Returns a string with the value of the TAS status to be printed on console + */ + std::string DebugInput(const TasData& data) const; + + /** + * Converts the given TAS status of multiple players into the text equivalent + * @return Returns a string with the value of the status of all TAS players to be printed on + * console + */ + std::string DebugInputs(const std::array<TasData, PLAYER_NUMBER>& arr) const; + + /** + * Converts an u32 containing the button status into the text equivalent + * @return Returns a string with the name of the buttons + */ + std::string ButtonsToString(u32 button) const; + + // Stores current controller configuration and sets a TAS controller for every active controller + // to the current config + void SwapToTasController(); + + // Sets the stored controller configuration to the current config + void SwapToStoredController(); + + size_t script_length{0}; + std::array<TasData, PLAYER_NUMBER> tas_data; + bool is_old_input_saved{false}; + bool is_recording{false}; + bool is_running{false}; + bool needs_reset{false}; + std::array<std::vector<TASCommand>, PLAYER_NUMBER> commands{}; + std::vector<TASCommand> record_commands{}; + size_t current_command{0}; + TASCommand last_input{}; // only used for recording + + // Old settings for swapping controllers + std::array<Settings::PlayerInput, 10> player_mappings; +}; +} // namespace TasInput diff --git a/src/input_common/tas/tas_poller.cpp b/src/input_common/tas/tas_poller.cpp new file mode 100644 index 000000000..15810d6b0 --- /dev/null +++ b/src/input_common/tas/tas_poller.cpp @@ -0,0 +1,101 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <mutex> +#include <utility> + +#include "common/settings.h" +#include "common/threadsafe_queue.h" +#include "input_common/tas/tas_input.h" +#include "input_common/tas/tas_poller.h" + +namespace InputCommon { + +class TasButton final : public Input::ButtonDevice { +public: + explicit TasButton(u32 button_, u32 pad_, const TasInput::Tas* tas_input_) + : button(button_), pad(pad_), tas_input(tas_input_) {} + + bool GetStatus() const override { + return (tas_input->GetTasState(pad).buttons & button) != 0; + } + +private: + const u32 button; + const u32 pad; + const TasInput::Tas* tas_input; +}; + +TasButtonFactory::TasButtonFactory(std::shared_ptr<TasInput::Tas> tas_input_) + : tas_input(std::move(tas_input_)) {} + +std::unique_ptr<Input::ButtonDevice> TasButtonFactory::Create(const Common::ParamPackage& params) { + const auto button_id = params.Get("button", 0); + const auto pad = params.Get("pad", 0); + + return std::make_unique<TasButton>(button_id, pad, tas_input.get()); +} + +class TasAnalog final : public Input::AnalogDevice { +public: + explicit TasAnalog(u32 pad_, u32 axis_x_, u32 axis_y_, const TasInput::Tas* tas_input_) + : pad(pad_), axis_x(axis_x_), axis_y(axis_y_), tas_input(tas_input_) {} + + float GetAxis(u32 axis) const { + std::lock_guard lock{mutex}; + return tas_input->GetTasState(pad).axis.at(axis); + } + + std::pair<float, float> GetAnalog(u32 analog_axis_x, u32 analog_axis_y) const { + float x = GetAxis(analog_axis_x); + float y = GetAxis(analog_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 { + return GetAnalog(axis_x, axis_y); + } + + Input::AnalogProperties GetAnalogProperties() const override { + return {0.0f, 1.0f, 0.5f}; + } + +private: + const u32 pad; + const u32 axis_x; + const u32 axis_y; + const TasInput::Tas* tas_input; + mutable std::mutex mutex; +}; + +/// An analog device factory that creates analog devices from GC Adapter +TasAnalogFactory::TasAnalogFactory(std::shared_ptr<TasInput::Tas> tas_input_) + : tas_input(std::move(tas_input_)) {} + +/** + * 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> TasAnalogFactory::Create(const Common::ParamPackage& params) { + const auto pad = static_cast<u32>(params.Get("pad", 0)); + const auto axis_x = static_cast<u32>(params.Get("axis_x", 0)); + const auto axis_y = static_cast<u32>(params.Get("axis_y", 1)); + + return std::make_unique<TasAnalog>(pad, axis_x, axis_y, tas_input.get()); +} + +} // namespace InputCommon diff --git a/src/input_common/tas/tas_poller.h b/src/input_common/tas/tas_poller.h new file mode 100644 index 000000000..09e426cef --- /dev/null +++ b/src/input_common/tas/tas_poller.h @@ -0,0 +1,43 @@ +// Copyright 2021 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/tas/tas_input.h" + +namespace InputCommon { + +/** + * A button device factory representing a tas bot. It receives tas events and forward them + * to all button devices it created. + */ +class TasButtonFactory final : public Input::Factory<Input::ButtonDevice> { +public: + explicit TasButtonFactory(std::shared_ptr<TasInput::Tas> tas_input_); + + /** + * 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; + +private: + std::shared_ptr<TasInput::Tas> tas_input; +}; + +/// An analog device factory that creates analog devices from tas +class TasAnalogFactory final : public Input::Factory<Input::AnalogDevice> { +public: + explicit TasAnalogFactory(std::shared_ptr<TasInput::Tas> tas_input_); + + std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override; + +private: + std::shared_ptr<TasInput::Tas> tas_input; +}; + +} // namespace InputCommon diff --git a/src/input_common/udp/client.h b/src/input_common/udp/client.h index a11ea3068..380f9bb76 100644 --- a/src/input_common/udp/client.h +++ b/src/input_common/udp/client.h @@ -21,8 +21,6 @@ namespace InputCommon::CemuhookUDP { -constexpr char DEFAULT_SRV[] = "127.0.0.1:26760"; - class Socket; namespace Response { diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp index 580063fa9..170db269a 100644 --- a/src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp +++ b/src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp @@ -58,8 +58,8 @@ void GetCbuf(EmitContext& ctx, std::string_view ret, const IR::Value& binding, const auto cbuf{fmt::format("{}_cbuf{}", ctx.stage_name, binding.U32())}; const auto cbuf_cast{fmt::format("{}({}[{}]{{}})", cast, cbuf, index)}; const auto extraction{num_bits == 32 ? cbuf_cast - : fmt ::format("bitfieldExtract({},int({}),{})", cbuf_cast, - bit_offset, num_bits)}; + : fmt::format("bitfieldExtract({},int({}),{})", cbuf_cast, + bit_offset, num_bits)}; if (!component_indexing_bug) { const auto result{fmt::format(fmt::runtime(extraction), swizzle)}; ctx.Add("{}={};", ret, result); diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_warp.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_warp.cpp index a982dd8a2..cd285e2c8 100644 --- a/src/shader_recompiler/backend/glsl/emit_glsl_warp.cpp +++ b/src/shader_recompiler/backend/glsl/emit_glsl_warp.cpp @@ -11,6 +11,8 @@ namespace Shader::Backend::GLSL { namespace { +constexpr char THREAD_ID[]{"gl_SubGroupInvocationARB"}; + void SetInBoundsFlag(EmitContext& ctx, IR::Inst& inst) { IR::Inst* const in_bounds{inst.GetAssociatedPseudoOperation(IR::Opcode::GetInBoundsFromOp)}; if (!in_bounds) { @@ -43,84 +45,100 @@ void UseShuffleNv(EmitContext& ctx, IR::Inst& inst, std::string_view shfl_op, ctx.AddU32("{}={}({},{},{},shfl_in_bounds);", inst, shfl_op, value, index, width); SetInBoundsFlag(ctx, inst); } + +std::string_view BallotIndex(EmitContext& ctx) { + if (!ctx.profile.warp_size_potentially_larger_than_guest) { + return ".x"; + } + return "[gl_SubGroupInvocationARB>>5]"; +} + +std::string GetMask(EmitContext& ctx, std::string_view mask) { + const auto ballot_index{BallotIndex(ctx)}; + return fmt::format("uint(uvec2({}){})", mask, ballot_index); +} } // Anonymous namespace void EmitLaneId(EmitContext& ctx, IR::Inst& inst) { - ctx.AddU32("{}=gl_SubGroupInvocationARB&31u;", inst); + ctx.AddU32("{}={}&31u;", inst, THREAD_ID); } void EmitVoteAll(EmitContext& ctx, IR::Inst& inst, std::string_view pred) { if (!ctx.profile.warp_size_potentially_larger_than_guest) { ctx.AddU1("{}=allInvocationsEqualARB({});", inst, pred); - } else { - const auto active_mask{fmt::format("uvec2(ballotARB(true))[gl_SubGroupInvocationARB]")}; - const auto ballot{fmt::format("uvec2(ballotARB({}))[gl_SubGroupInvocationARB]", pred)}; - ctx.AddU1("{}=({}&{})=={};", inst, ballot, active_mask, active_mask); + return; } + const auto ballot_index{BallotIndex(ctx)}; + const auto active_mask{fmt::format("uvec2(ballotARB(true)){}", ballot_index)}; + const auto ballot{fmt::format("uvec2(ballotARB({})){}", pred, ballot_index)}; + ctx.AddU1("{}=({}&{})=={};", inst, ballot, active_mask, active_mask); } void EmitVoteAny(EmitContext& ctx, IR::Inst& inst, std::string_view pred) { if (!ctx.profile.warp_size_potentially_larger_than_guest) { ctx.AddU1("{}=anyInvocationARB({});", inst, pred); - } else { - const auto active_mask{fmt::format("uvec2(ballotARB(true))[gl_SubGroupInvocationARB]")}; - const auto ballot{fmt::format("uvec2(ballotARB({}))[gl_SubGroupInvocationARB]", pred)}; - ctx.AddU1("{}=({}&{})!=0u;", inst, ballot, active_mask, active_mask); + return; } + const auto ballot_index{BallotIndex(ctx)}; + const auto active_mask{fmt::format("uvec2(ballotARB(true)){}", ballot_index)}; + const auto ballot{fmt::format("uvec2(ballotARB({})){}", pred, ballot_index)}; + ctx.AddU1("{}=({}&{})!=0u;", inst, ballot, active_mask, active_mask); } void EmitVoteEqual(EmitContext& ctx, IR::Inst& inst, std::string_view pred) { if (!ctx.profile.warp_size_potentially_larger_than_guest) { ctx.AddU1("{}=allInvocationsEqualARB({});", inst, pred); - } else { - const auto active_mask{fmt::format("uvec2(ballotARB(true))[gl_SubGroupInvocationARB]")}; - const auto ballot{fmt::format("uvec2(ballotARB({}))[gl_SubGroupInvocationARB]", pred)}; - const auto value{fmt::format("({}^{})", ballot, active_mask)}; - ctx.AddU1("{}=({}==0)||({}=={});", inst, value, value, active_mask); + return; } + const auto ballot_index{BallotIndex(ctx)}; + const auto active_mask{fmt::format("uvec2(ballotARB(true)){}", ballot_index)}; + const auto ballot{fmt::format("uvec2(ballotARB({})){}", pred, ballot_index)}; + const auto value{fmt::format("({}^{})", ballot, active_mask)}; + ctx.AddU1("{}=({}==0)||({}=={});", inst, value, value, active_mask); } void EmitSubgroupBallot(EmitContext& ctx, IR::Inst& inst, std::string_view pred) { - if (!ctx.profile.warp_size_potentially_larger_than_guest) { - ctx.AddU32("{}=uvec2(ballotARB({})).x;", inst, pred); - } else { - ctx.AddU32("{}=uvec2(ballotARB({}))[gl_SubGroupInvocationARB];", inst, pred); - } + const auto ballot_index{BallotIndex(ctx)}; + ctx.AddU32("{}=uvec2(ballotARB({})){};", inst, pred, ballot_index); } void EmitSubgroupEqMask(EmitContext& ctx, IR::Inst& inst) { - ctx.AddU32("{}=uint(gl_SubGroupEqMaskARB.x);", inst); + ctx.AddU32("{}={};", inst, GetMask(ctx, "gl_SubGroupEqMaskARB")); } void EmitSubgroupLtMask(EmitContext& ctx, IR::Inst& inst) { - ctx.AddU32("{}=uint(gl_SubGroupLtMaskARB.x);", inst); + ctx.AddU32("{}={};", inst, GetMask(ctx, "gl_SubGroupLtMaskARB")); } void EmitSubgroupLeMask(EmitContext& ctx, IR::Inst& inst) { - ctx.AddU32("{}=uint(gl_SubGroupLeMaskARB.x);", inst); + ctx.AddU32("{}={};", inst, GetMask(ctx, "gl_SubGroupLeMaskARB")); } void EmitSubgroupGtMask(EmitContext& ctx, IR::Inst& inst) { - ctx.AddU32("{}=uint(gl_SubGroupGtMaskARB.x);", inst); + ctx.AddU32("{}={};", inst, GetMask(ctx, "gl_SubGroupGtMaskARB")); } void EmitSubgroupGeMask(EmitContext& ctx, IR::Inst& inst) { - ctx.AddU32("{}=uint(gl_SubGroupGeMaskARB.x);", inst); + ctx.AddU32("{}={};", inst, GetMask(ctx, "gl_SubGroupGeMaskARB")); } void EmitShuffleIndex(EmitContext& ctx, IR::Inst& inst, std::string_view value, - std::string_view index, std::string_view clamp, - std::string_view segmentation_mask) { + std::string_view index, std::string_view clamp, std::string_view seg_mask) { if (ctx.profile.support_gl_warp_intrinsics) { - UseShuffleNv(ctx, inst, "shuffleNV", value, index, clamp, segmentation_mask); + UseShuffleNv(ctx, inst, "shuffleNV", value, index, clamp, seg_mask); return; } - const auto not_seg_mask{fmt::format("(~{})", segmentation_mask)}; - const auto thread_id{"gl_SubGroupInvocationARB"}; - const auto min_thread_id{ComputeMinThreadId(thread_id, segmentation_mask)}; - const auto max_thread_id{ComputeMaxThreadId(min_thread_id, clamp, not_seg_mask)}; + const bool big_warp{ctx.profile.warp_size_potentially_larger_than_guest}; + const auto is_upper_partition{"int(gl_SubGroupInvocationARB)>=32"}; + const auto upper_index{fmt::format("{}?{}+32:{}", is_upper_partition, index, index)}; + const auto upper_clamp{fmt::format("{}?{}+32:{}", is_upper_partition, clamp, clamp)}; + + const auto not_seg_mask{fmt::format("(~{})", seg_mask)}; + const auto min_thread_id{ComputeMinThreadId(THREAD_ID, seg_mask)}; + const auto max_thread_id{ + ComputeMaxThreadId(min_thread_id, big_warp ? upper_clamp : clamp, not_seg_mask)}; - const auto lhs{fmt::format("({}&{})", index, not_seg_mask)}; + const auto lhs{fmt::format("({}&{})", big_warp ? upper_index : index, not_seg_mask)}; const auto src_thread_id{fmt::format("({})|({})", lhs, min_thread_id)}; ctx.Add("shfl_in_bounds=int({})<=int({});", src_thread_id, max_thread_id); SetInBoundsFlag(ctx, inst); @@ -128,29 +146,34 @@ void EmitShuffleIndex(EmitContext& ctx, IR::Inst& inst, std::string_view value, } void EmitShuffleUp(EmitContext& ctx, IR::Inst& inst, std::string_view value, std::string_view index, - std::string_view clamp, std::string_view segmentation_mask) { + std::string_view clamp, std::string_view seg_mask) { if (ctx.profile.support_gl_warp_intrinsics) { - UseShuffleNv(ctx, inst, "shuffleUpNV", value, index, clamp, segmentation_mask); + UseShuffleNv(ctx, inst, "shuffleUpNV", value, index, clamp, seg_mask); return; } - const auto thread_id{"gl_SubGroupInvocationARB"}; - const auto max_thread_id{GetMaxThreadId(thread_id, clamp, segmentation_mask)}; - const auto src_thread_id{fmt::format("({}-{})", thread_id, index)}; + const bool big_warp{ctx.profile.warp_size_potentially_larger_than_guest}; + const auto is_upper_partition{"int(gl_SubGroupInvocationARB)>=32"}; + const auto upper_clamp{fmt::format("{}?{}+32:{}", is_upper_partition, clamp, clamp)}; + + const auto max_thread_id{GetMaxThreadId(THREAD_ID, big_warp ? upper_clamp : clamp, seg_mask)}; + const auto src_thread_id{fmt::format("({}-{})", THREAD_ID, index)}; ctx.Add("shfl_in_bounds=int({})>=int({});", src_thread_id, max_thread_id); SetInBoundsFlag(ctx, inst); ctx.AddU32("{}=shfl_in_bounds?readInvocationARB({},{}):{};", inst, value, src_thread_id, value); } void EmitShuffleDown(EmitContext& ctx, IR::Inst& inst, std::string_view value, - std::string_view index, std::string_view clamp, - std::string_view segmentation_mask) { + std::string_view index, std::string_view clamp, std::string_view seg_mask) { if (ctx.profile.support_gl_warp_intrinsics) { - UseShuffleNv(ctx, inst, "shuffleDownNV", value, index, clamp, segmentation_mask); + UseShuffleNv(ctx, inst, "shuffleDownNV", value, index, clamp, seg_mask); return; } - const auto thread_id{"gl_SubGroupInvocationARB"}; - const auto max_thread_id{GetMaxThreadId(thread_id, clamp, segmentation_mask)}; - const auto src_thread_id{fmt::format("({}+{})", thread_id, index)}; + const bool big_warp{ctx.profile.warp_size_potentially_larger_than_guest}; + const auto is_upper_partition{"int(gl_SubGroupInvocationARB)>=32"}; + const auto upper_clamp{fmt::format("{}?{}+32:{}", is_upper_partition, clamp, clamp)}; + + const auto max_thread_id{GetMaxThreadId(THREAD_ID, big_warp ? upper_clamp : clamp, seg_mask)}; + const auto src_thread_id{fmt::format("({}+{})", THREAD_ID, index)}; ctx.Add("shfl_in_bounds=int({})<=int({});", src_thread_id, max_thread_id); SetInBoundsFlag(ctx, inst); ctx.AddU32("{}=shfl_in_bounds?readInvocationARB({},{}):{};", inst, value, src_thread_id, value); @@ -158,14 +181,17 @@ void EmitShuffleDown(EmitContext& ctx, IR::Inst& inst, std::string_view value, void EmitShuffleButterfly(EmitContext& ctx, IR::Inst& inst, std::string_view value, std::string_view index, std::string_view clamp, - std::string_view segmentation_mask) { + std::string_view seg_mask) { if (ctx.profile.support_gl_warp_intrinsics) { - UseShuffleNv(ctx, inst, "shuffleXorNV", value, index, clamp, segmentation_mask); + UseShuffleNv(ctx, inst, "shuffleXorNV", value, index, clamp, seg_mask); return; } - const auto thread_id{"gl_SubGroupInvocationARB"}; - const auto max_thread_id{GetMaxThreadId(thread_id, clamp, segmentation_mask)}; - const auto src_thread_id{fmt::format("({}^{})", thread_id, index)}; + const bool big_warp{ctx.profile.warp_size_potentially_larger_than_guest}; + const auto is_upper_partition{"int(gl_SubGroupInvocationARB)>=32"}; + const auto upper_clamp{fmt::format("{}?{}+32:{}", is_upper_partition, clamp, clamp)}; + + const auto max_thread_id{GetMaxThreadId(THREAD_ID, big_warp ? upper_clamp : clamp, seg_mask)}; + const auto src_thread_id{fmt::format("({}^{})", THREAD_ID, index)}; ctx.Add("shfl_in_bounds=int({})<=int({});", src_thread_id, max_thread_id); SetInBoundsFlag(ctx, inst); ctx.AddU32("{}=shfl_in_bounds?readInvocationARB({},{}):{};", inst, value, src_thread_id, value); diff --git a/src/shader_recompiler/backend/spirv/emit_context.cpp b/src/shader_recompiler/backend/spirv/emit_context.cpp index 2d29d8c14..2885e6799 100644 --- a/src/shader_recompiler/backend/spirv/emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/emit_context.cpp @@ -15,6 +15,8 @@ namespace Shader::Backend::SPIRV { namespace { +constexpr size_t NUM_FIXEDFNCTEXTURE = 10; + enum class Operation { Increment, Decrement, @@ -427,6 +429,16 @@ Id DescType(EmitContext& ctx, Id sampled_type, Id pointer_type, u32 count) { return pointer_type; } } + +size_t FindNextUnusedLocation(const std::bitset<IR::NUM_GENERICS>& used_locations, + size_t start_offset) { + for (size_t location = start_offset; location < used_locations.size(); ++location) { + if (!used_locations.test(location)) { + return location; + } + } + throw RuntimeError("Unable to get an unused location for legacy attribute"); +} } // Anonymous namespace void VectorTypes::Define(Sirit::Module& sirit_ctx, Id base_type, std::string_view name) { @@ -1227,6 +1239,7 @@ void EmitContext::DefineInputs(const IR::Program& program) { loads[IR::Attribute::TessellationEvaluationPointV]) { tess_coord = DefineInput(*this, F32[3], false, spv::BuiltIn::TessCoord); } + std::bitset<IR::NUM_GENERICS> used_locations{}; for (size_t index = 0; index < IR::NUM_GENERICS; ++index) { const AttributeType input_type{runtime_info.generic_input_types[index]}; if (!runtime_info.previous_stage_stores.Generic(index)) { @@ -1238,6 +1251,7 @@ void EmitContext::DefineInputs(const IR::Program& program) { if (input_type == AttributeType::Disabled) { continue; } + used_locations.set(index); const Id type{GetAttributeType(*this, input_type)}; const Id id{DefineInput(*this, type, true)}; Decorate(id, spv::Decoration::Location, static_cast<u32>(index)); @@ -1263,6 +1277,26 @@ void EmitContext::DefineInputs(const IR::Program& program) { break; } } + size_t previous_unused_location = 0; + if (loads.AnyComponent(IR::Attribute::ColorFrontDiffuseR)) { + const size_t location = FindNextUnusedLocation(used_locations, previous_unused_location); + previous_unused_location = location; + used_locations.set(location); + const Id id{DefineInput(*this, F32[4], true)}; + Decorate(id, spv::Decoration::Location, location); + input_front_color = id; + } + for (size_t index = 0; index < NUM_FIXEDFNCTEXTURE; ++index) { + if (loads.AnyComponent(IR::Attribute::FixedFncTexture0S + index * 4)) { + const size_t location = + FindNextUnusedLocation(used_locations, previous_unused_location); + previous_unused_location = location; + used_locations.set(location); + const Id id{DefineInput(*this, F32[4], true)}; + Decorate(id, spv::Decoration::Location, location); + input_fixed_fnc_textures[index] = id; + } + } if (stage == Stage::TessellationEval) { for (size_t index = 0; index < info.uses_patches.size(); ++index) { if (!info.uses_patches[index]) { @@ -1313,9 +1347,31 @@ void EmitContext::DefineOutputs(const IR::Program& program) { viewport_mask = DefineOutput(*this, TypeArray(U32[1], Const(1u)), std::nullopt, spv::BuiltIn::ViewportMaskNV); } + std::bitset<IR::NUM_GENERICS> used_locations{}; for (size_t index = 0; index < IR::NUM_GENERICS; ++index) { if (info.stores.Generic(index)) { DefineGenericOutput(*this, index, invocations); + used_locations.set(index); + } + } + size_t previous_unused_location = 0; + if (info.stores.AnyComponent(IR::Attribute::ColorFrontDiffuseR)) { + const size_t location = FindNextUnusedLocation(used_locations, previous_unused_location); + previous_unused_location = location; + used_locations.set(location); + const Id id{DefineOutput(*this, F32[4], invocations)}; + Decorate(id, spv::Decoration::Location, static_cast<u32>(location)); + output_front_color = id; + } + for (size_t index = 0; index < NUM_FIXEDFNCTEXTURE; ++index) { + if (info.stores.AnyComponent(IR::Attribute::FixedFncTexture0S + index * 4)) { + const size_t location = + FindNextUnusedLocation(used_locations, previous_unused_location); + previous_unused_location = location; + used_locations.set(location); + const Id id{DefineOutput(*this, F32[4], invocations)}; + Decorate(id, spv::Decoration::Location, location); + output_fixed_fnc_textures[index] = id; } } switch (stage) { diff --git a/src/shader_recompiler/backend/spirv/emit_context.h b/src/shader_recompiler/backend/spirv/emit_context.h index e277bc358..847d0c0e6 100644 --- a/src/shader_recompiler/backend/spirv/emit_context.h +++ b/src/shader_recompiler/backend/spirv/emit_context.h @@ -268,10 +268,14 @@ public: Id write_global_func_u32x4{}; Id input_position{}; + Id input_front_color{}; + std::array<Id, 10> input_fixed_fnc_textures{}; std::array<Id, 32> input_generics{}; Id output_point_size{}; Id output_position{}; + Id output_front_color{}; + std::array<Id, 10> output_fixed_fnc_textures{}; std::array<std::array<GenericElementInfo, 4>, 32> output_generics{}; Id output_tess_level_outer{}; diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp index 9e54a17ee..6f60c6574 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp @@ -43,6 +43,25 @@ Id AttrPointer(EmitContext& ctx, Id pointer_type, Id vertex, Id base, Args&&... } } +bool IsFixedFncTexture(IR::Attribute attribute) { + return attribute >= IR::Attribute::FixedFncTexture0S && + attribute <= IR::Attribute::FixedFncTexture9Q; +} + +u32 FixedFncTextureAttributeIndex(IR::Attribute attribute) { + if (!IsFixedFncTexture(attribute)) { + throw InvalidArgument("Attribute {} is not a FixedFncTexture", attribute); + } + return (static_cast<u32>(attribute) - static_cast<u32>(IR::Attribute::FixedFncTexture0S)) / 4u; +} + +u32 FixedFncTextureAttributeElement(IR::Attribute attribute) { + if (!IsFixedFncTexture(attribute)) { + throw InvalidArgument("Attribute {} is not a FixedFncTexture", attribute); + } + return static_cast<u32>(attribute) % 4u; +} + template <typename... Args> Id OutputAccessChain(EmitContext& ctx, Id result_type, Id base, Args&&... args) { if (ctx.stage == Stage::TessellationControl) { @@ -74,6 +93,13 @@ std::optional<OutAttr> OutputAttrPointer(EmitContext& ctx, IR::Attribute attr) { return OutputAccessChain(ctx, ctx.output_f32, info.id, index_id); } } + if (IsFixedFncTexture(attr)) { + const u32 index{FixedFncTextureAttributeIndex(attr)}; + const u32 element{FixedFncTextureAttributeElement(attr)}; + const Id element_id{ctx.Const(element)}; + return OutputAccessChain(ctx, ctx.output_f32, ctx.output_fixed_fnc_textures[index], + element_id); + } switch (attr) { case IR::Attribute::PointSize: return ctx.output_point_size; @@ -85,6 +111,14 @@ std::optional<OutAttr> OutputAttrPointer(EmitContext& ctx, IR::Attribute attr) { const Id element_id{ctx.Const(element)}; return OutputAccessChain(ctx, ctx.output_f32, ctx.output_position, element_id); } + case IR::Attribute::ColorFrontDiffuseR: + case IR::Attribute::ColorFrontDiffuseG: + case IR::Attribute::ColorFrontDiffuseB: + case IR::Attribute::ColorFrontDiffuseA: { + const u32 element{static_cast<u32>(attr) % 4}; + const Id element_id{ctx.Const(element)}; + return OutputAccessChain(ctx, ctx.output_f32, ctx.output_front_color, element_id); + } case IR::Attribute::ClipDistance0: case IR::Attribute::ClipDistance1: case IR::Attribute::ClipDistance2: @@ -307,6 +341,12 @@ Id EmitGetAttribute(EmitContext& ctx, IR::Attribute attr, Id vertex) { const Id value{ctx.OpLoad(type->id, pointer)}; return type->needs_cast ? ctx.OpBitcast(ctx.F32[1], value) : value; } + if (IsFixedFncTexture(attr)) { + const u32 index{FixedFncTextureAttributeIndex(attr)}; + const Id attr_id{ctx.input_fixed_fnc_textures[index]}; + const Id attr_ptr{AttrPointer(ctx, ctx.input_f32, vertex, attr_id, ctx.Const(element))}; + return ctx.OpLoad(ctx.F32[1], attr_ptr); + } switch (attr) { case IR::Attribute::PrimitiveId: return ctx.OpBitcast(ctx.F32[1], ctx.OpLoad(ctx.U32[1], ctx.primitive_id)); @@ -316,6 +356,13 @@ Id EmitGetAttribute(EmitContext& ctx, IR::Attribute attr, Id vertex) { case IR::Attribute::PositionW: return ctx.OpLoad(ctx.F32[1], AttrPointer(ctx, ctx.input_f32, vertex, ctx.input_position, ctx.Const(element))); + case IR::Attribute::ColorFrontDiffuseR: + case IR::Attribute::ColorFrontDiffuseG: + case IR::Attribute::ColorFrontDiffuseB: + case IR::Attribute::ColorFrontDiffuseA: { + return ctx.OpLoad(ctx.F32[1], AttrPointer(ctx, ctx.input_f32, vertex, ctx.input_front_color, + ctx.Const(element))); + } case IR::Attribute::InstanceId: if (ctx.profile.support_vertex_instance_id) { return ctx.OpBitcast(ctx.F32[1], ctx.OpLoad(ctx.U32[1], ctx.instance_id)); @@ -430,7 +477,13 @@ void EmitSetSampleMask(EmitContext& ctx, Id value) { } void EmitSetFragDepth(EmitContext& ctx, Id value) { - ctx.OpStore(ctx.frag_depth, value); + if (!ctx.runtime_info.convert_depth_mode) { + ctx.OpStore(ctx.frag_depth, value); + return; + } + const Id unit{ctx.Const(0.5f)}; + const Id new_depth{ctx.OpFma(ctx.F32[1], value, unit, unit)}; + ctx.OpStore(ctx.frag_depth, new_depth); } void EmitGetZFlag(EmitContext&) { diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp index 78b1e1ba7..cef52c56e 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp @@ -7,8 +7,13 @@ namespace Shader::Backend::SPIRV { namespace { +Id GetThreadId(EmitContext& ctx) { + return ctx.OpLoad(ctx.U32[1], ctx.subgroup_local_invocation_id); +} + Id WarpExtract(EmitContext& ctx, Id value) { - const Id local_index{ctx.OpLoad(ctx.U32[1], ctx.subgroup_local_invocation_id)}; + const Id thread_id{GetThreadId(ctx)}; + const Id local_index{ctx.OpShiftRightArithmetic(ctx.U32[1], thread_id, ctx.Const(5U))}; return ctx.OpVectorExtractDynamic(ctx.U32[1], value, local_index); } @@ -48,10 +53,17 @@ Id SelectValue(EmitContext& ctx, Id in_range, Id value, Id src_thread_id) { return ctx.OpSelect(ctx.U32[1], in_range, ctx.OpSubgroupReadInvocationKHR(ctx.U32[1], value, src_thread_id), value); } + +Id GetUpperClamp(EmitContext& ctx, Id invocation_id, Id clamp) { + const Id thirty_two{ctx.Const(32u)}; + const Id is_upper_partition{ctx.OpSGreaterThanEqual(ctx.U1, invocation_id, thirty_two)}; + const Id upper_clamp{ctx.OpIAdd(ctx.U32[1], thirty_two, clamp)}; + return ctx.OpSelect(ctx.U32[1], is_upper_partition, upper_clamp, clamp); +} } // Anonymous namespace Id EmitLaneId(EmitContext& ctx) { - const Id id{ctx.OpLoad(ctx.U32[1], ctx.subgroup_local_invocation_id)}; + const Id id{GetThreadId(ctx)}; if (!ctx.profile.warp_size_potentially_larger_than_guest) { return id; } @@ -123,7 +135,15 @@ Id EmitSubgroupGeMask(EmitContext& ctx) { Id EmitShuffleIndex(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp, Id segmentation_mask) { const Id not_seg_mask{ctx.OpNot(ctx.U32[1], segmentation_mask)}; - const Id thread_id{ctx.OpLoad(ctx.U32[1], ctx.subgroup_local_invocation_id)}; + const Id thread_id{GetThreadId(ctx)}; + if (ctx.profile.warp_size_potentially_larger_than_guest) { + const Id thirty_two{ctx.Const(32u)}; + const Id is_upper_partition{ctx.OpSGreaterThanEqual(ctx.U1, thread_id, thirty_two)}; + const Id upper_index{ctx.OpIAdd(ctx.U32[1], thirty_two, index)}; + const Id upper_clamp{ctx.OpIAdd(ctx.U32[1], thirty_two, clamp)}; + index = ctx.OpSelect(ctx.U32[1], is_upper_partition, upper_index, index); + clamp = ctx.OpSelect(ctx.U32[1], is_upper_partition, upper_clamp, clamp); + } const Id min_thread_id{ComputeMinThreadId(ctx, thread_id, segmentation_mask)}; const Id max_thread_id{ComputeMaxThreadId(ctx, min_thread_id, clamp, not_seg_mask)}; @@ -137,7 +157,10 @@ Id EmitShuffleIndex(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id cla Id EmitShuffleUp(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp, Id segmentation_mask) { - const Id thread_id{ctx.OpLoad(ctx.U32[1], ctx.subgroup_local_invocation_id)}; + const Id thread_id{GetThreadId(ctx)}; + if (ctx.profile.warp_size_potentially_larger_than_guest) { + clamp = GetUpperClamp(ctx, thread_id, clamp); + } const Id max_thread_id{GetMaxThreadId(ctx, thread_id, clamp, segmentation_mask)}; const Id src_thread_id{ctx.OpISub(ctx.U32[1], thread_id, index)}; const Id in_range{ctx.OpSGreaterThanEqual(ctx.U1, src_thread_id, max_thread_id)}; @@ -148,7 +171,10 @@ Id EmitShuffleUp(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp, Id EmitShuffleDown(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp, Id segmentation_mask) { - const Id thread_id{ctx.OpLoad(ctx.U32[1], ctx.subgroup_local_invocation_id)}; + const Id thread_id{GetThreadId(ctx)}; + if (ctx.profile.warp_size_potentially_larger_than_guest) { + clamp = GetUpperClamp(ctx, thread_id, clamp); + } const Id max_thread_id{GetMaxThreadId(ctx, thread_id, clamp, segmentation_mask)}; const Id src_thread_id{ctx.OpIAdd(ctx.U32[1], thread_id, index)}; const Id in_range{ctx.OpSLessThanEqual(ctx.U1, src_thread_id, max_thread_id)}; @@ -159,7 +185,10 @@ Id EmitShuffleDown(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clam Id EmitShuffleButterfly(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp, Id segmentation_mask) { - const Id thread_id{ctx.OpLoad(ctx.U32[1], ctx.subgroup_local_invocation_id)}; + const Id thread_id{GetThreadId(ctx)}; + if (ctx.profile.warp_size_potentially_larger_than_guest) { + clamp = GetUpperClamp(ctx, thread_id, clamp); + } const Id max_thread_id{GetMaxThreadId(ctx, thread_id, clamp, segmentation_mask)}; const Id src_thread_id{ctx.OpBitwiseXor(ctx.U32[1], thread_id, index)}; const Id in_range{ctx.OpSLessThanEqual(ctx.U1, src_thread_id, max_thread_id)}; diff --git a/src/shader_recompiler/object_pool.h b/src/shader_recompiler/object_pool.h index f3b12d04b..a12ddcc8f 100644 --- a/src/shader_recompiler/object_pool.h +++ b/src/shader_recompiler/object_pool.h @@ -11,14 +11,16 @@ namespace Shader { template <typename T> -requires std::is_destructible_v<T> class ObjectPool { +requires std::is_destructible_v<T> +class ObjectPool { public: explicit ObjectPool(size_t chunk_size = 8192) : new_chunk_size{chunk_size} { node = &chunks.emplace_back(new_chunk_size); } template <typename... Args> - requires std::is_constructible_v<T, Args...>[[nodiscard]] T* Create(Args&&... args) { + requires std::is_constructible_v<T, Args...> + [[nodiscard]] T* Create(Args&&... args) { return std::construct_at(Memory(), std::forward<Args>(args)...); } diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 2f6cdd216..269db21a5 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -231,6 +231,7 @@ endif() target_include_directories(video_core PRIVATE ${FFmpeg_INCLUDE_DIR}) target_link_libraries(video_core PRIVATE ${FFmpeg_LIBRARIES}) +target_link_options(video_core PRIVATE ${FFmpeg_LDFLAGS}) add_dependencies(video_core host_shaders) target_include_directories(video_core PRIVATE ${HOST_SHADERS_INCLUDE}) diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 7bfd57369..d350c9b36 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -570,13 +570,12 @@ bool BufferCache<P>::DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 am ForEachWrittenRange(*cpu_src_address, amount, mirror); // This subtraction in this order is important for overlapping copies. common_ranges.subtract(subtract_interval); - bool atleast_1_download = tmp_intervals.size() != 0; - for (const IntervalType add_interval : tmp_intervals) { + const bool has_new_downloads = tmp_intervals.size() != 0; + for (const IntervalType& add_interval : tmp_intervals) { common_ranges.add(add_interval); } - runtime.CopyBuffer(dest_buffer, src_buffer, copies); - if (atleast_1_download) { + if (has_new_downloads) { dest_buffer.MarkRegionAsGpuModified(*cpu_dest_address, amount); } std::vector<u8> tmp_buffer(amount); diff --git a/src/video_core/command_classes/codecs/codec.cpp b/src/video_core/command_classes/codecs/codec.cpp index f798a0053..61966cbfe 100644 --- a/src/video_core/command_classes/codecs/codec.cpp +++ b/src/video_core/command_classes/codecs/codec.cpp @@ -5,6 +5,7 @@ #include <fstream> #include <vector> #include "common/assert.h" +#include "common/settings.h" #include "video_core/command_classes/codecs/codec.h" #include "video_core/command_classes/codecs/h264.h" #include "video_core/command_classes/codecs/vp9.h" @@ -16,108 +17,146 @@ extern "C" { } namespace Tegra { -#if defined(LIBVA_FOUND) -// Hardware acceleration code from FFmpeg/doc/examples/hw_decode.c originally under MIT license namespace { -constexpr std::array<const char*, 2> VAAPI_DRIVERS = { - "i915", - "amdgpu", -}; +constexpr AVPixelFormat PREFERRED_GPU_FMT = AV_PIX_FMT_NV12; +constexpr AVPixelFormat PREFERRED_CPU_FMT = AV_PIX_FMT_YUV420P; + +void AVPacketDeleter(AVPacket* ptr) { + av_packet_free(&ptr); +} -AVPixelFormat GetHwFormat(AVCodecContext*, const AVPixelFormat* pix_fmts) { +using AVPacketPtr = std::unique_ptr<AVPacket, decltype(&AVPacketDeleter)>; + +AVPixelFormat GetGpuFormat(AVCodecContext* av_codec_ctx, const AVPixelFormat* pix_fmts) { for (const AVPixelFormat* p = pix_fmts; *p != AV_PIX_FMT_NONE; ++p) { - if (*p == AV_PIX_FMT_VAAPI) { - return AV_PIX_FMT_VAAPI; + if (*p == av_codec_ctx->pix_fmt) { + return av_codec_ctx->pix_fmt; } } LOG_INFO(Service_NVDRV, "Could not find compatible GPU AV format, falling back to CPU"); - return *pix_fmts; + av_buffer_unref(&av_codec_ctx->hw_device_ctx); + av_codec_ctx->pix_fmt = PREFERRED_CPU_FMT; + return PREFERRED_CPU_FMT; +} +} // namespace + +void AVFrameDeleter(AVFrame* ptr) { + av_frame_free(&ptr); } -bool CreateVaapiHwdevice(AVBufferRef** av_hw_device) { +Codec::Codec(GPU& gpu_, const NvdecCommon::NvdecRegisters& regs) + : gpu(gpu_), state{regs}, h264_decoder(std::make_unique<Decoder::H264>(gpu)), + vp9_decoder(std::make_unique<Decoder::VP9>(gpu)) {} + +Codec::~Codec() { + if (!initialized) { + return; + } + // Free libav memory + avcodec_free_context(&av_codec_ctx); + av_buffer_unref(&av_gpu_decoder); +} + +bool Codec::CreateGpuAvDevice() { +#if defined(LIBVA_FOUND) + static constexpr std::array<const char*, 3> VAAPI_DRIVERS = { + "i915", + "iHD", + "amdgpu", + }; AVDictionary* hwdevice_options = nullptr; av_dict_set(&hwdevice_options, "connection_type", "drm", 0); for (const auto& driver : VAAPI_DRIVERS) { av_dict_set(&hwdevice_options, "kernel_driver", driver, 0); - const int hwdevice_error = av_hwdevice_ctx_create(av_hw_device, AV_HWDEVICE_TYPE_VAAPI, + const int hwdevice_error = av_hwdevice_ctx_create(&av_gpu_decoder, AV_HWDEVICE_TYPE_VAAPI, nullptr, hwdevice_options, 0); if (hwdevice_error >= 0) { LOG_INFO(Service_NVDRV, "Using VA-API with {}", driver); av_dict_free(&hwdevice_options); + av_codec_ctx->pix_fmt = AV_PIX_FMT_VAAPI; return true; } LOG_DEBUG(Service_NVDRV, "VA-API av_hwdevice_ctx_create failed {}", hwdevice_error); } LOG_DEBUG(Service_NVDRV, "VA-API av_hwdevice_ctx_create failed for all drivers"); av_dict_free(&hwdevice_options); - return false; -} -} // namespace #endif - -void AVFrameDeleter(AVFrame* ptr) { - av_frame_free(&ptr); + static constexpr auto HW_CONFIG_METHOD = AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX; + static constexpr std::array GPU_DECODER_TYPES{ + AV_HWDEVICE_TYPE_CUDA, +#ifdef _WIN32 + AV_HWDEVICE_TYPE_D3D11VA, +#else + AV_HWDEVICE_TYPE_VDPAU, +#endif + }; + for (const auto& type : GPU_DECODER_TYPES) { + const int hwdevice_res = av_hwdevice_ctx_create(&av_gpu_decoder, type, nullptr, nullptr, 0); + if (hwdevice_res < 0) { + LOG_DEBUG(Service_NVDRV, "{} av_hwdevice_ctx_create failed {}", + av_hwdevice_get_type_name(type), hwdevice_res); + continue; + } + for (int i = 0;; i++) { + const AVCodecHWConfig* config = avcodec_get_hw_config(av_codec, i); + if (!config) { + LOG_DEBUG(Service_NVDRV, "{} decoder does not support device type {}.", + av_codec->name, av_hwdevice_get_type_name(type)); + break; + } + if (config->methods & HW_CONFIG_METHOD && config->device_type == type) { + av_codec_ctx->pix_fmt = config->pix_fmt; + LOG_INFO(Service_NVDRV, "Using {} GPU decoder", av_hwdevice_get_type_name(type)); + return true; + } + } + } + return false; } -Codec::Codec(GPU& gpu_, const NvdecCommon::NvdecRegisters& regs) - : gpu(gpu_), state{regs}, h264_decoder(std::make_unique<Decoder::H264>(gpu)), - vp9_decoder(std::make_unique<Decoder::VP9>(gpu)) {} - -Codec::~Codec() { - if (!initialized) { - return; - } - // Free libav memory - avcodec_send_packet(av_codec_ctx, nullptr); - AVFrame* av_frame = av_frame_alloc(); - avcodec_receive_frame(av_codec_ctx, av_frame); - avcodec_flush_buffers(av_codec_ctx); - av_frame_free(&av_frame); - avcodec_close(av_codec_ctx); - av_buffer_unref(&av_hw_device); +void Codec::InitializeAvCodecContext() { + av_codec_ctx = avcodec_alloc_context3(av_codec); + av_opt_set(av_codec_ctx->priv_data, "tune", "zerolatency", 0); } -void Codec::InitializeHwdec() { - // Prioritize integrated GPU to mitigate bandwidth bottlenecks -#if defined(LIBVA_FOUND) - if (CreateVaapiHwdevice(&av_hw_device)) { - const auto hw_device_ctx = av_buffer_ref(av_hw_device); - ASSERT_MSG(hw_device_ctx, "av_buffer_ref failed"); - av_codec_ctx->hw_device_ctx = hw_device_ctx; - av_codec_ctx->get_format = GetHwFormat; +void Codec::InitializeGpuDecoder() { + if (!CreateGpuAvDevice()) { + av_buffer_unref(&av_gpu_decoder); return; } -#endif - // TODO more GPU accelerated decoders + auto* hw_device_ctx = av_buffer_ref(av_gpu_decoder); + ASSERT_MSG(hw_device_ctx, "av_buffer_ref failed"); + av_codec_ctx->hw_device_ctx = hw_device_ctx; + av_codec_ctx->get_format = GetGpuFormat; } void Codec::Initialize() { - AVCodecID codec; - switch (current_codec) { - case NvdecCommon::VideoCodec::H264: - codec = AV_CODEC_ID_H264; - break; - case NvdecCommon::VideoCodec::Vp9: - codec = AV_CODEC_ID_VP9; - break; - default: - UNIMPLEMENTED_MSG("Unknown codec {}", current_codec); + const AVCodecID codec = [&] { + switch (current_codec) { + case NvdecCommon::VideoCodec::H264: + return AV_CODEC_ID_H264; + case NvdecCommon::VideoCodec::Vp9: + return AV_CODEC_ID_VP9; + default: + UNIMPLEMENTED_MSG("Unknown codec {}", current_codec); + return AV_CODEC_ID_NONE; + } + }(); + av_codec = avcodec_find_decoder(codec); + + InitializeAvCodecContext(); + if (Settings::values.nvdec_emulation.GetValue() == Settings::NvdecEmulation::GPU) { + InitializeGpuDecoder(); + } + if (const int res = avcodec_open2(av_codec_ctx, av_codec, nullptr); res < 0) { + LOG_ERROR(Service_NVDRV, "avcodec_open2() Failed with result {}", res); + avcodec_free_context(&av_codec_ctx); + av_buffer_unref(&av_gpu_decoder); return; } - av_codec = avcodec_find_decoder(codec); - av_codec_ctx = avcodec_alloc_context3(av_codec); - av_opt_set(av_codec_ctx->priv_data, "tune", "zerolatency", 0); - InitializeHwdec(); if (!av_codec_ctx->hw_device_ctx) { LOG_INFO(Service_NVDRV, "Using FFmpeg software decoding"); } - const auto av_error = avcodec_open2(av_codec_ctx, av_codec, nullptr); - if (av_error < 0) { - LOG_ERROR(Service_NVDRV, "avcodec_open2() Failed."); - avcodec_close(av_codec_ctx); - av_buffer_unref(&av_hw_device); - return; - } initialized = true; } @@ -133,6 +172,9 @@ void Codec::Decode() { if (is_first_frame) { Initialize(); } + if (!initialized) { + return; + } bool vp9_hidden_frame = false; std::vector<u8> frame_data; if (current_codec == NvdecCommon::VideoCodec::H264) { @@ -141,50 +183,48 @@ void Codec::Decode() { frame_data = vp9_decoder->ComposeFrameHeader(state); vp9_hidden_frame = vp9_decoder->WasFrameHidden(); } - AVPacket packet{}; - av_init_packet(&packet); - packet.data = frame_data.data(); - packet.size = static_cast<s32>(frame_data.size()); - if (const int ret = avcodec_send_packet(av_codec_ctx, &packet); ret) { - LOG_DEBUG(Service_NVDRV, "avcodec_send_packet error {}", ret); + AVPacketPtr packet{av_packet_alloc(), AVPacketDeleter}; + if (!packet) { + LOG_ERROR(Service_NVDRV, "av_packet_alloc failed"); + return; + } + packet->data = frame_data.data(); + packet->size = static_cast<s32>(frame_data.size()); + if (const int res = avcodec_send_packet(av_codec_ctx, packet.get()); res != 0) { + LOG_DEBUG(Service_NVDRV, "avcodec_send_packet error {}", res); return; } // Only receive/store visible frames if (vp9_hidden_frame) { return; } - AVFrame* hw_frame = av_frame_alloc(); - AVFrame* sw_frame = hw_frame; - ASSERT_MSG(hw_frame, "av_frame_alloc hw_frame failed"); - if (const int ret = avcodec_receive_frame(av_codec_ctx, hw_frame); ret) { + AVFramePtr initial_frame{av_frame_alloc(), AVFrameDeleter}; + AVFramePtr final_frame{nullptr, AVFrameDeleter}; + ASSERT_MSG(initial_frame, "av_frame_alloc initial_frame failed"); + if (const int ret = avcodec_receive_frame(av_codec_ctx, initial_frame.get()); ret) { LOG_DEBUG(Service_NVDRV, "avcodec_receive_frame error {}", ret); - av_frame_free(&hw_frame); return; } - if (!hw_frame->width || !hw_frame->height) { + if (initial_frame->width == 0 || initial_frame->height == 0) { LOG_WARNING(Service_NVDRV, "Zero width or height in frame"); - av_frame_free(&hw_frame); return; } -#if defined(LIBVA_FOUND) - // Hardware acceleration code from FFmpeg/doc/examples/hw_decode.c under MIT license - if (hw_frame->format == AV_PIX_FMT_VAAPI) { - sw_frame = av_frame_alloc(); - ASSERT_MSG(sw_frame, "av_frame_alloc sw_frame failed"); + if (av_codec_ctx->hw_device_ctx) { + final_frame = AVFramePtr{av_frame_alloc(), AVFrameDeleter}; + ASSERT_MSG(final_frame, "av_frame_alloc final_frame failed"); // Can't use AV_PIX_FMT_YUV420P and share code with software decoding in vic.cpp // because Intel drivers crash unless using AV_PIX_FMT_NV12 - sw_frame->format = AV_PIX_FMT_NV12; - const int transfer_data_ret = av_hwframe_transfer_data(sw_frame, hw_frame, 0); - ASSERT_MSG(!transfer_data_ret, "av_hwframe_transfer_data error {}", transfer_data_ret); - av_frame_free(&hw_frame); + final_frame->format = PREFERRED_GPU_FMT; + const int ret = av_hwframe_transfer_data(final_frame.get(), initial_frame.get(), 0); + ASSERT_MSG(!ret, "av_hwframe_transfer_data error {}", ret); + } else { + final_frame = std::move(initial_frame); } -#endif - if (sw_frame->format != AV_PIX_FMT_YUV420P && sw_frame->format != AV_PIX_FMT_NV12) { - UNIMPLEMENTED_MSG("Unexpected video format from host graphics: {}", sw_frame->format); - av_frame_free(&sw_frame); + if (final_frame->format != PREFERRED_CPU_FMT && final_frame->format != PREFERRED_GPU_FMT) { + UNIMPLEMENTED_MSG("Unexpected video format: {}", final_frame->format); return; } - av_frames.push(AVFramePtr{sw_frame, AVFrameDeleter}); + av_frames.push(std::move(final_frame)); if (av_frames.size() > 10) { LOG_TRACE(Service_NVDRV, "av_frames.push overflow dropped frame"); av_frames.pop(); diff --git a/src/video_core/command_classes/codecs/codec.h b/src/video_core/command_classes/codecs/codec.h index 71936203f..f9a80886f 100644 --- a/src/video_core/command_classes/codecs/codec.h +++ b/src/video_core/command_classes/codecs/codec.h @@ -5,6 +5,7 @@ #pragma once #include <memory> +#include <string_view> #include <queue> #include "common/common_types.h" #include "video_core/command_classes/nvdec_common.h" @@ -50,18 +51,23 @@ public: /// Returns the value of current_codec [[nodiscard]] NvdecCommon::VideoCodec GetCurrentCodec() const; + /// Return name of the current codec [[nodiscard]] std::string_view GetCurrentCodecName() const; private: - void InitializeHwdec(); + void InitializeAvCodecContext(); + + void InitializeGpuDecoder(); + + bool CreateGpuAvDevice(); bool initialized{}; NvdecCommon::VideoCodec current_codec{NvdecCommon::VideoCodec::None}; AVCodec* av_codec{nullptr}; - AVBufferRef* av_hw_device{nullptr}; AVCodecContext* av_codec_ctx{nullptr}; + AVBufferRef* av_gpu_decoder{nullptr}; GPU& gpu; const NvdecCommon::NvdecRegisters& state; diff --git a/src/video_core/command_classes/codecs/h264.cpp b/src/video_core/command_classes/codecs/h264.cpp index 5fb6d45ee..51ee14c13 100644 --- a/src/video_core/command_classes/codecs/h264.cpp +++ b/src/video_core/command_classes/codecs/h264.cpp @@ -95,7 +95,8 @@ const std::vector<u8>& H264::ComposeFrameHeader(const NvdecCommon::NvdecRegister const s32 pic_height = context.h264_parameter_set.frame_height_in_map_units / (context.h264_parameter_set.frame_mbs_only_flag ? 1 : 2); - writer.WriteUe(16); + // TODO (ameerj): Where do we get this number, it seems to be particular for each stream + writer.WriteUe(6); // Max number of reference frames writer.WriteBit(false); writer.WriteUe(context.h264_parameter_set.pic_width_in_mbs - 1); writer.WriteUe(pic_height - 1); diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index 1aa43523a..7f4ca6282 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -475,10 +475,10 @@ public: // These values are used by Nouveau and some games. AddGL = 0x8006, - SubtractGL = 0x8007, - ReverseSubtractGL = 0x8008, - MinGL = 0x800a, - MaxGL = 0x800b + MinGL = 0x8007, + MaxGL = 0x8008, + SubtractGL = 0x800a, + ReverseSubtractGL = 0x800b }; enum class Factor : u32 { diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp index c7ec1eac9..67388d980 100644 --- a/src/video_core/engines/maxwell_dma.cpp +++ b/src/video_core/engines/maxwell_dma.cpp @@ -82,41 +82,41 @@ void MaxwellDMA::Launch() { } void MaxwellDMA::CopyPitchToPitch() { - // When `multi_line_enable` bit is disabled the copy is performed as if we were copying a 1D - // buffer of length `line_length_in`. - // Otherwise we copy a 2D image of dimensions (line_length_in, line_count). - auto& accelerate = rasterizer->AccessAccelerateDMA(); - if (!regs.launch_dma.multi_line_enable) { - const bool is_buffer_clear = regs.launch_dma.remap_enable != 0 && - regs.remap_const.dst_x == RemapConst::Swizzle::CONST_A; - // TODO: allow multisized components. - if (is_buffer_clear) { - ASSERT(regs.remap_const.component_size_minus_one == 3); - accelerate.BufferClear(regs.offset_out, regs.line_length_in, regs.remap_consta_value); - std::vector<u32> tmp_buffer(regs.line_length_in, regs.remap_consta_value); - memory_manager.WriteBlockUnsafe(regs.offset_out, - reinterpret_cast<u8*>(tmp_buffer.data()), - regs.line_length_in * sizeof(u32)); - return; - } - UNIMPLEMENTED_IF(regs.launch_dma.remap_enable != 0); - if (!accelerate.BufferCopy(regs.offset_in, regs.offset_out, regs.line_length_in)) { - std::vector<u8> tmp_buffer(regs.line_length_in); - memory_manager.ReadBlockUnsafe(regs.offset_in, tmp_buffer.data(), regs.line_length_in); - memory_manager.WriteBlock(regs.offset_out, tmp_buffer.data(), regs.line_length_in); + // When `multi_line_enable` bit is enabled we copy a 2D image of dimensions + // (line_length_in, line_count). + // Otherwise the copy is performed as if we were copying a 1D buffer of length line_length_in. + const bool remap_enabled = regs.launch_dma.remap_enable != 0; + if (regs.launch_dma.multi_line_enable) { + UNIMPLEMENTED_IF(remap_enabled); + + // Perform a line-by-line copy. + // We're going to take a subrect of size (line_length_in, line_count) from the source + // rectangle. There is no need to manually flush/invalidate the regions because CopyBlock + // does that for us. + for (u32 line = 0; line < regs.line_count; ++line) { + const GPUVAddr source_line = regs.offset_in + static_cast<size_t>(line) * regs.pitch_in; + const GPUVAddr dest_line = regs.offset_out + static_cast<size_t>(line) * regs.pitch_out; + memory_manager.CopyBlock(dest_line, source_line, regs.line_length_in); } return; } - - UNIMPLEMENTED_IF(regs.launch_dma.remap_enable != 0); - - // Perform a line-by-line copy. - // We're going to take a subrect of size (line_length_in, line_count) from the source rectangle. - // There is no need to manually flush/invalidate the regions because CopyBlock does that for us. - for (u32 line = 0; line < regs.line_count; ++line) { - const GPUVAddr source_line = regs.offset_in + static_cast<size_t>(line) * regs.pitch_in; - const GPUVAddr dest_line = regs.offset_out + static_cast<size_t>(line) * regs.pitch_out; - memory_manager.CopyBlock(dest_line, source_line, regs.line_length_in); + // TODO: allow multisized components. + auto& accelerate = rasterizer->AccessAccelerateDMA(); + const bool is_const_a_dst = regs.remap_const.dst_x == RemapConst::Swizzle::CONST_A; + const bool is_buffer_clear = remap_enabled && is_const_a_dst; + if (is_buffer_clear) { + ASSERT(regs.remap_const.component_size_minus_one == 3); + accelerate.BufferClear(regs.offset_out, regs.line_length_in, regs.remap_consta_value); + std::vector<u32> tmp_buffer(regs.line_length_in, regs.remap_consta_value); + memory_manager.WriteBlockUnsafe(regs.offset_out, reinterpret_cast<u8*>(tmp_buffer.data()), + regs.line_length_in * sizeof(u32)); + return; + } + UNIMPLEMENTED_IF(remap_enabled); + if (!accelerate.BufferCopy(regs.offset_in, regs.offset_out, regs.line_length_in)) { + std::vector<u8> tmp_buffer(regs.line_length_in); + memory_manager.ReadBlockUnsafe(regs.offset_in, tmp_buffer.data(), regs.line_length_in); + memory_manager.WriteBlock(regs.offset_out, tmp_buffer.data(), regs.line_length_in); } } diff --git a/src/video_core/engines/maxwell_dma.h b/src/video_core/engines/maxwell_dma.h index 9e457ae16..a04514425 100644 --- a/src/video_core/engines/maxwell_dma.h +++ b/src/video_core/engines/maxwell_dma.h @@ -175,7 +175,7 @@ public: static_assert(sizeof(LaunchDMA) == 4); struct RemapConst { - enum Swizzle : u32 { + enum class Swizzle : u32 { SRC_X = 0, SRC_Y = 1, SRC_Z = 2, diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index ff024f530..2ae3639b5 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp @@ -531,14 +531,6 @@ void GPU::TriggerCpuInterrupt(const u32 syncpoint_id, const u32 value) const { interrupt_manager.GPUInterruptSyncpt(syncpoint_id, value); } -void GPU::ShutDown() { - // Signal that threads should no longer block on syncpoint fences - shutting_down.store(true, std::memory_order_relaxed); - sync_cv.notify_all(); - - gpu_thread.ShutDown(); -} - void GPU::OnCommandListEnd() { if (is_async) { // This command only applies to asynchronous GPU mode diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h index a8e98e51b..e6a02a71b 100644 --- a/src/video_core/gpu.h +++ b/src/video_core/gpu.h @@ -219,9 +219,6 @@ public: return *shader_notify; } - // Stops the GPU execution and waits for the GPU to finish working - void ShutDown(); - /// Allows the CPU/NvFlinger to wait on the GPU before presenting a frame. void WaitFence(u32 syncpoint_id, u32 value); diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp index 46f642b19..9547f277a 100644 --- a/src/video_core/gpu_thread.cpp +++ b/src/video_core/gpu_thread.cpp @@ -17,9 +17,9 @@ namespace VideoCommon::GPUThread { /// Runs the GPU thread -static void RunThread(Core::System& system, VideoCore::RendererBase& renderer, - Core::Frontend::GraphicsContext& context, Tegra::DmaPusher& dma_pusher, - SynchState& state) { +static void RunThread(std::stop_token stop_token, Core::System& system, + VideoCore::RendererBase& renderer, Core::Frontend::GraphicsContext& context, + Tegra::DmaPusher& dma_pusher, SynchState& state) { std::string name = "yuzu:GPU"; MicroProfileOnThreadCreate(name.c_str()); SCOPE_EXIT({ MicroProfileOnThreadExit(); }); @@ -28,20 +28,14 @@ static void RunThread(Core::System& system, VideoCore::RendererBase& renderer, Common::SetCurrentThreadPriority(Common::ThreadPriority::High); system.RegisterHostThread(); - // Wait for first GPU command before acquiring the window context - state.queue.Wait(); - - // If emulation was stopped during disk shader loading, abort before trying to acquire context - if (!state.is_running) { - return; - } - auto current_context = context.Acquire(); VideoCore::RasterizerInterface* const rasterizer = renderer.ReadRasterizer(); - CommandDataContainer next; - while (state.is_running) { - next = state.queue.PopWait(); + while (!stop_token.stop_requested()) { + CommandDataContainer next = state.queue.PopWait(stop_token); + if (stop_token.stop_requested()) { + break; + } if (auto* submit_list = std::get_if<SubmitListCommand>(&next.data)) { dma_pusher.Push(std::move(submit_list->entries)); dma_pusher.DispatchCalls(); @@ -55,8 +49,6 @@ static void RunThread(Core::System& system, VideoCore::RendererBase& renderer, rasterizer->FlushRegion(flush->addr, flush->size); } else if (const auto* invalidate = std::get_if<InvalidateRegionCommand>(&next.data)) { rasterizer->OnCPUWrite(invalidate->addr, invalidate->size); - } else if (std::holds_alternative<EndProcessingCommand>(next.data)) { - ASSERT(state.is_running == false); } else { UNREACHABLE(); } @@ -73,16 +65,14 @@ static void RunThread(Core::System& system, VideoCore::RendererBase& renderer, ThreadManager::ThreadManager(Core::System& system_, bool is_async_) : system{system_}, is_async{is_async_} {} -ThreadManager::~ThreadManager() { - ShutDown(); -} +ThreadManager::~ThreadManager() = default; void ThreadManager::StartThread(VideoCore::RendererBase& renderer, Core::Frontend::GraphicsContext& context, Tegra::DmaPusher& dma_pusher) { rasterizer = renderer.ReadRasterizer(); - thread = std::thread(RunThread, std::ref(system), std::ref(renderer), std::ref(context), - std::ref(dma_pusher), std::ref(state)); + thread = std::jthread(RunThread, std::ref(system), std::ref(renderer), std::ref(context), + std::ref(dma_pusher), std::ref(state)); } void ThreadManager::SubmitList(Tegra::CommandList&& entries) { @@ -117,26 +107,6 @@ void ThreadManager::FlushAndInvalidateRegion(VAddr addr, u64 size) { rasterizer->OnCPUWrite(addr, size); } -void ThreadManager::ShutDown() { - if (!state.is_running) { - return; - } - - { - std::lock_guard lk(state.write_lock); - state.is_running = false; - state.cv.notify_all(); - } - - if (!thread.joinable()) { - return; - } - - // Notify GPU thread that a shutdown is pending - PushCommand(EndProcessingCommand()); - thread.join(); -} - void ThreadManager::OnCommandListEnd() { PushCommand(OnCommandListEndCommand()); } @@ -152,9 +122,8 @@ u64 ThreadManager::PushCommand(CommandData&& command_data, bool block) { state.queue.Push(CommandDataContainer(std::move(command_data), fence, block)); if (block) { - state.cv.wait(lk, [this, fence] { - return fence <= state.signaled_fence.load(std::memory_order_relaxed) || - !state.is_running; + state.cv.wait(lk, thread.get_stop_token(), [this, fence] { + return fence <= state.signaled_fence.load(std::memory_order_relaxed); }); } diff --git a/src/video_core/gpu_thread.h b/src/video_core/gpu_thread.h index 11a648f38..91bada925 100644 --- a/src/video_core/gpu_thread.h +++ b/src/video_core/gpu_thread.h @@ -33,9 +33,6 @@ class RendererBase; namespace VideoCommon::GPUThread { -/// Command to signal to the GPU thread that processing has ended -struct EndProcessingCommand final {}; - /// Command to signal to the GPU thread that a command list is ready for processing struct SubmitListCommand final { explicit SubmitListCommand(Tegra::CommandList&& entries_) : entries{std::move(entries_)} {} @@ -83,7 +80,7 @@ struct OnCommandListEndCommand final {}; struct GPUTickCommand final {}; using CommandData = - std::variant<EndProcessingCommand, SubmitListCommand, SwapBuffersCommand, FlushRegionCommand, + std::variant<std::monostate, SubmitListCommand, SwapBuffersCommand, FlushRegionCommand, InvalidateRegionCommand, FlushAndInvalidateRegionCommand, OnCommandListEndCommand, GPUTickCommand>; @@ -100,14 +97,12 @@ struct CommandDataContainer { /// Struct used to synchronize the GPU thread struct SynchState final { - std::atomic_bool is_running{true}; - - using CommandQueue = Common::SPSCQueue<CommandDataContainer>; + using CommandQueue = Common::SPSCQueue<CommandDataContainer, true>; std::mutex write_lock; CommandQueue queue; u64 last_fence{}; std::atomic<u64> signaled_fence{}; - std::condition_variable cv; + std::condition_variable_any cv; }; /// Class used to manage the GPU thread @@ -149,7 +144,7 @@ private: VideoCore::RasterizerInterface* rasterizer = nullptr; SynchState state; - std::thread thread; + std::jthread thread; }; } // namespace VideoCommon::GPUThread diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt index c9cff7450..20d748c12 100644 --- a/src/video_core/host_shaders/CMakeLists.txt +++ b/src/video_core/host_shaders/CMakeLists.txt @@ -6,7 +6,6 @@ set(SHADER_FILES convert_float_to_depth.frag full_screen_triangle.vert opengl_copy_bc4.comp - opengl_copy_bgra.comp opengl_present.frag opengl_present.vert pitch_unswizzle.comp diff --git a/src/video_core/host_shaders/opengl_copy_bgra.comp b/src/video_core/host_shaders/opengl_copy_bgra.comp deleted file mode 100644 index 2571a4abf..000000000 --- a/src/video_core/host_shaders/opengl_copy_bgra.comp +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2021 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#version 430 core - -layout (local_size_x = 4, local_size_y = 4) in; - -layout(binding = 0, rgba8) readonly uniform image2DArray bgr_input; -layout(binding = 1, rgba8) writeonly uniform image2DArray bgr_output; - -void main() { - vec4 color = imageLoad(bgr_input, ivec3(gl_GlobalInvocationID)); - imageStore(bgr_output, ivec3(gl_GlobalInvocationID), color.bgra); -} diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp index c60ed6453..dce00e829 100644 --- a/src/video_core/memory_manager.cpp +++ b/src/video_core/memory_manager.cpp @@ -2,6 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <algorithm> + #include "common/alignment.h" #include "common/assert.h" #include "common/logging/log.h" diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp index 07a995f7d..187a28e4d 100644 --- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp @@ -147,8 +147,7 @@ void BufferCacheRuntime::CopyBuffer(Buffer& dst_buffer, Buffer& src_buffer, void BufferCacheRuntime::ClearBuffer(Buffer& dest_buffer, u32 offset, size_t size, u32 value) { glClearNamedBufferSubData(dest_buffer.Handle(), GL_R32UI, static_cast<GLintptr>(offset), - static_cast<GLsizeiptr>(size / sizeof(u32)), GL_RED, GL_UNSIGNED_INT, - &value); + static_cast<GLsizeiptr>(size), GL_RED, GL_UNSIGNED_INT, &value); } void BufferCacheRuntime::BindIndexBuffer(Buffer& buffer, u32 offset, u32 size) { diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp index b0e14182e..02682bd76 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp @@ -293,6 +293,8 @@ void ShaderCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading, }}; LoadPipelines(stop_loading, shader_cache_filename, CACHE_VERSION, load_compute, load_graphics); + LOG_INFO(Render_OpenGL, "Total Pipeline Count: {}", state.total); + std::unique_lock lock{state.mutex}; callback(VideoCore::LoadCallbackStage::Build, 0, state.total); state.has_loaded = true; diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp index b0aee6cc1..54dae2c41 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.cpp +++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp @@ -461,7 +461,7 @@ bool TextureCacheRuntime::CanImageBeCopied(const Image& dst, const Image& src) { if (dst.info.type == ImageType::e3D && dst.info.format == PixelFormat::BC4_UNORM) { return false; } - if (IsPixelFormatBGR(dst.info.format) || IsPixelFormatBGR(src.info.format)) { + if (IsPixelFormatBGR(dst.info.format) != IsPixelFormatBGR(src.info.format)) { return false; } return true; @@ -473,7 +473,7 @@ void TextureCacheRuntime::EmulateCopyImage(Image& dst, Image& src, ASSERT(src.info.type == ImageType::e3D); util_shaders.CopyBC4(dst, src, copies); } else if (IsPixelFormatBGR(dst.info.format) || IsPixelFormatBGR(src.info.format)) { - util_shaders.CopyBGR(dst, src, copies); + bgr_copy_pass.CopyBGR(dst, src, copies); } else { UNREACHABLE(); } @@ -1112,4 +1112,37 @@ Framebuffer::Framebuffer(TextureCacheRuntime& runtime, std::span<ImageView*, NUM framebuffer.handle = handle; } +void BGRCopyPass::CopyBGR(Image& dst_image, Image& src_image, + std::span<const VideoCommon::ImageCopy> copies) { + static constexpr VideoCommon::Offset3D zero_offset{0, 0, 0}; + const u32 requested_pbo_size = + std::max(src_image.unswizzled_size_bytes, dst_image.unswizzled_size_bytes); + + if (bgr_pbo_size < requested_pbo_size) { + bgr_pbo.Create(); + bgr_pbo_size = requested_pbo_size; + glNamedBufferData(bgr_pbo.handle, bgr_pbo_size, nullptr, GL_STREAM_COPY); + } + for (const ImageCopy& copy : copies) { + ASSERT(copy.src_offset == zero_offset); + ASSERT(copy.dst_offset == zero_offset); + + // Copy from source to PBO + glPixelStorei(GL_PACK_ALIGNMENT, 1); + glPixelStorei(GL_PACK_ROW_LENGTH, copy.extent.width); + glBindBuffer(GL_PIXEL_PACK_BUFFER, bgr_pbo.handle); + glGetTextureSubImage(src_image.Handle(), 0, 0, 0, 0, copy.extent.width, copy.extent.height, + copy.src_subresource.num_layers, src_image.GlFormat(), + src_image.GlType(), static_cast<GLsizei>(bgr_pbo_size), nullptr); + + // Copy from PBO to destination in desired GL format + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glPixelStorei(GL_UNPACK_ROW_LENGTH, copy.extent.width); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, bgr_pbo.handle); + glTextureSubImage3D(dst_image.Handle(), 0, 0, 0, 0, copy.extent.width, copy.extent.height, + copy.dst_subresource.num_layers, dst_image.GlFormat(), + dst_image.GlType(), nullptr); + } +} + } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h index 4a4f6301c..c498a8a8f 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.h +++ b/src/video_core/renderer_opengl/gl_texture_cache.h @@ -47,6 +47,19 @@ struct FormatProperties { bool is_compressed; }; +class BGRCopyPass { +public: + BGRCopyPass() = default; + ~BGRCopyPass() = default; + + void CopyBGR(Image& dst_image, Image& src_image, + std::span<const VideoCommon::ImageCopy> copies); + +private: + OGLBuffer bgr_pbo; + size_t bgr_pbo_size{}; +}; + class TextureCacheRuntime { friend Framebuffer; friend Image; @@ -118,6 +131,7 @@ private: const Device& device; StateTracker& state_tracker; UtilShaders util_shaders; + BGRCopyPass bgr_copy_pass; std::array<std::unordered_map<GLenum, FormatProperties>, 3> format_properties; bool has_broken_texture_view_formats = false; @@ -162,6 +176,14 @@ public: return texture.handle; } + GLuint GlFormat() const noexcept { + return gl_format; + } + + GLuint GlType() const noexcept { + return gl_type; + } + private: void CopyBufferToImage(const VideoCommon::BufferImageCopy& copy, size_t buffer_offset); diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h index 672f94bfc..39158aa3e 100644 --- a/src/video_core/renderer_opengl/maxwell_to_gl.h +++ b/src/video_core/renderer_opengl/maxwell_to_gl.h @@ -52,7 +52,7 @@ constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> FORMAT_TAB {GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT}, // BC6H_UFLOAT {GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT}, // BC6H_SFLOAT {GL_COMPRESSED_RGBA_ASTC_4x4_KHR}, // ASTC_2D_4X4_UNORM - {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE}, // B8G8R8A8_UNORM + {GL_RGBA8, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV}, // B8G8R8A8_UNORM {GL_RGBA32F, GL_RGBA, GL_FLOAT}, // R32G32B32A32_FLOAT {GL_RGBA32I, GL_RGBA_INTEGER, GL_INT}, // R32G32B32A32_SINT {GL_RG32F, GL_RG, GL_FLOAT}, // R32G32_FLOAT @@ -81,7 +81,7 @@ constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> FORMAT_TAB {GL_COMPRESSED_RGBA_ASTC_8x8_KHR}, // ASTC_2D_8X8_UNORM {GL_COMPRESSED_RGBA_ASTC_8x5_KHR}, // ASTC_2D_8X5_UNORM {GL_COMPRESSED_RGBA_ASTC_5x4_KHR}, // ASTC_2D_5X4_UNORM - {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE}, // B8G8R8A8_SRGB + {GL_SRGB8_ALPHA8, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV}, // B8G8R8A8_SRGB {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT}, // BC1_RGBA_SRGB {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT}, // BC2_SRGB {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT}, // BC3_SRGB diff --git a/src/video_core/renderer_opengl/util_shaders.cpp b/src/video_core/renderer_opengl/util_shaders.cpp index 333f35a1c..897c380b3 100644 --- a/src/video_core/renderer_opengl/util_shaders.cpp +++ b/src/video_core/renderer_opengl/util_shaders.cpp @@ -14,7 +14,6 @@ #include "video_core/host_shaders/block_linear_unswizzle_2d_comp.h" #include "video_core/host_shaders/block_linear_unswizzle_3d_comp.h" #include "video_core/host_shaders/opengl_copy_bc4_comp.h" -#include "video_core/host_shaders/opengl_copy_bgra_comp.h" #include "video_core/host_shaders/pitch_unswizzle_comp.h" #include "video_core/renderer_opengl/gl_shader_manager.h" #include "video_core/renderer_opengl/gl_shader_util.h" @@ -44,11 +43,6 @@ namespace { OGLProgram MakeProgram(std::string_view source) { return CreateProgram(source, GL_COMPUTE_SHADER); } - -size_t NumPixelsInCopy(const VideoCommon::ImageCopy& copy) { - return static_cast<size_t>(copy.extent.width * copy.extent.height * - copy.src_subresource.num_layers); -} } // Anonymous namespace UtilShaders::UtilShaders(ProgramManager& program_manager_) @@ -56,7 +50,6 @@ UtilShaders::UtilShaders(ProgramManager& program_manager_) block_linear_unswizzle_2d_program(MakeProgram(BLOCK_LINEAR_UNSWIZZLE_2D_COMP)), block_linear_unswizzle_3d_program(MakeProgram(BLOCK_LINEAR_UNSWIZZLE_3D_COMP)), pitch_unswizzle_program(MakeProgram(PITCH_UNSWIZZLE_COMP)), - copy_bgra_program(MakeProgram(OPENGL_COPY_BGRA_COMP)), copy_bc4_program(MakeProgram(OPENGL_COPY_BC4_COMP)) { const auto swizzle_table = Tegra::Texture::MakeSwizzleTable(); swizzle_table_buffer.Create(); @@ -255,43 +248,6 @@ void UtilShaders::CopyBC4(Image& dst_image, Image& src_image, std::span<const Im program_manager.RestoreGuestCompute(); } -void UtilShaders::CopyBGR(Image& dst_image, Image& src_image, - std::span<const VideoCommon::ImageCopy> copies) { - static constexpr GLuint BINDING_INPUT_IMAGE = 0; - static constexpr GLuint BINDING_OUTPUT_IMAGE = 1; - static constexpr VideoCommon::Offset3D zero_offset{0, 0, 0}; - const u32 bytes_per_block = BytesPerBlock(dst_image.info.format); - switch (bytes_per_block) { - case 2: - // BGR565 copy - for (const ImageCopy& copy : copies) { - ASSERT(copy.src_offset == zero_offset); - ASSERT(copy.dst_offset == zero_offset); - bgr_copy_pass.Execute(dst_image, src_image, copy); - } - break; - case 4: { - // BGRA8 copy - program_manager.BindComputeProgram(copy_bgra_program.handle); - constexpr GLenum FORMAT = GL_RGBA8; - for (const ImageCopy& copy : copies) { - ASSERT(copy.src_offset == zero_offset); - ASSERT(copy.dst_offset == zero_offset); - glBindImageTexture(BINDING_INPUT_IMAGE, src_image.StorageHandle(), - copy.src_subresource.base_level, GL_FALSE, 0, GL_READ_ONLY, FORMAT); - glBindImageTexture(BINDING_OUTPUT_IMAGE, dst_image.StorageHandle(), - copy.dst_subresource.base_level, GL_FALSE, 0, GL_WRITE_ONLY, FORMAT); - glDispatchCompute(copy.extent.width, copy.extent.height, copy.extent.depth); - } - program_manager.RestoreGuestCompute(); - break; - } - default: - UNREACHABLE(); - break; - } -} - GLenum StoreFormat(u32 bytes_per_block) { switch (bytes_per_block) { case 1: @@ -309,36 +265,4 @@ GLenum StoreFormat(u32 bytes_per_block) { return GL_R8UI; } -void Bgr565CopyPass::Execute(const Image& dst_image, const Image& src_image, - const ImageCopy& copy) { - if (CopyBufferCreationNeeded(copy)) { - CreateNewCopyBuffer(copy, GL_TEXTURE_2D_ARRAY, GL_RGB565); - } - // Copy from source to PBO - glPixelStorei(GL_PACK_ALIGNMENT, 1); - glPixelStorei(GL_PACK_ROW_LENGTH, copy.extent.width); - glBindBuffer(GL_PIXEL_PACK_BUFFER, bgr16_pbo.handle); - glGetTextureSubImage(src_image.Handle(), 0, 0, 0, 0, copy.extent.width, copy.extent.height, - copy.src_subresource.num_layers, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, - static_cast<GLsizei>(bgr16_pbo_size), nullptr); - - // Copy from PBO to destination in reverse order - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glPixelStorei(GL_UNPACK_ROW_LENGTH, copy.extent.width); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, bgr16_pbo.handle); - glTextureSubImage3D(dst_image.Handle(), 0, 0, 0, 0, copy.extent.width, copy.extent.height, - copy.dst_subresource.num_layers, GL_RGB, GL_UNSIGNED_SHORT_5_6_5_REV, - nullptr); -} - -bool Bgr565CopyPass::CopyBufferCreationNeeded(const ImageCopy& copy) { - return bgr16_pbo_size < NumPixelsInCopy(copy) * sizeof(u16); -} - -void Bgr565CopyPass::CreateNewCopyBuffer(const ImageCopy& copy, GLenum target, GLuint format) { - bgr16_pbo.Create(); - bgr16_pbo_size = NumPixelsInCopy(copy) * sizeof(u16); - glNamedBufferData(bgr16_pbo.handle, bgr16_pbo_size, nullptr, GL_STREAM_COPY); -} - } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/util_shaders.h b/src/video_core/renderer_opengl/util_shaders.h index ef881e35f..5de95ea7a 100644 --- a/src/video_core/renderer_opengl/util_shaders.h +++ b/src/video_core/renderer_opengl/util_shaders.h @@ -19,22 +19,6 @@ class ProgramManager; struct ImageBufferMap; -class Bgr565CopyPass { -public: - Bgr565CopyPass() = default; - ~Bgr565CopyPass() = default; - - void Execute(const Image& dst_image, const Image& src_image, - const VideoCommon::ImageCopy& copy); - -private: - [[nodiscard]] bool CopyBufferCreationNeeded(const VideoCommon::ImageCopy& copy); - void CreateNewCopyBuffer(const VideoCommon::ImageCopy& copy, GLenum target, GLuint format); - - OGLBuffer bgr16_pbo; - size_t bgr16_pbo_size{}; -}; - class UtilShaders { public: explicit UtilShaders(ProgramManager& program_manager); @@ -55,9 +39,6 @@ public: void CopyBC4(Image& dst_image, Image& src_image, std::span<const VideoCommon::ImageCopy> copies); - void CopyBGR(Image& dst_image, Image& src_image, - std::span<const VideoCommon::ImageCopy> copies); - private: ProgramManager& program_manager; @@ -67,10 +48,7 @@ private: OGLProgram block_linear_unswizzle_2d_program; OGLProgram block_linear_unswizzle_3d_program; OGLProgram pitch_unswizzle_program; - OGLProgram copy_bgra_program; OGLProgram copy_bc4_program; - - Bgr565CopyPass bgr_copy_pass; }; GLenum StoreFormat(u32 bytes_per_block); diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index 7c9b0d6db..74822814d 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -97,19 +97,14 @@ RendererVulkan::RendererVulkan(Core::TelemetrySession& telemetry_session_, Core::Frontend::EmuWindow& emu_window, Core::Memory::Memory& cpu_memory_, Tegra::GPU& gpu_, std::unique_ptr<Core::Frontend::GraphicsContext> context_) try - : RendererBase(emu_window, std::move(context_)), - telemetry_session(telemetry_session_), - cpu_memory(cpu_memory_), - gpu(gpu_), - library(OpenLibrary()), + : RendererBase(emu_window, std::move(context_)), telemetry_session(telemetry_session_), + cpu_memory(cpu_memory_), gpu(gpu_), library(OpenLibrary()), instance(CreateInstance(library, dld, VK_API_VERSION_1_1, render_window.GetWindowInfo().type, true, Settings::values.renderer_debug.GetValue())), debug_callback(Settings::values.renderer_debug ? CreateDebugCallback(instance) : nullptr), surface(CreateSurface(instance, render_window)), - device(CreateDevice(instance, dld, *surface)), - memory_allocator(device, false), - state_tracker(gpu), - scheduler(device, state_tracker), + device(CreateDevice(instance, dld, *surface)), memory_allocator(device, false), + state_tracker(gpu), scheduler(device, state_tracker), swapchain(*surface, device, scheduler, render_window.GetFramebufferLayout().width, render_window.GetFramebufferLayout().height, false), blit_screen(cpu_memory, render_window, device, memory_allocator, swapchain, scheduler, @@ -149,7 +144,7 @@ void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { const Layout::FramebufferLayout layout = render_window.GetFramebufferLayout(); swapchain.Create(layout.width, layout.height, is_srgb); }; - if (swapchain.IsSubOptimal() || swapchain.HasColorSpaceChanged(is_srgb)) { + if (swapchain.NeedsRecreation(is_srgb)) { recreate_swapchain(); } bool is_outdated; @@ -164,7 +159,8 @@ void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { blit_screen.Recreate(); } const VkSemaphore render_semaphore = blit_screen.DrawToSwapchain(*framebuffer, use_accelerated); - scheduler.Flush(render_semaphore); + const VkSemaphore present_semaphore = swapchain.CurrentPresentSemaphore(); + scheduler.Flush(render_semaphore, present_semaphore); scheduler.WaitWorker(); swapchain.Present(render_semaphore); diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp index cb0580182..888bc7392 100644 --- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp +++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp @@ -358,7 +358,7 @@ void VKBlitScreen::CreateDescriptorPool() { void VKBlitScreen::CreateRenderPass() { const VkAttachmentDescription color_attachment{ .flags = 0, - .format = swapchain.GetImageFormat(), + .format = swapchain.GetImageViewFormat(), .samples = VK_SAMPLE_COUNT_1_BIT, .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, .storeOp = VK_ATTACHMENT_STORE_OP_STORE, diff --git a/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp b/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp index 8e77e4796..d87da2a34 100644 --- a/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp +++ b/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <algorithm> #include <mutex> #include <span> #include <vector> @@ -18,7 +19,6 @@ namespace Vulkan { // Prefer small grow rates to avoid saturating the descriptor pool with barely used pipelines constexpr size_t SETS_GROW_RATE = 16; constexpr s32 SCORE_THRESHOLD = 3; -constexpr u32 SETS_PER_POOL = 64; struct DescriptorBank { DescriptorBankInfo info; @@ -58,11 +58,12 @@ static DescriptorBankInfo MakeBankInfo(std::span<const Shader::Info> infos) { static void AllocatePool(const Device& device, DescriptorBank& bank) { std::array<VkDescriptorPoolSize, 6> pool_sizes; size_t pool_cursor{}; + const u32 sets_per_pool = device.GetSetsPerPool(); const auto add = [&](VkDescriptorType type, u32 count) { if (count > 0) { pool_sizes[pool_cursor++] = { .type = type, - .descriptorCount = count * SETS_PER_POOL, + .descriptorCount = count * sets_per_pool, }; } }; @@ -77,7 +78,7 @@ static void AllocatePool(const Device& device, DescriptorBank& bank) { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, .pNext = nullptr, .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, - .maxSets = SETS_PER_POOL, + .maxSets = sets_per_pool, .poolSizeCount = static_cast<u32>(pool_cursor), .pPoolSizes = std::data(pool_sizes), })); diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 7c0f91007..11cd41ad7 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -507,8 +507,9 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { vertex_attributes.push_back({ .location = static_cast<u32>(index), .binding = 0, - .format = type == 1 ? VK_FORMAT_R32_SFLOAT - : type == 2 ? VK_FORMAT_R32_SINT : VK_FORMAT_R32_UINT, + .format = type == 1 ? VK_FORMAT_R32_SFLOAT + : type == 2 ? VK_FORMAT_R32_SINT + : VK_FORMAT_R32_UINT, .offset = 0, }); } diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 31bfbcb06..eb8b4e08b 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -447,6 +447,8 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading VideoCommon::LoadPipelines(stop_loading, pipeline_cache_filename, CACHE_VERSION, load_compute, load_graphics); + LOG_INFO(Render_Vulkan, "Total Pipeline Count: {}", state.total); + std::unique_lock lock{state.mutex}; callback(VideoCore::LoadCallbackStage::Build, 0, state.total); state.has_loaded = true; diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 3ac18ea54..3bcd6d6cc 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -228,9 +228,7 @@ void RasterizerVulkan::Clear() { }; const u32 color_attachment = regs.clear_buffers.RT; - const auto attachment_aspect_mask = framebuffer->ImageRanges()[color_attachment].aspectMask; - const bool is_color_rt = (attachment_aspect_mask & VK_IMAGE_ASPECT_COLOR_BIT) != 0; - if (use_color && is_color_rt) { + if (use_color && framebuffer->HasAspectColorBit(color_attachment)) { VkClearValue clear_value; std::memcpy(clear_value.color.float32, regs.clear_color, sizeof(regs.clear_color)); @@ -248,12 +246,15 @@ void RasterizerVulkan::Clear() { return; } VkImageAspectFlags aspect_flags = 0; - if (use_depth) { + if (use_depth && framebuffer->HasAspectDepthBit()) { aspect_flags |= VK_IMAGE_ASPECT_DEPTH_BIT; } - if (use_stencil) { + if (use_stencil && framebuffer->HasAspectStencilBit()) { aspect_flags |= VK_IMAGE_ASPECT_STENCIL_BIT; } + if (aspect_flags == 0) { + return; + } scheduler.Record([clear_depth = regs.clear_depth, clear_stencil = regs.clear_stencil, clear_rect, aspect_flags](vk::CommandBuffer cmdbuf) { VkClearAttachment attachment; @@ -764,12 +765,7 @@ void RasterizerVulkan::UpdateStencilOp(Tegra::Engines::Maxwell3D::Regs& regs) { 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 { + // Separate stencil op per face 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; @@ -784,6 +780,13 @@ void RasterizerVulkan::UpdateStencilOp(Tegra::Engines::Maxwell3D::Regs& regs) { MaxwellToVK::StencilOp(back_zfail), MaxwellToVK::ComparisonOp(back_compare)); }); + } else { + // Front face defines the stencil op of both faces + 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)); + }); } } diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index 4840962de..0c11c814f 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -43,26 +43,19 @@ VKScheduler::VKScheduler(const Device& device_, StateTracker& state_tracker_) command_pool{std::make_unique<CommandPool>(*master_semaphore, device)} { AcquireNewChunk(); AllocateWorkerCommandBuffer(); - worker_thread = std::thread(&VKScheduler::WorkerThread, this); + worker_thread = std::jthread([this](std::stop_token token) { WorkerThread(token); }); } -VKScheduler::~VKScheduler() { - { - std::lock_guard lock{work_mutex}; - quit = true; - } - work_cv.notify_all(); - worker_thread.join(); -} +VKScheduler::~VKScheduler() = default; -void VKScheduler::Flush(VkSemaphore semaphore) { - SubmitExecution(semaphore); +void VKScheduler::Flush(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) { + SubmitExecution(signal_semaphore, wait_semaphore); AllocateNewContext(); } -void VKScheduler::Finish(VkSemaphore semaphore) { +void VKScheduler::Finish(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) { const u64 presubmit_tick = CurrentTick(); - SubmitExecution(semaphore); + SubmitExecution(signal_semaphore, wait_semaphore); WaitWorker(); Wait(presubmit_tick); AllocateNewContext(); @@ -135,7 +128,7 @@ bool VKScheduler::UpdateGraphicsPipeline(GraphicsPipeline* pipeline) { return true; } -void VKScheduler::WorkerThread() { +void VKScheduler::WorkerThread(std::stop_token stop_token) { Common::SetCurrentThreadName("yuzu:VulkanWorker"); do { if (work_queue.empty()) { @@ -144,8 +137,8 @@ void VKScheduler::WorkerThread() { std::unique_ptr<CommandChunk> work; { std::unique_lock lock{work_mutex}; - work_cv.wait(lock, [this] { return !work_queue.empty() || quit; }); - if (quit) { + work_cv.wait(lock, stop_token, [this] { return !work_queue.empty(); }); + if (stop_token.stop_requested()) { continue; } work = std::move(work_queue.front()); @@ -158,7 +151,7 @@ void VKScheduler::WorkerThread() { } std::lock_guard reserve_lock{reserve_mutex}; chunk_reserve.push_back(std::move(work)); - } while (!quit); + } while (!stop_token.stop_requested()); } void VKScheduler::AllocateWorkerCommandBuffer() { @@ -171,37 +164,41 @@ void VKScheduler::AllocateWorkerCommandBuffer() { }); } -void VKScheduler::SubmitExecution(VkSemaphore semaphore) { +void VKScheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) { EndPendingOperations(); InvalidateState(); const u64 signal_value = master_semaphore->NextTick(); - Record([semaphore, signal_value, this](vk::CommandBuffer cmdbuf) { + Record([signal_semaphore, wait_semaphore, signal_value, this](vk::CommandBuffer cmdbuf) { cmdbuf.End(); - - const u32 num_signal_semaphores = semaphore ? 2U : 1U; - - const u64 wait_value = signal_value - 1; - const VkPipelineStageFlags wait_stage_mask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; - const VkSemaphore timeline_semaphore = master_semaphore->Handle(); + + const u32 num_signal_semaphores = signal_semaphore ? 2U : 1U; const std::array signal_values{signal_value, u64(0)}; - const std::array signal_semaphores{timeline_semaphore, semaphore}; + const std::array signal_semaphores{timeline_semaphore, signal_semaphore}; + + const u32 num_wait_semaphores = wait_semaphore ? 2U : 1U; + const std::array wait_values{signal_value - 1, u64(1)}; + const std::array wait_semaphores{timeline_semaphore, wait_semaphore}; + static constexpr std::array<VkPipelineStageFlags, 2> wait_stage_masks{ + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + }; const VkTimelineSemaphoreSubmitInfoKHR timeline_si{ .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO_KHR, .pNext = nullptr, - .waitSemaphoreValueCount = 1, - .pWaitSemaphoreValues = &wait_value, + .waitSemaphoreValueCount = num_wait_semaphores, + .pWaitSemaphoreValues = wait_values.data(), .signalSemaphoreValueCount = num_signal_semaphores, .pSignalSemaphoreValues = signal_values.data(), }; const VkSubmitInfo submit_info{ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .pNext = &timeline_si, - .waitSemaphoreCount = 1, - .pWaitSemaphores = &timeline_semaphore, - .pWaitDstStageMask = &wait_stage_mask, + .waitSemaphoreCount = num_wait_semaphores, + .pWaitSemaphores = wait_semaphores.data(), + .pWaitDstStageMask = wait_stage_masks.data(), .commandBufferCount = 1, .pCommandBuffers = cmdbuf.address(), .signalSemaphoreCount = num_signal_semaphores, diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index cf39a2363..85fc1712f 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h @@ -34,10 +34,10 @@ public: ~VKScheduler(); /// Sends the current execution context to the GPU. - void Flush(VkSemaphore semaphore = nullptr); + void Flush(VkSemaphore signal_semaphore = nullptr, VkSemaphore wait_semaphore = nullptr); /// Sends the current execution context to the GPU and waits for it to complete. - void Finish(VkSemaphore semaphore = nullptr); + void Finish(VkSemaphore signal_semaphore = nullptr, VkSemaphore wait_semaphore = nullptr); /// Waits for the worker thread to finish executing everything. After this function returns it's /// safe to touch worker resources. @@ -187,11 +187,11 @@ private: GraphicsPipeline* graphics_pipeline = nullptr; }; - void WorkerThread(); + void WorkerThread(std::stop_token stop_token); void AllocateWorkerCommandBuffer(); - void SubmitExecution(VkSemaphore semaphore); + void SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore); void AllocateNewContext(); @@ -212,7 +212,6 @@ private: vk::CommandBuffer current_cmdbuf; std::unique_ptr<CommandChunk> chunk; - std::thread worker_thread; State state; @@ -224,9 +223,9 @@ private: std::vector<std::unique_ptr<CommandChunk>> chunk_reserve; std::mutex reserve_mutex; std::mutex work_mutex; - std::condition_variable work_cv; + std::condition_variable_any work_cv; std::condition_variable wait_cv; - std::atomic_bool quit{}; + std::jthread worker_thread; }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_state_tracker.h b/src/video_core/renderer_vulkan/vk_state_tracker.h index 5f78f6950..d90935f52 100644 --- a/src/video_core/renderer_vulkan/vk_state_tracker.h +++ b/src/video_core/renderer_vulkan/vk_state_tracker.h @@ -110,10 +110,6 @@ public: return Exchange(Dirty::DepthTestEnable, false); } - bool TouchDepthBoundsEnable() { - return Exchange(Dirty::DepthBoundsEnable, false); - } - bool TouchDepthWriteEnable() { return Exchange(Dirty::DepthWriteEnable, false); } diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index d990eefba..8972a6921 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -9,6 +9,7 @@ #include "common/assert.h" #include "common/logging/log.h" +#include "common/settings.h" #include "core/core.h" #include "core/frontend/framebuffer_layout.h" #include "video_core/renderer_vulkan/vk_scheduler.h" @@ -20,16 +21,15 @@ namespace Vulkan { namespace { -VkSurfaceFormatKHR ChooseSwapSurfaceFormat(vk::Span<VkSurfaceFormatKHR> formats, bool srgb) { +VkSurfaceFormatKHR ChooseSwapSurfaceFormat(vk::Span<VkSurfaceFormatKHR> formats) { if (formats.size() == 1 && formats[0].format == VK_FORMAT_UNDEFINED) { VkSurfaceFormatKHR format; format.format = VK_FORMAT_B8G8R8A8_UNORM; format.colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; return format; } - const auto& found = std::find_if(formats.begin(), formats.end(), [srgb](const auto& format) { - const auto request_format = srgb ? VK_FORMAT_B8G8R8A8_SRGB : VK_FORMAT_B8G8R8A8_UNORM; - return format.format == request_format && + const auto& found = std::find_if(formats.begin(), formats.end(), [](const auto& format) { + return format.format == VK_FORMAT_B8G8R8A8_UNORM && format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; }); return found != formats.end() ? *found : formats[0]; @@ -37,8 +37,19 @@ VkSurfaceFormatKHR ChooseSwapSurfaceFormat(vk::Span<VkSurfaceFormatKHR> formats, VkPresentModeKHR ChooseSwapPresentMode(vk::Span<VkPresentModeKHR> modes) { // Mailbox doesn't lock the application like fifo (vsync), prefer it - const auto found = std::find(modes.begin(), modes.end(), VK_PRESENT_MODE_MAILBOX_KHR); - return found != modes.end() ? *found : VK_PRESENT_MODE_FIFO_KHR; + const auto found_mailbox = std::find(modes.begin(), modes.end(), VK_PRESENT_MODE_MAILBOX_KHR); + if (found_mailbox != modes.end()) { + return VK_PRESENT_MODE_MAILBOX_KHR; + } + if (Settings::values.disable_fps_limit.GetValue()) { + // FIFO present mode locks the framerate to the monitor's refresh rate, + // Find an alternative to surpass this limitation if FPS is unlocked. + const auto found_imm = std::find(modes.begin(), modes.end(), VK_PRESENT_MODE_IMMEDIATE_KHR); + if (found_imm != modes.end()) { + return VK_PRESENT_MODE_IMMEDIATE_KHR; + } + } + return VK_PRESENT_MODE_FIFO_KHR; } VkExtent2D ChooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities, u32 width, u32 height) { @@ -107,14 +118,12 @@ void VKSwapchain::AcquireNextImage() { } void VKSwapchain::Present(VkSemaphore render_semaphore) { - const VkSemaphore present_semaphore{*present_semaphores[frame_index]}; - const std::array<VkSemaphore, 2> semaphores{present_semaphore, render_semaphore}; const auto present_queue{device.GetPresentQueue()}; const VkPresentInfoKHR present_info{ .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, .pNext = nullptr, - .waitSemaphoreCount = render_semaphore ? 2U : 1U, - .pWaitSemaphores = semaphores.data(), + .waitSemaphoreCount = render_semaphore ? 1U : 0U, + .pWaitSemaphores = &render_semaphore, .swapchainCount = 1, .pSwapchains = swapchain.address(), .pImageIndices = &image_index, @@ -145,8 +154,8 @@ void VKSwapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, const auto formats{physical_device.GetSurfaceFormatsKHR(surface)}; const auto present_modes{physical_device.GetSurfacePresentModesKHR(surface)}; - const VkSurfaceFormatKHR surface_format{ChooseSwapSurfaceFormat(formats, srgb)}; - const VkPresentModeKHR present_mode{ChooseSwapPresentMode(present_modes)}; + const VkSurfaceFormatKHR surface_format{ChooseSwapSurfaceFormat(formats)}; + present_mode = ChooseSwapPresentMode(present_modes); u32 requested_image_count{capabilities.minImageCount + 1}; if (capabilities.maxImageCount > 0 && requested_image_count > capabilities.maxImageCount) { @@ -180,6 +189,17 @@ void VKSwapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, swapchain_ci.queueFamilyIndexCount = static_cast<u32>(queue_indices.size()); swapchain_ci.pQueueFamilyIndices = queue_indices.data(); } + static constexpr std::array view_formats{VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_B8G8R8A8_SRGB}; + VkImageFormatListCreateInfo format_list{ + .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO_KHR, + .pNext = nullptr, + .viewFormatCount = static_cast<u32>(view_formats.size()), + .pViewFormats = view_formats.data(), + }; + if (device.IsKhrSwapchainMutableFormatEnabled()) { + format_list.pNext = std::exchange(swapchain_ci.pNext, &format_list); + swapchain_ci.flags |= VK_SWAPCHAIN_CREATE_MUTABLE_FORMAT_BIT_KHR; + } // Request the size again to reduce the possibility of a TOCTOU race condition. const auto updated_capabilities = physical_device.GetSurfaceCapabilitiesKHR(surface); swapchain_ci.imageExtent = ChooseSwapExtent(updated_capabilities, width, height); @@ -188,10 +208,11 @@ void VKSwapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, extent = swapchain_ci.imageExtent; current_srgb = srgb; + current_fps_unlocked = Settings::values.disable_fps_limit.GetValue(); images = swapchain.GetImages(); image_count = static_cast<u32>(images.size()); - image_format = surface_format.format; + image_view_format = srgb ? VK_FORMAT_B8G8R8A8_SRGB : VK_FORMAT_B8G8R8A8_UNORM; } void VKSwapchain::CreateSemaphores() { @@ -207,7 +228,7 @@ void VKSwapchain::CreateImageViews() { .flags = 0, .image = {}, .viewType = VK_IMAGE_VIEW_TYPE_2D, - .format = image_format, + .format = image_view_format, .components = { .r = VK_COMPONENT_SWIZZLE_IDENTITY, @@ -240,4 +261,14 @@ void VKSwapchain::Destroy() { swapchain.reset(); } +bool VKSwapchain::HasFpsUnlockChanged() const { + return current_fps_unlocked != Settings::values.disable_fps_limit.GetValue(); +} + +bool VKSwapchain::NeedsPresentModeUpdate() const { + // Mailbox present mode is the ideal for all scenarios. If it is not available, + // A different present mode is needed to support unlocked FPS above the monitor's refresh rate. + return present_mode != VK_PRESENT_MODE_MAILBOX_KHR && HasFpsUnlockChanged(); +} + } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_swapchain.h b/src/video_core/renderer_vulkan/vk_swapchain.h index 35c2cdc14..61a6d959e 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.h +++ b/src/video_core/renderer_vulkan/vk_swapchain.h @@ -33,6 +33,11 @@ public: /// Presents the rendered image to the swapchain. void Present(VkSemaphore render_semaphore); + /// Returns true when the swapchain needs to be recreated. + bool NeedsRecreation(bool is_srgb) const { + return HasColorSpaceChanged(is_srgb) || IsSubOptimal() || NeedsPresentModeUpdate(); + } + /// Returns true when the color space has changed. bool HasColorSpaceChanged(bool is_srgb) const { return current_srgb != is_srgb; @@ -68,8 +73,12 @@ public: return *image_views[index]; } - VkFormat GetImageFormat() const { - return image_format; + VkFormat GetImageViewFormat() const { + return image_view_format; + } + + VkSemaphore CurrentPresentSemaphore() const { + return *present_semaphores[frame_index]; } private: @@ -80,6 +89,10 @@ private: void Destroy(); + bool HasFpsUnlockChanged() const; + + bool NeedsPresentModeUpdate() const; + const VkSurfaceKHR surface; const Device& device; VKScheduler& scheduler; @@ -96,10 +109,12 @@ private: u32 image_index{}; u32 frame_index{}; - VkFormat image_format{}; + VkFormat image_view_format{}; VkExtent2D extent{}; + VkPresentModeKHR present_mode{}; bool current_srgb{}; + bool current_fps_unlocked{}; bool is_outdated{}; bool is_suboptimal{}; }; diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 8f4df7122..3b87640b5 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -127,7 +127,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) { const auto format_info = MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, false, format); VkImageCreateFlags flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; if (info.type == ImageType::e2D && info.resources.layers >= 6 && - info.size.width == info.size.height) { + info.size.width == info.size.height && !device.HasBrokenCubeImageCompability()) { flags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; } if (info.type == ImageType::e3D) { @@ -1186,9 +1186,12 @@ Framebuffer::Framebuffer(TextureCacheRuntime& runtime, std::span<ImageView*, NUM renderpass_key.depth_format = depth_buffer->format; num_layers = std::max(num_layers, depth_buffer->range.extent.layers); images[num_images] = depth_buffer->ImageHandle(); - image_ranges[num_images] = MakeSubresourceRange(depth_buffer); + const VkImageSubresourceRange subresource_range = MakeSubresourceRange(depth_buffer); + image_ranges[num_images] = subresource_range; samples = depth_buffer->Samples(); ++num_images; + has_depth = (subresource_range.aspectMask & VK_IMAGE_ASPECT_DEPTH_BIT) != 0; + has_stencil = (subresource_range.aspectMask & VK_IMAGE_ASPECT_STENCIL_BIT) != 0; } else { renderpass_key.depth_format = PixelFormat::Invalid; } diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index 5fe6b7ba3..6d5a68bfe 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -232,6 +232,18 @@ public: return image_ranges; } + [[nodiscard]] bool HasAspectColorBit(size_t index) const noexcept { + return (image_ranges.at(index).aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) != 0; + } + + [[nodiscard]] bool HasAspectDepthBit() const noexcept { + return has_depth; + } + + [[nodiscard]] bool HasAspectStencilBit() const noexcept { + return has_stencil; + } + private: vk::Framebuffer framebuffer; VkRenderPass renderpass{}; @@ -241,6 +253,8 @@ private: u32 num_images = 0; std::array<VkImage, 9> images{}; std::array<VkImageSubresourceRange, 9> image_ranges{}; + bool has_depth{}; + bool has_stencil{}; }; struct TextureCacheParams { diff --git a/src/video_core/shader_environment.cpp b/src/video_core/shader_environment.cpp index 8a4581c19..81a878bb2 100644 --- a/src/video_core/shader_environment.cpp +++ b/src/video_core/shader_environment.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <algorithm> #include <filesystem> #include <fstream> #include <memory> diff --git a/src/video_core/texture_cache/slot_vector.h b/src/video_core/texture_cache/slot_vector.h index 6180b8c0e..50df06409 100644 --- a/src/video_core/texture_cache/slot_vector.h +++ b/src/video_core/texture_cache/slot_vector.h @@ -4,6 +4,7 @@ #pragma once +#include <algorithm> #include <array> #include <bit> #include <concepts> @@ -30,8 +31,8 @@ struct SlotId { }; template <class T> -requires std::is_nothrow_move_assignable_v<T>&& - std::is_nothrow_move_constructible_v<T> class SlotVector { +requires std::is_nothrow_move_assignable_v<T> && std::is_nothrow_move_constructible_v<T> +class SlotVector { public: class Iterator { friend SlotVector<T>; diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp index 3b575db4d..cae543a51 100644 --- a/src/video_core/video_core.cpp +++ b/src/video_core/video_core.cpp @@ -37,7 +37,8 @@ std::unique_ptr<VideoCore::RendererBase> CreateRenderer( namespace VideoCore { std::unique_ptr<Tegra::GPU> CreateGPU(Core::Frontend::EmuWindow& emu_window, Core::System& system) { - const bool use_nvdec = Settings::values.use_nvdec_emulation.GetValue(); + const auto nvdec_value = Settings::values.nvdec_emulation.GetValue(); + const bool use_nvdec = nvdec_value != Settings::NvdecEmulation::Off; const bool use_async = Settings::values.use_asynchronous_gpu_emulation.GetValue(); auto gpu = std::make_unique<Tegra::GPU>(system, use_async, use_nvdec); auto context = emu_window.CreateSharedContext(); diff --git a/src/video_core/vulkan_common/vulkan_debug_callback.cpp b/src/video_core/vulkan_common/vulkan_debug_callback.cpp index 0f60765bb..cf94e1d39 100644 --- a/src/video_core/vulkan_common/vulkan_debug_callback.cpp +++ b/src/video_core/vulkan_common/vulkan_debug_callback.cpp @@ -16,6 +16,7 @@ VkBool32 Callback(VkDebugUtilsMessageSeverityFlagBitsEXT severity, switch (static_cast<u32>(data->messageIdNumber)) { case 0x682a878au: // VUID-vkCmdBindVertexBuffers2EXT-pBuffers-parameter case 0x99fb7dfdu: // UNASSIGNED-RequiredParameter (vkCmdBindVertexBuffers2EXT pBuffers[0]) + case 0xe8616bf2u: // Bound VkDescriptorSet 0x0[] was destroyed. Likely push_descriptor related return VK_FALSE; default: break; diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 86ca4be54..6388ed2eb 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -368,8 +368,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR }; SetNext(next, demote); + VkPhysicalDeviceFloat16Int8FeaturesKHR float16_int8; if (is_int8_supported || is_float16_supported) { - VkPhysicalDeviceFloat16Int8FeaturesKHR float16_int8{ + float16_int8 = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FLOAT16_INT8_FEATURES_KHR, .pNext = nullptr, .shaderFloat16 = is_float16_supported, @@ -587,6 +588,31 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR ext_extended_dynamic_state = false; } } + sets_per_pool = 64; + + const bool is_amd = + driver_id == VK_DRIVER_ID_AMD_PROPRIETARY || driver_id == VK_DRIVER_ID_AMD_OPEN_SOURCE; + if (is_amd) { + // AMD drivers need a higher amount of Sets per Pool in certain circunstances like in XC2. + sets_per_pool = 96; + // Disable VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT on AMD GCN4 and lower as it is broken. + if (!is_float16_supported) { + LOG_WARNING( + Render_Vulkan, + "AMD GCN4 and earlier do not properly support VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT"); + has_broken_cube_compatibility = true; + } + } + const bool is_amd_or_radv = is_amd || driver_id == VK_DRIVER_ID_MESA_RADV; + if (ext_sampler_filter_minmax && is_amd_or_radv) { + // Disable ext_sampler_filter_minmax on AMD GCN4 and lower as it is broken. + if (!is_float16_supported) { + LOG_WARNING(Render_Vulkan, + "Blacklisting AMD GCN4 and earlier for VK_EXT_sampler_filter_minmax"); + ext_sampler_filter_minmax = false; + } + } + if (ext_vertex_input_dynamic_state && driver_id == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS) { LOG_WARNING(Render_Vulkan, "Blacklisting Intel for VK_EXT_vertex_input_dynamic_state"); ext_vertex_input_dynamic_state = false; @@ -839,6 +865,8 @@ std::vector<const char*> Device::LoadExtensions(bool requires_surface) { bool has_khr_shader_float16_int8{}; bool has_khr_workgroup_memory_explicit_layout{}; bool has_khr_pipeline_executable_properties{}; + bool has_khr_image_format_list{}; + bool has_khr_swapchain_mutable_format{}; bool has_ext_subgroup_size_control{}; bool has_ext_transform_feedback{}; bool has_ext_custom_border_color{}; @@ -888,6 +916,9 @@ std::vector<const char*> Device::LoadExtensions(bool requires_surface) { test(has_ext_shader_atomic_int64, VK_KHR_SHADER_ATOMIC_INT64_EXTENSION_NAME, false); test(has_khr_workgroup_memory_explicit_layout, VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME, false); + test(has_khr_image_format_list, VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME, false); + test(has_khr_swapchain_mutable_format, VK_KHR_SWAPCHAIN_MUTABLE_FORMAT_EXTENSION_NAME, + false); test(has_ext_line_rasterization, VK_EXT_LINE_RASTERIZATION_EXTENSION_NAME, false); if (Settings::values.enable_nsight_aftermath) { test(nv_device_diagnostics_config, VK_NV_DEVICE_DIAGNOSTICS_CONFIG_EXTENSION_NAME, @@ -1066,6 +1097,11 @@ std::vector<const char*> Device::LoadExtensions(bool requires_surface) { khr_pipeline_executable_properties = true; } } + if (has_khr_image_format_list && has_khr_swapchain_mutable_format) { + extensions.push_back(VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME); + extensions.push_back(VK_KHR_SWAPCHAIN_MUTABLE_FORMAT_EXTENSION_NAME); + khr_swapchain_mutable_format = true; + } if (khr_push_descriptor) { VkPhysicalDevicePushDescriptorPropertiesKHR push_descriptor; push_descriptor.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PUSH_DESCRIPTOR_PROPERTIES_KHR; diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index 234d74129..d9e74f1aa 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -224,6 +224,11 @@ public: return khr_pipeline_executable_properties; } + /// Returns true if VK_KHR_swapchain_mutable_format is enabled. + bool IsKhrSwapchainMutableFormatEnabled() const { + return khr_swapchain_mutable_format; + } + /// Returns true if the device supports VK_KHR_workgroup_memory_explicit_layout. bool IsKhrWorkgroupMemoryExplicitLayoutSupported() const { return khr_workgroup_memory_explicit_layout; @@ -304,6 +309,11 @@ public: return has_renderdoc || has_nsight_graphics; } + /// Returns true when the device does not properly support cube compatibility. + bool HasBrokenCubeImageCompability() const { + return has_broken_cube_compatibility; + } + /// Returns the vendor name reported from Vulkan. std::string_view GetVendorName() const { return vendor_name; @@ -318,6 +328,10 @@ public: return device_access_memory; } + u32 GetSetsPerPool() const { + return sets_per_pool; + } + private: /// Checks if the physical device is suitable. void CheckSuitability(bool requires_swapchain) const; @@ -371,6 +385,7 @@ private: VkShaderStageFlags guest_warp_stages{}; ///< Stages where the guest warp size can be forced. u64 device_access_memory{}; ///< Total size of device local memory in bytes. u32 max_push_descriptors{}; ///< Maximum number of push descriptors + u32 sets_per_pool{}; ///< Sets per Description Pool bool is_optimal_astc_supported{}; ///< Support for native ASTC. bool is_float16_supported{}; ///< Support for float16 arithmetic. bool is_int8_supported{}; ///< Support for int8 arithmetic. @@ -390,6 +405,7 @@ private: bool khr_workgroup_memory_explicit_layout{}; ///< Support for explicit workgroup layouts. bool khr_push_descriptor{}; ///< Support for VK_KHR_push_descritor. bool khr_pipeline_executable_properties{}; ///< Support for executable properties. + bool khr_swapchain_mutable_format{}; ///< Support for VK_KHR_swapchain_mutable_format. bool ext_index_type_uint8{}; ///< Support for VK_EXT_index_type_uint8. bool ext_sampler_filter_minmax{}; ///< Support for VK_EXT_sampler_filter_minmax. bool ext_depth_range_unrestricted{}; ///< Support for VK_EXT_depth_range_unrestricted. @@ -406,6 +422,7 @@ private: bool ext_conservative_rasterization{}; ///< Support for VK_EXT_conservative_rasterization. bool ext_provoking_vertex{}; ///< Support for VK_EXT_provoking_vertex. bool nv_device_diagnostics_config{}; ///< Support for VK_NV_device_diagnostics_config. + bool has_broken_cube_compatibility{}; ///< Has broken cube compatiblity bit bool has_renderdoc{}; ///< Has RenderDoc attached bool has_nsight_graphics{}; ///< Has Nsight Graphics attached diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 19ba0dbba..402be6a78 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -108,6 +108,9 @@ add_executable(yuzu configuration/configure_system.cpp configuration/configure_system.h configuration/configure_system.ui + configuration/configure_tas.cpp + configuration/configure_tas.h + configuration/configure_tas.ui configuration/configure_touch_from_button.cpp configuration/configure_touch_from_button.h configuration/configure_touch_from_button.ui @@ -287,10 +290,6 @@ if (YUZU_USE_QT_WEB_ENGINE) target_compile_definitions(yuzu PRIVATE -DYUZU_USE_QT_WEB_ENGINE) endif () -if (YUZU_ENABLE_BOXCAT) - target_compile_definitions(yuzu PRIVATE -DYUZU_ENABLE_BOXCAT) -endif () - if(UNIX AND NOT APPLE) install(TARGETS yuzu RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin") endif() diff --git a/src/yuzu/applets/qt_web_browser.cpp b/src/yuzu/applets/qt_web_browser.cpp index 652d99570..7d433ca50 100644 --- a/src/yuzu/applets/qt_web_browser.cpp +++ b/src/yuzu/applets/qt_web_browser.cpp @@ -54,6 +54,9 @@ QtNXWebEngineView::QtNXWebEngineView(QWidget* parent, Core::System& system, input_interpreter(std::make_unique<InputInterpreter>(system)), default_profile{QWebEngineProfile::defaultProfile()}, global_settings{QWebEngineSettings::globalSettings()} { + default_profile->setPersistentStoragePath(QString::fromStdString(Common::FS::PathToUTF8String( + Common::FS::GetYuzuPath(Common::FS::YuzuPath::YuzuDir) / "qtwebengine"))); + QWebEngineScript gamepad; QWebEngineScript window_nx; diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 2e0ade815..8b9e186b0 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -36,6 +36,7 @@ #include "input_common/keyboard.h" #include "input_common/main.h" #include "input_common/mouse/mouse_input.h" +#include "input_common/tas/tas_input.h" #include "video_core/renderer_base.h" #include "video_core/video_core.h" #include "yuzu/bootmanager.h" @@ -301,17 +302,23 @@ GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread_, connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete); connect(this, &GRenderWindow::ExecuteProgramSignal, parent, &GMainWindow::OnExecuteProgram, Qt::QueuedConnection); + connect(this, &GRenderWindow::ExitSignal, parent, &GMainWindow::OnExit, Qt::QueuedConnection); } void GRenderWindow::ExecuteProgram(std::size_t program_index) { emit ExecuteProgramSignal(program_index); } +void GRenderWindow::Exit() { + emit ExitSignal(); +} + GRenderWindow::~GRenderWindow() { input_subsystem->Shutdown(); } void GRenderWindow::OnFrameDisplayed() { + input_subsystem->GetTas()->UpdateThread(); if (!first_frame) { first_frame = true; emit FirstFrameDisplayed(); diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index 402dd2ee1..54c4e2142 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -181,6 +181,9 @@ public: */ void ExecuteProgram(std::size_t program_index); + /// Instructs the window to exit the application. + void Exit(); + public slots: void OnEmulationStarting(EmuThread* emu_thread); void OnEmulationStopping(); @@ -191,6 +194,7 @@ signals: void Closed(); void FirstFrameDisplayed(); void ExecuteProgramSignal(std::size_t program_index); + void ExitSignal(); void MouseActivity(); private: diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 85d292bcc..eb941ce02 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -221,7 +221,7 @@ const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> Config::default // This must be in alphabetical order according to action name as it must have the same order as // UISetting::values.shortcuts, which is alphabetically ordered. // clang-format off -const std::array<UISettings::Shortcut, 18> Config::default_hotkeys{{ +const std::array<UISettings::Shortcut, 21> Config::default_hotkeys{{ {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}}, @@ -235,6 +235,9 @@ const std::array<UISettings::Shortcut, 18> Config::default_hotkeys{{ {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}}, + {QStringLiteral("TAS Start/Stop"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F5"), Qt::ApplicationShortcut}}, + {QStringLiteral("TAS Reset"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F6"), Qt::ApplicationShortcut}}, + {QStringLiteral("TAS Record"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F7"), Qt::ApplicationShortcut}}, {QStringLiteral("Toggle Filter Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), Qt::WindowShortcut}}, {QStringLiteral("Toggle Framerate Limit"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+U"), Qt::ApplicationShortcut}}, {QStringLiteral("Toggle Mouse Panning"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F9"), Qt::ApplicationShortcut}}, @@ -542,7 +545,6 @@ void Config::ReadAudioValues() { ReadBasicSetting(Settings::values.audio_device_id); ReadBasicSetting(Settings::values.sink_id); } - ReadGlobalSetting(Settings::values.enable_audio_stretching); ReadGlobalSetting(Settings::values.volume); qt_config->endGroup(); @@ -560,10 +562,20 @@ void Config::ReadControlValues() { ReadTouchscreenValues(); ReadMotionTouchValues(); +#ifdef _WIN32 + ReadBasicSetting(Settings::values.enable_raw_input); +#else + Settings::values.enable_raw_input = false; +#endif ReadBasicSetting(Settings::values.emulate_analog_keyboard); Settings::values.mouse_panning = false; ReadBasicSetting(Settings::values.mouse_panning_sensitivity); + ReadBasicSetting(Settings::values.tas_enable); + ReadBasicSetting(Settings::values.tas_loop); + ReadBasicSetting(Settings::values.tas_swap_controllers); + ReadBasicSetting(Settings::values.pause_tas_on_load); + ReadGlobalSetting(Settings::values.use_docked_mode); // Disable docked mode if handheld is selected @@ -661,6 +673,13 @@ void Config::ReadDataStorageValues() { QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::DumpDir))) .toString() .toStdString()); + FS::SetYuzuPath(FS::YuzuPath::TASDir, + qt_config + ->value(QStringLiteral("tas_directory"), + QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::TASDir))) + .toString() + .toStdString()); + ReadBasicSetting(Settings::values.gamecard_inserted); ReadBasicSetting(Settings::values.gamecard_current_game); ReadBasicSetting(Settings::values.gamecard_path); @@ -690,8 +709,6 @@ void Config::ReadDebuggingValues() { void Config::ReadServiceValues() { qt_config->beginGroup(QStringLiteral("Services")); - ReadBasicSetting(Settings::values.bcat_backend); - ReadBasicSetting(Settings::values.bcat_boxcat_local); ReadBasicSetting(Settings::values.network_interface); qt_config->endGroup(); } @@ -812,7 +829,7 @@ void Config::ReadRendererValues() { ReadGlobalSetting(Settings::values.use_disk_shader_cache); ReadGlobalSetting(Settings::values.gpu_accuracy); ReadGlobalSetting(Settings::values.use_asynchronous_gpu_emulation); - ReadGlobalSetting(Settings::values.use_nvdec_emulation); + ReadGlobalSetting(Settings::values.nvdec_emulation); ReadGlobalSetting(Settings::values.accelerate_astc); ReadGlobalSetting(Settings::values.use_vsync); ReadGlobalSetting(Settings::values.shader_backend); @@ -1163,7 +1180,6 @@ void Config::SaveAudioValues() { WriteBasicSetting(Settings::values.sink_id); WriteBasicSetting(Settings::values.audio_device_id); } - WriteGlobalSetting(Settings::values.enable_audio_stretching); WriteGlobalSetting(Settings::values.volume); qt_config->endGroup(); @@ -1184,10 +1200,16 @@ void Config::SaveControlValues() { WriteGlobalSetting(Settings::values.vibration_enabled); WriteGlobalSetting(Settings::values.enable_accurate_vibrations); WriteGlobalSetting(Settings::values.motion_enabled); + WriteBasicSetting(Settings::values.enable_raw_input); WriteBasicSetting(Settings::values.keyboard_enabled); WriteBasicSetting(Settings::values.emulate_analog_keyboard); WriteBasicSetting(Settings::values.mouse_panning_sensitivity); + WriteBasicSetting(Settings::values.tas_enable); + WriteBasicSetting(Settings::values.tas_loop); + WriteBasicSetting(Settings::values.tas_swap_controllers); + WriteBasicSetting(Settings::values.pause_tas_on_load); + qt_config->endGroup(); } @@ -1215,6 +1237,10 @@ void Config::SaveDataStorageValues() { WriteSetting(QStringLiteral("dump_directory"), QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::DumpDir)), QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::DumpDir))); + WriteSetting(QStringLiteral("tas_directory"), + QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::TASDir)), + QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::TASDir))); + WriteBasicSetting(Settings::values.gamecard_inserted); WriteBasicSetting(Settings::values.gamecard_current_game); WriteBasicSetting(Settings::values.gamecard_path); @@ -1241,8 +1267,6 @@ void Config::SaveDebuggingValues() { void Config::SaveNetworkValues() { qt_config->beginGroup(QStringLiteral("Services")); - WriteBasicSetting(Settings::values.bcat_backend); - WriteBasicSetting(Settings::values.bcat_boxcat_local); WriteBasicSetting(Settings::values.network_interface); qt_config->endGroup(); @@ -1349,7 +1373,10 @@ void Config::SaveRendererValues() { static_cast<u32>(Settings::values.gpu_accuracy.GetDefault()), Settings::values.gpu_accuracy.UsingGlobal()); WriteGlobalSetting(Settings::values.use_asynchronous_gpu_emulation); - WriteGlobalSetting(Settings::values.use_nvdec_emulation); + WriteSetting(QString::fromStdString(Settings::values.nvdec_emulation.GetLabel()), + static_cast<u32>(Settings::values.nvdec_emulation.GetValue(global)), + static_cast<u32>(Settings::values.nvdec_emulation.GetDefault()), + Settings::values.nvdec_emulation.UsingGlobal()); WriteGlobalSetting(Settings::values.accelerate_astc); WriteGlobalSetting(Settings::values.use_vsync); WriteSetting(QString::fromStdString(Settings::values.shader_backend.GetLabel()), diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h index 9555f4498..3ee694e7c 100644 --- a/src/yuzu/configuration/config.h +++ b/src/yuzu/configuration/config.h @@ -42,7 +42,7 @@ public: default_mouse_buttons; static const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> default_keyboard_keys; static const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> default_keyboard_mods; - static const std::array<UISettings::Shortcut, 18> default_hotkeys; + static const std::array<UISettings::Shortcut, 21> default_hotkeys; private: void Initialize(const std::string& config_name); @@ -182,5 +182,6 @@ private: Q_DECLARE_METATYPE(Settings::CPUAccuracy); Q_DECLARE_METATYPE(Settings::GPUAccuracy); Q_DECLARE_METATYPE(Settings::FullscreenMode); +Q_DECLARE_METATYPE(Settings::NvdecEmulation); Q_DECLARE_METATYPE(Settings::RendererBackend); Q_DECLARE_METATYPE(Settings::ShaderBackend); diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp index 1d84bf4ed..f437cb53d 100644 --- a/src/yuzu/configuration/configure_audio.cpp +++ b/src/yuzu/configuration/configure_audio.cpp @@ -50,8 +50,6 @@ void ConfigureAudio::SetConfiguration() { const auto volume_value = static_cast<int>(Settings::values.volume.GetValue()); ui->volume_slider->setValue(volume_value); - ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching.GetValue()); - if (!Settings::IsConfiguringGlobal()) { if (Settings::values.volume.UsingGlobal()) { ui->volume_combo_box->setCurrentIndex(0); @@ -100,8 +98,6 @@ void ConfigureAudio::SetVolumeIndicatorText(int percentage) { } void ConfigureAudio::ApplyConfiguration() { - ConfigurationShared::ApplyPerGameSetting(&Settings::values.enable_audio_stretching, - ui->toggle_audio_stretching, enable_audio_stretching); if (Settings::IsConfiguringGlobal()) { Settings::values.sink_id = @@ -162,15 +158,10 @@ void ConfigureAudio::RetranslateUI() { void ConfigureAudio::SetupPerGameUI() { if (Settings::IsConfiguringGlobal()) { ui->volume_slider->setEnabled(Settings::values.volume.UsingGlobal()); - ui->toggle_audio_stretching->setEnabled( - Settings::values.enable_audio_stretching.UsingGlobal()); return; } - ConfigurationShared::SetColoredTristate(ui->toggle_audio_stretching, - Settings::values.enable_audio_stretching, - enable_audio_stretching); connect(ui->volume_combo_box, qOverload<int>(&QComboBox::activated), this, [this](int index) { ui->volume_slider->setEnabled(index == 1); ConfigurationShared::SetHighlight(ui->volume_layout, index == 1); diff --git a/src/yuzu/configuration/configure_audio.h b/src/yuzu/configuration/configure_audio.h index 9dbd3d93e..5a01c8de7 100644 --- a/src/yuzu/configuration/configure_audio.h +++ b/src/yuzu/configuration/configure_audio.h @@ -41,6 +41,4 @@ private: void SetupPerGameUI(); std::unique_ptr<Ui::ConfigureAudio> ui; - - ConfigurationShared::CheckState enable_audio_stretching; }; diff --git a/src/yuzu/configuration/configure_audio.ui b/src/yuzu/configuration/configure_audio.ui index 9bd0cca96..bf736fc2c 100644 --- a/src/yuzu/configuration/configure_audio.ui +++ b/src/yuzu/configuration/configure_audio.ui @@ -32,16 +32,6 @@ </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" name="_2"> <item> <widget class="QLabel" name="audio_device_label"> diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui index 3fe9ff7de..b884a56b0 100644 --- a/src/yuzu/configuration/configure_debug.ui +++ b/src/yuzu/configuration/configure_debug.ui @@ -2,85 +2,55 @@ <ui version="4.0"> <class>ConfigureDebug</class> <widget class="QWidget" name="ConfigureDebug"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>400</width> - <height>777</height> - </rect> - </property> - <property name="windowTitle"> - <string>Form</string> - </property> <layout class="QVBoxLayout" name="verticalLayout_1"> <item> <widget class="QGroupBox" name="groupBox_2"> <property name="title"> <string>Logging</string> </property> - <layout class="QVBoxLayout" name="verticalLayout_4"> - <item> - <layout class="QHBoxLayout" name="horizontalLayout_2"> - <item> - <widget class="QLabel" name="label_1"> - <property name="text"> - <string>Global Log Filter</string> + <layout class="QGridLayout" name="gridLayout_1"> + <item row="0" column="0" colspan="2"> + <layout class="QHBoxLayout" name="horizontalLayout_1"> + <item> + <widget class="QLabel" name="label_1"> + <property name="text"> + <string>Global Log Filter</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="log_filter_edit"/> + </item> + </layout> + </item> + <item row="1" column="0"> + <widget class="QCheckBox" name="toggle_console"> + <property name="text"> + <string>Show Log in Console</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QPushButton" name="open_log_button"> + <property name="text"> + <string>Open Log Location</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QCheckBox" name="extended_logging"> + <property name="enabled"> + <bool>true</bool> </property> - </widget> - </item> - <item> - <widget class="QLineEdit" name="log_filter_edit"/> - </item> - </layout> - </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout_3"> - <item> - <widget class="QCheckBox" name="toggle_console"> - <property name="text"> - <string>Show Log in Console</string> + <property name="toolTip"> + <string>When checked, the max size of the log increases from 100 MB to 1 GB</string> </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="open_log_button"> <property name="text"> - <string>Open Log Location</string> + <string>Enable Extended Logging**</string> </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QCheckBox" name="extended_logging"> - <property name="enabled"> - <bool>true</bool> - </property> - <property name="toolTip"> - <string>When checked, the max size of the log increases from 100 MB to 1 GB</string> - </property> - <property name="text"> - <string>Enable Extended Logging</string> - </property> - </widget> - </item> - <item> - <widget class="QLabel" name="label_2"> - <property name="font"> - <font> - <italic>true</italic> - </font> - </property> - <property name="text"> - <string>This will be reset automatically when yuzu closes.</string> - </property> - <property name="indent"> - <number>20</number> - </property> - </widget> - </item> - </layout> + </widget> + </item> + </layout> </widget> </item> <item> @@ -111,7 +81,7 @@ <property name="title"> <string>Graphics</string> </property> - <layout class="QGridLayout" name="gridLayout_3"> + <layout class="QGridLayout" name="gridLayout_2"> <item row="0" column="0"> <widget class="QCheckBox" name="enable_graphics_debugging"> <property name="enabled"> @@ -176,33 +146,18 @@ <property name="title"> <string>Debugging</string> </property> - <layout class="QVBoxLayout" name="verticalLayout_7"> - <item> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="0" column="0"> <widget class="QCheckBox" name="fs_access_log"> <property name="text"> <string>Enable FS Access Log</string> </property> </widget> </item> - <item> + <item row="1" column="0"> <widget class="QCheckBox" name="reporting_services"> <property name="text"> - <string>Enable Verbose Reporting Services</string> - </property> - </widget> - </item> - <item> - <widget class="QLabel" name="label_4"> - <property name="font"> - <font> - <italic>true</italic> - </font> - </property> - <property name="text"> - <string>This will be reset automatically when yuzu closes.</string> - </property> - <property name="indent"> - <number>20</number> + <string>Enable Verbose Reporting Services**</string> </property> </widget> </item> @@ -214,47 +169,32 @@ <property name="title"> <string>Advanced</string> </property> - <layout class="QVBoxLayout" name="verticalLayout_8"> - <item> + <layout class="QGridLayout" name="gridLayout_4"> + <item> row="0" column="0"> <widget class="QCheckBox" name="quest_flag"> <property name="text"> <string>Kiosk (Quest) Mode</string> </property> </widget> </item> - <item> + <item row="1" column="0"> <widget class="QCheckBox" name="enable_cpu_debugging"> <property name="text"> <string>Enable CPU Debugging</string> </property> </widget> </item> - <item> + <item row="2" column="0"> <widget class="QCheckBox" name="use_debug_asserts"> <property name="text"> <string>Enable Debug Asserts</string> </property> </widget> </item> - <item> + <item row="0" column="1"> <widget class="QCheckBox" name="use_auto_stub"> <property name="text"> - <string>Enable Auto-Stub</string> - </property> - </widget> - </item> - <item> - <widget class="QLabel" name="label_5"> - <property name="font"> - <font> - <italic>true</italic> - </font> - </property> - <property name="text"> - <string>This will be reset automatically when yuzu closes.</string> - </property> - <property name="indent"> - <number>20</number> + <string>Enable Auto-Stub**</string> </property> </widget> </item> @@ -262,20 +202,19 @@ </widget> </item> <item> - <spacer name="verticalSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> + <widget class="QLabel" name="label_5"> + <property name="font"> + <font> + <italic>true</italic> + </font> </property> - <property name="sizeType"> - <enum>QSizePolicy::Expanding</enum> + <property name="text"> + <string>**This will be reset automatically when yuzu closes.</string> </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> + <property name="indent"> + <number>20</number> </property> - </spacer> + </widget> </item> </layout> </widget> diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index 37e896258..c594164be 100644 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp @@ -88,24 +88,30 @@ void ConfigureGraphics::SetConfiguration() { ui->api_widget->setEnabled(runtime_lock); ui->use_asynchronous_gpu_emulation->setEnabled(runtime_lock); ui->use_disk_shader_cache->setEnabled(runtime_lock); - ui->use_nvdec_emulation->setEnabled(runtime_lock); + ui->nvdec_emulation_widget->setEnabled(runtime_lock); ui->accelerate_astc->setEnabled(runtime_lock); 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()); - ui->use_nvdec_emulation->setChecked(Settings::values.use_nvdec_emulation.GetValue()); ui->accelerate_astc->setChecked(Settings::values.accelerate_astc.GetValue()); if (Settings::IsConfiguringGlobal()) { ui->api->setCurrentIndex(static_cast<int>(Settings::values.renderer_backend.GetValue())); ui->fullscreen_mode_combobox->setCurrentIndex( static_cast<int>(Settings::values.fullscreen_mode.GetValue())); + ui->nvdec_emulation->setCurrentIndex( + static_cast<int>(Settings::values.nvdec_emulation.GetValue())); ui->aspect_ratio_combobox->setCurrentIndex(Settings::values.aspect_ratio.GetValue()); } else { ConfigurationShared::SetPerGameSetting(ui->api, &Settings::values.renderer_backend); ConfigurationShared::SetHighlight(ui->api_widget, !Settings::values.renderer_backend.UsingGlobal()); + ConfigurationShared::SetPerGameSetting(ui->nvdec_emulation, + &Settings::values.nvdec_emulation); + ConfigurationShared::SetHighlight(ui->nvdec_emulation_widget, + !Settings::values.nvdec_emulation.UsingGlobal()); + ConfigurationShared::SetPerGameSetting(ui->fullscreen_mode_combobox, &Settings::values.fullscreen_mode); ConfigurationShared::SetHighlight(ui->fullscreen_mode_label, @@ -137,8 +143,6 @@ void ConfigureGraphics::ApplyConfiguration() { ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_asynchronous_gpu_emulation, ui->use_asynchronous_gpu_emulation, use_asynchronous_gpu_emulation); - ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_nvdec_emulation, - ui->use_nvdec_emulation, use_nvdec_emulation); ConfigurationShared::ApplyPerGameSetting(&Settings::values.accelerate_astc, ui->accelerate_astc, accelerate_astc); @@ -147,6 +151,9 @@ void ConfigureGraphics::ApplyConfiguration() { if (Settings::values.renderer_backend.UsingGlobal()) { Settings::values.renderer_backend.SetValue(GetCurrentGraphicsBackend()); } + if (Settings::values.nvdec_emulation.UsingGlobal()) { + Settings::values.nvdec_emulation.SetValue(GetCurrentNvdecEmulation()); + } if (Settings::values.shader_backend.UsingGlobal()) { Settings::values.shader_backend.SetValue(shader_backend); } @@ -180,6 +187,13 @@ void ConfigureGraphics::ApplyConfiguration() { } } + if (ui->nvdec_emulation->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { + Settings::values.nvdec_emulation.SetGlobal(true); + } else { + Settings::values.nvdec_emulation.SetGlobal(false); + Settings::values.nvdec_emulation.SetValue(GetCurrentNvdecEmulation()); + } + if (ui->bg_combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { Settings::values.bg_red.SetGlobal(true); Settings::values.bg_green.SetGlobal(true); @@ -278,6 +292,20 @@ Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const { ConfigurationShared::USE_GLOBAL_OFFSET); } +Settings::NvdecEmulation ConfigureGraphics::GetCurrentNvdecEmulation() const { + if (Settings::IsConfiguringGlobal()) { + return static_cast<Settings::NvdecEmulation>(ui->nvdec_emulation->currentIndex()); + } + + if (ui->nvdec_emulation->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { + Settings::values.nvdec_emulation.SetGlobal(true); + return Settings::values.nvdec_emulation.GetValue(); + } + Settings::values.nvdec_emulation.SetGlobal(false); + return static_cast<Settings::NvdecEmulation>(ui->nvdec_emulation->currentIndex() - + ConfigurationShared::USE_GLOBAL_OFFSET); +} + void ConfigureGraphics::SetupPerGameUI() { if (Settings::IsConfiguringGlobal()) { ui->api->setEnabled(Settings::values.renderer_backend.UsingGlobal()); @@ -286,7 +314,7 @@ void ConfigureGraphics::SetupPerGameUI() { 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_nvdec_emulation->setEnabled(Settings::values.use_nvdec_emulation.UsingGlobal()); + ui->nvdec_emulation->setEnabled(Settings::values.nvdec_emulation.UsingGlobal()); ui->accelerate_astc->setEnabled(Settings::values.accelerate_astc.UsingGlobal()); ui->use_disk_shader_cache->setEnabled(Settings::values.use_disk_shader_cache.UsingGlobal()); ui->bg_button->setEnabled(Settings::values.bg_red.UsingGlobal()); @@ -301,8 +329,6 @@ void ConfigureGraphics::SetupPerGameUI() { ConfigurationShared::SetColoredTristate( ui->use_disk_shader_cache, Settings::values.use_disk_shader_cache, use_disk_shader_cache); - ConfigurationShared::SetColoredTristate( - ui->use_nvdec_emulation, Settings::values.use_nvdec_emulation, use_nvdec_emulation); ConfigurationShared::SetColoredTristate(ui->accelerate_astc, Settings::values.accelerate_astc, accelerate_astc); ConfigurationShared::SetColoredTristate(ui->use_asynchronous_gpu_emulation, @@ -316,4 +342,6 @@ void ConfigureGraphics::SetupPerGameUI() { static_cast<int>(Settings::values.fullscreen_mode.GetValue(true))); ConfigurationShared::InsertGlobalItem( ui->api, static_cast<int>(Settings::values.renderer_backend.GetValue(true))); + ConfigurationShared::InsertGlobalItem( + ui->nvdec_emulation, static_cast<int>(Settings::values.nvdec_emulation.GetValue(true))); } diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h index c866b911b..7d7ac329d 100644 --- a/src/yuzu/configuration/configure_graphics.h +++ b/src/yuzu/configuration/configure_graphics.h @@ -43,6 +43,7 @@ private: void SetupPerGameUI(); Settings::RendererBackend GetCurrentGraphicsBackend() const; + Settings::NvdecEmulation GetCurrentNvdecEmulation() const; std::unique_ptr<Ui::ConfigureGraphics> ui; QColor bg_color; diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui index 099ddbb7c..1a12cfa4d 100644 --- a/src/yuzu/configuration/configure_graphics.ui +++ b/src/yuzu/configuration/configure_graphics.ui @@ -156,7 +156,7 @@ <item> <widget class="QCheckBox" name="use_disk_shader_cache"> <property name="text"> - <string>Use disk shader cache</string> + <string>Use disk pipeline cache</string> </property> </widget> </item> @@ -168,13 +168,6 @@ </widget> </item> <item> - <widget class="QCheckBox" name="use_nvdec_emulation"> - <property name="text"> - <string>Use NVDEC emulation</string> - </property> - </widget> - </item> - <item> <widget class="QCheckBox" name="accelerate_astc"> <property name="text"> <string>Accelerate ASTC texture decoding</string> @@ -182,6 +175,50 @@ </widget> </item> <item> + <widget class="QWidget" name="nvdec_emulation_widget" native="true"> + <layout class="QHBoxLayout" name="nvdec_emulation_layout"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="nvdec_emulation_label"> + <property name="text"> + <string>NVDEC emulation:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="nvdec_emulation"> + <item> + <property name="text"> + <string>Disabled</string> + </property> + </item> + <item> + <property name="text"> + <string>CPU Decoding</string> + </property> + </item> + <item> + <property name="text"> + <string>GPU Decoding</string> + </property> + </item> + </widget> + </item> + </layout> + </widget> + </item> + <item> <widget class="QWidget" name="fullscreen_mode_layout" native="true"> <layout class="QHBoxLayout" name="horizontalLayout_1"> <property name="leftMargin"> diff --git a/src/yuzu/configuration/configure_graphics_advanced.ui b/src/yuzu/configuration/configure_graphics_advanced.ui index 5891f8299..b91abc2f0 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.ui +++ b/src/yuzu/configuration/configure_graphics_advanced.ui @@ -82,7 +82,7 @@ <string>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</string> </property> <property name="text"> - <string>Use asynchronous shader building (hack)</string> + <string>Use asynchronous shader building (Hack)</string> </property> </widget> </item> @@ -92,7 +92,7 @@ <string>Enables Fast GPU Time. This option will force most games to run at their highest native resolution.</string> </property> <property name="text"> - <string>Use Fast GPU Time (hack)</string> + <string>Use Fast GPU Time (Hack)</string> </property> </widget> </item> diff --git a/src/yuzu/configuration/configure_input_advanced.cpp b/src/yuzu/configuration/configure_input_advanced.cpp index 2f1419b5b..b30f09013 100644 --- a/src/yuzu/configuration/configure_input_advanced.cpp +++ b/src/yuzu/configuration/configure_input_advanced.cpp @@ -88,6 +88,10 @@ ConfigureInputAdvanced::ConfigureInputAdvanced(QWidget* parent) connect(ui->buttonMotionTouch, &QPushButton::clicked, this, &ConfigureInputAdvanced::CallMotionTouchConfigDialog); +#ifndef _WIN32 + ui->enable_raw_input->setVisible(false); +#endif + LoadConfiguration(); } @@ -126,6 +130,7 @@ void ConfigureInputAdvanced::ApplyConfiguration() { Settings::values.mouse_panning_sensitivity = static_cast<float>(ui->mouse_panning_sensitivity->value()); Settings::values.touchscreen.enabled = ui->touchscreen_enabled->isChecked(); + Settings::values.enable_raw_input = ui->enable_raw_input->isChecked(); } void ConfigureInputAdvanced::LoadConfiguration() { @@ -155,6 +160,7 @@ void ConfigureInputAdvanced::LoadConfiguration() { ui->mouse_panning->setChecked(Settings::values.mouse_panning.GetValue()); ui->mouse_panning_sensitivity->setValue(Settings::values.mouse_panning_sensitivity.GetValue()); ui->touchscreen_enabled->setChecked(Settings::values.touchscreen.enabled); + ui->enable_raw_input->setChecked(Settings::values.enable_raw_input.GetValue()); UpdateUIEnabled(); } diff --git a/src/yuzu/configuration/configure_input_advanced.ui b/src/yuzu/configuration/configure_input_advanced.ui index d3ef5bd06..9095206a0 100644 --- a/src/yuzu/configuration/configure_input_advanced.ui +++ b/src/yuzu/configuration/configure_input_advanced.ui @@ -2672,6 +2672,22 @@ </property> </widget> </item> + <item row="9" column="0"> + <widget class="QCheckBox" name="enable_raw_input"> + <property name="toolTip"> + <string>Requires restarting yuzu</string> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>23</height> + </size> + </property> + <property name="text"> + <string>Enable XInput 8 player support (disables web applet)</string> + </property> + </widget> + </item> </layout> </widget> </item> diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index 7527c068b..88f4bf388 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -124,6 +124,19 @@ QString ButtonToText(const Common::ParamPackage& param) { return GetKeyName(param.Get("code", 0)); } + if (param.Get("engine", "") == "tas") { + if (param.Has("axis")) { + const QString axis_str = QString::fromStdString(param.Get("axis", "")); + + return QObject::tr("TAS Axis %1").arg(axis_str); + } + if (param.Has("button")) { + const QString button_str = QString::number(int(std::log2(param.Get("button", 0)))); + return QObject::tr("TAS Btn %1").arg(button_str); + } + return GetKeyName(param.Get("code", 0)); + } + if (param.Get("engine", "") == "cemuhookudp") { if (param.Has("pad_index")) { const QString motion_str = QString::fromStdString(param.Get("pad_index", "")); @@ -187,7 +200,8 @@ QString AnalogToText(const Common::ParamPackage& param, const std::string& dir) const QString axis_y_str = QString::fromStdString(param.Get("axis_y", "")); const bool invert_x = param.Get("invert_x", "+") == "-"; const bool invert_y = param.Get("invert_y", "+") == "-"; - if (engine_str == "sdl" || engine_str == "gcpad" || engine_str == "mouse") { + if (engine_str == "sdl" || engine_str == "gcpad" || engine_str == "mouse" || + engine_str == "tas") { if (dir == "modifier") { return QObject::tr("[unused]"); } @@ -926,9 +940,9 @@ void ConfigureInputPlayer::UpdateUI() { int slider_value; auto& param = analogs_param[analog_id]; - const bool is_controller = param.Get("engine", "") == "sdl" || - param.Get("engine", "") == "gcpad" || - param.Get("engine", "") == "mouse"; + const bool is_controller = + param.Get("engine", "") == "sdl" || param.Get("engine", "") == "gcpad" || + param.Get("engine", "") == "mouse" || param.Get("engine", "") == "tas"; if (is_controller) { if (!param.Has("deadzone")) { @@ -1045,8 +1059,12 @@ int ConfigureInputPlayer::GetIndexFromControllerType(Settings::ControllerType ty void ConfigureInputPlayer::UpdateInputDevices() { input_devices = input_subsystem->GetInputDevices(); ui->comboDevices->clear(); - for (auto device : input_devices) { - ui->comboDevices->addItem(QString::fromStdString(device.Get("display", "Unknown")), {}); + for (auto& device : input_devices) { + const std::string display = device.Get("display", "Unknown"); + ui->comboDevices->addItem(QString::fromStdString(display), {}); + if (display == "TAS") { + device.Set("pad", static_cast<u8>(player_index)); + } } } diff --git a/src/yuzu/configuration/configure_input_player_widget.cpp b/src/yuzu/configuration/configure_input_player_widget.cpp index 9c890ed5d..da328d904 100644 --- a/src/yuzu/configuration/configure_input_player_widget.cpp +++ b/src/yuzu/configuration/configure_input_player_widget.cpp @@ -175,7 +175,7 @@ void PlayerControlPreview::ResetInputs() { } void PlayerControlPreview::UpdateInput() { - if (!is_enabled && !mapping_active) { + if (!is_enabled && !mapping_active && !Settings::values.tas_enable) { return; } bool input_changed = false; @@ -222,6 +222,19 @@ void PlayerControlPreview::UpdateInput() { if (input_changed) { update(); + if (controller_callback.input != nullptr) { + ControllerInput input{ + .axis_values = {std::pair<float, float>{ + axis_values[Settings::NativeAnalog::LStick].value.x(), + axis_values[Settings::NativeAnalog::LStick].value.y()}, + std::pair<float, float>{ + axis_values[Settings::NativeAnalog::RStick].value.x(), + axis_values[Settings::NativeAnalog::RStick].value.y()}}, + .button_values = button_values, + .changed = true, + }; + controller_callback.input(std::move(input)); + } } if (mapping_active) { @@ -229,6 +242,10 @@ void PlayerControlPreview::UpdateInput() { } } +void PlayerControlPreview::SetCallBack(ControllerCallback callback_) { + controller_callback = std::move(callback_); +} + void PlayerControlPreview::paintEvent(QPaintEvent* event) { QFrame::paintEvent(event); QPainter p(this); diff --git a/src/yuzu/configuration/configure_input_player_widget.h b/src/yuzu/configuration/configure_input_player_widget.h index f4a6a5e1b..f4bbfa528 100644 --- a/src/yuzu/configuration/configure_input_player_widget.h +++ b/src/yuzu/configuration/configure_input_player_widget.h @@ -9,6 +9,7 @@ #include <QPointer> #include "common/settings.h" #include "core/frontend/input.h" +#include "yuzu/debugger/controller.h" class QLabel; @@ -33,6 +34,7 @@ public: void BeginMappingAnalog(std::size_t button_id); void EndMapping(); void UpdateInput(); + void SetCallBack(ControllerCallback callback_); protected: void paintEvent(QPaintEvent* event) override; @@ -181,6 +183,7 @@ private: using StickArray = std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID>; + ControllerCallback controller_callback; bool is_enabled{}; bool mapping_active{}; int blink_counter{}; diff --git a/src/yuzu/configuration/configure_network.cpp b/src/yuzu/configuration/configure_network.cpp index ae22f1018..cc15d36c2 100644 --- a/src/yuzu/configuration/configure_network.cpp +++ b/src/yuzu/configuration/configure_network.cpp @@ -6,64 +6,25 @@ #include <QtConcurrent/QtConcurrent> #include "common/settings.h" #include "core/core.h" -#include "core/hle/service/bcat/backend/boxcat.h" #include "core/network/network_interface.h" #include "ui_configure_network.h" #include "yuzu/configuration/configure_network.h" -#ifdef YUZU_ENABLE_BOXCAT -namespace { -QString FormatEventStatusString(const Service::BCAT::EventStatus& status) { - QString out; - - if (status.header.has_value()) { - out += QStringLiteral("<i>%1</i><br>").arg(QString::fromStdString(*status.header)); - } - - if (status.events.size() == 1) { - out += QStringLiteral("%1<br>").arg(QString::fromStdString(status.events.front())); - } else { - for (const auto& event : status.events) { - out += QStringLiteral("- %1<br>").arg(QString::fromStdString(event)); - } - } - - if (status.footer.has_value()) { - out += QStringLiteral("<i>%1</i><br>").arg(QString::fromStdString(*status.footer)); - } - - return out; -} -} // Anonymous namespace -#endif - ConfigureNetwork::ConfigureNetwork(QWidget* parent) : QWidget(parent), ui(std::make_unique<Ui::ConfigureNetwork>()) { ui->setupUi(this); - ui->bcat_source->addItem(QStringLiteral("None")); - ui->bcat_empty_label->setHidden(true); - ui->bcat_empty_header->setHidden(true); - -#ifdef YUZU_ENABLE_BOXCAT - ui->bcat_source->addItem(QStringLiteral("Boxcat"), QStringLiteral("boxcat")); -#endif - ui->network_interface->addItem(tr("None")); for (const auto& iface : Network::GetAvailableNetworkInterfaces()) { ui->network_interface->addItem(QString::fromStdString(iface.name)); } - connect(ui->bcat_source, QOverload<int>::of(&QComboBox::currentIndexChanged), this, - &ConfigureNetwork::OnBCATImplChanged); - this->SetConfiguration(); } ConfigureNetwork::~ConfigureNetwork() = default; void ConfigureNetwork::ApplyConfiguration() { - Settings::values.bcat_backend = ui->bcat_source->currentText().toLower().toStdString(); Settings::values.network_interface = ui->network_interface->currentText().toStdString(); } @@ -74,86 +35,8 @@ void ConfigureNetwork::RetranslateUi() { void ConfigureNetwork::SetConfiguration() { const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn(); - const int index = - ui->bcat_source->findData(QString::fromStdString(Settings::values.bcat_backend.GetValue())); - ui->bcat_source->setCurrentIndex(index == -1 ? 0 : index); - const std::string& network_interface = Settings::values.network_interface.GetValue(); ui->network_interface->setCurrentText(QString::fromStdString(network_interface)); ui->network_interface->setEnabled(runtime_lock); } - -std::pair<QString, QString> ConfigureNetwork::BCATDownloadEvents() { -#ifdef YUZU_ENABLE_BOXCAT - std::optional<std::string> global; - std::map<std::string, Service::BCAT::EventStatus> map; - const auto res = Service::BCAT::Boxcat::GetStatus(global, map); - - switch (res) { - case Service::BCAT::Boxcat::StatusResult::Success: - break; - case Service::BCAT::Boxcat::StatusResult::Offline: - return {QString{}, - tr("The boxcat service is offline or you are not connected to the internet.")}; - case Service::BCAT::Boxcat::StatusResult::ParseError: - return {QString{}, - tr("There was an error while processing the boxcat event data. Contact the yuzu " - "developers.")}; - case Service::BCAT::Boxcat::StatusResult::BadClientVersion: - return {QString{}, - tr("The version of yuzu you are using is either too new or too old for the server. " - "Try updating to the latest official release of yuzu.")}; - } - - if (map.empty()) { - return {QStringLiteral("Current Boxcat Events"), - tr("There are currently no events on boxcat.")}; - } - - QString out; - - if (global.has_value()) { - out += QStringLiteral("%1<br>").arg(QString::fromStdString(*global)); - } - - for (const auto& [key, value] : map) { - out += QStringLiteral("%1<b>%2</b><br>%3") - .arg(out.isEmpty() ? QString{} : QStringLiteral("<br>")) - .arg(QString::fromStdString(key)) - .arg(FormatEventStatusString(value)); - } - return {tr("Current Boxcat Events"), std::move(out)}; -#else - return {tr("Current Boxcat Events"), tr("There are currently no events on boxcat.")}; -#endif -} - -void ConfigureNetwork::OnBCATImplChanged() { -#ifdef YUZU_ENABLE_BOXCAT - const auto boxcat = ui->bcat_source->currentText() == QStringLiteral("Boxcat"); - ui->bcat_empty_header->setHidden(!boxcat); - ui->bcat_empty_label->setHidden(!boxcat); - ui->bcat_empty_header->setText(QString{}); - ui->bcat_empty_label->setText(tr("Yuzu is retrieving the latest boxcat status...")); - - if (!boxcat) - return; - - const auto future = QtConcurrent::run([this] { return BCATDownloadEvents(); }); - - watcher.setFuture(future); - connect(&watcher, &QFutureWatcher<std::pair<QString, QString>>::finished, this, - [this] { OnUpdateBCATEmptyLabel(watcher.result()); }); -#endif -} - -void ConfigureNetwork::OnUpdateBCATEmptyLabel(std::pair<QString, QString> string) { -#ifdef YUZU_ENABLE_BOXCAT - const auto boxcat = ui->bcat_source->currentText() == QStringLiteral("Boxcat"); - if (boxcat) { - ui->bcat_empty_header->setText(string.first); - ui->bcat_empty_label->setText(string.second); - } -#endif -} diff --git a/src/yuzu/configuration/configure_network.h b/src/yuzu/configuration/configure_network.h index 442b68e6b..028fd4acc 100644 --- a/src/yuzu/configuration/configure_network.h +++ b/src/yuzu/configuration/configure_network.h @@ -25,10 +25,5 @@ public: private: void SetConfiguration(); - std::pair<QString, QString> BCATDownloadEvents(); - void OnBCATImplChanged(); - void OnUpdateBCATEmptyLabel(std::pair<QString, QString> string); - std::unique_ptr<Ui::ConfigureNetwork> ui; - QFutureWatcher<std::pair<QString, QString>> watcher{this}; }; diff --git a/src/yuzu/configuration/configure_network.ui b/src/yuzu/configuration/configure_network.ui index 5f9b7e97b..9a79262f0 100644 --- a/src/yuzu/configuration/configure_network.ui +++ b/src/yuzu/configuration/configure_network.ui @@ -35,92 +35,6 @@ </layout> </widget> </item> - <item> - <widget class="QGroupBox" name="groupBox"> - <property name="title"> - <string>BCAT</string> - </property> - <layout class="QGridLayout" name="gridLayout"> - <item row="3" column="0"> - <widget class="QLabel" name="bcat_empty_header"> - <property name="text"> - <string/> - </property> - <property name="alignment"> - <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> - </property> - <property name="wordWrap"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="0" column="0"> - <widget class="QLabel" name="label"> - <property name="maximumSize"> - <size> - <width>16777215</width> - <height>16777215</height> - </size> - </property> - <property name="text"> - <string>BCAT Backend</string> - </property> - </widget> - </item> - <item row="1" column="1" colspan="2"> - <widget class="QLabel" name="label_2"> - <property name="maximumSize"> - <size> - <width>260</width> - <height>16777215</height> - </size> - </property> - <property name="text"> - <string>BCAT is Nintendo's way of sending data to games to engage its community and unlock additional content.</string> - </property> - <property name="wordWrap"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="2" column="1" colspan="2"> - <widget class="QLabel" name="label_3"> - <property name="text"> - <string><html><head/><body><p><a href="https://yuzu-emu.org/help/feature/boxcat"><span style=" text-decoration: underline; color:#0000ff;">Learn more about BCAT, Boxcat, and Current Events</span></a></p></body></html></string> - </property> - <property name="openExternalLinks"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="0" column="1" colspan="2"> - <widget class="QComboBox" name="bcat_source"/> - </item> - <item row="3" column="1" colspan="2"> - <widget class="QLabel" name="bcat_empty_label"> - <property name="enabled"> - <bool>true</bool> - </property> - <property name="maximumSize"> - <size> - <width>260</width> - <height>16777215</height> - </size> - </property> - <property name="text"> - <string/> - </property> - <property name="alignment"> - <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> - </property> - <property name="wordWrap"> - <bool>true</bool> - </property> - </widget> - </item> - </layout> - </widget> - </item> </layout> </item> <item> diff --git a/src/yuzu/configuration/configure_profile_manager.cpp b/src/yuzu/configuration/configure_profile_manager.cpp index ac849b01d..136614bf8 100644 --- a/src/yuzu/configuration/configure_profile_manager.cpp +++ b/src/yuzu/configuration/configure_profile_manager.cpp @@ -76,7 +76,7 @@ QString GetProfileUsernameFromUser(QWidget* parent, const QString& description_t } } // Anonymous namespace -ConfigureProfileManager ::ConfigureProfileManager(QWidget* parent) +ConfigureProfileManager::ConfigureProfileManager(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureProfileManager), profile_manager(std::make_unique<Service::Account::ProfileManager>()) { ui->setupUi(this); diff --git a/src/yuzu/configuration/configure_tas.cpp b/src/yuzu/configuration/configure_tas.cpp new file mode 100644 index 000000000..b666b175a --- /dev/null +++ b/src/yuzu/configuration/configure_tas.cpp @@ -0,0 +1,84 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <QFileDialog> +#include <QMessageBox> +#include "common/fs/fs.h" +#include "common/fs/path_util.h" +#include "common/settings.h" +#include "ui_configure_tas.h" +#include "yuzu/configuration/configure_tas.h" +#include "yuzu/uisettings.h" + +ConfigureTasDialog::ConfigureTasDialog(QWidget* parent) + : QDialog(parent), ui(std::make_unique<Ui::ConfigureTas>()) { + + ui->setupUi(this); + + setFocusPolicy(Qt::ClickFocus); + setWindowTitle(tr("TAS Configuration")); + + connect(ui->tas_path_button, &QToolButton::pressed, this, + [this] { SetDirectory(DirectoryTarget::TAS, ui->tas_path_edit); }); + + LoadConfiguration(); +} + +ConfigureTasDialog::~ConfigureTasDialog() = default; + +void ConfigureTasDialog::LoadConfiguration() { + ui->tas_path_edit->setText( + QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASDir))); + ui->tas_enable->setChecked(Settings::values.tas_enable.GetValue()); + ui->tas_control_swap->setChecked(Settings::values.tas_swap_controllers.GetValue()); + ui->tas_loop_script->setChecked(Settings::values.tas_loop.GetValue()); + ui->tas_pause_on_load->setChecked(Settings::values.pause_tas_on_load.GetValue()); +} + +void ConfigureTasDialog::ApplyConfiguration() { + Common::FS::SetYuzuPath(Common::FS::YuzuPath::TASDir, ui->tas_path_edit->text().toStdString()); + Settings::values.tas_enable.SetValue(ui->tas_enable->isChecked()); + Settings::values.tas_swap_controllers.SetValue(ui->tas_control_swap->isChecked()); + Settings::values.tas_loop.SetValue(ui->tas_loop_script->isChecked()); + Settings::values.pause_tas_on_load.SetValue(ui->tas_pause_on_load->isChecked()); +} + +void ConfigureTasDialog::SetDirectory(DirectoryTarget target, QLineEdit* edit) { + QString caption; + + switch (target) { + case DirectoryTarget::TAS: + caption = tr("Select TAS Load Directory..."); + break; + } + + QString str = QFileDialog::getExistingDirectory(this, caption, edit->text()); + + if (str.isEmpty()) { + return; + } + + if (str.back() != QChar::fromLatin1('/')) { + str.append(QChar::fromLatin1('/')); + } + + edit->setText(str); +} + +void ConfigureTasDialog::changeEvent(QEvent* event) { + if (event->type() == QEvent::LanguageChange) { + RetranslateUI(); + } + + QDialog::changeEvent(event); +} + +void ConfigureTasDialog::RetranslateUI() { + ui->retranslateUi(this); +} + +void ConfigureTasDialog::HandleApplyButtonClicked() { + UISettings::values.configuration_applied = true; + ApplyConfiguration(); +} diff --git a/src/yuzu/configuration/configure_tas.h b/src/yuzu/configuration/configure_tas.h new file mode 100644 index 000000000..1546bf16f --- /dev/null +++ b/src/yuzu/configuration/configure_tas.h @@ -0,0 +1,38 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <QDialog> + +namespace Ui { +class ConfigureTas; +} + +class ConfigureTasDialog : public QDialog { + Q_OBJECT + +public: + explicit ConfigureTasDialog(QWidget* parent); + ~ConfigureTasDialog() override; + + /// Save all button configurations to settings file + void ApplyConfiguration(); + +private: + enum class DirectoryTarget { + TAS, + }; + + void LoadConfiguration(); + + void SetDirectory(DirectoryTarget target, QLineEdit* edit); + + void changeEvent(QEvent* event) override; + void RetranslateUI(); + + void HandleApplyButtonClicked(); + + std::unique_ptr<Ui::ConfigureTas> ui; +}; diff --git a/src/yuzu/configuration/configure_tas.ui b/src/yuzu/configuration/configure_tas.ui new file mode 100644 index 000000000..95575ed9d --- /dev/null +++ b/src/yuzu/configuration/configure_tas.ui @@ -0,0 +1,153 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureTas</class> + <widget class="QDialog" name="ConfigureTas"> + <layout class="QVBoxLayout" name="verticalLayout_1"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_1"> + <item> + <widget class="QGroupBox" name="groupBox_1"> + <property name="title"> + <string>TAS</string> + </property> + <layout class="QGridLayout" name="gridLayout_1"> + <item row="0" column="0" colspan="4"> + <widget class="QLabel" name="label_1"> + <property name="text"> + <string>Reads controller input from scripts in the same format as TAS-nx scripts.<br/>For a more detailed explanation please consult the FAQ on the yuzu website.</string> + </property> + </widget> + </item> + <item row="1" column="0" colspan="4"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>To check which hotkeys control the playback/recording, please refer to the Hotkey settings (General -> Hotkeys).</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="2" column="0" colspan="4"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>WARNING: This is an experimental feature.<br/>It will not play back scripts frame perfectly with the current, imperfect syncing method.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QGroupBox" name="groupBox_2"> + <property name="title"> + <string>Settings</string> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="0" column="0" colspan="4"> + <widget class="QCheckBox" name="tas_enable"> + <property name="text"> + <string>Enable TAS features</string> + </property> + </widget> + </item> + <item row="1" column="0" colspan="4"> + <widget class="QCheckBox" name="tas_control_swap"> + <property name="text"> + <string>Automatic controller profile swapping</string> + </property> + </widget> + </item> + <item row="2" column="0" colspan="4"> + <widget class="QCheckBox" name="tas_loop_script"> + <property name="text"> + <string>Loop script</string> + </property> + </widget> + </item> + <item row="3" column="0" colspan="4"> + <widget class="QCheckBox" name="tas_pause_on_load"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Pause execution during loads</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QGroupBox" name="groupBox_3"> + <property name="title"> + <string>Script Directory</string> + </property> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="0" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Path</string> + </property> + </widget> + </item> + <item row="0" column="3"> + <widget class="QToolButton" name="tas_path_button"> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QLineEdit" name="tas_path_edit"/> + </item> + </layout> + </widget> + </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> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>ConfigureTas</receiver> + <slot>accept()</slot> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>ConfigureTas</receiver> + <slot>reject()</slot> + </connection> + </connections> +</ui> diff --git a/src/yuzu/configuration/configure_vibration.cpp b/src/yuzu/configuration/configure_vibration.cpp index 9d92c4949..46a0f3025 100644 --- a/src/yuzu/configuration/configure_vibration.cpp +++ b/src/yuzu/configuration/configure_vibration.cpp @@ -99,7 +99,7 @@ void ConfigureVibration::SetVibrationDevices(std::size_t player_index) { const auto guid = param.Get("guid", ""); const auto port = param.Get("port", ""); - if (engine.empty() || engine == "keyboard" || engine == "mouse") { + if (engine.empty() || engine == "keyboard" || engine == "mouse" || engine == "tas") { continue; } diff --git a/src/yuzu/debugger/controller.cpp b/src/yuzu/debugger/controller.cpp index c1fc69578..5a844409b 100644 --- a/src/yuzu/debugger/controller.cpp +++ b/src/yuzu/debugger/controller.cpp @@ -6,10 +6,13 @@ #include <QLayout> #include <QString> #include "common/settings.h" +#include "input_common/main.h" +#include "input_common/tas/tas_input.h" #include "yuzu/configuration/configure_input_player_widget.h" #include "yuzu/debugger/controller.h" -ControllerDialog::ControllerDialog(QWidget* parent) : QWidget(parent, Qt::Dialog) { +ControllerDialog::ControllerDialog(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_) + : QWidget(parent, Qt::Dialog), input_subsystem{input_subsystem_} { setObjectName(QStringLiteral("Controller")); setWindowTitle(tr("Controller P1")); resize(500, 350); @@ -38,6 +41,9 @@ void ControllerDialog::refreshConfiguration() { constexpr std::size_t player = 0; widget->SetPlayerInputRaw(player, players[player].buttons, players[player].analogs); widget->SetControllerType(players[player].controller_type); + ControllerCallback callback{[this](ControllerInput input) { InputController(input); }}; + widget->SetCallBack(callback); + widget->repaint(); widget->SetConnectedStatus(players[player].connected); } @@ -67,3 +73,13 @@ void ControllerDialog::hideEvent(QHideEvent* ev) { widget->SetConnectedStatus(false); QWidget::hideEvent(ev); } + +void ControllerDialog::InputController(ControllerInput input) { + u32 buttons = 0; + int index = 0; + for (bool btn : input.button_values) { + buttons |= (btn ? 1U : 0U) << index; + index++; + } + input_subsystem->GetTas()->RecordInput(buttons, input.axis_values); +} diff --git a/src/yuzu/debugger/controller.h b/src/yuzu/debugger/controller.h index c54750070..7742db58b 100644 --- a/src/yuzu/debugger/controller.h +++ b/src/yuzu/debugger/controller.h @@ -4,18 +4,35 @@ #pragma once +#include <QFileSystemWatcher> #include <QWidget> +#include "common/settings.h" class QAction; class QHideEvent; class QShowEvent; class PlayerControlPreview; +namespace InputCommon { +class InputSubsystem; +} + +struct ControllerInput { + std::array<std::pair<float, float>, Settings::NativeAnalog::NUM_STICKS_HID> axis_values{}; + std::array<bool, Settings::NativeButton::NumButtons> button_values{}; + bool changed{}; +}; + +struct ControllerCallback { + std::function<void(ControllerInput)> input; +}; + class ControllerDialog : public QWidget { Q_OBJECT public: - explicit ControllerDialog(QWidget* parent = nullptr); + explicit ControllerDialog(QWidget* parent = nullptr, + InputCommon::InputSubsystem* input_subsystem_ = nullptr); /// Returns a QAction that can be used to toggle visibility of this dialog. QAction* toggleViewAction(); @@ -26,6 +43,9 @@ protected: void hideEvent(QHideEvent* ev) override; private: + void InputController(ControllerInput input); QAction* toggle_view_action = nullptr; + QFileSystemWatcher* watcher = nullptr; PlayerControlPreview* widget; + InputCommon::InputSubsystem* input_subsystem; }; diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index e97804220..f9d949e75 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -515,16 +515,16 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location")); QAction* open_mod_location = context_menu.addAction(tr("Open Mod Data Location")); QAction* open_transferable_shader_cache = - context_menu.addAction(tr("Open Transferable Shader Cache")); + context_menu.addAction(tr("Open Transferable Pipeline Cache")); context_menu.addSeparator(); QMenu* remove_menu = context_menu.addMenu(tr("Remove")); QAction* remove_update = remove_menu->addAction(tr("Remove Installed Update")); QAction* remove_dlc = remove_menu->addAction(tr("Remove All Installed DLC")); QAction* remove_custom_config = remove_menu->addAction(tr("Remove Custom Configuration")); - QAction* remove_gl_shader_cache = remove_menu->addAction(tr("Remove OpenGL Shader Cache")); - QAction* remove_vk_shader_cache = remove_menu->addAction(tr("Remove Vulkan Shader Cache")); + QAction* remove_gl_shader_cache = remove_menu->addAction(tr("Remove OpenGL Pipeline Cache")); + QAction* remove_vk_shader_cache = remove_menu->addAction(tr("Remove Vulkan Pipeline Cache")); remove_menu->addSeparator(); - QAction* remove_shader_cache = remove_menu->addAction(tr("Remove All Shader Caches")); + QAction* remove_shader_cache = remove_menu->addAction(tr("Remove All Pipeline Caches")); QAction* remove_all_content = remove_menu->addAction(tr("Remove All Installed Contents")); QMenu* dump_romfs_menu = context_menu.addMenu(tr("Dump RomFS")); QAction* dump_romfs = dump_romfs_menu->addAction(tr("Dump RomFS")); diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index e36774cc6..0bd0c5b04 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -19,6 +19,7 @@ #include "common/nvidia_flags.h" #include "configuration/configure_input.h" #include "configuration/configure_per_game.h" +#include "configuration/configure_tas.h" #include "configuration/configure_vibration.h" #include "core/file_sys/vfs.h" #include "core/file_sys/vfs_real.h" @@ -102,6 +103,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "core/perf_stats.h" #include "core/telemetry_session.h" #include "input_common/main.h" +#include "input_common/tas/tas_input.h" #include "util/overlay_dialog.h" #include "video_core/gpu.h" #include "video_core/renderer_base.h" @@ -557,7 +559,8 @@ void GMainWindow::WebBrowserOpenWebPage(const std::string& main_url, const std::string& additional_args, bool is_local) { #ifdef YUZU_USE_QT_WEB_ENGINE - if (disable_web_applet) { + // Raw input breaks with the web applet, Disable web applets if enabled + if (disable_web_applet || Settings::values.enable_raw_input) { emit WebBrowserClosed(Service::AM::Applets::WebExitReason::WindowClosed, "http://localhost/"); return; @@ -746,6 +749,11 @@ void GMainWindow::InitializeWidgets() { statusBar()->addPermanentWidget(label); } + tas_label = new QLabel(); + tas_label->setObjectName(QStringLiteral("TASlabel")); + tas_label->setFocusPolicy(Qt::NoFocus); + statusBar()->insertPermanentWidget(0, tas_label); + // Setup Dock button dock_status_button = new QPushButton(); dock_status_button->setObjectName(QStringLiteral("TogglableStatusBarButton")); @@ -840,7 +848,7 @@ void GMainWindow::InitializeDebugWidgets() { waitTreeWidget->hide(); debug_menu->addAction(waitTreeWidget->toggleViewAction()); - controller_dialog = new ControllerDialog(this); + controller_dialog = new ControllerDialog(this, input_subsystem.get()); controller_dialog->hide(); debug_menu->addAction(controller_dialog->toggleViewAction()); @@ -1013,6 +1021,28 @@ void GMainWindow::InitializeHotkeys() { render_window->setAttribute(Qt::WA_Hover, true); } }); + connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Start/Stop"), this), + &QShortcut::activated, this, [&] { + if (!emulation_running) { + return; + } + input_subsystem->GetTas()->StartStop(); + }); + connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Reset"), this), + &QShortcut::activated, this, [&] { input_subsystem->GetTas()->Reset(); }); + connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Record"), this), + &QShortcut::activated, this, [&] { + if (!emulation_running) { + return; + } + bool is_recording = input_subsystem->GetTas()->Record(); + if (!is_recording) { + const auto res = QMessageBox::question(this, tr("TAS Recording"), + tr("Overwrite file of player 1?"), + QMessageBox::Yes | QMessageBox::No); + input_subsystem->GetTas()->SaveRecording(res == QMessageBox::Yes); + } + }); } void GMainWindow::SetDefaultUIGeometry() { @@ -1131,6 +1161,7 @@ void GMainWindow::ConnectMenuEvents() { connect(ui.action_Open_FAQ, &QAction::triggered, this, &GMainWindow::OnOpenFAQ); connect(ui.action_Restart, &QAction::triggered, this, [this] { BootGame(QString(game_path)); }); connect(ui.action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure); + connect(ui.action_Configure_Tas, &QAction::triggered, this, &GMainWindow::OnConfigureTas); connect(ui.action_Configure_Current_Game, &QAction::triggered, this, &GMainWindow::OnConfigurePerGame); @@ -1353,6 +1384,9 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t system.RegisterExecuteProgramCallback( [this](std::size_t program_index) { render_window->ExecuteProgram(program_index); }); + // Register an Exit callback such that Core can exit the currently running application. + system.RegisterExitCallback([this]() { render_window->Exit(); }); + connect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame); connect(render_window, &GRenderWindow::MouseActivity, this, &GMainWindow::OnMouseActivity); // BlockingQueuedConnection is important here, it makes sure we've finished refreshing our views @@ -1463,6 +1497,8 @@ void GMainWindow::ShutdownGame() { game_list->show(); } game_list->SetFilterFocus(); + tas_label->clear(); + input_subsystem->GetTas()->Stop(); render_window->removeEventFilter(render_window); render_window->setAttribute(Qt::WA_Hover, false); @@ -2436,6 +2472,10 @@ void GMainWindow::OnExecuteProgram(std::size_t program_index) { BootGame(last_filename_booted, 0, program_index); } +void GMainWindow::OnExit() { + OnStopGame(); +} + void GMainWindow::ErrorDisplayDisplayError(QString error_code, QString error_text) { OverlayDialog dialog(render_window, Core::System::GetInstance(), error_code, error_text, QString{}, tr("OK"), Qt::AlignLeft | Qt::AlignVCenter); @@ -2697,6 +2737,19 @@ void GMainWindow::OnConfigure() { UpdateStatusButtons(); } +void GMainWindow::OnConfigureTas() { + const auto& system = Core::System::GetInstance(); + ConfigureTasDialog dialog(this); + const auto result = dialog.exec(); + + if (result != QDialog::Accepted && !UISettings::values.configuration_applied) { + Settings::RestoreGlobalState(system.IsPoweredOn()); + return; + } else if (result == QDialog::Accepted) { + dialog.ApplyConfiguration(); + } +} + void GMainWindow::OnConfigurePerGame() { const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID(); OpenPerGameConfiguration(title_id, game_path.toStdString()); @@ -2873,12 +2926,32 @@ void GMainWindow::UpdateWindowTitle(std::string_view title_name, std::string_vie } } +QString GMainWindow::GetTasStateDescription() const { + auto [tas_status, current_tas_frame, total_tas_frames] = input_subsystem->GetTas()->GetStatus(); + switch (tas_status) { + case TasInput::TasState::Running: + return tr("TAS state: Running %1/%2").arg(current_tas_frame).arg(total_tas_frames); + case TasInput::TasState::Recording: + return tr("TAS state: Recording %1").arg(total_tas_frames); + case TasInput::TasState::Stopped: + return tr("TAS state: Idle %1/%2").arg(current_tas_frame).arg(total_tas_frames); + default: + return tr("TAS State: Invalid"); + } +} + void GMainWindow::UpdateStatusBar() { if (emu_thread == nullptr) { status_bar_update_timer.stop(); return; } + if (Settings::values.tas_enable) { + tas_label->setText(GetTasStateDescription()); + } else { + tas_label->clear(); + } + auto& system = Core::System::GetInstance(); auto results = system.GetAndResetPerfStats(); auto& shader_notify = system.GPU().ShaderNotify(); @@ -3174,12 +3247,11 @@ std::optional<u64> GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProv } bool GMainWindow::ConfirmClose() { - if (emu_thread == nullptr || !UISettings::values.confirm_before_closing) + if (emu_thread == nullptr || !UISettings::values.confirm_before_closing) { return true; - - QMessageBox::StandardButton answer = - QMessageBox::question(this, tr("yuzu"), tr("Are you sure you want to close yuzu?"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); + } + const auto text = tr("Are you sure you want to close yuzu?"); + const auto answer = QMessageBox::question(this, tr("yuzu"), text); return answer != QMessageBox::No; } @@ -3261,14 +3333,13 @@ bool GMainWindow::ConfirmChangeGame() { } bool GMainWindow::ConfirmForceLockedExit() { - if (emu_thread == nullptr) + if (emu_thread == nullptr || !UISettings::values.confirm_before_closing) { return true; + } + const auto text = tr("The currently running application has requested yuzu to not exit.\n\n" + "Would you like to bypass this and exit anyway?"); - const auto answer = - QMessageBox::question(this, tr("yuzu"), - tr("The currently running application has requested yuzu to not " - "exit.\n\nWould you like to bypass this and exit anyway?"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); + const auto answer = QMessageBox::question(this, tr("yuzu"), text); return answer != QMessageBox::No; } diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 38e66ccd0..60ce01471 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -153,6 +153,7 @@ signals: public slots: void OnLoadComplete(); void OnExecuteProgram(std::size_t program_index); + void OnExit(); void ControllerSelectorReconfigureControllers( const Core::Frontend::ControllerParameters& parameters); void SoftwareKeyboardInitialize( @@ -259,6 +260,7 @@ private slots: void OnMenuInstallToNAND(); void OnMenuRecentFile(); void OnConfigure(); + void OnConfigureTas(); void OnConfigurePerGame(); void OnLoadAmiibo(); void OnOpenYuzuFolder(); @@ -300,6 +302,7 @@ private: void OpenURL(const QUrl& url); void LoadTranslation(); void OpenPerGameConfiguration(u64 title_id, const std::string& file_name); + QString GetTasStateDescription() const; Ui::MainWindow ui; @@ -318,6 +321,7 @@ private: QLabel* emu_speed_label = nullptr; QLabel* game_fps_label = nullptr; QLabel* emu_frametime_label = nullptr; + QLabel* tas_label = nullptr; QPushButton* gpu_accuracy_button = nullptr; QPushButton* renderer_status_button = nullptr; QPushButton* dock_status_button = nullptr; diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index 048870687..653c010d8 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -100,6 +100,7 @@ <addaction name="action_Rederive"/> <addaction name="separator"/> <addaction name="action_Capture_Screenshot"/> + <addaction name="action_Configure_Tas"/> </widget> <widget class="QMenu" name="menu_Help"> <property name="title"> @@ -294,6 +295,11 @@ <string>&Capture Screenshot</string> </property> </action> + <action name="action_Configure_Tas"> + <property name="text"> + <string>Configure &TAS...</string> + </property> + </action> <action name="action_Configure_Current_Game"> <property name="enabled"> <bool>false</bool> diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index 757dd1ea0..434518d53 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -465,7 +465,7 @@ void Config::ReadValues() { ReadSetting("Renderer", Settings::values.disable_fps_limit); ReadSetting("Renderer", Settings::values.shader_backend); ReadSetting("Renderer", Settings::values.use_asynchronous_shaders); - ReadSetting("Renderer", Settings::values.use_nvdec_emulation); + ReadSetting("Renderer", Settings::values.nvdec_emulation); ReadSetting("Renderer", Settings::values.accelerate_astc); ReadSetting("Renderer", Settings::values.use_fast_gpu_time); @@ -475,7 +475,6 @@ void Config::ReadValues() { // Audio ReadSetting("Audio", Settings::values.sink_id); - ReadSetting("Audio", Settings::values.enable_audio_stretching); ReadSetting("Audio", Settings::values.audio_device_id); ReadSetting("Audio", Settings::values.volume); @@ -519,10 +518,6 @@ void Config::ReadValues() { ReadSetting("WebService", Settings::values.web_api_url); ReadSetting("WebService", Settings::values.yuzu_username); ReadSetting("WebService", Settings::values.yuzu_token); - - // Services - ReadSetting("Services", Settings::values.bcat_backend); - ReadSetting("Services", Settings::values.bcat_boxcat_local); } void Config::Reload() { diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index e02eceb99..8119a50d8 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -261,9 +261,9 @@ shader_backend = # 0 (default): Off, 1: On use_asynchronous_shaders = -# Enable NVDEC emulation. -# 0: Off, 1 (default): On -use_nvdec_emulation = +# NVDEC emulation. +# 0: Disabled, 1: CPU Decoding, 2 (default): GPU Decoding +nvdec_emulation = # Accelerate ASTC texture decoding. # 0: Off, 1 (default): On @@ -428,11 +428,6 @@ web_api_url = https://api.yuzu-emu.org yuzu_username = yuzu_token = -[Services] -# The name of the backend to use for BCAT -# If this is set to 'boxcat' boxcat will be used, otherwise a null implementation will be used -bcat_backend = - [AddOns] # Used to disable add-ons # List of title IDs of games that will have add-ons disabled (separated by '|'): |