From d4cb0eac87c9dea121a0f6bd2355fa5e6c641274 Mon Sep 17 00:00:00 2001 From: FengChen Date: Sat, 10 Sep 2022 17:09:45 +0800 Subject: [PATCH 01/91] video_core: Fix legacy to generic location unpaired --- .../frontend/maxwell/translate_program.cpp | 39 ++++++++++++------- src/shader_recompiler/runtime_info.h | 2 + src/shader_recompiler/shader_info.h | 3 ++ .../renderer_opengl/gl_shader_cache.cpp | 1 + .../renderer_vulkan/vk_pipeline_cache.cpp | 1 + 5 files changed, 31 insertions(+), 15 deletions(-) diff --git a/src/shader_recompiler/frontend/maxwell/translate_program.cpp b/src/shader_recompiler/frontend/maxwell/translate_program.cpp index 77efb4f577..3aee33a961 100644 --- a/src/shader_recompiler/frontend/maxwell/translate_program.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate_program.cpp @@ -137,28 +137,35 @@ bool IsLegacyAttribute(IR::Attribute attribute) { } std::map GenerateLegacyToGenericMappings( - const VaryingState& state, std::queue ununsed_generics) { + const VaryingState& state, std::queue unused_generics, + const std::map& previous_stage_mapping) { std::map mapping; + auto update_mapping = [&mapping, &unused_generics, previous_stage_mapping](IR::Attribute attr, + size_t count) { + if (previous_stage_mapping.find(attr) != previous_stage_mapping.end()) { + for (size_t i = 0; i < count; ++i) { + mapping.insert({attr + i, previous_stage_mapping.at(attr + i)}); + } + } else { + for (size_t i = 0; i < count; ++i) { + mapping.insert({attr + i, unused_generics.front() + i}); + } + unused_generics.pop(); + } + }; for (size_t index = 0; index < 4; ++index) { auto attr = IR::Attribute::ColorFrontDiffuseR + index * 4; if (state.AnyComponent(attr)) { - for (size_t i = 0; i < 4; ++i) { - mapping.insert({attr + i, ununsed_generics.front() + i}); - } - ununsed_generics.pop(); + update_mapping(attr, 4); } } if (state[IR::Attribute::FogCoordinate]) { - mapping.insert({IR::Attribute::FogCoordinate, ununsed_generics.front()}); - ununsed_generics.pop(); + update_mapping(IR::Attribute::FogCoordinate, 1); } for (size_t index = 0; index < IR::NUM_FIXEDFNCTEXTURE; ++index) { auto attr = IR::Attribute::FixedFncTexture0S + index * 4; if (state.AnyComponent(attr)) { - for (size_t i = 0; i < 4; ++i) { - mapping.insert({attr + i, ununsed_generics.front() + i}); - } - ununsed_generics.pop(); + update_mapping(attr, 4); } } return mapping; @@ -271,15 +278,16 @@ void ConvertLegacyToGeneric(IR::Program& program, const Shader::RuntimeInfo& run ununsed_output_generics.push(IR::Attribute::Generic0X + index * 4); } } - auto mappings = GenerateLegacyToGenericMappings(stores, ununsed_output_generics); + program.info.legacy_stores_mapping = + GenerateLegacyToGenericMappings(stores, ununsed_output_generics, {}); for (IR::Block* const block : program.post_order_blocks) { for (IR::Inst& inst : block->Instructions()) { switch (inst.GetOpcode()) { case IR::Opcode::SetAttribute: { const auto attr = inst.Arg(0).Attribute(); if (IsLegacyAttribute(attr)) { - stores.Set(mappings[attr], true); - inst.SetArg(0, Shader::IR::Value(mappings[attr])); + stores.Set(program.info.legacy_stores_mapping[attr], true); + inst.SetArg(0, Shader::IR::Value(program.info.legacy_stores_mapping[attr])); } break; } @@ -300,7 +308,8 @@ void ConvertLegacyToGeneric(IR::Program& program, const Shader::RuntimeInfo& run ununsed_input_generics.push(IR::Attribute::Generic0X + index * 4); } } - auto mappings = GenerateLegacyToGenericMappings(loads, ununsed_input_generics); + auto mappings = GenerateLegacyToGenericMappings( + loads, ununsed_input_generics, runtime_info.previous_stage_legacy_stores_mapping); for (IR::Block* const block : program.post_order_blocks) { for (IR::Inst& inst : block->Instructions()) { switch (inst.GetOpcode()) { diff --git a/src/shader_recompiler/runtime_info.h b/src/shader_recompiler/runtime_info.h index dcb5ab158d..549b81ef75 100644 --- a/src/shader_recompiler/runtime_info.h +++ b/src/shader_recompiler/runtime_info.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include #include @@ -60,6 +61,7 @@ struct TransformFeedbackVarying { struct RuntimeInfo { std::array generic_input_types{}; VaryingState previous_stage_stores; + std::map previous_stage_legacy_stores_mapping; bool convert_depth_mode{}; bool force_early_z{}; diff --git a/src/shader_recompiler/shader_info.h b/src/shader_recompiler/shader_info.h index f5690805c4..c3c586adae 100644 --- a/src/shader_recompiler/shader_info.h +++ b/src/shader_recompiler/shader_info.h @@ -5,6 +5,7 @@ #include #include +#include #include "common/common_types.h" #include "shader_recompiler/frontend/ir/type.h" @@ -123,6 +124,8 @@ struct Info { VaryingState stores; VaryingState passthrough; + std::map legacy_stores_mapping; + bool loads_indexed_attributes{}; std::array stores_frag_color{}; diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp index ddb70934c1..fa05b47ff6 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp @@ -63,6 +63,7 @@ Shader::RuntimeInfo MakeRuntimeInfo(const GraphicsPipelineKey& key, Shader::RuntimeInfo info; if (previous_program) { info.previous_stage_stores = previous_program->info.stores; + info.previous_stage_legacy_stores_mapping = previous_program->info.legacy_stores_mapping; } else { // Mark all stores as available for vertex shaders info.previous_stage_stores.mask.set(); diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 9708dc45e4..53dda50484 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -131,6 +131,7 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span program Shader::RuntimeInfo info; if (previous_program) { info.previous_stage_stores = previous_program->info.stores; + info.previous_stage_legacy_stores_mapping = previous_program->info.legacy_stores_mapping; if (previous_program->is_geometry_passthrough) { info.previous_stage_stores.mask |= previous_program->info.passthrough.mask; } From 40af1111c27968b5298a6ecd07b319422fed665d Mon Sep 17 00:00:00 2001 From: Kyle Kienapfel Date: Tue, 11 Oct 2022 13:01:29 -0700 Subject: [PATCH 02/91] CMake: Try add library "LZ4::lz4_shared" if "lz4::lz4" is unavailable Right now this looks like a distro specific problem, but we'll have to see. Over on Gentoo: with lz4 1.9.3 there is a lz4::lz4 library target, with 1.9.4 it's no longer mentioned in the cmake files provided by the package. (/usr/lib64/cmake/lz4) arch and openSUSE have lz4 1.9.4 available so I checked there, they only have .pc files for pkg-config, so asking for "lz4::lz4" works as usual MSVC does require "lz4::lz4" to be asked for --- src/common/CMakeLists.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index a026968734..46cf75fdeb 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -169,7 +169,11 @@ endif() create_target_directory_groups(common) target_link_libraries(common PUBLIC ${Boost_LIBRARIES} fmt::fmt microprofile Threads::Threads) -target_link_libraries(common PRIVATE lz4::lz4) +if (TARGET lz4::lz4) + target_link_libraries(common PRIVATE lz4::lz4) +else() + target_link_libraries(common PRIVATE LZ4::lz4_shared) +endif() if (TARGET zstd::zstd) target_link_libraries(common PRIVATE zstd::zstd) else() From 20139f8a551098633e4ea3eab079b590c47ee0f5 Mon Sep 17 00:00:00 2001 From: FengChen Date: Mon, 17 Oct 2022 09:40:44 +0800 Subject: [PATCH 03/91] Address feedback --- .../frontend/maxwell/translate_program.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/shader_recompiler/frontend/maxwell/translate_program.cpp b/src/shader_recompiler/frontend/maxwell/translate_program.cpp index 3aee33a961..b58741d4d0 100644 --- a/src/shader_recompiler/frontend/maxwell/translate_program.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate_program.cpp @@ -272,14 +272,14 @@ IR::Program MergeDualVertexPrograms(IR::Program& vertex_a, IR::Program& vertex_b void ConvertLegacyToGeneric(IR::Program& program, const Shader::RuntimeInfo& runtime_info) { auto& stores = program.info.stores; if (stores.Legacy()) { - std::queue ununsed_output_generics{}; + std::queue unused_output_generics{}; for (size_t index = 0; index < IR::NUM_GENERICS; ++index) { if (!stores.Generic(index)) { - ununsed_output_generics.push(IR::Attribute::Generic0X + index * 4); + unused_output_generics.push(IR::Attribute::Generic0X + index * 4); } } program.info.legacy_stores_mapping = - GenerateLegacyToGenericMappings(stores, ununsed_output_generics, {}); + GenerateLegacyToGenericMappings(stores, unused_output_generics, {}); for (IR::Block* const block : program.post_order_blocks) { for (IR::Inst& inst : block->Instructions()) { switch (inst.GetOpcode()) { @@ -300,16 +300,16 @@ void ConvertLegacyToGeneric(IR::Program& program, const Shader::RuntimeInfo& run auto& loads = program.info.loads; if (loads.Legacy()) { - std::queue ununsed_input_generics{}; + std::queue unused_input_generics{}; 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) || !loads.Generic(index) || input_type == AttributeType::Disabled) { - ununsed_input_generics.push(IR::Attribute::Generic0X + index * 4); + unused_input_generics.push(IR::Attribute::Generic0X + index * 4); } } auto mappings = GenerateLegacyToGenericMappings( - loads, ununsed_input_generics, runtime_info.previous_stage_legacy_stores_mapping); + loads, unused_input_generics, runtime_info.previous_stage_legacy_stores_mapping); for (IR::Block* const block : program.post_order_blocks) { for (IR::Inst& inst : block->Instructions()) { switch (inst.GetOpcode()) { From ae453ab6a829942a0db25b59e2cf97b5f7f1ecf9 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Sun, 16 Oct 2022 22:58:44 -0400 Subject: [PATCH 04/91] savedata_factory: Detect future save data paths Enable compatibility for new account/device save paths planned on a future implementation. --- src/core/file_sys/savedata_factory.cpp | 58 ++++++++++++++++++++++---- src/core/file_sys/savedata_factory.h | 4 +- src/yuzu/main.cpp | 10 +++-- 3 files changed, 59 insertions(+), 13 deletions(-) diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp index 8c1b2523cb..1567da2310 100644 --- a/src/core/file_sys/savedata_factory.cpp +++ b/src/core/file_sys/savedata_factory.cpp @@ -5,6 +5,7 @@ #include "common/assert.h" #include "common/common_types.h" #include "common/logging/log.h" +#include "common/uuid.h" #include "core/core.h" #include "core/file_sys/savedata_factory.h" #include "core/file_sys/vfs.h" @@ -59,6 +60,36 @@ bool ShouldSaveDataBeAutomaticallyCreated(SaveDataSpaceId space, const SaveDataA attr.title_id == 0 && attr.save_id == 0); } +std::string GetFutureSaveDataPath(SaveDataSpaceId space_id, SaveDataType type, u64 title_id, + u128 user_id) { + // Only detect nand user saves. + const auto space_id_path = [space_id]() -> std::string_view { + switch (space_id) { + case SaveDataSpaceId::NandUser: + return "/user/save"; + default: + return ""; + } + }(); + + if (space_id_path.empty()) { + return ""; + } + + Common::UUID uuid; + std::memcpy(uuid.uuid.data(), user_id.data(), sizeof(Common::UUID)); + + // Only detect account/device saves from the future location. + switch (type) { + case SaveDataType::SaveData: + return fmt::format("{}/account/{}/{:016X}/1", space_id_path, uuid.RawString(), title_id); + case SaveDataType::DeviceSaveData: + return fmt::format("{}/device/{:016X}/1", space_id_path, title_id); + default: + return ""; + } +} + } // Anonymous namespace std::string SaveDataAttribute::DebugInfo() const { @@ -82,7 +113,7 @@ ResultVal SaveDataFactory::Create(SaveDataSpaceId space, PrintSaveDataAttributeWarnings(meta); const auto save_directory = - GetFullPath(system, space, meta.type, meta.title_id, meta.user_id, meta.save_id); + GetFullPath(system, dir, space, meta.type, meta.title_id, meta.user_id, meta.save_id); auto out = dir->CreateDirectoryRelative(save_directory); @@ -99,7 +130,7 @@ ResultVal SaveDataFactory::Open(SaveDataSpaceId space, const SaveDataAttribute& meta) const { const auto save_directory = - GetFullPath(system, space, meta.type, meta.title_id, meta.user_id, meta.save_id); + GetFullPath(system, dir, space, meta.type, meta.title_id, meta.user_id, meta.save_id); auto out = dir->GetDirectoryRelative(save_directory); @@ -134,9 +165,9 @@ std::string SaveDataFactory::GetSaveDataSpaceIdPath(SaveDataSpaceId space) { } } -std::string SaveDataFactory::GetFullPath(Core::System& system, SaveDataSpaceId space, - SaveDataType type, u64 title_id, u128 user_id, - u64 save_id) { +std::string SaveDataFactory::GetFullPath(Core::System& system, VirtualDir dir, + SaveDataSpaceId space, SaveDataType type, u64 title_id, + u128 user_id, u64 save_id) { // According to switchbrew, if a save is of type SaveData and the title id field is 0, it should // be interpreted as the title id of the current process. if (type == SaveDataType::SaveData || type == SaveDataType::DeviceSaveData) { @@ -145,6 +176,17 @@ std::string SaveDataFactory::GetFullPath(Core::System& system, SaveDataSpaceId s } } + // For compat with a future impl. + if (std::string future_path = + GetFutureSaveDataPath(space, type, title_id & ~(0xFFULL), user_id); + !future_path.empty()) { + // Check if this location exists, and prefer it over the old. + if (const auto future_dir = dir->GetDirectoryRelative(future_path); future_dir != nullptr) { + LOG_INFO(Service_FS, "Using save at new location: {}", future_path); + return future_path; + } + } + std::string out = GetSaveDataSpaceIdPath(space); switch (type) { @@ -167,7 +209,8 @@ std::string SaveDataFactory::GetFullPath(Core::System& system, SaveDataSpaceId s SaveDataSize SaveDataFactory::ReadSaveDataSize(SaveDataType type, u64 title_id, u128 user_id) const { - const auto path = GetFullPath(system, SaveDataSpaceId::NandUser, type, title_id, user_id, 0); + const auto path = + GetFullPath(system, dir, SaveDataSpaceId::NandUser, type, title_id, user_id, 0); const auto relative_dir = GetOrCreateDirectoryRelative(dir, path); const auto size_file = relative_dir->GetFile(SAVE_DATA_SIZE_FILENAME); @@ -185,7 +228,8 @@ SaveDataSize SaveDataFactory::ReadSaveDataSize(SaveDataType type, u64 title_id, void SaveDataFactory::WriteSaveDataSize(SaveDataType type, u64 title_id, u128 user_id, SaveDataSize new_value) const { - const auto path = GetFullPath(system, SaveDataSpaceId::NandUser, type, title_id, user_id, 0); + const auto path = + GetFullPath(system, dir, SaveDataSpaceId::NandUser, type, title_id, user_id, 0); const auto relative_dir = GetOrCreateDirectoryRelative(dir, path); const auto size_file = relative_dir->CreateFile(SAVE_DATA_SIZE_FILENAME); diff --git a/src/core/file_sys/savedata_factory.h b/src/core/file_sys/savedata_factory.h index a763b94c88..d3633ef039 100644 --- a/src/core/file_sys/savedata_factory.h +++ b/src/core/file_sys/savedata_factory.h @@ -95,8 +95,8 @@ public: VirtualDir GetSaveDataSpaceDirectory(SaveDataSpaceId space) const; static std::string GetSaveDataSpaceIdPath(SaveDataSpaceId space); - static std::string GetFullPath(Core::System& system, SaveDataSpaceId space, SaveDataType type, - u64 title_id, u128 user_id, u64 save_id); + static std::string GetFullPath(Core::System& system, VirtualDir dir, SaveDataSpaceId space, + SaveDataType type, u64 title_id, u128 user_id, u64 save_id); SaveDataSize ReadSaveDataSize(SaveDataType type, u64 title_id, u128 user_id) const; void WriteSaveDataSize(SaveDataType type, u64 title_id, u128 user_id, diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index a94624be63..d05c7ece80 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -1895,6 +1895,8 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target case GameListOpenTarget::SaveData: { open_target = tr("Save Data"); const auto nand_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir); + auto vfs_nand_dir = + vfs->OpenDirectory(Common::FS::PathToUTF8String(nand_dir), FileSys::Mode::Read); if (has_user_save) { // User save data @@ -1921,15 +1923,15 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target ASSERT(user_id); const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath( - *system, FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::SaveData, - program_id, user_id->AsU128(), 0); + *system, vfs_nand_dir, FileSys::SaveDataSpaceId::NandUser, + FileSys::SaveDataType::SaveData, program_id, user_id->AsU128(), 0); path = Common::FS::ConcatPathSafe(nand_dir, user_save_data_path); } else { // Device save data const auto device_save_data_path = FileSys::SaveDataFactory::GetFullPath( - *system, FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::SaveData, - program_id, {}, 0); + *system, vfs_nand_dir, FileSys::SaveDataSpaceId::NandUser, + FileSys::SaveDataType::SaveData, program_id, {}, 0); path = Common::FS::ConcatPathSafe(nand_dir, device_save_data_path); } From bffbaddb797e0229e6d9e30fe0f75d56b4530903 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Mon, 17 Oct 2022 02:52:41 -0400 Subject: [PATCH 05/91] general: Add missing pragma once --- src/common/fixed_point.h | 5 +---- src/core/hle/service/vi/vi_results.h | 2 ++ 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/common/fixed_point.h b/src/common/fixed_point.h index 4a0f72cc9b..f9adfccb00 100644 --- a/src/common/fixed_point.h +++ b/src/common/fixed_point.h @@ -4,8 +4,7 @@ // From: https://github.com/eteran/cpp-utilities/blob/master/fixed/include/cpp-utilities/fixed.h // See also: http://stackoverflow.com/questions/79677/whats-the-best-way-to-do-fixed-point-math -#ifndef FIXED_H_ -#define FIXED_H_ +#pragma once #if __cplusplus >= 201402L #define CONSTEXPR14 constexpr @@ -702,5 +701,3 @@ constexpr bool operator!=(Number lhs, FixedPoint rhs) { } // namespace Common #undef CONSTEXPR14 - -#endif diff --git a/src/core/hle/service/vi/vi_results.h b/src/core/hle/service/vi/vi_results.h index a46c247d27..22bac799f0 100644 --- a/src/core/hle/service/vi/vi_results.h +++ b/src/core/hle/service/vi/vi_results.h @@ -1,6 +1,8 @@ // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + #include "core/hle/result.h" namespace Service::VI { From 88ccdaf10af1a055854c7b094882a3ed7af37e4b Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Mon, 17 Oct 2022 03:16:54 -0400 Subject: [PATCH 06/91] fixed_point: Replace CONSTEXPR14 with constexpr As we require the latest C++ standards to compile yuzu, checking for C++14 constexpr is not needed. --- src/common/fixed_point.h | 92 ++++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 50 deletions(-) diff --git a/src/common/fixed_point.h b/src/common/fixed_point.h index f9adfccb00..6eb6afe2f5 100644 --- a/src/common/fixed_point.h +++ b/src/common/fixed_point.h @@ -6,12 +6,6 @@ #pragma once -#if __cplusplus >= 201402L -#define CONSTEXPR14 constexpr -#else -#define CONSTEXPR14 -#endif - #include // for size_t #include #include @@ -105,7 +99,7 @@ constexpr B next_to_base(N rhs) { struct divide_by_zero : std::exception {}; template -CONSTEXPR14 FixedPoint divide( +constexpr FixedPoint divide( FixedPoint numerator, FixedPoint denominator, FixedPoint& remainder, typename std::enable_if::next_size::is_specialized>::type* = nullptr) { @@ -125,7 +119,7 @@ CONSTEXPR14 FixedPoint divide( } template -CONSTEXPR14 FixedPoint divide( +constexpr FixedPoint divide( FixedPoint numerator, FixedPoint denominator, FixedPoint& remainder, typename std::enable_if::next_size::is_specialized>::type* = nullptr) { @@ -195,7 +189,7 @@ CONSTEXPR14 FixedPoint divide( // this is the usual implementation of multiplication template -CONSTEXPR14 FixedPoint multiply( +constexpr FixedPoint multiply( FixedPoint lhs, FixedPoint rhs, typename std::enable_if::next_size::is_specialized>::type* = nullptr) { @@ -214,7 +208,7 @@ CONSTEXPR14 FixedPoint multiply( // it is slightly slower, but is more robust since it doesn't // require and upgraded type template -CONSTEXPR14 FixedPoint multiply( +constexpr FixedPoint multiply( FixedPoint lhs, FixedPoint rhs, typename std::enable_if::next_size::is_specialized>::type* = nullptr) { @@ -283,7 +277,7 @@ public: // constructors public: // conversion template - CONSTEXPR14 explicit FixedPoint(FixedPoint other) { + constexpr explicit FixedPoint(FixedPoint other) { static_assert(I2 <= I && F2 <= F, "Scaling conversion can only upgrade types"); using T = FixedPoint; @@ -352,81 +346,81 @@ public: // unary operators return FixedPoint::from_base(+data_); } - CONSTEXPR14 FixedPoint& operator++() { + constexpr FixedPoint& operator++() { data_ += one; return *this; } - CONSTEXPR14 FixedPoint& operator--() { + constexpr FixedPoint& operator--() { data_ -= one; return *this; } - CONSTEXPR14 FixedPoint operator++(int) { + constexpr FixedPoint operator++(int) { FixedPoint tmp(*this); data_ += one; return tmp; } - CONSTEXPR14 FixedPoint operator--(int) { + constexpr FixedPoint operator--(int) { FixedPoint tmp(*this); data_ -= one; return tmp; } public: // basic math operators - CONSTEXPR14 FixedPoint& operator+=(FixedPoint n) { + constexpr FixedPoint& operator+=(FixedPoint n) { data_ += n.data_; return *this; } - CONSTEXPR14 FixedPoint& operator-=(FixedPoint n) { + constexpr FixedPoint& operator-=(FixedPoint n) { data_ -= n.data_; return *this; } - CONSTEXPR14 FixedPoint& operator*=(FixedPoint n) { + constexpr FixedPoint& operator*=(FixedPoint n) { return assign(detail::multiply(*this, n)); } - CONSTEXPR14 FixedPoint& operator/=(FixedPoint n) { + constexpr FixedPoint& operator/=(FixedPoint n) { FixedPoint temp; return assign(detail::divide(*this, n, temp)); } private: - CONSTEXPR14 FixedPoint& assign(FixedPoint rhs) { + constexpr FixedPoint& assign(FixedPoint rhs) { data_ = rhs.data_; return *this; } public: // binary math operators, effects underlying bit pattern since these // don't really typically make sense for non-integer values - CONSTEXPR14 FixedPoint& operator&=(FixedPoint n) { + constexpr FixedPoint& operator&=(FixedPoint n) { data_ &= n.data_; return *this; } - CONSTEXPR14 FixedPoint& operator|=(FixedPoint n) { + constexpr FixedPoint& operator|=(FixedPoint n) { data_ |= n.data_; return *this; } - CONSTEXPR14 FixedPoint& operator^=(FixedPoint n) { + constexpr FixedPoint& operator^=(FixedPoint n) { data_ ^= n.data_; return *this; } template ::value>::type> - CONSTEXPR14 FixedPoint& operator>>=(Integer n) { + constexpr FixedPoint& operator>>=(Integer n) { data_ >>= n; return *this; } template ::value>::type> - CONSTEXPR14 FixedPoint& operator<<=(Integer n) { + constexpr FixedPoint& operator<<=(Integer n) { data_ <<= n; return *this; } @@ -484,7 +478,7 @@ public: // conversion to basic types } public: - CONSTEXPR14 void swap(FixedPoint& rhs) { + constexpr void swap(FixedPoint& rhs) { using std::swap; swap(data_, rhs.data_); } @@ -496,8 +490,8 @@ public: // if we have the same fractional portion, but differing integer portions, we trivially upgrade the // smaller type template -CONSTEXPR14 typename std::conditional= I2, FixedPoint, FixedPoint>::type -operator+(FixedPoint lhs, FixedPoint rhs) { +constexpr typename std::conditional= I2, FixedPoint, FixedPoint>::type operator+( + FixedPoint lhs, FixedPoint rhs) { using T = typename std::conditional= I2, FixedPoint, FixedPoint>::type; @@ -507,8 +501,8 @@ operator+(FixedPoint lhs, FixedPoint rhs) { } template -CONSTEXPR14 typename std::conditional= I2, FixedPoint, FixedPoint>::type -operator-(FixedPoint lhs, FixedPoint rhs) { +constexpr typename std::conditional= I2, FixedPoint, FixedPoint>::type operator-( + FixedPoint lhs, FixedPoint rhs) { using T = typename std::conditional= I2, FixedPoint, FixedPoint>::type; @@ -518,8 +512,8 @@ operator-(FixedPoint lhs, FixedPoint rhs) { } template -CONSTEXPR14 typename std::conditional= I2, FixedPoint, FixedPoint>::type -operator*(FixedPoint lhs, FixedPoint rhs) { +constexpr typename std::conditional= I2, FixedPoint, FixedPoint>::type operator*( + FixedPoint lhs, FixedPoint rhs) { using T = typename std::conditional= I2, FixedPoint, FixedPoint>::type; @@ -529,8 +523,8 @@ operator*(FixedPoint lhs, FixedPoint rhs) { } template -CONSTEXPR14 typename std::conditional= I2, FixedPoint, FixedPoint>::type -operator/(FixedPoint lhs, FixedPoint rhs) { +constexpr typename std::conditional= I2, FixedPoint, FixedPoint>::type operator/( + FixedPoint lhs, FixedPoint rhs) { using T = typename std::conditional= I2, FixedPoint, FixedPoint>::type; @@ -547,75 +541,75 @@ std::ostream& operator<<(std::ostream& os, FixedPoint f) { // basic math operators template -CONSTEXPR14 FixedPoint operator+(FixedPoint lhs, FixedPoint rhs) { +constexpr FixedPoint operator+(FixedPoint lhs, FixedPoint rhs) { lhs += rhs; return lhs; } template -CONSTEXPR14 FixedPoint operator-(FixedPoint lhs, FixedPoint rhs) { +constexpr FixedPoint operator-(FixedPoint lhs, FixedPoint rhs) { lhs -= rhs; return lhs; } template -CONSTEXPR14 FixedPoint operator*(FixedPoint lhs, FixedPoint rhs) { +constexpr FixedPoint operator*(FixedPoint lhs, FixedPoint rhs) { lhs *= rhs; return lhs; } template -CONSTEXPR14 FixedPoint operator/(FixedPoint lhs, FixedPoint rhs) { +constexpr FixedPoint operator/(FixedPoint lhs, FixedPoint rhs) { lhs /= rhs; return lhs; } template ::value>::type> -CONSTEXPR14 FixedPoint operator+(FixedPoint lhs, Number rhs) { +constexpr FixedPoint operator+(FixedPoint lhs, Number rhs) { lhs += FixedPoint(rhs); return lhs; } template ::value>::type> -CONSTEXPR14 FixedPoint operator-(FixedPoint lhs, Number rhs) { +constexpr FixedPoint operator-(FixedPoint lhs, Number rhs) { lhs -= FixedPoint(rhs); return lhs; } template ::value>::type> -CONSTEXPR14 FixedPoint operator*(FixedPoint lhs, Number rhs) { +constexpr FixedPoint operator*(FixedPoint lhs, Number rhs) { lhs *= FixedPoint(rhs); return lhs; } template ::value>::type> -CONSTEXPR14 FixedPoint operator/(FixedPoint lhs, Number rhs) { +constexpr FixedPoint operator/(FixedPoint lhs, Number rhs) { lhs /= FixedPoint(rhs); return lhs; } template ::value>::type> -CONSTEXPR14 FixedPoint operator+(Number lhs, FixedPoint rhs) { +constexpr FixedPoint operator+(Number lhs, FixedPoint rhs) { FixedPoint tmp(lhs); tmp += rhs; return tmp; } template ::value>::type> -CONSTEXPR14 FixedPoint operator-(Number lhs, FixedPoint rhs) { +constexpr FixedPoint operator-(Number lhs, FixedPoint rhs) { FixedPoint tmp(lhs); tmp -= rhs; return tmp; } template ::value>::type> -CONSTEXPR14 FixedPoint operator*(Number lhs, FixedPoint rhs) { +constexpr FixedPoint operator*(Number lhs, FixedPoint rhs) { FixedPoint tmp(lhs); tmp *= rhs; return tmp; } template ::value>::type> -CONSTEXPR14 FixedPoint operator/(Number lhs, FixedPoint rhs) { +constexpr FixedPoint operator/(Number lhs, FixedPoint rhs) { FixedPoint tmp(lhs); tmp /= rhs; return tmp; @@ -624,13 +618,13 @@ CONSTEXPR14 FixedPoint operator/(Number lhs, FixedPoint rhs) { // shift operators template ::value>::type> -CONSTEXPR14 FixedPoint operator<<(FixedPoint lhs, Integer rhs) { +constexpr FixedPoint operator<<(FixedPoint lhs, Integer rhs) { lhs <<= rhs; return lhs; } template ::value>::type> -CONSTEXPR14 FixedPoint operator>>(FixedPoint lhs, Integer rhs) { +constexpr FixedPoint operator>>(FixedPoint lhs, Integer rhs) { lhs >>= rhs; return lhs; } @@ -699,5 +693,3 @@ constexpr bool operator!=(Number lhs, FixedPoint rhs) { } } // namespace Common - -#undef CONSTEXPR14 From 99507d0188a1f7f7d8e11741f935918c7643ce76 Mon Sep 17 00:00:00 2001 From: FengChen Date: Sun, 16 Oct 2022 23:49:32 +0800 Subject: [PATCH 07/91] video_core: Implement memory manager page kind --- .../service/nvdrv/devices/nvhost_as_gpu.cpp | 12 +- src/video_core/CMakeLists.txt | 1 + src/video_core/memory_manager.cpp | 61 +++- src/video_core/memory_manager.h | 21 +- src/video_core/pte_kind.h | 264 ++++++++++++++++++ 5 files changed, 342 insertions(+), 17 deletions(-) create mode 100644 src/video_core/pte_kind.h diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp index 6411dbf433..b635e6ed13 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp @@ -311,7 +311,8 @@ NvResult nvhost_as_gpu::Remap(const std::vector& input, std::vector& out handle->address + (static_cast(entry.handle_offset_big_pages) << vm.big_page_size_bits))}; - gmmu->Map(virtual_address, cpu_address, size, use_big_pages); + gmmu->Map(virtual_address, cpu_address, size, static_cast(entry.kind), + use_big_pages); } } @@ -350,7 +351,8 @@ NvResult nvhost_as_gpu::MapBufferEx(const std::vector& input, std::vector(params.offset + params.buffer_offset)}; VAddr cpu_address{mapping->ptr + params.buffer_offset}; - gmmu->Map(gpu_address, cpu_address, params.mapping_size, mapping->big_page); + gmmu->Map(gpu_address, cpu_address, params.mapping_size, + static_cast(params.kind), mapping->big_page); return NvResult::Success; } catch (const std::out_of_range&) { @@ -389,7 +391,8 @@ NvResult nvhost_as_gpu::MapBufferEx(const std::vector& input, std::vectorsecond.big_pages && big_page; - gmmu->Map(params.offset, cpu_address, size, use_big_pages); + gmmu->Map(params.offset, cpu_address, size, static_cast(params.kind), + use_big_pages); auto mapping{std::make_shared(cpu_address, params.offset, size, true, use_big_pages, alloc->second.sparse)}; @@ -409,7 +412,8 @@ NvResult nvhost_as_gpu::MapBufferEx(const std::vector& input, std::vectorMap(params.offset, cpu_address, Common::AlignUp(size, page_size), big_page); + gmmu->Map(params.offset, cpu_address, Common::AlignUp(size, page_size), + static_cast(params.kind), big_page); auto mapping{ std::make_shared(cpu_address, params.offset, size, false, big_page, false)}; diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 40e6d1ec48..cb8b46edf0 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -82,6 +82,7 @@ add_library(video_core STATIC gpu_thread.h memory_manager.cpp memory_manager.h + pte_kind.h query_cache.h rasterizer_accelerated.cpp rasterizer_accelerated.h diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp index cca401c74b..d07b21bd67 100644 --- a/src/video_core/memory_manager.cpp +++ b/src/video_core/memory_manager.cpp @@ -41,7 +41,11 @@ MemoryManager::MemoryManager(Core::System& system_, u64 address_space_bits_, u64 big_entries.resize(big_page_table_size / 32, 0); big_page_table_cpu.resize(big_page_table_size); big_page_continous.resize(big_page_table_size / continous_bits, 0); + std::array kind_valus; + kind_valus.fill(PTEKind::INVALID); + big_kinds.resize(big_page_table_size / 32, kind_valus); entries.resize(page_table_size / 32, 0); + kinds.resize(big_page_table_size / 32, kind_valus); } MemoryManager::~MemoryManager() = default; @@ -78,6 +82,41 @@ void MemoryManager::SetEntry(size_t position, MemoryManager::EntryType entry) { } } +PTEKind MemoryManager::GetPageKind(GPUVAddr gpu_addr) const { + auto entry = GetEntry(gpu_addr); + if (entry == EntryType::Mapped || entry == EntryType::Reserved) [[likely]] { + return GetKind(gpu_addr); + } else { + return GetKind(gpu_addr); + } +} + +template +PTEKind MemoryManager::GetKind(size_t position) const { + if constexpr (is_big_page) { + position = position >> big_page_bits; + const size_t sub_index = position % 32; + return big_kinds[position / 32][sub_index]; + } else { + position = position >> page_bits; + const size_t sub_index = position % 32; + return kinds[position / 32][sub_index]; + } +} + +template +void MemoryManager::SetKind(size_t position, PTEKind kind) { + if constexpr (is_big_page) { + position = position >> big_page_bits; + const size_t sub_index = position % 32; + big_kinds[position / 32][sub_index] = kind; + } else { + position = position >> page_bits; + const size_t sub_index = position % 32; + kinds[position / 32][sub_index] = kind; + } +} + inline bool MemoryManager::IsBigPageContinous(size_t big_page_index) const { const u64 entry_mask = big_page_continous[big_page_index / continous_bits]; const size_t sub_index = big_page_index % continous_bits; @@ -92,8 +131,8 @@ inline void MemoryManager::SetBigPageContinous(size_t big_page_index, bool value } template -GPUVAddr MemoryManager::PageTableOp(GPUVAddr gpu_addr, [[maybe_unused]] VAddr cpu_addr, - size_t size) { +GPUVAddr MemoryManager::PageTableOp(GPUVAddr gpu_addr, [[maybe_unused]] VAddr cpu_addr, size_t size, + PTEKind kind) { u64 remaining_size{size}; if constexpr (entry_type == EntryType::Mapped) { page_table.ReserveRange(gpu_addr, size); @@ -102,6 +141,7 @@ GPUVAddr MemoryManager::PageTableOp(GPUVAddr gpu_addr, [[maybe_unused]] VAddr cp const GPUVAddr current_gpu_addr = gpu_addr + offset; [[maybe_unused]] const auto current_entry_type = GetEntry(current_gpu_addr); SetEntry(current_gpu_addr, entry_type); + SetKind(current_gpu_addr, kind); if (current_entry_type != entry_type) { rasterizer->ModifyGPUMemory(unique_identifier, gpu_addr, page_size); } @@ -118,12 +158,13 @@ GPUVAddr MemoryManager::PageTableOp(GPUVAddr gpu_addr, [[maybe_unused]] VAddr cp template GPUVAddr MemoryManager::BigPageTableOp(GPUVAddr gpu_addr, [[maybe_unused]] VAddr cpu_addr, - size_t size) { + size_t size, PTEKind kind) { u64 remaining_size{size}; for (u64 offset{}; offset < size; offset += big_page_size) { const GPUVAddr current_gpu_addr = gpu_addr + offset; [[maybe_unused]] const auto current_entry_type = GetEntry(current_gpu_addr); SetEntry(current_gpu_addr, entry_type); + SetKind(current_gpu_addr, kind); if (current_entry_type != entry_type) { rasterizer->ModifyGPUMemory(unique_identifier, gpu_addr, big_page_size); } @@ -159,19 +200,19 @@ void MemoryManager::BindRasterizer(VideoCore::RasterizerInterface* rasterizer_) rasterizer = rasterizer_; } -GPUVAddr MemoryManager::Map(GPUVAddr gpu_addr, VAddr cpu_addr, std::size_t size, +GPUVAddr MemoryManager::Map(GPUVAddr gpu_addr, VAddr cpu_addr, std::size_t size, PTEKind kind, bool is_big_pages) { if (is_big_pages) [[likely]] { - return BigPageTableOp(gpu_addr, cpu_addr, size); + return BigPageTableOp(gpu_addr, cpu_addr, size, kind); } - return PageTableOp(gpu_addr, cpu_addr, size); + return PageTableOp(gpu_addr, cpu_addr, size, kind); } GPUVAddr MemoryManager::MapSparse(GPUVAddr gpu_addr, std::size_t size, bool is_big_pages) { if (is_big_pages) [[likely]] { - return BigPageTableOp(gpu_addr, 0, size); + return BigPageTableOp(gpu_addr, 0, size, PTEKind::INVALID); } - return PageTableOp(gpu_addr, 0, size); + return PageTableOp(gpu_addr, 0, size, PTEKind::INVALID); } void MemoryManager::Unmap(GPUVAddr gpu_addr, std::size_t size) { @@ -188,8 +229,8 @@ void MemoryManager::Unmap(GPUVAddr gpu_addr, std::size_t size) { rasterizer->UnmapMemory(*cpu_addr, map_size); } - BigPageTableOp(gpu_addr, 0, size); - PageTableOp(gpu_addr, 0, size); + BigPageTableOp(gpu_addr, 0, size, PTEKind::INVALID); + PageTableOp(gpu_addr, 0, size, PTEKind::INVALID); } std::optional MemoryManager::GpuToCpuAddress(GPUVAddr gpu_addr) const { diff --git a/src/video_core/memory_manager.h b/src/video_core/memory_manager.h index f992e29f3d..ab4bc9ec63 100644 --- a/src/video_core/memory_manager.h +++ b/src/video_core/memory_manager.h @@ -11,6 +11,7 @@ #include "common/common_types.h" #include "common/multi_level_page_table.h" #include "common/virtual_buffer.h" +#include "video_core/pte_kind.h" namespace VideoCore { class RasterizerInterface; @@ -98,7 +99,8 @@ public: std::vector> GetSubmappedRange(GPUVAddr gpu_addr, std::size_t size) const; - GPUVAddr Map(GPUVAddr gpu_addr, VAddr cpu_addr, std::size_t size, bool is_big_pages = true); + GPUVAddr Map(GPUVAddr gpu_addr, VAddr cpu_addr, std::size_t size, + PTEKind kind = PTEKind::INVALID, bool is_big_pages = true); GPUVAddr MapSparse(GPUVAddr gpu_addr, std::size_t size, bool is_big_pages = true); void Unmap(GPUVAddr gpu_addr, std::size_t size); @@ -114,6 +116,8 @@ public: return gpu_addr < address_space_size; } + PTEKind GetPageKind(GPUVAddr gpu_addr) const; + private: template inline void MemoryOperation(GPUVAddr gpu_src_addr, std::size_t size, FuncMapped&& func_mapped, @@ -166,10 +170,12 @@ private: std::vector big_entries; template - GPUVAddr PageTableOp(GPUVAddr gpu_addr, [[maybe_unused]] VAddr cpu_addr, size_t size); + GPUVAddr PageTableOp(GPUVAddr gpu_addr, [[maybe_unused]] VAddr cpu_addr, size_t size, + PTEKind kind); template - GPUVAddr BigPageTableOp(GPUVAddr gpu_addr, [[maybe_unused]] VAddr cpu_addr, size_t size); + GPUVAddr BigPageTableOp(GPUVAddr gpu_addr, [[maybe_unused]] VAddr cpu_addr, size_t size, + PTEKind kind); template inline EntryType GetEntry(size_t position) const; @@ -177,6 +183,15 @@ private: template inline void SetEntry(size_t position, EntryType entry); + std::vector> kinds; + std::vector> big_kinds; + + template + inline PTEKind GetKind(size_t position) const; + + template + inline void SetKind(size_t position, PTEKind kind); + Common::MultiLevelPageTable page_table; Common::VirtualBuffer big_page_table_cpu; diff --git a/src/video_core/pte_kind.h b/src/video_core/pte_kind.h new file mode 100644 index 0000000000..591d7214b2 --- /dev/null +++ b/src/video_core/pte_kind.h @@ -0,0 +1,264 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/common_types.h" + +namespace Tegra { + +// https://github.com/NVIDIA/open-gpu-doc/blob/master/manuals/volta/gv100/dev_mmu.ref.txt +enum class PTEKind : u8 { + INVALID = 0xff, + PITCH = 0x00, + Z16 = 0x01, + Z16_2C = 0x02, + Z16_MS2_2C = 0x03, + Z16_MS4_2C = 0x04, + Z16_MS8_2C = 0x05, + Z16_MS16_2C = 0x06, + Z16_2Z = 0x07, + Z16_MS2_2Z = 0x08, + Z16_MS4_2Z = 0x09, + Z16_MS8_2Z = 0x0a, + Z16_MS16_2Z = 0x0b, + Z16_2CZ = 0x36, + Z16_MS2_2CZ = 0x37, + Z16_MS4_2CZ = 0x38, + Z16_MS8_2CZ = 0x39, + Z16_MS16_2CZ = 0x5f, + Z16_4CZ = 0x0c, + Z16_MS2_4CZ = 0x0d, + Z16_MS4_4CZ = 0x0e, + Z16_MS8_4CZ = 0x0f, + Z16_MS16_4CZ = 0x10, + S8Z24 = 0x11, + S8Z24_1Z = 0x12, + S8Z24_MS2_1Z = 0x13, + S8Z24_MS4_1Z = 0x14, + S8Z24_MS8_1Z = 0x15, + S8Z24_MS16_1Z = 0x16, + S8Z24_2CZ = 0x17, + S8Z24_MS2_2CZ = 0x18, + S8Z24_MS4_2CZ = 0x19, + S8Z24_MS8_2CZ = 0x1a, + S8Z24_MS16_2CZ = 0x1b, + S8Z24_2CS = 0x1c, + S8Z24_MS2_2CS = 0x1d, + S8Z24_MS4_2CS = 0x1e, + S8Z24_MS8_2CS = 0x1f, + S8Z24_MS16_2CS = 0x20, + S8Z24_4CSZV = 0x21, + S8Z24_MS2_4CSZV = 0x22, + S8Z24_MS4_4CSZV = 0x23, + S8Z24_MS8_4CSZV = 0x24, + S8Z24_MS16_4CSZV = 0x25, + V8Z24_MS4_VC12 = 0x26, + V8Z24_MS4_VC4 = 0x27, + V8Z24_MS8_VC8 = 0x28, + V8Z24_MS8_VC24 = 0x29, + V8Z24_MS4_VC12_1ZV = 0x2e, + V8Z24_MS4_VC4_1ZV = 0x2f, + V8Z24_MS8_VC8_1ZV = 0x30, + V8Z24_MS8_VC24_1ZV = 0x31, + V8Z24_MS4_VC12_2CS = 0x32, + V8Z24_MS4_VC4_2CS = 0x33, + V8Z24_MS8_VC8_2CS = 0x34, + V8Z24_MS8_VC24_2CS = 0x35, + V8Z24_MS4_VC12_2CZV = 0x3a, + V8Z24_MS4_VC4_2CZV = 0x3b, + V8Z24_MS8_VC8_2CZV = 0x3c, + V8Z24_MS8_VC24_2CZV = 0x3d, + V8Z24_MS4_VC12_2ZV = 0x3e, + V8Z24_MS4_VC4_2ZV = 0x3f, + V8Z24_MS8_VC8_2ZV = 0x40, + V8Z24_MS8_VC24_2ZV = 0x41, + V8Z24_MS4_VC12_4CSZV = 0x42, + V8Z24_MS4_VC4_4CSZV = 0x43, + V8Z24_MS8_VC8_4CSZV = 0x44, + V8Z24_MS8_VC24_4CSZV = 0x45, + Z24S8 = 0x46, + Z24S8_1Z = 0x47, + Z24S8_MS2_1Z = 0x48, + Z24S8_MS4_1Z = 0x49, + Z24S8_MS8_1Z = 0x4a, + Z24S8_MS16_1Z = 0x4b, + Z24S8_2CS = 0x4c, + Z24S8_MS2_2CS = 0x4d, + Z24S8_MS4_2CS = 0x4e, + Z24S8_MS8_2CS = 0x4f, + Z24S8_MS16_2CS = 0x50, + Z24S8_2CZ = 0x51, + Z24S8_MS2_2CZ = 0x52, + Z24S8_MS4_2CZ = 0x53, + Z24S8_MS8_2CZ = 0x54, + Z24S8_MS16_2CZ = 0x55, + Z24S8_4CSZV = 0x56, + Z24S8_MS2_4CSZV = 0x57, + Z24S8_MS4_4CSZV = 0x58, + Z24S8_MS8_4CSZV = 0x59, + Z24S8_MS16_4CSZV = 0x5a, + Z24V8_MS4_VC12 = 0x5b, + Z24V8_MS4_VC4 = 0x5c, + Z24V8_MS8_VC8 = 0x5d, + Z24V8_MS8_VC24 = 0x5e, + YUV_B8C1_2Y = 0x60, + YUV_B8C2_2Y = 0x61, + YUV_B10C1_2Y = 0x62, + YUV_B10C2_2Y = 0x6b, + YUV_B12C1_2Y = 0x6c, + YUV_B12C2_2Y = 0x6d, + Z24V8_MS4_VC12_1ZV = 0x63, + Z24V8_MS4_VC4_1ZV = 0x64, + Z24V8_MS8_VC8_1ZV = 0x65, + Z24V8_MS8_VC24_1ZV = 0x66, + Z24V8_MS4_VC12_2CS = 0x67, + Z24V8_MS4_VC4_2CS = 0x68, + Z24V8_MS8_VC8_2CS = 0x69, + Z24V8_MS8_VC24_2CS = 0x6a, + Z24V8_MS4_VC12_2CZV = 0x6f, + Z24V8_MS4_VC4_2CZV = 0x70, + Z24V8_MS8_VC8_2CZV = 0x71, + Z24V8_MS8_VC24_2CZV = 0x72, + Z24V8_MS4_VC12_2ZV = 0x73, + Z24V8_MS4_VC4_2ZV = 0x74, + Z24V8_MS8_VC8_2ZV = 0x75, + Z24V8_MS8_VC24_2ZV = 0x76, + Z24V8_MS4_VC12_4CSZV = 0x77, + Z24V8_MS4_VC4_4CSZV = 0x78, + Z24V8_MS8_VC8_4CSZV = 0x79, + Z24V8_MS8_VC24_4CSZV = 0x7a, + ZF32 = 0x7b, + ZF32_1Z = 0x7c, + ZF32_MS2_1Z = 0x7d, + ZF32_MS4_1Z = 0x7e, + ZF32_MS8_1Z = 0x7f, + ZF32_MS16_1Z = 0x80, + ZF32_2CS = 0x81, + ZF32_MS2_2CS = 0x82, + ZF32_MS4_2CS = 0x83, + ZF32_MS8_2CS = 0x84, + ZF32_MS16_2CS = 0x85, + ZF32_2CZ = 0x86, + ZF32_MS2_2CZ = 0x87, + ZF32_MS4_2CZ = 0x88, + ZF32_MS8_2CZ = 0x89, + ZF32_MS16_2CZ = 0x8a, + X8Z24_X16V8S8_MS4_VC12 = 0x8b, + X8Z24_X16V8S8_MS4_VC4 = 0x8c, + X8Z24_X16V8S8_MS8_VC8 = 0x8d, + X8Z24_X16V8S8_MS8_VC24 = 0x8e, + X8Z24_X16V8S8_MS4_VC12_1CS = 0x8f, + X8Z24_X16V8S8_MS4_VC4_1CS = 0x90, + X8Z24_X16V8S8_MS8_VC8_1CS = 0x91, + X8Z24_X16V8S8_MS8_VC24_1CS = 0x92, + X8Z24_X16V8S8_MS4_VC12_1ZV = 0x97, + X8Z24_X16V8S8_MS4_VC4_1ZV = 0x98, + X8Z24_X16V8S8_MS8_VC8_1ZV = 0x99, + X8Z24_X16V8S8_MS8_VC24_1ZV = 0x9a, + X8Z24_X16V8S8_MS4_VC12_1CZV = 0x9b, + X8Z24_X16V8S8_MS4_VC4_1CZV = 0x9c, + X8Z24_X16V8S8_MS8_VC8_1CZV = 0x9d, + X8Z24_X16V8S8_MS8_VC24_1CZV = 0x9e, + X8Z24_X16V8S8_MS4_VC12_2CS = 0x9f, + X8Z24_X16V8S8_MS4_VC4_2CS = 0xa0, + X8Z24_X16V8S8_MS8_VC8_2CS = 0xa1, + X8Z24_X16V8S8_MS8_VC24_2CS = 0xa2, + X8Z24_X16V8S8_MS4_VC12_2CSZV = 0xa3, + X8Z24_X16V8S8_MS4_VC4_2CSZV = 0xa4, + X8Z24_X16V8S8_MS8_VC8_2CSZV = 0xa5, + X8Z24_X16V8S8_MS8_VC24_2CSZV = 0xa6, + ZF32_X16V8S8_MS4_VC12 = 0xa7, + ZF32_X16V8S8_MS4_VC4 = 0xa8, + ZF32_X16V8S8_MS8_VC8 = 0xa9, + ZF32_X16V8S8_MS8_VC24 = 0xaa, + ZF32_X16V8S8_MS4_VC12_1CS = 0xab, + ZF32_X16V8S8_MS4_VC4_1CS = 0xac, + ZF32_X16V8S8_MS8_VC8_1CS = 0xad, + ZF32_X16V8S8_MS8_VC24_1CS = 0xae, + ZF32_X16V8S8_MS4_VC12_1ZV = 0xb3, + ZF32_X16V8S8_MS4_VC4_1ZV = 0xb4, + ZF32_X16V8S8_MS8_VC8_1ZV = 0xb5, + ZF32_X16V8S8_MS8_VC24_1ZV = 0xb6, + ZF32_X16V8S8_MS4_VC12_1CZV = 0xb7, + ZF32_X16V8S8_MS4_VC4_1CZV = 0xb8, + ZF32_X16V8S8_MS8_VC8_1CZV = 0xb9, + ZF32_X16V8S8_MS8_VC24_1CZV = 0xba, + ZF32_X16V8S8_MS4_VC12_2CS = 0xbb, + ZF32_X16V8S8_MS4_VC4_2CS = 0xbc, + ZF32_X16V8S8_MS8_VC8_2CS = 0xbd, + ZF32_X16V8S8_MS8_VC24_2CS = 0xbe, + ZF32_X16V8S8_MS4_VC12_2CSZV = 0xbf, + ZF32_X16V8S8_MS4_VC4_2CSZV = 0xc0, + ZF32_X16V8S8_MS8_VC8_2CSZV = 0xc1, + ZF32_X16V8S8_MS8_VC24_2CSZV = 0xc2, + ZF32_X24S8 = 0xc3, + ZF32_X24S8_1CS = 0xc4, + ZF32_X24S8_MS2_1CS = 0xc5, + ZF32_X24S8_MS4_1CS = 0xc6, + ZF32_X24S8_MS8_1CS = 0xc7, + ZF32_X24S8_MS16_1CS = 0xc8, + ZF32_X24S8_2CSZV = 0xce, + ZF32_X24S8_MS2_2CSZV = 0xcf, + ZF32_X24S8_MS4_2CSZV = 0xd0, + ZF32_X24S8_MS8_2CSZV = 0xd1, + ZF32_X24S8_MS16_2CSZV = 0xd2, + ZF32_X24S8_2CS = 0xd3, + ZF32_X24S8_MS2_2CS = 0xd4, + ZF32_X24S8_MS4_2CS = 0xd5, + ZF32_X24S8_MS8_2CS = 0xd6, + ZF32_X24S8_MS16_2CS = 0xd7, + S8 = 0x2a, + S8_2S = 0x2b, + GENERIC_16BX2 = 0xfe, + C32_2C = 0xd8, + C32_2CBR = 0xd9, + C32_2CBA = 0xda, + C32_2CRA = 0xdb, + C32_2BRA = 0xdc, + C32_MS2_2C = 0xdd, + C32_MS2_2CBR = 0xde, + C32_MS2_4CBRA = 0xcc, + C32_MS4_2C = 0xdf, + C32_MS4_2CBR = 0xe0, + C32_MS4_2CBA = 0xe1, + C32_MS4_2CRA = 0xe2, + C32_MS4_2BRA = 0xe3, + C32_MS4_4CBRA = 0x2c, + C32_MS8_MS16_2C = 0xe4, + C32_MS8_MS16_2CRA = 0xe5, + C64_2C = 0xe6, + C64_2CBR = 0xe7, + C64_2CBA = 0xe8, + C64_2CRA = 0xe9, + C64_2BRA = 0xea, + C64_MS2_2C = 0xeb, + C64_MS2_2CBR = 0xec, + C64_MS2_4CBRA = 0xcd, + C64_MS4_2C = 0xed, + C64_MS4_2CBR = 0xee, + C64_MS4_2CBA = 0xef, + C64_MS4_2CRA = 0xf0, + C64_MS4_2BRA = 0xf1, + C64_MS4_4CBRA = 0x2d, + C64_MS8_MS16_2C = 0xf2, + C64_MS8_MS16_2CRA = 0xf3, + C128_2C = 0xf4, + C128_2CR = 0xf5, + C128_MS2_2C = 0xf6, + C128_MS2_2CR = 0xf7, + C128_MS4_2C = 0xf8, + C128_MS4_2CR = 0xf9, + C128_MS8_MS16_2C = 0xfa, + C128_MS8_MS16_2CR = 0xfb, + X8C24 = 0xfc, + PITCH_NO_SWIZZLE = 0xfd, + SMSKED_MESSAGE = 0xca, + SMHOST_MESSAGE = 0xcb, +}; + +constexpr bool IsPitchKind(PTEKind kind) { + return kind == PTEKind::PITCH || kind == PTEKind::PITCH_NO_SWIZZLE; +} + +} // namespace Tegra From 23b6569fc26cd1675737a0ef02d0a82021b4d998 Mon Sep 17 00:00:00 2001 From: FengChen Date: Fri, 14 Oct 2022 22:20:45 +0800 Subject: [PATCH 08/91] video_core: implement 1D copies based on VMM 'kind' --- src/video_core/engines/maxwell_dma.cpp | 127 ++++++++++++++----------- src/video_core/engines/maxwell_dma.h | 2 - 2 files changed, 73 insertions(+), 56 deletions(-) diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp index 3909d36c19..4eb7a100d7 100644 --- a/src/video_core/engines/maxwell_dma.cpp +++ b/src/video_core/engines/maxwell_dma.cpp @@ -56,68 +56,87 @@ void MaxwellDMA::Launch() { ASSERT(launch.interrupt_type == LaunchDMA::InterruptType::NONE); ASSERT(launch.data_transfer_type == LaunchDMA::DataTransferType::NON_PIPELINED); - const bool is_src_pitch = launch.src_memory_layout == LaunchDMA::MemoryLayout::PITCH; - const bool is_dst_pitch = launch.dst_memory_layout == LaunchDMA::MemoryLayout::PITCH; + if (launch.multi_line_enable) { + const bool is_src_pitch = launch.src_memory_layout == LaunchDMA::MemoryLayout::PITCH; + const bool is_dst_pitch = launch.dst_memory_layout == LaunchDMA::MemoryLayout::PITCH; - if (!is_src_pitch && !is_dst_pitch) { - // If both the source and the destination are in block layout, assert. - UNIMPLEMENTED_MSG("Tiled->Tiled DMA transfers are not yet implemented"); - return; - } + if (!is_src_pitch && !is_dst_pitch) { + // If both the source and the destination are in block layout, assert. + UNIMPLEMENTED_MSG("Tiled->Tiled DMA transfers are not yet implemented"); + return; + } - if (is_src_pitch && is_dst_pitch) { - CopyPitchToPitch(); - } else { - ASSERT(launch.multi_line_enable == 1); - - if (!is_src_pitch && is_dst_pitch) { - CopyBlockLinearToPitch(); + if (is_src_pitch && is_dst_pitch) { + for (u32 line = 0; line < regs.line_count; ++line) { + const GPUVAddr source_line = + regs.offset_in + static_cast(line) * regs.pitch_in; + const GPUVAddr dest_line = + regs.offset_out + static_cast(line) * regs.pitch_out; + memory_manager.CopyBlock(dest_line, source_line, regs.line_length_in); + } } else { - CopyPitchToBlockLinear(); + if (!is_src_pitch && is_dst_pitch) { + CopyBlockLinearToPitch(); + } else { + CopyPitchToBlockLinear(); + } + } + } else { + // TODO: allow multisized components. + auto& accelerate = rasterizer->AccessAccelerateDMA(); + const bool is_const_a_dst = regs.remap_const.dst_x == RemapConst::Swizzle::CONST_A; + if (regs.launch_dma.remap_enable != 0 && is_const_a_dst) { + ASSERT(regs.remap_const.component_size_minus_one == 3); + accelerate.BufferClear(regs.offset_out, regs.line_length_in, regs.remap_consta_value); + std::vector tmp_buffer(regs.line_length_in, regs.remap_consta_value); + memory_manager.WriteBlockUnsafe(regs.offset_out, + reinterpret_cast(tmp_buffer.data()), + regs.line_length_in * sizeof(u32)); + } else { + auto convert_linear_2_blocklinear_addr = [](u64 address) { + return (address & ~0x1f0ULL) | ((address & 0x40) >> 2) | ((address & 0x10) << 1) | + ((address & 0x180) >> 1) | ((address & 0x20) << 3); + }; + auto src_kind = memory_manager.GetPageKind(regs.offset_in); + auto dst_kind = memory_manager.GetPageKind(regs.offset_out); + const bool is_src_pitch = IsPitchKind(static_cast(src_kind)); + const bool is_dst_pitch = IsPitchKind(static_cast(dst_kind)); + if (!is_src_pitch && is_dst_pitch) { + std::vector tmp_buffer(regs.line_length_in); + std::vector dst_buffer(regs.line_length_in); + memory_manager.ReadBlockUnsafe(regs.offset_in, tmp_buffer.data(), + regs.line_length_in); + for (u32 offset = 0; offset < regs.line_length_in; ++offset) { + dst_buffer[offset] = + tmp_buffer[convert_linear_2_blocklinear_addr(regs.offset_in + offset) - + regs.offset_in]; + } + memory_manager.WriteBlock(regs.offset_out, dst_buffer.data(), regs.line_length_in); + } else if (is_src_pitch && !is_dst_pitch) { + std::vector tmp_buffer(regs.line_length_in); + std::vector dst_buffer(regs.line_length_in); + memory_manager.ReadBlockUnsafe(regs.offset_in, tmp_buffer.data(), + regs.line_length_in); + for (u32 offset = 0; offset < regs.line_length_in; ++offset) { + dst_buffer[convert_linear_2_blocklinear_addr(regs.offset_out + offset) - + regs.offset_out] = tmp_buffer[offset]; + } + memory_manager.WriteBlock(regs.offset_out, dst_buffer.data(), regs.line_length_in); + } else { + if (!accelerate.BufferCopy(regs.offset_in, regs.offset_out, regs.line_length_in)) { + std::vector 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); + } + } } } + ReleaseSemaphore(); } -void MaxwellDMA::CopyPitchToPitch() { - // 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(line) * regs.pitch_in; - const GPUVAddr dest_line = regs.offset_out + static_cast(line) * regs.pitch_out; - memory_manager.CopyBlock(dest_line, source_line, regs.line_length_in); - } - return; - } - // 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 tmp_buffer(regs.line_length_in, regs.remap_consta_value); - memory_manager.WriteBlockUnsafe(regs.offset_out, reinterpret_cast(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 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); - } -} - void MaxwellDMA::CopyBlockLinearToPitch() { UNIMPLEMENTED_IF(regs.src_params.block_size.width != 0); UNIMPLEMENTED_IF(regs.src_params.layer != 0); diff --git a/src/video_core/engines/maxwell_dma.h b/src/video_core/engines/maxwell_dma.h index bc48320ce2..953e34adcc 100644 --- a/src/video_core/engines/maxwell_dma.h +++ b/src/video_core/engines/maxwell_dma.h @@ -219,8 +219,6 @@ private: /// registers. void Launch(); - void CopyPitchToPitch(); - void CopyBlockLinearToPitch(); void CopyPitchToBlockLinear(); From 40d9107b2315d09ccf96ef3888cdce5b1c7b09d1 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Mon, 17 Oct 2022 15:08:07 +0100 Subject: [PATCH 09/91] general: compress png images --- .../applet_pro_controller_dark_disabled.png | Bin 4477 -> 2712 bytes .../applet_pro_controller_disabled.png | Bin 4173 -> 2630 bytes ...pplet_pro_controller_midnight_disabled.png | Bin 4459 -> 2774 bytes dist/icons/overlay/button_A.png | Bin 3494 -> 1647 bytes dist/icons/overlay/button_B.png | Bin 3375 -> 1534 bytes dist/icons/overlay/button_X.png | Bin 3968 -> 1748 bytes dist/icons/overlay/button_Y.png | Bin 3337 -> 1504 bytes dist/icons/overlay/button_press_stick.png | Bin 5225 -> 2477 bytes dist/icons/overlay/controller_dual_joycon.png | Bin 7312 -> 3475 bytes .../overlay/controller_dual_joycon_dark.png | Bin 5889 -> 3107 bytes dist/icons/overlay/controller_handheld.png | Bin 4645 -> 2250 bytes .../overlay/controller_handheld_dark.png | Bin 3745 -> 2000 bytes dist/icons/overlay/controller_pro.png | Bin 9493 -> 4531 bytes dist/icons/overlay/controller_pro_dark.png | Bin 7488 -> 4531 bytes .../overlay/controller_single_joycon_left.png | Bin 7489 -> 3605 bytes .../controller_single_joycon_left_dark.png | Bin 6768 -> 3447 bytes .../controller_single_joycon_left_y_dark.png | Bin 2639 -> 1035 bytes .../controller_single_joycon_right.png | Bin 7497 -> 3603 bytes .../controller_single_joycon_right_dark.png | Bin 6729 -> 3406 bytes dist/icons/overlay/osk_button_backspace.png | Bin 2919 -> 1272 bytes .../overlay/osk_button_backspace_dark.png | Bin 2958 -> 1262 bytes .../colorful/icons/48x48/bad_folder.png | Bin 15494 -> 528 bytes .../default/icons/256x256/plus_folder.png | Bin 3521 -> 1948 bytes dist/qt_themes/default/icons/256x256/yuzu.png | Bin 6751 -> 4425 bytes .../qdarkstyle/icons/256x256/plus_folder.png | Bin 3931 -> 1924 bytes 25 files changed, 0 insertions(+), 0 deletions(-) diff --git a/dist/icons/controller/applet_pro_controller_dark_disabled.png b/dist/icons/controller/applet_pro_controller_dark_disabled.png index 416e1e2fb0d8c62d9fc767df45b0aa899d6869c5..d45f91db55a020385d07450ec87856710206eeb2 100644 GIT binary patch delta 2705 zcmV;C3U2lNBA6ABBYz5CNklqp5bbB%wr!i&m^7w4vu~emTUhDYwr$(C zZQH7oN$&Q|COw(#&Yk;X&iC~qok^;ztE)~`_bzLofd=Y^|Ni@Lf{u=kS*=SAbar;m z+}74{duwa^E8X4QQ-qfrIO2#SCU0wNe~H?EZEbBQSX~XYw10H1K#KW;+P|%>orA2d z2HM-(4}#TD`{cOej+?2sr?I3aB&FOB<&eL**6M12BGUq-lpjLnG>zTcSW?a9CDrj$ zOH0ebT=_#hsYe%!#hF=+F=|xPOk5ewrN1v%dOLbA7ea5qdDC2MT!V8yqbL3Cxioi> zKwlQLUWB646@Q_>d3dX>wwjarz8A{>$5Z+qF7-+D`!VJ3{uyYVGM5VJUl}xEuBHC_ zNTK&f4cDT6+#7vPNOOu%<~aJ^Nx$DhpZnF5=12PehbR1B==u-z^LMyT{XcTy-zJ3F zqEsqPRlC_Km1gyXKAB$ni${&V|3~c~YOf!7;DNJ{Qhz=Sm(&45_#^51ZzSCBTSa%6pd6q|OviwU(mU_aabAR9P;lrn?mK9UP8qCHc3>!9VN-p$W z@M}VtQ*!zhzd%a)z*A-;Rwc+<4ji9ZG3TWXxiBAMjI0_E2bqmN_yy;BrGa9_T>2*! zgx-_o1WccN#@fS&ZON@6xu5>fCl8b`dta#U5 zcbzN`j{#`*70LB5&G4>C{Yt5M`jR|-ynovnZ#N~z8ZJ*ac+uaS3w@7!sriS}1OK#y zAfBUlOpa15GuW({)exRA`+;YLGQ9}@VN(y50Hh$9mbWMLQE2JrXZ#eZP9acl#u5-` z#brT@Ym7E#?uz7+nBagX@rGTvEM4J`UEWOW#c|CoC{9}UzQJJ4X|Ls0x(y{mc znzo!hWzLD+`#C9VqTVHH237Pk&@$GX)_MkqVtz?ijQ@5PRtzP67LYYWGq9Qn87C2Y+K0!ffSm zcILEl#L>8MKZn&EN$oFtjU$X^WyNGI_tl_Q48PQB$@H0&O^a42r|b!(U_2;-xk%KL zhy>IQr5pc5wa&p~7J(ClrUNt14utUeBUVfnv;`XgAi_npm#7J;FK|^NQQ$%ZMx>(q zA7e>?$x`RfQI(4e^AQLY%YO-&tR?7gb|e6x5W7~)`UxpZOD6<_8&j=?6u~JnM#~Lj zPg=GNl`S~dF{MoDFUL{0m7230fWx$fgx7bm;w!Jbaw2EZTl%NcbKa!(Dz$b@U|+vu z9*^ZpqtjS43RhCfrLEQS8s}!2M8_L9Jt6d2C}*OySOdjmV#TO6(|_kbPH^_GKsgEN z%wb3lQ^+P1IGvzqSCIPDm>anSDNd4UortVDflqu)A9x~TJ~ILi5G|VrB2SPch($_*UaaJ6Y>s!aOUSSy#WZwdO zMID|P)qH;r?2{#2G+!|XAcTwf0a&BaMj0$HHi+w^K5WNiWW`YS6={kIOg|$)R)ULDlh{)0|gH>~3KJ_T6bdgWM&YmGN@v0)L08Bbc zoz?|?t|$F%GQQ{(Xx~(*rAXpImSX<42%ZyNU0w5#QobDsR*c$=M+qiZ`s{2wBW7FV zRkjH|jxqqt2!9cfEd7aV^DRp0QNqYlI;;UJo-pJ;FITneiqydN{8>%2mqP2GxH8wX z8i5pZxtkTcL9*RU?PuC{nU-ZlSnd0!_ZM<*tV#%D*23BqN*~T@fS#V7=_6K*QU$95 zNDRxAzj<|5C14~-L4x9xWjLGP=C?N-1& z9O-$M**T*p*EEdd%GIIEh%X2orvf)zU}bHaRqLRl`w z6iXXoi@KZxVN)vya6m9%iZz0p6}t~5;KV~eDvvKmS@q00&gHF(#LU|ItT}8}i~|EM zzX#bMBdZe71#|+%{E4-h!%01e?OHaDfg>eZSbs5O)=BVNplFks<%7 z8!HZLz^_8qYLD&0N3q~kBiE}|OM-;(1qKG& zp?~%r4ffS6T7_kS_k^3`b;pS(o;X>=igEZl?Tjx1MHX6t2?u={Gd^gDE_{eGqI0J& zILV2Ecn;K&72{YOh$uLUt5^;l^r=Tfqc|b~=hN527uO3;3n(L5z2UAEf06dry-0gd zxB|CHDNwyDl-`Y{e0XFz88nZg{)xuJJ%3*7lvPgDpB3YH2#$@|#>(!GWZHd-m-1A34^tOGJiR>^4z$ zfgA=M1||zYMTZ&{F951u6^0|ynTuF`7m+A{6^)9@k@jRYif|YuOOc77mn%dCvQ;H& zM5788$Oeg`JRxX9cD7kxU0t0K=jwA|W|x$V_O!(A5|JCV-Ec7NN$flC+;hHk#u>x^ z0-UW%`S$HE?pD!y5J7EL*FpD$s%m$!h)Vj@G@g1)5UcI?JS2N(Dn}6U2>P?^EVHDq z1?p2_{QjcL71@Z$Akb_D71fxEcu-=9=Fj-t)Z-wJ0#R$trO!YAoF{7RKYgn2`Fy@? zfad1r6O$%QdOI8r+rGq&qqMa2j94r-96(h)t=4{0+hiz4#;Mq`Mj5Tb7*QsGi~({L z460E?Jb<8_06B`wfAGn;Zbe1ay`Z-M zFZ+Dn>8FzwJI>s3&^90AxdzoKA{YgO@Sah*JQAtdp3XjPVI0TF!jttn%1A|Hf-G|* zwRh8ApdJW^!-H~i20sH#0sToR6uQ0tLmNSa_CI`43dDUEf&uM5ci+yX>+*`+UBcz!VX4{$r1Ai8z7c zdnZnse8I~*cRZD;KY&@YW@S&CHsj*+&bz2-&z|R6lj+=0RP>3-c~e(Snlxp8-sGv? zT{~ZVA(`H;T`%sRGI{Fbiq=oPaB}raFTK=brAqoJ#Z-z!bd&4W{iQ#H2#3Sg7`aiE z_e7YzVnyX#5#4P>R_6zTSN6we?$F_nV9bAI=d?T(4u^Z>U0PZ?$P+VLL2p91O3(-L z14Y49{vwf@?IOCV&6>hw@}z}Pm1|VY!~GdR9E%n$+HVZE1Bl4|Qzl*Tdtj?k&h3{& zk?9@3F4(tkUv8rKNONo|=%>4xAUCA-sk%wv>SUrGg$V|O9|NWnYxzSupL2?e&UFfk zN}YnDlJ=#sC(6po)`2#H=4NGOuN^ab#8r8D=dVedY^pJ!Hv>;7e6O~)_Ps>8kG6yK zSQzv`I-e&_4BZOMxc1s_4o#HxC`_xBOHh5*b={O4#9vT+r-{jKRMw*MASSlg2^4)d znXa|=JHP?p(sAR?UXUrdi(`GHuECAeUba3`dv7v%Lqo#>Rmy=_$E_V+qs!xR$8oX} zZ60}K&2dDY%gSlFI8oN4FdDO$D5QMh`u)LTRb2}By4)TNa=+hS^tD9&^78T~(3=1i zVd?VaD`&JPT6pmd!Ql8*aiE=CI!-Vz@qRcQHvT|xrBe`m#|aee$qy7=2e7`ewi4?b zZ;hRL=&jd3S6^TMmg6|1oIvnVOxAHHQ2a`MLGhQm4_NB^o5x@bEX%O z&-=taoho7AQ>jIgQr6sjybRa@Ol)n9R*1EQA{PPu(!mYDB9?!?)7tt2qlfXqYXU_C?Rq9rl(WA$GL6xB`Eu5JuVc^x&BHf=#(v=m9 zaLbGt(}p)jYOhG8FNO+`W`QFb(_bXSc_!hy#VfI-oJHVI33vY5(9rPr_*HiHiP1pr zm@(tK6}wwu)aV5WG2@Z{OqDRAsdfewBb6!bXzvw+h5=FKm+5-NjrBG4*p?Az3>$K0 z4?bnv)_~pz90C4U)C3zE8V&%=DJ;Cu?=Sks_1Bl40id?^PLN*%2Z3$Y#IB>gZ_(}w z?`DOVwR(QHVRpU9F9`n3AO}_LU83^C#>U#D19bwyyMa4^`1CuW5K6UTbVWEEHiHKb z-m1z>RW>eOT$Y||WPp5s@d^wAlJ(`E;zlw5`M`EApOHrVm6~|(+PF7q|Q8BMCcD><- zU~s&MmUX!`Mv49~mA>ORBUDTUs>@a6F@?`{m3PjV`Sa)dnhqap8kA+us;;g+=Jlv3Cyqd%WU|HX=zdoo zaU(U?Ce!x^f=?=pbtAQ(bQ~uallA8CVMB(mS+k}My3ClSrlXU9*W#_esL0ibT$~>$ za{7~~iOL{O7Jks7Lx;LOwxLkyfjBER#>JxpE`lyUMCRT`?-!Bm%)w!g>*8eLiO-~RUMqGUR=OG-w2vtloReit|&msWK^qrew>8)$x@=sMI~44_8O5j`n8bM&}X`TpV)?K0W8p`rTe^78Vgii(PxKpyR2 z#aqkEfABwDeR^B%N+8*{Sl)>iy%SCn&W>-WrD>CttgVXvV?(6w-@4rveGZkW@$1eR zqaqhtK zr!JUNP;xWySsT^M6J=KAMTIHx>ywX?>Lj1oml6#kk;qVN2?Gq|K)*V(Qrj>~3sqxI3Brp_-aE zp#7Z&9fk44n2c(V$EYg?Wtqzn&hZC=KS`zIHq@;9N%TVB5KIaQXBz>2Mhg$T)d#;>Yv4X4XFj(mUcKG5-lvdzDw*VLW9me_?5pNf_# z2s_f5R99EOZPgY5H|GZe*Yzc#OwrtYyi$a1z{fov@2>zJs;&JyG0#*%8i03D-caF} z*w*Q;>mKgSXMP|!%Lw1KMoX)!tKUwiE3m_gOoonCNwAYGsap+kY-p_CkRJ&CT<|mo zgTYVN*4F+#m98-+YEOQW#72!8rKxn|2#3RV)v8rEJk}W0cm7Dy-d5Utiy=fwDtHz5pFFMv*+Rv7LPhwmwpOvmk$Mjq1~+dHF>mH7~Ta z_=fvDI-=!7_VBK(Hysu(TzCMqV+Az#EnmL;yuR2afx)yrE66KuL*2LfVqlHz1fA4w z+>Qwl-P6~IYFjS?KIRkzdl0Cuc=+LU|E#O4YudEwH%I#F8{({(NMBfSDmVqfO$cWk zeec~%`f4MwmVLkk0OL5$CwJIFSP}WTzaaQUZ6{-6>F2@f?$F(#Q0UebD=P03;nogTJpIWhpUlN%)g>s$1U-m| zqLdsM4^AI5XOSC9!-HRBl*hiF@+k~IhAN{rqkme)ABh0$+_$YE$_#W^{H zX981NTcZ_2h72h?e!R6JLEWQd2m&~<0|_=CwNsk*I<6z?-3YZCCdIo zZ@)ex-kBdrgu~(H<;z#iGbZ+w2)E?r<^8U#tSo8kb^iSMYnu)qduY^IcKu7&fX(abJwW;jn4njFY?id(LH`5DJC1fZhvujj<1e!{Nb+ zI^l5GD!N3)EKPPiEd`3L@Q|7Gne(;+Ien?>uHAcYN93^EQ1eKJ<2WVA$;s*L73bs( zNxD3*kJN1fj=c2pzT0~9=M)rQ3!1&Yq4p0Z91dF*ZdEb&r#r|K2$W1#D~pNAjda-* zKb^Skw%b}xo-5XyQ=BVS>(Np{?np=a3JMC&M)f{Xy#-JMu)d*otHNqeRJH{I!7nBn zIZokMEcQ_hx4CY0%A+2q0|~w2wN+JBqZ4&D)Ybh>l-rFpk0#?b`31$7#MJY+h^~zH zqq}XJ`u)Y%D|#=61EM?&pfCd%i;?fR^)iu?@W z_0I7LpGO&w$SrO|&8E0+kCWk!<9P69Umm P00000NkvXXu0mjfJ-OS{ diff --git a/dist/icons/controller/applet_pro_controller_disabled.png b/dist/icons/controller/applet_pro_controller_disabled.png index 72a549ea98d988c7e10d82bd85126f163b409ed1..8c6bcd3083e8bb67cf18262ab161b64df79ace4b 100644 GIT binary patch delta 2622 zcmV-E3c>ZwAjTAsBYz4FNkl_G4yd zW@ct)W~Rd#N9l8YwxhVor9V9H?UfXJcePs0u5=gF-(V1#fZu`a0#DCr>`vm^)W=|A35uJY+eZYZkq{X?wl7qCAg+&s|>=jJYjm zdyfW%zw7_I?SEdQJ_m98@GKh+=)(K&!q0!_Z23FB{)cgY;p=ZX$Nk0P?;ZDde7y(n z-K(>+b28B?6bkN4RE)|%DJn6_H{NzXUTm?&X2y`um2*z6&Z5LP_g^SDl4Ik<^8X$d z$-mVVtLP2K=l_TE|Ho%WM2(??;OxKX%%(dCMy`vQ`F{;B|F8NokID~IMi0YFcfzm@ z)b{;9WQCmFyNY4=ENnR5oR-xy52d4{Z)U?V#%(xC4LNn~8Ah=@ztt(%N91gScOQ<4 zDPW>5fbn-AI^JW{&`D<1F~b5A!^!+U=Tw#w%|PDqR>cGVtVIaIle6>+W~mHktr8mE zqoU^Rc7L%h-GAk|=y4S&^e@+`4^M7n-=Zp+Dcz3yGv0~G9FGNl{R-^<& zANg+eq{z+Zd*`woyr<~hiKLrIIB6SgMP;650k+*lNC=i(L@VkHlKpRho*Ck`*Uq)G>poW zV}DMjyHW&)xk)5|3>{-06guEb$psAKyh#(s73I80%6o%5?2$ylgRFS(z4xB@`s=SB zFO_2)Z)fQBBxu4B(aWfN4mvFqF|J4eZKSPQ8waKL+968(Q8yDqp(V$Ar}LbEahjK7Z#^K+10UL&*w z3$WMFS<4SGY7+kbW_HZr!vCgaE2{cW6@<<-U11Zc}qWH(z9ZO_bzze zKgb)uh4~coS?JyW(%6{Dli|r@iGR@ltdhGBZKmz<%GmCHs#^XaevG$AT#jK9Ya%OV zAvg)^H4y@r0^ZkCU25{vAcJLo;qqW4R3oBq@+#?bzk)oHbIOZ_Ci)`?i_CqK^2<3^ zi93!Qmbc}J@!8)s`LN&`=Qi2^D}JOEy<#}X3F!2VVRtWBrxrYhb|`!Ll7AfYJa+=K zB>nBUjBZ`*Lp!_$m;~wRKp5*{?f@&^OEdx8;TW}{D84MIElL}?19PW>Q=f@#F>nW9 zmrFV63k-fzqy$4Ypy)pl#BQ%(uUHHcY_UeeZb!PV*$or7DF<~7vCL-@VJ8-9x+lmOBnU}lJwqU-& z&+p*pCE^nAWuaNP0Ez2g6lhboz%g2^SXKI8MkM>U8Qo))Gmd*Ei^Djw={V<2WnmX% zP6_4Bt61VhI7U{Oj3PqrOYB6?X(d0$+lmR0f@fVFGQ_L>jRjuG zle6#?A?lYu2qKB`I)C2#$z$Z+wSzK7;6Nkft+l`Lv{087BkZq>bqqsa6C`q`og86w z40D>OfsD0YtD&>=oEc%%|I9_HlW%>T9A?GjCo#+E6UT9;5*mQcz0mzXR@!wO^9Z7T z{a`EeST=R)Gs|u^$VZq?d8^cp(PzcC2nANoJgQ|>L%y~g_kVmI73*yXJ)8JC7c0AH zIIuFH)rl8fj8NZSGbH=y{ZgG?3Qzn3jv3xS=Z-A;=Vq{Co_um^X04iQ6E2eIWy}#MWsVhr^0hwPnG>N{xzRBU;KbTNO>u_x{ zD^7-ggkrmYR(~pNoa=!(2%q_>{|6?w#`~*1tNClUB+nA9!PEg(d`g3LnrT8eUMxs+ z8o?5TiFY+Wd{Us$A@`+oET?jSXaNGOco#oS$~6MHRbZ;$ba z+OL6lsDG7mJ>F$G-ch3Uh-SsfmlEzAMCb8Mzp74i^)&CeYqBt7juEX!G%MaaDeqA? z>L?lneLTKK9gM#ncVC=7}`d>*$kI5yPeq&x|$dpSH1L)dPN; zx~91_MaR6HFO^qpm;^cAjzR)jb-c>R zBJT+tinkjR1z2%U+#O$T6gqe9IN_u({r1sA_{Ha}L+YILeaCC-A_dyWiq*9^_j}qP zh=2I2YM+nMkETa?MZ&L9LLf8dZ2|Rv2HJ~cR{R}g#)Lb3Uy|EI{Xy|Iggz@a9k~f@;jUVb gLfTipWXOf#lc7*Vsnm+>L4zVwX6$V&~ADo8{ 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 Xf-zkcF-jto00000NkvXXu0mjf@=_E? diff --git a/dist/icons/controller/applet_pro_controller_midnight_disabled.png b/dist/icons/controller/applet_pro_controller_midnight_disabled.png index 2907f3be42b56b014388582d0bffca97e7e3a4df..d27dbfc66ab2a17842567e5bfce928e18a8662c1 100644 GIT binary patch delta 2767 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 VJWtHGUWWhx002ovPDHLkV1oMmUmXAd literal 4459 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 P|5Sjjr2~Rs;d|?U@cf2? diff --git a/dist/icons/overlay/button_B.png b/dist/icons/overlay/button_B.png index e8927addc4dd0ba42485d890925e4d0e157c437e..4a19d8176c74bf98c1be41a923e1fa765a7f53cd 100644 GIT binary patch delta 1527 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 BTUP)8 diff --git a/dist/icons/overlay/button_X.png b/dist/icons/overlay/button_X.png index fe70fb6852e8bda84daace73b0b85fffb1281368..f50a539748f6b7f4ec7c2915025ba6a969f82115 100644 GIT binary patch delta 1743 zcmV;=1~B=6AJh$y8Gi%-0091z-mCxs2AfGlK~#9!?VJOYmE83D@BS7$RlTxT>ei_`l^m=2D6O>8N-M3j(#qNc zC*n@k5?wJCGf;#i*8Z6!iZBCX(M7eyoj5UTk7jF7?Q377K7U6ss7W>cuu@W@M8ud92ij~R4VR#>_!4O!7 z9}=1IFu8+jiS=NJtjGID?wslYdkHGQ2&h)KTAQVAzzzYULk&tv@UbsL5{#H7_BD~3 zEZj=9#3nEWY=6T0YU_w>X74A*$|$hMVV`JznG3)aQ=ENN$Tn@;t^#8~ZBBleHc!J+ zFeWTboDz~vB(N9+V`Q};44X%w28n+JO^ppcgJ;z2nW+^Fo)NH*&Es8VU&y*kAau zc@Wlu-hXH>JeQ%g36We*a_p7ljh>UEKC(*oN1#{oIaRRw1R?US(B~V%JMVk6nZFlS zf!?{6NynQm<;g;iGg;%-AW;6 z3lY`n7k95MuTP(j^jc{15Lbw_$QaJ(XrKV+C&iMXxhv@01V3g!m}F9#6MS3qvYj9KynYz z8@RWE!RahmeS`QbFA3M%5rMI9(lh!k7@U)ZWqK{oA$Te)9Iwt~ArYLfw2Yz*G{Mut zGk@zxu%i%yZX--ktoOnNP0>%V`n~Y?p9n*!-Q5T7NuVjtVzppNTqYVKalETh+zFcF zH4jFgB#fbMb2VzW0?m=&2u+(qXcOjm*VQPxf+o3;bs9G7c^+Ys&hAFXf@W!>VYC%t zmQn6TXM$$Q%_SbF<$t+^ zA=Is|MsXr&h8vj_5+-v6s{@T~)pCw=AG*7PCMe)E!OpilpH;%O7otn0n2My*>UJTI zBlwJPwTfq$kD`Xso$~RaBlo)`&iWF;`7)vewtOJm@tR`@(&W)@Nnb9-+Tpeztg|)RvH8p?|o?=|p(M z?qGRU;y|xyvi`#DPIrC3^KLE?c*tW)EcXD*yYMBb&$D8B`Ub+4KZM>zJdx1nQ@@|| ztOxZtjTOSJeyN!dEfMVJBjMPFZzcjQZtv-ViW|1y}*6RxPMtzc4vz-B;yE2wY~ z8o`*8%sce|NA-#F3QG9|UOJr0a1qYJmP%qf2_%mZV{BIBhB?88wF#*FI3 zVWE^Jh}*~Pp_M4GPsI{2CM;1WTj41IRDv;}M&*TGLPX7v!MFNHT7TgS`=mJ7jLzT7 zibkhziPy0)u4R7A7vXA3!31=b6}_?#!N6#DC^#sU#nsBe@Ug4ZtyXGvdqgt9TX_+`MrnPj{Lxy7PSoNUV-HNwzGwio8$Nw>| zdLx3tYO>VRUkeErc}OcHEC-O-OYNRG-o9P6Kxd4`G!&r;YyV6WiZBhM&>8Ql+y2~Q lH!H2Q(n>3>w9<-={si0vexpcjv5002ovPDHLkV1h$KXdwUq literal 3968 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 U3YsB9nw9}wgaN!(%P#VN0Ffwzb^rhX diff --git a/dist/icons/overlay/button_Y.png b/dist/icons/overlay/button_Y.png index ca0de569df161c6492d7e42ed06f291b13a75322..435ec30d5970517add8906b4cb0f0fbc31bef447 100644 GIT binary patch delta 1497 zcmV;~1t$858sH0%8Gi%-0091z-mCxs1*b_wK~#9!?VMBmgs%$_=xzt*;G z+qTWIZ5uahdu-c8Pi8$lgWIkAQb{V^mD!xK>9>L2;zKUUa}bP=AwU7$_geRY{Q|a^geG z0;3^j;(b}ya>d$*;t7lf4KNaq*FMxrNl_Us@CRs+zeNXBvP#meh8Fk>On{B(>DIAB zavxD2o4^FwgcddTFS4e%9E-sMuuR3FMp6`?kJ1~i-#P6BMS4)oCOw{dAV8%Yf34Xf)=o%URjcwXJS2Q3G3a{ z6Vil>bs%VwYIzcxHLx7Cj8!GIO_!jnwZkO z3|m2q*($Fx^MiUVXgPVeT3pZn;2SUnd>PYI-U_CK8-L?e+x>MWd?)h5?KEVYe{dQR zid)!5$x}oy%PS5@yxIEoL^xW6)GRv;e}ds?N>52AA^?}aC~vtSblt|E zA&<-92{0^W9Pd+-t_ry23-pvcLxe^=8cH1^ z7>!=^lsrxN(oT7cmAsIMM2KTG;=`z-CvzS>1%GpRf^QL9t-QqtL`1|p+M;n*6P~80 zq%EQ36XmtXvXqF*G)>KtW576k>@$(OLUG*(eahF3L<~xf(GnGR#1lP_%jqfjo>Fsm z+kuFMyj4%M8yJuG=_z?qYj!)6#}o1BtR;$taYaiwN~0v5<@3%zK*VL9-XAU)pX-

TrA~TgaO=>0!49;8PN((_b*9wIb3<^%Y+fc zoq?jV0ceH?=qY)g;H#Q-IWivvieeaOilIuKrM!X*6z8wvWrQh)1d9$gCwivO5{tUp zHX+RMQ?Te{&>XMO(_|9MO^Qwo7F}RcRDZd|q9D&(OlsgWa^q*+wFtu0zoJI$h@Htk_lYDIr>!UT_}R`lVpdwc&(rRX1_x5cAn8bxI@Cv4nf zu_1M$f0rENgbzBUPPARrEj^o3C4agx@;~ckvs8&Ta-ycrepr$k(PgnVyo;OCA}X$P z;zfJ~n!x3?3QjjA`neM~sDw43<>X7N{yVm8U1>m5QGIQrp<^mycR9jZ&7UiFbIhge zFwC%0bB^&=1+qGpS*N+ot(Hh@0^2sTC)T1so&nDqO;4QWBo`N0Vuj{%QGc3v3lTBX zg1!1Ro#cZfiagl-Ty~gKn4I;0yoD`kZ}X$ygllaP3edGq;ghR^FAJ*qIVu&ix!NKy zdE`=Yhg00t++Q?JxLw{xG#2|hHl(y_Vhs>Cpl7a@V^eU1sBgZdrF*QUy>!K+hQ`?8 z;t_GMV|Pkw%Xcv?`e9u0wo(-zR{d5_I~xhl4cAB*m7KIj9PFMX?-Czk2!6%{%*9%4 z*v?wa#RU9>A^1Stx&0akIa#t~$&w{Y7HhH%`)G@)JHB+>00000NkvXXu0mjf5uC+4 literal 3337 zcmV+k4fgVhP)k@0^qU?z?;6edpc_*x1f?4-{8zI7xJU>= zT`pH15k0{en+;%`k+Gv^@H>pLa-YvvV`S`}7_@@s{pYbIz9m zSPUS=?0f|20PvQgD1Y#Jy+I=aT3%3($CDO`L|%-Zl}TpjGeE)swzRdiy?FBE$t!vW zvb3O1r*jr#Yzu&Io1Kd!>WJtCP1DK&kRAanDQL8L-3DNi*}3Qe5gp*1Kc&YdkL3h) zyWNiv(QD|u;7?>OGsYJAe7@Z>omxiF2@@tb+S=MyF~)uk3+1IV1Yjc({YulckPL^G zc;oW&^6pSobq}m_J!vS6u_HwEh~Mw8kK31wgyJFkefc2_9^tVdrz4Tbv_K$mA|5BE z3hH*d-2gryqHmd*hlQx;oXsA<}H(E|+o4$aQaPUf8d6}FhMkfkWfdrqfwnCJn937V3U zvKB-1Ix`-K=so~{6ty>`$#uD0GXd-|DqaFYjIp2ie7^VkGt~VJj^2UO&yM}po0Gl?lKmuqfc2D&x5+1c46 zQ&Up|GDd>Z($etgqmKdrR8>VwY3%F#*M?2DN}@x88Ko6ii?Y7gue-g!-176 zS7Pkgu|oEdNCXQPE|eAi_O`aR-vL9e%76fG?+!uB&~&cr?U z+#@rEZ1zl=x1k6Vs+m6Sm*CZy!GU>@z8`d%&Tp>P_FHQk%tsf`Z#3 zk;oeW95VfO=gO5UNJ>gVK|z6#eQIhdl9Q8hB5Bz*SemNdWLp%bo>-Rs!X3fQC?n-)2I6!Fu9J|vu7haJ3FqJ z)2C05sgqS&TB_@1Qv5j`Qxmnd7dY(aB#v#|X$&pdRX0*REYL`IiA>?3+)V6hZUz^Y4U7l?W9T75L_tSlkh!oos4 z@W2B?ww!Ym7Z)QO4h!cZk-Vq|as&;B!#|SYXP~TGw+`2?UF+WvorC);R;=i^gXC~H z@a(hC3g@tU_ikvKCTuSo&iTWDye8MwTV&_YpU11OzA9{=pP!G(lPC9S6U%sQeSJM% zdF2)1_i6@_YQrOo^FTEt(Lg>h3 zK!Tt-IXRgC?ut8|IEh3eC@wAr=Um9XuMX0ZB}Q z1$8(a#@U#4TuaWiHE5R#t|( zx;i1-8#_pfqG0LLrNSdhn>KC2g$t&vUehCyNMwA%3EJM?j&ULP zvt}VLFHh*W>gwv4MyiZ}F&4j|M3f^V8JSd8R^sE2KNhxMwrm-)va+yf(IO%HP$(4B zSh9h{FDNWJ>1e}-4Z)s(dSdvNAL@H0YF(bfgGs`6joE*49>(mX-?J2Lb`S`|i6sW+WXcf(ozDVFh9-SE;C| z5WXE}5$a`(trnxLwxZkOfDCN@-6{d3o2X@W+V(*CzBe>TD#?!NP( z?23hQ$^LBm=eqY?s-U4zXeR*Er(q2tu7<!yrL6*Rg{vAGi^hY*ahvYMKjrtZ&4 zFA`G}r5J!^3s)0}c12N2ZhTU@pkA-{9Dp}u`?a}=*Q%?lZ(43euQJXx=-j5*Mjtw& zE#Xb*BBe|}cR`vFxZh{7b!4@L= zrC$DrDnzvOiI4n#f1eBX z>b8kM7mTq50FDC4&^wk0oO1+&gYw4E#iXR9U-W0B`(!1*-+vasd~|LgZZj~5F}C2? zv14cYGnBfbb#F8@G}Mk6GiDTk`;CZ~fRy3DI5!P3P7RX@2?jhtaVI8Fh-H6!y0p z;fsJ(IA`yu%bep(*PDB&q5fd<3 zPyq0Hy{8#tKOEwhG?fmA<9anTVd!H0>`kofK%{f_9Oco13Ak>SKzc%p;=v%+4pu zr$kf^;H~J|6NWQr1?^6Letr(;d>#?a1|YVrgFKEC(L0>;H;rmm`ywF(?T*Le8BIhF zan7eO#->32rB5AzLyWQgM6@qjVq+N+PS6{1I-TjNs^$<;F6VqaV=M=qJ6WWmb9wbq z`2VWhUjw*=&J{+#22c+mNJK$ZRf9wn^!a?2Dg(5!v9YnSv9YnSv9YnSF_ZrRVS7ty TfH!AO00000NkvXXu0mjfpua&z diff --git a/dist/icons/overlay/button_press_stick.png b/dist/icons/overlay/button_press_stick.png index 6d0254d50ee535c54ae1f5576d1fbb53d3f4b488..13bbff9efba849435b0a1adf67a6fc8b771d83ca 100644 GIT binary patch literal 2477 zcmV;e2~zfnP){o^_Mx<~>RWEl3sb%I zlsf|kj9(Y<$s0@U|OcAT&z>!H15y1**jh&d5jOne_=Y0 z`6{h;yh%7>WJmp78uXV=x5o>j!tLe0j2m5_9G`eqe4f*szxN`RHdcKbYAE=M*P{6t z*%9Yu9DB~|LV2PtL!Qr+ZIeO~=Y`N!fAPYGHdcKb+HrAvQb1w{%(SP;JhfPco}HAM zg=6?StV_pRDyfZC-{#{=`j!_?iAnpKOY@j86F6$QI+?$T=Q&X%x zr0hlE01s1*%l^TF`XVRf@Al+PNr6oYaJQbUA24cBnT5qECv;G?4EcYFX$LvGu=NJ^ zF3JIZNsDdqLYQP&$~PevVYtDPAu6YFNim^;VnPyfxvn4Au|CC}tuL9(o9wT9k42(- zO-|6aILO?~8&Gz5U92(bF13wqk*IFt9hJ&$rkKppz2qEoQsdrg`ctY+1k+mvoQ6L5 z(b#)YHX5Tb!!Xii)%3qY!h!a6i%P>v=9t(to`Qf|jFYa@eU~EPNV_!PEpI13xXBn5 z{=onh)NyBs#9J0vk->&ojY}-&u&d65dc1>#L+$;RIR!_Uu2!uOn+Lw$hlLm6E{qhM_N}b4tR|Hbz%A={+|Rky=A~>GGpWv_l=G zp%zSa_#y3)RCfq3X`Fbz{zk96Nv3MvzhGKP2e{d;62a`2$w5~0>0`s|I?L;# zUXp~nO;*&8omL_*wOGj1v>#CA?abiQol&%L9xz->IGki7 z>%=ulfoGf~@;E7EoeY1Mh`EC=AcdO_I*k+if`QyfV>?({_X|opiN14=$hT>)B7bAb z$ob+5FIwCdCWUP4pc7lcOIYvxWKgK{DkH_|EvIO_OZDc^YqZ0B?9_>^z)CKSSG*i4 zqCGLi?nP=1D&9{yrlsw>PE0+f>~;wx$%u_q7cH-&(oBI5{vFtiXv? zC2}koz*WHs9*PO$GO~B#Sjoz#yP}Hm^*Ty{S4G?w_1-8lp?Mx=`!U+nq$1~3X{@TV zs=B@pT=tr1WM4+r_5Sq03b&KV0^gJwD+x^E@K;O8{$xQmuV8}BG;E()i0(V*1_}Z< zVs<8DUNz&KXoaHh-}6Fb0c-kt%uH#ZVBmb;4gcY$%<4nFtn3*ow{vf@;4R=!mzL8g z8zS8T0t$co6IXbZGD`e!u~I<7&!7I92kx4?UOf?pO02USf`3X*;(tCh(#=mJr~rr8 zqTIlp0T1{iv%XvtDbB#8Qj*}_f%Wr$2Aq8xwFc=Gm@_v6+XpAt%Fgm((Imc*L!?)h zRO%T)=eJtbtA;f#(8G6jmKu&aULKd==YlBwjEi|Wf4QD6FU||RW-2#PIAE(Rk9|!V za+JC*!N>EH_&;1n!V`QP7FWR+JQ=(Ze64Ip;em^h$nrJz%ldFlnc^mFDM!n-{5(Fz zclaB#=C;4_9X{pfx?J&Oep1Vp^2cJGa91lv z`?Rc6a6OtiKCOpmTY1u*T47Zn>Ov&=|BdT;EpAVFLWhPWah^I8a<%hHQ0$7mLxp|* zlsmAG#5QUZ@;6uz``}Le6(&QAcc;?8TS0ybFE#KYU&BZ8X0jMJMn_7oSi4v|=@lKx zjk4I>y+_Hlcrg!Y^CK@$r9-r*F1sq-?r!Kqr3QAxV)@Dfx3)XNrluWTgVsmPyKn2HDwl$^wiyq1!vEF^|=@k>c4{3z$ZqiSNK zHnJkF#vcV!_zf4!GE`5lKt{{XxE?=*Ch!X$kV7P<6x@S1l$Vp8aR%Qjui^uIjlVH- rZu=Ww%ZGRkcgbnI6P9fnLM6r=WtFyhltC3`00000NkvXXu0mjf+;P@y literal 5225 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 diff --git a/dist/icons/overlay/controller_dual_joycon.png b/dist/icons/overlay/controller_dual_joycon.png index 8e8b5ad417f9498bf2d3a158fb960671bdaec021..286b8d8aabb45985d8952422b62a977e27b8eb2a 100644 GIT binary patch literal 3475 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 literal 7312 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 diff --git a/dist/icons/overlay/controller_dual_joycon_dark.png b/dist/icons/overlay/controller_dual_joycon_dark.png index 63e03eb4e0f1c692864c6af98aa0d349c83dc3a5..3fba546186e15b63cae4386fc53c09658db953e2 100644 GIT binary patch literal 3107 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 IS?ulq0u7?|TmS$7 diff --git a/dist/icons/overlay/controller_handheld.png b/dist/icons/overlay/controller_handheld.png index deb375011c85a5f4d615c08e33fcbb97f8532944..38c38c0da4a2167ccb0ca5df842176db3a82c232 100644 GIT binary patch delta 2174 zcmXw52{e>#8y-IfnTWBBE&EpDMOi;Yc4PDtg^5>0NO72uVKgH<6CqoqNNBZhQ(jxq zs4Qb^Wb2y}Vkmq0-v0Cb=RD^*&wW4J_1xEWKj*Gc>KD2tA=o>i$t|ZokHhKG7KR@!$5`O)nUgS>U*d zjXZnBe;xmxxU#!djoU?@cXe^(-IjHk=yDg5%0CuZmi&_ty{xkRu>JP;1Y`QS@=NAB zQdbxu#T(gz5z$Oai|rseGOwja;#dnf3arRkrP;-r-^t#$AUSfEoJtidvtxQPoC-Vh zE*fN-C8ka;v{sJmRI8S7?B5wovc8rTIs?+DyVQR<^Vk8clHZ2A)Me&R?uF$+*s)N1 z)quu>i!OePHb1*Wu0Y>{X@Yi(}h}?^X9f*+FA+>jW}iHHSuvFbz~RV z$sqjZW2M{|NQ8*gv+Nqin~(dZj!Ds0QYgyyc=u3|#jKzyVbVTAu{`xS2D$F|xUO7c zP!`?g|FxQYJM9Dp`JgC`-$W_mT^2bh4PJ3Oy@7-l$GLtjHtr_+{M0W^@*c3kP;PRg zQ51Kzin;sM-s95iSs-?c&#BHUdhv(Y-)>PaPm*V;L=0gpjo{~gwzHK*VNxSjV1VzeE=svt~!@dnu6d~w`Tst1t|G< z79Sk!pmiDR$lBim^9|Jx66Vq~aloDoGV@F4O|820>uz{3c5&LL_@a`?l%Wx{s@_LD zUhA;MnPzx$$bfUTh5#5iSU{Xv2ND+nGmz)UwaKE~)u@x_n!aYgxb}{Om=Z)6)`sev z8{5kHV^}t(n8SlaiQRn1P{5QPT_GNjNFt>~04SBJT?bC(>(`z_N)d|e9%-@d0UKm^ zM)QZt$7H_H%n4lWf62aC$wUD4uoq3Abc47GyO%$m@Rqp|z(0o&bzWID zRM}d-cW`v1H0uHkhAGQ7iffYS12O3|R3c0~EKgpFykQ62`@kW#p?#3fkOFdH9zx#- zZGGr_qZvhp4RGa_H7byzC4r*web{{|T}KLrxVk|98FM_C*~=YuNOuj5rA0krka-iK zy#$$lPlv*_(Qyv5zYo(dtM?g%iT?sI^pI1Nw#ZYHGmUtPU9>kbgQ6&lqO@2Np$bDJ z8S zxG2z85G6e}FFH1T3FXxpU~8=bXHa|snWspYp8K#SIk|D@6wwT2d{}C4{q6I-MShr2 zXbBXe3yebfY3I>R?~FoJg7A@3w8MAdsN^}Q;c`QOVOptcpJZ3`oW-4V9DwQ#)G9ke zs6WUGW5>^jX!`Z!kRHL$?>DwnQ*0YYhEt;{LV&$ZIo@=!BA-g7{cIJXh)D| z*@<6{#@HfpTJ3(Z?a;;v(dIHa@+?QSk#tQge$z>Yy2BJa-9sCD_ALnhU#OQy zuB2vhS%rd9#X=XI>7HINikCUv9H}l5{6G1bmVj3-!fJbC#FLHmWQ#c>(f%fxPpXm$ zdfNDccNHyU`8~iC*4w`d#9%E}MhHPio^xe(DZz>)p9l4_j!(WOc7qsfJnK?e83l;Z z=JEYrz2wuX_o&8N4MRfe8jouFZVqQTH>`@s!F_|c>8dm;On~>EGu&M{%bHR7v#H(YcjsdWUY=XjKbAkKneBkOq3{)LlG8vh#bIya^2slg{13l638nx5 literal 4645 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 Gy#E75S7q@4 literal 3745 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 F|6eLh>Gl8s diff --git a/dist/icons/overlay/controller_pro.png b/dist/icons/overlay/controller_pro.png index 67cf86d5c46f43eedd96beacd789af20d94a07c1..78273fe57977b2932b78ce8a229a12e1e2973334 100644 GIT binary patch literal 4531 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 literal 9493 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 aGluAqkh6)lJK5kCCcsGF99gUDn(%+W&hcab diff --git a/dist/icons/overlay/controller_pro_dark.png b/dist/icons/overlay/controller_pro_dark.png index 7be655b961eba94566216874cf4b14c633c1e084..8d261f1f74ed96b04b4dfd20c0d82b848efe8eab 100644 GIT binary patch literal 4531 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 M8d?}M>AS`MAI6*SVgLXD literal 7488 zcmcIpi8qx0_n$G=$k-`{8e8^#$r6=)U!tNI$s<|IGWKB2K1#y3J71A!2v z$3rg=5{Xpw3GfZ}^a%1&41DNaMA1770*UmQ8(qE@Uc5<+K;Lr0jO=X9-@I|H!U2Y z9*^nE(-x7z5wA0w6cJuezYKv$qQMI>{D@q#uQ&qU8n*w+W`8Liw}F$zjh!xxU_*od zp6wpOmn!K7_>^IvFz_Q??n)2TG87l`+vwkG2pmZfpCuSS-e}V?7q(W zDB}u64y?-%$k++)oH*9KxcefXVFvV_;d&8a_x+}2(L#(q=>qBA*Wt`btVql~uoNN4Twoxy?VGlhsGj%~UVc}8B?KJEY`0qJpmwP~l!;m8i zyTvI2ef{Kq9E&LU7-Yo~ASD57**-K6@k)GvgzSSBPm`fm;KLl|y~2w)CR`7&Z+D{A z9(A%qm_dQ#+#YYQl^TYh=54EK%sz_IKyb7W+a5E$^D=PteE(`jM5@^-;n`f=%_Mo| zHLX7c2gJR^k}`d7?pCfF40uRBZzLmEfGz2jYktqw{^Pbg_pQ00-Vb+3$z?6`AqA(cvO=%Z7;`5`BH-vu<$Sn<_pgL9@QEjYtmdq)os$wmmT~+Q4w8_|f~JKe z2`2={TmIC!Y=@=)cwdM#M4Iaz%&3#Y+<0aY7D8*z@IK-|5xbcBS;+Z10@*_%=u^>coJj*Es-g3F*fPe2tegy1l+A!WZj;h)x`8m6 ze5sH2+Z^m-OX`NVRe0gjfv|uL!@?i$>2`S&EEDt{N8k<+ca}?Dz_a`I`A*6eT zvVM(*0=$W)5!8)QFo(T2glRCZP#PEt%D=&LW(IK3g}%zgDHyL+r~k1a&aKXqyRHt( z;BE~YGKA$K;!hYjtzam#iI_p+6HA!Un=GNE2h^DOpX-e%X@~1AAZ!r?|Go~Z=o#XQDcct80%J3Rn9&=vqZL)%Re-w4ejZ*`iwEG)kP_r zKK}-H$PYDN$S@}+VK~X9s?6Dn)C;XQ>z(>XBb6z~NSv_4ZTBiuKaS!b6i6%(p z$Sh0m8IP#0OM!oZn#2NY2MqR4yL?Vvm2!ss@&3_$iqw_o5~~ zIKj8g!1i467L;{81~!WXm{Lu$T&(`W%ZD*Af2JW6bg+hw*LLV!B!wmpoe_ElZ8dO`Dj;nKOC$YuKZp!4HI{wA1xH)|P z0wuS-6z`AI+_`dpUL18g`$gt(9tIHxIlkdcyS>4q78NH44WVj*kl@Pk(=t6wI00aP z+6;=nG+pgTIpflv;Q#2$%?DH@=*?qZ2M;WU;P0h=Yt3Tut)(Bo#pZ`Jle(B@pMo~N`>L%2Os5%G=6b&9@t$O>q_}7k?q9}F-U;1Qr@ih0m>>W=F zml{Xs#E2;K&9HWRTvhQ$!t+c+9jnE-+|d_C+l_Bgr<7UB|v`0`>i z*{GDNk^d(!rz`bh&fORC!nLOKpgP5O4Fz- zy5T0+(`VzN{6_%kele`l>o;PN!3f%6>UX+We=@!Z8^|2pQ1~=Qjbuvc-w%S09Z!4^ zC!ffS=Vw0wHmm$0FG4uR%#=$O{M{{P%_Ct!Y#w|k%p{dTc$ZBO$2KoA@W2PN>4V|q zpr!m&bp=fkV+u?R@SRn+PDOWr)0W~vS`J?3o_}=yFiPuiIuHAiaZw0MWLV?M8U2`0 zbXGxwC5RfD(8VAIT4a)_^U%RX4^kH9S!r&X=Y~F;pL99}BhGp#P4ePgP<>`BkQLcS z%UEFlQ^n(MnhWX~#g`8%Q-}dHC4nS+-b`!@+iSza6}xkOw+HmI zX&n%lF+Jjdp!t=1&MzGK;-ptld=n25o9NIxccc^hEbg_#v_P&Hf_X$Pdpt%T07kfa z-zLXqSN+UnRvcr_oBv9oL8OP+WeP71Mrq$qQrU#{^o3b5Enaen$#O;$V7L#cuX^yK zYOWl-Y(&{QMHeDC)f4r;qCeGB8a+KVRZ~Q;2RgN#lL+Vu57a|2{D_k|U<~+e@{O`a zIWe3y=ya~j=I7nh7xCwinom6D`N3?e#C+-6m(F0Mn5EDy@-rSb;#h(K7zwiFU4-N& z+>VFM-wRYJpNTHTOt>uHS$5t44WixN-fZ=dmt3H@gZ0qEx{4V{F%ogeeWH&MjR4s1 zs0*kKkXimJI!9e3vXQ+2_VL$gSX<^}jz-6|&?-K5R+i%uPrz0yG9DYqQEXKERIBr` zub$0?@*Q~(h1nFGO~>_5J2kktLc-MvQ4*yxUOZtc9eeC;pjIUCvhVA{QHX6#o2we~ zWCYdXIQL5$s+QDrY_K>++_L8T2}hooeOMX|T{t>SQ;F833RkA1_0c}K{+Dy(%MaC} zY@GyuwdMp1wE8>Ekgq6l)o&YE9aJslZ)0<#{_9}64o+R$B=*uaP%F+<`SaC4K6e(f zPj!n|-%q9IQEPrftq;)YjtcuKA%qmOo|SU{e5WR%8co2sY8NOeSm{5Glb)dez zSq|9|70`(l)t3FbM}h1sx4)JYJM@f%d6auPy)ZqV!1qs5a`pXcx|6={e2~t3E3YtX z)4|xz>r)?0^Z|68V(gLbkAupHJ~fiX#_MO)bxE(EY^O)deimEOeG(LC;OAq4G$>Ve z=6PE*L53Qi-EP$o2eRt`QBbGcVRaZ43Nd)Z^^x9Oyis9~f~lV3izll)?riSA0(tt@ z0MSuyS`*t>UASqqpi%cCzyHw_f02*BS$8tOZHW)heWyT8(!QgGc7|M7+M}w%c6%>= z3IzvPgm0ScP77dMUHBdoPyf{(S+OmTP-=7%6o@`HREcDKd-F=wa4}(&y};z2>{wJU zg>9|hXvR8bv-gj2>7F^TFX1r~j?L#~*7F?IUoV_0$^Mvr9D~x_Py5eghjGON;q#02 zWk=<5WQu5a7_lQaGecrqa1J)>|CM{NqN`1hyQgnNyyImLR?uyWzmlb0_cK(8K|cLK zv=O-E8l`pWReNvmdxc4Mrc!Joxl{ap(bb`TugCTri)lgq*D@J-U&wt_(Y;=7F=;z&v&WlDh`CsMresedld>yIfwgt46u8~0BkMNdhh8(FfEkn2{id+V=$0@slr9YuHf=H}U_1Jym$ z5b}W;v(Dm0y|gdf^HJBU!op-C3+y^r+WCE)xArYVqnJM{o(~*0g6j6Cu6i_Fee^4@ ze~Th+!`w~tz>SFCQd%3Wa4L3)yqjs#mO!}iD)xE}!ejSJQoE?8S4=_3aB#C$`xwB#HvFGh_=UyaG9>k{c`~;d{QQ9v*D7vg9ItZH zuC%HE$Q5w>?H^2SkLO)MG2*g;{iLw6Tj~SF>~u_+TmT=HXVH9Y-=_#|3HTG<)yvW? za$tav90W#^(&KFY`;5cl8=KD5`H6gIhU2R{?wt)X#LhG6fRa-L=zrtUh27* z_(yzMEH2`U^P?XdzwqSsf5kFWIp`Srpc+$>M>QwlKT+f3(!?fP`)$`(S1nX2{o>`K zj%2sJCg97ykVT+b`!Ptn zW4g8wD?mpKrM{~Jl;g`+ocdJXT@?g3bYa&T%@WV_Ce5|C@YS69E1+H5_z-in5Tm!O zA$YU$yw8Fuy5!KQeJR%u^!F<6d;H9IzY~=~|0OHZnL!#N<_h%W_BVG26+vy@gHlXW ztEv_)eo62EI@Xd*-2bovXIr~K(!JVWQZESEzNn_#)cCI5z27on?2)Uv%pvbU8LOn$ zA*-hWmK$)FTJ_r!CGn~G>PUSrcbm*cfyrUOHsCcA!F)r(*VDFMw`22yTG8AG-V6zJ z9nL`Oz`kB$1;;Ycn7464IX1rXQX)InF=53;e|V564Ke_8%4^ggR2SZKO~yT1_(0aP zDw6pSK{1dx@>CHHrG6s#VRAA!ehLxg-QyZ@#9|ZtleDNBICnG}sTi`}Txksp^;WhDGF(zj{M%0fuPQ8$2}k>5IPuR_@rP z=Z08|0?yKhh2%%_L&n7`!%r?O^P%if{s%?8obMk+gg*PWdq>|9*;Z|WevQmh91hgbZnd8H&f7+f z9Q8W_Ywz&+-88)`H&HfPh1-j7G(P6ejTSr3h1#XrH$bZ z(Ej=I)TlT0jN%rpM1(^pvwZ$U?e&lSIgG!mHA^9ThpBu%cjdWO?GEs|;+;;(3v5y| zLROj=URf_@q#5n%E!(|3gG)k#2`Y9qTrU zF(IWc8WAiJUFWZz zon)_b+9hV&XzMZqdoYJ z)_ve30}Z0$tM%qg_EMm_hSM%&qcM34W}F2`S6?$wYU8WAzsqvm; z{b`tqFsdzAmPEP_pGTv2%lpl+3YiWxVmEW(!T{ftO+rPUF}6O;;?pVA#bVnJ`WmWc z<2~nWq1B43NA>@?U$1r=XKRN?+`f8HH8EA<{$%&b%d|~R(s!rZc>Dsy>A!> zMhD&FNg9-6M%nj_%d;5@a(_D}CWuN5m(V&{BA4z026elM1jALo!SKXyzt9-}g)xY0 zukl0bEBl8(sD{$g1y-etPWRc_URcXLgY`R)ack)##F6gp&saKjn5P~Vm`T(ad{el? zXJ2yvYgl1^{rsy!M6XYwwaz$ufSNd zRjU?b9a0nLOYVs*>%H`Nm4gV0lJL6kqi=ks?IF9uR!Tm9W_Z!GMdBw=OP&uFHM5wq zZ>#mpjLk$}NM>s38{t@t(4XEsuTUW&q|heZ_<}z({qy;EvApJ8$c0XDFf#Q;dRn?}inMAB>@UkIp`8k_EU+3! z6oeP=XVcD-nTObC4oHJJJ<%D#Un>31Mu}xDkM-?x;y zpWi}RriSYUHF}&#m1%f@6l3~}J``M}NYGQilhy3mJCH#zbsbA>2mji#d-LnLF0r-b zX*HcDp-;*tP}95NKN+To`g4sr*cPT+Y;B1PT1FxBhBm8YCh^sg?;oD7C&SJ?r4%AewR>qKdZ> zT70^AnNq-QrfH4Rq5a5KWVV+}QzbHkvYH~NNVY&^vIujcgLo6YbE%cx@H zT>?{LMHNKP+RUbg+)Cd`8|qN-co*;#U^9^mUr{4f)+KStn7=LlL>BmQT9O$KCwe!S zEL}_F&K5emmdSt0#*3!uCa*8T{X^52vuSR&mZg&3{yIF9=OrYCxYNCo7d~AWP#0KE?OwUCre-Asv&Pw z)Cbo0&5Rxi?MCY^o?hbufByta2mXd#KfAt}f$^Dcgddg6sJ)+LI*qS%uz(Z|=pp<$o5**Te# zA_Z;Sts1Ql=7R2f29wXr?)f^B@hRlzv5z>Zr6!sxBa3YHlnpjPAq zY=JH@;4NXcsXl0gX5co?ZBKa{P@_d`7*@4o2+#!mgb~M)$;)om%FHd43b*(VR3@~w znc~N4PNR*tL@ky8X14;Je9l(J8=vv{`&5MHECFoQn#|sRfO|p1Cl}>gk%Qe$z#)(&H6BeWZV;nkxyVv)C>>a zgbnh8@gu0_1?QFCcj>5`@7+4VwJcLxbOPz&#Rj?vdPVX0^l= zU=B07lEi~QWP_OdoC#{g{PPBjCOjl*BO*3#@SMp*Xj%!GSqK&q^y`wIQtkps1_N`L zDO?zY#=!<%TlQE8i(;t4yN|mj(9&G`uQ#l zK{e9*`*D2?POh%97RNy%H<@P$p``ClBx|Lc(TM4x9 z1i5$mavH~7V1|Ks9M$t28m8J=I5QaPeGs1f1gBBAa$$63jl%r*8O1fI zs|!QS(_zid?xu-`xF0QE#G(5A#>kZEPex%SIT_Q*80#7ukVjEf+iDrrmGilCX?T3Xr$~}fj zQF?U7U5_=hLAIWuR8j=d5}Z}_lTJe6VsJy#FFO#TtuHY8`rAcUM-a;GHMJi{Q_HSB z%ntAZ;=ndGFw=~F9rYf=T>JY-FY) z{7N}SB#Uyqj>NCN$%8q8*%Le+SEfgmk+DZqb629}KszSBHtG?xAV7FE&!FrDi7Gf+q1jQXZDg8;YioH zWw^#FMYxx?$=@d!7((5vkMyV{9^FNjxld-?+@N@O)6f=fPtB}*Xg822kFZK|Kj>zN zwhyTrTDN;?Qk2+otWx?xVsjW3CL5;$*H~N5|1p=g^>&zYe3gz&_k4fas(VLtoBEDK zBj4bNNXsJ^qYXTf8blTArX^}FWjw*sX6{Jl*YdY7^g(A~;d087w8HnK5<&~tzTyIO z!FbmDK_w=1*Wq5a%ReHvFX1SDGeL`MU!Yl?Kh2uKVab~wY-SR+JN`R`HOno#%Xc;s2;Cg6~P zFhnh_H>6hHAF2#Xz8}8P=e$7~(4_B|+-cMdqqojk7CnAy)xl$1OSv^82WowJvy;+& ziLOvX``GfGhhm#`E>juS(FJk|GtVZvnCh&;Z#+@aS#pi9ac9&#{B6#5E9}C!l5&f? zgfH#A^=(V8s;7qCM(x#!3UgA-5gNWhI}Z+Dj40dIfL}QWt_-7h>*`Ht52ctQB;sn- zG}`TWFLDA-Basw|Z{A#{lDri>4UVCry3nQ&LB=g5^2E?`{Qhh47ZZ*B>D6fYS6Z%o zzex%atElm@6`WNc~0f^5*4e zJK|3Iq*ob}$mma}e_q-V+vbe$3Yssp3_8Q@9)ejq=jKM3XZRJQG#X{d-1iOBuHOi@ zOp-`rC3Dk~4R4G#POCozn;T%4L%lF>sTzQ*Uq%Ife!>?+XY*15#4aQHiK-yRCq!>2 zh`&OagTRLqT@RaIj~!jEn@3>3q#E6QH5fJ0$^c6wg0ao(Nv|3}biR6AJ3|UJa~?sm z+pv$V%&GPlBwKH-ZMy1NC6^5_p!eR|{bgX|h(F}V>E-&%TTIDPMd;S>A#h=6*odznG8;JHNcy@F7D(Z2MnZPlYpZA%#p03%Xocl54{6+KgncUcfj|N)NDfadyh&A?15oCgsQYOw_JaXFiYsKBU z+Y&$%DuahK&`tPw;U0NhE+{BXL6>C*tGOKabM+mj_+0WCe8>iQh z=%HQ?I;mrkuq?0jsMkZ$&R#jfBP6;jRjlAlp@mKcNSSzU{i(Ow{3>Wtv(jIR>d8&W z6wNJ)J$Js5WCX&`QEygUby#>TTE-F%Qbs$6cqQeizc*Fmx`F0b|0WN<&?H;eMD{a!V+5n?-eYvvmdrb#z_phJ?EDtflQ`wg0TJUZ9X(K6GZIusl=1r)%=Cotu zkrwi(s6hKP50QC5vGj$v<|u%daSR91qlL#HeQ^fuN=#>~D)=nJH;~U0K?>>n=`3U~ zD*<8jPv6Zg8@nj#HZCdg58oT#81RrwWs0g z-R(at>qgbf%0SjVd&cEWoMYxRw*c?ctmwRnfxpQBDgUBH`01T>XFv9Sk9&Rj+Gqyc z{@k#wsy=?C4dy-Fuog_zwb-TOAK|}IlJL=>^yTP^vyelK*1525l&;r52IBB-<@S_J}gLB zl_hTzb!Su;!{6xGTD4f1Is%w%E9rXT)l?LkC_(d(*WWph1L*AZ^5RXCw+V0dmd`We z0^LSq%Qg&4!&JUFyKtsjB1u6YBMzWb);hpO{vU*;+hXo9bpPpVl@xKOjX$L#MLU%d z8U^7CC>foRU&{OZWNr@fehzQ;v7`~%!$16^p{9!lcy@yAt*Pz^&Vm*-%g5W zTz%m6xRI^wRp;|lTL{R2-TN4+@^S5{Q)r@z&Nk`qD~V4MF*?T2FH+9?eL2R6+cr!) z`7BNWHmu_b!&bJly+*N-u@(xcX&+!~8qP3-sm;(~c|nm_GB2jp&}G@S6}J%tzvq_4 zpPYG)Vmr-_R#5ZpVCnHPv(XTMFWxbEA?U4l83#1`+WRSHubHsp zXzyGSKoe(H{T=_U3e0x>p}Or>v}EUvnrcU233Gw*VrhcBe~xFb-CgpSDwyC&$@k~1 zd9RvSWG1y>u2pVkv5*(YWwZfjEAD%cr_Z&?Ngrr>f#{8}ymWUH1!|2*b)fZ7H=4zJTq*6$bv|6Cp7_MDD)gY<5S$8e9jek*=?BkZVwy z5$Cy!P31BMp(PEb2)K&w*>Mn;D&D!AuN&G%HUHd?LK%bTslc=`K^~xa%02Irya;5+ z-V+CHf}lANzEv7>x}b7%XMw7lLbsz$lWjqnfUw=ZdXmeLjZcN|?Swuly7C6FM!0ue zsy5-EYG^wrgWL)_3mx1(!9(=#pHkv~7?iZJv!E974f2a}dCSXWAA*8ZEOfMbJbsc> zUYiL<>i2)Dzbsv{2XURkWAdk;e8x2f!Gykbb69U`5fa{$s@!%QaSax2RIMu%yvpk( zHBsStYx(@lCJ%nGn%H=kt!Dc@o^du_oZu5)ob(r*b`zi8+Euef|CfQg^<@iV-ddSu zA`P4_OVNX|l1IB4!X9l)#Yqs_>buz&XF{}5dT8Tn_{mvNG4R6Cj~(+}R4T>2>+s7~ z$kUGte2%xq-ku|8={h8YcxkU>_(@yNbMCRYR|-@I2B1F#eB zSgrAoF%xWD?=LvhRt9c8DBed;S=tc&+X!E>qmf)Ck?=KJ-o+KQN{ip3Yc9h{E8wKh zD#<(W0q*>u_)?ot_ZcOa*B={PnSp=~ed&2Jr(jw0eJTjw4IfTn>LW-0N`U) z>^tEjH82K57Xg%W(g!(b@J5LUC=7(7>{S`o>jAS9#IC*F{n^lCjR0vaWpL2uJ)sY? zulv|9prO?=ia>FVAcE+ls`i|^7-V`8bHD46-2)sj38eYf>k370vTlz&0b=-wT|q5vF0k-%%i2n$zlq0#z47l5F1OYPX7~FqJk{wr1 zUw178$hB?m*sptzqXtM=6>9oILzw~Ec5G0|#w<6{Ym))~Cy&DbghM-i3Sue?4J2-e z`{BYPGGof*O$2@ovqL52DcvW11&`}`nSVFCB!^>O{LE$+u%Q*jX<%4TLN{LUvczM2&`KlDjGQ=0lj`Ro9#cE-|pP|j0@ z>KI-!Pm68#JVpL-+~aAl^k*&r^T4|lk*N^KNOk-`yJ#9v=C082lpkZ-fWD}jsbUzC z$^^Yn>X|bgY|*Qwx*imyMcYgi))0~;U9(ujp`HZ69D*PvMMO=&a&~afT z>)QAJnq{RxsY3THF&U4{*+W>Nuhq_$A{h|9XOI_+X4#d;^L4JLJsXm3@|jaj%w$@r zyWI_}`3c{HpS*detm=hAQqHQB)JMPZ{EnrL&oDZT-pds`t0~!gbw5bYC_r`M>87jZ z2Ygs!irL>j?RZ$?!NQMfrjlH^Fy9q!-1 zUAYmMo1%3;Q($}OXh7udKW*)e_gPP_dbkXT`?LDW5qf*}F(Fi;s4XP}V+Gkz8*R+> zi7s)W)v#Le&KpT8S{{9$d|0Ypo@ppYq7Z-jv&Z`dR_J&C-Ko(L9P9SBEUvl175hi5 Nt7Uk-P!sp~{{S&bvCsej literal 7489 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 ccJxaTWG!3dl~5wv*h{=LDf6i)^Oh}3B1-lp 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 W`e5@R3Blx!K) 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 literal 7497 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 diff --git a/dist/icons/overlay/controller_single_joycon_right_dark.png b/dist/icons/overlay/controller_single_joycon_right_dark.png index afa80e6ef0f54acd62711c65abab9b9e7ad204a9..81cb94a1dcbb2ead1ad8c5049cdc08e24e564621 100644 GIT binary patch literal 3406 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 bBLq5>mpCCPtNrJre_L_(u6FguLGph9G1w%t literal 6729 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 ldcWMtNc(@e`la^d6613Y`I`V4 diff --git a/dist/icons/overlay/osk_button_backspace.png b/dist/icons/overlay/osk_button_backspace.png index 4ad284720ded558efda48766f900bc4426e3cd63..b7dc33228250bc2af38d43d24d05c2c25cd73a2b 100644 GIT binary patch delta 1263 zcmVCEmkaxCG}RWF%OnckANy6U%_W+heyPsJ_)Bnp(R%b9}bm&zm{ zQ6Re|=lFTdPv!AIqCj?A&h_)~YYqnz1>#MJnGdJ;1ri0)JbWYT z4kQWWO7<+pV}GA3cLI_GawT)V;^9-jbwFS$ox>JYT^j_(EYEUf zy@lp!$-DWie39YPipS9jqWD0rwY^vInCo4ONIXsu2_8Wd zALy6%d_wW?BG)Vyk025}f+#+a>B5&J&nZAG9zi5{1bjLV(mdfnu4K-Y#edKI-Vq}8K%{xXfn0N5C5s;vy9D(^ zqn0ATkO)n88U3ncm_{cp^1qWXQ?4M_86{=dkPgqMHo0Hpax@t?So zJbymg?lk}L2>e}3h!_Uy!`uxJl(IDZzy3OfNnvfDmDWg^RBM}A`fNOt28 zuuF?=@71vYknH#iV0I82K|h!wh!V3r zt9cGk{n9?K39|#lYx8-nPfkSPBY2ue1L^Q&LG&KG=0p@e#Ho40fn14~sHKo!+UG(P zJ_f9LG?0#KCci=-cdg?Qg$cmjZAv3Woc-RYXgBMLi;AbH{g0pGXfvwk9bwSOzc zBMLjrB6;Ei0p(77(@(^c5sdFK7jTdMEcZVkl9LcmJ z`HPomKDS632r!dr{F~Kw?WBQ#Y-!f~OCgozk_G~@b;YPKIW1`*z-}!@9)8QlBn<>` zFY%NAof(`o5MYlLM4#1qCk+J1*=X2zOqZmAT#1)^fr^{Fx#qmH08%%sc(?!(caOHu z7eM0Ln{O9D;y%Fqq5$%yGKk+5K;D$c^N#|^8@o8O0>~S#%$Ws{H)eg#Er2``r#~9m Z<4=bU9t_=IpXdMp002ovPDHLkV1my#P;~$R literal 2919 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 RA&CG0002ovPDHLkV1hb2VV(d0 diff --git a/dist/icons/overlay/osk_button_backspace_dark.png b/dist/icons/overlay/osk_button_backspace_dark.png index 19ac8847e42b79bc89d6219359f942926b248980..542038bef3b1444d403506f69fc640786b6725f6 100644 GIT binary patch delta 1253 zcmV+o+skG;>4K%zk0lK+Z#&+FMBAW;+Y_b z6pto~3zT)VdnC_ksE@@Xi8PNUiU(Bf#>W(o9RWn-(SJmmM-#;X%KGs68t`Z$&7+CJ zfvg{27Cisij?sBEk>=4v;Xsw4d|UCjZKCsNBF&?T!hzbx@FT(VkFAdg;pL*|54Yt1 zUw+sP>e-wt2Ow75r=s{zTNwcn?*aKP3V3EXP&O04R-yWVIpcVO#xA-sTfP{en zuIs-zbf3BrK(Ze(0C3xg>US&sfkeNgr^XHv)h}BQAkm-ssgXw#9?v=gXb;>;+$C3_Z6M$CSKAKIDej^@z>)l1?8a{hkwS0#0M7AS|BgIYf=Hn^4uNbAF~jql zn;#;D-g*YyBK*T!{G*#0I>kM_0KF9QHGeD6DemzXz}6QVL7!BIAxhkQ{5j79R9VMd zfBG7vDnH(wX;qTQfVl#>?( P00000NkvXXu0mjf)KO;VU>uUpy=TW+3)YCd zWks|lc6Mg(eRh3; zW+?6eLFnn}Ib?8f@O}VS#hv9~r{x3^LbManS^yWtoh1dZ;tt@4LZNUN5p6&!Phx19 z8Au3mI1#M}aCY2TQvOd%2xQy#kpMO^^BHkxN%sm$C5;Dordq)blYW*{NNd;re@ zI4`0&0hp;#W-l{esFd1jD%Zh=l#>1Y z|3ktG;s+tb*`DXE2XMGXnSXkox3s^%|7BCTQVvY)U*n~e7ZA}#=*jaofSy#I=!sPz zA;jeX9tY5&QRYq0^Uif$w`?d@iVtEFNJ{w`B3c8WEocp9-VI<;e}BI+7A(aVu?Qrk zydJ;<(EAMbr%WcZ$aURU4aG_cK;#11wtX{zyEVt|A08e)r(7<-ZYWktKq3{$w(Xml z`3IV1N{vQiaivmu(@3ZkEu#7wPeO=0nE7iuMVNVetya5W*REavHWe&Ik0@p!5<>ip zh_vJZuob|vxaBDn3bW!GTL{s#J26l%)Al6QAG4uloAyRg|=F)_9%dr02UL`(@Bvi6oIR4Ua?bt66Hb>$*1p z@J!?i$~f|jWr&$CQA%wHW9$j!8Ot_iUT$(}_q&;DbO!3~?moivygprdnE5u>b-#>c z%L9Zs6HP%LAOSOxF@f^={G3Lku@%5VojS~Xn^NjqhGGR~3i32DF3}X_X%eeMMg$T< z9P4@B769EEWtf@2-eU4JF(#2ud78u~k+wj&T<#Y#5{pDy0?nU4|CCH7vmHQ}Mwww|zSeP^yL7Ucf|;*}e|eg;6H%{i+e`gED1>O= zw{KrBg2~h53?hp32vlH!q?9?&^R@ywQDgieX1+`*^?*hVqhzz$s{uTrQ-YbF@G}!N ze}#%-4``#_$H7*`F#F#jC_SWUSsSUGZ&RoztN~+oKmS&&t|iiLQkS8 zFK3#|R+CVFPM8ee0tq2L;d$P30A_29`@Usa%au~=HEM)LR1&qiJb+JuY}-DIh&BPx z*-(6sh?bPgFG(tHNqx|Se_<{ zm1x|})ZX6SVP?J(Bd=Ks1QD$m92~q43A)T&mzd;fl1ParY}HUo4b|)QO91>)qyE)W z$~!flPYebd%3jiVK5-IF+UrxTR`(1I4J`%GuQA4rQp)dYJRdtITUxW(>|#W8WGD&_ z*eo_MFff?OWX=Qd7mabgCxrN_#`CdZx&c1wb7x^QrEbHWJ9qvaz!Cs&1s#Zper(&e z?uKoogmrpiI!QF;c2(DPUnQdR0K6M;EMn%LODR_cd_GdbK6*1^P2NS=Mp@ zL2ty$0C>>0?Xv%B?@%n z!f~A60=NRf+dyVA^JXb!F5vT_QLEK{1wDCcM6}XRKToMtsx$Lt%>1Orxa~yLE2aFn zMh)KtKCr_Q{PO6%&TBPloauSqx~{IStf5TWOhKa1PESlxqFCht%w9C^ zjovS~QKQC^j*gCpip64L-e*6FM4=zOIe6N5*C0pL0%t_5>%Pd$S778ah9Df~dESQ8PCIRZ$y_OVL?#dbD5W+K(KQHuk;5#@vYyH3^YaYG zO3@%vfdIg9oQIhCMvW3jd!E-Pg!rhjP$_|lTp$2YO5IIFw`i0&5kQ}m@)%Q@Qi2hS zKmg!4&aD7`6!du_It9Sy?(Xg*%%w{4OWgZSB&EC;z!zf9krI&DW}s0xm&;uT;PIGq zqy!}H83@409*<>NPcrkT;?9!dkGN+b03!|M?d|Os19&0sEGhm-#0=D=P$V zM3)0-#GNH&e-d;4YBJt==bh@DIdgUcSb>Buy9XQp1zs>)iA2UVssI2007*qoM6N<$ Ef|*-odH?_b diff --git a/dist/qt_themes/colorful/icons/48x48/bad_folder.png b/dist/qt_themes/colorful/icons/48x48/bad_folder.png index a7ab7a1f635d6c2aaee7ce3490ecbf0082d34bba..34069c6b230de579d436fa9f0c21ae37af905b2e 100644 GIT binary patch delta 503 zcmVMLN`2`L5i1E*bZ0xZA+EWiRRzyd75 z0+!u4eEZa7(PBUW_oJiOcj^3O-H(qLOCa)pFZSO!HzS~4t7FCya6CA!z)2mZY~eTSYnn^Nw}$ z>v}KiDEQb+U3CvQ9-g=)N*xLEKM|z&|4-Fq68}Xh(|h^CH3}8`9|*OzB>g7(FJHJ? tG64xtEsHs}s+0m{xeJ{8Cpa7q$popE`M?eo;|+1` literal 15494 zcmeI3e{2+G8po%F0G9AWW_E!ci(Q` zG*RO}?`C&)pZEJd&olFR-{;x+XP#(WS~sQQ)(VQErZm+1n&3BUd?rnR#}(y+kHfDU z;`J*vikkYa@hRJW@6=fo<+`G@1oVJ^i6E&_izurhU`a*ea5hD`=cVGJ6b8B}1lpBY zt@-NPN6jWht~IZ4_*sA43p$kgo&;#_S=u7?ge8}3p697>rvw-v3Utwwibi6Zkg7GO z;|lP%G0d1v=_z`+*1W(-XbSinOp-L?nG`vc!N0h=Q1|K|ijfU%W%rRjorEC1qY7H8EThzdxV5%FW~VA-M|nVCoozbY$j-6djJYXojsjG~!n3|K$1sEcEYQaraPjw}gn zY9cD?9wjQa11278cQb{-qM7+!C3w|{nt-PTJT^B|YPiTNz0-t-n5K&{2{iaTaKNG{ zvLHLe5HE!!TCzzlT9oY^?X&}UkQ^e*+hnVYv!@~Y{l)GhLi<#y)2J30I@4HWRf6FQ zr)34K!)lY-Xcx=1(HsYyvOKNEGh8yeot82<$kpQ0Q!-qRfQhk%3qI#+*YU5)R zHTWI;zD!Y_@Tt-MAVmo~C~Btq?3(xPr>OE74ZZ~}ssCI#yDWSre#^vb7k6Ksf4C(% z{lix!_M=6WCpY)qwC&u$k>h=j4pd#5b$b4Q=~VB{T{|xSE$0QTRZ!` zci(lx=O4W?V{?Eyc&Xy^|KCNw$eVA!wr%yd{=v7VH&F9Vl{;16(Bju_zBXijy{WwQ z#Kx-jTUXPzgLfbKV%Ld-y=5;xNG+-U>eCzR>q2uMJXAR?P<>(lx{V#nuDm-gaqZvF zPhwBZp7wWT&{S@s^6&3v9ozOs&6@Z7*4@TVo^*@*P}!>G!F?4Iwj7u_`IA|_mrgp$ z=d1h6&hMBz_1OBx$^Y2$DIb21N*&l63@;iV3|Ac9^XH*)&;OyPap%Nw*VorLlfUdA zoG=veKi+ljeDag~&U?d0A2>5OKEC(Ka}U{GezWc!%jQFCyy3UMZa?-y|79xj!8i8z zKf9;z6n~gmwRPZs(sugn(-U7?zIu;~ud9s3g4YL|rDf1R^*r*x+E z7qd64+j4v$`S9V-_;7IFo6{B_dhW_sJ#};5s5%;4+|^vUYwf>gbluU{)Vuxx_klZZ z^vU5T*EW4HPO6;vw=bQ)-&(zU-^sq)4xTuEa!TsItE8*GWxL+#tvuGcecIj)`X5*R s^QP0D`*wfPI<#j7RW|A3*VLll%v>_i@YJ7-CxsgpF7@sIb@0*e0i}o=LjV8( diff --git a/dist/qt_themes/default/icons/256x256/plus_folder.png b/dist/qt_themes/default/icons/256x256/plus_folder.png index 3a49669a3ae3f0ea46ebd5ddd0e51f2b31b37d47..f44c80c3ae25ce0c12c83d13a1ae84efc81d4018 100644 GIT binary patch literal 1948 zcma)-eKZsLAIHD5nN5aU-9;WUY$P#{Ym?Uzu!5(^E>B#&inOwpZ8zye?IvXKW~)!Ms)xH zinPlk0077;LI47`YG2aBX8=%(Bzd?~Q{*$RpiC?YcqtZz=o>c=z5GXg`t?63-4fe! zbFgeWf%WwJvdQ=(-W<2)YQ8C{o& zg%KN&u{8KkWTW87Pi~m!JOZ~0vCs!|#E3H1>6G~+t=_=;aI0LIErC@esf7Lra1lT< zUL{8U2vAuo{Vtqwgu!s|aBrP{@QutN5^`F0_o6&8;gQ@@I?pWGjd-lrrD{pw+McyA z<{Ydy^Jk#r2UV2syE+EOj$OiLBbl@NQp?+UMdpXKjail`u+IF!iDa{DftUI1h+kZE z0GS_^&O^mf2*W1qiNGM7b_(}mY%2?y`xRqy99f!HVTYN)XAGU`*d|ya^VQ;K8|kM- z3jerlBsC(&R(~b-X`bGWviEq7Ot&ga$msHIAeU8&^~lmUa?B{)?D9VH$U6Glq-j0I zVG~O986~Y8NtT8=+mA}G-v6l5Xy=%v5EiO@dM;7SYKk|(E>9S;k=Kh^VA*K0504J-a*hl;+5T^(q55GJg8L zGrVw>)<4d=*HGgH&Kl3&7W3+0{OI$?Hl(;#17=+B&dzr*eq8Ocx&Cu7$Vloc3+V!b zP5W29IX^(D)6G2y-le$U*necf?0F{^?!L6QC%M~=`WTkR0F&k&tS2iKEB;v@=;>aU z-V+rDpKb4vQb{)wg-;N~kLg`4QvOemgBk}HJ|)X2qj0LNQ2wtP0Y>E&w! zprWFIzR*eh{r&s!Yj?tGiji5!4`ydL~0$sKS0scSF>V;8?|7YEM!LB`Z zmR?&w2CJplJim6mDjn2JvbJo64=~OI&6E3Cl|#V!5_WX9Vhaa7s!LFjwY@$Fl+UoH z!XufdDz`gmVpLBCenlBaSoUaK2;YacVe4EgDcFNDl4e|pph5F&ey*#OgNQ;e3okpi z4wTdlpm|Ewy7@s6ac7qW&7Q5pDlTwYt+!~HszTdNn=fl7+iVt9T5A~fapw5%(-^z@ z`jK-=F<0UZ9SfCUs4p-fZ=17S4zl3uEJTv&55+ds3MzUm`s!cN=rO8R;iUfWc$X<9 zsHeJb(GnUqf6lYkc$cu%veQrhJ|p)lL@?r(Hy^ASWmYcejvVWma3e}yRmhqczNHRO z{Qt4szjrOkIM=WYFDgk~|MV3Im{s>|PXL=!^J)zp*`=;MF@^q%nbPwfpN7S=&TLA! z8pY?XtLtm?407y*TQt>JhtO6}*dj29k5*mfg&F@?*%z~H2o=l8TDBV4$XGz}1A^vH zzEO3f|0uK+_ChkgZRnUc5Z^Bf11ISl0O5G96Z=%#_uHYp5KA)D=r+*I%`|aKfKlB{ zJVPF|PtDbeloVc9See_AZ0NV*!?2-@vmgV5Y)oyzM`0m??Zw;f;=2m$5-iYGG0_Ua4T1^8k)8-V#duMR9 zXIl;?XR=ymU5;TUze(1MNnXErYbC>*;oPc336Ad{ZFR^bC;vJVxp7IVgjGB}tzbzn z|4S0x3?Yj*F|neAqv$MK)#FI3Q>ci5<$JoYif+Gx6oLfX?61r1Xu?Ra9W4v{9TJU1 z7g$?YVl`fyh@!qiB4@4tI$$Khwz>EEyCjhGB-mC*$NZF^>vz)^DdIR-#gN}6ekRh2 zXW-4fceM7Yf2c})q?U2LpH}v@xm!)PT~#?UM8l2rg3XhB#r7I^Sa2I^bnl6af#l9= zSa97AF_~Graq9C%$({)7W-ABh(CV_(f)Stg4S(jqR|HN@)z*jx=9Dzo(vvF)-y8In zN;B_{gg0;}oOt89<$}b7@)%a%IE|*ryp<|@O!;-otCVvUqLYVTaL0Al{MS!3CGi%} Yh89WFHBF|yt6vw8JpDWxh~e3P1CC-@r2qf` literal 3521 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 literal 6751 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

  • JPa8m5Yr$xgK)@(1w5gf%#jdc)j!Uh#5W{-0HM=WA9{1)v82HbQ}Zd7D7an zFFNNvRcHXqZkP`z6(H5_RTFhL2#Qn+DKz_#5d|hb??GwLq7-<-(oEe;efzPO)T1Mk z+WHh_uJ8=>YqZ+!?1t9%Y*4KL)zJ&2Y2PuD(?_b268W`9ki6WsC>i%xM50cOKSD4) zQ5RMyvVPV-?9q>SGz+KEHjD)$??TCU74LPA3HKjsVCz~R??yuPOE+|cz0VHO0pP&&- z=FEw7EM?FBK_C6tJg(P@QG(P#raZ#;P5grlzQtKfLf8cOj?QP&l+5SVwlibZ-96Y& zgl;Bc=!XzC+AZ5hdZN+84h67KpRI2KWqyobqYTD5ob#oM?gPGC={C1d@0A$bzEML% z;2MJG(>kbwFR6wMAYB(xxdk$V5x8#wm>!8pJN33b^^nzhy=uWiZKVJ+%O~euiG^>& z%S0+GA8CAe)b#eUTvoAuN!NmwMZ$tX3#@o>T#+V6RV2iP?r47J=TiGw*`3jOlTH2B zi|)8caXFggoNLd^({C{ORurNVSY=ddUG28-(=R2w?YI*Yi z`g6JMgi7gk*?KmY==1+Xy`IADxY(wko&Xcxs44q2e$Ja)Q>7QkUjyyr)hY6!sc_NO zUFuoQ!xcToxhTV6H5O`H6H-gSH$iVU(#Ay`f(-98s535#-Cc!|xG`rj?LQ3auC&m2 zi?&xWp*5$}{lWkC%}-7GxXn?JtpZxi?Tyiz-08H)(30&If2;D>C+#9(<` q24WP=5QAdb3a7{(wID43sz~k{y7DmLP6+MC5ny@V*0k0b6aRl+8gWDb diff --git a/dist/qt_themes/qdarkstyle/icons/256x256/plus_folder.png b/dist/qt_themes/qdarkstyle/icons/256x256/plus_folder.png index 002101114d150e304765a3466be9bf267ae96382..14c90fea54ea60d0e2eb024cf5bcca81a366a93f 100644 GIT binary patch literal 1924 zcmb`Hc{JPU8pq!sGLl>bO%ZJc4Wi=K76~$%(ummCcGBuJ*0$4DscJG+Gof}VN~*E6 zwl1SFmP;$0nn6@>Ek%tgVk~oODasf|tGLM@_s{#+z285c&-eMh=e*B3?>X<2?&C#Q zMd~5}0ICevV}1ZYG7AB?k_;PhtR(=H-5JM@25?3frHcGeh7Nu8@V%NEv$%8f5_!W6`ML-MdQ+D4C0#gVnC2wcWNh)*pa!N_g0 zY2G2D7EXh#QnMTwcQsk=IH{6O(T?j+h$tvGQo(}`W00V~A071Mnr>>ztQSpYDciTN z?qTgMBqqHeXXaVk{&bSx(z>5tSx0iHHR!geeEJ1xvZjYfaOty3!^{~*maH1i);rSH z==-oU?=2&2QEsK}-!zFKk4Q%W>Ws@=2 zLUSP>G<73~@t>xv^DND~`Cxp!-xjLyYm?^Z$Gc7X4>#_%6^q_`w>p(7O;TLycEzJ2 zhi-A1(*01UE%wEP7esTM+hqKsy6@YnMMcBna;576=?+GDDzc&F184ben8NFkBUAWd zW?BQ9^8wokBiomO20OCbD0Ge%<+T9e6V?aSsAh@Y0>Tn#K#NS*0l($YdF9f*tq#BM zZ7JC^hchq2bB_e;e#*%ub(wOHly!djAnX8ag~aEi28=%jJCI%e6ybg_JaGDWxF1aY zn+JPp_2d3fCl=P7nS|ihR}^D!{H%Tyi2tb~_P(=f2&W3N95p^oDMaaiy=opM0&G1dx)O(q;pYoA57 zC}K4-v(a4TNCT}#JU}fv5w0BvdIG`a^wvg?D$o>VXDJ$|m~zuUCiTZl!c3J**Ah<6 zU5)Z5C*~X|GfOO#I0nFdljE;?vfOLQmo*DX%%BsnMirPF#pRBAz_&b2*lDeaD`;lA z)Q*>TkFH#8PjUR0#kI3W8N<3rYGaD&|MT@AJU1>n*4snIeNnN_aym!Hy2@!3{WMHY z%69ZXNz=DJpsq?uji`*&2Rs_-*21ficT`9z;nyDq(sYFdiZFE*we;|yWNdre(%T0;FQF?&0WBHLAn2qGp}aP#phG0Aod^Jc0>yiTAMbw$zk zxyL95em))E9AZ>RAa!cQa_5xydNj()2g| z`OW;U#aALFM#smE6Y|CCTu{06fvnAFo7Xcz<8-$%9XpsJgVnMA9aXzX`t-HTZEZ;f zR;_vZs7wcO=VRv&8Lcn0J&ec%U6Stky2*D{R9TJ7Eys1$jDePbL&_0HS-QC-_I_EKiO$qU{vF zlPMgK6gH)WjlI9fn7EEwi&49ov-nYPftgUe?&uN`+czD%(3y5(me|J%ba^&)3&Fe- zFG{0ximo|6O8N5_UF_?=B1#W!vGyP-jD19K4e&=u3bIj!KKN2AL3sCM=O*T%R NFlb)Ks-0Mg{|0pPAie+q literal 3931 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 From 282cd3e5fe1ffe01d1ab008ef6b2a15266708d24 Mon Sep 17 00:00:00 2001 From: Liam Date: Mon, 17 Oct 2022 00:01:50 -0400 Subject: [PATCH 10/91] kernel: fix slab heap ABA --- src/core/hle/kernel/k_slab_heap.h | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/core/hle/kernel/k_slab_heap.h b/src/core/hle/kernel/k_slab_heap.h index 2b303537e6..a8c77a7d48 100644 --- a/src/core/hle/kernel/k_slab_heap.h +++ b/src/core/hle/kernel/k_slab_heap.h @@ -8,6 +8,7 @@ #include "common/assert.h" #include "common/common_funcs.h" #include "common/common_types.h" +#include "common/spin_lock.h" namespace Kernel { @@ -36,28 +37,34 @@ public: } void* Allocate() { - Node* ret = m_head.load(); + // KScopedInterruptDisable di; - do { - if (ret == nullptr) { - break; - } - } while (!m_head.compare_exchange_weak(ret, ret->next)); + m_lock.lock(); + Node* ret = m_head; + if (ret != nullptr) [[likely]] { + m_head = ret->next; + } + + m_lock.unlock(); return ret; } void Free(void* obj) { - Node* node = static_cast(obj); + // KScopedInterruptDisable di; - Node* cur_head = m_head.load(); - do { - node->next = cur_head; - } while (!m_head.compare_exchange_weak(cur_head, node)); + m_lock.lock(); + + Node* node = static_cast(obj); + node->next = m_head; + m_head = node; + + m_lock.unlock(); } private: std::atomic m_head{}; + Common::SpinLock m_lock; }; } // namespace impl From 5000d814afeefd7eb7f250888b76d6723790f762 Mon Sep 17 00:00:00 2001 From: Lioncash Date: Tue, 18 Oct 2022 12:54:53 -0400 Subject: [PATCH 11/91] fixed_point: Use variable templates and concepts where applicable Makes a few things a little less noisy and removes the need for SFINAE in quite a few functions. --- src/common/concepts.h | 8 +++ src/common/fixed_point.h | 120 ++++++++++++++++----------------------- 2 files changed, 56 insertions(+), 72 deletions(-) diff --git a/src/common/concepts.h b/src/common/concepts.h index a97555f6af..e8ce30dfed 100644 --- a/src/common/concepts.h +++ b/src/common/concepts.h @@ -34,4 +34,12 @@ concept DerivedFrom = requires { template concept ConvertibleTo = std::is_convertible_v; +// No equivalents in the stdlib + +template +concept IsArithmetic = std::is_arithmetic_v; + +template +concept IsIntegral = std::is_integral_v; + } // namespace Common diff --git a/src/common/fixed_point.h b/src/common/fixed_point.h index 6eb6afe2f5..b2e4898416 100644 --- a/src/common/fixed_point.h +++ b/src/common/fixed_point.h @@ -12,6 +12,8 @@ #include #include +#include + namespace Common { template @@ -50,8 +52,8 @@ struct type_from_size<64> { static constexpr size_t size = 64; using value_type = int64_t; - using unsigned_type = std::make_unsigned::type; - using signed_type = std::make_signed::type; + using unsigned_type = std::make_unsigned_t; + using signed_type = std::make_signed_t; using next_size = type_from_size<128>; }; @@ -61,8 +63,8 @@ struct type_from_size<32> { static constexpr size_t size = 32; using value_type = int32_t; - using unsigned_type = std::make_unsigned::type; - using signed_type = std::make_signed::type; + using unsigned_type = std::make_unsigned_t; + using signed_type = std::make_signed_t; using next_size = type_from_size<64>; }; @@ -72,8 +74,8 @@ struct type_from_size<16> { static constexpr size_t size = 16; using value_type = int16_t; - using unsigned_type = std::make_unsigned::type; - using signed_type = std::make_signed::type; + using unsigned_type = std::make_unsigned_t; + using signed_type = std::make_signed_t; using next_size = type_from_size<32>; }; @@ -83,8 +85,8 @@ struct type_from_size<8> { static constexpr size_t size = 8; using value_type = int8_t; - using unsigned_type = std::make_unsigned::type; - using signed_type = std::make_signed::type; + using unsigned_type = std::make_unsigned_t; + using signed_type = std::make_signed_t; using next_size = type_from_size<16>; }; @@ -101,7 +103,7 @@ struct divide_by_zero : std::exception {}; template constexpr FixedPoint divide( FixedPoint numerator, FixedPoint denominator, FixedPoint& remainder, - typename std::enable_if::next_size::is_specialized>::type* = nullptr) { + std::enable_if_t::next_size::is_specialized>* = nullptr) { using next_type = typename FixedPoint::next_type; using base_type = typename FixedPoint::base_type; @@ -121,7 +123,7 @@ constexpr FixedPoint divide( template constexpr FixedPoint divide( FixedPoint numerator, FixedPoint denominator, FixedPoint& remainder, - typename std::enable_if::next_size::is_specialized>::type* = nullptr) { + std::enable_if_t::next_size::is_specialized>* = nullptr) { using unsigned_type = typename FixedPoint::unsigned_type; @@ -191,7 +193,7 @@ constexpr FixedPoint divide( template constexpr FixedPoint multiply( FixedPoint lhs, FixedPoint rhs, - typename std::enable_if::next_size::is_specialized>::type* = nullptr) { + std::enable_if_t::next_size::is_specialized>* = nullptr) { using next_type = typename FixedPoint::next_type; using base_type = typename FixedPoint::base_type; @@ -210,7 +212,7 @@ constexpr FixedPoint multiply( template constexpr FixedPoint multiply( FixedPoint lhs, FixedPoint rhs, - typename std::enable_if::next_size::is_specialized>::type* = nullptr) { + std::enable_if_t::next_size::is_specialized>* = nullptr) { using base_type = typename FixedPoint::base_type; @@ -270,10 +272,8 @@ public: // constructors FixedPoint(FixedPoint&&) = default; FixedPoint& operator=(const FixedPoint&) = default; - template - constexpr FixedPoint( - Number n, typename std::enable_if::value>::type* = nullptr) - : data_(static_cast(n * one)) {} + template + constexpr FixedPoint(Number n) : data_(static_cast(n * one)) {} public: // conversion template @@ -411,15 +411,13 @@ public: // binary math operators, effects underlying bit pattern since these return *this; } - template ::value>::type> + template constexpr FixedPoint& operator>>=(Integer n) { data_ >>= n; return *this; } - template ::value>::type> + template constexpr FixedPoint& operator<<=(Integer n) { data_ <<= n; return *this; @@ -490,10 +488,10 @@ public: // if we have the same fractional portion, but differing integer portions, we trivially upgrade the // smaller type template -constexpr typename std::conditional= I2, FixedPoint, FixedPoint>::type operator+( +constexpr std::conditional_t= I2, FixedPoint, FixedPoint> operator+( FixedPoint lhs, FixedPoint rhs) { - using T = typename std::conditional= I2, FixedPoint, FixedPoint>::type; + using T = std::conditional_t= I2, FixedPoint, FixedPoint>; const T l = T::from_base(lhs.to_raw()); const T r = T::from_base(rhs.to_raw()); @@ -501,10 +499,10 @@ constexpr typename std::conditional= I2, FixedPoint, FixedPoint -constexpr typename std::conditional= I2, FixedPoint, FixedPoint>::type operator-( +constexpr std::conditional_t= I2, FixedPoint, FixedPoint> operator-( FixedPoint lhs, FixedPoint rhs) { - using T = typename std::conditional= I2, FixedPoint, FixedPoint>::type; + using T = std::conditional_t= I2, FixedPoint, FixedPoint>; const T l = T::from_base(lhs.to_raw()); const T r = T::from_base(rhs.to_raw()); @@ -512,10 +510,10 @@ constexpr typename std::conditional= I2, FixedPoint, FixedPoint -constexpr typename std::conditional= I2, FixedPoint, FixedPoint>::type operator*( +constexpr std::conditional_t= I2, FixedPoint, FixedPoint> operator*( FixedPoint lhs, FixedPoint rhs) { - using T = typename std::conditional= I2, FixedPoint, FixedPoint>::type; + using T = std::conditional_t= I2, FixedPoint, FixedPoint>; const T l = T::from_base(lhs.to_raw()); const T r = T::from_base(rhs.to_raw()); @@ -523,10 +521,10 @@ constexpr typename std::conditional= I2, FixedPoint, FixedPoint -constexpr typename std::conditional= I2, FixedPoint, FixedPoint>::type operator/( +constexpr std::conditional_t= I2, FixedPoint, FixedPoint> operator/( FixedPoint lhs, FixedPoint rhs) { - using T = typename std::conditional= I2, FixedPoint, FixedPoint>::type; + using T = std::conditional_t= I2, FixedPoint, FixedPoint>; const T l = T::from_base(lhs.to_raw()); const T r = T::from_base(rhs.to_raw()); @@ -561,54 +559,46 @@ constexpr FixedPoint operator/(FixedPoint lhs, FixedPoint rhs) return lhs; } -template ::value>::type> +template constexpr FixedPoint operator+(FixedPoint lhs, Number rhs) { lhs += FixedPoint(rhs); return lhs; } -template ::value>::type> +template constexpr FixedPoint operator-(FixedPoint lhs, Number rhs) { lhs -= FixedPoint(rhs); return lhs; } -template ::value>::type> +template constexpr FixedPoint operator*(FixedPoint lhs, Number rhs) { lhs *= FixedPoint(rhs); return lhs; } -template ::value>::type> +template constexpr FixedPoint operator/(FixedPoint lhs, Number rhs) { lhs /= FixedPoint(rhs); return lhs; } -template ::value>::type> +template constexpr FixedPoint operator+(Number lhs, FixedPoint rhs) { FixedPoint tmp(lhs); tmp += rhs; return tmp; } -template ::value>::type> +template constexpr FixedPoint operator-(Number lhs, FixedPoint rhs) { FixedPoint tmp(lhs); tmp -= rhs; return tmp; } -template ::value>::type> +template constexpr FixedPoint operator*(Number lhs, FixedPoint rhs) { FixedPoint tmp(lhs); tmp *= rhs; return tmp; } -template ::value>::type> +template constexpr FixedPoint operator/(Number lhs, FixedPoint rhs) { FixedPoint tmp(lhs); tmp /= rhs; @@ -616,78 +606,64 @@ constexpr FixedPoint operator/(Number lhs, FixedPoint rhs) { } // shift operators -template ::value>::type> +template constexpr FixedPoint operator<<(FixedPoint lhs, Integer rhs) { lhs <<= rhs; return lhs; } -template ::value>::type> +template constexpr FixedPoint operator>>(FixedPoint lhs, Integer rhs) { lhs >>= rhs; return lhs; } // comparison operators -template ::value>::type> +template constexpr bool operator>(FixedPoint lhs, Number rhs) { return lhs > FixedPoint(rhs); } -template ::value>::type> +template constexpr bool operator<(FixedPoint lhs, Number rhs) { return lhs < FixedPoint(rhs); } -template ::value>::type> +template constexpr bool operator>=(FixedPoint lhs, Number rhs) { return lhs >= FixedPoint(rhs); } -template ::value>::type> +template constexpr bool operator<=(FixedPoint lhs, Number rhs) { return lhs <= FixedPoint(rhs); } -template ::value>::type> +template constexpr bool operator==(FixedPoint lhs, Number rhs) { return lhs == FixedPoint(rhs); } -template ::value>::type> +template constexpr bool operator!=(FixedPoint lhs, Number rhs) { return lhs != FixedPoint(rhs); } -template ::value>::type> +template constexpr bool operator>(Number lhs, FixedPoint rhs) { return FixedPoint(lhs) > rhs; } -template ::value>::type> +template constexpr bool operator<(Number lhs, FixedPoint rhs) { return FixedPoint(lhs) < rhs; } -template ::value>::type> +template constexpr bool operator>=(Number lhs, FixedPoint rhs) { return FixedPoint(lhs) >= rhs; } -template ::value>::type> +template constexpr bool operator<=(Number lhs, FixedPoint rhs) { return FixedPoint(lhs) <= rhs; } -template ::value>::type> +template constexpr bool operator==(Number lhs, FixedPoint rhs) { return FixedPoint(lhs) == rhs; } -template ::value>::type> +template constexpr bool operator!=(Number lhs, FixedPoint rhs) { return FixedPoint(lhs) != rhs; } From 9393f90ccfd3131a021a1c5f3e42f20a287a6560 Mon Sep 17 00:00:00 2001 From: Lioncash Date: Tue, 18 Oct 2022 13:01:36 -0400 Subject: [PATCH 12/91] fixed_point: Use defaulted comparisons Collapses all of the comparison functions down to a single line. --- src/common/fixed_point.h | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/src/common/fixed_point.h b/src/common/fixed_point.h index b2e4898416..7f00dd7b59 100644 --- a/src/common/fixed_point.h +++ b/src/common/fixed_point.h @@ -301,29 +301,7 @@ public: } public: // comparison operators - constexpr bool operator==(FixedPoint rhs) const { - return data_ == rhs.data_; - } - - constexpr bool operator!=(FixedPoint rhs) const { - return data_ != rhs.data_; - } - - constexpr bool operator<(FixedPoint rhs) const { - return data_ < rhs.data_; - } - - constexpr bool operator>(FixedPoint rhs) const { - return data_ > rhs.data_; - } - - constexpr bool operator<=(FixedPoint rhs) const { - return data_ <= rhs.data_; - } - - constexpr bool operator>=(FixedPoint rhs) const { - return data_ >= rhs.data_; - } + friend constexpr auto operator<=>(FixedPoint lhs, FixedPoint rhs) = default; public: // unary operators constexpr bool operator!() const { From 0101ef9fb115e59f1b9b7a28890104b115eb184d Mon Sep 17 00:00:00 2001 From: Lioncash Date: Tue, 18 Oct 2022 13:05:08 -0400 Subject: [PATCH 13/91] fixed_point: Make to_uint() non-const This calls round_up(), which is a non-const member function, so if a fixed-point instantiation ever calls to_uint(), it'll result in a compiler error. This allows the member function to work. While we're at it, we can actually mark to_long_floor() as const, since it's not modifying any member state. --- src/common/fixed_point.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/fixed_point.h b/src/common/fixed_point.h index 7f00dd7b59..116dfbb33c 100644 --- a/src/common/fixed_point.h +++ b/src/common/fixed_point.h @@ -411,7 +411,7 @@ public: // conversion to basic types return static_cast((data_ & integer_mask) >> fractional_bits); } - constexpr unsigned int to_uint() const { + constexpr unsigned int to_uint() { round_up(); return static_cast((data_ & integer_mask) >> fractional_bits); } @@ -425,7 +425,7 @@ public: // conversion to basic types return static_cast((data_ & integer_mask) >> fractional_bits); } - constexpr int64_t to_long_floor() { + constexpr int64_t to_long_floor() const { return static_cast((data_ & integer_mask) >> fractional_bits); } From 2cc9d94060bbf4a6395ea1ebaa06ce65bb8fc07d Mon Sep 17 00:00:00 2001 From: Lioncash Date: Tue, 18 Oct 2022 13:10:33 -0400 Subject: [PATCH 14/91] fixed_point: Mark relevant member function [[nodiscard]] Marks member functions as discard, where ignoring the return value would be indicative of a bug or dead code. --- src/common/fixed_point.h | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/common/fixed_point.h b/src/common/fixed_point.h index 116dfbb33c..cbe76fbf1f 100644 --- a/src/common/fixed_point.h +++ b/src/common/fixed_point.h @@ -304,11 +304,11 @@ public: // comparison operators friend constexpr auto operator<=>(FixedPoint lhs, FixedPoint rhs) = default; public: // unary operators - constexpr bool operator!() const { + [[nodiscard]] constexpr bool operator!() const { return !data_; } - constexpr FixedPoint operator~() const { + [[nodiscard]] constexpr FixedPoint operator~() const { // NOTE(eteran): this will often appear to "just negate" the value // that is not an error, it is because -x == (~x+1) // and that "+1" is adding an infinitesimally small fraction to the @@ -316,11 +316,11 @@ public: // unary operators return FixedPoint::from_base(~data_); } - constexpr FixedPoint operator-() const { + [[nodiscard]] constexpr FixedPoint operator-() const { return FixedPoint::from_base(-data_); } - constexpr FixedPoint operator+() const { + [[nodiscard]] constexpr FixedPoint operator+() const { return FixedPoint::from_base(+data_); } @@ -406,42 +406,42 @@ public: // conversion to basic types data_ += (data_ & fractional_mask) >> 1; } - constexpr int to_int() { + [[nodiscard]] constexpr int to_int() { round_up(); return static_cast((data_ & integer_mask) >> fractional_bits); } - constexpr unsigned int to_uint() { + [[nodiscard]] constexpr unsigned int to_uint() { round_up(); return static_cast((data_ & integer_mask) >> fractional_bits); } - constexpr int64_t to_long() { + [[nodiscard]] constexpr int64_t to_long() { round_up(); return static_cast((data_ & integer_mask) >> fractional_bits); } - constexpr int to_int_floor() const { + [[nodiscard]] constexpr int to_int_floor() const { return static_cast((data_ & integer_mask) >> fractional_bits); } - constexpr int64_t to_long_floor() const { + [[nodiscard]] constexpr int64_t to_long_floor() const { return static_cast((data_ & integer_mask) >> fractional_bits); } - constexpr unsigned int to_uint_floor() const { + [[nodiscard]] constexpr unsigned int to_uint_floor() const { return static_cast((data_ & integer_mask) >> fractional_bits); } - constexpr float to_float() const { + [[nodiscard]] constexpr float to_float() const { return static_cast(data_) / FixedPoint::one; } - constexpr double to_double() const { + [[nodiscard]] constexpr double to_double() const { return static_cast(data_) / FixedPoint::one; } - constexpr base_type to_raw() const { + [[nodiscard]] constexpr base_type to_raw() const { return data_; } @@ -449,7 +449,7 @@ public: // conversion to basic types data_ &= fractional_mask; } - constexpr base_type get_frac() const { + [[nodiscard]] constexpr base_type get_frac() const { return data_ & fractional_mask; } From 0cfd90004bf058a278ff1db3442b523e2bb1b0fa Mon Sep 17 00:00:00 2001 From: Lioncash Date: Tue, 18 Oct 2022 13:13:26 -0400 Subject: [PATCH 15/91] fixed_point: Mark std::swap and move constructor as noexcept These shouldn't throw and can influence how some standard algorithms will work. --- src/common/fixed_point.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/fixed_point.h b/src/common/fixed_point.h index cbe76fbf1f..ad0d71e502 100644 --- a/src/common/fixed_point.h +++ b/src/common/fixed_point.h @@ -269,7 +269,7 @@ public: public: // constructors FixedPoint() = default; FixedPoint(const FixedPoint&) = default; - FixedPoint(FixedPoint&&) = default; + FixedPoint(FixedPoint&&) noexcept = default; FixedPoint& operator=(const FixedPoint&) = default; template @@ -454,7 +454,7 @@ public: // conversion to basic types } public: - constexpr void swap(FixedPoint& rhs) { + constexpr void swap(FixedPoint& rhs) noexcept { using std::swap; swap(data_, rhs.data_); } From b6119a55f9bbc306593afccc0820165d74c2fadc Mon Sep 17 00:00:00 2001 From: Lioncash Date: Tue, 18 Oct 2022 15:33:47 -0400 Subject: [PATCH 16/91] fixed_point: Mark copy/move assignment operators and constructors as constexpr Given these are just moving a raw value around, these can sensibly be made constexpr to make the interface more useful. --- src/common/fixed_point.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/common/fixed_point.h b/src/common/fixed_point.h index ad0d71e502..5aafa273b0 100644 --- a/src/common/fixed_point.h +++ b/src/common/fixed_point.h @@ -268,9 +268,12 @@ public: public: // constructors FixedPoint() = default; - FixedPoint(const FixedPoint&) = default; - FixedPoint(FixedPoint&&) noexcept = default; - FixedPoint& operator=(const FixedPoint&) = default; + + constexpr FixedPoint(const FixedPoint&) = default; + constexpr FixedPoint& operator=(const FixedPoint&) = default; + + constexpr FixedPoint(FixedPoint&&) noexcept = default; + constexpr FixedPoint& operator=(FixedPoint&&) noexcept = default; template constexpr FixedPoint(Number n) : data_(static_cast(n * one)) {} From 6e1c6297a3f8fcd243ec6b31d3832d84c7df474c Mon Sep 17 00:00:00 2001 From: Lioncash Date: Tue, 18 Oct 2022 15:37:37 -0400 Subject: [PATCH 17/91] fixed_point: Mark default constructor as constexpr Ensures that a fixed-point value is always initialized This likely also fixes several cases of uninitialized values being operated on, since we have multiple areas in the codebase where the default constructor is being used like: Common::FixedPoint<50, 14> current_sample{}; and is then followed up with an arithmetic operation like += or something else, which operates directly on FixedPoint's internal data member, which would previously be uninitialized. --- src/common/fixed_point.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/fixed_point.h b/src/common/fixed_point.h index 5aafa273b0..f899b0d540 100644 --- a/src/common/fixed_point.h +++ b/src/common/fixed_point.h @@ -267,7 +267,7 @@ public: static constexpr base_type one = base_type(1) << fractional_bits; public: // constructors - FixedPoint() = default; + constexpr FixedPoint() = default; constexpr FixedPoint(const FixedPoint&) = default; constexpr FixedPoint& operator=(const FixedPoint&) = default; @@ -463,7 +463,7 @@ public: } public: - base_type data_; + base_type data_{}; }; // if we have the same fractional portion, but differing integer portions, we trivially upgrade the From e63a5459e361d8f33fb9045bc9684d516d087b0c Mon Sep 17 00:00:00 2001 From: bunnei Date: Mon, 5 Sep 2022 17:27:48 -0700 Subject: [PATCH 18/91] core: hle: kernel: svc_common: Add WaitInfinite & cleanup. --- src/core/hle/kernel/svc_common.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/core/hle/kernel/svc_common.h b/src/core/hle/kernel/svc_common.h index 95750c3ebe..85506710ef 100644 --- a/src/core/hle/kernel/svc_common.h +++ b/src/core/hle/kernel/svc_common.h @@ -14,8 +14,11 @@ namespace Kernel::Svc { using namespace Common::Literals; -constexpr s32 ArgumentHandleCountMax = 0x40; -constexpr u32 HandleWaitMask{1u << 30}; +constexpr inline s32 ArgumentHandleCountMax = 0x40; + +constexpr inline u32 HandleWaitMask = 1u << 30; + +constexpr inline s64 WaitInfinite = -1; constexpr inline std::size_t HeapSizeAlignment = 2_MiB; From cb073f95dc6ef07934a47137b121a1328667df6b Mon Sep 17 00:00:00 2001 From: bunnei Date: Mon, 5 Sep 2022 17:29:14 -0700 Subject: [PATCH 19/91] core: hle: result: Add GetInnerValue and Includes methods. --- src/core/hle/result.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/core/hle/result.h b/src/core/hle/result.h index d67e68bae3..d714dea38e 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -135,6 +135,14 @@ union Result { [[nodiscard]] constexpr bool IsFailure() const { return !IsSuccess(); } + + [[nodiscard]] constexpr u32 GetInnerValue() const { + return static_cast(module.Value()) | (description << module.bits); + } + + [[nodiscard]] constexpr bool Includes(Result result) const { + return GetInnerValue() == result.GetInnerValue(); + } }; static_assert(std::is_trivial_v); From 47b8160666da8dcb679bb7cabe35a615a1786155 Mon Sep 17 00:00:00 2001 From: bunnei Date: Mon, 5 Sep 2022 17:42:24 -0700 Subject: [PATCH 20/91] core: device_memory: Templatize GetPointer(..). --- src/core/device_memory.h | 10 ++++++---- src/core/hle/kernel/init/init_slab_setup.cpp | 6 +++--- src/core/hle/kernel/k_code_memory.cpp | 2 +- src/core/hle/kernel/k_memory_manager.cpp | 2 +- src/core/hle/kernel/k_page_buffer.cpp | 2 +- src/core/hle/kernel/k_page_table.cpp | 6 +++--- src/core/hle/kernel/k_shared_memory.cpp | 2 +- src/core/hle/kernel/k_shared_memory.h | 4 ++-- src/core/memory.cpp | 6 +++--- 9 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/core/device_memory.h b/src/core/device_memory.h index df61b0c0b6..90510733c8 100644 --- a/src/core/device_memory.h +++ b/src/core/device_memory.h @@ -31,12 +31,14 @@ public: DramMemoryMap::Base; } - u8* GetPointer(PAddr addr) { - return buffer.BackingBasePointer() + (addr - DramMemoryMap::Base); + template + T* GetPointer(PAddr addr) { + return reinterpret_cast(buffer.BackingBasePointer() + (addr - DramMemoryMap::Base)); } - const u8* GetPointer(PAddr addr) const { - return buffer.BackingBasePointer() + (addr - DramMemoryMap::Base); + template + const T* GetPointer(PAddr addr) const { + return reinterpret_cast(buffer.BackingBasePointer() + (addr - DramMemoryMap::Base)); } Common::HostMemory buffer; diff --git a/src/core/hle/kernel/init/init_slab_setup.cpp b/src/core/hle/kernel/init/init_slab_setup.cpp index 9b6b284d08..c84d36c8c2 100644 --- a/src/core/hle/kernel/init/init_slab_setup.cpp +++ b/src/core/hle/kernel/init/init_slab_setup.cpp @@ -94,8 +94,8 @@ VAddr InitializeSlabHeap(Core::System& system, KMemoryLayout& memory_layout, VAd // TODO(bunnei): Fix this once we support the kernel virtual memory layout. if (size > 0) { - void* backing_kernel_memory{ - system.DeviceMemory().GetPointer(TranslateSlabAddrToPhysical(memory_layout, start))}; + void* backing_kernel_memory{system.DeviceMemory().GetPointer( + TranslateSlabAddrToPhysical(memory_layout, start))}; const KMemoryRegion* region = memory_layout.FindVirtual(start + size - 1); ASSERT(region != nullptr); @@ -181,7 +181,7 @@ void InitializeKPageBufferSlabHeap(Core::System& system) { ASSERT(slab_address != 0); // Initialize the slabheap. - KPageBuffer::InitializeSlabHeap(kernel, system.DeviceMemory().GetPointer(slab_address), + KPageBuffer::InitializeSlabHeap(kernel, system.DeviceMemory().GetPointer(slab_address), slab_size); } diff --git a/src/core/hle/kernel/k_code_memory.cpp b/src/core/hle/kernel/k_code_memory.cpp index da57ceb21e..4b1c134d40 100644 --- a/src/core/hle/kernel/k_code_memory.cpp +++ b/src/core/hle/kernel/k_code_memory.cpp @@ -34,7 +34,7 @@ Result KCodeMemory::Initialize(Core::DeviceMemory& device_memory, VAddr addr, si // Clear the memory. for (const auto& block : m_page_group.Nodes()) { - std::memset(device_memory.GetPointer(block.GetAddress()), 0xFF, block.GetSize()); + std::memset(device_memory.GetPointer(block.GetAddress()), 0xFF, block.GetSize()); } // Set remaining tracking members. diff --git a/src/core/hle/kernel/k_memory_manager.cpp b/src/core/hle/kernel/k_memory_manager.cpp index 5b0a9963a8..6467115056 100644 --- a/src/core/hle/kernel/k_memory_manager.cpp +++ b/src/core/hle/kernel/k_memory_manager.cpp @@ -331,7 +331,7 @@ Result KMemoryManager::AllocateAndOpenForProcess(KPageGroup* out, size_t num_pag // Set all the allocated memory. for (const auto& block : out->Nodes()) { - std::memset(system.DeviceMemory().GetPointer(block.GetAddress()), fill_pattern, + std::memset(system.DeviceMemory().GetPointer(block.GetAddress()), fill_pattern, block.GetSize()); } diff --git a/src/core/hle/kernel/k_page_buffer.cpp b/src/core/hle/kernel/k_page_buffer.cpp index 1a0bf44393..0c16dded4b 100644 --- a/src/core/hle/kernel/k_page_buffer.cpp +++ b/src/core/hle/kernel/k_page_buffer.cpp @@ -12,7 +12,7 @@ namespace Kernel { KPageBuffer* KPageBuffer::FromPhysicalAddress(Core::System& system, PAddr phys_addr) { ASSERT(Common::IsAligned(phys_addr, PageSize)); - return reinterpret_cast(system.DeviceMemory().GetPointer(phys_addr)); + return system.DeviceMemory().GetPointer(phys_addr); } } // namespace Kernel diff --git a/src/core/hle/kernel/k_page_table.cpp b/src/core/hle/kernel/k_page_table.cpp index d975de8449..8ebb753381 100644 --- a/src/core/hle/kernel/k_page_table.cpp +++ b/src/core/hle/kernel/k_page_table.cpp @@ -1648,7 +1648,7 @@ Result KPageTable::SetHeapSize(VAddr* out, std::size_t size) { // Clear all the newly allocated pages. for (const auto& it : pg.Nodes()) { - std::memset(system.DeviceMemory().GetPointer(it.GetAddress()), heap_fill_value, + std::memset(system.DeviceMemory().GetPointer(it.GetAddress()), heap_fill_value, it.GetSize()); } @@ -1805,9 +1805,9 @@ bool KPageTable::IsRegionMapped(VAddr address, u64 size) { } bool KPageTable::IsRegionContiguous(VAddr addr, u64 size) const { - auto start_ptr = system.Memory().GetPointer(addr); + auto start_ptr = system.DeviceMemory().GetPointer(addr); for (u64 offset{}; offset < size; offset += PageSize) { - if (start_ptr != system.Memory().GetPointer(addr + offset)) { + if (start_ptr != system.DeviceMemory().GetPointer(addr + offset)) { return false; } start_ptr += PageSize; diff --git a/src/core/hle/kernel/k_shared_memory.cpp b/src/core/hle/kernel/k_shared_memory.cpp index 8ff1545b6c..a039cc591c 100644 --- a/src/core/hle/kernel/k_shared_memory.cpp +++ b/src/core/hle/kernel/k_shared_memory.cpp @@ -50,7 +50,7 @@ Result KSharedMemory::Initialize(Core::DeviceMemory& device_memory_, KProcess* o is_initialized = true; // Clear all pages in the memory. - std::memset(device_memory_.GetPointer(physical_address_), 0, size_); + std::memset(device_memory_.GetPointer(physical_address_), 0, size_); return ResultSuccess; } diff --git a/src/core/hle/kernel/k_shared_memory.h b/src/core/hle/kernel/k_shared_memory.h index 34cb984564..5620c3660a 100644 --- a/src/core/hle/kernel/k_shared_memory.h +++ b/src/core/hle/kernel/k_shared_memory.h @@ -54,7 +54,7 @@ public: * @return A pointer to the shared memory block from the specified offset */ u8* GetPointer(std::size_t offset = 0) { - return device_memory->GetPointer(physical_address + offset); + return device_memory->GetPointer(physical_address + offset); } /** @@ -63,7 +63,7 @@ public: * @return A pointer to the shared memory block from the specified offset */ const u8* GetPointer(std::size_t offset = 0) const { - return device_memory->GetPointer(physical_address + offset); + return device_memory->GetPointer(physical_address + offset); } void Finalize() override; diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 2ac792566e..9637cb5b1e 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -65,7 +65,7 @@ struct Memory::Impl { return {}; } - return system.DeviceMemory().GetPointer(paddr) + vaddr; + return system.DeviceMemory().GetPointer(paddr) + vaddr; } [[nodiscard]] u8* GetPointerFromDebugMemory(VAddr vaddr) const { @@ -75,7 +75,7 @@ struct Memory::Impl { return {}; } - return system.DeviceMemory().GetPointer(paddr) + vaddr; + return system.DeviceMemory().GetPointer(paddr) + vaddr; } u8 Read8(const VAddr addr) { @@ -499,7 +499,7 @@ struct Memory::Impl { } else { while (base != end) { page_table.pointers[base].Store( - system.DeviceMemory().GetPointer(target) - (base << YUZU_PAGEBITS), type); + system.DeviceMemory().GetPointer(target) - (base << YUZU_PAGEBITS), type); page_table.backing_addr[base] = target - (base << YUZU_PAGEBITS); ASSERT_MSG(page_table.pointers[base].Pointer(), From 113a5ed68fd2ab0050ebfb520bbd17399cc51298 Mon Sep 17 00:00:00 2001 From: bunnei Date: Mon, 5 Sep 2022 17:43:36 -0700 Subject: [PATCH 21/91] core: hle: kernel: svc_types: Add SystemThreadPriorityHighest and ProcessState. --- src/core/hle/kernel/svc_types.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/core/hle/kernel/svc_types.h b/src/core/hle/kernel/svc_types.h index 79e15183aa..bb4f7b004b 100644 --- a/src/core/hle/kernel/svc_types.h +++ b/src/core/hle/kernel/svc_types.h @@ -95,6 +95,19 @@ constexpr inline s32 IdealCoreNoUpdate = -3; constexpr inline s32 LowestThreadPriority = 63; constexpr inline s32 HighestThreadPriority = 0; +constexpr inline s32 SystemThreadPriorityHighest = 16; + +enum ProcessState : u32 { + ProcessState_Created = 0, + ProcessState_CreatedAttached = 1, + ProcessState_Running = 2, + ProcessState_Crashed = 3, + ProcessState_RunningAttached = 4, + ProcessState_Terminating = 5, + ProcessState_Terminated = 6, + ProcessState_DebugBreak = 7, +}; + constexpr inline size_t ThreadLocalRegionSize = 0x200; } // namespace Kernel::Svc From 25dcaf1ecaeb3998a2cb8b03a7aa8a02402e0bad Mon Sep 17 00:00:00 2001 From: bunnei Date: Mon, 5 Sep 2022 17:47:00 -0700 Subject: [PATCH 22/91] core: hle: kernel: k_process: Change Status -> State. --- src/core/hle/kernel/k_process.cpp | 20 ++++++++-------- src/core/hle/kernel/k_process.h | 40 ++++++++++++------------------- src/core/hle/kernel/svc.cpp | 4 ++-- 3 files changed, 27 insertions(+), 37 deletions(-) diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp index d3e99665f8..1d3157a9f4 100644 --- a/src/core/hle/kernel/k_process.cpp +++ b/src/core/hle/kernel/k_process.cpp @@ -72,7 +72,7 @@ Result KProcess::Initialize(KProcess* process, Core::System& system, std::string process->name = std::move(process_name); process->resource_limit = res_limit; - process->status = ProcessStatus::Created; + process->state = State::Created; process->program_id = 0; process->process_id = type == ProcessType::KernelInternal ? kernel.CreateNewKernelProcessID() : kernel.CreateNewUserProcessID(); @@ -289,7 +289,7 @@ Result KProcess::Reset() { KScopedSchedulerLock sl{kernel}; // Validate that we're in a state that we can reset. - R_UNLESS(status != ProcessStatus::Exited, ResultInvalidState); + R_UNLESS(state != State::Terminated, ResultInvalidState); R_UNLESS(is_signaled, ResultInvalidState); // Clear signaled. @@ -304,8 +304,8 @@ Result KProcess::SetActivity(ProcessActivity activity) { KScopedSchedulerLock sl{kernel}; // Validate our state. - R_UNLESS(status != ProcessStatus::Exiting, ResultInvalidState); - R_UNLESS(status != ProcessStatus::Exited, ResultInvalidState); + R_UNLESS(state != State::Terminating, ResultInvalidState); + R_UNLESS(state != State::Terminated, ResultInvalidState); // Either pause or resume. if (activity == ProcessActivity::Paused) { @@ -411,13 +411,13 @@ void KProcess::Run(s32 main_thread_priority, u64 stack_size) { const std::size_t heap_capacity{memory_usage_capacity - (main_thread_stack_size + image_size)}; ASSERT(!page_table->SetMaxHeapSize(heap_capacity).IsError()); - ChangeStatus(ProcessStatus::Running); + ChangeState(State::Running); SetupMainThread(kernel.System(), *this, main_thread_priority, main_thread_stack_top); } void KProcess::PrepareForTermination() { - ChangeStatus(ProcessStatus::Exiting); + ChangeState(State::Terminating); const auto stop_threads = [this](const std::vector& in_thread_list) { for (auto* thread : in_thread_list) { @@ -445,7 +445,7 @@ void KProcess::PrepareForTermination() { main_thread_stack_size + image_size); } - ChangeStatus(ProcessStatus::Exited); + ChangeState(State::Terminated); } void KProcess::Finalize() { @@ -652,12 +652,12 @@ KProcess::KProcess(KernelCore& kernel_) KProcess::~KProcess() = default; -void KProcess::ChangeStatus(ProcessStatus new_status) { - if (status == new_status) { +void KProcess::ChangeState(State new_state) { + if (state == new_state) { return; } - status = new_status; + state = new_state; is_signaled = true; NotifyAvailable(); } diff --git a/src/core/hle/kernel/k_process.h b/src/core/hle/kernel/k_process.h index d56d73bab1..b1c7da4543 100644 --- a/src/core/hle/kernel/k_process.h +++ b/src/core/hle/kernel/k_process.h @@ -45,24 +45,6 @@ enum class MemoryRegion : u16 { BASE = 3, }; -/** - * Indicates the status of a Process instance. - * - * @note These match the values as used by kernel, - * so new entries should only be added if RE - * shows that a new value has been introduced. - */ -enum class ProcessStatus { - Created, - CreatedWithDebuggerAttached, - Running, - WaitingForDebuggerToAttach, - DebuggerAttached, - Exiting, - Exited, - DebugBreak, -}; - enum class ProcessActivity : u32 { Runnable, Paused, @@ -89,6 +71,17 @@ public: explicit KProcess(KernelCore& kernel_); ~KProcess() override; + enum class State { + Created = Svc::ProcessState_Created, + CreatedAttached = Svc::ProcessState_CreatedAttached, + Running = Svc::ProcessState_Running, + Crashed = Svc::ProcessState_Crashed, + RunningAttached = Svc::ProcessState_RunningAttached, + Terminating = Svc::ProcessState_Terminating, + Terminated = Svc::ProcessState_Terminated, + DebugBreak = Svc::ProcessState_DebugBreak, + }; + enum : u64 { /// Lowest allowed process ID for a kernel initial process. InitialKIPIDMin = 1, @@ -163,8 +156,8 @@ public: } /// Gets the current status of the process - ProcessStatus GetStatus() const { - return status; + State GetState() const { + return state; } /// Gets the unique ID that identifies this particular process. @@ -415,10 +408,7 @@ private: pinned_threads[core_id] = nullptr; } - /// Changes the process status. If the status is different - /// from the current process status, then this will trigger - /// a process signal. - void ChangeStatus(ProcessStatus new_status); + void ChangeState(State new_state); /// Allocates the main thread stack for the process, given the stack size in bytes. Result AllocateMainThreadStack(std::size_t stack_size); @@ -427,7 +417,7 @@ private: std::unique_ptr page_table; /// Current status of the process - ProcessStatus status{}; + State state{}; /// The ID of this process u64 process_id = 0; diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 1d145ea91e..bac61fd096 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -1888,7 +1888,7 @@ static void ExitProcess(Core::System& system) { auto* current_process = system.Kernel().CurrentProcess(); LOG_INFO(Kernel_SVC, "Process {} exiting", current_process->GetProcessID()); - ASSERT_MSG(current_process->GetStatus() == ProcessStatus::Running, + ASSERT_MSG(current_process->GetState() == KProcess::State::Running, "Process has already exited"); system.Exit(); @@ -2557,7 +2557,7 @@ static Result GetProcessInfo(Core::System& system, u64* out, Handle process_hand return ResultInvalidEnumValue; } - *out = static_cast(process->GetStatus()); + *out = static_cast(process->GetState()); return ResultSuccess; } From 345b9e6a08f7ce99bb71f7184157ed0fe22bf27d Mon Sep 17 00:00:00 2001 From: bunnei Date: Mon, 5 Sep 2022 17:51:50 -0700 Subject: [PATCH 23/91] core: hle: kernel: Add KDynamicPageManager. --- src/core/CMakeLists.txt | 1 + src/core/hle/kernel/k_dynamic_page_manager.h | 136 +++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 src/core/hle/kernel/k_dynamic_page_manager.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index abeb5859b5..2bb4dea6a5 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -190,6 +190,7 @@ add_library(core STATIC hle/kernel/k_code_memory.h hle/kernel/k_condition_variable.cpp hle/kernel/k_condition_variable.h + hle/kernel/k_dynamic_page_manager.h hle/kernel/k_event.cpp hle/kernel/k_event.h hle/kernel/k_handle_table.cpp diff --git a/src/core/hle/kernel/k_dynamic_page_manager.h b/src/core/hle/kernel/k_dynamic_page_manager.h new file mode 100644 index 0000000000..88d53776ae --- /dev/null +++ b/src/core/hle/kernel/k_dynamic_page_manager.h @@ -0,0 +1,136 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/alignment.h" +#include "common/common_types.h" +#include "core/hle/kernel/k_page_bitmap.h" +#include "core/hle/kernel/k_spin_lock.h" +#include "core/hle/kernel/memory_types.h" +#include "core/hle/kernel/svc_results.h" + +namespace Kernel { + +class KDynamicPageManager { +public: + class PageBuffer { + private: + u8 m_buffer[PageSize]; + }; + static_assert(sizeof(PageBuffer) == PageSize); + +public: + KDynamicPageManager() = default; + + template + T* GetPointer(VAddr addr) { + return reinterpret_cast(m_backing_memory.data() + (addr - m_address)); + } + + template + const T* GetPointer(VAddr addr) const { + return reinterpret_cast(m_backing_memory.data() + (addr - m_address)); + } + + Result Initialize(VAddr addr, size_t sz) { + // We need to have positive size. + R_UNLESS(sz > 0, ResultOutOfMemory); + m_backing_memory.resize(sz); + + // Calculate management overhead. + const size_t management_size = + KPageBitmap::CalculateManagementOverheadSize(sz / sizeof(PageBuffer)); + const size_t allocatable_size = sz - management_size; + + // Set tracking fields. + m_address = addr; + m_size = Common::AlignDown(allocatable_size, sizeof(PageBuffer)); + m_count = allocatable_size / sizeof(PageBuffer); + R_UNLESS(m_count > 0, ResultOutOfMemory); + + // Clear the management region. + u64* management_ptr = GetPointer(m_address + allocatable_size); + std::memset(management_ptr, 0, management_size); + + // Initialize the bitmap. + m_page_bitmap.Initialize(management_ptr, m_count); + + // Free the pages to the bitmap. + for (size_t i = 0; i < m_count; i++) { + // Ensure the freed page is all-zero. + std::memset(GetPointer(m_address) + i, 0, PageSize); + + // Set the bit for the free page. + m_page_bitmap.SetBit(i); + } + + return ResultSuccess; + } + + VAddr GetAddress() const { + return m_address; + } + size_t GetSize() const { + return m_size; + } + size_t GetUsed() const { + return m_used; + } + size_t GetPeak() const { + return m_peak; + } + size_t GetCount() const { + return m_count; + } + + PageBuffer* Allocate() { + // Take the lock. + // TODO(bunnei): We should disable interrupts here via KScopedInterruptDisable. + KScopedSpinLock lk(m_lock); + + // Find a random free block. + s64 soffset = m_page_bitmap.FindFreeBlock(true); + if (soffset < 0) [[unlikely]] { + return nullptr; + } + + const size_t offset = static_cast(soffset); + + // Update our tracking. + m_page_bitmap.ClearBit(offset); + m_peak = std::max(m_peak, (++m_used)); + + return GetPointer(m_address) + offset; + } + + void Free(PageBuffer* pb) { + // Ensure all pages in the heap are zero. + std::memset(pb, 0, PageSize); + + // Take the lock. + // TODO(bunnei): We should disable interrupts here via KScopedInterruptDisable. + KScopedSpinLock lk(m_lock); + + // Set the bit for the free page. + size_t offset = (reinterpret_cast(pb) - m_address) / sizeof(PageBuffer); + m_page_bitmap.SetBit(offset); + + // Decrement our used count. + --m_used; + } + +private: + KSpinLock m_lock; + KPageBitmap m_page_bitmap; + size_t m_used{}; + size_t m_peak{}; + size_t m_count{}; + VAddr m_address{}; + size_t m_size{}; + + // TODO(bunnei): Back by host memory until we emulate kernel virtual address space. + std::vector m_backing_memory; +}; + +} // namespace Kernel From 9ec5f75f43c2ecbfdf52b45f78029b1fd1080658 Mon Sep 17 00:00:00 2001 From: bunnei Date: Mon, 5 Sep 2022 17:53:44 -0700 Subject: [PATCH 24/91] core: hle: kernel: Add KDynamicSlabHeap. --- src/core/CMakeLists.txt | 1 + src/core/hle/kernel/k_dynamic_slab_heap.h | 122 ++++++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 src/core/hle/kernel/k_dynamic_slab_heap.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 2bb4dea6a5..2965717627 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -191,6 +191,7 @@ add_library(core STATIC hle/kernel/k_condition_variable.cpp hle/kernel/k_condition_variable.h hle/kernel/k_dynamic_page_manager.h + hle/kernel/k_dynamic_slab_heap.h hle/kernel/k_event.cpp hle/kernel/k_event.h hle/kernel/k_handle_table.cpp diff --git a/src/core/hle/kernel/k_dynamic_slab_heap.h b/src/core/hle/kernel/k_dynamic_slab_heap.h new file mode 100644 index 0000000000..3a0ddd0500 --- /dev/null +++ b/src/core/hle/kernel/k_dynamic_slab_heap.h @@ -0,0 +1,122 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "common/common_funcs.h" +#include "core/hle/kernel/k_dynamic_page_manager.h" +#include "core/hle/kernel/k_slab_heap.h" + +namespace Kernel { + +template +class KDynamicSlabHeap : protected impl::KSlabHeapImpl { + YUZU_NON_COPYABLE(KDynamicSlabHeap); + YUZU_NON_MOVEABLE(KDynamicSlabHeap); + +public: + constexpr KDynamicSlabHeap() = default; + + constexpr VAddr GetAddress() const { + return m_address; + } + constexpr size_t GetSize() const { + return m_size; + } + constexpr size_t GetUsed() const { + return m_used.load(); + } + constexpr size_t GetPeak() const { + return m_peak.load(); + } + constexpr size_t GetCount() const { + return m_count.load(); + } + + constexpr bool IsInRange(VAddr addr) const { + return this->GetAddress() <= addr && addr <= this->GetAddress() + this->GetSize() - 1; + } + + void Initialize(KDynamicPageManager* page_allocator, size_t num_objects) { + ASSERT(page_allocator != nullptr); + + // Initialize members. + m_address = page_allocator->GetAddress(); + m_size = page_allocator->GetSize(); + + // Initialize the base allocator. + KSlabHeapImpl::Initialize(); + + // Allocate until we have the correct number of objects. + while (m_count.load() < num_objects) { + auto* allocated = reinterpret_cast(page_allocator->Allocate()); + ASSERT(allocated != nullptr); + + for (size_t i = 0; i < sizeof(PageBuffer) / sizeof(T); i++) { + KSlabHeapImpl::Free(allocated + i); + } + + m_count += sizeof(PageBuffer) / sizeof(T); + } + } + + T* Allocate(KDynamicPageManager* page_allocator) { + T* allocated = static_cast(KSlabHeapImpl::Allocate()); + + // If we successfully allocated and we should clear the node, do so. + if constexpr (ClearNode) { + if (allocated != nullptr) [[likely]] { + reinterpret_cast(allocated)->next = nullptr; + } + } + + // If we fail to allocate, try to get a new page from our next allocator. + if (allocated == nullptr) [[unlikely]] { + if (page_allocator != nullptr) { + allocated = reinterpret_cast(page_allocator->Allocate()); + if (allocated != nullptr) { + // If we succeeded in getting a page, free the rest to our slab. + for (size_t i = 1; i < sizeof(PageBuffer) / sizeof(T); i++) { + KSlabHeapImpl::Free(allocated + i); + } + m_count += sizeof(PageBuffer) / sizeof(T); + } + } + } + + if (allocated != nullptr) [[likely]] { + // Construct the object. + std::construct_at(allocated); + + // Update our tracking. + const size_t used = ++m_used; + size_t peak = m_peak.load(); + while (peak < used) { + if (m_peak.compare_exchange_weak(peak, used, std::memory_order_relaxed)) { + break; + } + } + } + + return allocated; + } + + void Free(T* t) { + KSlabHeapImpl::Free(t); + --m_used; + } + +private: + using PageBuffer = KDynamicPageManager::PageBuffer; + +private: + std::atomic m_used{}; + std::atomic m_peak{}; + std::atomic m_count{}; + VAddr m_address{}; + size_t m_size{}; +}; + +} // namespace Kernel From d02ccfb15d1f3d4fcdb9feae60ae136fcfd99788 Mon Sep 17 00:00:00 2001 From: bunnei Date: Mon, 5 Sep 2022 17:55:51 -0700 Subject: [PATCH 25/91] core: hle: kernel: Add KDynamicResourceManager. --- src/core/CMakeLists.txt | 1 + .../hle/kernel/k_dynamic_resource_manager.h | 58 +++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 src/core/hle/kernel/k_dynamic_resource_manager.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 2965717627..e7fe675cbf 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -191,6 +191,7 @@ add_library(core STATIC hle/kernel/k_condition_variable.cpp hle/kernel/k_condition_variable.h hle/kernel/k_dynamic_page_manager.h + hle/kernel/k_dynamic_resource_manager.h hle/kernel/k_dynamic_slab_heap.h hle/kernel/k_event.cpp hle/kernel/k_event.h diff --git a/src/core/hle/kernel/k_dynamic_resource_manager.h b/src/core/hle/kernel/k_dynamic_resource_manager.h new file mode 100644 index 0000000000..1ce517e8e9 --- /dev/null +++ b/src/core/hle/kernel/k_dynamic_resource_manager.h @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/common_funcs.h" +#include "core/hle/kernel/k_dynamic_slab_heap.h" +#include "core/hle/kernel/k_memory_block.h" + +namespace Kernel { + +template +class KDynamicResourceManager { + YUZU_NON_COPYABLE(KDynamicResourceManager); + YUZU_NON_MOVEABLE(KDynamicResourceManager); + +public: + using DynamicSlabType = KDynamicSlabHeap; + +public: + constexpr KDynamicResourceManager() = default; + + constexpr size_t GetSize() const { + return m_slab_heap->GetSize(); + } + constexpr size_t GetUsed() const { + return m_slab_heap->GetUsed(); + } + constexpr size_t GetPeak() const { + return m_slab_heap->GetPeak(); + } + constexpr size_t GetCount() const { + return m_slab_heap->GetCount(); + } + + void Initialize(KDynamicPageManager* page_allocator, DynamicSlabType* slab_heap) { + m_page_allocator = page_allocator; + m_slab_heap = slab_heap; + } + + T* Allocate() const { + return m_slab_heap->Allocate(m_page_allocator); + } + + void Free(T* t) const { + m_slab_heap->Free(t); + } + +private: + KDynamicPageManager* m_page_allocator{}; + DynamicSlabType* m_slab_heap{}; +}; + +class KMemoryBlockSlabManager : public KDynamicResourceManager {}; + +using KMemoryBlockSlabHeap = typename KMemoryBlockSlabManager::DynamicSlabType; + +} // namespace Kernel From 57a77e9ff4b4a63c106c0ac3448a8f1452b5384c Mon Sep 17 00:00:00 2001 From: bunnei Date: Mon, 5 Sep 2022 18:19:30 -0700 Subject: [PATCH 26/91] core: hle: kernel: k_thread: Implement thread termination DPC. --- src/core/arm/arm_interface.cpp | 8 +++ src/core/hle/kernel/k_interrupt_manager.cpp | 8 +++ src/core/hle/kernel/k_interrupt_manager.h | 4 +- src/core/hle/kernel/k_thread.cpp | 76 +++++++++++++++++++++ src/core/hle/kernel/k_thread.h | 4 ++ 5 files changed, 99 insertions(+), 1 deletion(-) diff --git a/src/core/arm/arm_interface.cpp b/src/core/arm/arm_interface.cpp index 953d964399..29ba562dce 100644 --- a/src/core/arm/arm_interface.cpp +++ b/src/core/arm/arm_interface.cpp @@ -134,6 +134,14 @@ void ARM_Interface::Run() { } system.ExitDynarmicProfile(); + // If the thread is scheduled for termination, exit the thread. + if (current_thread->HasDpc()) { + if (current_thread->IsTerminationRequested()) { + current_thread->Exit(); + UNREACHABLE(); + } + } + // Notify the debugger and go to sleep if a breakpoint was hit, // or if the thread is unable to continue for any reason. if (Has(hr, breakpoint) || Has(hr, no_execute)) { diff --git a/src/core/hle/kernel/k_interrupt_manager.cpp b/src/core/hle/kernel/k_interrupt_manager.cpp index 1b577a5b3e..ad73f3eab5 100644 --- a/src/core/hle/kernel/k_interrupt_manager.cpp +++ b/src/core/hle/kernel/k_interrupt_manager.cpp @@ -36,4 +36,12 @@ void HandleInterrupt(KernelCore& kernel, s32 core_id) { kernel.CurrentScheduler()->RequestScheduleOnInterrupt(); } +void SendInterProcessorInterrupt(KernelCore& kernel, u64 core_mask) { + for (std::size_t core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; ++core_id) { + if (core_mask & (1ULL << core_id)) { + kernel.PhysicalCore(core_id).Interrupt(); + } + } +} + } // namespace Kernel::KInterruptManager diff --git a/src/core/hle/kernel/k_interrupt_manager.h b/src/core/hle/kernel/k_interrupt_manager.h index f103dfe3f1..803dc92117 100644 --- a/src/core/hle/kernel/k_interrupt_manager.h +++ b/src/core/hle/kernel/k_interrupt_manager.h @@ -11,6 +11,8 @@ class KernelCore; namespace KInterruptManager { void HandleInterrupt(KernelCore& kernel, s32 core_id); -} +void SendInterProcessorInterrupt(KernelCore& kernel, u64 core_mask); + +} // namespace KInterruptManager } // namespace Kernel diff --git a/src/core/hle/kernel/k_thread.cpp b/src/core/hle/kernel/k_thread.cpp index 174afc80d7..89b32d509e 100644 --- a/src/core/hle/kernel/k_thread.cpp +++ b/src/core/hle/kernel/k_thread.cpp @@ -30,6 +30,7 @@ #include "core/hle/kernel/k_worker_task_manager.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/svc_results.h" +#include "core/hle/kernel/svc_types.h" #include "core/hle/result.h" #include "core/memory.h" @@ -38,6 +39,9 @@ #endif namespace { + +constexpr inline s32 TerminatingThreadPriority = Kernel::Svc::SystemThreadPriorityHighest - 1; + static void ResetThreadContext32(Core::ARM_Interface::ThreadContext32& context, u32 stack_top, u32 entry_point, u32 arg) { context = {}; @@ -1073,6 +1077,78 @@ void KThread::Exit() { UNREACHABLE_MSG("KThread::Exit() would return"); } +Result KThread::Terminate() { + ASSERT(this != GetCurrentThreadPointer(kernel)); + + // Request the thread terminate if it hasn't already. + if (const auto new_state = this->RequestTerminate(); new_state != ThreadState::Terminated) { + // If the thread isn't terminated, wait for it to terminate. + s32 index; + KSynchronizationObject* objects[] = {this}; + R_TRY(KSynchronizationObject::Wait(kernel, std::addressof(index), objects, 1, + Svc::WaitInfinite)); + } + + return ResultSuccess; +} + +ThreadState KThread::RequestTerminate() { + ASSERT(this != GetCurrentThreadPointer(kernel)); + + KScopedSchedulerLock sl{kernel}; + + // Determine if this is the first termination request. + const bool first_request = [&]() -> bool { + // Perform an atomic compare-and-swap from false to true. + bool expected = false; + return termination_requested.compare_exchange_strong(expected, true); + }(); + + // If this is the first request, start termination procedure. + if (first_request) { + // If the thread is in initialized state, just change state to terminated. + if (this->GetState() == ThreadState::Initialized) { + thread_state = ThreadState::Terminated; + return ThreadState::Terminated; + } + + // Register the terminating dpc. + this->RegisterDpc(DpcFlag::Terminating); + + // If the thread is pinned, unpin it. + if (this->GetStackParameters().is_pinned) { + this->GetOwnerProcess()->UnpinThread(this); + } + + // If the thread is suspended, continue it. + if (this->IsSuspended()) { + suspend_allowed_flags = 0; + this->UpdateState(); + } + + // Change the thread's priority to be higher than any system thread's. + if (this->GetBasePriority() >= Svc::SystemThreadPriorityHighest) { + this->SetBasePriority(TerminatingThreadPriority); + } + + // If the thread is runnable, send a termination interrupt to other cores. + if (this->GetState() == ThreadState::Runnable) { + if (const u64 core_mask = + physical_affinity_mask.GetAffinityMask() & ~(1ULL << GetCurrentCoreId(kernel)); + core_mask != 0) { + Kernel::KInterruptManager::SendInterProcessorInterrupt(kernel, core_mask); + } + } + + // Wake up the thread. + if (this->GetState() == ThreadState::Waiting) { + wait_queue->CancelWait(this, ResultTerminationRequested, true); + } + } + + return this->GetState(); +} + Result KThread::Sleep(s64 timeout) { ASSERT(!kernel.GlobalSchedulerContext().IsLocked()); ASSERT(this == GetCurrentThreadPointer(kernel)); diff --git a/src/core/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h index 9ee20208eb..e2a27d6036 100644 --- a/src/core/hle/kernel/k_thread.h +++ b/src/core/hle/kernel/k_thread.h @@ -180,6 +180,10 @@ public: void Exit(); + Result Terminate(); + + ThreadState RequestTerminate(); + [[nodiscard]] u32 GetSuspendFlags() const { return suspend_allowed_flags & suspend_request_flags; } From 2bb41cffca7e5ec6383a59c513ef9d7e2def5f51 Mon Sep 17 00:00:00 2001 From: bunnei Date: Fri, 9 Sep 2022 21:12:37 -0700 Subject: [PATCH 27/91] core: hle: kernel: k_memory_block_manager: Update. --- .../hle/kernel/k_memory_block_manager.cpp | 441 +++++++++++------- src/core/hle/kernel/k_memory_block_manager.h | 147 ++++-- 2 files changed, 397 insertions(+), 191 deletions(-) diff --git a/src/core/hle/kernel/k_memory_block_manager.cpp b/src/core/hle/kernel/k_memory_block_manager.cpp index 3ddb9984fa..c908af75a9 100644 --- a/src/core/hle/kernel/k_memory_block_manager.cpp +++ b/src/core/hle/kernel/k_memory_block_manager.cpp @@ -2,221 +2,336 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "core/hle/kernel/k_memory_block_manager.h" -#include "core/hle/kernel/memory_types.h" namespace Kernel { -KMemoryBlockManager::KMemoryBlockManager(VAddr start_addr_, VAddr end_addr_) - : start_addr{start_addr_}, end_addr{end_addr_} { - const u64 num_pages{(end_addr - start_addr) / PageSize}; - memory_block_tree.emplace_back(start_addr, num_pages, KMemoryState::Free, - KMemoryPermission::None, KMemoryAttribute::None); +KMemoryBlockManager::KMemoryBlockManager() = default; + +Result KMemoryBlockManager::Initialize(VAddr st, VAddr nd, KMemoryBlockSlabManager* slab_manager) { + // Allocate a block to encapsulate the address space, insert it into the tree. + KMemoryBlock* start_block = slab_manager->Allocate(); + R_UNLESS(start_block != nullptr, ResultOutOfResource); + + // Set our start and end. + m_start_address = st; + m_end_address = nd; + ASSERT(Common::IsAligned(m_start_address, PageSize)); + ASSERT(Common::IsAligned(m_end_address, PageSize)); + + // Initialize and insert the block. + start_block->Initialize(m_start_address, (m_end_address - m_start_address) / PageSize, + KMemoryState::Free, KMemoryPermission::None, KMemoryAttribute::None); + m_memory_block_tree.insert(*start_block); + + return ResultSuccess; } -KMemoryBlockManager::iterator KMemoryBlockManager::FindIterator(VAddr addr) { - auto node{memory_block_tree.begin()}; - while (node != end()) { - const VAddr node_end_addr{node->GetNumPages() * PageSize + node->GetAddress()}; - if (node->GetAddress() <= addr && node_end_addr - 1 >= addr) { - return node; - } - node = std::next(node); +void KMemoryBlockManager::Finalize(KMemoryBlockSlabManager* slab_manager, + HostUnmapCallback&& host_unmap_callback) { + // Erase every block until we have none left. + auto it = m_memory_block_tree.begin(); + while (it != m_memory_block_tree.end()) { + KMemoryBlock* block = std::addressof(*it); + it = m_memory_block_tree.erase(it); + slab_manager->Free(block); + host_unmap_callback(block->GetAddress(), block->GetSize()); } - return end(); + + ASSERT(m_memory_block_tree.empty()); } -VAddr KMemoryBlockManager::FindFreeArea(VAddr region_start, std::size_t region_num_pages, - std::size_t num_pages, std::size_t align, - std::size_t offset, std::size_t guard_pages) { - if (num_pages == 0) { - return {}; - } +VAddr KMemoryBlockManager::FindFreeArea(VAddr region_start, size_t region_num_pages, + size_t num_pages, size_t alignment, size_t offset, + size_t guard_pages) const { + if (num_pages > 0) { + const VAddr region_end = region_start + region_num_pages * PageSize; + const VAddr region_last = region_end - 1; + for (const_iterator it = this->FindIterator(region_start); it != m_memory_block_tree.cend(); + it++) { + const KMemoryInfo info = it->GetMemoryInfo(); + if (region_last < info.GetAddress()) { + break; + } + if (info.m_state != KMemoryState::Free) { + continue; + } - const VAddr region_end{region_start + region_num_pages * PageSize}; - const VAddr region_last{region_end - 1}; - for (auto it{FindIterator(region_start)}; it != memory_block_tree.cend(); it++) { - const auto info{it->GetMemoryInfo()}; - if (region_last < info.GetAddress()) { - break; - } + VAddr area = (info.GetAddress() <= region_start) ? region_start : info.GetAddress(); + area += guard_pages * PageSize; - if (info.state != KMemoryState::Free) { - continue; - } + const VAddr offset_area = Common::AlignDown(area, alignment) + offset; + area = (area <= offset_area) ? offset_area : offset_area + alignment; - VAddr area{(info.GetAddress() <= region_start) ? region_start : info.GetAddress()}; - area += guard_pages * PageSize; + const VAddr area_end = area + num_pages * PageSize + guard_pages * PageSize; + const VAddr area_last = area_end - 1; - const VAddr offset_area{Common::AlignDown(area, align) + offset}; - area = (area <= offset_area) ? offset_area : offset_area + align; - - const VAddr area_end{area + num_pages * PageSize + guard_pages * PageSize}; - const VAddr area_last{area_end - 1}; - - if (info.GetAddress() <= area && area < area_last && area_last <= region_last && - area_last <= info.GetLastAddress()) { - return area; + if (info.GetAddress() <= area && area < area_last && area_last <= region_last && + area_last <= info.GetLastAddress()) { + return area; + } } } return {}; } -void KMemoryBlockManager::Update(VAddr addr, std::size_t num_pages, KMemoryState prev_state, - KMemoryPermission prev_perm, KMemoryAttribute prev_attribute, - KMemoryState state, KMemoryPermission perm, - KMemoryAttribute attribute) { - const VAddr update_end_addr{addr + num_pages * PageSize}; - iterator node{memory_block_tree.begin()}; +void KMemoryBlockManager::CoalesceForUpdate(KMemoryBlockManagerUpdateAllocator* allocator, + VAddr address, size_t num_pages) { + // Find the iterator now that we've updated. + iterator it = this->FindIterator(address); + if (address != m_start_address) { + it--; + } - prev_attribute |= KMemoryAttribute::IpcAndDeviceMapped; - - while (node != memory_block_tree.end()) { - KMemoryBlock* block{&(*node)}; - iterator next_node{std::next(node)}; - const VAddr cur_addr{block->GetAddress()}; - const VAddr cur_end_addr{block->GetNumPages() * PageSize + cur_addr}; - - if (addr < cur_end_addr && cur_addr < update_end_addr) { - if (!block->HasProperties(prev_state, prev_perm, prev_attribute)) { - node = next_node; - continue; - } - - iterator new_node{node}; - if (addr > cur_addr) { - memory_block_tree.insert(node, block->Split(addr)); - } - - if (update_end_addr < cur_end_addr) { - new_node = memory_block_tree.insert(node, block->Split(update_end_addr)); - } - - new_node->Update(state, perm, attribute); - - MergeAdjacent(new_node, next_node); - } - - if (cur_end_addr - 1 >= update_end_addr - 1) { + // Coalesce blocks that we can. + while (true) { + iterator prev = it++; + if (it == m_memory_block_tree.end()) { break; } - node = next_node; - } -} - -void KMemoryBlockManager::Update(VAddr addr, std::size_t num_pages, KMemoryState state, - KMemoryPermission perm, KMemoryAttribute attribute) { - const VAddr update_end_addr{addr + num_pages * PageSize}; - iterator node{memory_block_tree.begin()}; - - while (node != memory_block_tree.end()) { - KMemoryBlock* block{&(*node)}; - iterator next_node{std::next(node)}; - const VAddr cur_addr{block->GetAddress()}; - const VAddr cur_end_addr{block->GetNumPages() * PageSize + cur_addr}; - - if (addr < cur_end_addr && cur_addr < update_end_addr) { - iterator new_node{node}; - - if (addr > cur_addr) { - memory_block_tree.insert(node, block->Split(addr)); - } - - if (update_end_addr < cur_end_addr) { - new_node = memory_block_tree.insert(node, block->Split(update_end_addr)); - } - - new_node->Update(state, perm, attribute); - - MergeAdjacent(new_node, next_node); + if (prev->CanMergeWith(*it)) { + KMemoryBlock* block = std::addressof(*it); + m_memory_block_tree.erase(it); + prev->Add(*block); + allocator->Free(block); + it = prev; } - if (cur_end_addr - 1 >= update_end_addr - 1) { + if (address + num_pages * PageSize < it->GetMemoryInfo().GetEndAddress()) { break; } - - node = next_node; } } -void KMemoryBlockManager::UpdateLock(VAddr addr, std::size_t num_pages, LockFunc&& lock_func, +void KMemoryBlockManager::Update(KMemoryBlockManagerUpdateAllocator* allocator, VAddr address, + size_t num_pages, KMemoryState state, KMemoryPermission perm, + KMemoryAttribute attr, + KMemoryBlockDisableMergeAttribute set_disable_attr, + KMemoryBlockDisableMergeAttribute clear_disable_attr) { + // Ensure for auditing that we never end up with an invalid tree. + KScopedMemoryBlockManagerAuditor auditor(this); + ASSERT(Common::IsAligned(address, PageSize)); + ASSERT((attr & (KMemoryAttribute::IpcLocked | KMemoryAttribute::DeviceShared)) == + KMemoryAttribute::None); + + VAddr cur_address = address; + size_t remaining_pages = num_pages; + iterator it = this->FindIterator(address); + + while (remaining_pages > 0) { + const size_t remaining_size = remaining_pages * PageSize; + KMemoryInfo cur_info = it->GetMemoryInfo(); + if (it->HasProperties(state, perm, attr)) { + // If we already have the right properties, just advance. + if (cur_address + remaining_size < cur_info.GetEndAddress()) { + remaining_pages = 0; + cur_address += remaining_size; + } else { + remaining_pages = + (cur_address + remaining_size - cur_info.GetEndAddress()) / PageSize; + cur_address = cur_info.GetEndAddress(); + } + } else { + // If we need to, create a new block before and insert it. + if (cur_info.GetAddress() != cur_address) { + KMemoryBlock* new_block = allocator->Allocate(); + + it->Split(new_block, cur_address); + it = m_memory_block_tree.insert(*new_block); + it++; + + cur_info = it->GetMemoryInfo(); + cur_address = cur_info.GetAddress(); + } + + // If we need to, create a new block after and insert it. + if (cur_info.GetSize() > remaining_size) { + KMemoryBlock* new_block = allocator->Allocate(); + + it->Split(new_block, cur_address + remaining_size); + it = m_memory_block_tree.insert(*new_block); + + cur_info = it->GetMemoryInfo(); + } + + // Update block state. + it->Update(state, perm, attr, cur_address == address, static_cast(set_disable_attr), + static_cast(clear_disable_attr)); + cur_address += cur_info.GetSize(); + remaining_pages -= cur_info.GetNumPages(); + } + it++; + } + + this->CoalesceForUpdate(allocator, address, num_pages); +} + +void KMemoryBlockManager::UpdateIfMatch(KMemoryBlockManagerUpdateAllocator* allocator, + VAddr address, size_t num_pages, KMemoryState test_state, + KMemoryPermission test_perm, KMemoryAttribute test_attr, + KMemoryState state, KMemoryPermission perm, + KMemoryAttribute attr) { + // Ensure for auditing that we never end up with an invalid tree. + KScopedMemoryBlockManagerAuditor auditor(this); + ASSERT(Common::IsAligned(address, PageSize)); + ASSERT((attr & (KMemoryAttribute::IpcLocked | KMemoryAttribute::DeviceShared)) == + KMemoryAttribute::None); + + VAddr cur_address = address; + size_t remaining_pages = num_pages; + iterator it = this->FindIterator(address); + + while (remaining_pages > 0) { + const size_t remaining_size = remaining_pages * PageSize; + KMemoryInfo cur_info = it->GetMemoryInfo(); + if (it->HasProperties(test_state, test_perm, test_attr) && + !it->HasProperties(state, perm, attr)) { + // If we need to, create a new block before and insert it. + if (cur_info.GetAddress() != cur_address) { + KMemoryBlock* new_block = allocator->Allocate(); + + it->Split(new_block, cur_address); + it = m_memory_block_tree.insert(*new_block); + it++; + + cur_info = it->GetMemoryInfo(); + cur_address = cur_info.GetAddress(); + } + + // If we need to, create a new block after and insert it. + if (cur_info.GetSize() > remaining_size) { + KMemoryBlock* new_block = allocator->Allocate(); + + it->Split(new_block, cur_address + remaining_size); + it = m_memory_block_tree.insert(*new_block); + + cur_info = it->GetMemoryInfo(); + } + + // Update block state. + it->Update(state, perm, attr, false, 0, 0); + cur_address += cur_info.GetSize(); + remaining_pages -= cur_info.GetNumPages(); + } else { + // If we already have the right properties, just advance. + if (cur_address + remaining_size < cur_info.GetEndAddress()) { + remaining_pages = 0; + cur_address += remaining_size; + } else { + remaining_pages = + (cur_address + remaining_size - cur_info.GetEndAddress()) / PageSize; + cur_address = cur_info.GetEndAddress(); + } + } + it++; + } + + this->CoalesceForUpdate(allocator, address, num_pages); +} + +void KMemoryBlockManager::UpdateLock(KMemoryBlockManagerUpdateAllocator* allocator, VAddr address, + size_t num_pages, MemoryBlockLockFunction lock_func, KMemoryPermission perm) { - const VAddr update_end_addr{addr + num_pages * PageSize}; - iterator node{memory_block_tree.begin()}; + // Ensure for auditing that we never end up with an invalid tree. + KScopedMemoryBlockManagerAuditor auditor(this); + ASSERT(Common::IsAligned(address, PageSize)); - while (node != memory_block_tree.end()) { - KMemoryBlock* block{&(*node)}; - iterator next_node{std::next(node)}; - const VAddr cur_addr{block->GetAddress()}; - const VAddr cur_end_addr{block->GetNumPages() * PageSize + cur_addr}; + VAddr cur_address = address; + size_t remaining_pages = num_pages; + iterator it = this->FindIterator(address); - if (addr < cur_end_addr && cur_addr < update_end_addr) { - iterator new_node{node}; + const VAddr end_address = address + (num_pages * PageSize); - if (addr > cur_addr) { - memory_block_tree.insert(node, block->Split(addr)); - } + while (remaining_pages > 0) { + const size_t remaining_size = remaining_pages * PageSize; + KMemoryInfo cur_info = it->GetMemoryInfo(); - if (update_end_addr < cur_end_addr) { - new_node = memory_block_tree.insert(node, block->Split(update_end_addr)); - } + // If we need to, create a new block before and insert it. + if (cur_info.m_address != cur_address) { + KMemoryBlock* new_block = allocator->Allocate(); - lock_func(new_node, perm); + it->Split(new_block, cur_address); + it = m_memory_block_tree.insert(*new_block); + it++; - MergeAdjacent(new_node, next_node); + cur_info = it->GetMemoryInfo(); + cur_address = cur_info.GetAddress(); } - if (cur_end_addr - 1 >= update_end_addr - 1) { - break; + if (cur_info.GetSize() > remaining_size) { + // If we need to, create a new block after and insert it. + KMemoryBlock* new_block = allocator->Allocate(); + + it->Split(new_block, cur_address + remaining_size); + it = m_memory_block_tree.insert(*new_block); + + cur_info = it->GetMemoryInfo(); } - node = next_node; + // Call the locked update function. + (std::addressof(*it)->*lock_func)(perm, cur_info.GetAddress() == address, + cur_info.GetEndAddress() == end_address); + cur_address += cur_info.GetSize(); + remaining_pages -= cur_info.GetNumPages(); + it++; } + + this->CoalesceForUpdate(allocator, address, num_pages); } -void KMemoryBlockManager::IterateForRange(VAddr start, VAddr end, IterateFunc&& func) { - const_iterator it{FindIterator(start)}; - KMemoryInfo info{}; - do { - info = it->GetMemoryInfo(); - func(info); - it = std::next(it); - } while (info.addr + info.size - 1 < end - 1 && it != cend()); -} +// Debug. +bool KMemoryBlockManager::CheckState() const { + // Loop over every block, ensuring that we are sorted and coalesced. + auto it = m_memory_block_tree.cbegin(); + auto prev = it++; + while (it != m_memory_block_tree.cend()) { + const KMemoryInfo prev_info = prev->GetMemoryInfo(); + const KMemoryInfo cur_info = it->GetMemoryInfo(); -void KMemoryBlockManager::MergeAdjacent(iterator it, iterator& next_it) { - KMemoryBlock* block{&(*it)}; - - auto EraseIt = [&](const iterator it_to_erase) { - if (next_it == it_to_erase) { - next_it = std::next(next_it); + // Sequential blocks which can be merged should be merged. + if (prev->CanMergeWith(*it)) { + return false; } - memory_block_tree.erase(it_to_erase); - }; - if (it != memory_block_tree.begin()) { - KMemoryBlock* prev{&(*std::prev(it))}; + // Sequential blocks should be sequential. + if (prev_info.GetEndAddress() != cur_info.GetAddress()) { + return false; + } - if (block->HasSameProperties(*prev)) { - const iterator prev_it{std::prev(it)}; + // If the block is ipc locked, it must have a count. + if ((cur_info.m_attribute & KMemoryAttribute::IpcLocked) != KMemoryAttribute::None && + cur_info.m_ipc_lock_count == 0) { + return false; + } - prev->Add(block->GetNumPages()); - EraseIt(it); + // If the block is device shared, it must have a count. + if ((cur_info.m_attribute & KMemoryAttribute::DeviceShared) != KMemoryAttribute::None && + cur_info.m_device_use_count == 0) { + return false; + } - it = prev_it; - block = prev; + // Advance the iterator. + prev = it++; + } + + // Our loop will miss checking the last block, potentially, so check it. + if (prev != m_memory_block_tree.cend()) { + const KMemoryInfo prev_info = prev->GetMemoryInfo(); + // If the block is ipc locked, it must have a count. + if ((prev_info.m_attribute & KMemoryAttribute::IpcLocked) != KMemoryAttribute::None && + prev_info.m_ipc_lock_count == 0) { + return false; + } + + // If the block is device shared, it must have a count. + if ((prev_info.m_attribute & KMemoryAttribute::DeviceShared) != KMemoryAttribute::None && + prev_info.m_device_use_count == 0) { + return false; } } - if (it != cend()) { - const KMemoryBlock* const next{&(*std::next(it))}; - - if (block->HasSameProperties(*next)) { - block->Add(next->GetNumPages()); - EraseIt(std::next(it)); - } - } + return true; } } // namespace Kernel diff --git a/src/core/hle/kernel/k_memory_block_manager.h b/src/core/hle/kernel/k_memory_block_manager.h index e14741b898..b4ee4e319d 100644 --- a/src/core/hle/kernel/k_memory_block_manager.h +++ b/src/core/hle/kernel/k_memory_block_manager.h @@ -4,63 +4,154 @@ #pragma once #include -#include +#include "common/common_funcs.h" #include "common/common_types.h" +#include "core/hle/kernel/k_dynamic_resource_manager.h" #include "core/hle/kernel/k_memory_block.h" namespace Kernel { +class KMemoryBlockManagerUpdateAllocator { +public: + static constexpr size_t MaxBlocks = 2; + +private: + KMemoryBlock* m_blocks[MaxBlocks]; + size_t m_index; + KMemoryBlockSlabManager* m_slab_manager; + +private: + Result Initialize(size_t num_blocks) { + // Check num blocks. + ASSERT(num_blocks <= MaxBlocks); + + // Set index. + m_index = MaxBlocks - num_blocks; + + // Allocate the blocks. + for (size_t i = 0; i < num_blocks && i < MaxBlocks; ++i) { + m_blocks[m_index + i] = m_slab_manager->Allocate(); + R_UNLESS(m_blocks[m_index + i] != nullptr, ResultOutOfResource); + } + + return ResultSuccess; + } + +public: + KMemoryBlockManagerUpdateAllocator(Result* out_result, KMemoryBlockSlabManager* sm, + size_t num_blocks = MaxBlocks) + : m_blocks(), m_index(MaxBlocks), m_slab_manager(sm) { + *out_result = this->Initialize(num_blocks); + } + + ~KMemoryBlockManagerUpdateAllocator() { + for (const auto& block : m_blocks) { + if (block != nullptr) { + m_slab_manager->Free(block); + } + } + } + + KMemoryBlock* Allocate() { + ASSERT(m_index < MaxBlocks); + ASSERT(m_blocks[m_index] != nullptr); + KMemoryBlock* block = nullptr; + std::swap(block, m_blocks[m_index++]); + return block; + } + + void Free(KMemoryBlock* block) { + ASSERT(m_index <= MaxBlocks); + ASSERT(block != nullptr); + if (m_index == 0) { + m_slab_manager->Free(block); + } else { + m_blocks[--m_index] = block; + } + } +}; + class KMemoryBlockManager final { public: - using MemoryBlockTree = std::list; + using MemoryBlockTree = + Common::IntrusiveRedBlackTreeBaseTraits::TreeType; + using MemoryBlockLockFunction = void (KMemoryBlock::*)(KMemoryPermission new_perm, bool left, + bool right); using iterator = MemoryBlockTree::iterator; using const_iterator = MemoryBlockTree::const_iterator; public: - KMemoryBlockManager(VAddr start_addr_, VAddr end_addr_); + KMemoryBlockManager(); + + using HostUnmapCallback = std::function; + + Result Initialize(VAddr st, VAddr nd, KMemoryBlockSlabManager* slab_manager); + void Finalize(KMemoryBlockSlabManager* slab_manager, HostUnmapCallback&& host_unmap_callback); iterator end() { - return memory_block_tree.end(); + return m_memory_block_tree.end(); } const_iterator end() const { - return memory_block_tree.end(); + return m_memory_block_tree.end(); } const_iterator cend() const { - return memory_block_tree.cend(); + return m_memory_block_tree.cend(); } - iterator FindIterator(VAddr addr); + VAddr FindFreeArea(VAddr region_start, size_t region_num_pages, size_t num_pages, + size_t alignment, size_t offset, size_t guard_pages) const; - VAddr FindFreeArea(VAddr region_start, std::size_t region_num_pages, std::size_t num_pages, - std::size_t align, std::size_t offset, std::size_t guard_pages); + void Update(KMemoryBlockManagerUpdateAllocator* allocator, VAddr address, size_t num_pages, + KMemoryState state, KMemoryPermission perm, KMemoryAttribute attr, + KMemoryBlockDisableMergeAttribute set_disable_attr, + KMemoryBlockDisableMergeAttribute clear_disable_attr); + void UpdateLock(KMemoryBlockManagerUpdateAllocator* allocator, VAddr address, size_t num_pages, + MemoryBlockLockFunction lock_func, KMemoryPermission perm); - void Update(VAddr addr, std::size_t num_pages, KMemoryState prev_state, - KMemoryPermission prev_perm, KMemoryAttribute prev_attribute, KMemoryState state, - KMemoryPermission perm, KMemoryAttribute attribute); + void UpdateIfMatch(KMemoryBlockManagerUpdateAllocator* allocator, VAddr address, + size_t num_pages, KMemoryState test_state, KMemoryPermission test_perm, + KMemoryAttribute test_attr, KMemoryState state, KMemoryPermission perm, + KMemoryAttribute attr); - void Update(VAddr addr, std::size_t num_pages, KMemoryState state, - KMemoryPermission perm = KMemoryPermission::None, - KMemoryAttribute attribute = KMemoryAttribute::None); + iterator FindIterator(VAddr address) const { + return m_memory_block_tree.find(KMemoryBlock( + address, 1, KMemoryState::Free, KMemoryPermission::None, KMemoryAttribute::None)); + } - using LockFunc = std::function; - void UpdateLock(VAddr addr, std::size_t num_pages, LockFunc&& lock_func, - KMemoryPermission perm); + const KMemoryBlock* FindBlock(VAddr address) const { + if (const_iterator it = this->FindIterator(address); it != m_memory_block_tree.end()) { + return std::addressof(*it); + } - using IterateFunc = std::function; - void IterateForRange(VAddr start, VAddr end, IterateFunc&& func); + return nullptr; + } - KMemoryBlock& FindBlock(VAddr addr) { - return *FindIterator(addr); + // Debug. + bool CheckState() const; + +private: + void CoalesceForUpdate(KMemoryBlockManagerUpdateAllocator* allocator, VAddr address, + size_t num_pages); + + MemoryBlockTree m_memory_block_tree; + VAddr m_start_address{}; + VAddr m_end_address{}; +}; + +class KScopedMemoryBlockManagerAuditor { +public: + explicit KScopedMemoryBlockManagerAuditor(KMemoryBlockManager* m) : m_manager(m) { + ASSERT(m_manager->CheckState()); + } + explicit KScopedMemoryBlockManagerAuditor(KMemoryBlockManager& m) + : KScopedMemoryBlockManagerAuditor(std::addressof(m)) {} + ~KScopedMemoryBlockManagerAuditor() { + ASSERT(m_manager->CheckState()); } private: - void MergeAdjacent(iterator it, iterator& next_it); - - [[maybe_unused]] const VAddr start_addr; - [[maybe_unused]] const VAddr end_addr; - - MemoryBlockTree memory_block_tree; + KMemoryBlockManager* m_manager; }; } // namespace Kernel From 58eb6953d1417d667af36461ac8391e005f49457 Mon Sep 17 00:00:00 2001 From: bunnei Date: Fri, 9 Sep 2022 21:17:52 -0700 Subject: [PATCH 28/91] core: hle: kernel: k_memory_block: Update. --- src/core/hle/kernel/k_memory_block.h | 514 ++++++++++++++++++++------- src/core/hle/service/ldr/ldr.cpp | 4 +- 2 files changed, 395 insertions(+), 123 deletions(-) diff --git a/src/core/hle/kernel/k_memory_block.h b/src/core/hle/kernel/k_memory_block.h index 18df1f836a..9444f6bd28 100644 --- a/src/core/hle/kernel/k_memory_block.h +++ b/src/core/hle/kernel/k_memory_block.h @@ -6,6 +6,7 @@ #include "common/alignment.h" #include "common/assert.h" #include "common/common_types.h" +#include "common/intrusive_red_black_tree.h" #include "core/hle/kernel/memory_types.h" #include "core/hle/kernel/svc_types.h" @@ -168,9 +169,8 @@ constexpr KMemoryPermission ConvertToKMemoryPermission(Svc::MemoryPermission per enum class KMemoryAttribute : u8 { None = 0x00, - Mask = 0x7F, - All = Mask, - DontCareMask = 0x80, + All = 0xFF, + UserMask = All, Locked = static_cast(Svc::MemoryAttribute::Locked), IpcLocked = static_cast(Svc::MemoryAttribute::IpcLocked), @@ -178,76 +178,112 @@ enum class KMemoryAttribute : u8 { Uncached = static_cast(Svc::MemoryAttribute::Uncached), SetMask = Uncached, - - IpcAndDeviceMapped = IpcLocked | DeviceShared, - LockedAndIpcLocked = Locked | IpcLocked, - DeviceSharedAndUncached = DeviceShared | Uncached }; DECLARE_ENUM_FLAG_OPERATORS(KMemoryAttribute); -static_assert((static_cast(KMemoryAttribute::Mask) & - static_cast(KMemoryAttribute::DontCareMask)) == 0); +enum class KMemoryBlockDisableMergeAttribute : u8 { + None = 0, + Normal = (1u << 0), + DeviceLeft = (1u << 1), + IpcLeft = (1u << 2), + Locked = (1u << 3), + DeviceRight = (1u << 4), + + AllLeft = Normal | DeviceLeft | IpcLeft | Locked, + AllRight = DeviceRight, +}; +DECLARE_ENUM_FLAG_OPERATORS(KMemoryBlockDisableMergeAttribute); struct KMemoryInfo { - VAddr addr{}; - std::size_t size{}; - KMemoryState state{}; - KMemoryPermission perm{}; - KMemoryAttribute attribute{}; - KMemoryPermission original_perm{}; - u16 ipc_lock_count{}; - u16 device_use_count{}; + uintptr_t m_address; + size_t m_size; + KMemoryState m_state; + u16 m_device_disable_merge_left_count; + u16 m_device_disable_merge_right_count; + u16 m_ipc_lock_count; + u16 m_device_use_count; + u16 m_ipc_disable_merge_count; + KMemoryPermission m_permission; + KMemoryAttribute m_attribute; + KMemoryPermission m_original_permission; + KMemoryBlockDisableMergeAttribute m_disable_merge_attribute; constexpr Svc::MemoryInfo GetSvcMemoryInfo() const { return { - addr, - size, - static_cast(state & KMemoryState::Mask), - static_cast(attribute & KMemoryAttribute::Mask), - static_cast(perm & KMemoryPermission::UserMask), - ipc_lock_count, - device_use_count, + .addr = m_address, + .size = m_size, + .state = static_cast(m_state & KMemoryState::Mask), + .attr = static_cast(m_attribute & KMemoryAttribute::UserMask), + .perm = static_cast(m_permission & KMemoryPermission::UserMask), + .ipc_refcount = m_ipc_lock_count, + .device_refcount = m_device_use_count, + .padding = {}, }; } - constexpr VAddr GetAddress() const { - return addr; + constexpr uintptr_t GetAddress() const { + return m_address; } - constexpr std::size_t GetSize() const { - return size; + + constexpr size_t GetSize() const { + return m_size; } - constexpr std::size_t GetNumPages() const { - return GetSize() / PageSize; + + constexpr size_t GetNumPages() const { + return this->GetSize() / PageSize; } - constexpr VAddr GetEndAddress() const { - return GetAddress() + GetSize(); + + constexpr uintptr_t GetEndAddress() const { + return this->GetAddress() + this->GetSize(); } - constexpr VAddr GetLastAddress() const { - return GetEndAddress() - 1; + + constexpr uintptr_t GetLastAddress() const { + return this->GetEndAddress() - 1; } + + constexpr u16 GetIpcLockCount() const { + return m_ipc_lock_count; + } + + constexpr u16 GetIpcDisableMergeCount() const { + return m_ipc_disable_merge_count; + } + constexpr KMemoryState GetState() const { - return state; - } - constexpr KMemoryAttribute GetAttribute() const { - return attribute; + return m_state; } + constexpr KMemoryPermission GetPermission() const { - return perm; + return m_permission; + } + + constexpr KMemoryPermission GetOriginalPermission() const { + return m_original_permission; + } + + constexpr KMemoryAttribute GetAttribute() const { + return m_attribute; + } + + constexpr KMemoryBlockDisableMergeAttribute GetDisableMergeAttribute() const { + return m_disable_merge_attribute; } }; -class KMemoryBlock final { - friend class KMemoryBlockManager; - +class KMemoryBlock : public Common::IntrusiveRedBlackTreeBaseNode { private: - VAddr addr{}; - std::size_t num_pages{}; - KMemoryState state{KMemoryState::None}; - u16 ipc_lock_count{}; - u16 device_use_count{}; - KMemoryPermission perm{KMemoryPermission::None}; - KMemoryPermission original_perm{KMemoryPermission::None}; - KMemoryAttribute attribute{KMemoryAttribute::None}; + u16 m_device_disable_merge_left_count; + u16 m_device_disable_merge_right_count; + VAddr m_address; + size_t m_num_pages; + KMemoryState m_memory_state; + u16 m_ipc_lock_count; + u16 m_device_use_count; + u16 m_ipc_disable_merge_count; + KMemoryPermission m_permission; + KMemoryPermission m_original_permission; + KMemoryAttribute m_attribute; + KMemoryBlockDisableMergeAttribute m_disable_merge_attribute; public: static constexpr int Compare(const KMemoryBlock& lhs, const KMemoryBlock& rhs) { @@ -261,113 +297,349 @@ public: } public: - constexpr KMemoryBlock() = default; - constexpr KMemoryBlock(VAddr addr_, std::size_t num_pages_, KMemoryState state_, - KMemoryPermission perm_, KMemoryAttribute attribute_) - : addr{addr_}, num_pages(num_pages_), state{state_}, perm{perm_}, attribute{attribute_} {} - constexpr VAddr GetAddress() const { - return addr; + return m_address; } - constexpr std::size_t GetNumPages() const { - return num_pages; + constexpr size_t GetNumPages() const { + return m_num_pages; } - constexpr std::size_t GetSize() const { - return GetNumPages() * PageSize; + constexpr size_t GetSize() const { + return this->GetNumPages() * PageSize; } constexpr VAddr GetEndAddress() const { - return GetAddress() + GetSize(); + return this->GetAddress() + this->GetSize(); } constexpr VAddr GetLastAddress() const { - return GetEndAddress() - 1; + return this->GetEndAddress() - 1; + } + + constexpr u16 GetIpcLockCount() const { + return m_ipc_lock_count; + } + + constexpr u16 GetIpcDisableMergeCount() const { + return m_ipc_disable_merge_count; + } + + constexpr KMemoryPermission GetPermission() const { + return m_permission; + } + + constexpr KMemoryPermission GetOriginalPermission() const { + return m_original_permission; + } + + constexpr KMemoryAttribute GetAttribute() const { + return m_attribute; } constexpr KMemoryInfo GetMemoryInfo() const { return { - GetAddress(), GetSize(), state, perm, - attribute, original_perm, ipc_lock_count, device_use_count, + .m_address = this->GetAddress(), + .m_size = this->GetSize(), + .m_state = m_memory_state, + .m_device_disable_merge_left_count = m_device_disable_merge_left_count, + .m_device_disable_merge_right_count = m_device_disable_merge_right_count, + .m_ipc_lock_count = m_ipc_lock_count, + .m_device_use_count = m_device_use_count, + .m_ipc_disable_merge_count = m_ipc_disable_merge_count, + .m_permission = m_permission, + .m_attribute = m_attribute, + .m_original_permission = m_original_permission, + .m_disable_merge_attribute = m_disable_merge_attribute, }; } - void ShareToDevice(KMemoryPermission /*new_perm*/) { - ASSERT((attribute & KMemoryAttribute::DeviceShared) == KMemoryAttribute::DeviceShared || - device_use_count == 0); - attribute |= KMemoryAttribute::DeviceShared; - const u16 new_use_count{++device_use_count}; - ASSERT(new_use_count > 0); +public: + explicit KMemoryBlock() = default; + + constexpr KMemoryBlock(VAddr addr, size_t np, KMemoryState ms, KMemoryPermission p, + KMemoryAttribute attr) + : Common::IntrusiveRedBlackTreeBaseNode(), + m_device_disable_merge_left_count(), m_device_disable_merge_right_count(), + m_address(addr), m_num_pages(np), m_memory_state(ms), m_ipc_lock_count(0), + m_device_use_count(0), m_ipc_disable_merge_count(), m_permission(p), + m_original_permission(KMemoryPermission::None), m_attribute(attr), + m_disable_merge_attribute() {} + + constexpr void Initialize(VAddr addr, size_t np, KMemoryState ms, KMemoryPermission p, + KMemoryAttribute attr) { + m_device_disable_merge_left_count = 0; + m_device_disable_merge_right_count = 0; + m_address = addr; + m_num_pages = np; + m_memory_state = ms; + m_ipc_lock_count = 0; + m_device_use_count = 0; + m_permission = p; + m_original_permission = KMemoryPermission::None; + m_attribute = attr; + m_disable_merge_attribute = KMemoryBlockDisableMergeAttribute::None; } - void UnshareToDevice(KMemoryPermission /*new_perm*/) { - ASSERT((attribute & KMemoryAttribute::DeviceShared) == KMemoryAttribute::DeviceShared); - const u16 prev_use_count{device_use_count--}; - ASSERT(prev_use_count > 0); - if (prev_use_count == 1) { - attribute &= ~KMemoryAttribute::DeviceShared; - } - } - -private: constexpr bool HasProperties(KMemoryState s, KMemoryPermission p, KMemoryAttribute a) const { - constexpr KMemoryAttribute AttributeIgnoreMask{KMemoryAttribute::DontCareMask | - KMemoryAttribute::IpcLocked | - KMemoryAttribute::DeviceShared}; - return state == s && perm == p && - (attribute | AttributeIgnoreMask) == (a | AttributeIgnoreMask); + constexpr auto AttributeIgnoreMask = + KMemoryAttribute::IpcLocked | KMemoryAttribute::DeviceShared; + return m_memory_state == s && m_permission == p && + (m_attribute | AttributeIgnoreMask) == (a | AttributeIgnoreMask); } constexpr bool HasSameProperties(const KMemoryBlock& rhs) const { - return state == rhs.state && perm == rhs.perm && original_perm == rhs.original_perm && - attribute == rhs.attribute && ipc_lock_count == rhs.ipc_lock_count && - device_use_count == rhs.device_use_count; + return m_memory_state == rhs.m_memory_state && m_permission == rhs.m_permission && + m_original_permission == rhs.m_original_permission && + m_attribute == rhs.m_attribute && m_ipc_lock_count == rhs.m_ipc_lock_count && + m_device_use_count == rhs.m_device_use_count; } - constexpr bool Contains(VAddr start) const { - return GetAddress() <= start && start <= GetEndAddress(); + constexpr bool CanMergeWith(const KMemoryBlock& rhs) const { + return this->HasSameProperties(rhs) && + (m_disable_merge_attribute & KMemoryBlockDisableMergeAttribute::AllRight) == + KMemoryBlockDisableMergeAttribute::None && + (rhs.m_disable_merge_attribute & KMemoryBlockDisableMergeAttribute::AllLeft) == + KMemoryBlockDisableMergeAttribute::None; } - constexpr void Add(std::size_t count) { - ASSERT(count > 0); - ASSERT(GetAddress() + count * PageSize - 1 < GetEndAddress() + count * PageSize - 1); - - num_pages += count; + constexpr bool Contains(VAddr addr) const { + return this->GetAddress() <= addr && addr <= this->GetEndAddress(); } - constexpr void Update(KMemoryState new_state, KMemoryPermission new_perm, - KMemoryAttribute new_attribute) { - ASSERT(original_perm == KMemoryPermission::None); - ASSERT((attribute & KMemoryAttribute::IpcLocked) == KMemoryAttribute::None); + constexpr void Add(const KMemoryBlock& added_block) { + ASSERT(added_block.GetNumPages() > 0); + ASSERT(this->GetAddress() + added_block.GetSize() - 1 < + this->GetEndAddress() + added_block.GetSize() - 1); - state = new_state; - perm = new_perm; - - attribute = static_cast( - new_attribute | - (attribute & (KMemoryAttribute::IpcLocked | KMemoryAttribute::DeviceShared))); + m_num_pages += added_block.GetNumPages(); + m_disable_merge_attribute = static_cast( + m_disable_merge_attribute | added_block.m_disable_merge_attribute); + m_device_disable_merge_right_count = added_block.m_device_disable_merge_right_count; } - constexpr KMemoryBlock Split(VAddr split_addr) { - ASSERT(GetAddress() < split_addr); - ASSERT(Contains(split_addr)); - ASSERT(Common::IsAligned(split_addr, PageSize)); + constexpr void Update(KMemoryState s, KMemoryPermission p, KMemoryAttribute a, + bool set_disable_merge_attr, u8 set_mask, u8 clear_mask) { + ASSERT(m_original_permission == KMemoryPermission::None); + ASSERT((m_attribute & KMemoryAttribute::IpcLocked) == KMemoryAttribute::None); - KMemoryBlock block; - block.addr = addr; - block.num_pages = (split_addr - GetAddress()) / PageSize; - block.state = state; - block.ipc_lock_count = ipc_lock_count; - block.device_use_count = device_use_count; - block.perm = perm; - block.original_perm = original_perm; - block.attribute = attribute; + m_memory_state = s; + m_permission = p; + m_attribute = static_cast( + a | (m_attribute & (KMemoryAttribute::IpcLocked | KMemoryAttribute::DeviceShared))); - addr = split_addr; - num_pages -= block.num_pages; + if (set_disable_merge_attr && set_mask != 0) { + m_disable_merge_attribute = m_disable_merge_attribute | + static_cast(set_mask); + } + if (clear_mask != 0) { + m_disable_merge_attribute = m_disable_merge_attribute & + static_cast(~clear_mask); + } + } - return block; + constexpr void Split(KMemoryBlock* block, VAddr addr) { + ASSERT(this->GetAddress() < addr); + ASSERT(this->Contains(addr)); + ASSERT(Common::IsAligned(addr, PageSize)); + + block->m_address = m_address; + block->m_num_pages = (addr - this->GetAddress()) / PageSize; + block->m_memory_state = m_memory_state; + block->m_ipc_lock_count = m_ipc_lock_count; + block->m_device_use_count = m_device_use_count; + block->m_permission = m_permission; + block->m_original_permission = m_original_permission; + block->m_attribute = m_attribute; + block->m_disable_merge_attribute = static_cast( + m_disable_merge_attribute & KMemoryBlockDisableMergeAttribute::AllLeft); + block->m_ipc_disable_merge_count = m_ipc_disable_merge_count; + block->m_device_disable_merge_left_count = m_device_disable_merge_left_count; + block->m_device_disable_merge_right_count = 0; + + m_address = addr; + m_num_pages -= block->m_num_pages; + + m_ipc_disable_merge_count = 0; + m_device_disable_merge_left_count = 0; + m_disable_merge_attribute = static_cast( + m_disable_merge_attribute & KMemoryBlockDisableMergeAttribute::AllRight); + } + + constexpr void UpdateDeviceDisableMergeStateForShareLeft( + [[maybe_unused]] KMemoryPermission new_perm, bool left, [[maybe_unused]] bool right) { + if (left) { + m_disable_merge_attribute = static_cast( + m_disable_merge_attribute | KMemoryBlockDisableMergeAttribute::DeviceLeft); + const u16 new_device_disable_merge_left_count = ++m_device_disable_merge_left_count; + ASSERT(new_device_disable_merge_left_count > 0); + } + } + + constexpr void UpdateDeviceDisableMergeStateForShareRight( + [[maybe_unused]] KMemoryPermission new_perm, [[maybe_unused]] bool left, bool right) { + if (right) { + m_disable_merge_attribute = static_cast( + m_disable_merge_attribute | KMemoryBlockDisableMergeAttribute::DeviceRight); + const u16 new_device_disable_merge_right_count = ++m_device_disable_merge_right_count; + ASSERT(new_device_disable_merge_right_count > 0); + } + } + + constexpr void UpdateDeviceDisableMergeStateForShare(KMemoryPermission new_perm, bool left, + bool right) { + this->UpdateDeviceDisableMergeStateForShareLeft(new_perm, left, right); + this->UpdateDeviceDisableMergeStateForShareRight(new_perm, left, right); + } + + constexpr void ShareToDevice([[maybe_unused]] KMemoryPermission new_perm, bool left, + bool right) { + // We must either be shared or have a zero lock count. + ASSERT((m_attribute & KMemoryAttribute::DeviceShared) == KMemoryAttribute::DeviceShared || + m_device_use_count == 0); + + // Share. + const u16 new_count = ++m_device_use_count; + ASSERT(new_count > 0); + + m_attribute = static_cast(m_attribute | KMemoryAttribute::DeviceShared); + + this->UpdateDeviceDisableMergeStateForShare(new_perm, left, right); + } + + constexpr void UpdateDeviceDisableMergeStateForUnshareLeft( + [[maybe_unused]] KMemoryPermission new_perm, bool left, [[maybe_unused]] bool right) { + + if (left) { + if (!m_device_disable_merge_left_count) { + return; + } + --m_device_disable_merge_left_count; + } + + m_device_disable_merge_left_count = + std::min(m_device_disable_merge_left_count, m_device_use_count); + + if (m_device_disable_merge_left_count == 0) { + m_disable_merge_attribute = static_cast( + m_disable_merge_attribute & ~KMemoryBlockDisableMergeAttribute::DeviceLeft); + } + } + + constexpr void UpdateDeviceDisableMergeStateForUnshareRight( + [[maybe_unused]] KMemoryPermission new_perm, [[maybe_unused]] bool left, bool right) { + if (right) { + const u16 old_device_disable_merge_right_count = m_device_disable_merge_right_count--; + ASSERT(old_device_disable_merge_right_count > 0); + if (old_device_disable_merge_right_count == 1) { + m_disable_merge_attribute = static_cast( + m_disable_merge_attribute & ~KMemoryBlockDisableMergeAttribute::DeviceRight); + } + } + } + + constexpr void UpdateDeviceDisableMergeStateForUnshare(KMemoryPermission new_perm, bool left, + bool right) { + this->UpdateDeviceDisableMergeStateForUnshareLeft(new_perm, left, right); + this->UpdateDeviceDisableMergeStateForUnshareRight(new_perm, left, right); + } + + constexpr void UnshareToDevice([[maybe_unused]] KMemoryPermission new_perm, bool left, + bool right) { + // We must be shared. + ASSERT((m_attribute & KMemoryAttribute::DeviceShared) == KMemoryAttribute::DeviceShared); + + // Unhare. + const u16 old_count = m_device_use_count--; + ASSERT(old_count > 0); + + if (old_count == 1) { + m_attribute = + static_cast(m_attribute & ~KMemoryAttribute::DeviceShared); + } + + this->UpdateDeviceDisableMergeStateForUnshare(new_perm, left, right); + } + + constexpr void UnshareToDeviceRight([[maybe_unused]] KMemoryPermission new_perm, bool left, + bool right) { + + // We must be shared. + ASSERT((m_attribute & KMemoryAttribute::DeviceShared) == KMemoryAttribute::DeviceShared); + + // Unhare. + const u16 old_count = m_device_use_count--; + ASSERT(old_count > 0); + + if (old_count == 1) { + m_attribute = + static_cast(m_attribute & ~KMemoryAttribute::DeviceShared); + } + + this->UpdateDeviceDisableMergeStateForUnshareRight(new_perm, left, right); + } + + constexpr void LockForIpc(KMemoryPermission new_perm, bool left, [[maybe_unused]] bool right) { + // We must either be locked or have a zero lock count. + ASSERT((m_attribute & KMemoryAttribute::IpcLocked) == KMemoryAttribute::IpcLocked || + m_ipc_lock_count == 0); + + // Lock. + const u16 new_lock_count = ++m_ipc_lock_count; + ASSERT(new_lock_count > 0); + + // If this is our first lock, update our permissions. + if (new_lock_count == 1) { + ASSERT(m_original_permission == KMemoryPermission::None); + ASSERT((m_permission | new_perm | KMemoryPermission::NotMapped) == + (m_permission | KMemoryPermission::NotMapped)); + ASSERT((m_permission & KMemoryPermission::UserExecute) != + KMemoryPermission::UserExecute || + (new_perm == KMemoryPermission::UserRead)); + m_original_permission = m_permission; + m_permission = static_cast( + (new_perm & KMemoryPermission::IpcLockChangeMask) | + (m_original_permission & ~KMemoryPermission::IpcLockChangeMask)); + } + m_attribute = static_cast(m_attribute | KMemoryAttribute::IpcLocked); + + if (left) { + m_disable_merge_attribute = static_cast( + m_disable_merge_attribute | KMemoryBlockDisableMergeAttribute::IpcLeft); + const u16 new_ipc_disable_merge_count = ++m_ipc_disable_merge_count; + ASSERT(new_ipc_disable_merge_count > 0); + } + } + + constexpr void UnlockForIpc([[maybe_unused]] KMemoryPermission new_perm, bool left, + [[maybe_unused]] bool right) { + // We must be locked. + ASSERT((m_attribute & KMemoryAttribute::IpcLocked) == KMemoryAttribute::IpcLocked); + + // Unlock. + const u16 old_lock_count = m_ipc_lock_count--; + ASSERT(old_lock_count > 0); + + // If this is our last unlock, update our permissions. + if (old_lock_count == 1) { + ASSERT(m_original_permission != KMemoryPermission::None); + m_permission = m_original_permission; + m_original_permission = KMemoryPermission::None; + m_attribute = static_cast(m_attribute & ~KMemoryAttribute::IpcLocked); + } + + if (left) { + const u16 old_ipc_disable_merge_count = m_ipc_disable_merge_count--; + ASSERT(old_ipc_disable_merge_count > 0); + if (old_ipc_disable_merge_count == 1) { + m_disable_merge_attribute = static_cast( + m_disable_merge_attribute & ~KMemoryBlockDisableMergeAttribute::IpcLeft); + } + } + } + + constexpr KMemoryBlockDisableMergeAttribute GetDisableMergeAttribute() const { + return m_disable_merge_attribute; } }; static_assert(std::is_trivially_destructible::value); diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp index becd6d1b9f..652441bc29 100644 --- a/src/core/hle/service/ldr/ldr.cpp +++ b/src/core/hle/service/ldr/ldr.cpp @@ -290,7 +290,7 @@ public: const std::size_t padding_size{page_table.GetNumGuardPages() * Kernel::PageSize}; const auto start_info{page_table.QueryInfo(start - 1)}; - if (start_info.state != Kernel::KMemoryState::Free) { + if (start_info.GetState() != Kernel::KMemoryState::Free) { return {}; } @@ -300,7 +300,7 @@ public: const auto end_info{page_table.QueryInfo(start + size)}; - if (end_info.state != Kernel::KMemoryState::Free) { + if (end_info.GetState() != Kernel::KMemoryState::Free) { return {}; } From ed591934fbffa32af0151302fd07e9fce776eb17 Mon Sep 17 00:00:00 2001 From: bunnei Date: Fri, 9 Sep 2022 21:38:28 -0700 Subject: [PATCH 29/91] core: hle: kernel: k_page_table: Update, and integrate with new KMemoryBlockManager/SlabManager. --- src/core/hle/kernel/k_page_table.cpp | 619 ++++++++++++++++----------- src/core/hle/kernel/k_page_table.h | 25 +- 2 files changed, 393 insertions(+), 251 deletions(-) diff --git a/src/core/hle/kernel/k_page_table.cpp b/src/core/hle/kernel/k_page_table.cpp index 8ebb753381..2cf46af0a4 100644 --- a/src/core/hle/kernel/k_page_table.cpp +++ b/src/core/hle/kernel/k_page_table.cpp @@ -49,6 +49,7 @@ KPageTable::~KPageTable() = default; Result KPageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_type, bool enable_aslr, VAddr code_addr, std::size_t code_size, + KMemoryBlockSlabManager* mem_block_slab_manager, KMemoryManager::Pool pool) { const auto GetSpaceStart = [this](KAddressSpaceInfo::Type type) { @@ -113,6 +114,7 @@ Result KPageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_type address_space_start = start; address_space_end = end; is_kernel = false; + memory_block_slab_manager = mem_block_slab_manager; // Determine the region we can place our undetermineds in VAddr alloc_start{}; @@ -254,7 +256,14 @@ Result KPageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_type page_table_impl.Resize(address_space_width, PageBits); - return InitializeMemoryLayout(start, end); + return memory_block_manager.Initialize(address_space_start, address_space_end, + memory_block_slab_manager); +} + +void KPageTable::Finalize() { + memory_block_manager.Finalize(memory_block_slab_manager, [&](VAddr addr, u64 size) { + system.Memory().UnmapRegion(page_table_impl, addr, size); + }); } Result KPageTable::MapProcessCode(VAddr addr, std::size_t num_pages, KMemoryState state, @@ -271,6 +280,13 @@ Result KPageTable::MapProcessCode(VAddr addr, std::size_t num_pages, KMemoryStat R_TRY(this->CheckMemoryState(addr, size, KMemoryState::All, KMemoryState::Free, KMemoryPermission::None, KMemoryPermission::None, KMemoryAttribute::None, KMemoryAttribute::None)); + + // Create an update allocator. + Result allocator_result{ResultSuccess}; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), + memory_block_slab_manager); + + // Allocate and open. KPageGroup pg; R_TRY(system.Kernel().MemoryManager().AllocateAndOpen( &pg, num_pages, @@ -278,7 +294,10 @@ Result KPageTable::MapProcessCode(VAddr addr, std::size_t num_pages, KMemoryStat R_TRY(Operate(addr, num_pages, pg, OperationType::MapGroup)); - block_manager->Update(addr, num_pages, state, perm); + // Update the blocks. + memory_block_manager.Update(std::addressof(allocator), addr, num_pages, state, perm, + KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal, + KMemoryBlockDisableMergeAttribute::None); return ResultSuccess; } @@ -307,6 +326,18 @@ Result KPageTable::MapCodeMemory(VAddr dst_address, VAddr src_address, std::size KMemoryPermission::None, KMemoryAttribute::None, KMemoryAttribute::None)); + // Create an update allocator for the source. + Result src_allocator_result{ResultSuccess}; + KMemoryBlockManagerUpdateAllocator src_allocator( + std::addressof(src_allocator_result), memory_block_slab_manager, num_src_allocator_blocks); + R_TRY(src_allocator_result); + + // Create an update allocator for the destination. + Result dst_allocator_result{ResultSuccess}; + KMemoryBlockManagerUpdateAllocator dst_allocator( + std::addressof(dst_allocator_result), memory_block_slab_manager, num_dst_allocator_blocks); + R_TRY(dst_allocator_result); + // Map the code memory. { // Determine the number of pages being operated on. @@ -335,10 +366,14 @@ Result KPageTable::MapCodeMemory(VAddr dst_address, VAddr src_address, std::size unprot_guard.Cancel(); // Apply the memory block updates. - block_manager->Update(src_address, num_pages, src_state, new_perm, - KMemoryAttribute::Locked); - block_manager->Update(dst_address, num_pages, KMemoryState::AliasCode, new_perm, - KMemoryAttribute::None); + memory_block_manager.Update(std::addressof(src_allocator), src_address, num_pages, + src_state, new_perm, KMemoryAttribute::Locked, + KMemoryBlockDisableMergeAttribute::Locked, + KMemoryBlockDisableMergeAttribute::None); + memory_block_manager.Update(std::addressof(dst_allocator), dst_address, num_pages, + KMemoryState::AliasCode, new_perm, KMemoryAttribute::None, + KMemoryBlockDisableMergeAttribute::Normal, + KMemoryBlockDisableMergeAttribute::None); } return ResultSuccess; @@ -370,7 +405,7 @@ Result KPageTable::UnmapCodeMemory(VAddr dst_address, VAddr src_address, std::si // Determine whether any pages being unmapped are code. bool any_code_pages = false; { - KMemoryBlockManager::const_iterator it = block_manager->FindIterator(dst_address); + KMemoryBlockManager::const_iterator it = memory_block_manager.FindIterator(dst_address); while (true) { // Get the memory info. const KMemoryInfo info = it->GetMemoryInfo(); @@ -408,6 +443,20 @@ Result KPageTable::UnmapCodeMemory(VAddr dst_address, VAddr src_address, std::si // Determine the number of pages being operated on. const std::size_t num_pages = size / PageSize; + // Create an update allocator for the source. + Result src_allocator_result{ResultSuccess}; + KMemoryBlockManagerUpdateAllocator src_allocator(std::addressof(src_allocator_result), + memory_block_slab_manager, + num_src_allocator_blocks); + R_TRY(src_allocator_result); + + // Create an update allocator for the destination. + Result dst_allocator_result{ResultSuccess}; + KMemoryBlockManagerUpdateAllocator dst_allocator(std::addressof(dst_allocator_result), + memory_block_slab_manager, + num_dst_allocator_blocks); + R_TRY(dst_allocator_result); + // Unmap the aliased copy of the pages. R_TRY(Operate(dst_address, num_pages, KMemoryPermission::None, OperationType::Unmap)); @@ -416,9 +465,14 @@ Result KPageTable::UnmapCodeMemory(VAddr dst_address, VAddr src_address, std::si OperationType::ChangePermissions)); // Apply the memory block updates. - block_manager->Update(dst_address, num_pages, KMemoryState::None); - block_manager->Update(src_address, num_pages, KMemoryState::Normal, - KMemoryPermission::UserReadWrite); + memory_block_manager.Update(std::addressof(dst_allocator), dst_address, num_pages, + KMemoryState::None, KMemoryPermission::None, + KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::None, + KMemoryBlockDisableMergeAttribute::Normal); + memory_block_manager.Update(std::addressof(src_allocator), src_address, num_pages, + KMemoryState::Normal, KMemoryPermission::UserReadWrite, + KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::None, + KMemoryBlockDisableMergeAttribute::Locked); // Note that we reprotected pages. reprotected_pages = true; @@ -434,55 +488,12 @@ VAddr KPageTable::FindFreeArea(VAddr region_start, std::size_t region_num_pages, if (num_pages <= region_num_pages) { if (this->IsAslrEnabled()) { - // Try to directly find a free area up to 8 times. - for (std::size_t i = 0; i < 8; i++) { - const std::size_t random_offset = - KSystemControl::GenerateRandomRange( - 0, (region_num_pages - num_pages - guard_pages) * PageSize / alignment) * - alignment; - const VAddr candidate = - Common::AlignDown((region_start + random_offset), alignment) + offset; - - KMemoryInfo info = this->QueryInfoImpl(candidate); - - if (info.state != KMemoryState::Free) { - continue; - } - if (region_start > candidate) { - continue; - } - if (info.GetAddress() + guard_pages * PageSize > candidate) { - continue; - } - - const VAddr candidate_end = candidate + (num_pages + guard_pages) * PageSize - 1; - if (candidate_end > info.GetLastAddress()) { - continue; - } - if (candidate_end > region_start + region_num_pages * PageSize - 1) { - continue; - } - - address = candidate; - break; - } - // Fall back to finding the first free area with a random offset. - if (address == 0) { - // NOTE: Nintendo does not account for guard pages here. - // This may theoretically cause an offset to be chosen that cannot be mapped. We - // will account for guard pages. - const std::size_t offset_pages = KSystemControl::GenerateRandomRange( - 0, region_num_pages - num_pages - guard_pages); - address = block_manager->FindFreeArea(region_start + offset_pages * PageSize, - region_num_pages - offset_pages, num_pages, - alignment, offset, guard_pages); - } + UNIMPLEMENTED(); } - // Find the first free area. if (address == 0) { - address = block_manager->FindFreeArea(region_start, region_num_pages, num_pages, - alignment, offset, guard_pages); + address = memory_block_manager.FindFreeArea(region_start, region_num_pages, num_pages, + alignment, offset, guard_pages); } } @@ -649,11 +660,19 @@ Result KPageTable::UnmapProcessMemory(VAddr dst_addr, std::size_t size, KPageTab KMemoryPermission::None, KMemoryAttribute::All, KMemoryAttribute::None)); + // Create an update allocator. + Result allocator_result{ResultSuccess}; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), + memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + CASCADE_CODE(Operate(dst_addr, num_pages, KMemoryPermission::None, OperationType::Unmap)); // Apply the memory block update. - block_manager->Update(dst_addr, num_pages, KMemoryState::Free, KMemoryPermission::None, - KMemoryAttribute::None); + memory_block_manager.Update(std::addressof(allocator), dst_addr, num_pages, KMemoryState::Free, + KMemoryPermission::None, KMemoryAttribute::None, + KMemoryBlockDisableMergeAttribute::None, + KMemoryBlockDisableMergeAttribute::Normal); system.InvalidateCpuInstructionCaches(); @@ -682,10 +701,10 @@ Result KPageTable::MapPhysicalMemory(VAddr address, std::size_t size) { cur_address = address; mapped_size = 0; - auto it = block_manager->FindIterator(cur_address); + auto it = memory_block_manager.FindIterator(cur_address); while (true) { // Check that the iterator is valid. - ASSERT(it != block_manager->end()); + ASSERT(it != memory_block_manager.end()); // Get the memory info. const KMemoryInfo info = it->GetMemoryInfo(); @@ -739,10 +758,10 @@ Result KPageTable::MapPhysicalMemory(VAddr address, std::size_t size) { size_t checked_mapped_size = 0; cur_address = address; - auto it = block_manager->FindIterator(cur_address); + auto it = memory_block_manager.FindIterator(cur_address); while (true) { // Check that the iterator is valid. - ASSERT(it != block_manager->end()); + ASSERT(it != memory_block_manager.end()); // Get the memory info. const KMemoryInfo info = it->GetMemoryInfo(); @@ -782,6 +801,14 @@ Result KPageTable::MapPhysicalMemory(VAddr address, std::size_t size) { } } + // Create an update allocator. + ASSERT(num_allocator_blocks <= KMemoryBlockManagerUpdateAllocator::MaxBlocks); + Result allocator_result{ResultSuccess}; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), + memory_block_slab_manager, + num_allocator_blocks); + R_TRY(allocator_result); + // Reset the current tracking address, and make sure we clean up on failure. cur_address = address; auto unmap_guard = detail::ScopeExit([&] { @@ -791,10 +818,10 @@ Result KPageTable::MapPhysicalMemory(VAddr address, std::size_t size) { // Iterate, unmapping the pages. cur_address = address; - auto it = block_manager->FindIterator(cur_address); + auto it = memory_block_manager.FindIterator(cur_address); while (true) { // Check that the iterator is valid. - ASSERT(it != block_manager->end()); + ASSERT(it != memory_block_manager.end()); // Get the memory info. const KMemoryInfo info = it->GetMemoryInfo(); @@ -830,10 +857,10 @@ Result KPageTable::MapPhysicalMemory(VAddr address, std::size_t size) { PAddr pg_phys_addr = pg_it->GetAddress(); size_t pg_pages = pg_it->GetNumPages(); - auto it = block_manager->FindIterator(cur_address); + auto it = memory_block_manager.FindIterator(cur_address); while (true) { // Check that the iterator is valid. - ASSERT(it != block_manager->end()); + ASSERT(it != memory_block_manager.end()); // Get the memory info. const KMemoryInfo info = it->GetMemoryInfo(); @@ -889,10 +916,10 @@ Result KPageTable::MapPhysicalMemory(VAddr address, std::size_t size) { mapped_physical_memory_size += (size - mapped_size); // Update the relevant memory blocks. - block_manager->Update(address, size / PageSize, KMemoryState::Free, - KMemoryPermission::None, KMemoryAttribute::None, - KMemoryState::Normal, KMemoryPermission::UserReadWrite, - KMemoryAttribute::None); + memory_block_manager.UpdateIfMatch( + std::addressof(allocator), address, size / PageSize, KMemoryState::Free, + KMemoryPermission::None, KMemoryAttribute::None, KMemoryState::Normal, + KMemoryPermission::UserReadWrite, KMemoryAttribute::None); // Cancel our guard. unmap_guard.Cancel(); @@ -924,10 +951,10 @@ Result KPageTable::UnmapPhysicalMemory(VAddr address, std::size_t size) { cur_address = address; mapped_size = 0; - auto it = block_manager->FindIterator(cur_address); + auto it = memory_block_manager.FindIterator(cur_address); while (true) { // Check that the iterator is valid. - ASSERT(it != block_manager->end()); + ASSERT(it != memory_block_manager.end()); // Get the memory info. const KMemoryInfo info = it->GetMemoryInfo(); @@ -1022,6 +1049,13 @@ Result KPageTable::UnmapPhysicalMemory(VAddr address, std::size_t size) { } ASSERT(pg.GetNumPages() == mapped_size / PageSize); + // Create an update allocator. + ASSERT(num_allocator_blocks <= KMemoryBlockManagerUpdateAllocator::MaxBlocks); + Result allocator_result{ResultSuccess}; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), + memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + // Reset the current tracking address, and make sure we clean up on failure. cur_address = address; auto remap_guard = detail::ScopeExit([&] { @@ -1030,7 +1064,7 @@ Result KPageTable::UnmapPhysicalMemory(VAddr address, std::size_t size) { cur_address = address; // Iterate over the memory we unmapped. - auto it = block_manager->FindIterator(cur_address); + auto it = memory_block_manager.FindIterator(cur_address); auto pg_it = pg.Nodes().begin(); PAddr pg_phys_addr = pg_it->GetAddress(); size_t pg_pages = pg_it->GetNumPages(); @@ -1085,10 +1119,10 @@ Result KPageTable::UnmapPhysicalMemory(VAddr address, std::size_t size) { }); // Iterate over the memory, unmapping as we go. - auto it = block_manager->FindIterator(cur_address); + auto it = memory_block_manager.FindIterator(cur_address); while (true) { // Check that the iterator is valid. - ASSERT(it != block_manager->end()); + ASSERT(it != memory_block_manager.end()); // Get the memory info. const KMemoryInfo info = it->GetMemoryInfo(); @@ -1120,8 +1154,10 @@ Result KPageTable::UnmapPhysicalMemory(VAddr address, std::size_t size) { process->GetResourceLimit()->Release(LimitableResource::PhysicalMemory, mapped_size); // Update memory blocks. - block_manager->Update(address, size / PageSize, KMemoryState::Free, KMemoryPermission::None, - KMemoryAttribute::None); + memory_block_manager.Update(std::addressof(allocator), address, size / PageSize, + KMemoryState::Free, KMemoryPermission::None, KMemoryAttribute::None, + KMemoryBlockDisableMergeAttribute::None, + KMemoryBlockDisableMergeAttribute::None); // TODO(bunnei): This is a workaround until the next set of changes, where we add reference // counting for mapped pages. Until then, we must manually close the reference to the page @@ -1134,83 +1170,134 @@ Result KPageTable::UnmapPhysicalMemory(VAddr address, std::size_t size) { return ResultSuccess; } -Result KPageTable::MapMemory(VAddr dst_addr, VAddr src_addr, std::size_t size) { +Result KPageTable::MapMemory(VAddr dst_address, VAddr src_address, std::size_t size) { + // Lock the table. KScopedLightLock lk(general_lock); - KMemoryState src_state{}; - CASCADE_CODE(CheckMemoryState( - &src_state, nullptr, nullptr, nullptr, src_addr, size, KMemoryState::FlagCanAlias, - KMemoryState::FlagCanAlias, KMemoryPermission::All, KMemoryPermission::UserReadWrite, - KMemoryAttribute::Mask, KMemoryAttribute::None, KMemoryAttribute::IpcAndDeviceMapped)); + // Validate that the source address's state is valid. + KMemoryState src_state; + size_t num_src_allocator_blocks; + R_TRY(this->CheckMemoryState(std::addressof(src_state), nullptr, nullptr, + std::addressof(num_src_allocator_blocks), src_address, size, + KMemoryState::FlagCanAlias, KMemoryState::FlagCanAlias, + KMemoryPermission::All, KMemoryPermission::UserReadWrite, + KMemoryAttribute::All, KMemoryAttribute::None)); - if (IsRegionMapped(dst_addr, size)) { - return ResultInvalidCurrentMemory; - } + // Validate that the dst address's state is valid. + size_t num_dst_allocator_blocks; + R_TRY(this->CheckMemoryState(std::addressof(num_dst_allocator_blocks), dst_address, size, + KMemoryState::All, KMemoryState::Free, KMemoryPermission::None, + KMemoryPermission::None, KMemoryAttribute::None, + KMemoryAttribute::None)); + // Create an update allocator for the source. + Result src_allocator_result{ResultSuccess}; + KMemoryBlockManagerUpdateAllocator src_allocator( + std::addressof(src_allocator_result), memory_block_slab_manager, num_src_allocator_blocks); + R_TRY(src_allocator_result); + + // Create an update allocator for the destination. + Result dst_allocator_result{ResultSuccess}; + KMemoryBlockManagerUpdateAllocator dst_allocator( + std::addressof(dst_allocator_result), memory_block_slab_manager, num_dst_allocator_blocks); + R_TRY(dst_allocator_result); + + // Map the memory. KPageGroup page_linked_list; const std::size_t num_pages{size / PageSize}; + const KMemoryPermission new_src_perm = static_cast( + KMemoryPermission::KernelRead | KMemoryPermission::NotMapped); + const KMemoryAttribute new_src_attr = KMemoryAttribute::Locked; - AddRegionToPages(src_addr, num_pages, page_linked_list); - + AddRegionToPages(src_address, num_pages, page_linked_list); { + // Reprotect the source as kernel-read/not mapped. auto block_guard = detail::ScopeExit([&] { - Operate(src_addr, num_pages, KMemoryPermission::UserReadWrite, + Operate(src_address, num_pages, KMemoryPermission::UserReadWrite, OperationType::ChangePermissions); }); - - CASCADE_CODE(Operate(src_addr, num_pages, KMemoryPermission::None, - OperationType::ChangePermissions)); - CASCADE_CODE(MapPages(dst_addr, page_linked_list, KMemoryPermission::UserReadWrite)); + R_TRY(Operate(src_address, num_pages, new_src_perm, OperationType::ChangePermissions)); + R_TRY(MapPages(dst_address, page_linked_list, KMemoryPermission::UserReadWrite)); block_guard.Cancel(); } - block_manager->Update(src_addr, num_pages, src_state, KMemoryPermission::None, - KMemoryAttribute::Locked); - block_manager->Update(dst_addr, num_pages, KMemoryState::Stack, - KMemoryPermission::UserReadWrite); + // Apply the memory block updates. + memory_block_manager.Update(std::addressof(src_allocator), src_address, num_pages, src_state, + new_src_perm, new_src_attr, + KMemoryBlockDisableMergeAttribute::Locked, + KMemoryBlockDisableMergeAttribute::None); + memory_block_manager.Update(std::addressof(dst_allocator), dst_address, num_pages, + KMemoryState::Stack, KMemoryPermission::UserReadWrite, + KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal, + KMemoryBlockDisableMergeAttribute::None); return ResultSuccess; } -Result KPageTable::UnmapMemory(VAddr dst_addr, VAddr src_addr, std::size_t size) { +Result KPageTable::UnmapMemory(VAddr dst_address, VAddr src_address, std::size_t size) { + // Lock the table. KScopedLightLock lk(general_lock); - KMemoryState src_state{}; - CASCADE_CODE(CheckMemoryState( - &src_state, nullptr, nullptr, nullptr, src_addr, size, KMemoryState::FlagCanAlias, - KMemoryState::FlagCanAlias, KMemoryPermission::All, KMemoryPermission::None, - KMemoryAttribute::Mask, KMemoryAttribute::Locked, KMemoryAttribute::IpcAndDeviceMapped)); + // Validate that the source address's state is valid. + KMemoryState src_state; + size_t num_src_allocator_blocks; + R_TRY(this->CheckMemoryState( + std::addressof(src_state), nullptr, nullptr, std::addressof(num_src_allocator_blocks), + src_address, size, KMemoryState::FlagCanAlias, KMemoryState::FlagCanAlias, + KMemoryPermission::All, KMemoryPermission::NotMapped | KMemoryPermission::KernelRead, + KMemoryAttribute::All, KMemoryAttribute::Locked)); - KMemoryPermission dst_perm{}; - CASCADE_CODE(CheckMemoryState(nullptr, &dst_perm, nullptr, nullptr, dst_addr, size, - KMemoryState::All, KMemoryState::Stack, KMemoryPermission::None, - KMemoryPermission::None, KMemoryAttribute::Mask, - KMemoryAttribute::None, KMemoryAttribute::IpcAndDeviceMapped)); + // Validate that the dst address's state is valid. + KMemoryPermission dst_perm; + size_t num_dst_allocator_blocks; + R_TRY(this->CheckMemoryState( + nullptr, std::addressof(dst_perm), nullptr, std::addressof(num_dst_allocator_blocks), + dst_address, size, KMemoryState::All, KMemoryState::Stack, KMemoryPermission::None, + KMemoryPermission::None, KMemoryAttribute::All, KMemoryAttribute::None)); + + // Create an update allocator for the source. + Result src_allocator_result{ResultSuccess}; + KMemoryBlockManagerUpdateAllocator src_allocator( + std::addressof(src_allocator_result), memory_block_slab_manager, num_src_allocator_blocks); + R_TRY(src_allocator_result); + + // Create an update allocator for the destination. + Result dst_allocator_result{ResultSuccess}; + KMemoryBlockManagerUpdateAllocator dst_allocator( + std::addressof(dst_allocator_result), memory_block_slab_manager, num_dst_allocator_blocks); + R_TRY(dst_allocator_result); KPageGroup src_pages; KPageGroup dst_pages; const std::size_t num_pages{size / PageSize}; - AddRegionToPages(src_addr, num_pages, src_pages); - AddRegionToPages(dst_addr, num_pages, dst_pages); + AddRegionToPages(src_address, num_pages, src_pages); + AddRegionToPages(dst_address, num_pages, dst_pages); if (!dst_pages.IsEqual(src_pages)) { return ResultInvalidMemoryRegion; } { - auto block_guard = detail::ScopeExit([&] { MapPages(dst_addr, dst_pages, dst_perm); }); + auto block_guard = detail::ScopeExit([&] { MapPages(dst_address, dst_pages, dst_perm); }); - CASCADE_CODE(Operate(dst_addr, num_pages, KMemoryPermission::None, OperationType::Unmap)); - CASCADE_CODE(Operate(src_addr, num_pages, KMemoryPermission::UserReadWrite, - OperationType::ChangePermissions)); + R_TRY(Operate(dst_address, num_pages, KMemoryPermission::None, OperationType::Unmap)); + R_TRY(Operate(src_address, num_pages, KMemoryPermission::UserReadWrite, + OperationType::ChangePermissions)); block_guard.Cancel(); } - block_manager->Update(src_addr, num_pages, src_state, KMemoryPermission::UserReadWrite); - block_manager->Update(dst_addr, num_pages, KMemoryState::Free); + // Apply the memory block updates. + memory_block_manager.Update(std::addressof(src_allocator), src_address, num_pages, src_state, + KMemoryPermission::UserReadWrite, KMemoryAttribute::None, + KMemoryBlockDisableMergeAttribute::None, + KMemoryBlockDisableMergeAttribute::Locked); + memory_block_manager.Update(std::addressof(dst_allocator), dst_address, num_pages, + KMemoryState::None, KMemoryPermission::None, KMemoryAttribute::None, + KMemoryBlockDisableMergeAttribute::None, + KMemoryBlockDisableMergeAttribute::Normal); return ResultSuccess; } @@ -1254,11 +1341,18 @@ Result KPageTable::MapPages(VAddr address, KPageGroup& page_linked_list, KMemory KMemoryPermission::None, KMemoryPermission::None, KMemoryAttribute::None, KMemoryAttribute::None)); + // Create an update allocator. + Result allocator_result{ResultSuccess}; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), + memory_block_slab_manager); + // Map the pages. R_TRY(MapPages(address, page_linked_list, perm)); // Update the blocks. - block_manager->Update(address, num_pages, state, perm); + memory_block_manager.Update(std::addressof(allocator), address, num_pages, state, perm, + KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal, + KMemoryBlockDisableMergeAttribute::None); return ResultSuccess; } @@ -1288,6 +1382,11 @@ Result KPageTable::MapPages(VAddr* out_addr, std::size_t num_pages, std::size_t KMemoryAttribute::None, KMemoryAttribute::None) .IsSuccess()); + // Create an update allocator. + Result allocator_result{ResultSuccess}; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), + memory_block_slab_manager); + // Perform mapping operation. if (is_pa_valid) { R_TRY(this->Operate(addr, num_pages, perm, OperationType::Map, phys_addr)); @@ -1296,7 +1395,9 @@ Result KPageTable::MapPages(VAddr* out_addr, std::size_t num_pages, std::size_t } // Update the blocks. - block_manager->Update(addr, num_pages, state, perm); + memory_block_manager.Update(std::addressof(allocator), addr, num_pages, state, perm, + KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal, + KMemoryBlockDisableMergeAttribute::None); // We successfully mapped the pages. *out_addr = addr; @@ -1321,25 +1422,36 @@ Result KPageTable::UnmapPages(VAddr addr, const KPageGroup& page_linked_list) { return ResultSuccess; } -Result KPageTable::UnmapPages(VAddr addr, KPageGroup& page_linked_list, KMemoryState state) { +Result KPageTable::UnmapPages(VAddr address, KPageGroup& page_linked_list, KMemoryState state) { // Check that the unmap is in range. const std::size_t num_pages{page_linked_list.GetNumPages()}; const std::size_t size{num_pages * PageSize}; - R_UNLESS(this->Contains(addr, size), ResultInvalidCurrentMemory); + R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory); // Lock the table. KScopedLightLock lk(general_lock); // Check the memory state. - R_TRY(this->CheckMemoryState(addr, size, KMemoryState::All, state, KMemoryPermission::None, + size_t num_allocator_blocks; + R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), address, size, + KMemoryState::All, state, KMemoryPermission::None, KMemoryPermission::None, KMemoryAttribute::All, KMemoryAttribute::None)); + // Create an update allocator. + Result allocator_result{ResultSuccess}; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), + memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + // Perform the unmap. - R_TRY(UnmapPages(addr, page_linked_list)); + R_TRY(UnmapPages(address, page_linked_list)); // Update the blocks. - block_manager->Update(addr, num_pages, state, KMemoryPermission::None); + memory_block_manager.Update(std::addressof(allocator), address, num_pages, KMemoryState::Free, + KMemoryPermission::None, KMemoryAttribute::None, + KMemoryBlockDisableMergeAttribute::None, + KMemoryBlockDisableMergeAttribute::Normal); return ResultSuccess; } @@ -1359,11 +1471,20 @@ Result KPageTable::UnmapPages(VAddr address, std::size_t num_pages, KMemoryState KMemoryPermission::None, KMemoryAttribute::All, KMemoryAttribute::None)); + // Create an update allocator. + Result allocator_result{ResultSuccess}; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), + memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + // Perform the unmap. R_TRY(Operate(address, num_pages, KMemoryPermission::None, OperationType::Unmap)); // Update the blocks. - block_manager->Update(address, num_pages, KMemoryState::Free, KMemoryPermission::None); + memory_block_manager.Update(std::addressof(allocator), address, num_pages, KMemoryState::Free, + KMemoryPermission::None, KMemoryAttribute::None, + KMemoryBlockDisableMergeAttribute::None, + KMemoryBlockDisableMergeAttribute::Normal); return ResultSuccess; } @@ -1435,13 +1556,21 @@ Result KPageTable::SetProcessMemoryPermission(VAddr addr, std::size_t size, // Succeed if there's nothing to do. R_SUCCEED_IF(old_perm == new_perm && old_state == new_state); + // Create an update allocator. + Result allocator_result{ResultSuccess}; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), + memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + // Perform mapping operation. const auto operation = was_x ? OperationType::ChangePermissionsAndRefresh : OperationType::ChangePermissions; R_TRY(Operate(addr, num_pages, new_perm, operation)); // Update the blocks. - block_manager->Update(addr, num_pages, new_state, new_perm, KMemoryAttribute::None); + memory_block_manager.Update(std::addressof(allocator), addr, num_pages, new_state, new_perm, + KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::None, + KMemoryBlockDisableMergeAttribute::None); // Ensure cache coherency, if we're setting pages as executable. if (is_x) { @@ -1454,51 +1583,30 @@ Result KPageTable::SetProcessMemoryPermission(VAddr addr, std::size_t size, KMemoryInfo KPageTable::QueryInfoImpl(VAddr addr) { KScopedLightLock lk(general_lock); - return block_manager->FindBlock(addr).GetMemoryInfo(); + return memory_block_manager.FindBlock(addr)->GetMemoryInfo(); } KMemoryInfo KPageTable::QueryInfo(VAddr addr) { if (!Contains(addr, 1)) { - return {address_space_end, 0 - address_space_end, KMemoryState::Inaccessible, - KMemoryPermission::None, KMemoryAttribute::None, KMemoryPermission::None}; + return { + .m_address = address_space_end, + .m_size = 0 - address_space_end, + .m_state = static_cast(Svc::MemoryState::Inaccessible), + .m_device_disable_merge_left_count = 0, + .m_device_disable_merge_right_count = 0, + .m_ipc_lock_count = 0, + .m_device_use_count = 0, + .m_ipc_disable_merge_count = 0, + .m_permission = KMemoryPermission::None, + .m_attribute = KMemoryAttribute::None, + .m_original_permission = KMemoryPermission::None, + .m_disable_merge_attribute = KMemoryBlockDisableMergeAttribute::None, + }; } return QueryInfoImpl(addr); } -Result KPageTable::ReserveTransferMemory(VAddr addr, std::size_t size, KMemoryPermission perm) { - KScopedLightLock lk(general_lock); - - KMemoryState state{}; - KMemoryAttribute attribute{}; - - R_TRY(CheckMemoryState(&state, nullptr, &attribute, nullptr, addr, size, - KMemoryState::FlagCanTransfer | KMemoryState::FlagReferenceCounted, - KMemoryState::FlagCanTransfer | KMemoryState::FlagReferenceCounted, - KMemoryPermission::All, KMemoryPermission::UserReadWrite, - KMemoryAttribute::Mask, KMemoryAttribute::None, - KMemoryAttribute::IpcAndDeviceMapped)); - - block_manager->Update(addr, size / PageSize, state, perm, attribute | KMemoryAttribute::Locked); - - return ResultSuccess; -} - -Result KPageTable::ResetTransferMemory(VAddr addr, std::size_t size) { - KScopedLightLock lk(general_lock); - - KMemoryState state{}; - - R_TRY(CheckMemoryState(&state, nullptr, nullptr, nullptr, addr, size, - KMemoryState::FlagCanTransfer | KMemoryState::FlagReferenceCounted, - KMemoryState::FlagCanTransfer | KMemoryState::FlagReferenceCounted, - KMemoryPermission::None, KMemoryPermission::None, KMemoryAttribute::Mask, - KMemoryAttribute::Locked, KMemoryAttribute::IpcAndDeviceMapped)); - - block_manager->Update(addr, size / PageSize, state, KMemoryPermission::UserReadWrite); - return ResultSuccess; -} - Result KPageTable::SetMemoryPermission(VAddr addr, std::size_t size, Svc::MemoryPermission svc_perm) { const size_t num_pages = size / PageSize; @@ -1509,20 +1617,30 @@ Result KPageTable::SetMemoryPermission(VAddr addr, std::size_t size, // Verify we can change the memory permission. KMemoryState old_state; KMemoryPermission old_perm; - R_TRY(this->CheckMemoryState( - std::addressof(old_state), std::addressof(old_perm), nullptr, nullptr, addr, size, - KMemoryState::FlagCanReprotect, KMemoryState::FlagCanReprotect, KMemoryPermission::None, - KMemoryPermission::None, KMemoryAttribute::All, KMemoryAttribute::None)); + size_t num_allocator_blocks; + R_TRY(this->CheckMemoryState(std::addressof(old_state), std::addressof(old_perm), nullptr, + std::addressof(num_allocator_blocks), addr, size, + KMemoryState::FlagCanReprotect, KMemoryState::FlagCanReprotect, + KMemoryPermission::None, KMemoryPermission::None, + KMemoryAttribute::All, KMemoryAttribute::None)); // Determine new perm. const KMemoryPermission new_perm = ConvertToKMemoryPermission(svc_perm); R_SUCCEED_IF(old_perm == new_perm); + // Create an update allocator. + Result allocator_result{ResultSuccess}; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), + memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + // Perform mapping operation. R_TRY(Operate(addr, num_pages, new_perm, OperationType::ChangePermissions)); // Update the blocks. - block_manager->Update(addr, num_pages, old_state, new_perm, KMemoryAttribute::None); + memory_block_manager.Update(std::addressof(allocator), addr, num_pages, old_state, new_perm, + KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::None, + KMemoryBlockDisableMergeAttribute::None); return ResultSuccess; } @@ -1548,6 +1666,12 @@ Result KPageTable::SetMemoryAttribute(VAddr addr, std::size_t size, u32 mask, u3 KMemoryState::FlagCanChangeAttribute, KMemoryPermission::None, KMemoryPermission::None, AttributeTestMask, KMemoryAttribute::None, ~AttributeTestMask)); + // Create an update allocator. + Result allocator_result{ResultSuccess}; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), + memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + // Determine the new attribute. const KMemoryAttribute new_attr = static_cast(((old_attr & static_cast(~mask)) | @@ -1557,7 +1681,9 @@ Result KPageTable::SetMemoryAttribute(VAddr addr, std::size_t size, u32 mask, u3 this->Operate(addr, num_pages, old_perm, OperationType::ChangePermissionsAndRefresh); // Update the blocks. - block_manager->Update(addr, num_pages, old_state, old_perm, new_attr); + memory_block_manager.Update(std::addressof(allocator), addr, num_pages, old_state, old_perm, + new_attr, KMemoryBlockDisableMergeAttribute::None, + KMemoryBlockDisableMergeAttribute::None); return ResultSuccess; } @@ -1603,6 +1729,12 @@ Result KPageTable::SetHeapSize(VAddr* out, std::size_t size) { KMemoryPermission::All, KMemoryPermission::UserReadWrite, KMemoryAttribute::All, KMemoryAttribute::None)); + // Create an update allocator. + Result allocator_result{ResultSuccess}; + KMemoryBlockManagerUpdateAllocator allocator( + std::addressof(allocator_result), memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + // Unmap the end of the heap. const auto num_pages = (GetHeapSize() - size) / PageSize; R_TRY(Operate(heap_region_start + size, num_pages, KMemoryPermission::None, @@ -1613,8 +1745,12 @@ Result KPageTable::SetHeapSize(VAddr* out, std::size_t size) { LimitableResource::PhysicalMemory, num_pages * PageSize); // Apply the memory block update. - block_manager->Update(heap_region_start + size, num_pages, KMemoryState::Free, - KMemoryPermission::None, KMemoryAttribute::None); + memory_block_manager.Update(std::addressof(allocator), heap_region_start + size, + num_pages, KMemoryState::Free, KMemoryPermission::None, + KMemoryAttribute::None, + KMemoryBlockDisableMergeAttribute::None, + size == 0 ? KMemoryBlockDisableMergeAttribute::Normal + : KMemoryBlockDisableMergeAttribute::None); // Update the current heap end. current_heap_end = heap_region_start + size; @@ -1667,6 +1803,12 @@ Result KPageTable::SetHeapSize(VAddr* out, std::size_t size) { KMemoryPermission::None, KMemoryPermission::None, KMemoryAttribute::None, KMemoryAttribute::None)); + // Create an update allocator. + Result allocator_result{ResultSuccess}; + KMemoryBlockManagerUpdateAllocator allocator( + std::addressof(allocator_result), memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + // Map the pages. const auto num_pages = allocation_size / PageSize; R_TRY(Operate(current_heap_end, num_pages, pg, OperationType::MapGroup)); @@ -1681,8 +1823,12 @@ Result KPageTable::SetHeapSize(VAddr* out, std::size_t size) { memory_reservation.Commit(); // Apply the memory block update. - block_manager->Update(current_heap_end, num_pages, KMemoryState::Normal, - KMemoryPermission::UserReadWrite, KMemoryAttribute::None); + memory_block_manager.Update( + std::addressof(allocator), current_heap_end, num_pages, KMemoryState::Normal, + KMemoryPermission::UserReadWrite, KMemoryAttribute::None, + heap_region_start == current_heap_end ? KMemoryBlockDisableMergeAttribute::Normal + : KMemoryBlockDisableMergeAttribute::None, + KMemoryBlockDisableMergeAttribute::None); // Update the current heap end. current_heap_end = heap_region_start + size; @@ -1713,6 +1859,11 @@ ResultVal KPageTable::AllocateAndMapMemory(std::size_t needed_num_pages, return ResultOutOfMemory; } + // Create an update allocator. + Result allocator_result{ResultSuccess}; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), + memory_block_slab_manager); + if (is_map_only) { R_TRY(Operate(addr, needed_num_pages, perm, OperationType::Map, map_addr)); } else { @@ -1723,53 +1874,38 @@ ResultVal KPageTable::AllocateAndMapMemory(std::size_t needed_num_pages, R_TRY(Operate(addr, needed_num_pages, page_group, OperationType::MapGroup)); } - block_manager->Update(addr, needed_num_pages, state, perm); + // Update the blocks. + memory_block_manager.Update(std::addressof(allocator), addr, needed_num_pages, state, perm, + KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal, + KMemoryBlockDisableMergeAttribute::None); return addr; } -Result KPageTable::LockForDeviceAddressSpace(VAddr addr, std::size_t size) { +Result KPageTable::UnlockForDeviceAddressSpace(VAddr address, std::size_t size) { + // Lightly validate the range before doing anything else. + const size_t num_pages = size / PageSize; + R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory); + + // Lock the table. KScopedLightLock lk(general_lock); - KMemoryPermission perm{}; - if (const Result result{CheckMemoryState( - nullptr, &perm, nullptr, nullptr, addr, size, KMemoryState::FlagCanChangeAttribute, - KMemoryState::FlagCanChangeAttribute, KMemoryPermission::None, KMemoryPermission::None, - KMemoryAttribute::LockedAndIpcLocked, KMemoryAttribute::None, - KMemoryAttribute::DeviceSharedAndUncached)}; - result.IsError()) { - return result; - } + // Check the memory state. + size_t num_allocator_blocks; + R_TRY(this->CheckMemoryStateContiguous( + std::addressof(num_allocator_blocks), address, size, KMemoryState::FlagCanDeviceMap, + KMemoryState::FlagCanDeviceMap, KMemoryPermission::None, KMemoryPermission::None, + KMemoryAttribute::DeviceShared | KMemoryAttribute::Locked, KMemoryAttribute::DeviceShared)); - block_manager->UpdateLock( - addr, size / PageSize, - [](KMemoryBlockManager::iterator block, KMemoryPermission permission) { - block->ShareToDevice(permission); - }, - perm); + // Create an update allocator. + Result allocator_result{ResultSuccess}; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), + memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); - return ResultSuccess; -} - -Result KPageTable::UnlockForDeviceAddressSpace(VAddr addr, std::size_t size) { - KScopedLightLock lk(general_lock); - - KMemoryPermission perm{}; - if (const Result result{CheckMemoryState( - nullptr, &perm, nullptr, nullptr, addr, size, KMemoryState::FlagCanChangeAttribute, - KMemoryState::FlagCanChangeAttribute, KMemoryPermission::None, KMemoryPermission::None, - KMemoryAttribute::LockedAndIpcLocked, KMemoryAttribute::None, - KMemoryAttribute::DeviceSharedAndUncached)}; - result.IsError()) { - return result; - } - - block_manager->UpdateLock( - addr, size / PageSize, - [](KMemoryBlockManager::iterator block, KMemoryPermission permission) { - block->UnshareToDevice(permission); - }, - perm); + // Update the memory blocks. + memory_block_manager.UpdateLock(std::addressof(allocator), address, num_pages, + &KMemoryBlock::UnshareToDevice, KMemoryPermission::None); return ResultSuccess; } @@ -1791,19 +1927,6 @@ Result KPageTable::UnlockForCodeMemory(VAddr addr, std::size_t size, const KPage KMemoryAttribute::Locked, KMemoryPermission::UserReadWrite, KMemoryAttribute::Locked, &pg); } -Result KPageTable::InitializeMemoryLayout(VAddr start, VAddr end) { - block_manager = std::make_unique(start, end); - - return ResultSuccess; -} - -bool KPageTable::IsRegionMapped(VAddr address, u64 size) { - return CheckMemoryState(address, size, KMemoryState::All, KMemoryState::Free, - KMemoryPermission::All, KMemoryPermission::None, KMemoryAttribute::Mask, - KMemoryAttribute::None, KMemoryAttribute::IpcAndDeviceMapped) - .IsError(); -} - bool KPageTable::IsRegionContiguous(VAddr addr, u64 size) const { auto start_ptr = system.DeviceMemory().GetPointer(addr); for (u64 offset{}; offset < size; offset += PageSize) { @@ -1831,8 +1954,8 @@ VAddr KPageTable::AllocateVirtualMemory(VAddr start, std::size_t region_num_page if (is_aslr_enabled) { UNIMPLEMENTED(); } - return block_manager->FindFreeArea(start, region_num_pages, needed_num_pages, align, 0, - IsKernel() ? 1 : 4); + return memory_block_manager.FindFreeArea(start, region_num_pages, needed_num_pages, align, 0, + IsKernel() ? 1 : 4); } Result KPageTable::Operate(VAddr addr, std::size_t num_pages, const KPageGroup& page_group, @@ -2008,9 +2131,9 @@ Result KPageTable::CheckMemoryState(const KMemoryInfo& info, KMemoryState state_ KMemoryPermission perm, KMemoryAttribute attr_mask, KMemoryAttribute attr) const { // Validate the states match expectation. - R_UNLESS((info.state & state_mask) == state, ResultInvalidCurrentMemory); - R_UNLESS((info.perm & perm_mask) == perm, ResultInvalidCurrentMemory); - R_UNLESS((info.attribute & attr_mask) == attr, ResultInvalidCurrentMemory); + R_UNLESS((info.m_state & state_mask) == state, ResultInvalidCurrentMemory); + R_UNLESS((info.m_permission & perm_mask) == perm, ResultInvalidCurrentMemory); + R_UNLESS((info.m_attribute & attr_mask) == attr, ResultInvalidCurrentMemory); return ResultSuccess; } @@ -2024,7 +2147,7 @@ Result KPageTable::CheckMemoryStateContiguous(std::size_t* out_blocks_needed, VA // Get information about the first block. const VAddr last_addr = addr + size - 1; - KMemoryBlockManager::const_iterator it = block_manager->FindIterator(addr); + KMemoryBlockManager::const_iterator it = memory_block_manager.FindIterator(addr); KMemoryInfo info = it->GetMemoryInfo(); // If the start address isn't aligned, we need a block. @@ -2042,7 +2165,7 @@ Result KPageTable::CheckMemoryStateContiguous(std::size_t* out_blocks_needed, VA // Advance our iterator. it++; - ASSERT(it != block_manager->cend()); + ASSERT(it != memory_block_manager.cend()); info = it->GetMemoryInfo(); } @@ -2067,7 +2190,7 @@ Result KPageTable::CheckMemoryState(KMemoryState* out_state, KMemoryPermission* // Get information about the first block. const VAddr last_addr = addr + size - 1; - KMemoryBlockManager::const_iterator it = block_manager->FindIterator(addr); + KMemoryBlockManager::const_iterator it = memory_block_manager.FindIterator(addr); KMemoryInfo info = it->GetMemoryInfo(); // If the start address isn't aligned, we need a block. @@ -2075,14 +2198,14 @@ Result KPageTable::CheckMemoryState(KMemoryState* out_state, KMemoryPermission* (Common::AlignDown(addr, PageSize) != info.GetAddress()) ? 1 : 0; // Validate all blocks in the range have correct state. - const KMemoryState first_state = info.state; - const KMemoryPermission first_perm = info.perm; - const KMemoryAttribute first_attr = info.attribute; + const KMemoryState first_state = info.m_state; + const KMemoryPermission first_perm = info.m_permission; + const KMemoryAttribute first_attr = info.m_attribute; while (true) { // Validate the current block. - R_UNLESS(info.state == first_state, ResultInvalidCurrentMemory); - R_UNLESS(info.perm == first_perm, ResultInvalidCurrentMemory); - R_UNLESS((info.attribute | ignore_attr) == (first_attr | ignore_attr), + R_UNLESS(info.m_state == first_state, ResultInvalidCurrentMemory); + R_UNLESS(info.m_permission == first_perm, ResultInvalidCurrentMemory); + R_UNLESS((info.m_attribute | ignore_attr) == (first_attr | ignore_attr), ResultInvalidCurrentMemory); // Validate against the provided masks. @@ -2095,7 +2218,7 @@ Result KPageTable::CheckMemoryState(KMemoryState* out_state, KMemoryPermission* // Advance our iterator. it++; - ASSERT(it != block_manager->cend()); + ASSERT(it != memory_block_manager.cend()); info = it->GetMemoryInfo(); } @@ -2162,6 +2285,12 @@ Result KPageTable::LockMemoryAndOpen(KPageGroup* out_pg, PAddr* out_paddr, VAddr R_TRY(this->MakePageGroup(*out_pg, addr, num_pages)); } + // Create an update allocator. + Result allocator_result{ResultSuccess}; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), + memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + // Decide on new perm and attr. new_perm = (new_perm != KMemoryPermission::None) ? new_perm : old_perm; KMemoryAttribute new_attr = static_cast(old_attr | lock_attr); @@ -2172,7 +2301,9 @@ Result KPageTable::LockMemoryAndOpen(KPageGroup* out_pg, PAddr* out_paddr, VAddr } // Apply the memory block updates. - block_manager->Update(addr, num_pages, old_state, new_perm, new_attr); + memory_block_manager.Update(std::addressof(allocator), addr, num_pages, old_state, new_perm, + new_attr, KMemoryBlockDisableMergeAttribute::Locked, + KMemoryBlockDisableMergeAttribute::None); return ResultSuccess; } @@ -2213,13 +2344,21 @@ Result KPageTable::UnlockMemory(VAddr addr, size_t size, KMemoryState state_mask new_perm = (new_perm != KMemoryPermission::None) ? new_perm : old_perm; KMemoryAttribute new_attr = static_cast(old_attr & ~lock_attr); + // Create an update allocator. + Result allocator_result{ResultSuccess}; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), + memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + // Update permission, if we need to. if (new_perm != old_perm) { R_TRY(Operate(addr, num_pages, new_perm, OperationType::ChangePermissions)); } // Apply the memory block updates. - block_manager->Update(addr, num_pages, old_state, new_perm, new_attr); + memory_block_manager.Update(std::addressof(allocator), addr, num_pages, old_state, new_perm, + new_attr, KMemoryBlockDisableMergeAttribute::None, + KMemoryBlockDisableMergeAttribute::Locked); return ResultSuccess; } diff --git a/src/core/hle/kernel/k_page_table.h b/src/core/hle/kernel/k_page_table.h index 25774f2321..fa11a0fe36 100644 --- a/src/core/hle/kernel/k_page_table.h +++ b/src/core/hle/kernel/k_page_table.h @@ -9,8 +9,10 @@ #include "common/common_types.h" #include "common/page_table.h" #include "core/file_sys/program_metadata.h" +#include "core/hle/kernel/k_dynamic_resource_manager.h" #include "core/hle/kernel/k_light_lock.h" #include "core/hle/kernel/k_memory_block.h" +#include "core/hle/kernel/k_memory_block_manager.h" #include "core/hle/kernel/k_memory_layout.h" #include "core/hle/kernel/k_memory_manager.h" #include "core/hle/result.h" @@ -34,7 +36,12 @@ public: ~KPageTable(); Result InitializeForProcess(FileSys::ProgramAddressSpaceType as_type, bool enable_aslr, - VAddr code_addr, std::size_t code_size, KMemoryManager::Pool pool); + VAddr code_addr, std::size_t code_size, + KMemoryBlockSlabManager* mem_block_slab_manager, + KMemoryManager::Pool pool); + + void Finalize(); + Result MapProcessCode(VAddr addr, std::size_t pages_count, KMemoryState state, KMemoryPermission perm); Result MapCodeMemory(VAddr dst_address, VAddr src_address, std::size_t size); @@ -58,8 +65,6 @@ public: Result UnmapPages(VAddr address, std::size_t num_pages, KMemoryState state); Result SetProcessMemoryPermission(VAddr addr, std::size_t size, Svc::MemoryPermission svc_perm); KMemoryInfo QueryInfo(VAddr addr); - Result ReserveTransferMemory(VAddr addr, std::size_t size, KMemoryPermission perm); - Result ResetTransferMemory(VAddr addr, std::size_t size); Result SetMemoryPermission(VAddr addr, std::size_t size, Svc::MemoryPermission perm); Result SetMemoryAttribute(VAddr addr, std::size_t size, u32 mask, u32 attr); Result SetMaxHeapSize(std::size_t size); @@ -68,7 +73,6 @@ public: bool is_map_only, VAddr region_start, std::size_t region_num_pages, KMemoryState state, KMemoryPermission perm, PAddr map_addr = 0); - Result LockForDeviceAddressSpace(VAddr addr, std::size_t size); Result UnlockForDeviceAddressSpace(VAddr addr, std::size_t size); Result LockForCodeMemory(KPageGroup* out, VAddr addr, std::size_t size); Result UnlockForCodeMemory(VAddr addr, std::size_t size, const KPageGroup& pg); @@ -96,17 +100,14 @@ private: ChangePermissionsAndRefresh, }; - static constexpr KMemoryAttribute DefaultMemoryIgnoreAttr = KMemoryAttribute::DontCareMask | - KMemoryAttribute::IpcLocked | - KMemoryAttribute::DeviceShared; + static constexpr KMemoryAttribute DefaultMemoryIgnoreAttr = + KMemoryAttribute::IpcLocked | KMemoryAttribute::DeviceShared; - Result InitializeMemoryLayout(VAddr start, VAddr end); Result MapPages(VAddr addr, const KPageGroup& page_linked_list, KMemoryPermission perm); Result MapPages(VAddr* out_addr, std::size_t num_pages, std::size_t alignment, PAddr phys_addr, bool is_pa_valid, VAddr region_start, std::size_t region_num_pages, KMemoryState state, KMemoryPermission perm); Result UnmapPages(VAddr addr, const KPageGroup& page_linked_list); - bool IsRegionMapped(VAddr address, u64 size); bool IsRegionContiguous(VAddr addr, u64 size) const; void AddRegionToPages(VAddr start, std::size_t num_pages, KPageGroup& page_linked_list); KMemoryInfo QueryInfoImpl(VAddr addr); @@ -194,8 +195,6 @@ private: mutable KLightLock general_lock; mutable KLightLock map_physical_memory_lock; - std::unique_ptr block_manager; - public: constexpr VAddr GetAddressSpaceStart() const { return address_space_start; @@ -346,9 +345,13 @@ private: std::size_t max_physical_memory_size{}; std::size_t address_space_width{}; + KMemoryBlockManager memory_block_manager; + bool is_kernel{}; bool is_aslr_enabled{}; + KMemoryBlockSlabManager* memory_block_slab_manager{}; + u32 heap_fill_value{}; const KMemoryRegion* cached_physical_heap_region{}; From 1baedfa12cc84efd878567e91672f7e0f6de7b5a Mon Sep 17 00:00:00 2001 From: bunnei Date: Fri, 16 Sep 2022 23:33:47 -0700 Subject: [PATCH 30/91] core: hle: kernel: Integration application memory block slab manager. --- src/core/hle/kernel/k_process.cpp | 6 +++--- src/core/hle/kernel/kernel.cpp | 34 +++++++++++++++++++++++++++++++ src/core/hle/kernel/kernel.h | 7 +++++++ 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp index 1d3157a9f4..abc2115bd9 100644 --- a/src/core/hle/kernel/k_process.cpp +++ b/src/core/hle/kernel/k_process.cpp @@ -356,9 +356,9 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std: return ResultLimitReached; } // Initialize proces address space - if (const Result result{page_table->InitializeForProcess(metadata.GetAddressSpaceType(), false, - 0x8000000, code_size, - KMemoryManager::Pool::Application)}; + if (const Result result{page_table->InitializeForProcess( + metadata.GetAddressSpaceType(), false, 0x8000000, code_size, + &kernel.GetApplicationMemoryBlockManager(), KMemoryManager::Pool::Application)}; result.IsError()) { return result; } diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 9251f29ad7..d572394727 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -24,6 +24,7 @@ #include "core/hardware_properties.h" #include "core/hle/kernel/init/init_slab_setup.h" #include "core/hle/kernel/k_client_port.h" +#include "core/hle/kernel/k_dynamic_resource_manager.h" #include "core/hle/kernel/k_handle_table.h" #include "core/hle/kernel/k_memory_layout.h" #include "core/hle/kernel/k_memory_manager.h" @@ -76,6 +77,14 @@ struct KernelCore::Impl { InitializePreemption(kernel); InitializePhysicalCores(); + // Initialize the Dynamic Slab Heaps. + { + const auto& pt_heap_region = memory_layout->GetPageTableHeapRegion(); + ASSERT(pt_heap_region.GetEndAddress() != 0); + + InitializeResourceManagers(pt_heap_region.GetAddress(), pt_heap_region.GetSize()); + } + RegisterHostThread(); } @@ -257,6 +266,18 @@ struct KernelCore::Impl { system.CoreTiming().ScheduleLoopingEvent(time_interval, time_interval, preemption_event); } + void InitializeResourceManagers(VAddr address, size_t size) { + dynamic_page_manager = std::make_unique(); + memory_block_heap = std::make_unique(); + app_memory_block_manager = std::make_unique(); + + dynamic_page_manager->Initialize(address, size); + static constexpr size_t ApplicationMemoryBlockSlabHeapSize = 20000; + memory_block_heap->Initialize(dynamic_page_manager.get(), + ApplicationMemoryBlockSlabHeapSize); + app_memory_block_manager->Initialize(nullptr, memory_block_heap.get()); + } + void InitializeShutdownThreads() { for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) { shutdown_threads[core_id] = KThread::Create(system.Kernel()); @@ -770,6 +791,11 @@ struct KernelCore::Impl { // Kernel memory management std::unique_ptr memory_manager; + // Dynamic slab managers + std::unique_ptr dynamic_page_manager; + std::unique_ptr memory_block_heap; + std::unique_ptr app_memory_block_manager; + // Shared memory for services Kernel::KSharedMemory* hid_shared_mem{}; Kernel::KSharedMemory* font_shared_mem{}; @@ -1041,6 +1067,14 @@ const KMemoryManager& KernelCore::MemoryManager() const { return *impl->memory_manager; } +KMemoryBlockSlabManager& KernelCore::GetApplicationMemoryBlockManager() { + return *impl->app_memory_block_manager; +} + +const KMemoryBlockSlabManager& KernelCore::GetApplicationMemoryBlockManager() const { + return *impl->app_memory_block_manager; +} + Kernel::KSharedMemory& KernelCore::GetHidSharedMem() { return *impl->hid_shared_mem; } diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 0847cbcbf7..79e66483ee 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -37,6 +37,7 @@ class KClientSession; class KEvent; class KHandleTable; class KLinkedListNode; +class KMemoryBlockSlabManager; class KMemoryLayout; class KMemoryManager; class KPageBuffer; @@ -238,6 +239,12 @@ public: /// Gets the virtual memory manager for the kernel. const KMemoryManager& MemoryManager() const; + /// Gets the application memory block manager for the kernel. + KMemoryBlockSlabManager& GetApplicationMemoryBlockManager(); + + /// Gets the application memory block manager for the kernel. + const KMemoryBlockSlabManager& GetApplicationMemoryBlockManager() const; + /// Gets the shared memory object for HID services. Kernel::KSharedMemory& GetHidSharedMem(); From d00245d4440d30a2217c025572cf8a47f4ea2573 Mon Sep 17 00:00:00 2001 From: bunnei Date: Sat, 10 Sep 2022 23:59:34 -0700 Subject: [PATCH 31/91] video_core: renderer_vulkan: vk_query_cache: Avoid shutdown crash in QueryPool::Reserve. --- src/video_core/renderer_vulkan/vk_query_cache.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_query_cache.cpp b/src/video_core/renderer_vulkan/vk_query_cache.cpp index 7cb02631c4..4b15c0f85b 100644 --- a/src/video_core/renderer_vulkan/vk_query_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_query_cache.cpp @@ -59,10 +59,11 @@ void QueryPool::Reserve(std::pair query) { std::find_if(pools.begin(), pools.end(), [query_pool = query.first](vk::QueryPool& pool) { return query_pool == *pool; }); - ASSERT(it != std::end(pools)); - const std::ptrdiff_t pool_index = std::distance(std::begin(pools), it); - usage[pool_index * GROW_STEP + static_cast(query.second)] = false; + if (it != std::end(pools)) { + const std::ptrdiff_t pool_index = std::distance(std::begin(pools), it); + usage[pool_index * GROW_STEP + static_cast(query.second)] = false; + } } QueryCache::QueryCache(VideoCore::RasterizerInterface& rasterizer_, const Device& device_, From ff26190d422599ded0166cba686e7456c59163a5 Mon Sep 17 00:00:00 2001 From: bunnei Date: Sat, 1 Oct 2022 14:08:47 -0700 Subject: [PATCH 32/91] core: hle: kernel: k_page_table: Impl. LockForUn/MapDeviceAddressSpace, cleanup. --- src/core/hle/kernel/k_page_table.cpp | 881 ++++++++++--------- src/core/hle/kernel/k_page_table.h | 269 +++--- src/core/hle/service/nvdrv/devices/nvmap.cpp | 3 +- 3 files changed, 616 insertions(+), 537 deletions(-) diff --git a/src/core/hle/kernel/k_page_table.cpp b/src/core/hle/kernel/k_page_table.cpp index 2cf46af0a4..fcffc0b88d 100644 --- a/src/core/hle/kernel/k_page_table.cpp +++ b/src/core/hle/kernel/k_page_table.cpp @@ -25,7 +25,7 @@ namespace { using namespace Common::Literals; -constexpr std::size_t GetAddressSpaceWidthFromType(FileSys::ProgramAddressSpaceType as_type) { +constexpr size_t GetAddressSpaceWidthFromType(FileSys::ProgramAddressSpaceType as_type) { switch (as_type) { case FileSys::ProgramAddressSpaceType::Is32Bit: case FileSys::ProgramAddressSpaceType::Is32BitNoMap: @@ -43,28 +43,29 @@ constexpr std::size_t GetAddressSpaceWidthFromType(FileSys::ProgramAddressSpaceT } // namespace KPageTable::KPageTable(Core::System& system_) - : general_lock{system_.Kernel()}, map_physical_memory_lock{system_.Kernel()}, system{system_} {} + : m_general_lock{system_.Kernel()}, + m_map_physical_memory_lock{system_.Kernel()}, m_system{system_} {} KPageTable::~KPageTable() = default; Result KPageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_type, bool enable_aslr, - VAddr code_addr, std::size_t code_size, + VAddr code_addr, size_t code_size, KMemoryBlockSlabManager* mem_block_slab_manager, KMemoryManager::Pool pool) { const auto GetSpaceStart = [this](KAddressSpaceInfo::Type type) { - return KAddressSpaceInfo::GetAddressSpaceStart(address_space_width, type); + return KAddressSpaceInfo::GetAddressSpaceStart(m_address_space_width, type); }; const auto GetSpaceSize = [this](KAddressSpaceInfo::Type type) { - return KAddressSpaceInfo::GetAddressSpaceSize(address_space_width, type); + return KAddressSpaceInfo::GetAddressSpaceSize(m_address_space_width, type); }; // Set our width and heap/alias sizes - address_space_width = GetAddressSpaceWidthFromType(as_type); + m_address_space_width = GetAddressSpaceWidthFromType(as_type); const VAddr start = 0; - const VAddr end{1ULL << address_space_width}; - std::size_t alias_region_size{GetSpaceSize(KAddressSpaceInfo::Type::Alias)}; - std::size_t heap_region_size{GetSpaceSize(KAddressSpaceInfo::Type::Heap)}; + const VAddr end{1ULL << m_address_space_width}; + size_t alias_region_size{GetSpaceSize(KAddressSpaceInfo::Type::Alias)}; + size_t heap_region_size{GetSpaceSize(KAddressSpaceInfo::Type::Heap)}; ASSERT(code_addr < code_addr + code_size); ASSERT(code_addr + code_size - 1 <= end - 1); @@ -76,67 +77,68 @@ Result KPageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_type } // Set code regions and determine remaining - constexpr std::size_t RegionAlignment{2_MiB}; + constexpr size_t RegionAlignment{2_MiB}; VAddr process_code_start{}; VAddr process_code_end{}; - std::size_t stack_region_size{}; - std::size_t kernel_map_region_size{}; + size_t stack_region_size{}; + size_t kernel_map_region_size{}; - if (address_space_width == 39) { + if (m_address_space_width == 39) { alias_region_size = GetSpaceSize(KAddressSpaceInfo::Type::Alias); heap_region_size = GetSpaceSize(KAddressSpaceInfo::Type::Heap); stack_region_size = GetSpaceSize(KAddressSpaceInfo::Type::Stack); kernel_map_region_size = GetSpaceSize(KAddressSpaceInfo::Type::MapSmall); - code_region_start = GetSpaceStart(KAddressSpaceInfo::Type::Map39Bit); - code_region_end = code_region_start + GetSpaceSize(KAddressSpaceInfo::Type::Map39Bit); - alias_code_region_start = code_region_start; - alias_code_region_end = code_region_end; + m_code_region_start = GetSpaceStart(KAddressSpaceInfo::Type::Map39Bit); + m_code_region_end = m_code_region_start + GetSpaceSize(KAddressSpaceInfo::Type::Map39Bit); + m_alias_code_region_start = m_code_region_start; + m_alias_code_region_end = m_code_region_end; process_code_start = Common::AlignDown(code_addr, RegionAlignment); process_code_end = Common::AlignUp(code_addr + code_size, RegionAlignment); } else { stack_region_size = 0; kernel_map_region_size = 0; - code_region_start = GetSpaceStart(KAddressSpaceInfo::Type::MapSmall); - code_region_end = code_region_start + GetSpaceSize(KAddressSpaceInfo::Type::MapSmall); - stack_region_start = code_region_start; - alias_code_region_start = code_region_start; - alias_code_region_end = GetSpaceStart(KAddressSpaceInfo::Type::MapLarge) + - GetSpaceSize(KAddressSpaceInfo::Type::MapLarge); - stack_region_end = code_region_end; - kernel_map_region_start = code_region_start; - kernel_map_region_end = code_region_end; - process_code_start = code_region_start; - process_code_end = code_region_end; + m_code_region_start = GetSpaceStart(KAddressSpaceInfo::Type::MapSmall); + m_code_region_end = m_code_region_start + GetSpaceSize(KAddressSpaceInfo::Type::MapSmall); + m_stack_region_start = m_code_region_start; + m_alias_code_region_start = m_code_region_start; + m_alias_code_region_end = GetSpaceStart(KAddressSpaceInfo::Type::MapLarge) + + GetSpaceSize(KAddressSpaceInfo::Type::MapLarge); + m_stack_region_end = m_code_region_end; + m_kernel_map_region_start = m_code_region_start; + m_kernel_map_region_end = m_code_region_end; + process_code_start = m_code_region_start; + process_code_end = m_code_region_end; } // Set other basic fields - is_aslr_enabled = enable_aslr; - address_space_start = start; - address_space_end = end; - is_kernel = false; - memory_block_slab_manager = mem_block_slab_manager; + m_enable_aslr = enable_aslr; + m_enable_device_address_space_merge = false; + m_address_space_start = start; + m_address_space_end = end; + m_is_kernel = false; + m_memory_block_slab_manager = mem_block_slab_manager; // Determine the region we can place our undetermineds in VAddr alloc_start{}; - std::size_t alloc_size{}; - if ((process_code_start - code_region_start) >= (end - process_code_end)) { - alloc_start = code_region_start; - alloc_size = process_code_start - code_region_start; + size_t alloc_size{}; + if ((process_code_start - m_code_region_start) >= (end - process_code_end)) { + alloc_start = m_code_region_start; + alloc_size = process_code_start - m_code_region_start; } else { alloc_start = process_code_end; alloc_size = end - process_code_end; } - const std::size_t needed_size{ + const size_t needed_size{ (alias_region_size + heap_region_size + stack_region_size + kernel_map_region_size)}; if (alloc_size < needed_size) { ASSERT(false); return ResultOutOfMemory; } - const std::size_t remaining_size{alloc_size - needed_size}; + const size_t remaining_size{alloc_size - needed_size}; // Determine random placements for each region - std::size_t alias_rnd{}, heap_rnd{}, stack_rnd{}, kmap_rnd{}; + size_t alias_rnd{}, heap_rnd{}, stack_rnd{}, kmap_rnd{}; if (enable_aslr) { alias_rnd = KSystemControl::GenerateRandomRange(0, remaining_size / RegionAlignment) * RegionAlignment; @@ -149,124 +151,124 @@ Result KPageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_type } // Setup heap and alias regions - alias_region_start = alloc_start + alias_rnd; - alias_region_end = alias_region_start + alias_region_size; - heap_region_start = alloc_start + heap_rnd; - heap_region_end = heap_region_start + heap_region_size; + m_alias_region_start = alloc_start + alias_rnd; + m_alias_region_end = m_alias_region_start + alias_region_size; + m_heap_region_start = alloc_start + heap_rnd; + m_heap_region_end = m_heap_region_start + heap_region_size; if (alias_rnd <= heap_rnd) { - heap_region_start += alias_region_size; - heap_region_end += alias_region_size; + m_heap_region_start += alias_region_size; + m_heap_region_end += alias_region_size; } else { - alias_region_start += heap_region_size; - alias_region_end += heap_region_size; + m_alias_region_start += heap_region_size; + m_alias_region_end += heap_region_size; } // Setup stack region if (stack_region_size) { - stack_region_start = alloc_start + stack_rnd; - stack_region_end = stack_region_start + stack_region_size; + m_stack_region_start = alloc_start + stack_rnd; + m_stack_region_end = m_stack_region_start + stack_region_size; if (alias_rnd < stack_rnd) { - stack_region_start += alias_region_size; - stack_region_end += alias_region_size; + m_stack_region_start += alias_region_size; + m_stack_region_end += alias_region_size; } else { - alias_region_start += stack_region_size; - alias_region_end += stack_region_size; + m_alias_region_start += stack_region_size; + m_alias_region_end += stack_region_size; } if (heap_rnd < stack_rnd) { - stack_region_start += heap_region_size; - stack_region_end += heap_region_size; + m_stack_region_start += heap_region_size; + m_stack_region_end += heap_region_size; } else { - heap_region_start += stack_region_size; - heap_region_end += stack_region_size; + m_heap_region_start += stack_region_size; + m_heap_region_end += stack_region_size; } } // Setup kernel map region if (kernel_map_region_size) { - kernel_map_region_start = alloc_start + kmap_rnd; - kernel_map_region_end = kernel_map_region_start + kernel_map_region_size; + m_kernel_map_region_start = alloc_start + kmap_rnd; + m_kernel_map_region_end = m_kernel_map_region_start + kernel_map_region_size; if (alias_rnd < kmap_rnd) { - kernel_map_region_start += alias_region_size; - kernel_map_region_end += alias_region_size; + m_kernel_map_region_start += alias_region_size; + m_kernel_map_region_end += alias_region_size; } else { - alias_region_start += kernel_map_region_size; - alias_region_end += kernel_map_region_size; + m_alias_region_start += kernel_map_region_size; + m_alias_region_end += kernel_map_region_size; } if (heap_rnd < kmap_rnd) { - kernel_map_region_start += heap_region_size; - kernel_map_region_end += heap_region_size; + m_kernel_map_region_start += heap_region_size; + m_kernel_map_region_end += heap_region_size; } else { - heap_region_start += kernel_map_region_size; - heap_region_end += kernel_map_region_size; + m_heap_region_start += kernel_map_region_size; + m_heap_region_end += kernel_map_region_size; } if (stack_region_size) { if (stack_rnd < kmap_rnd) { - kernel_map_region_start += stack_region_size; - kernel_map_region_end += stack_region_size; + m_kernel_map_region_start += stack_region_size; + m_kernel_map_region_end += stack_region_size; } else { - stack_region_start += kernel_map_region_size; - stack_region_end += kernel_map_region_size; + m_stack_region_start += kernel_map_region_size; + m_stack_region_end += kernel_map_region_size; } } } // Set heap members - current_heap_end = heap_region_start; - max_heap_size = 0; - max_physical_memory_size = 0; + m_current_heap_end = m_heap_region_start; + m_max_heap_size = 0; + m_max_physical_memory_size = 0; // Ensure that we regions inside our address space auto IsInAddressSpace = [&](VAddr addr) { - return address_space_start <= addr && addr <= address_space_end; + return m_address_space_start <= addr && addr <= m_address_space_end; }; - ASSERT(IsInAddressSpace(alias_region_start)); - ASSERT(IsInAddressSpace(alias_region_end)); - ASSERT(IsInAddressSpace(heap_region_start)); - ASSERT(IsInAddressSpace(heap_region_end)); - ASSERT(IsInAddressSpace(stack_region_start)); - ASSERT(IsInAddressSpace(stack_region_end)); - ASSERT(IsInAddressSpace(kernel_map_region_start)); - ASSERT(IsInAddressSpace(kernel_map_region_end)); + ASSERT(IsInAddressSpace(m_alias_region_start)); + ASSERT(IsInAddressSpace(m_alias_region_end)); + ASSERT(IsInAddressSpace(m_heap_region_start)); + ASSERT(IsInAddressSpace(m_heap_region_end)); + ASSERT(IsInAddressSpace(m_stack_region_start)); + ASSERT(IsInAddressSpace(m_stack_region_end)); + ASSERT(IsInAddressSpace(m_kernel_map_region_start)); + ASSERT(IsInAddressSpace(m_kernel_map_region_end)); // Ensure that we selected regions that don't overlap - const VAddr alias_start{alias_region_start}; - const VAddr alias_last{alias_region_end - 1}; - const VAddr heap_start{heap_region_start}; - const VAddr heap_last{heap_region_end - 1}; - const VAddr stack_start{stack_region_start}; - const VAddr stack_last{stack_region_end - 1}; - const VAddr kmap_start{kernel_map_region_start}; - const VAddr kmap_last{kernel_map_region_end - 1}; + const VAddr alias_start{m_alias_region_start}; + const VAddr alias_last{m_alias_region_end - 1}; + const VAddr heap_start{m_heap_region_start}; + const VAddr heap_last{m_heap_region_end - 1}; + const VAddr stack_start{m_stack_region_start}; + const VAddr stack_last{m_stack_region_end - 1}; + const VAddr kmap_start{m_kernel_map_region_start}; + const VAddr kmap_last{m_kernel_map_region_end - 1}; ASSERT(alias_last < heap_start || heap_last < alias_start); ASSERT(alias_last < stack_start || stack_last < alias_start); ASSERT(alias_last < kmap_start || kmap_last < alias_start); ASSERT(heap_last < stack_start || stack_last < heap_start); ASSERT(heap_last < kmap_start || kmap_last < heap_start); - current_heap_end = heap_region_start; - max_heap_size = 0; - mapped_physical_memory_size = 0; - memory_pool = pool; + m_current_heap_end = m_heap_region_start; + m_max_heap_size = 0; + m_mapped_physical_memory_size = 0; + m_memory_pool = pool; - page_table_impl.Resize(address_space_width, PageBits); + m_page_table_impl.Resize(m_address_space_width, PageBits); - return memory_block_manager.Initialize(address_space_start, address_space_end, - memory_block_slab_manager); + return m_memory_block_manager.Initialize(m_address_space_start, m_address_space_end, + m_memory_block_slab_manager); } void KPageTable::Finalize() { - memory_block_manager.Finalize(memory_block_slab_manager, [&](VAddr addr, u64 size) { - system.Memory().UnmapRegion(page_table_impl, addr, size); + m_memory_block_manager.Finalize(m_memory_block_slab_manager, [&](VAddr addr, u64 size) { + m_system.Memory().UnmapRegion(m_page_table_impl, addr, size); }); } -Result KPageTable::MapProcessCode(VAddr addr, std::size_t num_pages, KMemoryState state, +Result KPageTable::MapProcessCode(VAddr addr, size_t num_pages, KMemoryState state, KMemoryPermission perm) { const u64 size{num_pages * PageSize}; @@ -274,7 +276,7 @@ Result KPageTable::MapProcessCode(VAddr addr, std::size_t num_pages, KMemoryStat R_UNLESS(this->CanContain(addr, size, state), ResultInvalidCurrentMemory); // Lock the table. - KScopedLightLock lk(general_lock); + KScopedLightLock lk(m_general_lock); // Verify that the destination memory is unmapped. R_TRY(this->CheckMemoryState(addr, size, KMemoryState::All, KMemoryState::Free, @@ -284,43 +286,43 @@ Result KPageTable::MapProcessCode(VAddr addr, std::size_t num_pages, KMemoryStat // Create an update allocator. Result allocator_result{ResultSuccess}; KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), - memory_block_slab_manager); + m_memory_block_slab_manager); // Allocate and open. KPageGroup pg; - R_TRY(system.Kernel().MemoryManager().AllocateAndOpen( + R_TRY(m_system.Kernel().MemoryManager().AllocateAndOpen( &pg, num_pages, - KMemoryManager::EncodeOption(KMemoryManager::Pool::Application, allocation_option))); + KMemoryManager::EncodeOption(KMemoryManager::Pool::Application, m_allocation_option))); R_TRY(Operate(addr, num_pages, pg, OperationType::MapGroup)); // Update the blocks. - memory_block_manager.Update(std::addressof(allocator), addr, num_pages, state, perm, - KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal, - KMemoryBlockDisableMergeAttribute::None); + m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, state, perm, + KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal, + KMemoryBlockDisableMergeAttribute::None); return ResultSuccess; } -Result KPageTable::MapCodeMemory(VAddr dst_address, VAddr src_address, std::size_t size) { +Result KPageTable::MapCodeMemory(VAddr dst_address, VAddr src_address, size_t size) { // Validate the mapping request. R_UNLESS(this->CanContain(dst_address, size, KMemoryState::AliasCode), ResultInvalidMemoryRegion); // Lock the table. - KScopedLightLock lk(general_lock); + KScopedLightLock lk(m_general_lock); // Verify that the source memory is normal heap. KMemoryState src_state{}; KMemoryPermission src_perm{}; - std::size_t num_src_allocator_blocks{}; + size_t num_src_allocator_blocks{}; R_TRY(this->CheckMemoryState(&src_state, &src_perm, nullptr, &num_src_allocator_blocks, src_address, size, KMemoryState::All, KMemoryState::Normal, KMemoryPermission::All, KMemoryPermission::UserReadWrite, KMemoryAttribute::All, KMemoryAttribute::None)); // Verify that the destination memory is unmapped. - std::size_t num_dst_allocator_blocks{}; + size_t num_dst_allocator_blocks{}; R_TRY(this->CheckMemoryState(&num_dst_allocator_blocks, dst_address, size, KMemoryState::All, KMemoryState::Free, KMemoryPermission::None, KMemoryPermission::None, KMemoryAttribute::None, @@ -328,20 +330,22 @@ Result KPageTable::MapCodeMemory(VAddr dst_address, VAddr src_address, std::size // Create an update allocator for the source. Result src_allocator_result{ResultSuccess}; - KMemoryBlockManagerUpdateAllocator src_allocator( - std::addressof(src_allocator_result), memory_block_slab_manager, num_src_allocator_blocks); + KMemoryBlockManagerUpdateAllocator src_allocator(std::addressof(src_allocator_result), + m_memory_block_slab_manager, + num_src_allocator_blocks); R_TRY(src_allocator_result); // Create an update allocator for the destination. Result dst_allocator_result{ResultSuccess}; - KMemoryBlockManagerUpdateAllocator dst_allocator( - std::addressof(dst_allocator_result), memory_block_slab_manager, num_dst_allocator_blocks); + KMemoryBlockManagerUpdateAllocator dst_allocator(std::addressof(dst_allocator_result), + m_memory_block_slab_manager, + num_dst_allocator_blocks); R_TRY(dst_allocator_result); // Map the code memory. { // Determine the number of pages being operated on. - const std::size_t num_pages = size / PageSize; + const size_t num_pages = size / PageSize; // Create page groups for the memory being mapped. KPageGroup pg; @@ -366,37 +370,37 @@ Result KPageTable::MapCodeMemory(VAddr dst_address, VAddr src_address, std::size unprot_guard.Cancel(); // Apply the memory block updates. - memory_block_manager.Update(std::addressof(src_allocator), src_address, num_pages, - src_state, new_perm, KMemoryAttribute::Locked, - KMemoryBlockDisableMergeAttribute::Locked, - KMemoryBlockDisableMergeAttribute::None); - memory_block_manager.Update(std::addressof(dst_allocator), dst_address, num_pages, - KMemoryState::AliasCode, new_perm, KMemoryAttribute::None, - KMemoryBlockDisableMergeAttribute::Normal, - KMemoryBlockDisableMergeAttribute::None); + m_memory_block_manager.Update(std::addressof(src_allocator), src_address, num_pages, + src_state, new_perm, KMemoryAttribute::Locked, + KMemoryBlockDisableMergeAttribute::Locked, + KMemoryBlockDisableMergeAttribute::None); + m_memory_block_manager.Update(std::addressof(dst_allocator), dst_address, num_pages, + KMemoryState::AliasCode, new_perm, KMemoryAttribute::None, + KMemoryBlockDisableMergeAttribute::Normal, + KMemoryBlockDisableMergeAttribute::None); } return ResultSuccess; } -Result KPageTable::UnmapCodeMemory(VAddr dst_address, VAddr src_address, std::size_t size, +Result KPageTable::UnmapCodeMemory(VAddr dst_address, VAddr src_address, size_t size, ICacheInvalidationStrategy icache_invalidation_strategy) { // Validate the mapping request. R_UNLESS(this->CanContain(dst_address, size, KMemoryState::AliasCode), ResultInvalidMemoryRegion); // Lock the table. - KScopedLightLock lk(general_lock); + KScopedLightLock lk(m_general_lock); // Verify that the source memory is locked normal heap. - std::size_t num_src_allocator_blocks{}; + size_t num_src_allocator_blocks{}; R_TRY(this->CheckMemoryState(std::addressof(num_src_allocator_blocks), src_address, size, KMemoryState::All, KMemoryState::Normal, KMemoryPermission::None, KMemoryPermission::None, KMemoryAttribute::All, KMemoryAttribute::Locked)); // Verify that the destination memory is aliasable code. - std::size_t num_dst_allocator_blocks{}; + size_t num_dst_allocator_blocks{}; R_TRY(this->CheckMemoryStateContiguous( std::addressof(num_dst_allocator_blocks), dst_address, size, KMemoryState::FlagCanCodeAlias, KMemoryState::FlagCanCodeAlias, KMemoryPermission::None, KMemoryPermission::None, @@ -405,7 +409,7 @@ Result KPageTable::UnmapCodeMemory(VAddr dst_address, VAddr src_address, std::si // Determine whether any pages being unmapped are code. bool any_code_pages = false; { - KMemoryBlockManager::const_iterator it = memory_block_manager.FindIterator(dst_address); + KMemoryBlockManager::const_iterator it = m_memory_block_manager.FindIterator(dst_address); while (true) { // Get the memory info. const KMemoryInfo info = it->GetMemoryInfo(); @@ -431,9 +435,9 @@ Result KPageTable::UnmapCodeMemory(VAddr dst_address, VAddr src_address, std::si SCOPE_EXIT({ if (reprotected_pages && any_code_pages) { if (icache_invalidation_strategy == ICacheInvalidationStrategy::InvalidateRange) { - system.InvalidateCpuInstructionCacheRange(dst_address, size); + m_system.InvalidateCpuInstructionCacheRange(dst_address, size); } else { - system.InvalidateCpuInstructionCaches(); + m_system.InvalidateCpuInstructionCaches(); } } }); @@ -441,19 +445,19 @@ Result KPageTable::UnmapCodeMemory(VAddr dst_address, VAddr src_address, std::si // Unmap. { // Determine the number of pages being operated on. - const std::size_t num_pages = size / PageSize; + const size_t num_pages = size / PageSize; // Create an update allocator for the source. Result src_allocator_result{ResultSuccess}; KMemoryBlockManagerUpdateAllocator src_allocator(std::addressof(src_allocator_result), - memory_block_slab_manager, + m_memory_block_slab_manager, num_src_allocator_blocks); R_TRY(src_allocator_result); // Create an update allocator for the destination. Result dst_allocator_result{ResultSuccess}; KMemoryBlockManagerUpdateAllocator dst_allocator(std::addressof(dst_allocator_result), - memory_block_slab_manager, + m_memory_block_slab_manager, num_dst_allocator_blocks); R_TRY(dst_allocator_result); @@ -465,14 +469,14 @@ Result KPageTable::UnmapCodeMemory(VAddr dst_address, VAddr src_address, std::si OperationType::ChangePermissions)); // Apply the memory block updates. - memory_block_manager.Update(std::addressof(dst_allocator), dst_address, num_pages, - KMemoryState::None, KMemoryPermission::None, - KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::None, - KMemoryBlockDisableMergeAttribute::Normal); - memory_block_manager.Update(std::addressof(src_allocator), src_address, num_pages, - KMemoryState::Normal, KMemoryPermission::UserReadWrite, - KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::None, - KMemoryBlockDisableMergeAttribute::Locked); + m_memory_block_manager.Update( + std::addressof(dst_allocator), dst_address, num_pages, KMemoryState::None, + KMemoryPermission::None, KMemoryAttribute::None, + KMemoryBlockDisableMergeAttribute::None, KMemoryBlockDisableMergeAttribute::Normal); + m_memory_block_manager.Update( + std::addressof(src_allocator), src_address, num_pages, KMemoryState::Normal, + KMemoryPermission::UserReadWrite, KMemoryAttribute::None, + KMemoryBlockDisableMergeAttribute::None, KMemoryBlockDisableMergeAttribute::Locked); // Note that we reprotected pages. reprotected_pages = true; @@ -481,9 +485,8 @@ Result KPageTable::UnmapCodeMemory(VAddr dst_address, VAddr src_address, std::si return ResultSuccess; } -VAddr KPageTable::FindFreeArea(VAddr region_start, std::size_t region_num_pages, - std::size_t num_pages, std::size_t alignment, std::size_t offset, - std::size_t guard_pages) { +VAddr KPageTable::FindFreeArea(VAddr region_start, size_t region_num_pages, size_t num_pages, + size_t alignment, size_t offset, size_t guard_pages) { VAddr address = 0; if (num_pages <= region_num_pages) { @@ -492,8 +495,8 @@ VAddr KPageTable::FindFreeArea(VAddr region_start, std::size_t region_num_pages, } // Find the first free area. if (address == 0) { - address = memory_block_manager.FindFreeArea(region_start, region_num_pages, num_pages, - alignment, offset, guard_pages); + address = m_memory_block_manager.FindFreeArea(region_start, region_num_pages, num_pages, + alignment, offset, guard_pages); } } @@ -511,7 +514,8 @@ Result KPageTable::MakePageGroup(KPageGroup& pg, VAddr addr, size_t num_pages) { // Begin traversal. Common::PageTable::TraversalContext context; Common::PageTable::TraversalEntry next_entry; - R_UNLESS(page_table_impl.BeginTraversal(next_entry, context, addr), ResultInvalidCurrentMemory); + R_UNLESS(m_page_table_impl.BeginTraversal(next_entry, context, addr), + ResultInvalidCurrentMemory); // Prepare tracking variables. PAddr cur_addr = next_entry.phys_addr; @@ -519,9 +523,9 @@ Result KPageTable::MakePageGroup(KPageGroup& pg, VAddr addr, size_t num_pages) { size_t tot_size = cur_size; // Iterate, adding to group as we go. - const auto& memory_layout = system.Kernel().MemoryLayout(); + const auto& memory_layout = m_system.Kernel().MemoryLayout(); while (tot_size < size) { - R_UNLESS(page_table_impl.ContinueTraversal(next_entry, context), + R_UNLESS(m_page_table_impl.ContinueTraversal(next_entry, context), ResultInvalidCurrentMemory); if (next_entry.phys_addr != (cur_addr + cur_size)) { @@ -557,7 +561,7 @@ bool KPageTable::IsValidPageGroup(const KPageGroup& pg_ll, VAddr addr, size_t nu const size_t size = num_pages * PageSize; const auto& pg = pg_ll.Nodes(); - const auto& memory_layout = system.Kernel().MemoryLayout(); + const auto& memory_layout = m_system.Kernel().MemoryLayout(); // Empty groups are necessarily invalid. if (pg.empty()) { @@ -584,7 +588,7 @@ bool KPageTable::IsValidPageGroup(const KPageGroup& pg_ll, VAddr addr, size_t nu // Begin traversal. Common::PageTable::TraversalContext context; Common::PageTable::TraversalEntry next_entry; - if (!page_table_impl.BeginTraversal(next_entry, context, addr)) { + if (!m_page_table_impl.BeginTraversal(next_entry, context, addr)) { return false; } @@ -595,7 +599,7 @@ bool KPageTable::IsValidPageGroup(const KPageGroup& pg_ll, VAddr addr, size_t nu // Iterate, comparing expected to actual. while (tot_size < size) { - if (!page_table_impl.ContinueTraversal(next_entry, context)) { + if (!m_page_table_impl.ContinueTraversal(next_entry, context)) { return false; } @@ -641,11 +645,11 @@ bool KPageTable::IsValidPageGroup(const KPageGroup& pg_ll, VAddr addr, size_t nu return cur_block_address == cur_addr && cur_block_pages == (cur_size / PageSize); } -Result KPageTable::UnmapProcessMemory(VAddr dst_addr, std::size_t size, KPageTable& src_page_table, +Result KPageTable::UnmapProcessMemory(VAddr dst_addr, size_t size, KPageTable& src_page_table, VAddr src_addr) { - KScopedLightLock lk(general_lock); + KScopedLightLock lk(m_general_lock); - const std::size_t num_pages{size / PageSize}; + const size_t num_pages{size / PageSize}; // Check that the memory is mapped in the destination process. size_t num_allocator_blocks; @@ -663,48 +667,48 @@ Result KPageTable::UnmapProcessMemory(VAddr dst_addr, std::size_t size, KPageTab // Create an update allocator. Result allocator_result{ResultSuccess}; KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), - memory_block_slab_manager, num_allocator_blocks); + m_memory_block_slab_manager, num_allocator_blocks); R_TRY(allocator_result); CASCADE_CODE(Operate(dst_addr, num_pages, KMemoryPermission::None, OperationType::Unmap)); // Apply the memory block update. - memory_block_manager.Update(std::addressof(allocator), dst_addr, num_pages, KMemoryState::Free, - KMemoryPermission::None, KMemoryAttribute::None, - KMemoryBlockDisableMergeAttribute::None, - KMemoryBlockDisableMergeAttribute::Normal); + m_memory_block_manager.Update(std::addressof(allocator), dst_addr, num_pages, + KMemoryState::Free, KMemoryPermission::None, + KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::None, + KMemoryBlockDisableMergeAttribute::Normal); - system.InvalidateCpuInstructionCaches(); + m_system.InvalidateCpuInstructionCaches(); return ResultSuccess; } -Result KPageTable::MapPhysicalMemory(VAddr address, std::size_t size) { +Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) { // Lock the physical memory lock. - KScopedLightLock map_phys_mem_lk(map_physical_memory_lock); + KScopedLightLock map_phys_mem_lk(m_map_physical_memory_lock); // Calculate the last address for convenience. const VAddr last_address = address + size - 1; // Define iteration variables. VAddr cur_address; - std::size_t mapped_size; + size_t mapped_size; // The entire mapping process can be retried. while (true) { // Check if the memory is already mapped. { // Lock the table. - KScopedLightLock lk(general_lock); + KScopedLightLock lk(m_general_lock); // Iterate over the memory. cur_address = address; mapped_size = 0; - auto it = memory_block_manager.FindIterator(cur_address); + auto it = m_memory_block_manager.FindIterator(cur_address); while (true) { // Check that the iterator is valid. - ASSERT(it != memory_block_manager.end()); + ASSERT(it != m_memory_block_manager.end()); // Get the memory info. const KMemoryInfo info = it->GetMemoryInfo(); @@ -735,20 +739,20 @@ Result KPageTable::MapPhysicalMemory(VAddr address, std::size_t size) { { // Reserve the memory from the process resource limit. KScopedResourceReservation memory_reservation( - system.Kernel().CurrentProcess()->GetResourceLimit(), + m_system.Kernel().CurrentProcess()->GetResourceLimit(), LimitableResource::PhysicalMemory, size - mapped_size); R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached); // Allocate pages for the new memory. KPageGroup pg; - R_TRY(system.Kernel().MemoryManager().AllocateAndOpenForProcess( + R_TRY(m_system.Kernel().MemoryManager().AllocateAndOpenForProcess( &pg, (size - mapped_size) / PageSize, - KMemoryManager::EncodeOption(memory_pool, allocation_option), 0, 0)); + KMemoryManager::EncodeOption(m_memory_pool, m_allocation_option), 0, 0)); // Map the memory. { // Lock the table. - KScopedLightLock lk(general_lock); + KScopedLightLock lk(m_general_lock); size_t num_allocator_blocks = 0; @@ -758,10 +762,10 @@ Result KPageTable::MapPhysicalMemory(VAddr address, std::size_t size) { size_t checked_mapped_size = 0; cur_address = address; - auto it = memory_block_manager.FindIterator(cur_address); + auto it = m_memory_block_manager.FindIterator(cur_address); while (true) { // Check that the iterator is valid. - ASSERT(it != memory_block_manager.end()); + ASSERT(it != m_memory_block_manager.end()); // Get the memory info. const KMemoryInfo info = it->GetMemoryInfo(); @@ -805,7 +809,7 @@ Result KPageTable::MapPhysicalMemory(VAddr address, std::size_t size) { ASSERT(num_allocator_blocks <= KMemoryBlockManagerUpdateAllocator::MaxBlocks); Result allocator_result{ResultSuccess}; KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), - memory_block_slab_manager, + m_memory_block_slab_manager, num_allocator_blocks); R_TRY(allocator_result); @@ -818,10 +822,10 @@ Result KPageTable::MapPhysicalMemory(VAddr address, std::size_t size) { // Iterate, unmapping the pages. cur_address = address; - auto it = memory_block_manager.FindIterator(cur_address); + auto it = m_memory_block_manager.FindIterator(cur_address); while (true) { // Check that the iterator is valid. - ASSERT(it != memory_block_manager.end()); + ASSERT(it != m_memory_block_manager.end()); // Get the memory info. const KMemoryInfo info = it->GetMemoryInfo(); @@ -857,10 +861,10 @@ Result KPageTable::MapPhysicalMemory(VAddr address, std::size_t size) { PAddr pg_phys_addr = pg_it->GetAddress(); size_t pg_pages = pg_it->GetNumPages(); - auto it = memory_block_manager.FindIterator(cur_address); + auto it = m_memory_block_manager.FindIterator(cur_address); while (true) { // Check that the iterator is valid. - ASSERT(it != memory_block_manager.end()); + ASSERT(it != m_memory_block_manager.end()); // Get the memory info. const KMemoryInfo info = it->GetMemoryInfo(); @@ -913,10 +917,10 @@ Result KPageTable::MapPhysicalMemory(VAddr address, std::size_t size) { memory_reservation.Commit(); // Increase our tracked mapped size. - mapped_physical_memory_size += (size - mapped_size); + m_mapped_physical_memory_size += (size - mapped_size); // Update the relevant memory blocks. - memory_block_manager.UpdateIfMatch( + m_memory_block_manager.UpdateIfMatch( std::addressof(allocator), address, size / PageSize, KMemoryState::Free, KMemoryPermission::None, KMemoryAttribute::None, KMemoryState::Normal, KMemoryPermission::UserReadWrite, KMemoryAttribute::None); @@ -930,20 +934,20 @@ Result KPageTable::MapPhysicalMemory(VAddr address, std::size_t size) { } } -Result KPageTable::UnmapPhysicalMemory(VAddr address, std::size_t size) { +Result KPageTable::UnmapPhysicalMemory(VAddr address, size_t size) { // Lock the physical memory lock. - KScopedLightLock map_phys_mem_lk(map_physical_memory_lock); + KScopedLightLock map_phys_mem_lk(m_map_physical_memory_lock); // Lock the table. - KScopedLightLock lk(general_lock); + KScopedLightLock lk(m_general_lock); // Calculate the last address for convenience. const VAddr last_address = address + size - 1; // Define iteration variables. VAddr cur_address = 0; - std::size_t mapped_size = 0; - std::size_t num_allocator_blocks = 0; + size_t mapped_size = 0; + size_t num_allocator_blocks = 0; // Check if the memory is mapped. { @@ -951,10 +955,10 @@ Result KPageTable::UnmapPhysicalMemory(VAddr address, std::size_t size) { cur_address = address; mapped_size = 0; - auto it = memory_block_manager.FindIterator(cur_address); + auto it = m_memory_block_manager.FindIterator(cur_address); while (true) { // Check that the iterator is valid. - ASSERT(it != memory_block_manager.end()); + ASSERT(it != m_memory_block_manager.end()); // Get the memory info. const KMemoryInfo info = it->GetMemoryInfo(); @@ -1053,7 +1057,7 @@ Result KPageTable::UnmapPhysicalMemory(VAddr address, std::size_t size) { ASSERT(num_allocator_blocks <= KMemoryBlockManagerUpdateAllocator::MaxBlocks); Result allocator_result{ResultSuccess}; KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), - memory_block_slab_manager, num_allocator_blocks); + m_memory_block_slab_manager, num_allocator_blocks); R_TRY(allocator_result); // Reset the current tracking address, and make sure we clean up on failure. @@ -1064,7 +1068,7 @@ Result KPageTable::UnmapPhysicalMemory(VAddr address, std::size_t size) { cur_address = address; // Iterate over the memory we unmapped. - auto it = memory_block_manager.FindIterator(cur_address); + auto it = m_memory_block_manager.FindIterator(cur_address); auto pg_it = pg.Nodes().begin(); PAddr pg_phys_addr = pg_it->GetAddress(); size_t pg_pages = pg_it->GetNumPages(); @@ -1119,10 +1123,10 @@ Result KPageTable::UnmapPhysicalMemory(VAddr address, std::size_t size) { }); // Iterate over the memory, unmapping as we go. - auto it = memory_block_manager.FindIterator(cur_address); + auto it = m_memory_block_manager.FindIterator(cur_address); while (true) { // Check that the iterator is valid. - ASSERT(it != memory_block_manager.end()); + ASSERT(it != m_memory_block_manager.end()); // Get the memory info. const KMemoryInfo info = it->GetMemoryInfo(); @@ -1149,20 +1153,20 @@ Result KPageTable::UnmapPhysicalMemory(VAddr address, std::size_t size) { } // Release the memory resource. - mapped_physical_memory_size -= mapped_size; - auto process{system.Kernel().CurrentProcess()}; + m_mapped_physical_memory_size -= mapped_size; + auto process{m_system.Kernel().CurrentProcess()}; process->GetResourceLimit()->Release(LimitableResource::PhysicalMemory, mapped_size); // Update memory blocks. - memory_block_manager.Update(std::addressof(allocator), address, size / PageSize, - KMemoryState::Free, KMemoryPermission::None, KMemoryAttribute::None, - KMemoryBlockDisableMergeAttribute::None, - KMemoryBlockDisableMergeAttribute::None); + m_memory_block_manager.Update(std::addressof(allocator), address, size / PageSize, + KMemoryState::Free, KMemoryPermission::None, + KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::None, + KMemoryBlockDisableMergeAttribute::None); // TODO(bunnei): This is a workaround until the next set of changes, where we add reference // counting for mapped pages. Until then, we must manually close the reference to the page // group. - system.Kernel().MemoryManager().Close(pg); + m_system.Kernel().MemoryManager().Close(pg); // We succeeded. remap_guard.Cancel(); @@ -1170,9 +1174,9 @@ Result KPageTable::UnmapPhysicalMemory(VAddr address, std::size_t size) { return ResultSuccess; } -Result KPageTable::MapMemory(VAddr dst_address, VAddr src_address, std::size_t size) { +Result KPageTable::MapMemory(VAddr dst_address, VAddr src_address, size_t size) { // Lock the table. - KScopedLightLock lk(general_lock); + KScopedLightLock lk(m_general_lock); // Validate that the source address's state is valid. KMemoryState src_state; @@ -1192,19 +1196,21 @@ Result KPageTable::MapMemory(VAddr dst_address, VAddr src_address, std::size_t s // Create an update allocator for the source. Result src_allocator_result{ResultSuccess}; - KMemoryBlockManagerUpdateAllocator src_allocator( - std::addressof(src_allocator_result), memory_block_slab_manager, num_src_allocator_blocks); + KMemoryBlockManagerUpdateAllocator src_allocator(std::addressof(src_allocator_result), + m_memory_block_slab_manager, + num_src_allocator_blocks); R_TRY(src_allocator_result); // Create an update allocator for the destination. Result dst_allocator_result{ResultSuccess}; - KMemoryBlockManagerUpdateAllocator dst_allocator( - std::addressof(dst_allocator_result), memory_block_slab_manager, num_dst_allocator_blocks); + KMemoryBlockManagerUpdateAllocator dst_allocator(std::addressof(dst_allocator_result), + m_memory_block_slab_manager, + num_dst_allocator_blocks); R_TRY(dst_allocator_result); // Map the memory. KPageGroup page_linked_list; - const std::size_t num_pages{size / PageSize}; + const size_t num_pages{size / PageSize}; const KMemoryPermission new_src_perm = static_cast( KMemoryPermission::KernelRead | KMemoryPermission::NotMapped); const KMemoryAttribute new_src_attr = KMemoryAttribute::Locked; @@ -1223,21 +1229,21 @@ Result KPageTable::MapMemory(VAddr dst_address, VAddr src_address, std::size_t s } // Apply the memory block updates. - memory_block_manager.Update(std::addressof(src_allocator), src_address, num_pages, src_state, - new_src_perm, new_src_attr, - KMemoryBlockDisableMergeAttribute::Locked, - KMemoryBlockDisableMergeAttribute::None); - memory_block_manager.Update(std::addressof(dst_allocator), dst_address, num_pages, - KMemoryState::Stack, KMemoryPermission::UserReadWrite, - KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal, - KMemoryBlockDisableMergeAttribute::None); + m_memory_block_manager.Update(std::addressof(src_allocator), src_address, num_pages, src_state, + new_src_perm, new_src_attr, + KMemoryBlockDisableMergeAttribute::Locked, + KMemoryBlockDisableMergeAttribute::None); + m_memory_block_manager.Update(std::addressof(dst_allocator), dst_address, num_pages, + KMemoryState::Stack, KMemoryPermission::UserReadWrite, + KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal, + KMemoryBlockDisableMergeAttribute::None); return ResultSuccess; } -Result KPageTable::UnmapMemory(VAddr dst_address, VAddr src_address, std::size_t size) { +Result KPageTable::UnmapMemory(VAddr dst_address, VAddr src_address, size_t size) { // Lock the table. - KScopedLightLock lk(general_lock); + KScopedLightLock lk(m_general_lock); // Validate that the source address's state is valid. KMemoryState src_state; @@ -1258,19 +1264,21 @@ Result KPageTable::UnmapMemory(VAddr dst_address, VAddr src_address, std::size_t // Create an update allocator for the source. Result src_allocator_result{ResultSuccess}; - KMemoryBlockManagerUpdateAllocator src_allocator( - std::addressof(src_allocator_result), memory_block_slab_manager, num_src_allocator_blocks); + KMemoryBlockManagerUpdateAllocator src_allocator(std::addressof(src_allocator_result), + m_memory_block_slab_manager, + num_src_allocator_blocks); R_TRY(src_allocator_result); // Create an update allocator for the destination. Result dst_allocator_result{ResultSuccess}; - KMemoryBlockManagerUpdateAllocator dst_allocator( - std::addressof(dst_allocator_result), memory_block_slab_manager, num_dst_allocator_blocks); + KMemoryBlockManagerUpdateAllocator dst_allocator(std::addressof(dst_allocator_result), + m_memory_block_slab_manager, + num_dst_allocator_blocks); R_TRY(dst_allocator_result); KPageGroup src_pages; KPageGroup dst_pages; - const std::size_t num_pages{size / PageSize}; + const size_t num_pages{size / PageSize}; AddRegionToPages(src_address, num_pages, src_pages); AddRegionToPages(dst_address, num_pages, dst_pages); @@ -1290,14 +1298,14 @@ Result KPageTable::UnmapMemory(VAddr dst_address, VAddr src_address, std::size_t } // Apply the memory block updates. - memory_block_manager.Update(std::addressof(src_allocator), src_address, num_pages, src_state, - KMemoryPermission::UserReadWrite, KMemoryAttribute::None, - KMemoryBlockDisableMergeAttribute::None, - KMemoryBlockDisableMergeAttribute::Locked); - memory_block_manager.Update(std::addressof(dst_allocator), dst_address, num_pages, - KMemoryState::None, KMemoryPermission::None, KMemoryAttribute::None, - KMemoryBlockDisableMergeAttribute::None, - KMemoryBlockDisableMergeAttribute::Normal); + m_memory_block_manager.Update(std::addressof(src_allocator), src_address, num_pages, src_state, + KMemoryPermission::UserReadWrite, KMemoryAttribute::None, + KMemoryBlockDisableMergeAttribute::None, + KMemoryBlockDisableMergeAttribute::Locked); + m_memory_block_manager.Update(std::addressof(dst_allocator), dst_address, num_pages, + KMemoryState::None, KMemoryPermission::None, + KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::None, + KMemoryBlockDisableMergeAttribute::Normal); return ResultSuccess; } @@ -1312,7 +1320,7 @@ Result KPageTable::MapPages(VAddr addr, const KPageGroup& page_linked_list, if (const auto result{ Operate(cur_addr, node.GetNumPages(), perm, OperationType::Map, node.GetAddress())}; result.IsError()) { - const std::size_t num_pages{(addr - cur_addr) / PageSize}; + const size_t num_pages{(addr - cur_addr) / PageSize}; ASSERT(Operate(addr, num_pages, KMemoryPermission::None, OperationType::Unmap) .IsSuccess()); @@ -1329,12 +1337,12 @@ Result KPageTable::MapPages(VAddr addr, const KPageGroup& page_linked_list, Result KPageTable::MapPages(VAddr address, KPageGroup& page_linked_list, KMemoryState state, KMemoryPermission perm) { // Check that the map is in range. - const std::size_t num_pages{page_linked_list.GetNumPages()}; - const std::size_t size{num_pages * PageSize}; + const size_t num_pages{page_linked_list.GetNumPages()}; + const size_t size{num_pages * PageSize}; R_UNLESS(this->CanContain(address, size, state), ResultInvalidCurrentMemory); // Lock the table. - KScopedLightLock lk(general_lock); + KScopedLightLock lk(m_general_lock); // Check the memory state. R_TRY(this->CheckMemoryState(address, size, KMemoryState::All, KMemoryState::Free, @@ -1344,23 +1352,22 @@ Result KPageTable::MapPages(VAddr address, KPageGroup& page_linked_list, KMemory // Create an update allocator. Result allocator_result{ResultSuccess}; KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), - memory_block_slab_manager); + m_memory_block_slab_manager); // Map the pages. R_TRY(MapPages(address, page_linked_list, perm)); // Update the blocks. - memory_block_manager.Update(std::addressof(allocator), address, num_pages, state, perm, - KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal, - KMemoryBlockDisableMergeAttribute::None); + m_memory_block_manager.Update(std::addressof(allocator), address, num_pages, state, perm, + KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal, + KMemoryBlockDisableMergeAttribute::None); return ResultSuccess; } -Result KPageTable::MapPages(VAddr* out_addr, std::size_t num_pages, std::size_t alignment, - PAddr phys_addr, bool is_pa_valid, VAddr region_start, - std::size_t region_num_pages, KMemoryState state, - KMemoryPermission perm) { +Result KPageTable::MapPages(VAddr* out_addr, size_t num_pages, size_t alignment, PAddr phys_addr, + bool is_pa_valid, VAddr region_start, size_t region_num_pages, + KMemoryState state, KMemoryPermission perm) { ASSERT(Common::IsAligned(alignment, PageSize) && alignment >= PageSize); // Ensure this is a valid map request. @@ -1369,7 +1376,7 @@ Result KPageTable::MapPages(VAddr* out_addr, std::size_t num_pages, std::size_t R_UNLESS(num_pages < region_num_pages, ResultOutOfMemory); // Lock the table. - KScopedLightLock lk(general_lock); + KScopedLightLock lk(m_general_lock); // Find a random address to map at. VAddr addr = this->FindFreeArea(region_start, region_num_pages, num_pages, alignment, 0, @@ -1385,7 +1392,7 @@ Result KPageTable::MapPages(VAddr* out_addr, std::size_t num_pages, std::size_t // Create an update allocator. Result allocator_result{ResultSuccess}; KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), - memory_block_slab_manager); + m_memory_block_slab_manager); // Perform mapping operation. if (is_pa_valid) { @@ -1395,9 +1402,9 @@ Result KPageTable::MapPages(VAddr* out_addr, std::size_t num_pages, std::size_t } // Update the blocks. - memory_block_manager.Update(std::addressof(allocator), addr, num_pages, state, perm, - KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal, - KMemoryBlockDisableMergeAttribute::None); + m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, state, perm, + KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal, + KMemoryBlockDisableMergeAttribute::None); // We successfully mapped the pages. *out_addr = addr; @@ -1424,12 +1431,12 @@ Result KPageTable::UnmapPages(VAddr addr, const KPageGroup& page_linked_list) { Result KPageTable::UnmapPages(VAddr address, KPageGroup& page_linked_list, KMemoryState state) { // Check that the unmap is in range. - const std::size_t num_pages{page_linked_list.GetNumPages()}; - const std::size_t size{num_pages * PageSize}; + const size_t num_pages{page_linked_list.GetNumPages()}; + const size_t size{num_pages * PageSize}; R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory); // Lock the table. - KScopedLightLock lk(general_lock); + KScopedLightLock lk(m_general_lock); // Check the memory state. size_t num_allocator_blocks; @@ -1441,31 +1448,31 @@ Result KPageTable::UnmapPages(VAddr address, KPageGroup& page_linked_list, KMemo // Create an update allocator. Result allocator_result{ResultSuccess}; KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), - memory_block_slab_manager, num_allocator_blocks); + m_memory_block_slab_manager, num_allocator_blocks); R_TRY(allocator_result); // Perform the unmap. R_TRY(UnmapPages(address, page_linked_list)); // Update the blocks. - memory_block_manager.Update(std::addressof(allocator), address, num_pages, KMemoryState::Free, - KMemoryPermission::None, KMemoryAttribute::None, - KMemoryBlockDisableMergeAttribute::None, - KMemoryBlockDisableMergeAttribute::Normal); + m_memory_block_manager.Update(std::addressof(allocator), address, num_pages, KMemoryState::Free, + KMemoryPermission::None, KMemoryAttribute::None, + KMemoryBlockDisableMergeAttribute::None, + KMemoryBlockDisableMergeAttribute::Normal); return ResultSuccess; } -Result KPageTable::UnmapPages(VAddr address, std::size_t num_pages, KMemoryState state) { +Result KPageTable::UnmapPages(VAddr address, size_t num_pages, KMemoryState state) { // Check that the unmap is in range. - const std::size_t size = num_pages * PageSize; + const size_t size = num_pages * PageSize; R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory); // Lock the table. - KScopedLightLock lk(general_lock); + KScopedLightLock lk(m_general_lock); // Check the memory state. - std::size_t num_allocator_blocks{}; + size_t num_allocator_blocks{}; R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), address, size, KMemoryState::All, state, KMemoryPermission::None, KMemoryPermission::None, KMemoryAttribute::All, @@ -1474,17 +1481,17 @@ Result KPageTable::UnmapPages(VAddr address, std::size_t num_pages, KMemoryState // Create an update allocator. Result allocator_result{ResultSuccess}; KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), - memory_block_slab_manager, num_allocator_blocks); + m_memory_block_slab_manager, num_allocator_blocks); R_TRY(allocator_result); // Perform the unmap. R_TRY(Operate(address, num_pages, KMemoryPermission::None, OperationType::Unmap)); // Update the blocks. - memory_block_manager.Update(std::addressof(allocator), address, num_pages, KMemoryState::Free, - KMemoryPermission::None, KMemoryAttribute::None, - KMemoryBlockDisableMergeAttribute::None, - KMemoryBlockDisableMergeAttribute::Normal); + m_memory_block_manager.Update(std::addressof(allocator), address, num_pages, KMemoryState::Free, + KMemoryPermission::None, KMemoryAttribute::None, + KMemoryBlockDisableMergeAttribute::None, + KMemoryBlockDisableMergeAttribute::Normal); return ResultSuccess; } @@ -1501,7 +1508,7 @@ Result KPageTable::MakeAndOpenPageGroup(KPageGroup* out, VAddr address, size_t n R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory); // Lock the table. - KScopedLightLock lk(general_lock); + KScopedLightLock lk(m_general_lock); // Check if state allows us to create the group. R_TRY(this->CheckMemoryState(address, size, state_mask | KMemoryState::FlagReferenceCounted, @@ -1514,12 +1521,12 @@ Result KPageTable::MakeAndOpenPageGroup(KPageGroup* out, VAddr address, size_t n return ResultSuccess; } -Result KPageTable::SetProcessMemoryPermission(VAddr addr, std::size_t size, +Result KPageTable::SetProcessMemoryPermission(VAddr addr, size_t size, Svc::MemoryPermission svc_perm) { const size_t num_pages = size / PageSize; // Lock the table. - KScopedLightLock lk(general_lock); + KScopedLightLock lk(m_general_lock); // Verify we can change the memory permission. KMemoryState old_state; @@ -1559,7 +1566,7 @@ Result KPageTable::SetProcessMemoryPermission(VAddr addr, std::size_t size, // Create an update allocator. Result allocator_result{ResultSuccess}; KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), - memory_block_slab_manager, num_allocator_blocks); + m_memory_block_slab_manager, num_allocator_blocks); R_TRY(allocator_result); // Perform mapping operation. @@ -1568,29 +1575,29 @@ Result KPageTable::SetProcessMemoryPermission(VAddr addr, std::size_t size, R_TRY(Operate(addr, num_pages, new_perm, operation)); // Update the blocks. - memory_block_manager.Update(std::addressof(allocator), addr, num_pages, new_state, new_perm, - KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::None, - KMemoryBlockDisableMergeAttribute::None); + m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, new_state, new_perm, + KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::None, + KMemoryBlockDisableMergeAttribute::None); // Ensure cache coherency, if we're setting pages as executable. if (is_x) { - system.InvalidateCpuInstructionCacheRange(addr, size); + m_system.InvalidateCpuInstructionCacheRange(addr, size); } return ResultSuccess; } KMemoryInfo KPageTable::QueryInfoImpl(VAddr addr) { - KScopedLightLock lk(general_lock); + KScopedLightLock lk(m_general_lock); - return memory_block_manager.FindBlock(addr)->GetMemoryInfo(); + return m_memory_block_manager.FindBlock(addr)->GetMemoryInfo(); } KMemoryInfo KPageTable::QueryInfo(VAddr addr) { if (!Contains(addr, 1)) { return { - .m_address = address_space_end, - .m_size = 0 - address_space_end, + .m_address = m_address_space_end, + .m_size = 0 - m_address_space_end, .m_state = static_cast(Svc::MemoryState::Inaccessible), .m_device_disable_merge_left_count = 0, .m_device_disable_merge_right_count = 0, @@ -1607,12 +1614,11 @@ KMemoryInfo KPageTable::QueryInfo(VAddr addr) { return QueryInfoImpl(addr); } -Result KPageTable::SetMemoryPermission(VAddr addr, std::size_t size, - Svc::MemoryPermission svc_perm) { +Result KPageTable::SetMemoryPermission(VAddr addr, size_t size, Svc::MemoryPermission svc_perm) { const size_t num_pages = size / PageSize; // Lock the table. - KScopedLightLock lk(general_lock); + KScopedLightLock lk(m_general_lock); // Verify we can change the memory permission. KMemoryState old_state; @@ -1631,27 +1637,27 @@ Result KPageTable::SetMemoryPermission(VAddr addr, std::size_t size, // Create an update allocator. Result allocator_result{ResultSuccess}; KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), - memory_block_slab_manager, num_allocator_blocks); + m_memory_block_slab_manager, num_allocator_blocks); R_TRY(allocator_result); // Perform mapping operation. R_TRY(Operate(addr, num_pages, new_perm, OperationType::ChangePermissions)); // Update the blocks. - memory_block_manager.Update(std::addressof(allocator), addr, num_pages, old_state, new_perm, - KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::None, - KMemoryBlockDisableMergeAttribute::None); + m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, old_state, new_perm, + KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::None, + KMemoryBlockDisableMergeAttribute::None); return ResultSuccess; } -Result KPageTable::SetMemoryAttribute(VAddr addr, std::size_t size, u32 mask, u32 attr) { +Result KPageTable::SetMemoryAttribute(VAddr addr, size_t size, u32 mask, u32 attr) { const size_t num_pages = size / PageSize; ASSERT((static_cast(mask) | KMemoryAttribute::SetMask) == KMemoryAttribute::SetMask); // Lock the table. - KScopedLightLock lk(general_lock); + KScopedLightLock lk(m_general_lock); // Verify we can change the memory attribute. KMemoryState old_state; @@ -1669,7 +1675,7 @@ Result KPageTable::SetMemoryAttribute(VAddr addr, std::size_t size, u32 mask, u3 // Create an update allocator. Result allocator_result{ResultSuccess}; KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), - memory_block_slab_manager, num_allocator_blocks); + m_memory_block_slab_manager, num_allocator_blocks); R_TRY(allocator_result); // Determine the new attribute. @@ -1681,124 +1687,125 @@ Result KPageTable::SetMemoryAttribute(VAddr addr, std::size_t size, u32 mask, u3 this->Operate(addr, num_pages, old_perm, OperationType::ChangePermissionsAndRefresh); // Update the blocks. - memory_block_manager.Update(std::addressof(allocator), addr, num_pages, old_state, old_perm, - new_attr, KMemoryBlockDisableMergeAttribute::None, - KMemoryBlockDisableMergeAttribute::None); + m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, old_state, old_perm, + new_attr, KMemoryBlockDisableMergeAttribute::None, + KMemoryBlockDisableMergeAttribute::None); return ResultSuccess; } -Result KPageTable::SetMaxHeapSize(std::size_t size) { +Result KPageTable::SetMaxHeapSize(size_t size) { // Lock the table. - KScopedLightLock lk(general_lock); + KScopedLightLock lk(m_general_lock); // Only process page tables are allowed to set heap size. ASSERT(!this->IsKernel()); - max_heap_size = size; + m_max_heap_size = size; return ResultSuccess; } -Result KPageTable::SetHeapSize(VAddr* out, std::size_t size) { +Result KPageTable::SetHeapSize(VAddr* out, size_t size) { // Lock the physical memory mutex. - KScopedLightLock map_phys_mem_lk(map_physical_memory_lock); + KScopedLightLock map_phys_mem_lk(m_map_physical_memory_lock); // Try to perform a reduction in heap, instead of an extension. VAddr cur_address{}; - std::size_t allocation_size{}; + size_t allocation_size{}; { // Lock the table. - KScopedLightLock lk(general_lock); + KScopedLightLock lk(m_general_lock); // Validate that setting heap size is possible at all. - R_UNLESS(!is_kernel, ResultOutOfMemory); - R_UNLESS(size <= static_cast(heap_region_end - heap_region_start), + R_UNLESS(!m_is_kernel, ResultOutOfMemory); + R_UNLESS(size <= static_cast(m_heap_region_end - m_heap_region_start), ResultOutOfMemory); - R_UNLESS(size <= max_heap_size, ResultOutOfMemory); + R_UNLESS(size <= m_max_heap_size, ResultOutOfMemory); if (size < GetHeapSize()) { // The size being requested is less than the current size, so we need to free the end of // the heap. // Validate memory state. - std::size_t num_allocator_blocks; + size_t num_allocator_blocks; R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), - heap_region_start + size, GetHeapSize() - size, + m_heap_region_start + size, GetHeapSize() - size, KMemoryState::All, KMemoryState::Normal, KMemoryPermission::All, KMemoryPermission::UserReadWrite, KMemoryAttribute::All, KMemoryAttribute::None)); // Create an update allocator. Result allocator_result{ResultSuccess}; - KMemoryBlockManagerUpdateAllocator allocator( - std::addressof(allocator_result), memory_block_slab_manager, num_allocator_blocks); + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), + m_memory_block_slab_manager, + num_allocator_blocks); R_TRY(allocator_result); // Unmap the end of the heap. const auto num_pages = (GetHeapSize() - size) / PageSize; - R_TRY(Operate(heap_region_start + size, num_pages, KMemoryPermission::None, + R_TRY(Operate(m_heap_region_start + size, num_pages, KMemoryPermission::None, OperationType::Unmap)); // Release the memory from the resource limit. - system.Kernel().CurrentProcess()->GetResourceLimit()->Release( + m_system.Kernel().CurrentProcess()->GetResourceLimit()->Release( LimitableResource::PhysicalMemory, num_pages * PageSize); // Apply the memory block update. - memory_block_manager.Update(std::addressof(allocator), heap_region_start + size, - num_pages, KMemoryState::Free, KMemoryPermission::None, - KMemoryAttribute::None, - KMemoryBlockDisableMergeAttribute::None, - size == 0 ? KMemoryBlockDisableMergeAttribute::Normal - : KMemoryBlockDisableMergeAttribute::None); + m_memory_block_manager.Update(std::addressof(allocator), m_heap_region_start + size, + num_pages, KMemoryState::Free, KMemoryPermission::None, + KMemoryAttribute::None, + KMemoryBlockDisableMergeAttribute::None, + size == 0 ? KMemoryBlockDisableMergeAttribute::Normal + : KMemoryBlockDisableMergeAttribute::None); // Update the current heap end. - current_heap_end = heap_region_start + size; + m_current_heap_end = m_heap_region_start + size; // Set the output. - *out = heap_region_start; + *out = m_heap_region_start; return ResultSuccess; } else if (size == GetHeapSize()) { // The size requested is exactly the current size. - *out = heap_region_start; + *out = m_heap_region_start; return ResultSuccess; } else { // We have to allocate memory. Determine how much to allocate and where while the table // is locked. - cur_address = current_heap_end; + cur_address = m_current_heap_end; allocation_size = size - GetHeapSize(); } } // Reserve memory for the heap extension. KScopedResourceReservation memory_reservation( - system.Kernel().CurrentProcess()->GetResourceLimit(), LimitableResource::PhysicalMemory, + m_system.Kernel().CurrentProcess()->GetResourceLimit(), LimitableResource::PhysicalMemory, allocation_size); R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached); // Allocate pages for the heap extension. KPageGroup pg; - R_TRY(system.Kernel().MemoryManager().AllocateAndOpen( + R_TRY(m_system.Kernel().MemoryManager().AllocateAndOpen( &pg, allocation_size / PageSize, - KMemoryManager::EncodeOption(memory_pool, allocation_option))); + KMemoryManager::EncodeOption(m_memory_pool, m_allocation_option))); // Clear all the newly allocated pages. for (const auto& it : pg.Nodes()) { - std::memset(system.DeviceMemory().GetPointer(it.GetAddress()), heap_fill_value, + std::memset(m_system.DeviceMemory().GetPointer(it.GetAddress()), m_heap_fill_value, it.GetSize()); } // Map the pages. { // Lock the table. - KScopedLightLock lk(general_lock); + KScopedLightLock lk(m_general_lock); // Ensure that the heap hasn't changed since we began executing. - ASSERT(cur_address == current_heap_end); + ASSERT(cur_address == m_current_heap_end); // Check the memory state. - std::size_t num_allocator_blocks{}; - R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), current_heap_end, + size_t num_allocator_blocks{}; + R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), m_current_heap_end, allocation_size, KMemoryState::All, KMemoryState::Free, KMemoryPermission::None, KMemoryPermission::None, KMemoryAttribute::None, KMemoryAttribute::None)); @@ -1806,16 +1813,16 @@ Result KPageTable::SetHeapSize(VAddr* out, std::size_t size) { // Create an update allocator. Result allocator_result{ResultSuccess}; KMemoryBlockManagerUpdateAllocator allocator( - std::addressof(allocator_result), memory_block_slab_manager, num_allocator_blocks); + std::addressof(allocator_result), m_memory_block_slab_manager, num_allocator_blocks); R_TRY(allocator_result); // Map the pages. const auto num_pages = allocation_size / PageSize; - R_TRY(Operate(current_heap_end, num_pages, pg, OperationType::MapGroup)); + R_TRY(Operate(m_current_heap_end, num_pages, pg, OperationType::MapGroup)); // Clear all the newly allocated pages. - for (std::size_t cur_page = 0; cur_page < num_pages; ++cur_page) { - std::memset(system.Memory().GetPointer(current_heap_end + (cur_page * PageSize)), 0, + for (size_t cur_page = 0; cur_page < num_pages; ++cur_page) { + std::memset(m_system.Memory().GetPointer(m_current_heap_end + (cur_page * PageSize)), 0, PageSize); } @@ -1823,27 +1830,27 @@ Result KPageTable::SetHeapSize(VAddr* out, std::size_t size) { memory_reservation.Commit(); // Apply the memory block update. - memory_block_manager.Update( - std::addressof(allocator), current_heap_end, num_pages, KMemoryState::Normal, + m_memory_block_manager.Update( + std::addressof(allocator), m_current_heap_end, num_pages, KMemoryState::Normal, KMemoryPermission::UserReadWrite, KMemoryAttribute::None, - heap_region_start == current_heap_end ? KMemoryBlockDisableMergeAttribute::Normal - : KMemoryBlockDisableMergeAttribute::None, + m_heap_region_start == m_current_heap_end ? KMemoryBlockDisableMergeAttribute::Normal + : KMemoryBlockDisableMergeAttribute::None, KMemoryBlockDisableMergeAttribute::None); // Update the current heap end. - current_heap_end = heap_region_start + size; + m_current_heap_end = m_heap_region_start + size; // Set the output. - *out = heap_region_start; + *out = m_heap_region_start; return ResultSuccess; } } -ResultVal KPageTable::AllocateAndMapMemory(std::size_t needed_num_pages, std::size_t align, +ResultVal KPageTable::AllocateAndMapMemory(size_t needed_num_pages, size_t align, bool is_map_only, VAddr region_start, - std::size_t region_num_pages, KMemoryState state, + size_t region_num_pages, KMemoryState state, KMemoryPermission perm, PAddr map_addr) { - KScopedLightLock lk(general_lock); + KScopedLightLock lk(m_general_lock); if (!CanContain(region_start, region_num_pages * PageSize, state)) { return ResultInvalidCurrentMemory; @@ -1862,33 +1869,98 @@ ResultVal KPageTable::AllocateAndMapMemory(std::size_t needed_num_pages, // Create an update allocator. Result allocator_result{ResultSuccess}; KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), - memory_block_slab_manager); + m_memory_block_slab_manager); if (is_map_only) { R_TRY(Operate(addr, needed_num_pages, perm, OperationType::Map, map_addr)); } else { KPageGroup page_group; - R_TRY(system.Kernel().MemoryManager().AllocateAndOpenForProcess( + R_TRY(m_system.Kernel().MemoryManager().AllocateAndOpenForProcess( &page_group, needed_num_pages, - KMemoryManager::EncodeOption(memory_pool, allocation_option), 0, 0)); + KMemoryManager::EncodeOption(m_memory_pool, m_allocation_option), 0, 0)); R_TRY(Operate(addr, needed_num_pages, page_group, OperationType::MapGroup)); } // Update the blocks. - memory_block_manager.Update(std::addressof(allocator), addr, needed_num_pages, state, perm, - KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal, - KMemoryBlockDisableMergeAttribute::None); + m_memory_block_manager.Update(std::addressof(allocator), addr, needed_num_pages, state, perm, + KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal, + KMemoryBlockDisableMergeAttribute::None); return addr; } -Result KPageTable::UnlockForDeviceAddressSpace(VAddr address, std::size_t size) { +Result KPageTable::LockForMapDeviceAddressSpace(VAddr address, size_t size, KMemoryPermission perm, + bool is_aligned) { // Lightly validate the range before doing anything else. const size_t num_pages = size / PageSize; R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory); // Lock the table. - KScopedLightLock lk(general_lock); + KScopedLightLock lk(m_general_lock); + + // Check the memory state. + const auto test_state = + (is_aligned ? KMemoryState::FlagCanAlignedDeviceMap : KMemoryState::FlagCanDeviceMap); + size_t num_allocator_blocks; + R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), address, size, test_state, + test_state, perm, perm, + KMemoryAttribute::IpcLocked | KMemoryAttribute::Locked, + KMemoryAttribute::None, KMemoryAttribute::DeviceShared)); + + // Create an update allocator. + Result allocator_result{ResultSuccess}; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), + m_memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + + // Update the memory blocks. + m_memory_block_manager.UpdateLock(std::addressof(allocator), address, num_pages, + &KMemoryBlock::ShareToDevice, KMemoryPermission::None); + + return ResultSuccess; +} + +Result KPageTable::LockForUnmapDeviceAddressSpace(VAddr address, size_t size) { + // Lightly validate the range before doing anything else. + const size_t num_pages = size / PageSize; + R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory); + + // Lock the table. + KScopedLightLock lk(m_general_lock); + + // Check the memory state. + size_t num_allocator_blocks; + R_TRY(this->CheckMemoryStateContiguous( + std::addressof(num_allocator_blocks), address, size, + KMemoryState::FlagReferenceCounted | KMemoryState::FlagCanDeviceMap, + KMemoryState::FlagReferenceCounted | KMemoryState::FlagCanDeviceMap, + KMemoryPermission::None, KMemoryPermission::None, + KMemoryAttribute::DeviceShared | KMemoryAttribute::Locked, KMemoryAttribute::DeviceShared)); + + // Create an update allocator. + Result allocator_result{ResultSuccess}; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), + m_memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + + // Update the memory blocks. + const KMemoryBlockManager::MemoryBlockLockFunction lock_func = + m_enable_device_address_space_merge + ? &KMemoryBlock::UpdateDeviceDisableMergeStateForShare + : &KMemoryBlock::UpdateDeviceDisableMergeStateForShareRight; + m_memory_block_manager.UpdateLock(std::addressof(allocator), address, num_pages, lock_func, + KMemoryPermission::None); + + return ResultSuccess; +} + +Result KPageTable::UnlockForDeviceAddressSpace(VAddr address, size_t size) { + // Lightly validate the range before doing anything else. + const size_t num_pages = size / PageSize; + R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory); + + // Lock the table. + KScopedLightLock lk(m_general_lock); // Check the memory state. size_t num_allocator_blocks; @@ -1900,17 +1972,17 @@ Result KPageTable::UnlockForDeviceAddressSpace(VAddr address, std::size_t size) // Create an update allocator. Result allocator_result{ResultSuccess}; KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), - memory_block_slab_manager, num_allocator_blocks); + m_memory_block_slab_manager, num_allocator_blocks); R_TRY(allocator_result); // Update the memory blocks. - memory_block_manager.UpdateLock(std::addressof(allocator), address, num_pages, - &KMemoryBlock::UnshareToDevice, KMemoryPermission::None); + m_memory_block_manager.UpdateLock(std::addressof(allocator), address, num_pages, + &KMemoryBlock::UnshareToDevice, KMemoryPermission::None); return ResultSuccess; } -Result KPageTable::LockForCodeMemory(KPageGroup* out, VAddr addr, std::size_t size) { +Result KPageTable::LockForCodeMemory(KPageGroup* out, VAddr addr, size_t size) { return this->LockMemoryAndOpen( out, nullptr, addr, size, KMemoryState::FlagCanCodeMemory, KMemoryState::FlagCanCodeMemory, KMemoryPermission::All, KMemoryPermission::UserReadWrite, KMemoryAttribute::All, @@ -1920,7 +1992,7 @@ Result KPageTable::LockForCodeMemory(KPageGroup* out, VAddr addr, std::size_t si KMemoryAttribute::Locked); } -Result KPageTable::UnlockForCodeMemory(VAddr addr, std::size_t size, const KPageGroup& pg) { +Result KPageTable::UnlockForCodeMemory(VAddr addr, size_t size, const KPageGroup& pg) { return this->UnlockMemory( addr, size, KMemoryState::FlagCanCodeMemory, KMemoryState::FlagCanCodeMemory, KMemoryPermission::None, KMemoryPermission::None, KMemoryAttribute::All, @@ -1928,9 +2000,9 @@ Result KPageTable::UnlockForCodeMemory(VAddr addr, std::size_t size, const KPage } bool KPageTable::IsRegionContiguous(VAddr addr, u64 size) const { - auto start_ptr = system.DeviceMemory().GetPointer(addr); + auto start_ptr = m_system.DeviceMemory().GetPointer(addr); for (u64 offset{}; offset < size; offset += PageSize) { - if (start_ptr != system.DeviceMemory().GetPointer(addr + offset)) { + if (start_ptr != m_system.DeviceMemory().GetPointer(addr + offset)) { return false; } start_ptr += PageSize; @@ -1938,8 +2010,7 @@ bool KPageTable::IsRegionContiguous(VAddr addr, u64 size) const { return true; } -void KPageTable::AddRegionToPages(VAddr start, std::size_t num_pages, - KPageGroup& page_linked_list) { +void KPageTable::AddRegionToPages(VAddr start, size_t num_pages, KPageGroup& page_linked_list) { VAddr addr{start}; while (addr < start + (num_pages * PageSize)) { const PAddr paddr{GetPhysicalAddr(addr)}; @@ -1949,16 +2020,16 @@ void KPageTable::AddRegionToPages(VAddr start, std::size_t num_pages, } } -VAddr KPageTable::AllocateVirtualMemory(VAddr start, std::size_t region_num_pages, - u64 needed_num_pages, std::size_t align) { - if (is_aslr_enabled) { +VAddr KPageTable::AllocateVirtualMemory(VAddr start, size_t region_num_pages, u64 needed_num_pages, + size_t align) { + if (m_enable_aslr) { UNIMPLEMENTED(); } - return memory_block_manager.FindFreeArea(start, region_num_pages, needed_num_pages, align, 0, - IsKernel() ? 1 : 4); + return m_memory_block_manager.FindFreeArea(start, region_num_pages, needed_num_pages, align, 0, + IsKernel() ? 1 : 4); } -Result KPageTable::Operate(VAddr addr, std::size_t num_pages, const KPageGroup& page_group, +Result KPageTable::Operate(VAddr addr, size_t num_pages, const KPageGroup& page_group, OperationType operation) { ASSERT(this->IsLockedByCurrentThread()); @@ -1967,11 +2038,11 @@ Result KPageTable::Operate(VAddr addr, std::size_t num_pages, const KPageGroup& ASSERT(num_pages == page_group.GetNumPages()); for (const auto& node : page_group.Nodes()) { - const std::size_t size{node.GetNumPages() * PageSize}; + const size_t size{node.GetNumPages() * PageSize}; switch (operation) { case OperationType::MapGroup: - system.Memory().MapMemoryRegion(page_table_impl, addr, size, node.GetAddress()); + m_system.Memory().MapMemoryRegion(m_page_table_impl, addr, size, node.GetAddress()); break; default: ASSERT(false); @@ -1983,7 +2054,7 @@ Result KPageTable::Operate(VAddr addr, std::size_t num_pages, const KPageGroup& return ResultSuccess; } -Result KPageTable::Operate(VAddr addr, std::size_t num_pages, KMemoryPermission perm, +Result KPageTable::Operate(VAddr addr, size_t num_pages, KMemoryPermission perm, OperationType operation, PAddr map_addr) { ASSERT(this->IsLockedByCurrentThread()); @@ -1993,12 +2064,12 @@ Result KPageTable::Operate(VAddr addr, std::size_t num_pages, KMemoryPermission switch (operation) { case OperationType::Unmap: - system.Memory().UnmapRegion(page_table_impl, addr, num_pages * PageSize); + m_system.Memory().UnmapRegion(m_page_table_impl, addr, num_pages * PageSize); break; case OperationType::Map: { ASSERT(map_addr); ASSERT(Common::IsAligned(map_addr, PageSize)); - system.Memory().MapMemoryRegion(page_table_impl, addr, num_pages * PageSize, map_addr); + m_system.Memory().MapMemoryRegion(m_page_table_impl, addr, num_pages * PageSize, map_addr); break; } case OperationType::ChangePermissions: @@ -2014,18 +2085,18 @@ VAddr KPageTable::GetRegionAddress(KMemoryState state) const { switch (state) { case KMemoryState::Free: case KMemoryState::Kernel: - return address_space_start; + return m_address_space_start; case KMemoryState::Normal: - return heap_region_start; + return m_heap_region_start; case KMemoryState::Ipc: case KMemoryState::NonSecureIpc: case KMemoryState::NonDeviceIpc: - return alias_region_start; + return m_alias_region_start; case KMemoryState::Stack: - return stack_region_start; + return m_stack_region_start; case KMemoryState::Static: case KMemoryState::ThreadLocal: - return kernel_map_region_start; + return m_kernel_map_region_start; case KMemoryState::Io: case KMemoryState::Shared: case KMemoryState::AliasCode: @@ -2036,31 +2107,31 @@ VAddr KPageTable::GetRegionAddress(KMemoryState state) const { case KMemoryState::GeneratedCode: case KMemoryState::CodeOut: case KMemoryState::Coverage: - return alias_code_region_start; + return m_alias_code_region_start; case KMemoryState::Code: case KMemoryState::CodeData: - return code_region_start; + return m_code_region_start; default: UNREACHABLE(); } } -std::size_t KPageTable::GetRegionSize(KMemoryState state) const { +size_t KPageTable::GetRegionSize(KMemoryState state) const { switch (state) { case KMemoryState::Free: case KMemoryState::Kernel: - return address_space_end - address_space_start; + return m_address_space_end - m_address_space_start; case KMemoryState::Normal: - return heap_region_end - heap_region_start; + return m_heap_region_end - m_heap_region_start; case KMemoryState::Ipc: case KMemoryState::NonSecureIpc: case KMemoryState::NonDeviceIpc: - return alias_region_end - alias_region_start; + return m_alias_region_end - m_alias_region_start; case KMemoryState::Stack: - return stack_region_end - stack_region_start; + return m_stack_region_end - m_stack_region_start; case KMemoryState::Static: case KMemoryState::ThreadLocal: - return kernel_map_region_end - kernel_map_region_start; + return m_kernel_map_region_end - m_kernel_map_region_start; case KMemoryState::Io: case KMemoryState::Shared: case KMemoryState::AliasCode: @@ -2071,16 +2142,16 @@ std::size_t KPageTable::GetRegionSize(KMemoryState state) const { case KMemoryState::GeneratedCode: case KMemoryState::CodeOut: case KMemoryState::Coverage: - return alias_code_region_end - alias_code_region_start; + return m_alias_code_region_end - m_alias_code_region_start; case KMemoryState::Code: case KMemoryState::CodeData: - return code_region_end - code_region_start; + return m_code_region_end - m_code_region_start; default: UNREACHABLE(); } } -bool KPageTable::CanContain(VAddr addr, std::size_t size, KMemoryState state) const { +bool KPageTable::CanContain(VAddr addr, size_t size, KMemoryState state) const { const VAddr end = addr + size; const VAddr last = end - 1; @@ -2089,10 +2160,10 @@ bool KPageTable::CanContain(VAddr addr, std::size_t size, KMemoryState state) co const bool is_in_region = region_start <= addr && addr < end && last <= region_start + region_size - 1; - const bool is_in_heap = !(end <= heap_region_start || heap_region_end <= addr || - heap_region_start == heap_region_end); - const bool is_in_alias = !(end <= alias_region_start || alias_region_end <= addr || - alias_region_start == alias_region_end); + const bool is_in_heap = !(end <= m_heap_region_start || m_heap_region_end <= addr || + m_heap_region_start == m_heap_region_end); + const bool is_in_alias = !(end <= m_alias_region_start || m_alias_region_end <= addr || + m_alias_region_start == m_alias_region_end); switch (state) { case KMemoryState::Free: case KMemoryState::Kernel: @@ -2138,16 +2209,16 @@ Result KPageTable::CheckMemoryState(const KMemoryInfo& info, KMemoryState state_ return ResultSuccess; } -Result KPageTable::CheckMemoryStateContiguous(std::size_t* out_blocks_needed, VAddr addr, - std::size_t size, KMemoryState state_mask, - KMemoryState state, KMemoryPermission perm_mask, - KMemoryPermission perm, KMemoryAttribute attr_mask, +Result KPageTable::CheckMemoryStateContiguous(size_t* out_blocks_needed, VAddr addr, size_t size, + KMemoryState state_mask, KMemoryState state, + KMemoryPermission perm_mask, KMemoryPermission perm, + KMemoryAttribute attr_mask, KMemoryAttribute attr) const { ASSERT(this->IsLockedByCurrentThread()); // Get information about the first block. const VAddr last_addr = addr + size - 1; - KMemoryBlockManager::const_iterator it = memory_block_manager.FindIterator(addr); + KMemoryBlockManager::const_iterator it = m_memory_block_manager.FindIterator(addr); KMemoryInfo info = it->GetMemoryInfo(); // If the start address isn't aligned, we need a block. @@ -2165,7 +2236,7 @@ Result KPageTable::CheckMemoryStateContiguous(std::size_t* out_blocks_needed, VA // Advance our iterator. it++; - ASSERT(it != memory_block_manager.cend()); + ASSERT(it != m_memory_block_manager.cend()); info = it->GetMemoryInfo(); } @@ -2181,8 +2252,8 @@ Result KPageTable::CheckMemoryStateContiguous(std::size_t* out_blocks_needed, VA } Result KPageTable::CheckMemoryState(KMemoryState* out_state, KMemoryPermission* out_perm, - KMemoryAttribute* out_attr, std::size_t* out_blocks_needed, - VAddr addr, std::size_t size, KMemoryState state_mask, + KMemoryAttribute* out_attr, size_t* out_blocks_needed, + VAddr addr, size_t size, KMemoryState state_mask, KMemoryState state, KMemoryPermission perm_mask, KMemoryPermission perm, KMemoryAttribute attr_mask, KMemoryAttribute attr, KMemoryAttribute ignore_attr) const { @@ -2190,7 +2261,7 @@ Result KPageTable::CheckMemoryState(KMemoryState* out_state, KMemoryPermission* // Get information about the first block. const VAddr last_addr = addr + size - 1; - KMemoryBlockManager::const_iterator it = memory_block_manager.FindIterator(addr); + KMemoryBlockManager::const_iterator it = m_memory_block_manager.FindIterator(addr); KMemoryInfo info = it->GetMemoryInfo(); // If the start address isn't aligned, we need a block. @@ -2218,7 +2289,7 @@ Result KPageTable::CheckMemoryState(KMemoryState* out_state, KMemoryPermission* // Advance our iterator. it++; - ASSERT(it != memory_block_manager.cend()); + ASSERT(it != m_memory_block_manager.cend()); info = it->GetMemoryInfo(); } @@ -2257,7 +2328,7 @@ Result KPageTable::LockMemoryAndOpen(KPageGroup* out_pg, PAddr* out_paddr, VAddr R_UNLESS(this->Contains(addr, size), ResultInvalidCurrentMemory); // Lock the table. - KScopedLightLock lk(general_lock); + KScopedLightLock lk(m_general_lock); // Check that the output page group is empty, if it exists. if (out_pg) { @@ -2288,7 +2359,7 @@ Result KPageTable::LockMemoryAndOpen(KPageGroup* out_pg, PAddr* out_paddr, VAddr // Create an update allocator. Result allocator_result{ResultSuccess}; KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), - memory_block_slab_manager, num_allocator_blocks); + m_memory_block_slab_manager, num_allocator_blocks); R_TRY(allocator_result); // Decide on new perm and attr. @@ -2301,9 +2372,9 @@ Result KPageTable::LockMemoryAndOpen(KPageGroup* out_pg, PAddr* out_paddr, VAddr } // Apply the memory block updates. - memory_block_manager.Update(std::addressof(allocator), addr, num_pages, old_state, new_perm, - new_attr, KMemoryBlockDisableMergeAttribute::Locked, - KMemoryBlockDisableMergeAttribute::None); + m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, old_state, new_perm, + new_attr, KMemoryBlockDisableMergeAttribute::Locked, + KMemoryBlockDisableMergeAttribute::None); return ResultSuccess; } @@ -2322,7 +2393,7 @@ Result KPageTable::UnlockMemory(VAddr addr, size_t size, KMemoryState state_mask R_UNLESS(this->Contains(addr, size), ResultInvalidCurrentMemory); // Lock the table. - KScopedLightLock lk(general_lock); + KScopedLightLock lk(m_general_lock); // Check the state. KMemoryState old_state{}; @@ -2347,7 +2418,7 @@ Result KPageTable::UnlockMemory(VAddr addr, size_t size, KMemoryState state_mask // Create an update allocator. Result allocator_result{ResultSuccess}; KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), - memory_block_slab_manager, num_allocator_blocks); + m_memory_block_slab_manager, num_allocator_blocks); R_TRY(allocator_result); // Update permission, if we need to. @@ -2356,9 +2427,9 @@ Result KPageTable::UnlockMemory(VAddr addr, size_t size, KMemoryState state_mask } // Apply the memory block updates. - memory_block_manager.Update(std::addressof(allocator), addr, num_pages, old_state, new_perm, - new_attr, KMemoryBlockDisableMergeAttribute::None, - KMemoryBlockDisableMergeAttribute::Locked); + m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, old_state, new_perm, + new_attr, KMemoryBlockDisableMergeAttribute::None, + KMemoryBlockDisableMergeAttribute::Locked); return ResultSuccess; } diff --git a/src/core/hle/kernel/k_page_table.h b/src/core/hle/kernel/k_page_table.h index fa11a0fe36..2258543197 100644 --- a/src/core/hle/kernel/k_page_table.h +++ b/src/core/hle/kernel/k_page_table.h @@ -36,60 +36,66 @@ public: ~KPageTable(); Result InitializeForProcess(FileSys::ProgramAddressSpaceType as_type, bool enable_aslr, - VAddr code_addr, std::size_t code_size, + VAddr code_addr, size_t code_size, KMemoryBlockSlabManager* mem_block_slab_manager, KMemoryManager::Pool pool); void Finalize(); - Result MapProcessCode(VAddr addr, std::size_t pages_count, KMemoryState state, + Result MapProcessCode(VAddr addr, size_t pages_count, KMemoryState state, KMemoryPermission perm); - Result MapCodeMemory(VAddr dst_address, VAddr src_address, std::size_t size); - Result UnmapCodeMemory(VAddr dst_address, VAddr src_address, std::size_t size, + Result MapCodeMemory(VAddr dst_address, VAddr src_address, size_t size); + Result UnmapCodeMemory(VAddr dst_address, VAddr src_address, size_t size, ICacheInvalidationStrategy icache_invalidation_strategy); - Result UnmapProcessMemory(VAddr dst_addr, std::size_t size, KPageTable& src_page_table, + Result UnmapProcessMemory(VAddr dst_addr, size_t size, KPageTable& src_page_table, VAddr src_addr); - Result MapPhysicalMemory(VAddr addr, std::size_t size); - Result UnmapPhysicalMemory(VAddr addr, std::size_t size); - Result MapMemory(VAddr dst_addr, VAddr src_addr, std::size_t size); - Result UnmapMemory(VAddr dst_addr, VAddr src_addr, std::size_t size); + Result MapPhysicalMemory(VAddr addr, size_t size); + Result UnmapPhysicalMemory(VAddr addr, size_t size); + Result MapMemory(VAddr dst_addr, VAddr src_addr, size_t size); + Result UnmapMemory(VAddr dst_addr, VAddr src_addr, size_t size); Result MapPages(VAddr addr, KPageGroup& page_linked_list, KMemoryState state, KMemoryPermission perm); - Result MapPages(VAddr* out_addr, std::size_t num_pages, std::size_t alignment, PAddr phys_addr, + Result MapPages(VAddr* out_addr, size_t num_pages, size_t alignment, PAddr phys_addr, KMemoryState state, KMemoryPermission perm) { return this->MapPages(out_addr, num_pages, alignment, phys_addr, true, this->GetRegionAddress(state), this->GetRegionSize(state) / PageSize, state, perm); } Result UnmapPages(VAddr addr, KPageGroup& page_linked_list, KMemoryState state); - Result UnmapPages(VAddr address, std::size_t num_pages, KMemoryState state); - Result SetProcessMemoryPermission(VAddr addr, std::size_t size, Svc::MemoryPermission svc_perm); + Result UnmapPages(VAddr address, size_t num_pages, KMemoryState state); + Result SetProcessMemoryPermission(VAddr addr, size_t size, Svc::MemoryPermission svc_perm); KMemoryInfo QueryInfo(VAddr addr); - Result SetMemoryPermission(VAddr addr, std::size_t size, Svc::MemoryPermission perm); - Result SetMemoryAttribute(VAddr addr, std::size_t size, u32 mask, u32 attr); - Result SetMaxHeapSize(std::size_t size); - Result SetHeapSize(VAddr* out, std::size_t size); - ResultVal AllocateAndMapMemory(std::size_t needed_num_pages, std::size_t align, - bool is_map_only, VAddr region_start, - std::size_t region_num_pages, KMemoryState state, - KMemoryPermission perm, PAddr map_addr = 0); - Result UnlockForDeviceAddressSpace(VAddr addr, std::size_t size); - Result LockForCodeMemory(KPageGroup* out, VAddr addr, std::size_t size); - Result UnlockForCodeMemory(VAddr addr, std::size_t size, const KPageGroup& pg); + Result SetMemoryPermission(VAddr addr, size_t size, Svc::MemoryPermission perm); + Result SetMemoryAttribute(VAddr addr, size_t size, u32 mask, u32 attr); + Result SetMaxHeapSize(size_t size); + Result SetHeapSize(VAddr* out, size_t size); + ResultVal AllocateAndMapMemory(size_t needed_num_pages, size_t align, bool is_map_only, + VAddr region_start, size_t region_num_pages, + KMemoryState state, KMemoryPermission perm, + PAddr map_addr = 0); + + Result LockForMapDeviceAddressSpace(VAddr address, size_t size, KMemoryPermission perm, + bool is_aligned); + Result LockForUnmapDeviceAddressSpace(VAddr address, size_t size); + + Result UnlockForDeviceAddressSpace(VAddr addr, size_t size); + + Result LockForCodeMemory(KPageGroup* out, VAddr addr, size_t size); + Result UnlockForCodeMemory(VAddr addr, size_t size, const KPageGroup& pg); Result MakeAndOpenPageGroup(KPageGroup* out, VAddr address, size_t num_pages, KMemoryState state_mask, KMemoryState state, KMemoryPermission perm_mask, KMemoryPermission perm, KMemoryAttribute attr_mask, KMemoryAttribute attr); Common::PageTable& PageTableImpl() { - return page_table_impl; + return m_page_table_impl; } const Common::PageTable& PageTableImpl() const { - return page_table_impl; + return m_page_table_impl; } - bool CanContain(VAddr addr, std::size_t size, KMemoryState state) const; + bool CanContain(VAddr addr, size_t size, KMemoryState state) const; private: enum class OperationType : u32 { @@ -104,30 +110,30 @@ private: KMemoryAttribute::IpcLocked | KMemoryAttribute::DeviceShared; Result MapPages(VAddr addr, const KPageGroup& page_linked_list, KMemoryPermission perm); - Result MapPages(VAddr* out_addr, std::size_t num_pages, std::size_t alignment, PAddr phys_addr, - bool is_pa_valid, VAddr region_start, std::size_t region_num_pages, + Result MapPages(VAddr* out_addr, size_t num_pages, size_t alignment, PAddr phys_addr, + bool is_pa_valid, VAddr region_start, size_t region_num_pages, KMemoryState state, KMemoryPermission perm); Result UnmapPages(VAddr addr, const KPageGroup& page_linked_list); bool IsRegionContiguous(VAddr addr, u64 size) const; - void AddRegionToPages(VAddr start, std::size_t num_pages, KPageGroup& page_linked_list); + void AddRegionToPages(VAddr start, size_t num_pages, KPageGroup& page_linked_list); KMemoryInfo QueryInfoImpl(VAddr addr); - VAddr AllocateVirtualMemory(VAddr start, std::size_t region_num_pages, u64 needed_num_pages, - std::size_t align); - Result Operate(VAddr addr, std::size_t num_pages, const KPageGroup& page_group, + VAddr AllocateVirtualMemory(VAddr start, size_t region_num_pages, u64 needed_num_pages, + size_t align); + Result Operate(VAddr addr, size_t num_pages, const KPageGroup& page_group, OperationType operation); - Result Operate(VAddr addr, std::size_t num_pages, KMemoryPermission perm, - OperationType operation, PAddr map_addr = 0); + Result Operate(VAddr addr, size_t num_pages, KMemoryPermission perm, OperationType operation, + PAddr map_addr = 0); VAddr GetRegionAddress(KMemoryState state) const; - std::size_t GetRegionSize(KMemoryState state) const; + size_t GetRegionSize(KMemoryState state) const; - VAddr FindFreeArea(VAddr region_start, std::size_t region_num_pages, std::size_t num_pages, - std::size_t alignment, std::size_t offset, std::size_t guard_pages); + VAddr FindFreeArea(VAddr region_start, size_t region_num_pages, size_t num_pages, + size_t alignment, size_t offset, size_t guard_pages); - Result CheckMemoryStateContiguous(std::size_t* out_blocks_needed, VAddr addr, std::size_t size, + Result CheckMemoryStateContiguous(size_t* out_blocks_needed, VAddr addr, size_t size, KMemoryState state_mask, KMemoryState state, KMemoryPermission perm_mask, KMemoryPermission perm, KMemoryAttribute attr_mask, KMemoryAttribute attr) const; - Result CheckMemoryStateContiguous(VAddr addr, std::size_t size, KMemoryState state_mask, + Result CheckMemoryStateContiguous(VAddr addr, size_t size, KMemoryState state_mask, KMemoryState state, KMemoryPermission perm_mask, KMemoryPermission perm, KMemoryAttribute attr_mask, KMemoryAttribute attr) const { @@ -139,12 +145,12 @@ private: KMemoryPermission perm_mask, KMemoryPermission perm, KMemoryAttribute attr_mask, KMemoryAttribute attr) const; Result CheckMemoryState(KMemoryState* out_state, KMemoryPermission* out_perm, - KMemoryAttribute* out_attr, std::size_t* out_blocks_needed, VAddr addr, - std::size_t size, KMemoryState state_mask, KMemoryState state, + KMemoryAttribute* out_attr, size_t* out_blocks_needed, VAddr addr, + size_t size, KMemoryState state_mask, KMemoryState state, KMemoryPermission perm_mask, KMemoryPermission perm, KMemoryAttribute attr_mask, KMemoryAttribute attr, KMemoryAttribute ignore_attr = DefaultMemoryIgnoreAttr) const; - Result CheckMemoryState(std::size_t* out_blocks_needed, VAddr addr, std::size_t size, + Result CheckMemoryState(size_t* out_blocks_needed, VAddr addr, size_t size, KMemoryState state_mask, KMemoryState state, KMemoryPermission perm_mask, KMemoryPermission perm, KMemoryAttribute attr_mask, KMemoryAttribute attr, @@ -152,8 +158,8 @@ private: return CheckMemoryState(nullptr, nullptr, nullptr, out_blocks_needed, addr, size, state_mask, state, perm_mask, perm, attr_mask, attr, ignore_attr); } - Result CheckMemoryState(VAddr addr, std::size_t size, KMemoryState state_mask, - KMemoryState state, KMemoryPermission perm_mask, KMemoryPermission perm, + Result CheckMemoryState(VAddr addr, size_t size, KMemoryState state_mask, KMemoryState state, + KMemoryPermission perm_mask, KMemoryPermission perm, KMemoryAttribute attr_mask, KMemoryAttribute attr, KMemoryAttribute ignore_attr = DefaultMemoryIgnoreAttr) const { return this->CheckMemoryState(nullptr, addr, size, state_mask, state, perm_mask, perm, @@ -175,13 +181,13 @@ private: bool IsValidPageGroup(const KPageGroup& pg, VAddr addr, size_t num_pages); bool IsLockedByCurrentThread() const { - return general_lock.IsLockedByCurrentThread(); + return m_general_lock.IsLockedByCurrentThread(); } bool IsHeapPhysicalAddress(const KMemoryLayout& layout, PAddr phys_addr) { ASSERT(this->IsLockedByCurrentThread()); - return layout.IsHeapPhysicalAddress(cached_physical_heap_region, phys_addr); + return layout.IsHeapPhysicalAddress(m_cached_physical_heap_region, phys_addr); } bool GetPhysicalAddressLocked(PAddr* out, VAddr virt_addr) const { @@ -192,93 +198,93 @@ private: return *out != 0; } - mutable KLightLock general_lock; - mutable KLightLock map_physical_memory_lock; + mutable KLightLock m_general_lock; + mutable KLightLock m_map_physical_memory_lock; public: constexpr VAddr GetAddressSpaceStart() const { - return address_space_start; + return m_address_space_start; } constexpr VAddr GetAddressSpaceEnd() const { - return address_space_end; + return m_address_space_end; } - constexpr std::size_t GetAddressSpaceSize() const { - return address_space_end - address_space_start; + constexpr size_t GetAddressSpaceSize() const { + return m_address_space_end - m_address_space_start; } constexpr VAddr GetHeapRegionStart() const { - return heap_region_start; + return m_heap_region_start; } constexpr VAddr GetHeapRegionEnd() const { - return heap_region_end; + return m_heap_region_end; } - constexpr std::size_t GetHeapRegionSize() const { - return heap_region_end - heap_region_start; + constexpr size_t GetHeapRegionSize() const { + return m_heap_region_end - m_heap_region_start; } constexpr VAddr GetAliasRegionStart() const { - return alias_region_start; + return m_alias_region_start; } constexpr VAddr GetAliasRegionEnd() const { - return alias_region_end; + return m_alias_region_end; } - constexpr std::size_t GetAliasRegionSize() const { - return alias_region_end - alias_region_start; + constexpr size_t GetAliasRegionSize() const { + return m_alias_region_end - m_alias_region_start; } constexpr VAddr GetStackRegionStart() const { - return stack_region_start; + return m_stack_region_start; } constexpr VAddr GetStackRegionEnd() const { - return stack_region_end; + return m_stack_region_end; } - constexpr std::size_t GetStackRegionSize() const { - return stack_region_end - stack_region_start; + constexpr size_t GetStackRegionSize() const { + return m_stack_region_end - m_stack_region_start; } constexpr VAddr GetKernelMapRegionStart() const { - return kernel_map_region_start; + return m_kernel_map_region_start; } constexpr VAddr GetKernelMapRegionEnd() const { - return kernel_map_region_end; + return m_kernel_map_region_end; } constexpr VAddr GetCodeRegionStart() const { - return code_region_start; + return m_code_region_start; } constexpr VAddr GetCodeRegionEnd() const { - return code_region_end; + return m_code_region_end; } constexpr VAddr GetAliasCodeRegionStart() const { - return alias_code_region_start; + return m_alias_code_region_start; } constexpr VAddr GetAliasCodeRegionSize() const { - return alias_code_region_end - alias_code_region_start; + return m_alias_code_region_end - m_alias_code_region_start; } - std::size_t GetNormalMemorySize() { - KScopedLightLock lk(general_lock); - return GetHeapSize() + mapped_physical_memory_size; + size_t GetNormalMemorySize() { + KScopedLightLock lk(m_general_lock); + return GetHeapSize() + m_mapped_physical_memory_size; } - constexpr std::size_t GetAddressSpaceWidth() const { - return address_space_width; + constexpr size_t GetAddressSpaceWidth() const { + return m_address_space_width; } - constexpr std::size_t GetHeapSize() const { - return current_heap_end - heap_region_start; + constexpr size_t GetHeapSize() const { + return m_current_heap_end - m_heap_region_start; } - constexpr bool IsInsideAddressSpace(VAddr address, std::size_t size) const { - return address_space_start <= address && address + size - 1 <= address_space_end - 1; + constexpr bool IsInsideAddressSpace(VAddr address, size_t size) const { + return m_address_space_start <= address && address + size - 1 <= m_address_space_end - 1; } - constexpr bool IsOutsideAliasRegion(VAddr address, std::size_t size) const { - return alias_region_start > address || address + size - 1 > alias_region_end - 1; + constexpr bool IsOutsideAliasRegion(VAddr address, size_t size) const { + return m_alias_region_start > address || address + size - 1 > m_alias_region_end - 1; } - constexpr bool IsOutsideStackRegion(VAddr address, std::size_t size) const { - return stack_region_start > address || address + size - 1 > stack_region_end - 1; + constexpr bool IsOutsideStackRegion(VAddr address, size_t size) const { + return m_stack_region_start > address || address + size - 1 > m_stack_region_end - 1; } - constexpr bool IsInvalidRegion(VAddr address, std::size_t size) const { + constexpr bool IsInvalidRegion(VAddr address, size_t size) const { return address + size - 1 > GetAliasCodeRegionStart() + GetAliasCodeRegionSize() - 1; } - constexpr bool IsInsideHeapRegion(VAddr address, std::size_t size) const { - return address + size > heap_region_start && heap_region_end > address; + constexpr bool IsInsideHeapRegion(VAddr address, size_t size) const { + return address + size > m_heap_region_start && m_heap_region_end > address; } - constexpr bool IsInsideAliasRegion(VAddr address, std::size_t size) const { - return address + size > alias_region_start && alias_region_end > address; + constexpr bool IsInsideAliasRegion(VAddr address, size_t size) const { + return address + size > m_alias_region_start && m_alias_region_end > address; } - constexpr bool IsOutsideASLRRegion(VAddr address, std::size_t size) const { + constexpr bool IsOutsideASLRRegion(VAddr address, size_t size) const { if (IsInvalidRegion(address, size)) { return true; } @@ -290,77 +296,78 @@ public: } return {}; } - constexpr bool IsInsideASLRRegion(VAddr address, std::size_t size) const { + constexpr bool IsInsideASLRRegion(VAddr address, size_t size) const { return !IsOutsideASLRRegion(address, size); } - constexpr std::size_t GetNumGuardPages() const { + constexpr size_t GetNumGuardPages() const { return IsKernel() ? 1 : 4; } PAddr GetPhysicalAddr(VAddr addr) const { - const auto backing_addr = page_table_impl.backing_addr[addr >> PageBits]; + const auto backing_addr = m_page_table_impl.backing_addr[addr >> PageBits]; ASSERT(backing_addr); return backing_addr + addr; } constexpr bool Contains(VAddr addr) const { - return address_space_start <= addr && addr <= address_space_end - 1; + return m_address_space_start <= addr && addr <= m_address_space_end - 1; } - constexpr bool Contains(VAddr addr, std::size_t size) const { - return address_space_start <= addr && addr < addr + size && - addr + size - 1 <= address_space_end - 1; + constexpr bool Contains(VAddr addr, size_t size) const { + return m_address_space_start <= addr && addr < addr + size && + addr + size - 1 <= m_address_space_end - 1; } private: constexpr bool IsKernel() const { - return is_kernel; + return m_is_kernel; } constexpr bool IsAslrEnabled() const { - return is_aslr_enabled; + return m_enable_aslr; } - constexpr bool ContainsPages(VAddr addr, std::size_t num_pages) const { - return (address_space_start <= addr) && - (num_pages <= (address_space_end - address_space_start) / PageSize) && - (addr + num_pages * PageSize - 1 <= address_space_end - 1); + constexpr bool ContainsPages(VAddr addr, size_t num_pages) const { + return (m_address_space_start <= addr) && + (num_pages <= (m_address_space_end - m_address_space_start) / PageSize) && + (addr + num_pages * PageSize - 1 <= m_address_space_end - 1); } private: - VAddr address_space_start{}; - VAddr address_space_end{}; - VAddr heap_region_start{}; - VAddr heap_region_end{}; - VAddr current_heap_end{}; - VAddr alias_region_start{}; - VAddr alias_region_end{}; - VAddr stack_region_start{}; - VAddr stack_region_end{}; - VAddr kernel_map_region_start{}; - VAddr kernel_map_region_end{}; - VAddr code_region_start{}; - VAddr code_region_end{}; - VAddr alias_code_region_start{}; - VAddr alias_code_region_end{}; + VAddr m_address_space_start{}; + VAddr m_address_space_end{}; + VAddr m_heap_region_start{}; + VAddr m_heap_region_end{}; + VAddr m_current_heap_end{}; + VAddr m_alias_region_start{}; + VAddr m_alias_region_end{}; + VAddr m_stack_region_start{}; + VAddr m_stack_region_end{}; + VAddr m_kernel_map_region_start{}; + VAddr m_kernel_map_region_end{}; + VAddr m_code_region_start{}; + VAddr m_code_region_end{}; + VAddr m_alias_code_region_start{}; + VAddr m_alias_code_region_end{}; - std::size_t mapped_physical_memory_size{}; - std::size_t max_heap_size{}; - std::size_t max_physical_memory_size{}; - std::size_t address_space_width{}; + size_t m_mapped_physical_memory_size{}; + size_t m_max_heap_size{}; + size_t m_max_physical_memory_size{}; + size_t m_address_space_width{}; - KMemoryBlockManager memory_block_manager; + KMemoryBlockManager m_memory_block_manager; - bool is_kernel{}; - bool is_aslr_enabled{}; + bool m_is_kernel{}; + bool m_enable_aslr{}; + bool m_enable_device_address_space_merge{}; - KMemoryBlockSlabManager* memory_block_slab_manager{}; + KMemoryBlockSlabManager* m_memory_block_slab_manager{}; - u32 heap_fill_value{}; - const KMemoryRegion* cached_physical_heap_region{}; + u32 m_heap_fill_value{}; + const KMemoryRegion* m_cached_physical_heap_region{}; - KMemoryManager::Pool memory_pool{KMemoryManager::Pool::Application}; - KMemoryManager::Direction allocation_option{KMemoryManager::Direction::FromFront}; + KMemoryManager::Pool m_memory_pool{KMemoryManager::Pool::Application}; + KMemoryManager::Direction m_allocation_option{KMemoryManager::Direction::FromFront}; - Common::PageTable page_table_impl; + Common::PageTable m_page_table_impl; - Core::System& system; + Core::System& m_system; }; } // namespace Kernel diff --git a/src/core/hle/service/nvdrv/devices/nvmap.cpp b/src/core/hle/service/nvdrv/devices/nvmap.cpp index ddf273b5ec..b606790217 100644 --- a/src/core/hle/service/nvdrv/devices/nvmap.cpp +++ b/src/core/hle/service/nvdrv/devices/nvmap.cpp @@ -128,7 +128,8 @@ NvResult nvmap::IocAlloc(const std::vector& input, std::vector& output) } ASSERT(system.CurrentProcess() ->PageTable() - .LockForDeviceAddressSpace(handle_description->address, handle_description->size) + .LockForMapDeviceAddressSpace(handle_description->address, handle_description->size, + Kernel::KMemoryPermission::None, true) .IsSuccess()); std::memcpy(output.data(), ¶ms, sizeof(params)); return result; From 8d4e026d0575fe705957d6f17dc90a57f1bc0bf7 Mon Sep 17 00:00:00 2001 From: bunnei Date: Sun, 11 Sep 2022 00:06:41 -0700 Subject: [PATCH 33/91] core: hle: kernel: Remove junk. --- src/core/hle/kernel/kernel.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index d572394727..b6bbd4984b 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -108,10 +108,6 @@ struct KernelCore::Impl { next_user_process_id = KProcess::ProcessIDMin; next_thread_id = 1; - for (auto& core : cores) { - core = nullptr; - } - global_handle_table->Finalize(); global_handle_table.reset(); @@ -365,11 +361,6 @@ struct KernelCore::Impl { static inline thread_local KThread* current_thread{nullptr}; KThread* GetCurrentEmuThread() { - // If we are shutting down the kernel, none of this is relevant anymore. - if (IsShuttingDown()) { - return {}; - } - const auto thread_id = GetCurrentHostThreadID(); if (thread_id >= Core::Hardware::NUM_CPU_CORES) { return GetHostDummyThread(); From 79bcb38321cbde163280b04ee5a03773d54edfd9 Mon Sep 17 00:00:00 2001 From: bunnei Date: Sun, 2 Oct 2022 02:06:13 -0700 Subject: [PATCH 34/91] core: hle: kernel: k_interrupt_manager: HandleInterrupt should not depend on current process. --- src/core/hle/kernel/k_interrupt_manager.cpp | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/core/hle/kernel/k_interrupt_manager.cpp b/src/core/hle/kernel/k_interrupt_manager.cpp index ad73f3eab5..4a6b60d268 100644 --- a/src/core/hle/kernel/k_interrupt_manager.cpp +++ b/src/core/hle/kernel/k_interrupt_manager.cpp @@ -11,25 +11,22 @@ namespace Kernel::KInterruptManager { void HandleInterrupt(KernelCore& kernel, s32 core_id) { - auto* process = kernel.CurrentProcess(); - if (!process) { - return; - } - // Acknowledge the interrupt. kernel.PhysicalCore(core_id).ClearInterrupt(); auto& current_thread = GetCurrentThread(kernel); - // If the user disable count is set, we may need to pin the current thread. - if (current_thread.GetUserDisableCount() && !process->GetPinnedThread(core_id)) { - KScopedSchedulerLock sl{kernel}; + if (auto* process = kernel.CurrentProcess(); process) { + // If the user disable count is set, we may need to pin the current thread. + if (current_thread.GetUserDisableCount() && !process->GetPinnedThread(core_id)) { + KScopedSchedulerLock sl{kernel}; - // Pin the current thread. - process->PinCurrentThread(core_id); + // Pin the current thread. + process->PinCurrentThread(core_id); - // Set the interrupt flag for the thread. - GetCurrentThread(kernel).SetInterruptFlag(); + // Set the interrupt flag for the thread. + GetCurrentThread(kernel).SetInterruptFlag(); + } } // Request interrupt scheduling. From abcc009dff5d98b5a04229f3a82baab23d568244 Mon Sep 17 00:00:00 2001 From: bunnei Date: Sun, 2 Oct 2022 14:26:30 -0700 Subject: [PATCH 35/91] core: hle: kernel: k_process: Improve management of page table & cleanup. --- src/core/hle/kernel/k_page_table.cpp | 23 +++++++---- src/core/hle/kernel/k_page_table.h | 8 ++-- src/core/hle/kernel/k_process.cpp | 62 +++++++++++++++++----------- src/core/hle/kernel/k_process.h | 31 ++++++++------ src/core/hle/kernel/kernel.cpp | 23 +++++++---- src/core/hle/kernel/kernel.h | 3 ++ src/core/hle/kernel/svc.cpp | 2 +- 7 files changed, 92 insertions(+), 60 deletions(-) diff --git a/src/core/hle/kernel/k_page_table.cpp b/src/core/hle/kernel/k_page_table.cpp index fcffc0b88d..22098c056e 100644 --- a/src/core/hle/kernel/k_page_table.cpp +++ b/src/core/hle/kernel/k_page_table.cpp @@ -256,16 +256,21 @@ Result KPageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_type m_mapped_physical_memory_size = 0; m_memory_pool = pool; - m_page_table_impl.Resize(m_address_space_width, PageBits); + m_page_table_impl = std::make_unique(); + m_page_table_impl->Resize(m_address_space_width, PageBits); return m_memory_block_manager.Initialize(m_address_space_start, m_address_space_end, m_memory_block_slab_manager); } void KPageTable::Finalize() { + // Finalize memory blocks. m_memory_block_manager.Finalize(m_memory_block_slab_manager, [&](VAddr addr, u64 size) { - m_system.Memory().UnmapRegion(m_page_table_impl, addr, size); + m_system.Memory().UnmapRegion(*m_page_table_impl, addr, size); }); + + // Close the backing page table, as the destructor is not called for guest objects. + m_page_table_impl.reset(); } Result KPageTable::MapProcessCode(VAddr addr, size_t num_pages, KMemoryState state, @@ -514,7 +519,7 @@ Result KPageTable::MakePageGroup(KPageGroup& pg, VAddr addr, size_t num_pages) { // Begin traversal. Common::PageTable::TraversalContext context; Common::PageTable::TraversalEntry next_entry; - R_UNLESS(m_page_table_impl.BeginTraversal(next_entry, context, addr), + R_UNLESS(m_page_table_impl->BeginTraversal(next_entry, context, addr), ResultInvalidCurrentMemory); // Prepare tracking variables. @@ -525,7 +530,7 @@ Result KPageTable::MakePageGroup(KPageGroup& pg, VAddr addr, size_t num_pages) { // Iterate, adding to group as we go. const auto& memory_layout = m_system.Kernel().MemoryLayout(); while (tot_size < size) { - R_UNLESS(m_page_table_impl.ContinueTraversal(next_entry, context), + R_UNLESS(m_page_table_impl->ContinueTraversal(next_entry, context), ResultInvalidCurrentMemory); if (next_entry.phys_addr != (cur_addr + cur_size)) { @@ -588,7 +593,7 @@ bool KPageTable::IsValidPageGroup(const KPageGroup& pg_ll, VAddr addr, size_t nu // Begin traversal. Common::PageTable::TraversalContext context; Common::PageTable::TraversalEntry next_entry; - if (!m_page_table_impl.BeginTraversal(next_entry, context, addr)) { + if (!m_page_table_impl->BeginTraversal(next_entry, context, addr)) { return false; } @@ -599,7 +604,7 @@ bool KPageTable::IsValidPageGroup(const KPageGroup& pg_ll, VAddr addr, size_t nu // Iterate, comparing expected to actual. while (tot_size < size) { - if (!m_page_table_impl.ContinueTraversal(next_entry, context)) { + if (!m_page_table_impl->ContinueTraversal(next_entry, context)) { return false; } @@ -2042,7 +2047,7 @@ Result KPageTable::Operate(VAddr addr, size_t num_pages, const KPageGroup& page_ switch (operation) { case OperationType::MapGroup: - m_system.Memory().MapMemoryRegion(m_page_table_impl, addr, size, node.GetAddress()); + m_system.Memory().MapMemoryRegion(*m_page_table_impl, addr, size, node.GetAddress()); break; default: ASSERT(false); @@ -2064,12 +2069,12 @@ Result KPageTable::Operate(VAddr addr, size_t num_pages, KMemoryPermission perm, switch (operation) { case OperationType::Unmap: - m_system.Memory().UnmapRegion(m_page_table_impl, addr, num_pages * PageSize); + m_system.Memory().UnmapRegion(*m_page_table_impl, addr, num_pages * PageSize); break; case OperationType::Map: { ASSERT(map_addr); ASSERT(Common::IsAligned(map_addr, PageSize)); - m_system.Memory().MapMemoryRegion(m_page_table_impl, addr, num_pages * PageSize, map_addr); + m_system.Memory().MapMemoryRegion(*m_page_table_impl, addr, num_pages * PageSize, map_addr); break; } case OperationType::ChangePermissions: diff --git a/src/core/hle/kernel/k_page_table.h b/src/core/hle/kernel/k_page_table.h index 2258543197..1811d3e2d2 100644 --- a/src/core/hle/kernel/k_page_table.h +++ b/src/core/hle/kernel/k_page_table.h @@ -88,11 +88,11 @@ public: KMemoryAttribute attr_mask, KMemoryAttribute attr); Common::PageTable& PageTableImpl() { - return m_page_table_impl; + return *m_page_table_impl; } const Common::PageTable& PageTableImpl() const { - return m_page_table_impl; + return *m_page_table_impl; } bool CanContain(VAddr addr, size_t size, KMemoryState state) const; @@ -303,7 +303,7 @@ public: return IsKernel() ? 1 : 4; } PAddr GetPhysicalAddr(VAddr addr) const { - const auto backing_addr = m_page_table_impl.backing_addr[addr >> PageBits]; + const auto backing_addr = m_page_table_impl->backing_addr[addr >> PageBits]; ASSERT(backing_addr); return backing_addr + addr; } @@ -365,7 +365,7 @@ private: KMemoryManager::Pool m_memory_pool{KMemoryManager::Pool::Application}; KMemoryManager::Direction m_allocation_option{KMemoryManager::Direction::FromFront}; - Common::PageTable m_page_table_impl; + std::unique_ptr m_page_table_impl; Core::System& m_system; }; diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp index abc2115bd9..1a0aec56a7 100644 --- a/src/core/hle/kernel/k_process.cpp +++ b/src/core/hle/kernel/k_process.cpp @@ -72,6 +72,7 @@ Result KProcess::Initialize(KProcess* process, Core::System& system, std::string process->name = std::move(process_name); process->resource_limit = res_limit; + process->system_resource_address = 0; process->state = State::Created; process->program_id = 0; process->process_id = type == ProcessType::KernelInternal ? kernel.CreateNewKernelProcessID() @@ -92,6 +93,7 @@ Result KProcess::Initialize(KProcess* process, Core::System& system, std::string process->exception_thread = nullptr; process->is_suspended = false; process->schedule_count = 0; + process->is_handle_table_initialized = false; // Open a reference to the resource limit. process->resource_limit->Open(); @@ -121,9 +123,9 @@ void KProcess::DecrementRunningThreadCount() { } } -u64 KProcess::GetTotalPhysicalMemoryAvailable() const { +u64 KProcess::GetTotalPhysicalMemoryAvailable() { const u64 capacity{resource_limit->GetFreeValue(LimitableResource::PhysicalMemory) + - page_table->GetNormalMemorySize() + GetSystemResourceSize() + image_size + + page_table.GetNormalMemorySize() + GetSystemResourceSize() + image_size + main_thread_stack_size}; if (const auto pool_size = kernel.MemoryManager().GetSize(KMemoryManager::Pool::Application); capacity != pool_size) { @@ -135,16 +137,16 @@ u64 KProcess::GetTotalPhysicalMemoryAvailable() const { return memory_usage_capacity; } -u64 KProcess::GetTotalPhysicalMemoryAvailableWithoutSystemResource() const { +u64 KProcess::GetTotalPhysicalMemoryAvailableWithoutSystemResource() { return GetTotalPhysicalMemoryAvailable() - GetSystemResourceSize(); } -u64 KProcess::GetTotalPhysicalMemoryUsed() const { - return image_size + main_thread_stack_size + page_table->GetNormalMemorySize() + +u64 KProcess::GetTotalPhysicalMemoryUsed() { + return image_size + main_thread_stack_size + page_table.GetNormalMemorySize() + GetSystemResourceSize(); } -u64 KProcess::GetTotalPhysicalMemoryUsedWithoutSystemResource() const { +u64 KProcess::GetTotalPhysicalMemoryUsedWithoutSystemResource() { return GetTotalPhysicalMemoryUsed() - GetSystemResourceUsage(); } @@ -348,6 +350,9 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std: system_resource_size = metadata.GetSystemResourceSize(); image_size = code_size; + // We currently do not support process-specific system resource + UNIMPLEMENTED_IF(system_resource_size != 0); + KScopedResourceReservation memory_reservation(resource_limit, LimitableResource::PhysicalMemory, code_size + system_resource_size); if (!memory_reservation.Succeeded()) { @@ -356,7 +361,7 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std: return ResultLimitReached; } // Initialize proces address space - if (const Result result{page_table->InitializeForProcess( + if (const Result result{page_table.InitializeForProcess( metadata.GetAddressSpaceType(), false, 0x8000000, code_size, &kernel.GetApplicationMemoryBlockManager(), KMemoryManager::Pool::Application)}; result.IsError()) { @@ -364,9 +369,9 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std: } // Map process code region - if (const Result result{page_table->MapProcessCode(page_table->GetCodeRegionStart(), - code_size / PageSize, KMemoryState::Code, - KMemoryPermission::None)}; + if (const Result result{page_table.MapProcessCode(page_table.GetCodeRegionStart(), + code_size / PageSize, KMemoryState::Code, + KMemoryPermission::None)}; result.IsError()) { return result; } @@ -374,7 +379,7 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std: // Initialize process capabilities const auto& caps{metadata.GetKernelCapabilities()}; if (const Result result{ - capabilities.InitializeForUserProcess(caps.data(), caps.size(), *page_table)}; + capabilities.InitializeForUserProcess(caps.data(), caps.size(), page_table)}; result.IsError()) { return result; } @@ -384,12 +389,12 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std: case FileSys::ProgramAddressSpaceType::Is32Bit: case FileSys::ProgramAddressSpaceType::Is36Bit: case FileSys::ProgramAddressSpaceType::Is39Bit: - memory_usage_capacity = page_table->GetHeapRegionEnd() - page_table->GetHeapRegionStart(); + memory_usage_capacity = page_table.GetHeapRegionEnd() - page_table.GetHeapRegionStart(); break; case FileSys::ProgramAddressSpaceType::Is32BitNoMap: - memory_usage_capacity = page_table->GetHeapRegionEnd() - page_table->GetHeapRegionStart() + - page_table->GetAliasRegionEnd() - page_table->GetAliasRegionStart(); + memory_usage_capacity = page_table.GetHeapRegionEnd() - page_table.GetHeapRegionStart() + + page_table.GetAliasRegionEnd() - page_table.GetAliasRegionStart(); break; default: @@ -397,7 +402,7 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std: } // Create TLS region - R_TRY(this->CreateThreadLocalRegion(std::addressof(tls_region_address))); + R_TRY(this->CreateThreadLocalRegion(std::addressof(plr_address))); memory_reservation.Commit(); return handle_table.Initialize(capabilities.GetHandleTableSize()); @@ -409,7 +414,7 @@ void KProcess::Run(s32 main_thread_priority, u64 stack_size) { resource_limit->Reserve(LimitableResource::PhysicalMemory, main_thread_stack_size); const std::size_t heap_capacity{memory_usage_capacity - (main_thread_stack_size + image_size)}; - ASSERT(!page_table->SetMaxHeapSize(heap_capacity).IsError()); + ASSERT(!page_table.SetMaxHeapSize(heap_capacity).IsError()); ChangeState(State::Running); @@ -437,8 +442,8 @@ void KProcess::PrepareForTermination() { stop_threads(kernel.System().GlobalSchedulerContext().GetThreadList()); - this->DeleteThreadLocalRegion(tls_region_address); - tls_region_address = 0; + this->DeleteThreadLocalRegion(plr_address); + plr_address = 0; if (resource_limit) { resource_limit->Release(LimitableResource::PhysicalMemory, @@ -474,7 +479,7 @@ void KProcess::Finalize() { } // Finalize the page table. - page_table.reset(); + page_table.Finalize(); // Perform inherited finalization. KAutoObjectWithSlabHeapAndContainer::Finalize(); @@ -628,7 +633,7 @@ bool KProcess::RemoveWatchpoint(Core::System& system, VAddr addr, u64 size, void KProcess::LoadModule(CodeSet code_set, VAddr base_addr) { const auto ReprotectSegment = [&](const CodeSet::Segment& segment, Svc::MemoryPermission permission) { - page_table->SetProcessMemoryPermission(segment.addr + base_addr, segment.size, permission); + page_table.SetProcessMemoryPermission(segment.addr + base_addr, segment.size, permission); }; kernel.System().Memory().WriteBlock(*this, base_addr, code_set.memory.data(), @@ -645,8 +650,7 @@ bool KProcess::IsSignaled() const { } KProcess::KProcess(KernelCore& kernel_) - : KAutoObjectWithSlabHeapAndContainer{kernel_}, page_table{std::make_unique( - kernel_.System())}, + : KAutoObjectWithSlabHeapAndContainer{kernel_}, page_table{kernel_.System()}, handle_table{kernel_}, address_arbiter{kernel_.System()}, condition_var{kernel_.System()}, state_lock{kernel_}, list_lock{kernel_} {} @@ -668,11 +672,11 @@ Result KProcess::AllocateMainThreadStack(std::size_t stack_size) { // The kernel always ensures that the given stack size is page aligned. main_thread_stack_size = Common::AlignUp(stack_size, PageSize); - const VAddr start{page_table->GetStackRegionStart()}; - const std::size_t size{page_table->GetStackRegionEnd() - start}; + const VAddr start{page_table.GetStackRegionStart()}; + const std::size_t size{page_table.GetStackRegionEnd() - start}; CASCADE_RESULT(main_thread_stack_top, - page_table->AllocateAndMapMemory( + page_table.AllocateAndMapMemory( main_thread_stack_size / PageSize, PageSize, false, start, size / PageSize, KMemoryState::Stack, KMemoryPermission::UserReadWrite)); @@ -681,4 +685,12 @@ Result KProcess::AllocateMainThreadStack(std::size_t stack_size) { return ResultSuccess; } +void KProcess::FinalizeHandleTable() { + // Finalize the table. + handle_table.Finalize(); + + // Note that the table is finalized. + is_handle_table_initialized = false; +} + } // namespace Kernel diff --git a/src/core/hle/kernel/k_process.h b/src/core/hle/kernel/k_process.h index b1c7da4543..fcc2897f99 100644 --- a/src/core/hle/kernel/k_process.h +++ b/src/core/hle/kernel/k_process.h @@ -13,6 +13,7 @@ #include "core/hle/kernel/k_auto_object.h" #include "core/hle/kernel/k_condition_variable.h" #include "core/hle/kernel/k_handle_table.h" +#include "core/hle/kernel/k_page_table.h" #include "core/hle/kernel/k_synchronization_object.h" #include "core/hle/kernel/k_thread_local_page.h" #include "core/hle/kernel/k_worker_task.h" @@ -31,7 +32,6 @@ class ProgramMetadata; namespace Kernel { class KernelCore; -class KPageTable; class KResourceLimit; class KThread; class KSharedMemoryInfo; @@ -107,12 +107,12 @@ public: /// Gets a reference to the process' page table. KPageTable& PageTable() { - return *page_table; + return page_table; } /// Gets const a reference to the process' page table. const KPageTable& PageTable() const { - return *page_table; + return page_table; } /// Gets a reference to the process' handle table. @@ -150,9 +150,8 @@ public: return address_arbiter.WaitForAddress(address, arb_type, value, timeout); } - /// Gets the address to the process' dedicated TLS region. - VAddr GetTLSRegionAddress() const { - return tls_region_address; + VAddr GetProcessLocalRegionAddress() const { + return plr_address; } /// Gets the current status of the process @@ -279,18 +278,18 @@ public: } /// Retrieves the total physical memory available to this process in bytes. - u64 GetTotalPhysicalMemoryAvailable() const; + u64 GetTotalPhysicalMemoryAvailable(); /// Retrieves the total physical memory available to this process in bytes, /// without the size of the personal system resource heap added to it. - u64 GetTotalPhysicalMemoryAvailableWithoutSystemResource() const; + u64 GetTotalPhysicalMemoryAvailableWithoutSystemResource(); /// Retrieves the total physical memory used by this process in bytes. - u64 GetTotalPhysicalMemoryUsed() const; + u64 GetTotalPhysicalMemoryUsed(); /// Retrieves the total physical memory used by this process in bytes, /// without the size of the personal system resource heap added to it. - u64 GetTotalPhysicalMemoryUsedWithoutSystemResource() const; + u64 GetTotalPhysicalMemoryUsedWithoutSystemResource(); /// Gets the list of all threads created with this process as their owner. std::list& GetThreadList() { @@ -413,8 +412,10 @@ private: /// Allocates the main thread stack for the process, given the stack size in bytes. Result AllocateMainThreadStack(std::size_t stack_size); + void FinalizeHandleTable(); + /// Memory manager for this process - std::unique_ptr page_table; + KPageTable page_table; /// Current status of the process State state{}; @@ -433,6 +434,8 @@ private: /// Resource limit descriptor for this process KResourceLimit* resource_limit{}; + VAddr system_resource_address{}; + /// The ideal CPU core for this process, threads are scheduled on this core by default. u8 ideal_core = 0; @@ -459,7 +462,7 @@ private: KConditionVariable condition_var; /// Address indicating the location of the process' dedicated TLS region. - VAddr tls_region_address = 0; + VAddr plr_address = 0; /// Random values for svcGetInfo RandomEntropy std::array random_entropy{}; @@ -485,8 +488,12 @@ private: /// Schedule count of this process s64 schedule_count{}; + size_t memory_release_hint{}; + bool is_signaled{}; bool is_suspended{}; + bool is_immortal{}; + bool is_handle_table_initialized{}; bool is_initialized{}; std::atomic num_running_threads{}; diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index b6bbd4984b..6879de9ef3 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -95,6 +95,15 @@ struct KernelCore::Impl { } } + void CloseCurrentProcess() { + (*current_process).Finalize(); + // current_process->Close(); + // TODO: The current process should be destroyed based on accurate ref counting after + // calling Close(). Adding a manual Destroy() call instead to avoid a memory leak. + (*current_process).Destroy(); + current_process = nullptr; + } + void Shutdown() { is_shutting_down.store(true, std::memory_order_relaxed); SCOPE_EXIT({ is_shutting_down.store(false, std::memory_order_relaxed); }); @@ -157,15 +166,7 @@ struct KernelCore::Impl { } } - // Shutdown all processes. - if (current_process) { - (*current_process).Finalize(); - // current_process->Close(); - // TODO: The current process should be destroyed based on accurate ref counting after - // calling Close(). Adding a manual Destroy() call instead to avoid a memory leak. - (*current_process).Destroy(); - current_process = nullptr; - } + CloseCurrentProcess(); // Track kernel objects that were not freed on shutdown { @@ -870,6 +871,10 @@ const KProcess* KernelCore::CurrentProcess() const { return impl->current_process; } +void KernelCore::CloseCurrentProcess() { + impl->CloseCurrentProcess(); +} + const std::vector& KernelCore::GetProcessList() const { return impl->process_list; } diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 79e66483ee..6eded95393 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -131,6 +131,9 @@ public: /// Retrieves a const pointer to the current process. const KProcess* CurrentProcess() const; + /// Closes the current process. + void CloseCurrentProcess(); + /// Retrieves the list of processes. const std::vector& GetProcessList() const; diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index bac61fd096..b07ae3f027 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -933,7 +933,7 @@ static Result GetInfo(Core::System& system, u64* result, u64 info_id, Handle han return ResultSuccess; case GetInfoType::UserExceptionContextAddr: - *result = process->GetTLSRegionAddress(); + *result = process->GetProcessLocalRegionAddress(); return ResultSuccess; case GetInfoType::TotalPhysicalMemoryAvailableWithoutSystemResource: From 1b787adbd0b58986e6efdf6d536dcc949362c108 Mon Sep 17 00:00:00 2001 From: bunnei Date: Sat, 10 Sep 2022 23:45:07 -0700 Subject: [PATCH 36/91] core: hle: kernel: Fix InitializePreemption order. --- src/core/hle/kernel/kernel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 6879de9ef3..eed2dc9f3e 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -74,8 +74,8 @@ struct KernelCore::Impl { InitializeMemoryLayout(); Init::InitializeKPageBufferSlabHeap(system); InitializeShutdownThreads(); - InitializePreemption(kernel); InitializePhysicalCores(); + InitializePreemption(kernel); // Initialize the Dynamic Slab Heaps. { From a4d11f4427859cbe82fc825f8493844e17bb668f Mon Sep 17 00:00:00 2001 From: bunnei Date: Sat, 10 Sep 2022 01:48:15 -0700 Subject: [PATCH 37/91] core: Partially persist emulation state across game boots. --- src/core/core.cpp | 65 +++++++++++++++++++--------------- src/core/core.h | 10 ++++-- src/core/core_timing.cpp | 29 +++++++-------- src/core/core_timing.h | 7 ++-- src/tests/core/core_timing.cpp | 3 -- src/yuzu/bootmanager.cpp | 4 +-- src/yuzu/main.cpp | 1 + src/yuzu_cmd/yuzu.cpp | 4 ++- 8 files changed, 65 insertions(+), 58 deletions(-) diff --git a/src/core/core.cpp b/src/core/core.cpp index 1deeee1545..2c4c0dbe40 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -133,6 +133,30 @@ struct System::Impl { : kernel{system}, fs_controller{system}, memory{system}, hid_core{}, room_network{}, cpu_manager{system}, reporter{system}, applet_manager{system}, time_manager{system} {} + void Initialize(System& system) { + device_memory = std::make_unique(); + + is_multicore = Settings::values.use_multi_core.GetValue(); + + core_timing.SetMulticore(is_multicore); + core_timing.Initialize([&system]() { system.RegisterHostThread(); }); + + const auto posix_time = std::chrono::system_clock::now().time_since_epoch(); + const auto current_time = + std::chrono::duration_cast(posix_time).count(); + Settings::values.custom_rtc_differential = + Settings::values.custom_rtc.value_or(current_time) - current_time; + + // Create a default fs if one doesn't already exist. + if (virtual_filesystem == nullptr) + virtual_filesystem = std::make_shared(); + if (content_provider == nullptr) + content_provider = std::make_unique(); + + // Create default implementations of applets if one is not provided. + applet_manager.SetDefaultAppletsIfMissing(); + } + SystemResultStatus Run() { std::unique_lock lk(suspend_guard); status = SystemResultStatus::Success; @@ -178,37 +202,17 @@ struct System::Impl { debugger = std::make_unique(system, port); } - SystemResultStatus Init(System& system, Frontend::EmuWindow& emu_window) { + SystemResultStatus SetupForMainProcess(System& system, Frontend::EmuWindow& emu_window) { LOG_DEBUG(Core, "initialized OK"); - device_memory = std::make_unique(); - - is_multicore = Settings::values.use_multi_core.GetValue(); is_async_gpu = Settings::values.use_asynchronous_gpu_emulation.GetValue(); kernel.SetMulticore(is_multicore); cpu_manager.SetMulticore(is_multicore); cpu_manager.SetAsyncGpu(is_async_gpu); - core_timing.SetMulticore(is_multicore); kernel.Initialize(); cpu_manager.Initialize(); - core_timing.Initialize([&system]() { system.RegisterHostThread(); }); - - const auto posix_time = std::chrono::system_clock::now().time_since_epoch(); - const auto current_time = - std::chrono::duration_cast(posix_time).count(); - Settings::values.custom_rtc_differential = - Settings::values.custom_rtc.value_or(current_time) - current_time; - - // Create a default fs if one doesn't already exist. - if (virtual_filesystem == nullptr) - virtual_filesystem = std::make_shared(); - if (content_provider == nullptr) - content_provider = std::make_unique(); - - /// Create default implementations of applets if one is not provided. - applet_manager.SetDefaultAppletsIfMissing(); /// Reset all glue registrations arp_manager.ResetAll(); @@ -253,11 +257,11 @@ struct System::Impl { return SystemResultStatus::ErrorGetLoader; } - SystemResultStatus init_result{Init(system, emu_window)}; + SystemResultStatus init_result{SetupForMainProcess(system, emu_window)}; if (init_result != SystemResultStatus::Success) { LOG_CRITICAL(Core, "Failed to initialize system (Error {})!", static_cast(init_result)); - Shutdown(); + ShutdownMainProcess(); return init_result; } @@ -276,7 +280,7 @@ struct System::Impl { const auto [load_result, load_parameters] = app_loader->Load(*main_process, system); if (load_result != Loader::ResultStatus::Success) { LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", load_result); - Shutdown(); + ShutdownMainProcess(); return static_cast( static_cast(SystemResultStatus::ErrorLoader) + static_cast(load_result)); @@ -335,7 +339,7 @@ struct System::Impl { return status; } - void Shutdown() { + void ShutdownMainProcess() { SetShuttingDown(true); // Log last frame performance stats if game was loded @@ -369,7 +373,7 @@ struct System::Impl { cheat_engine.reset(); telemetry_session.reset(); time_manager.Shutdown(); - core_timing.Shutdown(); + core_timing.ClearPendingEvents(); app_loader.reset(); audio_core.reset(); gpu_core.reset(); @@ -377,7 +381,6 @@ struct System::Impl { perf_stats.reset(); kernel.Shutdown(); memory.Reset(); - applet_manager.ClearAll(); if (auto room_member = room_network.GetRoomMember().lock()) { Network::GameInfo game_info{}; @@ -520,6 +523,10 @@ const CpuManager& System::GetCpuManager() const { return impl->cpu_manager; } +void System::Initialize() { + impl->Initialize(*this); +} + SystemResultStatus System::Run() { return impl->Run(); } @@ -540,8 +547,8 @@ void System::InvalidateCpuInstructionCacheRange(VAddr addr, std::size_t size) { impl->kernel.InvalidateCpuInstructionCacheRange(addr, size); } -void System::Shutdown() { - impl->Shutdown(); +void System::ShutdownMainProcess() { + impl->ShutdownMainProcess(); } bool System::IsShuttingDown() const { diff --git a/src/core/core.h b/src/core/core.h index 7843cc8ad9..4ebedffd91 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -142,6 +142,12 @@ public: System(System&&) = delete; System& operator=(System&&) = delete; + /** + * Initializes the system + * This function will initialize core functionaility used for system emulation + */ + void Initialize(); + /** * Run the OS and Application * This function will start emulation and run the relevant devices @@ -166,8 +172,8 @@ public: void InvalidateCpuInstructionCacheRange(VAddr addr, std::size_t size); - /// Shutdown the emulated system. - void Shutdown(); + /// Shutdown the main emulated process. + void ShutdownMainProcess(); /// Check if the core is shutting down. [[nodiscard]] bool IsShuttingDown() const; diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index 2678ce5328..2afb2696cc 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -40,7 +40,17 @@ struct CoreTiming::Event { CoreTiming::CoreTiming() : clock{Common::CreateBestMatchingClock(Hardware::BASE_CLOCK_RATE, Hardware::CNTFREQ)} {} -CoreTiming::~CoreTiming() = default; +CoreTiming::~CoreTiming() { + paused = true; + shutting_down = true; + pause_event.Set(); + event.Set(); + if (timer_thread) { + timer_thread->join(); + } + timer_thread.reset(); + has_started = false; +} void CoreTiming::ThreadEntry(CoreTiming& instance) { constexpr char name[] = "HostTiming"; @@ -65,17 +75,8 @@ void CoreTiming::Initialize(std::function&& on_thread_init_) { } } -void CoreTiming::Shutdown() { - paused = true; - shutting_down = true; - pause_event.Set(); - event.Set(); - if (timer_thread) { - timer_thread->join(); - } - ClearPendingEvents(); - timer_thread.reset(); - has_started = false; +void CoreTiming::ClearPendingEvents() { + event_queue.clear(); } void CoreTiming::Pause(bool is_paused) { @@ -196,10 +197,6 @@ u64 CoreTiming::GetClockTicks() const { return CpuCyclesToClockCycles(ticks); } -void CoreTiming::ClearPendingEvents() { - event_queue.clear(); -} - void CoreTiming::RemoveEvent(const std::shared_ptr& event_type) { std::scoped_lock lock{basic_lock}; diff --git a/src/core/core_timing.h b/src/core/core_timing.h index 3259397b28..7996b529fe 100644 --- a/src/core/core_timing.h +++ b/src/core/core_timing.h @@ -61,8 +61,8 @@ public: /// required to end slice - 1 and start slice 0 before the first cycle of code is executed. void Initialize(std::function&& on_thread_init_); - /// Tears down all timing related functionality. - void Shutdown(); + /// Clear all pending events. This should ONLY be done on exit. + void ClearPendingEvents(); /// Sets if emulation is multicore or single core, must be set before Initialize void SetMulticore(bool is_multicore_) { @@ -136,9 +136,6 @@ public: private: struct Event; - /// Clear all pending events. This should ONLY be done on exit. - void ClearPendingEvents(); - static void ThreadEntry(CoreTiming& instance); void ThreadLoop(); diff --git a/src/tests/core/core_timing.cpp b/src/tests/core/core_timing.cpp index 7c432a63c5..284b2ae66f 100644 --- a/src/tests/core/core_timing.cpp +++ b/src/tests/core/core_timing.cpp @@ -40,9 +40,6 @@ struct ScopeInit final { core_timing.SetMulticore(true); core_timing.Initialize([]() {}); } - ~ScopeInit() { - core_timing.Shutdown(); - } Core::Timing::CoreTiming core_timing; }; diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 24251247d2..6acfb7b06c 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -120,8 +120,8 @@ void EmuThread::run() { } } - // Shutdown the core emulation - system.Shutdown(); + // Shutdown the main emulated process + system.ShutdownMainProcess(); #if MICROPROFILE_ENABLED MicroProfileOnThreadExit(); diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index a94624be63..501c342551 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -294,6 +294,7 @@ GMainWindow::GMainWindow(std::unique_ptr config_, bool has_broken_vulkan #ifdef __linux__ SetupSigInterrupts(); #endif + system->Initialize(); Common::Log::Initialize(); LoadTranslation(); diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 3a0f33cba1..e16f79eb40 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -302,6 +302,8 @@ int main(int argc, char** argv) { } Core::System system{}; + system.Initialize(); + InputCommon::InputSubsystem input_subsystem{}; // Apply the command line arguments @@ -392,7 +394,7 @@ int main(int argc, char** argv) { } system.DetachDebugger(); void(system.Pause()); - system.Shutdown(); + system.ShutdownMainProcess(); detached_tasks.WaitForAllTasks(); return 0; From 829e82e264504696ce1d0ae9421a53d16bf104ea Mon Sep 17 00:00:00 2001 From: bunnei Date: Fri, 14 Oct 2022 22:55:51 -0700 Subject: [PATCH 38/91] core: hle: kernel: Use result macros for new/changed code. --- src/core/hle/kernel/k_dynamic_page_manager.h | 2 +- .../hle/kernel/k_memory_block_manager.cpp | 2 +- src/core/hle/kernel/k_memory_block_manager.h | 2 +- src/core/hle/kernel/k_page_table.cpp | 111 ++++++++---------- src/core/hle/kernel/k_page_table.h | 19 +-- src/core/hle/kernel/k_process.cpp | 42 +++---- src/core/hle/kernel/k_process.h | 16 ++- src/core/hle/kernel/k_thread.cpp | 41 +++---- src/core/hle/result.h | 3 - 9 files changed, 110 insertions(+), 128 deletions(-) diff --git a/src/core/hle/kernel/k_dynamic_page_manager.h b/src/core/hle/kernel/k_dynamic_page_manager.h index 88d53776ae..9076c8fa3c 100644 --- a/src/core/hle/kernel/k_dynamic_page_manager.h +++ b/src/core/hle/kernel/k_dynamic_page_manager.h @@ -65,7 +65,7 @@ public: m_page_bitmap.SetBit(i); } - return ResultSuccess; + R_SUCCEED(); } VAddr GetAddress() const { diff --git a/src/core/hle/kernel/k_memory_block_manager.cpp b/src/core/hle/kernel/k_memory_block_manager.cpp index c908af75a9..cf4c1e371b 100644 --- a/src/core/hle/kernel/k_memory_block_manager.cpp +++ b/src/core/hle/kernel/k_memory_block_manager.cpp @@ -23,7 +23,7 @@ Result KMemoryBlockManager::Initialize(VAddr st, VAddr nd, KMemoryBlockSlabManag KMemoryState::Free, KMemoryPermission::None, KMemoryAttribute::None); m_memory_block_tree.insert(*start_block); - return ResultSuccess; + R_SUCCEED(); } void KMemoryBlockManager::Finalize(KMemoryBlockSlabManager* slab_manager, diff --git a/src/core/hle/kernel/k_memory_block_manager.h b/src/core/hle/kernel/k_memory_block_manager.h index b4ee4e319d..9b5873883d 100644 --- a/src/core/hle/kernel/k_memory_block_manager.h +++ b/src/core/hle/kernel/k_memory_block_manager.h @@ -35,7 +35,7 @@ private: R_UNLESS(m_blocks[m_index + i] != nullptr, ResultOutOfResource); } - return ResultSuccess; + R_SUCCEED(); } public: diff --git a/src/core/hle/kernel/k_page_table.cpp b/src/core/hle/kernel/k_page_table.cpp index 22098c056e..307e491cb5 100644 --- a/src/core/hle/kernel/k_page_table.cpp +++ b/src/core/hle/kernel/k_page_table.cpp @@ -128,12 +128,9 @@ Result KPageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_type alloc_start = process_code_end; alloc_size = end - process_code_end; } - const size_t needed_size{ - (alias_region_size + heap_region_size + stack_region_size + kernel_map_region_size)}; - if (alloc_size < needed_size) { - ASSERT(false); - return ResultOutOfMemory; - } + const size_t needed_size = + (alias_region_size + heap_region_size + stack_region_size + kernel_map_region_size); + R_UNLESS(alloc_size >= needed_size, ResultOutOfMemory); const size_t remaining_size{alloc_size - needed_size}; @@ -259,8 +256,9 @@ Result KPageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_type m_page_table_impl = std::make_unique(); m_page_table_impl->Resize(m_address_space_width, PageBits); - return m_memory_block_manager.Initialize(m_address_space_start, m_address_space_end, - m_memory_block_slab_manager); + // Initialize our memory block manager. + R_RETURN(m_memory_block_manager.Initialize(m_address_space_start, m_address_space_end, + m_memory_block_slab_manager)); } void KPageTable::Finalize() { @@ -306,7 +304,7 @@ Result KPageTable::MapProcessCode(VAddr addr, size_t num_pages, KMemoryState sta KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal, KMemoryBlockDisableMergeAttribute::None); - return ResultSuccess; + R_SUCCEED(); } Result KPageTable::MapCodeMemory(VAddr dst_address, VAddr src_address, size_t size) { @@ -385,7 +383,7 @@ Result KPageTable::MapCodeMemory(VAddr dst_address, VAddr src_address, size_t si KMemoryBlockDisableMergeAttribute::None); } - return ResultSuccess; + R_SUCCEED(); } Result KPageTable::UnmapCodeMemory(VAddr dst_address, VAddr src_address, size_t size, @@ -487,7 +485,7 @@ Result KPageTable::UnmapCodeMemory(VAddr dst_address, VAddr src_address, size_t reprotected_pages = true; } - return ResultSuccess; + R_SUCCEED(); } VAddr KPageTable::FindFreeArea(VAddr region_start, size_t region_num_pages, size_t num_pages, @@ -558,7 +556,7 @@ Result KPageTable::MakePageGroup(KPageGroup& pg, VAddr addr, size_t num_pages) { R_UNLESS(IsHeapPhysicalAddress(memory_layout, cur_addr), ResultInvalidCurrentMemory); R_TRY(pg.AddBlock(cur_addr, cur_pages)); - return ResultSuccess; + R_SUCCEED(); } bool KPageTable::IsValidPageGroup(const KPageGroup& pg_ll, VAddr addr, size_t num_pages) { @@ -685,7 +683,7 @@ Result KPageTable::UnmapProcessMemory(VAddr dst_addr, size_t size, KPageTable& s m_system.InvalidateCpuInstructionCaches(); - return ResultSuccess; + R_SUCCEED(); } Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) { @@ -933,7 +931,7 @@ Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) { // Cancel our guard. unmap_guard.Cancel(); - return ResultSuccess; + R_SUCCEED(); } } } @@ -1176,7 +1174,7 @@ Result KPageTable::UnmapPhysicalMemory(VAddr address, size_t size) { // We succeeded. remap_guard.Cancel(); - return ResultSuccess; + R_SUCCEED(); } Result KPageTable::MapMemory(VAddr dst_address, VAddr src_address, size_t size) { @@ -1243,7 +1241,7 @@ Result KPageTable::MapMemory(VAddr dst_address, VAddr src_address, size_t size) KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal, KMemoryBlockDisableMergeAttribute::None); - return ResultSuccess; + R_SUCCEED(); } Result KPageTable::UnmapMemory(VAddr dst_address, VAddr src_address, size_t size) { @@ -1288,9 +1286,7 @@ Result KPageTable::UnmapMemory(VAddr dst_address, VAddr src_address, size_t size AddRegionToPages(src_address, num_pages, src_pages); AddRegionToPages(dst_address, num_pages, dst_pages); - if (!dst_pages.IsEqual(src_pages)) { - return ResultInvalidMemoryRegion; - } + R_UNLESS(dst_pages.IsEqual(src_pages), ResultInvalidMemoryRegion); { auto block_guard = detail::ScopeExit([&] { MapPages(dst_address, dst_pages, dst_perm); }); @@ -1312,7 +1308,7 @@ Result KPageTable::UnmapMemory(VAddr dst_address, VAddr src_address, size_t size KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::None, KMemoryBlockDisableMergeAttribute::Normal); - return ResultSuccess; + R_SUCCEED(); } Result KPageTable::MapPages(VAddr addr, const KPageGroup& page_linked_list, @@ -1330,13 +1326,13 @@ Result KPageTable::MapPages(VAddr addr, const KPageGroup& page_linked_list, ASSERT(Operate(addr, num_pages, KMemoryPermission::None, OperationType::Unmap) .IsSuccess()); - return result; + R_RETURN(result); } cur_addr += node.GetNumPages() * PageSize; } - return ResultSuccess; + R_SUCCEED(); } Result KPageTable::MapPages(VAddr address, KPageGroup& page_linked_list, KMemoryState state, @@ -1367,7 +1363,7 @@ Result KPageTable::MapPages(VAddr address, KPageGroup& page_linked_list, KMemory KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal, KMemoryBlockDisableMergeAttribute::None); - return ResultSuccess; + R_SUCCEED(); } Result KPageTable::MapPages(VAddr* out_addr, size_t num_pages, size_t alignment, PAddr phys_addr, @@ -1413,7 +1409,7 @@ Result KPageTable::MapPages(VAddr* out_addr, size_t num_pages, size_t alignment, // We successfully mapped the pages. *out_addr = addr; - return ResultSuccess; + R_SUCCEED(); } Result KPageTable::UnmapPages(VAddr addr, const KPageGroup& page_linked_list) { @@ -1425,13 +1421,13 @@ Result KPageTable::UnmapPages(VAddr addr, const KPageGroup& page_linked_list) { if (const auto result{Operate(cur_addr, node.GetNumPages(), KMemoryPermission::None, OperationType::Unmap)}; result.IsError()) { - return result; + R_RETURN(result); } cur_addr += node.GetNumPages() * PageSize; } - return ResultSuccess; + R_SUCCEED(); } Result KPageTable::UnmapPages(VAddr address, KPageGroup& page_linked_list, KMemoryState state) { @@ -1465,7 +1461,7 @@ Result KPageTable::UnmapPages(VAddr address, KPageGroup& page_linked_list, KMemo KMemoryBlockDisableMergeAttribute::None, KMemoryBlockDisableMergeAttribute::Normal); - return ResultSuccess; + R_SUCCEED(); } Result KPageTable::UnmapPages(VAddr address, size_t num_pages, KMemoryState state) { @@ -1498,7 +1494,7 @@ Result KPageTable::UnmapPages(VAddr address, size_t num_pages, KMemoryState stat KMemoryBlockDisableMergeAttribute::None, KMemoryBlockDisableMergeAttribute::Normal); - return ResultSuccess; + R_SUCCEED(); } Result KPageTable::MakeAndOpenPageGroup(KPageGroup* out, VAddr address, size_t num_pages, @@ -1523,7 +1519,7 @@ Result KPageTable::MakeAndOpenPageGroup(KPageGroup* out, VAddr address, size_t n // Create a new page group for the region. R_TRY(this->MakePageGroup(*out, address, num_pages)); - return ResultSuccess; + R_SUCCEED(); } Result KPageTable::SetProcessMemoryPermission(VAddr addr, size_t size, @@ -1589,7 +1585,7 @@ Result KPageTable::SetProcessMemoryPermission(VAddr addr, size_t size, m_system.InvalidateCpuInstructionCacheRange(addr, size); } - return ResultSuccess; + R_SUCCEED(); } KMemoryInfo KPageTable::QueryInfoImpl(VAddr addr) { @@ -1653,7 +1649,7 @@ Result KPageTable::SetMemoryPermission(VAddr addr, size_t size, Svc::MemoryPermi KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::None, KMemoryBlockDisableMergeAttribute::None); - return ResultSuccess; + R_SUCCEED(); } Result KPageTable::SetMemoryAttribute(VAddr addr, size_t size, u32 mask, u32 attr) { @@ -1696,7 +1692,7 @@ Result KPageTable::SetMemoryAttribute(VAddr addr, size_t size, u32 mask, u32 att new_attr, KMemoryBlockDisableMergeAttribute::None, KMemoryBlockDisableMergeAttribute::None); - return ResultSuccess; + R_SUCCEED(); } Result KPageTable::SetMaxHeapSize(size_t size) { @@ -1708,7 +1704,7 @@ Result KPageTable::SetMaxHeapSize(size_t size) { m_max_heap_size = size; - return ResultSuccess; + R_SUCCEED(); } Result KPageTable::SetHeapSize(VAddr* out, size_t size) { @@ -1769,11 +1765,11 @@ Result KPageTable::SetHeapSize(VAddr* out, size_t size) { // Set the output. *out = m_heap_region_start; - return ResultSuccess; + R_SUCCEED(); } else if (size == GetHeapSize()) { // The size requested is exactly the current size. *out = m_heap_region_start; - return ResultSuccess; + R_SUCCEED(); } else { // We have to allocate memory. Determine how much to allocate and where while the table // is locked. @@ -1847,7 +1843,7 @@ Result KPageTable::SetHeapSize(VAddr* out, size_t size) { // Set the output. *out = m_heap_region_start; - return ResultSuccess; + R_SUCCEED(); } } @@ -1857,19 +1853,12 @@ ResultVal KPageTable::AllocateAndMapMemory(size_t needed_num_pages, size_ KMemoryPermission perm, PAddr map_addr) { KScopedLightLock lk(m_general_lock); - if (!CanContain(region_start, region_num_pages * PageSize, state)) { - return ResultInvalidCurrentMemory; - } - - if (region_num_pages <= needed_num_pages) { - return ResultOutOfMemory; - } - + R_UNLESS(CanContain(region_start, region_num_pages * PageSize, state), + ResultInvalidCurrentMemory); + R_UNLESS(region_num_pages > needed_num_pages, ResultOutOfMemory); const VAddr addr{ AllocateVirtualMemory(region_start, region_num_pages, needed_num_pages, align)}; - if (!addr) { - return ResultOutOfMemory; - } + R_UNLESS(addr, ResultOutOfMemory); // Create an update allocator. Result allocator_result{ResultSuccess}; @@ -1922,7 +1911,7 @@ Result KPageTable::LockForMapDeviceAddressSpace(VAddr address, size_t size, KMem m_memory_block_manager.UpdateLock(std::addressof(allocator), address, num_pages, &KMemoryBlock::ShareToDevice, KMemoryPermission::None); - return ResultSuccess; + R_SUCCEED(); } Result KPageTable::LockForUnmapDeviceAddressSpace(VAddr address, size_t size) { @@ -1956,7 +1945,7 @@ Result KPageTable::LockForUnmapDeviceAddressSpace(VAddr address, size_t size) { m_memory_block_manager.UpdateLock(std::addressof(allocator), address, num_pages, lock_func, KMemoryPermission::None); - return ResultSuccess; + R_SUCCEED(); } Result KPageTable::UnlockForDeviceAddressSpace(VAddr address, size_t size) { @@ -1984,24 +1973,24 @@ Result KPageTable::UnlockForDeviceAddressSpace(VAddr address, size_t size) { m_memory_block_manager.UpdateLock(std::addressof(allocator), address, num_pages, &KMemoryBlock::UnshareToDevice, KMemoryPermission::None); - return ResultSuccess; + R_SUCCEED(); } Result KPageTable::LockForCodeMemory(KPageGroup* out, VAddr addr, size_t size) { - return this->LockMemoryAndOpen( + R_RETURN(this->LockMemoryAndOpen( out, nullptr, addr, size, KMemoryState::FlagCanCodeMemory, KMemoryState::FlagCanCodeMemory, KMemoryPermission::All, KMemoryPermission::UserReadWrite, KMemoryAttribute::All, KMemoryAttribute::None, static_cast(KMemoryPermission::NotMapped | KMemoryPermission::KernelReadWrite), - KMemoryAttribute::Locked); + KMemoryAttribute::Locked)); } Result KPageTable::UnlockForCodeMemory(VAddr addr, size_t size, const KPageGroup& pg) { - return this->UnlockMemory( + R_RETURN(this->UnlockMemory( addr, size, KMemoryState::FlagCanCodeMemory, KMemoryState::FlagCanCodeMemory, KMemoryPermission::None, KMemoryPermission::None, KMemoryAttribute::All, - KMemoryAttribute::Locked, KMemoryPermission::UserReadWrite, KMemoryAttribute::Locked, &pg); + KMemoryAttribute::Locked, KMemoryPermission::UserReadWrite, KMemoryAttribute::Locked, &pg)); } bool KPageTable::IsRegionContiguous(VAddr addr, u64 size) const { @@ -2056,7 +2045,7 @@ Result KPageTable::Operate(VAddr addr, size_t num_pages, const KPageGroup& page_ addr += size; } - return ResultSuccess; + R_SUCCEED(); } Result KPageTable::Operate(VAddr addr, size_t num_pages, KMemoryPermission perm, @@ -2083,7 +2072,7 @@ Result KPageTable::Operate(VAddr addr, size_t num_pages, KMemoryPermission perm, default: ASSERT(false); } - return ResultSuccess; + R_SUCCEED(); } VAddr KPageTable::GetRegionAddress(KMemoryState state) const { @@ -2211,7 +2200,7 @@ Result KPageTable::CheckMemoryState(const KMemoryInfo& info, KMemoryState state_ R_UNLESS((info.m_permission & perm_mask) == perm, ResultInvalidCurrentMemory); R_UNLESS((info.m_attribute & attr_mask) == attr, ResultInvalidCurrentMemory); - return ResultSuccess; + R_SUCCEED(); } Result KPageTable::CheckMemoryStateContiguous(size_t* out_blocks_needed, VAddr addr, size_t size, @@ -2253,7 +2242,7 @@ Result KPageTable::CheckMemoryStateContiguous(size_t* out_blocks_needed, VAddr a *out_blocks_needed = blocks_for_start_align + blocks_for_end_align; } - return ResultSuccess; + R_SUCCEED(); } Result KPageTable::CheckMemoryState(KMemoryState* out_state, KMemoryPermission* out_perm, @@ -2315,7 +2304,7 @@ Result KPageTable::CheckMemoryState(KMemoryState* out_state, KMemoryPermission* if (out_blocks_needed != nullptr) { *out_blocks_needed = blocks_for_start_align + blocks_for_end_align; } - return ResultSuccess; + R_SUCCEED(); } Result KPageTable::LockMemoryAndOpen(KPageGroup* out_pg, PAddr* out_paddr, VAddr addr, size_t size, @@ -2381,7 +2370,7 @@ Result KPageTable::LockMemoryAndOpen(KPageGroup* out_pg, PAddr* out_paddr, VAddr new_attr, KMemoryBlockDisableMergeAttribute::Locked, KMemoryBlockDisableMergeAttribute::None); - return ResultSuccess; + R_SUCCEED(); } Result KPageTable::UnlockMemory(VAddr addr, size_t size, KMemoryState state_mask, @@ -2436,7 +2425,7 @@ Result KPageTable::UnlockMemory(VAddr addr, size_t size, KMemoryState state_mask new_attr, KMemoryBlockDisableMergeAttribute::None, KMemoryBlockDisableMergeAttribute::Locked); - return ResultSuccess; + R_SUCCEED(); } } // namespace Kernel diff --git a/src/core/hle/kernel/k_page_table.h b/src/core/hle/kernel/k_page_table.h index 1811d3e2d2..c6aeacd96c 100644 --- a/src/core/hle/kernel/k_page_table.h +++ b/src/core/hle/kernel/k_page_table.h @@ -57,9 +57,9 @@ public: KMemoryPermission perm); Result MapPages(VAddr* out_addr, size_t num_pages, size_t alignment, PAddr phys_addr, KMemoryState state, KMemoryPermission perm) { - return this->MapPages(out_addr, num_pages, alignment, phys_addr, true, - this->GetRegionAddress(state), this->GetRegionSize(state) / PageSize, - state, perm); + R_RETURN(this->MapPages(out_addr, num_pages, alignment, phys_addr, true, + this->GetRegionAddress(state), + this->GetRegionSize(state) / PageSize, state, perm)); } Result UnmapPages(VAddr addr, KPageGroup& page_linked_list, KMemoryState state); Result UnmapPages(VAddr address, size_t num_pages, KMemoryState state); @@ -137,8 +137,8 @@ private: KMemoryState state, KMemoryPermission perm_mask, KMemoryPermission perm, KMemoryAttribute attr_mask, KMemoryAttribute attr) const { - return this->CheckMemoryStateContiguous(nullptr, addr, size, state_mask, state, perm_mask, - perm, attr_mask, attr); + R_RETURN(this->CheckMemoryStateContiguous(nullptr, addr, size, state_mask, state, perm_mask, + perm, attr_mask, attr)); } Result CheckMemoryState(const KMemoryInfo& info, KMemoryState state_mask, KMemoryState state, @@ -155,15 +155,16 @@ private: KMemoryPermission perm_mask, KMemoryPermission perm, KMemoryAttribute attr_mask, KMemoryAttribute attr, KMemoryAttribute ignore_attr = DefaultMemoryIgnoreAttr) const { - return CheckMemoryState(nullptr, nullptr, nullptr, out_blocks_needed, addr, size, - state_mask, state, perm_mask, perm, attr_mask, attr, ignore_attr); + R_RETURN(CheckMemoryState(nullptr, nullptr, nullptr, out_blocks_needed, addr, size, + state_mask, state, perm_mask, perm, attr_mask, attr, + ignore_attr)); } Result CheckMemoryState(VAddr addr, size_t size, KMemoryState state_mask, KMemoryState state, KMemoryPermission perm_mask, KMemoryPermission perm, KMemoryAttribute attr_mask, KMemoryAttribute attr, KMemoryAttribute ignore_attr = DefaultMemoryIgnoreAttr) const { - return this->CheckMemoryState(nullptr, addr, size, state_mask, state, perm_mask, perm, - attr_mask, attr, ignore_attr); + R_RETURN(this->CheckMemoryState(nullptr, addr, size, state_mask, state, perm_mask, perm, + attr_mask, attr, ignore_attr)); } Result LockMemoryAndOpen(KPageGroup* out_pg, PAddr* out_paddr, VAddr addr, size_t size, diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp index 1a0aec56a7..8c3495e5a5 100644 --- a/src/core/hle/kernel/k_process.cpp +++ b/src/core/hle/kernel/k_process.cpp @@ -98,7 +98,7 @@ Result KProcess::Initialize(KProcess* process, Core::System& system, std::string // Open a reference to the resource limit. process->resource_limit->Open(); - return ResultSuccess; + R_SUCCEED(); } void KProcess::DoWorkerTaskImpl() { @@ -246,7 +246,7 @@ Result KProcess::AddSharedMemory(KSharedMemory* shmem, [[maybe_unused]] VAddr ad shmem->Open(); shemen_info->Open(); - return ResultSuccess; + R_SUCCEED(); } void KProcess::RemoveSharedMemory(KSharedMemory* shmem, [[maybe_unused]] VAddr address, @@ -296,7 +296,7 @@ Result KProcess::Reset() { // Clear signaled. is_signaled = false; - return ResultSuccess; + R_SUCCEED(); } Result KProcess::SetActivity(ProcessActivity activity) { @@ -312,9 +312,7 @@ Result KProcess::SetActivity(ProcessActivity activity) { // Either pause or resume. if (activity == ProcessActivity::Paused) { // Verify that we're not suspended. - if (is_suspended) { - return ResultInvalidState; - } + R_UNLESS(!is_suspended, ResultInvalidState); // Suspend all threads. for (auto* thread : GetThreadList()) { @@ -327,9 +325,7 @@ Result KProcess::SetActivity(ProcessActivity activity) { ASSERT(activity == ProcessActivity::Runnable); // Verify that we're suspended. - if (!is_suspended) { - return ResultInvalidState; - } + R_UNLESS(is_suspended, ResultInvalidState); // Resume all threads. for (auto* thread : GetThreadList()) { @@ -340,7 +336,7 @@ Result KProcess::SetActivity(ProcessActivity activity) { SetSuspended(false); } - return ResultSuccess; + R_SUCCEED(); } Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size) { @@ -358,14 +354,14 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std: if (!memory_reservation.Succeeded()) { LOG_ERROR(Kernel, "Could not reserve process memory requirements of size {:X} bytes", code_size + system_resource_size); - return ResultLimitReached; + R_RETURN(ResultLimitReached); } // Initialize proces address space if (const Result result{page_table.InitializeForProcess( metadata.GetAddressSpaceType(), false, 0x8000000, code_size, &kernel.GetApplicationMemoryBlockManager(), KMemoryManager::Pool::Application)}; result.IsError()) { - return result; + R_RETURN(result); } // Map process code region @@ -373,7 +369,7 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std: code_size / PageSize, KMemoryState::Code, KMemoryPermission::None)}; result.IsError()) { - return result; + R_RETURN(result); } // Initialize process capabilities @@ -381,7 +377,7 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std: if (const Result result{ capabilities.InitializeForUserProcess(caps.data(), caps.size(), page_table)}; result.IsError()) { - return result; + R_RETURN(result); } // Set memory usage capacity @@ -405,7 +401,7 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std: R_TRY(this->CreateThreadLocalRegion(std::addressof(plr_address))); memory_reservation.Commit(); - return handle_table.Initialize(capabilities.GetHandleTableSize()); + R_RETURN(handle_table.Initialize(capabilities.GetHandleTableSize())); } void KProcess::Run(s32 main_thread_priority, u64 stack_size) { @@ -504,7 +500,7 @@ Result KProcess::CreateThreadLocalRegion(VAddr* out) { } *out = tlr; - return ResultSuccess; + R_SUCCEED(); } } @@ -533,7 +529,7 @@ Result KProcess::CreateThreadLocalRegion(VAddr* out) { // We succeeded! tlp_guard.Cancel(); *out = tlr; - return ResultSuccess; + R_SUCCEED(); } Result KProcess::DeleteThreadLocalRegion(VAddr addr) { @@ -581,7 +577,7 @@ Result KProcess::DeleteThreadLocalRegion(VAddr addr) { KThreadLocalPage::Free(kernel, page_to_free); } - return ResultSuccess; + R_SUCCEED(); } bool KProcess::InsertWatchpoint(Core::System& system, VAddr addr, u64 size, @@ -682,15 +678,7 @@ Result KProcess::AllocateMainThreadStack(std::size_t stack_size) { main_thread_stack_top += main_thread_stack_size; - return ResultSuccess; -} - -void KProcess::FinalizeHandleTable() { - // Finalize the table. - handle_table.Finalize(); - - // Note that the table is finalized. - is_handle_table_initialized = false; + R_SUCCEED(); } } // namespace Kernel diff --git a/src/core/hle/kernel/k_process.h b/src/core/hle/kernel/k_process.h index fcc2897f99..788faec1d5 100644 --- a/src/core/hle/kernel/k_process.h +++ b/src/core/hle/kernel/k_process.h @@ -138,16 +138,16 @@ public: } Result WaitConditionVariable(VAddr address, u64 cv_key, u32 tag, s64 ns) { - return condition_var.Wait(address, cv_key, tag, ns); + R_RETURN(condition_var.Wait(address, cv_key, tag, ns)); } Result SignalAddressArbiter(VAddr address, Svc::SignalType signal_type, s32 value, s32 count) { - return address_arbiter.SignalToAddress(address, signal_type, value, count); + R_RETURN(address_arbiter.SignalToAddress(address, signal_type, value, count)); } Result WaitAddressArbiter(VAddr address, Svc::ArbitrationType arb_type, s32 value, s64 timeout) { - return address_arbiter.WaitForAddress(address, arb_type, value, timeout); + R_RETURN(address_arbiter.WaitForAddress(address, arb_type, value, timeout)); } VAddr GetProcessLocalRegionAddress() const { @@ -407,13 +407,19 @@ private: pinned_threads[core_id] = nullptr; } + void FinalizeHandleTable() { + // Finalize the table. + handle_table.Finalize(); + + // Note that the table is finalized. + is_handle_table_initialized = false; + } + void ChangeState(State new_state); /// Allocates the main thread stack for the process, given the stack size in bytes. Result AllocateMainThreadStack(std::size_t stack_size); - void FinalizeHandleTable(); - /// Memory manager for this process KPageTable page_table; diff --git a/src/core/hle/kernel/k_thread.cpp b/src/core/hle/kernel/k_thread.cpp index 89b32d509e..b7bfcdce31 100644 --- a/src/core/hle/kernel/k_thread.cpp +++ b/src/core/hle/kernel/k_thread.cpp @@ -245,7 +245,7 @@ Result KThread::Initialize(KThreadFunction func, uintptr_t arg, VAddr user_stack } } - return ResultSuccess; + R_SUCCEED(); } Result KThread::InitializeThread(KThread* thread, KThreadFunction func, uintptr_t arg, @@ -258,7 +258,7 @@ Result KThread::InitializeThread(KThread* thread, KThreadFunction func, uintptr_ thread->host_context = std::make_shared(std::move(init_func)); thread->is_single_core = !Settings::values.use_multi_core.GetValue(); - return ResultSuccess; + R_SUCCEED(); } Result KThread::InitializeDummyThread(KThread* thread) { @@ -268,31 +268,32 @@ Result KThread::InitializeDummyThread(KThread* thread) { // Initialize emulation parameters. thread->stack_parameters.disable_count = 0; - return ResultSuccess; + R_SUCCEED(); } Result KThread::InitializeMainThread(Core::System& system, KThread* thread, s32 virt_core) { - return InitializeThread(thread, {}, {}, {}, IdleThreadPriority, virt_core, {}, ThreadType::Main, - system.GetCpuManager().GetGuestActivateFunc()); + R_RETURN(InitializeThread(thread, {}, {}, {}, IdleThreadPriority, virt_core, {}, + ThreadType::Main, system.GetCpuManager().GetGuestActivateFunc())); } Result KThread::InitializeIdleThread(Core::System& system, KThread* thread, s32 virt_core) { - return InitializeThread(thread, {}, {}, {}, IdleThreadPriority, virt_core, {}, ThreadType::Main, - system.GetCpuManager().GetIdleThreadStartFunc()); + R_RETURN(InitializeThread(thread, {}, {}, {}, IdleThreadPriority, virt_core, {}, + ThreadType::Main, system.GetCpuManager().GetIdleThreadStartFunc())); } Result KThread::InitializeHighPriorityThread(Core::System& system, KThread* thread, KThreadFunction func, uintptr_t arg, s32 virt_core) { - return InitializeThread(thread, func, arg, {}, {}, virt_core, nullptr, ThreadType::HighPriority, - system.GetCpuManager().GetShutdownThreadStartFunc()); + R_RETURN(InitializeThread(thread, func, arg, {}, {}, virt_core, nullptr, + ThreadType::HighPriority, + system.GetCpuManager().GetShutdownThreadStartFunc())); } Result KThread::InitializeUserThread(Core::System& system, KThread* thread, KThreadFunction func, uintptr_t arg, VAddr user_stack_top, s32 prio, s32 virt_core, KProcess* owner) { system.Kernel().GlobalSchedulerContext().AddThread(thread); - return InitializeThread(thread, func, arg, user_stack_top, prio, virt_core, owner, - ThreadType::User, system.GetCpuManager().GetGuestThreadFunc()); + R_RETURN(InitializeThread(thread, func, arg, user_stack_top, prio, virt_core, owner, + ThreadType::User, system.GetCpuManager().GetGuestThreadFunc())); } void KThread::PostDestroy(uintptr_t arg) { @@ -542,7 +543,7 @@ Result KThread::GetCoreMask(s32* out_ideal_core, u64* out_affinity_mask) { *out_ideal_core = virtual_ideal_core_id; *out_affinity_mask = virtual_affinity_mask; - return ResultSuccess; + R_SUCCEED(); } Result KThread::GetPhysicalCoreMask(s32* out_ideal_core, u64* out_affinity_mask) { @@ -558,7 +559,7 @@ Result KThread::GetPhysicalCoreMask(s32* out_ideal_core, u64* out_affinity_mask) *out_affinity_mask = original_physical_affinity_mask.GetAffinityMask(); } - return ResultSuccess; + R_SUCCEED(); } Result KThread::SetCoreMask(s32 core_id_, u64 v_affinity_mask) { @@ -670,7 +671,7 @@ Result KThread::SetCoreMask(s32 core_id_, u64 v_affinity_mask) { } while (retry_update); } - return ResultSuccess; + R_SUCCEED(); } void KThread::SetBasePriority(s32 value) { @@ -843,7 +844,7 @@ Result KThread::SetActivity(Svc::ThreadActivity activity) { } while (thread_is_current); } - return ResultSuccess; + R_SUCCEED(); } Result KThread::GetThreadContext3(std::vector& out) { @@ -878,7 +879,7 @@ Result KThread::GetThreadContext3(std::vector& out) { } } - return ResultSuccess; + R_SUCCEED(); } void KThread::AddWaiterImpl(KThread* thread) { @@ -1042,7 +1043,7 @@ Result KThread::Run() { // Set our state and finish. SetState(ThreadState::Runnable); - return ResultSuccess; + R_SUCCEED(); } } @@ -1089,7 +1090,7 @@ Result KThread::Terminate() { Svc::WaitInfinite)); } - return ResultSuccess; + R_SUCCEED(); } ThreadState KThread::RequestTerminate() { @@ -1162,7 +1163,7 @@ Result KThread::Sleep(s64 timeout) { // Check if the thread should terminate. if (this->IsTerminationRequested()) { slp.CancelSleep(); - return ResultTerminationRequested; + R_THROW(ResultTerminationRequested); } // Wait for the sleep to end. @@ -1170,7 +1171,7 @@ Result KThread::Sleep(s64 timeout) { SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::Sleep); } - return ResultSuccess; + R_SUCCEED(); } void KThread::IfDummyThreadTryWait() { diff --git a/src/core/hle/result.h b/src/core/hle/result.h index d714dea38e..ef4b2d4173 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -470,9 +470,6 @@ constexpr inline Result __TmpCurrentResultReference = ResultSuccess; #define R_UNLESS(expr, res) \ { \ if (!(expr)) { \ - if (res.IsError()) { \ - LOG_ERROR(Kernel, "Failed with result: {}", res.raw); \ - } \ R_THROW(res); \ } \ } From 11f85ea7130a5245bd8d2090f0dd76ba65f15d23 Mon Sep 17 00:00:00 2001 From: bunnei Date: Fri, 14 Oct 2022 23:37:02 -0700 Subject: [PATCH 39/91] core: core_timing: Remove unused IsHostTiming. --- src/core/core_timing.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/core/core_timing.h b/src/core/core_timing.h index 7996b529fe..bd21dd904c 100644 --- a/src/core/core_timing.h +++ b/src/core/core_timing.h @@ -69,11 +69,6 @@ public: is_multicore = is_multicore_; } - /// Check if it's using host timing. - bool IsHostTiming() const { - return is_multicore; - } - /// Pauses/Unpauses the execution of the timer thread. void Pause(bool is_paused); From 638fa6170a8a4c36ffa644055e683a7e50aa7ae5 Mon Sep 17 00:00:00 2001 From: bunnei Date: Sat, 15 Oct 2022 00:48:28 -0700 Subject: [PATCH 40/91] core: core_timing: Re-initialize if single/multicore state changes. --- src/core/core.cpp | 25 ++++++++++++++++++++----- src/core/core_timing.cpp | 23 ++++++++++++++--------- src/core/core_timing.h | 2 ++ 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/src/core/core.cpp b/src/core/core.cpp index 2c4c0dbe40..622a20510c 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -155,6 +155,24 @@ struct System::Impl { // Create default implementations of applets if one is not provided. applet_manager.SetDefaultAppletsIfMissing(); + + is_async_gpu = Settings::values.use_asynchronous_gpu_emulation.GetValue(); + + kernel.SetMulticore(is_multicore); + cpu_manager.SetMulticore(is_multicore); + cpu_manager.SetAsyncGpu(is_async_gpu); + } + + void ReinitializeIfNecessary(System& system) { + if (is_multicore == Settings::values.use_multi_core.GetValue()) { + return; + } + + LOG_DEBUG(Kernel, "Re-initializing"); + + is_multicore = Settings::values.use_multi_core.GetValue(); + + Initialize(system); } SystemResultStatus Run() { @@ -205,11 +223,8 @@ struct System::Impl { SystemResultStatus SetupForMainProcess(System& system, Frontend::EmuWindow& emu_window) { LOG_DEBUG(Core, "initialized OK"); - is_async_gpu = Settings::values.use_asynchronous_gpu_emulation.GetValue(); - - kernel.SetMulticore(is_multicore); - cpu_manager.SetMulticore(is_multicore); - cpu_manager.SetAsyncGpu(is_async_gpu); + // Setting changes may require a full system reinitialization (e.g., disabling multicore). + ReinitializeIfNecessary(system); kernel.Initialize(); cpu_manager.Initialize(); diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index 2afb2696cc..0e7b5f9436 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -41,15 +41,7 @@ CoreTiming::CoreTiming() : clock{Common::CreateBestMatchingClock(Hardware::BASE_CLOCK_RATE, Hardware::CNTFREQ)} {} CoreTiming::~CoreTiming() { - paused = true; - shutting_down = true; - pause_event.Set(); - event.Set(); - if (timer_thread) { - timer_thread->join(); - } - timer_thread.reset(); - has_started = false; + Reset(); } void CoreTiming::ThreadEntry(CoreTiming& instance) { @@ -63,6 +55,7 @@ void CoreTiming::ThreadEntry(CoreTiming& instance) { } void CoreTiming::Initialize(std::function&& on_thread_init_) { + Reset(); on_thread_init = std::move(on_thread_init_); event_fifo_id = 0; shutting_down = false; @@ -304,6 +297,18 @@ void CoreTiming::ThreadLoop() { } } +void CoreTiming::Reset() { + paused = true; + shutting_down = true; + pause_event.Set(); + event.Set(); + if (timer_thread) { + timer_thread->join(); + } + timer_thread.reset(); + has_started = false; +} + std::chrono::nanoseconds CoreTiming::GetGlobalTimeNs() const { if (is_multicore) { return clock->GetTimeNS(); diff --git a/src/core/core_timing.h b/src/core/core_timing.h index bd21dd904c..b5925193c7 100644 --- a/src/core/core_timing.h +++ b/src/core/core_timing.h @@ -134,6 +134,8 @@ private: static void ThreadEntry(CoreTiming& instance); void ThreadLoop(); + void Reset(); + std::unique_ptr clock; s64 global_timer = 0; From a264b54022d97824a5889c711f4977bc4ecdbca3 Mon Sep 17 00:00:00 2001 From: bunnei Date: Tue, 18 Oct 2022 19:12:18 -0700 Subject: [PATCH 41/91] core: Initialize: Add missing braces. --- src/core/core.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/core/core.cpp b/src/core/core.cpp index 622a20510c..7fb8bc0195 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -148,10 +148,12 @@ struct System::Impl { Settings::values.custom_rtc.value_or(current_time) - current_time; // Create a default fs if one doesn't already exist. - if (virtual_filesystem == nullptr) + if (virtual_filesystem == nullptr) { virtual_filesystem = std::make_shared(); - if (content_provider == nullptr) + } + if (content_provider == nullptr) { content_provider = std::make_unique(); + } // Create default implementations of applets if one is not provided. applet_manager.SetDefaultAppletsIfMissing(); From 3cb44981420fb7d493e2b1ff9ee1e5670fae2486 Mon Sep 17 00:00:00 2001 From: Fernando Sahmkow Date: Wed, 19 Oct 2022 06:21:51 +0200 Subject: [PATCH 42/91] Maxwell3D/Puller: Fix regressions and syncing issues. --- src/video_core/engines/maxwell_3d.cpp | 17 +++++++---------- src/video_core/engines/puller.cpp | 5 ++--- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index 84c1abf3d0..fdf470913e 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -473,9 +473,7 @@ void Maxwell3D::ProcessQueryGet() { switch (regs.report_semaphore.query.operation) { case Regs::ReportSemaphore::Operation::Release: - if (regs.report_semaphore.query.release == - Regs::ReportSemaphore::Release::AfterAllPreceedingWrites || - regs.report_semaphore.query.short_query != 0) { + if (regs.report_semaphore.query.short_query != 0) { const GPUVAddr sequence_address{regs.report_semaphore.Address()}; const u32 payload = regs.report_semaphore.payload; std::function operation([this, sequence_address, payload] { @@ -489,11 +487,10 @@ void Maxwell3D::ProcessQueryGet() { }; const GPUVAddr sequence_address{regs.report_semaphore.Address()}; const u32 payload = regs.report_semaphore.payload; - std::function operation([this, sequence_address, payload] { + [this, sequence_address, payload] { memory_manager.Write(sequence_address + sizeof(u64), system.GPU().GetTicks()); memory_manager.Write(sequence_address, payload); - }); - rasterizer->SyncOperation(std::move(operation)); + }(); } break; case Regs::ReportSemaphore::Operation::Acquire: @@ -569,11 +566,11 @@ void Maxwell3D::ProcessCounterReset() { void Maxwell3D::ProcessSyncPoint() { const u32 sync_point = regs.sync_info.sync_point.Value(); - const auto condition = regs.sync_info.condition.Value(); - [[maybe_unused]] const u32 cache_flush = regs.sync_info.clean_l2.Value(); - if (condition == Regs::SyncInfo::Condition::RopWritesDone) { - rasterizer->SignalSyncPoint(sync_point); + const u32 cache_flush = regs.sync_info.clean_l2.Value(); + if (cache_flush != 0) { + rasterizer->InvalidateGPUCache(); } + rasterizer->SignalSyncPoint(sync_point); } void Maxwell3D::DrawArrays() { diff --git a/src/video_core/engines/puller.cpp b/src/video_core/engines/puller.cpp index cca890792a..3977bb0fb0 100644 --- a/src/video_core/engines/puller.cpp +++ b/src/video_core/engines/puller.cpp @@ -75,11 +75,10 @@ void Puller::ProcessSemaphoreTriggerMethod() { if (op == GpuSemaphoreOperation::WriteLong) { const GPUVAddr sequence_address{regs.semaphore_address.SemaphoreAddress()}; const u32 payload = regs.semaphore_sequence; - std::function operation([this, sequence_address, payload] { + [this, sequence_address, payload] { memory_manager.Write(sequence_address + sizeof(u64), gpu.GetTicks()); memory_manager.Write(sequence_address, payload); - }); - rasterizer->SignalFence(std::move(operation)); + }(); } else { do { const u32 word{memory_manager.Read(regs.semaphore_address.SemaphoreAddress())}; From 7bd3930939dfafc463b87b6df965b8b4391f1f56 Mon Sep 17 00:00:00 2001 From: Kelebek1 Date: Wed, 19 Oct 2022 05:38:12 +0100 Subject: [PATCH 43/91] Update audio_core for firmware 15.0.0 --- src/audio_core/renderer/system.cpp | 85 ++++++++++++------- src/audio_core/renderer/system.h | 16 ++++ .../renderer/voice/voice_context.cpp | 4 +- src/core/hle/service/audio/audctl.cpp | 16 ++++ src/core/hle/service/audio/audren_u.cpp | 26 ++++++ 5 files changed, 114 insertions(+), 33 deletions(-) diff --git a/src/audio_core/renderer/system.cpp b/src/audio_core/renderer/system.cpp index bde794cd1c..4fac30c7c8 100644 --- a/src/audio_core/renderer/system.cpp +++ b/src/audio_core/renderer/system.cpp @@ -98,9 +98,8 @@ System::System(Core::System& core_, Kernel::KEvent* adsp_rendered_event_) : core{core_}, adsp{core.AudioCore().GetADSP()}, adsp_rendered_event{adsp_rendered_event_} {} Result System::Initialize(const AudioRendererParameterInternal& params, - Kernel::KTransferMemory* transfer_memory, const u64 transfer_memory_size, - const u32 process_handle_, const u64 applet_resource_user_id_, - const s32 session_id_) { + Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size, + u32 process_handle_, u64 applet_resource_user_id_, s32 session_id_) { if (!CheckValidRevision(params.revision)) { return Service::Audio::ERR_INVALID_REVISION; } @@ -354,6 +353,8 @@ Result System::Initialize(const AudioRendererParameterInternal& params, render_time_limit_percent = 100; drop_voice = params.voice_drop_enabled && params.execution_mode == ExecutionMode::Auto; + drop_voice_param = 1.0f; + num_voices_dropped = 0; allocator.Align(0x40); command_workbuffer_size = allocator.GetRemainingSize(); @@ -547,7 +548,7 @@ u32 System::GetRenderingTimeLimit() const { return render_time_limit_percent; } -void System::SetRenderingTimeLimit(const u32 limit) { +void System::SetRenderingTimeLimit(u32 limit) { render_time_limit_percent = limit; } @@ -635,7 +636,7 @@ void System::SendCommandToDsp() { } u64 System::GenerateCommand(std::span in_command_buffer, - [[maybe_unused]] const u64 command_buffer_size_) { + [[maybe_unused]] u64 command_buffer_size_) { PoolMapper::ClearUseState(memory_pool_workbuffer, memory_pool_count); const auto start_time{core.CoreTiming().GetClockTicks()}; @@ -693,7 +694,8 @@ u64 System::GenerateCommand(std::span in_command_buffer, voice_context.SortInfo(); - const auto start_estimated_time{command_buffer.estimated_process_time}; + const auto start_estimated_time{drop_voice_param * + static_cast(command_buffer.estimated_process_time)}; command_generator.GenerateVoiceCommands(); command_generator.GenerateSubMixCommands(); @@ -712,11 +714,16 @@ u64 System::GenerateCommand(std::span in_command_buffer, render_context.behavior->IsAudioRendererProcessingTimeLimit70PercentSupported(); time_limit_percent = 70.0f; } + + const auto end_estimated_time{drop_voice_param * + static_cast(command_buffer.estimated_process_time)}; + const auto estimated_time{start_estimated_time - end_estimated_time}; + const auto time_limit{static_cast( - static_cast(start_estimated_time - command_buffer.estimated_process_time) + - (((time_limit_percent / 100.0f) * 2'880'000.0) * - (static_cast(render_time_limit_percent) / 100.0f)))}; - num_voices_dropped = DropVoices(command_buffer, start_estimated_time, time_limit); + estimated_time + (((time_limit_percent / 100.0f) * 2'880'000.0) * + (static_cast(render_time_limit_percent) / 100.0f)))}; + num_voices_dropped = + DropVoices(command_buffer, static_cast(start_estimated_time), time_limit); } command_list_header->buffer_size = command_buffer.size; @@ -737,24 +744,33 @@ u64 System::GenerateCommand(std::span in_command_buffer, return command_buffer.size; } -u32 System::DropVoices(CommandBuffer& command_buffer, const u32 estimated_process_time, - const u32 time_limit) { +f32 System::GetVoiceDropParameter() const { + return drop_voice_param; +} + +void System::SetVoiceDropParameter(f32 voice_drop_) { + drop_voice_param = voice_drop_; +} + +u32 System::DropVoices(CommandBuffer& command_buffer, u32 estimated_process_time, u32 time_limit) { u32 i{0}; auto command_list{command_buffer.command_list.data() + sizeof(CommandListHeader)}; - ICommand* cmd{}; + ICommand* cmd{nullptr}; - for (; i < command_buffer.count; i++) { + // Find a first valid voice to drop + while (i < command_buffer.count) { cmd = reinterpret_cast(command_list); - if (cmd->type != CommandId::Performance && - cmd->type != CommandId::DataSourcePcmInt16Version1 && - cmd->type != CommandId::DataSourcePcmInt16Version2 && - cmd->type != CommandId::DataSourcePcmFloatVersion1 && - cmd->type != CommandId::DataSourcePcmFloatVersion2 && - cmd->type != CommandId::DataSourceAdpcmVersion1 && - cmd->type != CommandId::DataSourceAdpcmVersion2) { + if (cmd->type == CommandId::Performance || + cmd->type == CommandId::DataSourcePcmInt16Version1 || + cmd->type == CommandId::DataSourcePcmInt16Version2 || + cmd->type == CommandId::DataSourcePcmFloatVersion1 || + cmd->type == CommandId::DataSourcePcmFloatVersion2 || + cmd->type == CommandId::DataSourceAdpcmVersion1 || + cmd->type == CommandId::DataSourceAdpcmVersion2) { break; } command_list += cmd->size; + i++; } if (cmd == nullptr || command_buffer.count == 0 || i >= command_buffer.count) { @@ -767,6 +783,7 @@ u32 System::DropVoices(CommandBuffer& command_buffer, const u32 estimated_proces const auto node_id_type{cmd->node_id >> 28}; const auto node_id_base{cmd->node_id & 0xFFF}; + // If the new estimated process time falls below the limit, we're done dropping. if (estimated_process_time <= time_limit) { break; } @@ -775,6 +792,7 @@ u32 System::DropVoices(CommandBuffer& command_buffer, const u32 estimated_proces break; } + // Don't drop voices marked with the highest priority. auto& voice_info{voice_context.GetInfo(node_id_base)}; if (voice_info.priority == HighestVoicePriority) { break; @@ -783,18 +801,23 @@ u32 System::DropVoices(CommandBuffer& command_buffer, const u32 estimated_proces voices_dropped++; voice_info.voice_dropped = true; - if (i < command_buffer.count) { - while (cmd->node_id == node_id) { - if (cmd->type == CommandId::DepopPrepare) { - cmd->enabled = true; - } else if (cmd->type == CommandId::Performance || !cmd->enabled) { - cmd->enabled = false; - } - i++; - command_list += cmd->size; - cmd = reinterpret_cast(command_list); + // First iteration should drop the voice, and then iterate through all of the commands tied + // to the voice. We don't need reverb on a voice which we've just removed, for example. + // Depops can't be removed otherwise we'll introduce audio popping, and we don't + // remove perf commands. Lower the estimated time for each command dropped. + while (i < command_buffer.count && cmd->node_id == node_id) { + if (cmd->type == CommandId::DepopPrepare) { + cmd->enabled = true; + } else if (cmd->enabled && cmd->type != CommandId::Performance) { + cmd->enabled = false; + estimated_process_time -= static_cast( + drop_voice_param * static_cast(cmd->estimated_process_time)); } + command_list += cmd->size; + cmd = reinterpret_cast(command_list); + i++; } + i++; } return voices_dropped; } diff --git a/src/audio_core/renderer/system.h b/src/audio_core/renderer/system.h index bcbe65b070..429196e41d 100644 --- a/src/audio_core/renderer/system.h +++ b/src/audio_core/renderer/system.h @@ -196,6 +196,20 @@ public: */ u32 DropVoices(CommandBuffer& command_buffer, u32 estimated_process_time, u32 time_limit); + /** + * Get the current voice drop parameter. + * + * @return The current voice drop. + */ + f32 GetVoiceDropParameter() const; + + /** + * Set the voice drop parameter. + * + * @param The new voice drop. + */ + void SetVoiceDropParameter(f32 voice_drop); + private: /// Core system Core::System& core; @@ -301,6 +315,8 @@ private: u32 num_voices_dropped{}; /// Tick that rendering started u64 render_start_tick{}; + /// Parameter to control the threshold for dropping voices if the audio graph gets too large + f32 drop_voice_param{1.0f}; }; } // namespace AudioRenderer diff --git a/src/audio_core/renderer/voice/voice_context.cpp b/src/audio_core/renderer/voice/voice_context.cpp index eafb51b011..a501a677d8 100644 --- a/src/audio_core/renderer/voice/voice_context.cpp +++ b/src/audio_core/renderer/voice/voice_context.cpp @@ -74,8 +74,8 @@ void VoiceContext::SortInfo() { } std::ranges::sort(sorted_voice_info, [](const VoiceInfo* a, const VoiceInfo* b) { - return a->priority != b->priority ? a->priority < b->priority - : a->sort_order < b->sort_order; + return a->priority != b->priority ? a->priority > b->priority + : a->sort_order > b->sort_order; }); } diff --git a/src/core/hle/service/audio/audctl.cpp b/src/core/hle/service/audio/audctl.cpp index 4a2ae5f88a..5abf22ba49 100644 --- a/src/core/hle/service/audio/audctl.cpp +++ b/src/core/hle/service/audio/audctl.cpp @@ -45,9 +45,25 @@ AudCtl::AudCtl(Core::System& system_) : ServiceFramework{system_, "audctl"} { {32, nullptr, "GetActiveOutputTarget"}, {33, nullptr, "GetTargetDeviceInfo"}, {34, nullptr, "AcquireTargetNotification"}, + {35, nullptr, "SetHearingProtectionSafeguardTimerRemainingTimeForDebug"}, + {36, nullptr, "GetHearingProtectionSafeguardTimerRemainingTimeForDebug"}, + {37, nullptr, "SetHearingProtectionSafeguardEnabled"}, + {38, nullptr, "IsHearingProtectionSafeguardEnabled"}, + {39, nullptr, "IsHearingProtectionSafeguardMonitoringOutputForDebug"}, + {40, nullptr, "GetSystemInformationForDebug"}, + {41, nullptr, "SetVolumeButtonLongPressTime"}, + {42, nullptr, "SetNativeVolumeForDebug"}, {10000, nullptr, "NotifyAudioOutputTargetForPlayReport"}, {10001, nullptr, "NotifyAudioOutputChannelCountForPlayReport"}, {10002, nullptr, "NotifyUnsupportedUsbOutputDeviceAttachedForPlayReport"}, + {10100, nullptr, "GetAudioVolumeDataForPlayReport"}, + {10101, nullptr, "BindAudioVolumeUpdateEventForPlayReport"}, + {10102, nullptr, "BindAudioOutputTargetUpdateEventForPlayReport"}, + {10103, nullptr, "GetAudioOutputTargetForPlayReport"}, + {10104, nullptr, "GetAudioOutputChannelCountForPlayReport"}, + {10105, nullptr, "BindAudioOutputChannelCountUpdateEventForPlayReport"}, + {10106, nullptr, "GetDefaultAudioOutputTargetForPlayReport"}, + {50000, nullptr, "SetAnalogInputBoostGainForPrototyping"}, }; // clang-format on diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp index 60c30cd5bc..13423dca67 100644 --- a/src/core/hle/service/audio/audren_u.cpp +++ b/src/core/hle/service/audio/audren_u.cpp @@ -52,6 +52,8 @@ public: {9, &IAudioRenderer::GetRenderingTimeLimit, "GetRenderingTimeLimit"}, {10, &IAudioRenderer::RequestUpdate, "RequestUpdateAuto"}, {11, nullptr, "ExecuteAudioRendererRendering"}, + {12, &IAudioRenderer::SetVoiceDropParameter, "SetVoiceDropParameter"}, + {13, &IAudioRenderer::GetVoiceDropParameter, "GetVoiceDropParameter"}, }; // clang-format on RegisterHandlers(functions); @@ -205,6 +207,30 @@ private: LOG_DEBUG(Service_Audio, "called"); } + void SetVoiceDropParameter(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_Audio, "called"); + + IPC::RequestParser rp{ctx}; + auto voice_drop_param{rp.Pop()}; + + auto& system_ = impl->GetSystem(); + system_.SetVoiceDropParameter(voice_drop_param); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void GetVoiceDropParameter(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_Audio, "called"); + + auto& system_ = impl->GetSystem(); + auto voice_drop_param{system_.GetVoiceDropParameter()}; + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(voice_drop_param); + } + KernelHelpers::ServiceContext service_context; Kernel::KEvent* rendered_event; Manager& manager; From 470e89a8edb73f6710ce652e2ecfe25049307109 Mon Sep 17 00:00:00 2001 From: Kyle Kienapfel Date: Wed, 19 Oct 2022 03:51:51 -0700 Subject: [PATCH 44/91] UI: Add option to hide the compatibility list Option is added directly below the option for the addons column Defaulting to hide compatibility list. Changing default works properly. Co-authored-by: Piplup --- src/yuzu/configuration/config.cpp | 2 ++ src/yuzu/configuration/configure_ui.cpp | 3 +++ src/yuzu/configuration/configure_ui.ui | 7 +++++++ src/yuzu/game_list.cpp | 2 ++ src/yuzu/uisettings.h | 3 +++ 5 files changed, 17 insertions(+) diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 195074bf25..927dd1069e 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -819,6 +819,7 @@ void Config::ReadUIGamelistValues() { qt_config->beginGroup(QStringLiteral("UIGameList")); ReadBasicSetting(UISettings::values.show_add_ons); + ReadBasicSetting(UISettings::values.show_compat); ReadBasicSetting(UISettings::values.game_icon_size); ReadBasicSetting(UISettings::values.folder_icon_size); ReadBasicSetting(UISettings::values.row_1_text_id); @@ -1414,6 +1415,7 @@ void Config::SaveUIGamelistValues() { qt_config->beginGroup(QStringLiteral("UIGameList")); WriteBasicSetting(UISettings::values.show_add_ons); + WriteBasicSetting(UISettings::values.show_compat); WriteBasicSetting(UISettings::values.game_icon_size); WriteBasicSetting(UISettings::values.folder_icon_size); WriteBasicSetting(UISettings::values.row_1_text_id); diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp index 48f71b53c6..92e6da6ee8 100644 --- a/src/yuzu/configuration/configure_ui.cpp +++ b/src/yuzu/configuration/configure_ui.cpp @@ -72,6 +72,7 @@ ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent) // Force game list reload if any of the relevant settings are changed. connect(ui->show_add_ons, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate); + connect(ui->show_compat, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate); connect(ui->game_icon_size_combobox, QOverload::of(&QComboBox::currentIndexChanged), this, &ConfigureUi::RequestGameListUpdate); connect(ui->folder_icon_size_combobox, QOverload::of(&QComboBox::currentIndexChanged), @@ -109,6 +110,7 @@ void ConfigureUi::ApplyConfiguration() { UISettings::values.theme = ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString(); UISettings::values.show_add_ons = ui->show_add_ons->isChecked(); + UISettings::values.show_compat = ui->show_compat->isChecked(); UISettings::values.game_icon_size = ui->game_icon_size_combobox->currentData().toUInt(); UISettings::values.folder_icon_size = ui->folder_icon_size_combobox->currentData().toUInt(); UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt(); @@ -129,6 +131,7 @@ void ConfigureUi::SetConfiguration() { ui->language_combobox->setCurrentIndex( ui->language_combobox->findData(UISettings::values.language)); ui->show_add_ons->setChecked(UISettings::values.show_add_ons.GetValue()); + ui->show_compat->setChecked(UISettings::values.show_compat.GetValue()); ui->game_icon_size_combobox->setCurrentIndex( ui->game_icon_size_combobox->findData(UISettings::values.game_icon_size.GetValue())); ui->folder_icon_size_combobox->setCurrentIndex( diff --git a/src/yuzu/configuration/configure_ui.ui b/src/yuzu/configuration/configure_ui.ui index a50df7f6f8..f0b719ba31 100644 --- a/src/yuzu/configuration/configure_ui.ui +++ b/src/yuzu/configuration/configure_ui.ui @@ -76,6 +76,13 @@ + + + + Show Compatibility List + + + diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index b127badc25..d6adfca16a 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -335,6 +335,7 @@ GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvid RetranslateUI(); tree_view->setColumnHidden(COLUMN_ADD_ONS, !UISettings::values.show_add_ons); + tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat); item_model->setSortRole(GameListItemPath::SortRole); connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::OnUpdateThemedIcons); @@ -786,6 +787,7 @@ void GameList::PopulateAsync(QVector& game_dirs) { // Update the columns in case UISettings has changed tree_view->setColumnHidden(COLUMN_ADD_ONS, !UISettings::values.show_add_ons); + tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat); // Delete any rows that might already exist if we're repopulating item_model->removeRows(0, item_model->rowCount()); diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h index 753797efc4..4f5b2a99d4 100644 --- a/src/yuzu/uisettings.h +++ b/src/yuzu/uisettings.h @@ -129,6 +129,9 @@ struct Values { Settings::Setting favorites_expanded{true, "favorites_expanded"}; QVector favorited_ids; + // Compatibility List + Settings::Setting show_compat{false, "show_compat"}; + bool configuration_applied; bool reset_to_defaults; Settings::Setting disable_web_applet{true, "disable_web_applet"}; From 97879faea43c1fad6cbb0b63573c75644705e2e9 Mon Sep 17 00:00:00 2001 From: bunnei Date: Tue, 18 Oct 2022 19:13:20 -0700 Subject: [PATCH 45/91] core: hle: kernel: Migrate ProcessState to enum class. --- src/core/hle/kernel/k_process.h | 16 ++++++++-------- src/core/hle/kernel/svc_types.h | 18 +++++++++--------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/core/hle/kernel/k_process.h b/src/core/hle/kernel/k_process.h index 788faec1d5..2e0cc3d0bc 100644 --- a/src/core/hle/kernel/k_process.h +++ b/src/core/hle/kernel/k_process.h @@ -72,14 +72,14 @@ public: ~KProcess() override; enum class State { - Created = Svc::ProcessState_Created, - CreatedAttached = Svc::ProcessState_CreatedAttached, - Running = Svc::ProcessState_Running, - Crashed = Svc::ProcessState_Crashed, - RunningAttached = Svc::ProcessState_RunningAttached, - Terminating = Svc::ProcessState_Terminating, - Terminated = Svc::ProcessState_Terminated, - DebugBreak = Svc::ProcessState_DebugBreak, + Created = static_cast(Svc::ProcessState::Created), + CreatedAttached = static_cast(Svc::ProcessState::CreatedAttached), + Running = static_cast(Svc::ProcessState::Running), + Crashed = static_cast(Svc::ProcessState::Crashed), + RunningAttached = static_cast(Svc::ProcessState::RunningAttached), + Terminating = static_cast(Svc::ProcessState::Terminating), + Terminated = static_cast(Svc::ProcessState::Terminated), + DebugBreak = static_cast(Svc::ProcessState::DebugBreak), }; enum : u64 { diff --git a/src/core/hle/kernel/svc_types.h b/src/core/hle/kernel/svc_types.h index bb4f7b004b..abb9847fe8 100644 --- a/src/core/hle/kernel/svc_types.h +++ b/src/core/hle/kernel/svc_types.h @@ -97,15 +97,15 @@ constexpr inline s32 HighestThreadPriority = 0; constexpr inline s32 SystemThreadPriorityHighest = 16; -enum ProcessState : u32 { - ProcessState_Created = 0, - ProcessState_CreatedAttached = 1, - ProcessState_Running = 2, - ProcessState_Crashed = 3, - ProcessState_RunningAttached = 4, - ProcessState_Terminating = 5, - ProcessState_Terminated = 6, - ProcessState_DebugBreak = 7, +enum class ProcessState : u32 { + Created = 0, + CreatedAttached = 1, + Running = 2, + Crashed = 3, + RunningAttached = 4, + Terminating = 5, + Terminated = 6, + DebugBreak = 7, }; constexpr inline size_t ThreadLocalRegionSize = 0x200; From 3efb8eb2dc8bf14eecb7e731a61712e0290d9f5d Mon Sep 17 00:00:00 2001 From: Liam Date: Fri, 14 Oct 2022 21:24:25 -0400 Subject: [PATCH 46/91] kernel: add KSessionRequest --- src/core/CMakeLists.txt | 2 + src/core/hle/kernel/init/init_slab_setup.cpp | 2 + src/core/hle/kernel/k_client_session.cpp | 15 +- src/core/hle/kernel/k_linked_list.h | 1 + src/core/hle/kernel/k_page_buffer.h | 1 + src/core/hle/kernel/k_server_session.cpp | 141 +++++---- src/core/hle/kernel/k_server_session.h | 8 +- src/core/hle/kernel/k_session_request.cpp | 61 ++++ src/core/hle/kernel/k_session_request.h | 307 +++++++++++++++++++ src/core/hle/kernel/k_shared_memory_info.h | 3 +- src/core/hle/kernel/k_thread_local_page.h | 2 +- src/core/hle/kernel/kernel.h | 4 + src/core/hle/kernel/slab_helpers.h | 2 +- 13 files changed, 488 insertions(+), 61 deletions(-) create mode 100644 src/core/hle/kernel/k_session_request.cpp create mode 100644 src/core/hle/kernel/k_session_request.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index e7fe675cbf..055bea6419 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -243,6 +243,8 @@ add_library(core STATIC hle/kernel/k_server_session.h hle/kernel/k_session.cpp hle/kernel/k_session.h + hle/kernel/k_session_request.cpp + hle/kernel/k_session_request.h hle/kernel/k_shared_memory.cpp hle/kernel/k_shared_memory.h hle/kernel/k_shared_memory_info.h diff --git a/src/core/hle/kernel/init/init_slab_setup.cpp b/src/core/hle/kernel/init/init_slab_setup.cpp index c84d36c8c2..477e4e4075 100644 --- a/src/core/hle/kernel/init/init_slab_setup.cpp +++ b/src/core/hle/kernel/init/init_slab_setup.cpp @@ -18,6 +18,7 @@ #include "core/hle/kernel/k_process.h" #include "core/hle/kernel/k_resource_limit.h" #include "core/hle/kernel/k_session.h" +#include "core/hle/kernel/k_session_request.h" #include "core/hle/kernel/k_shared_memory.h" #include "core/hle/kernel/k_shared_memory_info.h" #include "core/hle/kernel/k_system_control.h" @@ -34,6 +35,7 @@ namespace Kernel::Init { HANDLER(KThread, (SLAB_COUNT(KThread)), ##__VA_ARGS__) \ HANDLER(KEvent, (SLAB_COUNT(KEvent)), ##__VA_ARGS__) \ HANDLER(KPort, (SLAB_COUNT(KPort)), ##__VA_ARGS__) \ + HANDLER(KSessionRequest, (SLAB_COUNT(KSession) * 2), ##__VA_ARGS__) \ HANDLER(KSharedMemory, (SLAB_COUNT(KSharedMemory)), ##__VA_ARGS__) \ HANDLER(KSharedMemoryInfo, (SLAB_COUNT(KSharedMemory) * 8), ##__VA_ARGS__) \ HANDLER(KTransferMemory, (SLAB_COUNT(KTransferMemory)), ##__VA_ARGS__) \ diff --git a/src/core/hle/kernel/k_client_session.cpp b/src/core/hle/kernel/k_client_session.cpp index 8892c5b7cb..b4197a8d56 100644 --- a/src/core/hle/kernel/k_client_session.cpp +++ b/src/core/hle/kernel/k_client_session.cpp @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "common/scope_exit.h" #include "core/hle/kernel/hle_ipc.h" #include "core/hle/kernel/k_client_session.h" #include "core/hle/kernel/k_server_session.h" @@ -10,6 +11,8 @@ namespace Kernel { +static constexpr u32 MessageBufferSize = 0x100; + KClientSession::KClientSession(KernelCore& kernel_) : KAutoObjectWithSlabHeapAndContainer{kernel_} {} KClientSession::~KClientSession() = default; @@ -22,8 +25,16 @@ void KClientSession::Destroy() { void KClientSession::OnServerClosed() {} Result KClientSession::SendSyncRequest() { - // Signal the server session that new data is available - return parent->GetServerSession().OnRequest(); + // Create a session request. + KSessionRequest* request = KSessionRequest::Create(kernel); + R_UNLESS(request != nullptr, ResultOutOfResource); + SCOPE_EXIT({ request->Close(); }); + + // Initialize the request. + request->Initialize(nullptr, GetCurrentThread(kernel).GetTLSAddress(), MessageBufferSize); + + // Send the request. + return parent->GetServerSession().OnRequest(request); } } // namespace Kernel diff --git a/src/core/hle/kernel/k_linked_list.h b/src/core/hle/kernel/k_linked_list.h index 78859ced30..29ebd16b77 100644 --- a/src/core/hle/kernel/k_linked_list.h +++ b/src/core/hle/kernel/k_linked_list.h @@ -16,6 +16,7 @@ class KLinkedListNode : public boost::intrusive::list_base_hook<>, public KSlabAllocated { public: + explicit KLinkedListNode(KernelCore&) {} KLinkedListNode() = default; void Initialize(void* it) { diff --git a/src/core/hle/kernel/k_page_buffer.h b/src/core/hle/kernel/k_page_buffer.h index 7e50dc1d18..aef06e2130 100644 --- a/src/core/hle/kernel/k_page_buffer.h +++ b/src/core/hle/kernel/k_page_buffer.h @@ -13,6 +13,7 @@ namespace Kernel { class KPageBuffer final : public KSlabAllocated { public: + explicit KPageBuffer(KernelCore&) {} KPageBuffer() = default; static KPageBuffer* FromPhysicalAddress(Core::System& system, PAddr phys_addr); diff --git a/src/core/hle/kernel/k_server_session.cpp b/src/core/hle/kernel/k_server_session.cpp index 4252c9adbd..685a2a6e67 100644 --- a/src/core/hle/kernel/k_server_session.cpp +++ b/src/core/hle/kernel/k_server_session.cpp @@ -29,8 +29,6 @@ namespace Kernel { using ThreadQueueImplForKServerSessionRequest = KThreadQueue; -static constexpr u32 MessageBufferSize = 0x100; - KServerSession::KServerSession(KernelCore& kernel_) : KSynchronizationObject{kernel_}, m_lock{kernel_} {} @@ -73,7 +71,7 @@ bool KServerSession::IsSignaled() const { } // Otherwise, we're signaled if we have a request and aren't handling one. - return !m_thread_request_list.empty() && m_current_thread_request == nullptr; + return !m_request_list.empty() && m_current_request == nullptr; } void KServerSession::AppendDomainHandler(SessionRequestHandlerPtr handler) { @@ -178,7 +176,7 @@ Result KServerSession::CompleteSyncRequest(HLERequestContext& context) { return result; } -Result KServerSession::OnRequest() { +Result KServerSession::OnRequest(KSessionRequest* request) { // Create the wait queue. ThreadQueueImplForKServerSessionRequest wait_queue{kernel}; @@ -198,14 +196,13 @@ Result KServerSession::OnRequest() { this->QueueSyncRequest(GetCurrentThreadPointer(kernel), memory); } else { // Non-HLE request. - auto* thread{GetCurrentThreadPointer(kernel)}; // Get whether we're empty. - const bool was_empty = m_thread_request_list.empty(); + const bool was_empty = m_request_list.empty(); - // Add the thread to the list. - thread->Open(); - m_thread_request_list.push_back(thread); + // Add the request to the list. + request->Open(); + m_request_list.push_back(*request); // If we were empty, signal. if (was_empty) { @@ -213,6 +210,9 @@ Result KServerSession::OnRequest() { } } + // If we have a request event, this is asynchronous, and we don't need to wait. + R_SUCCEED_IF(request->GetEvent() != nullptr); + // This is a synchronous request, so we should wait for our request to complete. GetCurrentThread(kernel).SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::IPC); GetCurrentThread(kernel).BeginWait(&wait_queue); @@ -223,32 +223,32 @@ Result KServerSession::OnRequest() { Result KServerSession::SendReply() { // Lock the session. - KScopedLightLock lk(m_lock); + KScopedLightLock lk{m_lock}; // Get the request. - KThread* client_thread; + KSessionRequest* request; { KScopedSchedulerLock sl{kernel}; // Get the current request. - client_thread = m_current_thread_request; - R_UNLESS(client_thread != nullptr, ResultInvalidState); + request = m_current_request; + R_UNLESS(request != nullptr, ResultInvalidState); // Clear the current request, since we're processing it. - m_current_thread_request = nullptr; - if (!m_thread_request_list.empty()) { + m_current_request = nullptr; + if (!m_request_list.empty()) { this->NotifyAvailable(); } } // Close reference to the request once we're done processing it. - SCOPE_EXIT({ client_thread->Close(); }); + SCOPE_EXIT({ request->Close(); }); // Extract relevant information from the request. - // const uintptr_t client_message = request->GetAddress(); - // const size_t client_buffer_size = request->GetSize(); - // KThread *client_thread = request->GetThread(); - // KEvent *event = request->GetEvent(); + const uintptr_t client_message = request->GetAddress(); + const size_t client_buffer_size = request->GetSize(); + KThread* client_thread = request->GetThread(); + KEvent* event = request->GetEvent(); // Check whether we're closed. const bool closed = (client_thread == nullptr || parent->IsClientClosed()); @@ -261,8 +261,8 @@ Result KServerSession::SendReply() { UNIMPLEMENTED_IF(server_thread->GetOwnerProcess() != client_thread->GetOwnerProcess()); auto* src_msg_buffer = memory.GetPointer(server_thread->GetTLSAddress()); - auto* dst_msg_buffer = memory.GetPointer(client_thread->GetTLSAddress()); - std::memcpy(dst_msg_buffer, src_msg_buffer, MessageBufferSize); + auto* dst_msg_buffer = memory.GetPointer(client_message); + std::memcpy(dst_msg_buffer, src_msg_buffer, client_buffer_size); } else { result = ResultSessionClosed; } @@ -278,11 +278,30 @@ Result KServerSession::SendReply() { // If there's a client thread, update it. if (client_thread != nullptr) { - // End the client thread's wait. - KScopedSchedulerLock sl{kernel}; + if (event != nullptr) { + // // Get the client process/page table. + // KProcess *client_process = client_thread->GetOwnerProcess(); + // KPageTable *client_page_table = &client_process->PageTable(); - if (!client_thread->IsTerminationRequested()) { - client_thread->EndWait(client_result); + // // If we need to, reply with an async error. + // if (R_FAILED(client_result)) { + // ReplyAsyncError(client_process, client_message, client_buffer_size, + // client_result); + // } + + // // Unlock the client buffer. + // // NOTE: Nintendo does not check the result of this. + // client_page_table->UnlockForIpcUserBuffer(client_message, client_buffer_size); + + // Signal the event. + event->Signal(); + } else { + // End the client thread's wait. + KScopedSchedulerLock sl{kernel}; + + if (!client_thread->IsTerminationRequested()) { + client_thread->EndWait(client_result); + } } } @@ -291,10 +310,10 @@ Result KServerSession::SendReply() { Result KServerSession::ReceiveRequest() { // Lock the session. - KScopedLightLock lk(m_lock); + KScopedLightLock lk{m_lock}; // Get the request and client thread. - // KSessionRequest *request; + KSessionRequest* request; KThread* client_thread; { @@ -304,35 +323,41 @@ Result KServerSession::ReceiveRequest() { R_UNLESS(!parent->IsClientClosed(), ResultSessionClosed); // Ensure we aren't already servicing a request. - R_UNLESS(m_current_thread_request == nullptr, ResultNotFound); + R_UNLESS(m_current_request == nullptr, ResultNotFound); // Ensure we have a request to service. - R_UNLESS(!m_thread_request_list.empty(), ResultNotFound); + R_UNLESS(!m_request_list.empty(), ResultNotFound); // Pop the first request from the list. - client_thread = m_thread_request_list.front(); - m_thread_request_list.pop_front(); + request = &m_request_list.front(); + m_request_list.pop_front(); // Get the thread for the request. + client_thread = request->GetThread(); R_UNLESS(client_thread != nullptr, ResultSessionClosed); // Open the client thread. client_thread->Open(); } - // SCOPE_EXIT({ client_thread->Close(); }); + SCOPE_EXIT({ client_thread->Close(); }); // Set the request as our current. - m_current_thread_request = client_thread; + m_current_request = request; + + // Get the client address. + uintptr_t client_message = request->GetAddress(); + size_t client_buffer_size = request->GetSize(); + // bool recv_list_broken = false; // Receive the message. Core::Memory::Memory& memory{kernel.System().Memory()}; KThread* server_thread{GetCurrentThreadPointer(kernel)}; UNIMPLEMENTED_IF(server_thread->GetOwnerProcess() != client_thread->GetOwnerProcess()); - auto* src_msg_buffer = memory.GetPointer(client_thread->GetTLSAddress()); + auto* src_msg_buffer = memory.GetPointer(client_message); auto* dst_msg_buffer = memory.GetPointer(server_thread->GetTLSAddress()); - std::memcpy(dst_msg_buffer, src_msg_buffer, MessageBufferSize); + std::memcpy(dst_msg_buffer, src_msg_buffer, client_buffer_size); // We succeeded. return ResultSuccess; @@ -344,35 +369,34 @@ void KServerSession::CleanupRequests() { // Clean up any pending requests. while (true) { // Get the next request. - // KSessionRequest *request = nullptr; - KThread* client_thread = nullptr; + KSessionRequest* request = nullptr; { KScopedSchedulerLock sl{kernel}; - if (m_current_thread_request) { + if (m_current_request) { // Choose the current request if we have one. - client_thread = m_current_thread_request; - m_current_thread_request = nullptr; - } else if (!m_thread_request_list.empty()) { + request = m_current_request; + m_current_request = nullptr; + } else if (!m_request_list.empty()) { // Pop the request from the front of the list. - client_thread = m_thread_request_list.front(); - m_thread_request_list.pop_front(); + request = &m_request_list.front(); + m_request_list.pop_front(); } } // If there's no request, we're done. - if (client_thread == nullptr) { + if (request == nullptr) { break; } // Close a reference to the request once it's cleaned up. - SCOPE_EXIT({ client_thread->Close(); }); + SCOPE_EXIT({ request->Close(); }); // Extract relevant information from the request. // const uintptr_t client_message = request->GetAddress(); // const size_t client_buffer_size = request->GetSize(); - // KThread *client_thread = request->GetThread(); - // KEvent *event = request->GetEvent(); + KThread* client_thread = request->GetThread(); + KEvent* event = request->GetEvent(); // KProcess *server_process = request->GetServerProcess(); // KProcess *client_process = (client_thread != nullptr) ? @@ -385,11 +409,24 @@ void KServerSession::CleanupRequests() { // If there's a client thread, update it. if (client_thread != nullptr) { - // End the client thread's wait. - KScopedSchedulerLock sl{kernel}; + if (event != nullptr) { + // // We need to reply async. + // ReplyAsyncError(client_process, client_message, client_buffer_size, + // (R_SUCCEEDED(result) ? ResultSessionClosed : result)); - if (!client_thread->IsTerminationRequested()) { - client_thread->EndWait(ResultSessionClosed); + // // Unlock the client buffer. + // NOTE: Nintendo does not check the result of this. + // client_page_table->UnlockForIpcUserBuffer(client_message, client_buffer_size); + + // Signal the event. + event->Signal(); + } else { + // End the client thread's wait. + KScopedSchedulerLock sl{kernel}; + + if (!client_thread->IsTerminationRequested()) { + client_thread->EndWait(ResultSessionClosed); + } } } } diff --git a/src/core/hle/kernel/k_server_session.h b/src/core/hle/kernel/k_server_session.h index 748d52826c..c40ff4aaf7 100644 --- a/src/core/hle/kernel/k_server_session.h +++ b/src/core/hle/kernel/k_server_session.h @@ -12,6 +12,7 @@ #include "core/hle/kernel/hle_ipc.h" #include "core/hle/kernel/k_light_lock.h" +#include "core/hle/kernel/k_session_request.h" #include "core/hle/kernel/k_synchronization_object.h" #include "core/hle/result.h" @@ -94,7 +95,7 @@ public: } /// TODO: flesh these out to match the real kernel - Result OnRequest(); + Result OnRequest(KSessionRequest* request); Result SendReply(); Result ReceiveRequest(); @@ -122,9 +123,8 @@ private: KSession* parent{}; /// List of threads which are pending a reply. - /// FIXME: KSessionRequest - std::list m_thread_request_list; - KThread* m_current_thread_request{}; + boost::intrusive::list m_request_list; + KSessionRequest* m_current_request; KLightLock m_lock; }; diff --git a/src/core/hle/kernel/k_session_request.cpp b/src/core/hle/kernel/k_session_request.cpp new file mode 100644 index 0000000000..520da6aa74 --- /dev/null +++ b/src/core/hle/kernel/k_session_request.cpp @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/kernel/k_page_buffer.h" +#include "core/hle/kernel/k_session_request.h" + +namespace Kernel { + +Result KSessionRequest::SessionMappings::PushMap(VAddr client, VAddr server, size_t size, + KMemoryState state, size_t index) { + // At most 15 buffers of each type (4-bit descriptor counts). + ASSERT(index < ((1ul << 4) - 1) * 3); + + // Get the mapping. + Mapping* mapping; + if (index < NumStaticMappings) { + mapping = &m_static_mappings[index]; + } else { + // Allocate a page for the extra mappings. + if (m_mappings == nullptr) { + KPageBuffer* page_buffer = KPageBuffer::Allocate(kernel); + R_UNLESS(page_buffer != nullptr, ResultOutOfMemory); + + m_mappings = reinterpret_cast(page_buffer); + } + + mapping = &m_mappings[index - NumStaticMappings]; + } + + // Set the mapping. + mapping->Set(client, server, size, state); + + return ResultSuccess; +} + +Result KSessionRequest::SessionMappings::PushSend(VAddr client, VAddr server, size_t size, + KMemoryState state) { + ASSERT(m_num_recv == 0); + ASSERT(m_num_exch == 0); + return this->PushMap(client, server, size, state, m_num_send++); +} + +Result KSessionRequest::SessionMappings::PushReceive(VAddr client, VAddr server, size_t size, + KMemoryState state) { + ASSERT(m_num_exch == 0); + return this->PushMap(client, server, size, state, m_num_send + m_num_recv++); +} + +Result KSessionRequest::SessionMappings::PushExchange(VAddr client, VAddr server, size_t size, + KMemoryState state) { + return this->PushMap(client, server, size, state, m_num_send + m_num_recv + m_num_exch++); +} + +void KSessionRequest::SessionMappings::Finalize() { + if (m_mappings) { + KPageBuffer::Free(kernel, reinterpret_cast(m_mappings)); + m_mappings = nullptr; + } +} + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_session_request.h b/src/core/hle/kernel/k_session_request.h new file mode 100644 index 0000000000..fcf5215975 --- /dev/null +++ b/src/core/hle/kernel/k_session_request.h @@ -0,0 +1,307 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/hle/kernel/k_auto_object.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_memory_block.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/k_thread.h" +#include "core/hle/kernel/slab_helpers.h" + +namespace Kernel { + +class KSessionRequest final : public KSlabAllocated, + public KAutoObject, + public boost::intrusive::list_base_hook<> { + KERNEL_AUTOOBJECT_TRAITS(KSessionRequest, KAutoObject); + +public: + class SessionMappings { + private: + static constexpr size_t NumStaticMappings = 8; + + class Mapping { + public: + constexpr void Set(VAddr c, VAddr s, size_t sz, KMemoryState st) { + m_client_address = c; + m_server_address = s; + m_size = sz; + m_state = st; + } + + constexpr VAddr GetClientAddress() const { + return m_client_address; + } + constexpr VAddr GetServerAddress() const { + return m_server_address; + } + constexpr size_t GetSize() const { + return m_size; + } + constexpr KMemoryState GetMemoryState() const { + return m_state; + } + + private: + VAddr m_client_address; + VAddr m_server_address; + size_t m_size; + KMemoryState m_state; + }; + + public: + explicit SessionMappings(KernelCore& kernel_) + : kernel(kernel_), m_mappings(nullptr), m_num_send(), m_num_recv(), m_num_exch() {} + + void Initialize() {} + void Finalize(); + + size_t GetSendCount() const { + return m_num_send; + } + size_t GetReceiveCount() const { + return m_num_recv; + } + size_t GetExchangeCount() const { + return m_num_exch; + } + + Result PushSend(VAddr client, VAddr server, size_t size, KMemoryState state); + Result PushReceive(VAddr client, VAddr server, size_t size, KMemoryState state); + Result PushExchange(VAddr client, VAddr server, size_t size, KMemoryState state); + + VAddr GetSendClientAddress(size_t i) const { + return GetSendMapping(i).GetClientAddress(); + } + VAddr GetSendServerAddress(size_t i) const { + return GetSendMapping(i).GetServerAddress(); + } + size_t GetSendSize(size_t i) const { + return GetSendMapping(i).GetSize(); + } + KMemoryState GetSendMemoryState(size_t i) const { + return GetSendMapping(i).GetMemoryState(); + } + + VAddr GetReceiveClientAddress(size_t i) const { + return GetReceiveMapping(i).GetClientAddress(); + } + VAddr GetReceiveServerAddress(size_t i) const { + return GetReceiveMapping(i).GetServerAddress(); + } + size_t GetReceiveSize(size_t i) const { + return GetReceiveMapping(i).GetSize(); + } + KMemoryState GetReceiveMemoryState(size_t i) const { + return GetReceiveMapping(i).GetMemoryState(); + } + + VAddr GetExchangeClientAddress(size_t i) const { + return GetExchangeMapping(i).GetClientAddress(); + } + VAddr GetExchangeServerAddress(size_t i) const { + return GetExchangeMapping(i).GetServerAddress(); + } + size_t GetExchangeSize(size_t i) const { + return GetExchangeMapping(i).GetSize(); + } + KMemoryState GetExchangeMemoryState(size_t i) const { + return GetExchangeMapping(i).GetMemoryState(); + } + + private: + Result PushMap(VAddr client, VAddr server, size_t size, KMemoryState state, size_t index); + + const Mapping& GetSendMapping(size_t i) const { + ASSERT(i < m_num_send); + + const size_t index = i; + if (index < NumStaticMappings) { + return m_static_mappings[index]; + } else { + return m_mappings[index - NumStaticMappings]; + } + } + + const Mapping& GetReceiveMapping(size_t i) const { + ASSERT(i < m_num_recv); + + const size_t index = m_num_send + i; + if (index < NumStaticMappings) { + return m_static_mappings[index]; + } else { + return m_mappings[index - NumStaticMappings]; + } + } + + const Mapping& GetExchangeMapping(size_t i) const { + ASSERT(i < m_num_exch); + + const size_t index = m_num_send + m_num_recv + i; + if (index < NumStaticMappings) { + return m_static_mappings[index]; + } else { + return m_mappings[index - NumStaticMappings]; + } + } + + private: + KernelCore& kernel; + Mapping m_static_mappings[NumStaticMappings]; + Mapping* m_mappings; + u8 m_num_send; + u8 m_num_recv; + u8 m_num_exch; + }; + +public: + explicit KSessionRequest(KernelCore& kernel_) + : KAutoObject(kernel_), m_mappings(kernel_), m_thread(nullptr), m_server(nullptr), + m_event(nullptr) {} + + static KSessionRequest* Create(KernelCore& kernel) { + KSessionRequest* req = KSessionRequest::Allocate(kernel); + if (req != nullptr) [[likely]] { + KAutoObject::Create(req); + } + return req; + } + + void Destroy() override { + this->Finalize(); + KSessionRequest::Free(kernel, this); + } + + void Initialize(KEvent* event, uintptr_t address, size_t size) { + m_mappings.Initialize(); + + m_thread = GetCurrentThreadPointer(kernel); + m_event = event; + m_address = address; + m_size = size; + + m_thread->Open(); + if (m_event != nullptr) { + m_event->Open(); + } + } + + static void PostDestroy(uintptr_t arg) {} + + KThread* GetThread() const { + return m_thread; + } + KEvent* GetEvent() const { + return m_event; + } + uintptr_t GetAddress() const { + return m_address; + } + size_t GetSize() const { + return m_size; + } + KProcess* GetServerProcess() const { + return m_server; + } + + void SetServerProcess(KProcess* process) { + m_server = process; + m_server->Open(); + } + + void ClearThread() { + m_thread = nullptr; + } + void ClearEvent() { + m_event = nullptr; + } + + size_t GetSendCount() const { + return m_mappings.GetSendCount(); + } + size_t GetReceiveCount() const { + return m_mappings.GetReceiveCount(); + } + size_t GetExchangeCount() const { + return m_mappings.GetExchangeCount(); + } + + Result PushSend(VAddr client, VAddr server, size_t size, KMemoryState state) { + return m_mappings.PushSend(client, server, size, state); + } + + Result PushReceive(VAddr client, VAddr server, size_t size, KMemoryState state) { + return m_mappings.PushReceive(client, server, size, state); + } + + Result PushExchange(VAddr client, VAddr server, size_t size, KMemoryState state) { + return m_mappings.PushExchange(client, server, size, state); + } + + VAddr GetSendClientAddress(size_t i) const { + return m_mappings.GetSendClientAddress(i); + } + VAddr GetSendServerAddress(size_t i) const { + return m_mappings.GetSendServerAddress(i); + } + size_t GetSendSize(size_t i) const { + return m_mappings.GetSendSize(i); + } + KMemoryState GetSendMemoryState(size_t i) const { + return m_mappings.GetSendMemoryState(i); + } + + VAddr GetReceiveClientAddress(size_t i) const { + return m_mappings.GetReceiveClientAddress(i); + } + VAddr GetReceiveServerAddress(size_t i) const { + return m_mappings.GetReceiveServerAddress(i); + } + size_t GetReceiveSize(size_t i) const { + return m_mappings.GetReceiveSize(i); + } + KMemoryState GetReceiveMemoryState(size_t i) const { + return m_mappings.GetReceiveMemoryState(i); + } + + VAddr GetExchangeClientAddress(size_t i) const { + return m_mappings.GetExchangeClientAddress(i); + } + VAddr GetExchangeServerAddress(size_t i) const { + return m_mappings.GetExchangeServerAddress(i); + } + size_t GetExchangeSize(size_t i) const { + return m_mappings.GetExchangeSize(i); + } + KMemoryState GetExchangeMemoryState(size_t i) const { + return m_mappings.GetExchangeMemoryState(i); + } + +private: + // NOTE: This is public and virtual in Nintendo's kernel. + void Finalize() { + m_mappings.Finalize(); + + if (m_thread) { + m_thread->Close(); + } + if (m_event) { + m_event->Close(); + } + if (m_server) { + m_server->Close(); + } + } + +private: + SessionMappings m_mappings; + KThread* m_thread; + KProcess* m_server; + KEvent* m_event; + uintptr_t m_address; + size_t m_size; +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_shared_memory_info.h b/src/core/hle/kernel/k_shared_memory_info.h index e43db8515b..2bb6b6d088 100644 --- a/src/core/hle/kernel/k_shared_memory_info.h +++ b/src/core/hle/kernel/k_shared_memory_info.h @@ -15,7 +15,8 @@ class KSharedMemoryInfo final : public KSlabAllocated, public boost::intrusive::list_base_hook<> { public: - explicit KSharedMemoryInfo() = default; + explicit KSharedMemoryInfo(KernelCore&) {} + KSharedMemoryInfo() = default; constexpr void Initialize(KSharedMemory* shmem) { shared_memory = shmem; diff --git a/src/core/hle/kernel/k_thread_local_page.h b/src/core/hle/kernel/k_thread_local_page.h index 0a7f226807..5d466ace7a 100644 --- a/src/core/hle/kernel/k_thread_local_page.h +++ b/src/core/hle/kernel/k_thread_local_page.h @@ -26,7 +26,7 @@ public: static_assert(RegionsPerPage > 0); public: - constexpr explicit KThreadLocalPage(VAddr addr = {}) : m_virt_addr(addr) { + constexpr explicit KThreadLocalPage(KernelCore&, VAddr addr = {}) : m_virt_addr(addr) { m_is_region_free.fill(true); } diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 6eded95393..266be2bc4f 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -47,6 +47,7 @@ class KResourceLimit; class KScheduler; class KServerSession; class KSession; +class KSessionRequest; class KSharedMemory; class KSharedMemoryInfo; class KThread; @@ -360,6 +361,8 @@ public: return slab_heap_container->page_buffer; } else if constexpr (std::is_same_v) { return slab_heap_container->thread_local_page; + } else if constexpr (std::is_same_v) { + return slab_heap_container->session_request; } } @@ -422,6 +425,7 @@ private: KSlabHeap code_memory; KSlabHeap page_buffer; KSlabHeap thread_local_page; + KSlabHeap session_request; }; std::unique_ptr slab_heap_container; diff --git a/src/core/hle/kernel/slab_helpers.h b/src/core/hle/kernel/slab_helpers.h index 299a981a8a..06b51e9199 100644 --- a/src/core/hle/kernel/slab_helpers.h +++ b/src/core/hle/kernel/slab_helpers.h @@ -24,7 +24,7 @@ public: } static Derived* Allocate(KernelCore& kernel) { - return kernel.SlabHeap().Allocate(); + return kernel.SlabHeap().Allocate(kernel); } static void Free(KernelCore& kernel, Derived* obj) { From fca195b4fb1255c20579fd25d8565f0ae4759b6e Mon Sep 17 00:00:00 2001 From: Liam Date: Sat, 15 Oct 2022 21:57:40 -0400 Subject: [PATCH 47/91] kernel: remove most SessionRequestManager handling from KServerSession --- src/core/hle/ipc_helpers.h | 11 ++- src/core/hle/kernel/hle_ipc.cpp | 110 +++++++++++++++++++--- src/core/hle/kernel/hle_ipc.h | 9 ++ src/core/hle/kernel/k_server_session.cpp | 89 +---------------- src/core/hle/kernel/k_server_session.h | 33 ------- src/core/hle/service/sm/sm_controller.cpp | 5 +- 6 files changed, 119 insertions(+), 138 deletions(-) diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h index 0cc26a2118..aa27be7678 100644 --- a/src/core/hle/ipc_helpers.h +++ b/src/core/hle/ipc_helpers.h @@ -86,13 +86,13 @@ public: u32 num_domain_objects{}; const bool always_move_handles{ (static_cast(flags) & static_cast(Flags::AlwaysMoveHandles)) != 0}; - if (!ctx.Session()->IsDomain() || always_move_handles) { + if (!ctx.Session()->GetSessionRequestManager()->IsDomain() || always_move_handles) { num_handles_to_move = num_objects_to_move; } else { num_domain_objects = num_objects_to_move; } - if (ctx.Session()->IsDomain()) { + if (ctx.Session()->GetSessionRequestManager()->IsDomain()) { raw_data_size += static_cast(sizeof(DomainMessageHeader) / sizeof(u32) + num_domain_objects); ctx.write_size += num_domain_objects; @@ -125,7 +125,8 @@ public: if (!ctx.IsTipc()) { AlignWithPadding(); - if (ctx.Session()->IsDomain() && ctx.HasDomainMessageHeader()) { + if (ctx.Session()->GetSessionRequestManager()->IsDomain() && + ctx.HasDomainMessageHeader()) { IPC::DomainMessageHeader domain_header{}; domain_header.num_objects = num_domain_objects; PushRaw(domain_header); @@ -145,7 +146,7 @@ public: template void PushIpcInterface(std::shared_ptr iface) { - if (context->Session()->IsDomain()) { + if (context->Session()->GetSessionRequestManager()->IsDomain()) { context->AddDomainObject(std::move(iface)); } else { kernel.CurrentProcess()->GetResourceLimit()->Reserve( @@ -386,7 +387,7 @@ public: template std::weak_ptr PopIpcInterface() { - ASSERT(context->Session()->IsDomain()); + ASSERT(context->Session()->GetSessionRequestManager()->IsDomain()); ASSERT(context->GetDomainMessageHeader().input_object_count > 0); return context->GetDomainHandler(Pop() - 1); } diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp index 5b3feec662..e4f43a0537 100644 --- a/src/core/hle/kernel/hle_ipc.cpp +++ b/src/core/hle/kernel/hle_ipc.cpp @@ -19,6 +19,7 @@ #include "core/hle/kernel/k_server_session.h" #include "core/hle/kernel/k_thread.h" #include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/service_thread.h" #include "core/memory.h" namespace Kernel { @@ -56,16 +57,103 @@ bool SessionRequestManager::HasSessionRequestHandler(const HLERequestContext& co } } +Result SessionRequestManager::CompleteSyncRequest(KServerSession* server_session, + HLERequestContext& context) { + Result result = ResultSuccess; + + // If the session has been converted to a domain, handle the domain request + if (this->HasSessionRequestHandler(context)) { + if (IsDomain() && context.HasDomainMessageHeader()) { + result = HandleDomainSyncRequest(server_session, context); + // If there is no domain header, the regular session handler is used + } else if (this->HasSessionHandler()) { + // If this manager has an associated HLE handler, forward the request to it. + result = this->SessionHandler().HandleSyncRequest(*server_session, context); + } + } else { + ASSERT_MSG(false, "Session handler is invalid, stubbing response!"); + IPC::ResponseBuilder rb(context, 2); + rb.Push(ResultSuccess); + } + + if (convert_to_domain) { + ASSERT_MSG(!IsDomain(), "ServerSession is already a domain instance."); + this->ConvertToDomain(); + convert_to_domain = false; + } + + return result; +} + +Result SessionRequestManager::HandleDomainSyncRequest(KServerSession* server_session, + HLERequestContext& context) { + if (!context.HasDomainMessageHeader()) { + return ResultSuccess; + } + + // Set domain handlers in HLE context, used for domain objects (IPC interfaces) as inputs + context.SetSessionRequestManager(server_session->GetSessionRequestManager()); + + // If there is a DomainMessageHeader, then this is CommandType "Request" + const auto& domain_message_header = context.GetDomainMessageHeader(); + const u32 object_id{domain_message_header.object_id}; + switch (domain_message_header.command) { + case IPC::DomainMessageHeader::CommandType::SendMessage: + if (object_id > this->DomainHandlerCount()) { + LOG_CRITICAL(IPC, + "object_id {} is too big! This probably means a recent service call " + "needed to return a new interface!", + object_id); + ASSERT(false); + return ResultSuccess; // Ignore error if asserts are off + } + if (auto strong_ptr = this->DomainHandler(object_id - 1).lock()) { + return strong_ptr->HandleSyncRequest(*server_session, context); + } else { + ASSERT(false); + return ResultSuccess; + } + + case IPC::DomainMessageHeader::CommandType::CloseVirtualHandle: { + LOG_DEBUG(IPC, "CloseVirtualHandle, object_id=0x{:08X}", object_id); + + this->CloseDomainHandler(object_id - 1); + + IPC::ResponseBuilder rb{context, 2}; + rb.Push(ResultSuccess); + return ResultSuccess; + } + } + + LOG_CRITICAL(IPC, "Unknown domain command={}", domain_message_header.command.Value()); + ASSERT(false); + return ResultSuccess; +} + +Result SessionRequestManager::QueueSyncRequest(KSession* parent, + std::shared_ptr&& context) { + // Ensure we have a session request handler + if (this->HasSessionRequestHandler(*context)) { + if (auto strong_ptr = this->GetServiceThread().lock()) { + strong_ptr->QueueSyncRequest(*parent, std::move(context)); + } else { + ASSERT_MSG(false, "strong_ptr is nullptr!"); + } + } else { + ASSERT_MSG(false, "handler is invalid!"); + } + + return ResultSuccess; +} + void SessionRequestHandler::ClientConnected(KServerSession* session) { - session->ClientConnected(shared_from_this()); + session->GetSessionRequestManager()->SetSessionHandler(shared_from_this()); // Ensure our server session is tracked globally. kernel.RegisterServerObject(session); } -void SessionRequestHandler::ClientDisconnected(KServerSession* session) { - session->ClientDisconnected(); -} +void SessionRequestHandler::ClientDisconnected(KServerSession* session) {} HLERequestContext::HLERequestContext(KernelCore& kernel_, Core::Memory::Memory& memory_, KServerSession* server_session_, KThread* thread_) @@ -126,7 +214,7 @@ void HLERequestContext::ParseCommandBuffer(const KHandleTable& handle_table, u32 // Padding to align to 16 bytes rp.AlignWithPadding(); - if (Session()->IsDomain() && + if (Session()->GetSessionRequestManager()->IsDomain() && ((command_header->type == IPC::CommandType::Request || command_header->type == IPC::CommandType::RequestWithContext) || !incoming)) { @@ -135,7 +223,7 @@ void HLERequestContext::ParseCommandBuffer(const KHandleTable& handle_table, u32 if (incoming || domain_message_header) { domain_message_header = rp.PopRaw(); } else { - if (Session()->IsDomain()) { + if (Session()->GetSessionRequestManager()->IsDomain()) { LOG_WARNING(IPC, "Domain request has no DomainMessageHeader!"); } } @@ -228,12 +316,12 @@ Result HLERequestContext::WriteToOutgoingCommandBuffer(KThread& requesting_threa // Write the domain objects to the command buffer, these go after the raw untranslated data. // TODO(Subv): This completely ignores C buffers. - if (Session()->IsDomain()) { + if (server_session->GetSessionRequestManager()->IsDomain()) { current_offset = domain_offset - static_cast(outgoing_domain_objects.size()); - for (const auto& object : outgoing_domain_objects) { - server_session->AppendDomainHandler(object); - cmd_buf[current_offset++] = - static_cast(server_session->NumDomainRequestHandlers()); + for (auto& object : outgoing_domain_objects) { + server_session->GetSessionRequestManager()->AppendDomainHandler(std::move(object)); + cmd_buf[current_offset++] = static_cast( + server_session->GetSessionRequestManager()->DomainHandlerCount()); } } diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h index e258e2cdfe..a0522bca0b 100644 --- a/src/core/hle/kernel/hle_ipc.h +++ b/src/core/hle/kernel/hle_ipc.h @@ -121,6 +121,10 @@ public: is_domain = true; } + void ConvertToDomainOnRequestEnd() { + convert_to_domain = true; + } + std::size_t DomainHandlerCount() const { return domain_handlers.size(); } @@ -164,7 +168,12 @@ public: bool HasSessionRequestHandler(const HLERequestContext& context) const; + Result HandleDomainSyncRequest(KServerSession* server_session, HLERequestContext& context); + Result CompleteSyncRequest(KServerSession* server_session, HLERequestContext& context); + Result QueueSyncRequest(KSession* parent, std::shared_ptr&& context); + private: + bool convert_to_domain{}; bool is_domain{}; SessionRequestHandlerPtr session_handler; std::vector domain_handlers; diff --git a/src/core/hle/kernel/k_server_session.cpp b/src/core/hle/kernel/k_server_session.cpp index 685a2a6e67..faf03fcc89 100644 --- a/src/core/hle/kernel/k_server_session.cpp +++ b/src/core/hle/kernel/k_server_session.cpp @@ -22,7 +22,6 @@ #include "core/hle/kernel/k_thread.h" #include "core/hle/kernel/k_thread_queue.h" #include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/service_thread.h" #include "core/memory.h" namespace Kernel { @@ -74,101 +73,17 @@ bool KServerSession::IsSignaled() const { return !m_request_list.empty() && m_current_request == nullptr; } -void KServerSession::AppendDomainHandler(SessionRequestHandlerPtr handler) { - manager->AppendDomainHandler(std::move(handler)); -} - -std::size_t KServerSession::NumDomainRequestHandlers() const { - return manager->DomainHandlerCount(); -} - -Result KServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& context) { - if (!context.HasDomainMessageHeader()) { - return ResultSuccess; - } - - // Set domain handlers in HLE context, used for domain objects (IPC interfaces) as inputs - context.SetSessionRequestManager(manager); - - // If there is a DomainMessageHeader, then this is CommandType "Request" - const auto& domain_message_header = context.GetDomainMessageHeader(); - const u32 object_id{domain_message_header.object_id}; - switch (domain_message_header.command) { - case IPC::DomainMessageHeader::CommandType::SendMessage: - if (object_id > manager->DomainHandlerCount()) { - LOG_CRITICAL(IPC, - "object_id {} is too big! This probably means a recent service call " - "to {} needed to return a new interface!", - object_id, name); - ASSERT(false); - return ResultSuccess; // Ignore error if asserts are off - } - if (auto strong_ptr = manager->DomainHandler(object_id - 1).lock()) { - return strong_ptr->HandleSyncRequest(*this, context); - } else { - ASSERT(false); - return ResultSuccess; - } - - case IPC::DomainMessageHeader::CommandType::CloseVirtualHandle: { - LOG_DEBUG(IPC, "CloseVirtualHandle, object_id=0x{:08X}", object_id); - - manager->CloseDomainHandler(object_id - 1); - - IPC::ResponseBuilder rb{context, 2}; - rb.Push(ResultSuccess); - return ResultSuccess; - } - } - - LOG_CRITICAL(IPC, "Unknown domain command={}", domain_message_header.command.Value()); - ASSERT(false); - return ResultSuccess; -} - Result KServerSession::QueueSyncRequest(KThread* thread, Core::Memory::Memory& memory) { u32* cmd_buf{reinterpret_cast(memory.GetPointer(thread->GetTLSAddress()))}; auto context = std::make_shared(kernel, memory, this, thread); context->PopulateFromIncomingCommandBuffer(kernel.CurrentProcess()->GetHandleTable(), cmd_buf); - // Ensure we have a session request handler - if (manager->HasSessionRequestHandler(*context)) { - if (auto strong_ptr = manager->GetServiceThread().lock()) { - strong_ptr->QueueSyncRequest(*parent, std::move(context)); - } else { - ASSERT_MSG(false, "strong_ptr is nullptr!"); - } - } else { - ASSERT_MSG(false, "handler is invalid!"); - } - - return ResultSuccess; + return manager->QueueSyncRequest(parent, std::move(context)); } Result KServerSession::CompleteSyncRequest(HLERequestContext& context) { - Result result = ResultSuccess; - - // If the session has been converted to a domain, handle the domain request - if (manager->HasSessionRequestHandler(context)) { - if (IsDomain() && context.HasDomainMessageHeader()) { - result = HandleDomainSyncRequest(context); - // If there is no domain header, the regular session handler is used - } else if (manager->HasSessionHandler()) { - // If this ServerSession has an associated HLE handler, forward the request to it. - result = manager->SessionHandler().HandleSyncRequest(*this, context); - } - } else { - ASSERT_MSG(false, "Session handler is invalid, stubbing response!"); - IPC::ResponseBuilder rb(context, 2); - rb.Push(ResultSuccess); - } - - if (convert_to_domain) { - ASSERT_MSG(!IsDomain(), "ServerSession is already a domain instance."); - manager->ConvertToDomain(); - convert_to_domain = false; - } + Result result = manager->CompleteSyncRequest(this, context); // The calling thread is waiting for this request to complete, so wake it up. context.GetThread().EndWait(result); diff --git a/src/core/hle/kernel/k_server_session.h b/src/core/hle/kernel/k_server_session.h index c40ff4aaf7..32135473b7 100644 --- a/src/core/hle/kernel/k_server_session.h +++ b/src/core/hle/kernel/k_server_session.h @@ -58,37 +58,8 @@ public: } bool IsSignaled() const override; - void OnClientClosed(); - void ClientConnected(SessionRequestHandlerPtr handler) { - if (manager) { - manager->SetSessionHandler(std::move(handler)); - } - } - - void ClientDisconnected() { - manager = nullptr; - } - - /// Adds a new domain request handler to the collection of request handlers within - /// this ServerSession instance. - void AppendDomainHandler(SessionRequestHandlerPtr handler); - - /// Retrieves the total number of domain request handlers that have been - /// appended to this ServerSession instance. - std::size_t NumDomainRequestHandlers() const; - - /// Returns true if the session has been converted to a domain, otherwise False - bool IsDomain() const { - return manager && manager->IsDomain(); - } - - /// Converts the session to a domain at the end of the current command - void ConvertToDomain() { - convert_to_domain = true; - } - /// Gets the session request manager, which forwards requests to the underlying service std::shared_ptr& GetSessionRequestManager() { return manager; @@ -109,10 +80,6 @@ private: /// Completes a sync request from the emulated application. Result CompleteSyncRequest(HLERequestContext& context); - /// Handles a SyncRequest to a domain, forwarding the request to the proper object or closing an - /// object handle. - Result HandleDomainSyncRequest(Kernel::HLERequestContext& context); - /// This session's HLE request handlers; if nullptr, this is not an HLE server std::shared_ptr manager; diff --git a/src/core/hle/service/sm/sm_controller.cpp b/src/core/hle/service/sm/sm_controller.cpp index 2a4bd64ab2..273f795684 100644 --- a/src/core/hle/service/sm/sm_controller.cpp +++ b/src/core/hle/service/sm/sm_controller.cpp @@ -15,9 +15,10 @@ namespace Service::SM { void Controller::ConvertCurrentObjectToDomain(Kernel::HLERequestContext& ctx) { - ASSERT_MSG(!ctx.Session()->IsDomain(), "Session is already a domain"); + ASSERT_MSG(!ctx.Session()->GetSessionRequestManager()->IsDomain(), + "Session is already a domain"); LOG_DEBUG(Service, "called, server_session={}", ctx.Session()->GetId()); - ctx.Session()->ConvertToDomain(); + ctx.Session()->GetSessionRequestManager()->ConvertToDomainOnRequestEnd(); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); From d4c0b7b43739e67af1dddf5ec4210f6af809628e Mon Sep 17 00:00:00 2001 From: Kyle Kienapfel Date: Thu, 20 Oct 2022 06:55:23 -0700 Subject: [PATCH 48/91] Controller Applet had instance of Undocked, make Handheld Remember that time we renamed the Undocked option to Handheld in the status bar, and then later remembered the Controller Configuration? Scrolling through Transifex I noticed that we still have one instance of "Undocked" in the text. --- src/yuzu/applets/qt_controller.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yuzu/applets/qt_controller.ui b/src/yuzu/applets/qt_controller.ui index c8cb6bcf38..f5eccba70b 100644 --- a/src/yuzu/applets/qt_controller.ui +++ b/src/yuzu/applets/qt_controller.ui @@ -2300,7 +2300,7 @@ - Undocked + Handheld From 0b181eeef4f56a814ea6c9b86d789be3ab8d1ae5 Mon Sep 17 00:00:00 2001 From: Lioncash Date: Fri, 21 Oct 2022 00:05:39 -0400 Subject: [PATCH 49/91] hid/npad: Fix copy size in GetSupportedNpadIdTypes Previously this was passing the size of the vector into memcpy rather than the size in bytes to copy, which would result in a partial read. Thankfully, this function isn't used yet, so this gets rid of a bug before it's able to do anything. --- src/core/hle/service/hid/controllers/npad.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index 98e4f2af7f..ba8a1f7866 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -745,8 +745,9 @@ void Controller_NPad::SetSupportedNpadIdTypes(u8* data, std::size_t length) { } void Controller_NPad::GetSupportedNpadIdTypes(u32* data, std::size_t max_length) { - ASSERT(max_length < supported_npad_id_types.size()); - std::memcpy(data, supported_npad_id_types.data(), supported_npad_id_types.size()); + const auto copy_amount = supported_npad_id_types.size() * sizeof(u32); + ASSERT(max_length <= copy_amount); + std::memcpy(data, supported_npad_id_types.data(), copy_amount); } std::size_t Controller_NPad::GetSupportedNpadIdTypesSize() const { From 7f66050f0c383a5c7d82c5c58098f819d4e1e0bc Mon Sep 17 00:00:00 2001 From: german77 Date: Fri, 21 Oct 2022 00:23:12 -0500 Subject: [PATCH 50/91] input_common: cache vibration tests --- src/common/input.h | 5 +- src/core/hid/emulated_controller.cpp | 45 +++---------- src/core/hid/emulated_controller.h | 5 +- src/core/hle/service/hid/controllers/npad.cpp | 4 +- src/input_common/drivers/gc_adapter.cpp | 6 +- src/input_common/drivers/gc_adapter.h | 4 +- src/input_common/drivers/sdl_driver.cpp | 64 ++++++++++++++++--- src/input_common/drivers/sdl_driver.h | 4 +- src/input_common/input_engine.h | 7 +- src/input_common/input_poller.cpp | 6 +- 10 files changed, 93 insertions(+), 57 deletions(-) diff --git a/src/common/input.h b/src/common/input.h index b533f3844c..cb30b72547 100644 --- a/src/common/input.h +++ b/src/common/input.h @@ -100,7 +100,6 @@ enum class CameraError { enum class VibrationAmplificationType { Linear, Exponential, - Test, }; // Analog properties for calibration @@ -325,6 +324,10 @@ public: return VibrationError::NotSupported; } + virtual bool IsVibrationEnabled() { + return false; + } + virtual PollingError SetPollingMode([[maybe_unused]] PollingMode polling_mode) { return PollingError::NotSupported; } diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp index 025f1c78e4..06c2081a98 100644 --- a/src/core/hid/emulated_controller.cpp +++ b/src/core/hid/emulated_controller.cpp @@ -970,14 +970,7 @@ bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue v Common::Input::VibrationError::None; } -bool EmulatedController::TestVibration(std::size_t device_index) { - if (device_index >= output_devices.size()) { - return false; - } - if (!output_devices[device_index]) { - return false; - } - +bool EmulatedController::IsVibrationEnabled(std::size_t device_index) { const auto player_index = NpadIdTypeToIndex(npad_id_type); const auto& player = Settings::values.players.GetValue()[player_index]; @@ -985,31 +978,15 @@ bool EmulatedController::TestVibration(std::size_t device_index) { return false; } - const Common::Input::VibrationStatus test_vibration = { - .low_amplitude = 0.001f, - .low_frequency = DEFAULT_VIBRATION_VALUE.low_frequency, - .high_amplitude = 0.001f, - .high_frequency = DEFAULT_VIBRATION_VALUE.high_frequency, - .type = Common::Input::VibrationAmplificationType::Test, - }; + if (device_index >= output_devices.size()) { + return false; + } - const Common::Input::VibrationStatus zero_vibration = { - .low_amplitude = DEFAULT_VIBRATION_VALUE.low_amplitude, - .low_frequency = DEFAULT_VIBRATION_VALUE.low_frequency, - .high_amplitude = DEFAULT_VIBRATION_VALUE.high_amplitude, - .high_frequency = DEFAULT_VIBRATION_VALUE.high_frequency, - .type = Common::Input::VibrationAmplificationType::Test, - }; + if (!output_devices[device_index]) { + return false; + } - // Send a slight vibration to test for rumble support - output_devices[device_index]->SetVibration(test_vibration); - - // Wait for about 15ms to ensure the controller is ready for the stop command - std::this_thread::sleep_for(std::chrono::milliseconds(15)); - - // Stop any vibration and return the result - return output_devices[device_index]->SetVibration(zero_vibration) == - Common::Input::VibrationError::None; + return output_devices[device_index]->IsVibrationEnabled(); } bool EmulatedController::SetPollingMode(Common::Input::PollingMode polling_mode) { @@ -1234,12 +1211,6 @@ bool EmulatedController::IsConnected(bool get_temporary_value) const { return is_connected; } -bool EmulatedController::IsVibrationEnabled() const { - const auto player_index = NpadIdTypeToIndex(npad_id_type); - const auto& player = Settings::values.players.GetValue()[player_index]; - return player.vibration_enabled; -} - NpadIdType EmulatedController::GetNpadIdType() const { std::scoped_lock lock{mutex}; return npad_id_type; diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h index 319226bf82..d004ca56ad 100644 --- a/src/core/hid/emulated_controller.h +++ b/src/core/hid/emulated_controller.h @@ -206,9 +206,6 @@ public: */ bool IsConnected(bool get_temporary_value = false) const; - /// Returns true if vibration is enabled - bool IsVibrationEnabled() const; - /// Removes all callbacks created from input devices void UnloadInput(); @@ -339,7 +336,7 @@ public: * Sends a small vibration to the output device * @return true if SetVibration was successfull */ - bool TestVibration(std::size_t device_index); + bool IsVibrationEnabled(std::size_t device_index); /** * Sets the desired data to be polled from a controller diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index 98e4f2af7f..b85831de10 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -867,7 +867,7 @@ bool Controller_NPad::VibrateControllerAtIndex(Core::HID::NpadIdType npad_id, return false; } - if (!controller.device->IsVibrationEnabled()) { + if (!controller.device->IsVibrationEnabled(device_index)) { if (controller.vibration[device_index].latest_vibration_value.low_amplitude != 0.0f || controller.vibration[device_index].latest_vibration_value.high_amplitude != 0.0f) { // Send an empty vibration to stop any vibrations. @@ -1000,7 +1000,7 @@ void Controller_NPad::InitializeVibrationDeviceAtIndex(Core::HID::NpadIdType npa } controller.vibration[device_index].device_mounted = - controller.device->TestVibration(device_index); + controller.device->IsVibrationEnabled(device_index); } void Controller_NPad::SetPermitVibrationSession(bool permit_vibration_session) { diff --git a/src/input_common/drivers/gc_adapter.cpp b/src/input_common/drivers/gc_adapter.cpp index f4dd24e7df..826fa21097 100644 --- a/src/input_common/drivers/gc_adapter.cpp +++ b/src/input_common/drivers/gc_adapter.cpp @@ -324,7 +324,7 @@ bool GCAdapter::GetGCEndpoint(libusb_device* device) { return true; } -Common::Input::VibrationError GCAdapter::SetRumble( +Common::Input::VibrationError GCAdapter::SetVibration( const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) { const auto mean_amplitude = (vibration.low_amplitude + vibration.high_amplitude) * 0.5f; const auto processed_amplitude = @@ -338,6 +338,10 @@ Common::Input::VibrationError GCAdapter::SetRumble( return Common::Input::VibrationError::None; } +bool GCAdapter::IsVibrationEnabled([[maybe_unused]] const PadIdentifier& identifier) { + return rumble_enabled; +} + void GCAdapter::UpdateVibrations() { // Use 8 states to keep the switching between on/off fast enough for // a human to feel different vibration strenght diff --git a/src/input_common/drivers/gc_adapter.h b/src/input_common/drivers/gc_adapter.h index 8682da8470..7f81767f77 100644 --- a/src/input_common/drivers/gc_adapter.h +++ b/src/input_common/drivers/gc_adapter.h @@ -25,9 +25,11 @@ public: explicit GCAdapter(std::string input_engine_); ~GCAdapter() override; - Common::Input::VibrationError SetRumble( + Common::Input::VibrationError SetVibration( const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override; + bool IsVibrationEnabled(const PadIdentifier& identifier) override; + /// Used for automapping features std::vector GetInputDevices() const override; ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override; diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp index b72e4b3975..ddbe8a8966 100644 --- a/src/input_common/drivers/sdl_driver.cpp +++ b/src/input_common/drivers/sdl_driver.cpp @@ -114,6 +114,20 @@ public: } return false; } + + void EnableVibration(bool is_enabled) { + has_vibration = is_enabled; + is_vibration_tested = true; + } + + bool HasVibration() const { + return has_vibration; + } + + bool IsVibrationTested() const { + return is_vibration_tested; + } + /** * The Pad identifier of the joystick */ @@ -236,6 +250,8 @@ private: u64 last_motion_update{}; bool has_gyro{false}; bool has_accel{false}; + bool has_vibration{false}; + bool is_vibration_tested{false}; BasicMotion motion; }; @@ -517,7 +533,7 @@ std::vector SDLDriver::GetInputDevices() const { return devices; } -Common::Input::VibrationError SDLDriver::SetRumble( +Common::Input::VibrationError SDLDriver::SetVibration( const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) { const auto joystick = GetSDLJoystickByGUID(identifier.guid.RawString(), static_cast(identifier.port)); @@ -546,13 +562,6 @@ Common::Input::VibrationError SDLDriver::SetRumble( .type = Common::Input::VibrationAmplificationType::Exponential, }; - if (vibration.type == Common::Input::VibrationAmplificationType::Test) { - if (!joystick->RumblePlay(new_vibration)) { - return Common::Input::VibrationError::Unknown; - } - return Common::Input::VibrationError::None; - } - vibration_queue.Push(VibrationRequest{ .identifier = identifier, .vibration = new_vibration, @@ -561,6 +570,45 @@ Common::Input::VibrationError SDLDriver::SetRumble( return Common::Input::VibrationError::None; } +bool SDLDriver::IsVibrationEnabled(const PadIdentifier& identifier) { + const auto joystick = + GetSDLJoystickByGUID(identifier.guid.RawString(), static_cast(identifier.port)); + + constexpr Common::Input::VibrationStatus test_vibration{ + .low_amplitude = 1, + .low_frequency = 160.0f, + .high_amplitude = 1, + .high_frequency = 320.0f, + .type = Common::Input::VibrationAmplificationType::Exponential, + }; + + constexpr Common::Input::VibrationStatus zero_vibration{ + .low_amplitude = 0, + .low_frequency = 160.0f, + .high_amplitude = 0, + .high_frequency = 320.0f, + .type = Common::Input::VibrationAmplificationType::Exponential, + }; + + if (joystick->IsVibrationTested()) { + return joystick->HasVibration(); + } + + // First vibration might fail + joystick->RumblePlay(test_vibration); + + // Wait for about 15ms to ensure the controller is ready for the stop command + std::this_thread::sleep_for(std::chrono::milliseconds(15)); + + if (!joystick->RumblePlay(zero_vibration)) { + joystick->EnableVibration(false); + return false; + } + + joystick->EnableVibration(true); + return true; +} + void SDLDriver::SendVibrations() { while (!vibration_queue.Empty()) { VibrationRequest request; diff --git a/src/input_common/drivers/sdl_driver.h b/src/input_common/drivers/sdl_driver.h index fc3a445724..d1b4471cf7 100644 --- a/src/input_common/drivers/sdl_driver.h +++ b/src/input_common/drivers/sdl_driver.h @@ -61,9 +61,11 @@ public: bool IsStickInverted(const Common::ParamPackage& params) override; - Common::Input::VibrationError SetRumble( + Common::Input::VibrationError SetVibration( const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override; + bool IsVibrationEnabled(const PadIdentifier& identifier) override; + private: struct VibrationRequest { PadIdentifier identifier; diff --git a/src/input_common/input_engine.h b/src/input_common/input_engine.h index cfbdb26bd7..d4c264a8eb 100644 --- a/src/input_common/input_engine.h +++ b/src/input_common/input_engine.h @@ -108,12 +108,17 @@ public: [[maybe_unused]] const Common::Input::LedStatus& led_status) {} // Sets rumble to a controller - virtual Common::Input::VibrationError SetRumble( + virtual Common::Input::VibrationError SetVibration( [[maybe_unused]] const PadIdentifier& identifier, [[maybe_unused]] const Common::Input::VibrationStatus& vibration) { return Common::Input::VibrationError::NotSupported; } + // Returns true if device supports vibrations + virtual bool IsVibrationEnabled([[maybe_unused]] const PadIdentifier& identifier) { + return false; + } + // Sets polling mode to a controller virtual Common::Input::PollingError SetPollingMode( [[maybe_unused]] const PadIdentifier& identifier, diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp index ca33fb4eb2..fff9731cee 100644 --- a/src/input_common/input_poller.cpp +++ b/src/input_common/input_poller.cpp @@ -763,7 +763,11 @@ public: Common::Input::VibrationError SetVibration( const Common::Input::VibrationStatus& vibration_status) override { - return input_engine->SetRumble(identifier, vibration_status); + return input_engine->SetVibration(identifier, vibration_status); + } + + bool IsVibrationEnabled() override { + return input_engine->IsVibrationEnabled(identifier); } Common::Input::PollingError SetPollingMode(Common::Input::PollingMode polling_mode) override { From 3968faec06a24dfc97f0042591e8adc18823a8d8 Mon Sep 17 00:00:00 2001 From: Lioncash Date: Fri, 21 Oct 2022 01:52:10 -0400 Subject: [PATCH 51/91] k_session_request: Simplify constructor initialization --- src/core/hle/kernel/k_session_request.h | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/core/hle/kernel/k_session_request.h b/src/core/hle/kernel/k_session_request.h index fcf5215975..5a43933cff 100644 --- a/src/core/hle/kernel/k_session_request.h +++ b/src/core/hle/kernel/k_session_request.h @@ -52,8 +52,7 @@ public: }; public: - explicit SessionMappings(KernelCore& kernel_) - : kernel(kernel_), m_mappings(nullptr), m_num_send(), m_num_recv(), m_num_exch() {} + explicit SessionMappings(KernelCore& kernel_) : kernel(kernel_) {} void Initialize() {} void Finalize(); @@ -150,16 +149,14 @@ public: private: KernelCore& kernel; Mapping m_static_mappings[NumStaticMappings]; - Mapping* m_mappings; - u8 m_num_send; - u8 m_num_recv; - u8 m_num_exch; + Mapping* m_mappings{}; + u8 m_num_send{}; + u8 m_num_recv{}; + u8 m_num_exch{}; }; public: - explicit KSessionRequest(KernelCore& kernel_) - : KAutoObject(kernel_), m_mappings(kernel_), m_thread(nullptr), m_server(nullptr), - m_event(nullptr) {} + explicit KSessionRequest(KernelCore& kernel_) : KAutoObject(kernel_), m_mappings(kernel_) {} static KSessionRequest* Create(KernelCore& kernel) { KSessionRequest* req = KSessionRequest::Allocate(kernel); @@ -297,11 +294,11 @@ private: private: SessionMappings m_mappings; - KThread* m_thread; - KProcess* m_server; - KEvent* m_event; - uintptr_t m_address; - size_t m_size; + KThread* m_thread{}; + KProcess* m_server{}; + KEvent* m_event{}; + uintptr_t m_address{}; + size_t m_size{}; }; } // namespace Kernel From 969387a79ab5888c39c5ac8bd298205e700819f8 Mon Sep 17 00:00:00 2001 From: Lioncash Date: Fri, 21 Oct 2022 01:54:31 -0400 Subject: [PATCH 52/91] k_session_request: Turn C-style array into std::array Makes for stronger typing and allows tooling bounds checks provided by the standard library for debugging purposes. --- src/core/hle/kernel/k_session_request.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/hle/kernel/k_session_request.h b/src/core/hle/kernel/k_session_request.h index 5a43933cff..42b1207f08 100644 --- a/src/core/hle/kernel/k_session_request.h +++ b/src/core/hle/kernel/k_session_request.h @@ -3,6 +3,8 @@ #pragma once +#include + #include "core/hle/kernel/k_auto_object.h" #include "core/hle/kernel/k_event.h" #include "core/hle/kernel/k_memory_block.h" @@ -148,7 +150,7 @@ public: private: KernelCore& kernel; - Mapping m_static_mappings[NumStaticMappings]; + std::array m_static_mappings; Mapping* m_mappings{}; u8 m_num_send{}; u8 m_num_recv{}; From f16db300c6117da0286a5504cd3605e6aceaf40f Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Fri, 21 Oct 2022 01:54:44 -0400 Subject: [PATCH 53/91] format_lookup_table: Implement R32_B24G8 with D32_FLOAT_S8_UINT This format is similar to Z32_FLOAT_X24S8_UINT, which is implemented with D32_FLOAT_S8_UINT. Used in Persona 5 Royal --- src/video_core/texture_cache/format_lookup_table.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/video_core/texture_cache/format_lookup_table.cpp b/src/video_core/texture_cache/format_lookup_table.cpp index ad935d3860..08aa8ca339 100644 --- a/src/video_core/texture_cache/format_lookup_table.cpp +++ b/src/video_core/texture_cache/format_lookup_table.cpp @@ -150,6 +150,8 @@ PixelFormat PixelFormatFromTextureInfo(TextureFormat format, ComponentType red, return PixelFormat::D24_UNORM_S8_UINT; case Hash(TextureFormat::D32S8, FLOAT, UINT, UNORM, UNORM, LINEAR): return PixelFormat::D32_FLOAT_S8_UINT; + case Hash(TextureFormat::R32_B24G8, FLOAT, UINT, UNORM, UNORM, LINEAR): + return PixelFormat::D32_FLOAT_S8_UINT; case Hash(TextureFormat::BC1_RGBA, UNORM, LINEAR): return PixelFormat::BC1_RGBA_UNORM; case Hash(TextureFormat::BC1_RGBA, UNORM, SRGB): From 93a7058d8ef62e978a5fc73c1c31f60c33866ee1 Mon Sep 17 00:00:00 2001 From: Lioncash Date: Fri, 21 Oct 2022 01:56:14 -0400 Subject: [PATCH 54/91] k_session_request: Add missing override specifier --- src/core/hle/kernel/k_session_request.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/hle/kernel/k_session_request.h b/src/core/hle/kernel/k_session_request.h index 42b1207f08..e5558bc2c9 100644 --- a/src/core/hle/kernel/k_session_request.h +++ b/src/core/hle/kernel/k_session_request.h @@ -280,7 +280,7 @@ public: private: // NOTE: This is public and virtual in Nintendo's kernel. - void Finalize() { + void Finalize() override { m_mappings.Finalize(); if (m_thread) { From 1f54cd4ac78dd1af48490dcc404bec4adf2876f3 Mon Sep 17 00:00:00 2001 From: FengChen Date: Fri, 21 Oct 2022 15:38:50 +0800 Subject: [PATCH 55/91] video_coare: Reimplementing the maxwell drawing trigger mechanism --- src/video_core/engines/maxwell_3d.cpp | 257 ++++++++---------- src/video_core/engines/maxwell_3d.h | 33 +-- src/video_core/macro/macro_hle.cpp | 47 ++-- src/video_core/macro/macro_interpreter.cpp | 2 +- src/video_core/macro/macro_jit_x64.cpp | 2 +- src/video_core/rasterizer_interface.h | 2 +- .../renderer_opengl/gl_rasterizer.cpp | 5 +- .../renderer_opengl/gl_rasterizer.h | 2 +- .../renderer_vulkan/vk_rasterizer.cpp | 11 +- .../renderer_vulkan/vk_rasterizer.h | 2 +- 10 files changed, 139 insertions(+), 224 deletions(-) diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index 89a9d1f5ac..b41aa6fc1c 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -117,10 +117,15 @@ void Maxwell3D::InitializeRegisterDefaults() { shadow_state = regs; - mme_inline[MAXWELL3D_REG_INDEX(draw.end)] = true; - mme_inline[MAXWELL3D_REG_INDEX(draw.begin)] = true; - mme_inline[MAXWELL3D_REG_INDEX(vertex_buffer.count)] = true; - mme_inline[MAXWELL3D_REG_INDEX(index_buffer.count)] = true; + draw_command[MAXWELL3D_REG_INDEX(draw.end)] = true; + draw_command[MAXWELL3D_REG_INDEX(draw.begin)] = true; + draw_command[MAXWELL3D_REG_INDEX(vertex_buffer.first)] = true; + draw_command[MAXWELL3D_REG_INDEX(vertex_buffer.count)] = true; + draw_command[MAXWELL3D_REG_INDEX(index_buffer.first)] = true; + draw_command[MAXWELL3D_REG_INDEX(index_buffer.count)] = true; + draw_command[MAXWELL3D_REG_INDEX(index_buffer32_first)] = true; + draw_command[MAXWELL3D_REG_INDEX(index_buffer16_first)] = true; + draw_command[MAXWELL3D_REG_INDEX(index_buffer8_first)] = true; } void Maxwell3D::ProcessMacro(u32 method, const u32* base_start, u32 amount, bool is_last_call) { @@ -208,25 +213,6 @@ void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argume return ProcessCBBind(3); case MAXWELL3D_REG_INDEX(bind_groups[4].raw_config): return ProcessCBBind(4); - case MAXWELL3D_REG_INDEX(draw.end): - return DrawArrays(); - case MAXWELL3D_REG_INDEX(index_buffer32_first): - regs.index_buffer.count = regs.index_buffer32_first.count; - regs.index_buffer.first = regs.index_buffer32_first.first; - dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; - return DrawArrays(); - case MAXWELL3D_REG_INDEX(index_buffer16_first): - regs.index_buffer.count = regs.index_buffer16_first.count; - regs.index_buffer.first = regs.index_buffer16_first.first; - dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; - return DrawArrays(); - case MAXWELL3D_REG_INDEX(index_buffer8_first): - regs.index_buffer.count = regs.index_buffer8_first.count; - regs.index_buffer.first = regs.index_buffer8_first.first; - dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; - // a macro calls this one over and over, should it increase instancing? - // Used by Hades and likely other Vulkan games. - return DrawArrays(); case MAXWELL3D_REG_INDEX(topology_override): use_topology_override = true; return; @@ -261,14 +247,13 @@ void Maxwell3D::CallMacroMethod(u32 method, const std::vector& parameters) // Execute the current macro. macro_engine->Execute(macro_positions[entry], parameters); - if (mme_draw.current_mode != MMEDrawMode::Undefined) { - FlushMMEInlineDraw(); - } + + ProcessDeferredDraw(); } void Maxwell3D::CallMethod(u32 method, u32 method_argument, bool is_last_call) { - // It is an error to write to a register other than the current macro's ARG register before it - // has finished execution. + // It is an error to write to a register other than the current macro's ARG register before + // it has finished execution. if (executing_macro != 0) { ASSERT(method == executing_macro + 1); } @@ -283,9 +268,16 @@ void Maxwell3D::CallMethod(u32 method, u32 method_argument, bool is_last_call) { ASSERT_MSG(method < Regs::NUM_REGS, "Invalid Maxwell3D register, increase the size of the Regs structure"); - const u32 argument = ProcessShadowRam(method, method_argument); - ProcessDirtyRegisters(method, argument); - ProcessMethodCall(method, argument, method_argument, is_last_call); + if (draw_command[method]) { + regs.reg_array[method] = method_argument; + deferred_draw_method.push_back(method); + } else { + ProcessDeferredDraw(); + + const u32 argument = ProcessShadowRam(method, method_argument); + ProcessDirtyRegisters(method, argument); + ProcessMethodCall(method, argument, method_argument, is_last_call); + } } void Maxwell3D::CallMultiMethod(u32 method, const u32* base_start, u32 amount, @@ -326,55 +318,6 @@ void Maxwell3D::CallMultiMethod(u32 method, const u32* base_start, u32 amount, } } -void Maxwell3D::StepInstance(const MMEDrawMode expected_mode, const u32 count) { - if (mme_draw.current_mode == MMEDrawMode::Undefined) { - if (mme_draw.gl_begin_consume) { - mme_draw.current_mode = expected_mode; - mme_draw.current_count = count; - mme_draw.instance_count = 1; - mme_draw.gl_begin_consume = false; - mme_draw.gl_end_count = 0; - } - return; - } else { - if (mme_draw.current_mode == expected_mode && count == mme_draw.current_count && - mme_draw.instance_mode && mme_draw.gl_begin_consume) { - mme_draw.instance_count++; - mme_draw.gl_begin_consume = false; - return; - } else { - FlushMMEInlineDraw(); - } - } - // Tail call in case it needs to retry. - StepInstance(expected_mode, count); -} - -void Maxwell3D::CallMethodFromMME(u32 method, u32 method_argument) { - if (mme_inline[method]) { - regs.reg_array[method] = method_argument; - if (method == MAXWELL3D_REG_INDEX(vertex_buffer.count) || - method == MAXWELL3D_REG_INDEX(index_buffer.count)) { - const MMEDrawMode expected_mode = method == MAXWELL3D_REG_INDEX(vertex_buffer.count) - ? MMEDrawMode::Array - : MMEDrawMode::Indexed; - StepInstance(expected_mode, method_argument); - } else if (method == MAXWELL3D_REG_INDEX(draw.begin)) { - mme_draw.instance_mode = - (regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::Subsequent) || - (regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::Unchanged); - mme_draw.gl_begin_consume = true; - } else { - mme_draw.gl_end_count++; - } - } else { - if (mme_draw.current_mode != MMEDrawMode::Undefined) { - FlushMMEInlineDraw(); - } - CallMethod(method, method_argument, true); - } -} - void Maxwell3D::ProcessTopologyOverride() { using PrimitiveTopology = Maxwell3D::Regs::PrimitiveTopology; using PrimitiveTopologyOverride = Maxwell3D::Regs::PrimitiveTopologyOverride; @@ -404,41 +347,6 @@ void Maxwell3D::ProcessTopologyOverride() { } } -void Maxwell3D::FlushMMEInlineDraw() { - LOG_TRACE(HW_GPU, "called, topology={}, count={}", regs.draw.topology.Value(), - regs.vertex_buffer.count); - ASSERT_MSG(!(regs.index_buffer.count && regs.vertex_buffer.count), "Both indexed and direct?"); - ASSERT(mme_draw.instance_count == mme_draw.gl_end_count); - - // Both instance configuration registers can not be set at the same time. - ASSERT_MSG(regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::First || - regs.draw.instance_id != Maxwell3D::Regs::Draw::InstanceId::Unchanged, - "Illegal combination of instancing parameters"); - - ProcessTopologyOverride(); - - const bool is_indexed = mme_draw.current_mode == MMEDrawMode::Indexed; - if (ShouldExecute()) { - rasterizer->Draw(is_indexed, true); - } - - // TODO(bunnei): Below, we reset vertex count so that we can use these registers to determine if - // the game is trying to draw indexed or direct mode. This needs to be verified on HW still - - // it's possible that it is incorrect and that there is some other register used to specify the - // drawing mode. - if (is_indexed) { - regs.index_buffer.count = 0; - } else { - regs.vertex_buffer.count = 0; - } - mme_draw.current_mode = MMEDrawMode::Undefined; - mme_draw.current_count = 0; - mme_draw.instance_count = 0; - mme_draw.instance_mode = false; - mme_draw.gl_begin_consume = false; - mme_draw.gl_end_count = 0; -} - void Maxwell3D::ProcessMacroUpload(u32 data) { macro_engine->AddCode(regs.load_mme.instruction_ptr++, data); } @@ -576,42 +484,6 @@ void Maxwell3D::ProcessSyncPoint() { } } -void Maxwell3D::DrawArrays() { - LOG_TRACE(HW_GPU, "called, topology={}, count={}", regs.draw.topology.Value(), - regs.vertex_buffer.count); - ASSERT_MSG(!(regs.index_buffer.count && regs.vertex_buffer.count), "Both indexed and direct?"); - - // Both instance configuration registers can not be set at the same time. - ASSERT_MSG(regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::First || - regs.draw.instance_id != Maxwell3D::Regs::Draw::InstanceId::Unchanged, - "Illegal combination of instancing parameters"); - - ProcessTopologyOverride(); - - if (regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::Subsequent) { - // Increment the current instance *before* drawing. - state.current_instance++; - } else if (regs.draw.instance_id != Maxwell3D::Regs::Draw::InstanceId::Unchanged) { - // Reset the current instance to 0. - state.current_instance = 0; - } - - const bool is_indexed{regs.index_buffer.count && !regs.vertex_buffer.count}; - if (ShouldExecute()) { - rasterizer->Draw(is_indexed, false); - } - - // TODO(bunnei): Below, we reset vertex count so that we can use these registers to determine if - // the game is trying to draw indexed or direct mode. This needs to be verified on HW still - - // it's possible that it is incorrect and that there is some other register used to specify the - // drawing mode. - if (is_indexed) { - regs.index_buffer.count = 0; - } else { - regs.vertex_buffer.count = 0; - } -} - std::optional Maxwell3D::GetQueryResult() { switch (regs.report_semaphore.query.report) { case Regs::ReportSemaphore::Report::Payload: @@ -694,4 +566,87 @@ void Maxwell3D::ProcessClearBuffers() { rasterizer->Clear(); } +void Maxwell3D::ProcessDeferredDraw() { + auto method_count = deferred_draw_method.size(); + if (method_count) { + enum class DrawMode { + Undefined, + General, + Instance, + }; + DrawMode draw_mode{DrawMode::Undefined}; + u32 instance_count = 1; + + auto first_method = deferred_draw_method[0]; + if (MAXWELL3D_REG_INDEX(draw.begin) == first_method) { + // The minimum number of methods for drawing must be greater than or equal to + // 3[draw.begin->vertex(index)count->draw.end] to avoid errors in index mode drawing + if (method_count < 3) { + return; + } + draw_mode = + (regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::Subsequent) || + (regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::Unchanged) + ? DrawMode::Instance + : DrawMode::General; + } else if (MAXWELL3D_REG_INDEX(index_buffer32_first) == first_method || + MAXWELL3D_REG_INDEX(index_buffer16_first) == first_method || + MAXWELL3D_REG_INDEX(index_buffer8_first) == first_method) { + draw_mode = DrawMode::General; + } + + // Drawing will only begin with draw.begin or index_buffer method, other methods directly + // clear + if (draw_mode == DrawMode::Undefined) { + deferred_draw_method.clear(); + return; + } + + if (draw_mode == DrawMode::Instance) { + ASSERT_MSG(deferred_draw_method.size() % 4 == 0, "Instance mode method size error"); + instance_count = static_cast(deferred_draw_method.size()) / 4; + } else { + if (MAXWELL3D_REG_INDEX(index_buffer32_first) == first_method) { + regs.index_buffer.count = regs.index_buffer32_first.count; + regs.index_buffer.first = regs.index_buffer32_first.first; + dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; + } else if (MAXWELL3D_REG_INDEX(index_buffer32_first) == first_method) { + regs.index_buffer.count = regs.index_buffer16_first.count; + regs.index_buffer.first = regs.index_buffer16_first.first; + dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; + } else if (MAXWELL3D_REG_INDEX(index_buffer32_first) == first_method) { + regs.index_buffer.count = regs.index_buffer8_first.count; + regs.index_buffer.first = regs.index_buffer8_first.first; + dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; + } + } + + LOG_TRACE(HW_GPU, "called, topology={}, count={}", regs.draw.topology.Value(), + regs.vertex_buffer.count); + + ASSERT_MSG(!(regs.index_buffer.count && regs.vertex_buffer.count), + "Both indexed and direct?"); + + // Both instance configuration registers can not be set at the same time. + ASSERT_MSG(regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::First || + regs.draw.instance_id != Maxwell3D::Regs::Draw::InstanceId::Unchanged, + "Illegal combination of instancing parameters"); + + ProcessTopologyOverride(); + + const bool is_indexed = regs.index_buffer.count && !regs.vertex_buffer.count; + if (ShouldExecute()) { + rasterizer->Draw(is_indexed, instance_count); + } + + if (is_indexed) { + regs.index_buffer.count = 0; + } else { + regs.vertex_buffer.count = 0; + } + + deferred_draw_method.clear(); + } +} + } // namespace Tegra::Engines diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index 75e3b868dc..1472e88717 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -3048,8 +3048,6 @@ public: }; std::array shader_stages; - - u32 current_instance = 0; ///< Current instance to be used to simulate instanced rendering. }; State state{}; @@ -3064,11 +3062,6 @@ public: void CallMultiMethod(u32 method, const u32* base_start, u32 amount, u32 methods_pending) override; - /// Write the value to the register identified by method. - void CallMethodFromMME(u32 method, u32 method_argument); - - void FlushMMEInlineDraw(); - bool ShouldExecute() const { return execute_on; } @@ -3081,21 +3074,6 @@ public: return *rasterizer; } - enum class MMEDrawMode : u32 { - Undefined, - Array, - Indexed, - }; - - struct MMEDrawState { - MMEDrawMode current_mode{MMEDrawMode::Undefined}; - u32 current_count{}; - u32 instance_count{}; - bool instance_mode{}; - bool gl_begin_consume{}; - u32 gl_end_count{}; - } mme_draw; - struct DirtyState { using Flags = std::bitset::max()>; using Table = std::array; @@ -3164,14 +3142,10 @@ private: /// Handles a write to the CB_BIND register. void ProcessCBBind(size_t stage_index); - /// Handles a write to the VERTEX_END_GL register, triggering a draw. - void DrawArrays(); - /// Handles use of topology overrides (e.g., to avoid using a topology assigned from a macro) void ProcessTopologyOverride(); - // Handles a instance drawcall from MME - void StepInstance(MMEDrawMode expected_mode, u32 count); + void ProcessDeferredDraw(); /// Returns a query's value or an empty object if the value will be deferred through a cache. std::optional GetQueryResult(); @@ -3184,8 +3158,6 @@ private: /// Start offsets of each macro in macro_memory std::array macro_positions{}; - std::array mme_inline{}; - /// Macro method that is currently being executed / being fed parameters. u32 executing_macro = 0; /// Parameters that have been submitted to the macro call so far. @@ -3198,6 +3170,9 @@ private: bool execute_on{true}; bool use_topology_override{false}; + + std::array draw_command{}; + std::vector deferred_draw_method; }; #define ASSERT_REG_POSITION(field_name, position) \ diff --git a/src/video_core/macro/macro_hle.cpp b/src/video_core/macro/macro_hle.cpp index 8a8adbb42f..f896591bfe 100644 --- a/src/video_core/macro/macro_hle.cpp +++ b/src/video_core/macro/macro_hle.cpp @@ -22,35 +22,29 @@ void HLE_771BB18C62444DA0(Engines::Maxwell3D& maxwell3d, const std::vector& maxwell3d.regs.draw.topology.Assign( static_cast(parameters[0] & 0x3ffffff)); maxwell3d.regs.global_base_instance_index = parameters[5]; - maxwell3d.mme_draw.instance_count = instance_count; maxwell3d.regs.global_base_vertex_index = parameters[3]; maxwell3d.regs.index_buffer.count = parameters[1]; maxwell3d.regs.index_buffer.first = parameters[4]; if (maxwell3d.ShouldExecute()) { - maxwell3d.Rasterizer().Draw(true, true); + maxwell3d.Rasterizer().Draw(true, instance_count); } maxwell3d.regs.index_buffer.count = 0; - maxwell3d.mme_draw.instance_count = 0; - maxwell3d.mme_draw.current_mode = Engines::Maxwell3D::MMEDrawMode::Undefined; } void HLE_0D61FC9FAAC9FCAD(Engines::Maxwell3D& maxwell3d, const std::vector& parameters) { - const u32 count = (maxwell3d.GetRegisterValue(0xD1B) & parameters[2]); + const u32 instance_count = (maxwell3d.GetRegisterValue(0xD1B) & parameters[2]); maxwell3d.regs.vertex_buffer.first = parameters[3]; maxwell3d.regs.vertex_buffer.count = parameters[1]; maxwell3d.regs.global_base_instance_index = parameters[4]; maxwell3d.regs.draw.topology.Assign( static_cast(parameters[0])); - maxwell3d.mme_draw.instance_count = count; if (maxwell3d.ShouldExecute()) { - maxwell3d.Rasterizer().Draw(false, true); + maxwell3d.Rasterizer().Draw(false, instance_count); } maxwell3d.regs.vertex_buffer.count = 0; - maxwell3d.mme_draw.instance_count = 0; - maxwell3d.mme_draw.current_mode = Engines::Maxwell3D::MMEDrawMode::Undefined; } void HLE_0217920100488FF7(Engines::Maxwell3D& maxwell3d, const std::vector& parameters) { @@ -63,24 +57,21 @@ void HLE_0217920100488FF7(Engines::Maxwell3D& maxwell3d, const std::vector& maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; maxwell3d.regs.global_base_vertex_index = element_base; maxwell3d.regs.global_base_instance_index = base_instance; - maxwell3d.mme_draw.instance_count = instance_count; - maxwell3d.CallMethodFromMME(0x8e3, 0x640); - maxwell3d.CallMethodFromMME(0x8e4, element_base); - maxwell3d.CallMethodFromMME(0x8e5, base_instance); + maxwell3d.CallMethod(0x8e3, 0x640, true); + maxwell3d.CallMethod(0x8e4, element_base, true); + maxwell3d.CallMethod(0x8e5, base_instance, true); maxwell3d.regs.draw.topology.Assign( static_cast(parameters[0])); if (maxwell3d.ShouldExecute()) { - maxwell3d.Rasterizer().Draw(true, true); + maxwell3d.Rasterizer().Draw(true, instance_count); } maxwell3d.regs.vertex_id_base = 0x0; maxwell3d.regs.index_buffer.count = 0; maxwell3d.regs.global_base_vertex_index = 0x0; maxwell3d.regs.global_base_instance_index = 0x0; - maxwell3d.mme_draw.instance_count = 0; - maxwell3d.CallMethodFromMME(0x8e3, 0x640); - maxwell3d.CallMethodFromMME(0x8e4, 0x0); - maxwell3d.CallMethodFromMME(0x8e5, 0x0); - maxwell3d.mme_draw.current_mode = Engines::Maxwell3D::MMEDrawMode::Undefined; + maxwell3d.CallMethod(0x8e3, 0x640, true); + maxwell3d.CallMethod(0x8e4, 0x0, true); + maxwell3d.CallMethod(0x8e5, 0x0, true); } // Multidraw Indirect @@ -91,11 +82,9 @@ void HLE_3F5E74B9C9A50164(Engines::Maxwell3D& maxwell3d, const std::vector& maxwell3d.regs.index_buffer.count = 0; maxwell3d.regs.global_base_vertex_index = 0x0; maxwell3d.regs.global_base_instance_index = 0x0; - maxwell3d.mme_draw.instance_count = 0; - maxwell3d.CallMethodFromMME(0x8e3, 0x640); - maxwell3d.CallMethodFromMME(0x8e4, 0x0); - maxwell3d.CallMethodFromMME(0x8e5, 0x0); - maxwell3d.mme_draw.current_mode = Engines::Maxwell3D::MMEDrawMode::Undefined; + maxwell3d.CallMethod(0x8e3, 0x640, true); + maxwell3d.CallMethod(0x8e4, 0x0, true); + maxwell3d.CallMethod(0x8e5, 0x0, true); maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; }); const u32 start_indirect = parameters[0]; @@ -127,15 +116,13 @@ void HLE_3F5E74B9C9A50164(Engines::Maxwell3D& maxwell3d, const std::vector& maxwell3d.regs.index_buffer.count = num_vertices; maxwell3d.regs.global_base_vertex_index = base_vertex; maxwell3d.regs.global_base_instance_index = base_instance; - maxwell3d.mme_draw.instance_count = instance_count; - maxwell3d.CallMethodFromMME(0x8e3, 0x640); - maxwell3d.CallMethodFromMME(0x8e4, base_vertex); - maxwell3d.CallMethodFromMME(0x8e5, base_instance); + maxwell3d.CallMethod(0x8e3, 0x640, true); + maxwell3d.CallMethod(0x8e4, base_vertex, true); + maxwell3d.CallMethod(0x8e5, base_instance, true); maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; if (maxwell3d.ShouldExecute()) { - maxwell3d.Rasterizer().Draw(true, true); + maxwell3d.Rasterizer().Draw(true, instance_count); } - maxwell3d.mme_draw.current_mode = Engines::Maxwell3D::MMEDrawMode::Undefined; } } diff --git a/src/video_core/macro/macro_interpreter.cpp b/src/video_core/macro/macro_interpreter.cpp index f670b1bca8..c0d32c112c 100644 --- a/src/video_core/macro/macro_interpreter.cpp +++ b/src/video_core/macro/macro_interpreter.cpp @@ -335,7 +335,7 @@ void MacroInterpreterImpl::SetMethodAddress(u32 address) { } void MacroInterpreterImpl::Send(u32 value) { - maxwell3d.CallMethodFromMME(method_address.address, value); + maxwell3d.CallMethod(method_address.address, value, true); // Increment the method address by the method increment. method_address.address.Assign(method_address.address.Value() + method_address.increment.Value()); diff --git a/src/video_core/macro/macro_jit_x64.cpp b/src/video_core/macro/macro_jit_x64.cpp index a302a9603d..25c1ce798c 100644 --- a/src/video_core/macro/macro_jit_x64.cpp +++ b/src/video_core/macro/macro_jit_x64.cpp @@ -346,7 +346,7 @@ void MacroJITx64Impl::Compile_Read(Macro::Opcode opcode) { } void Send(Engines::Maxwell3D* maxwell3d, Macro::MethodAddress method_address, u32 value) { - maxwell3d->CallMethodFromMME(method_address.address, value); + maxwell3d->CallMethod(method_address.address, value, true); } void MacroJITx64Impl::Compile_Send(Xbyak::Reg32 value) { diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h index d2d40884c2..1cbfef0902 100644 --- a/src/video_core/rasterizer_interface.h +++ b/src/video_core/rasterizer_interface.h @@ -40,7 +40,7 @@ public: virtual ~RasterizerInterface() = default; /// Dispatches a draw invocation - virtual void Draw(bool is_indexed, bool is_instanced) = 0; + virtual void Draw(bool is_indexed, u32 instance_count) = 0; /// Clear the current framebuffer virtual void Clear() = 0; diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index e5c09a969b..21bac6ebf2 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -205,7 +205,7 @@ void RasterizerOpenGL::Clear() { ++num_queued_commands; } -void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) { +void RasterizerOpenGL::Draw(bool is_indexed, u32 instance_count) { MICROPROFILE_SCOPE(OpenGL_Drawing); SCOPE_EXIT({ gpu.TickWork(); }); @@ -228,8 +228,7 @@ void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) { BeginTransformFeedback(pipeline, primitive_mode); const GLuint base_instance = static_cast(maxwell3d->regs.global_base_instance_index); - const GLsizei num_instances = - static_cast(is_instanced ? maxwell3d->mme_draw.instance_count : 1); + const GLsizei num_instances = static_cast(instance_count); if (is_indexed) { const GLint base_vertex = static_cast(maxwell3d->regs.global_base_vertex_index); const GLsizei num_vertices = static_cast(maxwell3d->regs.index_buffer.count); diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 45131b7852..c93ba3b42b 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -68,7 +68,7 @@ public: StateTracker& state_tracker_); ~RasterizerOpenGL() override; - void Draw(bool is_indexed, bool is_instanced) override; + void Draw(bool is_indexed, u32 instance_count) override; void Clear() override; void DispatchCompute() override; void ResetCounter(VideoCore::QueryType type) override; diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 47dfb45a1d..9a7d90b2a4 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -127,11 +127,10 @@ VkRect2D GetScissorState(const Maxwell& regs, size_t index, u32 up_scale = 1, u3 return scissor; } -DrawParams MakeDrawParams(const Maxwell& regs, u32 num_instances, bool is_instanced, - bool is_indexed) { +DrawParams MakeDrawParams(const Maxwell& regs, u32 num_instances, bool is_indexed) { DrawParams params{ .base_instance = regs.global_base_instance_index, - .num_instances = is_instanced ? num_instances : 1, + .num_instances = num_instances, .base_vertex = is_indexed ? regs.global_base_vertex_index : regs.vertex_buffer.first, .num_vertices = is_indexed ? regs.index_buffer.count : regs.vertex_buffer.count, .first_index = is_indexed ? regs.index_buffer.first : 0, @@ -177,7 +176,7 @@ RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra RasterizerVulkan::~RasterizerVulkan() = default; -void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) { +void RasterizerVulkan::Draw(bool is_indexed, u32 instance_count) { MICROPROFILE_SCOPE(Vulkan_Drawing); SCOPE_EXIT({ gpu.TickWork(); }); @@ -199,8 +198,8 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) { UpdateDynamicStates(); const auto& regs{maxwell3d->regs}; - const u32 num_instances{maxwell3d->mme_draw.instance_count}; - const DrawParams draw_params{MakeDrawParams(regs, num_instances, is_instanced, is_indexed)}; + const u32 num_instances{instance_count}; + const DrawParams draw_params{MakeDrawParams(regs, num_instances, is_indexed)}; scheduler.Record([draw_params](vk::CommandBuffer cmdbuf) { if (draw_params.is_indexed) { cmdbuf.DrawIndexed(draw_params.num_vertices, draw_params.num_instances, diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index 4cde3c983a..b3a182588d 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -64,7 +64,7 @@ public: StateTracker& state_tracker_, Scheduler& scheduler_); ~RasterizerVulkan() override; - void Draw(bool is_indexed, bool is_instanced) override; + void Draw(bool is_indexed, u32 instance_count) override; void Clear() override; void DispatchCompute() override; void ResetCounter(VideoCore::QueryType type) override; From 82fdfb33ac7cc6da4f24c5a8bd88855287e93fcc Mon Sep 17 00:00:00 2001 From: Narr the Reg Date: Fri, 21 Oct 2022 21:28:08 -0500 Subject: [PATCH 56/91] service: nfp: remove unnecessary include --- src/core/hle/service/nfp/nfp_device.cpp | 1 + src/core/hle/service/nfp/nfp_device.h | 1 - src/core/hle/service/nfp/nfp_types.h | 5 ----- src/core/hle/service/nfp/nfp_user.cpp | 3 --- src/core/hle/service/nfp/nfp_user.h | 8 ++++++-- 5 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/core/hle/service/nfp/nfp_device.cpp b/src/core/hle/service/nfp/nfp_device.cpp index 76f8a267a1..4e6b8e558d 100644 --- a/src/core/hle/service/nfp/nfp_device.cpp +++ b/src/core/hle/service/nfp/nfp_device.cpp @@ -17,6 +17,7 @@ #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/k_event.h" #include "core/hle/service/mii/mii_manager.h" +#include "core/hle/service/mii/types.h" #include "core/hle/service/nfp/amiibo_crypto.h" #include "core/hle/service/nfp/nfp.h" #include "core/hle/service/nfp/nfp_device.h" diff --git a/src/core/hle/service/nfp/nfp_device.h b/src/core/hle/service/nfp/nfp_device.h index a5b72cf193..76d0e9ae46 100644 --- a/src/core/hle/service/nfp/nfp_device.h +++ b/src/core/hle/service/nfp/nfp_device.h @@ -8,7 +8,6 @@ #include "common/common_funcs.h" #include "core/hle/service/kernel_helpers.h" -#include "core/hle/service/mii/types.h" #include "core/hle/service/nfp/nfp_types.h" #include "core/hle/service/service.h" diff --git a/src/core/hle/service/nfp/nfp_types.h b/src/core/hle/service/nfp/nfp_types.h index c09f9ddb6a..63d5917cb0 100644 --- a/src/core/hle/service/nfp/nfp_types.h +++ b/src/core/hle/service/nfp/nfp_types.h @@ -17,11 +17,6 @@ enum class ServiceType : u32 { System, }; -enum class State : u32 { - NonInitialized, - Initialized, -}; - enum class DeviceState : u32 { Initialized, SearchingForTag, diff --git a/src/core/hle/service/nfp/nfp_user.cpp b/src/core/hle/service/nfp/nfp_user.cpp index 4ed53b5344..33e2ef5183 100644 --- a/src/core/hle/service/nfp/nfp_user.cpp +++ b/src/core/hle/service/nfp/nfp_user.cpp @@ -6,12 +6,9 @@ #include "common/logging/log.h" #include "core/core.h" -#include "core/hid/emulated_controller.h" -#include "core/hid/hid_core.h" #include "core/hid/hid_types.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/k_event.h" -#include "core/hle/service/mii/mii_manager.h" #include "core/hle/service/nfp/nfp_device.h" #include "core/hle/service/nfp/nfp_result.h" #include "core/hle/service/nfp/nfp_user.h" diff --git a/src/core/hle/service/nfp/nfp_user.h b/src/core/hle/service/nfp/nfp_user.h index 68c60ae82e..47aff3695a 100644 --- a/src/core/hle/service/nfp/nfp_user.h +++ b/src/core/hle/service/nfp/nfp_user.h @@ -4,8 +4,7 @@ #pragma once #include "core/hle/service/kernel_helpers.h" -#include "core/hle/service/nfp/nfp.h" -#include "core/hle/service/nfp/nfp_types.h" +#include "core/hle/service/service.h" namespace Service::NFP { class NfpDevice; @@ -15,6 +14,11 @@ public: explicit IUser(Core::System& system_); private: + enum class State : u32 { + NonInitialized, + Initialized, + }; + void Initialize(Kernel::HLERequestContext& ctx); void Finalize(Kernel::HLERequestContext& ctx); void ListDevices(Kernel::HLERequestContext& ctx); From 3e0aaeba98e3278b26f1d6be5dd013a953ff784f Mon Sep 17 00:00:00 2001 From: Narr the Reg Date: Fri, 21 Oct 2022 22:20:27 -0500 Subject: [PATCH 57/91] service: nfp: Allow amiibos without keys --- src/core/hle/service/nfp/amiibo_crypto.cpp | 8 +++++++- src/core/hle/service/nfp/amiibo_crypto.h | 3 +++ src/core/hle/service/nfp/nfp_device.cpp | 8 ++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/core/hle/service/nfp/amiibo_crypto.cpp b/src/core/hle/service/nfp/amiibo_crypto.cpp index c32a6816b7..167e295720 100644 --- a/src/core/hle/service/nfp/amiibo_crypto.cpp +++ b/src/core/hle/service/nfp/amiibo_crypto.cpp @@ -9,6 +9,7 @@ #include #include "common/fs/file.h" +#include "common/fs/fs.h" #include "common/fs/path_util.h" #include "common/logging/log.h" #include "core/hle/service/mii/mii_manager.h" @@ -279,7 +280,7 @@ bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info) { Common::FS::FileType::BinaryFile}; if (!keys_file.IsOpen()) { - LOG_ERROR(Service_NFP, "No keys detected"); + LOG_ERROR(Service_NFP, "Failed to open key file"); return false; } @@ -295,6 +296,11 @@ bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info) { return true; } +bool IsKeyAvailable() { + const auto yuzu_keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir); + return Common::FS::Exists(yuzu_keys_dir / "key_retail.bin"); +} + bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& tag_data) { InternalKey locked_secret{}; InternalKey unfixed_info{}; diff --git a/src/core/hle/service/nfp/amiibo_crypto.h b/src/core/hle/service/nfp/amiibo_crypto.h index 0175ced919..1fa61174ed 100644 --- a/src/core/hle/service/nfp/amiibo_crypto.h +++ b/src/core/hle/service/nfp/amiibo_crypto.h @@ -91,6 +91,9 @@ void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& ou /// Loads both amiibo keys from key_retail.bin bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info); +/// Returns true if key_retail.bin exist +bool IsKeyAvailable(); + /// Decodes encripted amiibo data returns true if output is valid bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& tag_data); diff --git a/src/core/hle/service/nfp/nfp_device.cpp b/src/core/hle/service/nfp/nfp_device.cpp index 4e6b8e558d..b196725601 100644 --- a/src/core/hle/service/nfp/nfp_device.cpp +++ b/src/core/hle/service/nfp/nfp_device.cpp @@ -234,6 +234,14 @@ Result NfpDevice::Mount(MountTarget mount_target_) { return NotAnAmiibo; } + // Mark amiibos as read only when keys are missing + if (!AmiiboCrypto::IsKeyAvailable()) { + LOG_ERROR(Service_NFP, "No keys detected"); + device_state = DeviceState::TagMounted; + mount_target = MountTarget::Rom; + return ResultSuccess; + } + if (!AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) { LOG_ERROR(Service_NFP, "Can't decode amiibo {}", device_state); return CorruptedData; From 2f90694797e30088820937855acf613bdcf27247 Mon Sep 17 00:00:00 2001 From: FengChen Date: Fri, 21 Oct 2022 19:14:22 +0800 Subject: [PATCH 58/91] video_core: Implement maxwell inline_index method --- src/video_core/engines/maxwell_3d.cpp | 170 ++++++++++-------- src/video_core/engines/maxwell_3d.h | 13 +- .../renderer_opengl/gl_rasterizer.cpp | 12 ++ .../renderer_opengl/gl_rasterizer.h | 2 + .../renderer_vulkan/vk_rasterizer.cpp | 15 ++ .../renderer_vulkan/vk_rasterizer.h | 2 + 6 files changed, 135 insertions(+), 79 deletions(-) diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index b41aa6fc1c..25fcdb1e35 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -126,6 +126,9 @@ void Maxwell3D::InitializeRegisterDefaults() { draw_command[MAXWELL3D_REG_INDEX(index_buffer32_first)] = true; draw_command[MAXWELL3D_REG_INDEX(index_buffer16_first)] = true; draw_command[MAXWELL3D_REG_INDEX(index_buffer8_first)] = true; + draw_command[MAXWELL3D_REG_INDEX(draw_inline_index)] = true; + draw_command[MAXWELL3D_REG_INDEX(inline_index_2x16.even)] = true; + draw_command[MAXWELL3D_REG_INDEX(inline_index_4x8.index0)] = true; } void Maxwell3D::ProcessMacro(u32 method, const u32* base_start, u32 amount, bool is_last_call) { @@ -271,6 +274,23 @@ void Maxwell3D::CallMethod(u32 method, u32 method_argument, bool is_last_call) { if (draw_command[method]) { regs.reg_array[method] = method_argument; deferred_draw_method.push_back(method); + auto u32_to_u8 = [&](const u32 argument) { + inline_index_draw_indexes.push_back(static_cast(argument & 0x000000ff)); + inline_index_draw_indexes.push_back(static_cast((argument & 0x0000ff00) >> 8)); + inline_index_draw_indexes.push_back(static_cast((argument & 0x00ff0000) >> 16)); + inline_index_draw_indexes.push_back(static_cast((argument & 0xff000000) >> 24)); + }; + if (MAXWELL3D_REG_INDEX(draw_inline_index) == method) { + u32_to_u8(method_argument); + } else if (MAXWELL3D_REG_INDEX(inline_index_2x16.even) == method) { + u32_to_u8(regs.inline_index_2x16.even); + u32_to_u8(regs.inline_index_2x16.odd); + } else if (MAXWELL3D_REG_INDEX(inline_index_4x8.index0) == method) { + u32_to_u8(regs.inline_index_4x8.index0); + u32_to_u8(regs.inline_index_4x8.index1); + u32_to_u8(regs.inline_index_4x8.index2); + u32_to_u8(regs.inline_index_4x8.index3); + } } else { ProcessDeferredDraw(); @@ -567,86 +587,94 @@ void Maxwell3D::ProcessClearBuffers() { } void Maxwell3D::ProcessDeferredDraw() { - auto method_count = deferred_draw_method.size(); - if (method_count) { - enum class DrawMode { - Undefined, - General, - Instance, - }; - DrawMode draw_mode{DrawMode::Undefined}; - u32 instance_count = 1; + if (deferred_draw_method.empty()) { + return; + } - auto first_method = deferred_draw_method[0]; - if (MAXWELL3D_REG_INDEX(draw.begin) == first_method) { - // The minimum number of methods for drawing must be greater than or equal to - // 3[draw.begin->vertex(index)count->draw.end] to avoid errors in index mode drawing - if (method_count < 3) { - return; - } - draw_mode = - (regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::Subsequent) || - (regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::Unchanged) - ? DrawMode::Instance - : DrawMode::General; - } else if (MAXWELL3D_REG_INDEX(index_buffer32_first) == first_method || - MAXWELL3D_REG_INDEX(index_buffer16_first) == first_method || - MAXWELL3D_REG_INDEX(index_buffer8_first) == first_method) { - draw_mode = DrawMode::General; - } + enum class DrawMode { + Undefined, + General, + Instance, + }; + DrawMode draw_mode{DrawMode::Undefined}; + u32 instance_count = 1; - // Drawing will only begin with draw.begin or index_buffer method, other methods directly - // clear - if (draw_mode == DrawMode::Undefined) { - deferred_draw_method.clear(); + auto first_method = deferred_draw_method[0]; + if (MAXWELL3D_REG_INDEX(draw.begin) == first_method) { + // The minimum number of methods for drawing must be greater than or equal to + // 3[draw.begin->vertex(index)count->draw.end] to avoid errors in index mode drawing + if (deferred_draw_method.size() < 3) { return; } + draw_mode = (regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::Subsequent) || + (regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::Unchanged) + ? DrawMode::Instance + : DrawMode::General; + } else if (MAXWELL3D_REG_INDEX(index_buffer32_first) == first_method || + MAXWELL3D_REG_INDEX(index_buffer16_first) == first_method || + MAXWELL3D_REG_INDEX(index_buffer8_first) == first_method) { + draw_mode = DrawMode::General; + } - if (draw_mode == DrawMode::Instance) { - ASSERT_MSG(deferred_draw_method.size() % 4 == 0, "Instance mode method size error"); - instance_count = static_cast(deferred_draw_method.size()) / 4; + // Drawing will only begin with draw.begin or index_buffer method, other methods directly + // clear + if (draw_mode == DrawMode::Undefined) { + deferred_draw_method.clear(); + return; + } + + if (draw_mode == DrawMode::Instance) { + ASSERT_MSG(deferred_draw_method.size() % 4 == 0, "Instance mode method size error"); + instance_count = static_cast(deferred_draw_method.size()) / 4; + } else { + if (MAXWELL3D_REG_INDEX(index_buffer32_first) == first_method) { + regs.index_buffer.count = regs.index_buffer32_first.count; + regs.index_buffer.first = regs.index_buffer32_first.first; + dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; + } else if (MAXWELL3D_REG_INDEX(index_buffer32_first) == first_method) { + regs.index_buffer.count = regs.index_buffer16_first.count; + regs.index_buffer.first = regs.index_buffer16_first.first; + dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; + } else if (MAXWELL3D_REG_INDEX(index_buffer32_first) == first_method) { + regs.index_buffer.count = regs.index_buffer8_first.count; + regs.index_buffer.first = regs.index_buffer8_first.first; + dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; } else { - if (MAXWELL3D_REG_INDEX(index_buffer32_first) == first_method) { - regs.index_buffer.count = regs.index_buffer32_first.count; - regs.index_buffer.first = regs.index_buffer32_first.first; - dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; - } else if (MAXWELL3D_REG_INDEX(index_buffer32_first) == first_method) { - regs.index_buffer.count = regs.index_buffer16_first.count; - regs.index_buffer.first = regs.index_buffer16_first.first; - dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; - } else if (MAXWELL3D_REG_INDEX(index_buffer32_first) == first_method) { - regs.index_buffer.count = regs.index_buffer8_first.count; - regs.index_buffer.first = regs.index_buffer8_first.first; - dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; + auto second_method = deferred_draw_method[1]; + if (MAXWELL3D_REG_INDEX(draw_inline_index) == second_method || + MAXWELL3D_REG_INDEX(inline_index_2x16.even) == second_method || + MAXWELL3D_REG_INDEX(inline_index_4x8.index0) == second_method) { + regs.index_buffer.count = static_cast(inline_index_draw_indexes.size() / 4); + regs.index_buffer.format = Regs::IndexFormat::UnsignedInt; } } - - LOG_TRACE(HW_GPU, "called, topology={}, count={}", regs.draw.topology.Value(), - regs.vertex_buffer.count); - - ASSERT_MSG(!(regs.index_buffer.count && regs.vertex_buffer.count), - "Both indexed and direct?"); - - // Both instance configuration registers can not be set at the same time. - ASSERT_MSG(regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::First || - regs.draw.instance_id != Maxwell3D::Regs::Draw::InstanceId::Unchanged, - "Illegal combination of instancing parameters"); - - ProcessTopologyOverride(); - - const bool is_indexed = regs.index_buffer.count && !regs.vertex_buffer.count; - if (ShouldExecute()) { - rasterizer->Draw(is_indexed, instance_count); - } - - if (is_indexed) { - regs.index_buffer.count = 0; - } else { - regs.vertex_buffer.count = 0; - } - - deferred_draw_method.clear(); } + + LOG_TRACE(HW_GPU, "called, topology={}, count={}", regs.draw.topology.Value(), + regs.vertex_buffer.count); + + ASSERT_MSG(!(regs.index_buffer.count && regs.vertex_buffer.count), "Both indexed and direct?"); + + // Both instance configuration registers can not be set at the same time. + ASSERT_MSG(regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::First || + regs.draw.instance_id != Maxwell3D::Regs::Draw::InstanceId::Unchanged, + "Illegal combination of instancing parameters"); + + ProcessTopologyOverride(); + + const bool is_indexed = regs.index_buffer.count && !regs.vertex_buffer.count; + if (ShouldExecute()) { + rasterizer->Draw(is_indexed, instance_count); + } + + if (is_indexed) { + regs.index_buffer.count = 0; + } else { + regs.vertex_buffer.count = 0; + } + + deferred_draw_method.clear(); + inline_index_draw_indexes.clear(); } } // namespace Tegra::Engines diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index 1472e88717..bd23ebc12c 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -1739,14 +1739,11 @@ public: Footprint_1x1_Virtual = 2, }; - struct InlineIndex4x8Align { + struct InlineIndex4x8 { union { BitField<0, 30, u32> count; BitField<30, 2, u32> start; }; - }; - - struct InlineIndex4x8Index { union { BitField<0, 8, u32> index0; BitField<8, 8, u32> index1; @@ -2836,8 +2833,7 @@ public: u32 depth_write_enabled; ///< 0x12E8 u32 alpha_test_enabled; ///< 0x12EC INSERT_PADDING_BYTES_NOINIT(0x10); - InlineIndex4x8Align inline_index_4x8_align; ///< 0x1300 - InlineIndex4x8Index inline_index_4x8_index; ///< 0x1304 + InlineIndex4x8 inline_index_4x8; ///< 0x1300 D3DCullMode d3d_cull_mode; ///< 0x1308 ComparisonOp depth_test_func; ///< 0x130C f32 alpha_test_ref; ///< 0x1310 @@ -3083,6 +3079,8 @@ public: Tables tables{}; } dirty; + std::vector inline_index_draw_indexes; + private: void InitializeRegisterDefaults(); @@ -3377,8 +3375,7 @@ ASSERT_REG_POSITION(alpha_to_coverage_dither, 0x12E0); ASSERT_REG_POSITION(blend_per_target_enabled, 0x12E4); ASSERT_REG_POSITION(depth_write_enabled, 0x12E8); ASSERT_REG_POSITION(alpha_test_enabled, 0x12EC); -ASSERT_REG_POSITION(inline_index_4x8_align, 0x1300); -ASSERT_REG_POSITION(inline_index_4x8_index, 0x1304); +ASSERT_REG_POSITION(inline_index_4x8, 0x1300); ASSERT_REG_POSITION(d3d_cull_mode, 0x1308); ASSERT_REG_POSITION(depth_test_func, 0x130C); ASSERT_REG_POSITION(alpha_test_ref, 0x1310); diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 21bac6ebf2..1590b21dee 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -222,6 +222,8 @@ void RasterizerOpenGL::Draw(bool is_indexed, u32 instance_count) { pipeline->SetEngine(maxwell3d, gpu_memory); pipeline->Configure(is_indexed); + BindInlineIndexBuffer(); + SyncState(); const GLenum primitive_mode = MaxwellToGL::PrimitiveTopology(maxwell3d->regs.draw.topology); @@ -1128,6 +1130,16 @@ void RasterizerOpenGL::ReleaseChannel(s32 channel_id) { query_cache.EraseChannel(channel_id); } +void RasterizerOpenGL::BindInlineIndexBuffer() { + if (maxwell3d->inline_index_draw_indexes.empty()) { + return; + } + const auto data_count = static_cast(maxwell3d->inline_index_draw_indexes.size()); + auto buffer = Buffer(buffer_cache_runtime, *this, 0, data_count); + buffer.ImmediateUpload(0, maxwell3d->inline_index_draw_indexes); + buffer_cache_runtime.BindIndexBuffer(buffer, 0, data_count); +} + AccelerateDMA::AccelerateDMA(BufferCache& buffer_cache_) : buffer_cache{buffer_cache_} {} bool AccelerateDMA::BufferCopy(GPUVAddr src_address, GPUVAddr dest_address, u64 amount) { diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index c93ba3b42b..793e0d608d 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -199,6 +199,8 @@ private: /// End a transform feedback void EndTransformFeedback(); + void BindInlineIndexBuffer(); + Tegra::GPU& gpu; const Device& device; diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 9a7d90b2a4..9f05a7a18c 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -193,6 +193,8 @@ void RasterizerVulkan::Draw(bool is_indexed, u32 instance_count) { pipeline->SetEngine(maxwell3d, gpu_memory); pipeline->Configure(is_indexed); + BindInlineIndexBuffer(); + BeginTransformFeedback(); UpdateDynamicStates(); @@ -1008,4 +1010,17 @@ void RasterizerVulkan::ReleaseChannel(s32 channel_id) { query_cache.EraseChannel(channel_id); } +void RasterizerVulkan::BindInlineIndexBuffer() { + if (maxwell3d->inline_index_draw_indexes.empty()) { + return; + } + const auto data_count = static_cast(maxwell3d->inline_index_draw_indexes.size()); + auto buffer = buffer_cache_runtime.UploadStagingBuffer(data_count); + std::memcpy(buffer.mapped_span.data(), maxwell3d->inline_index_draw_indexes.data(), data_count); + buffer_cache_runtime.BindIndexBuffer( + maxwell3d->regs.draw.topology, maxwell3d->regs.index_buffer.format, + maxwell3d->regs.index_buffer.first, maxwell3d->regs.index_buffer.count, buffer.buffer, + static_cast(buffer.offset), data_count); +} + } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index b3a182588d..e2fdc7611d 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -141,6 +141,8 @@ private: void UpdateVertexInput(Tegra::Engines::Maxwell3D::Regs& regs); + void BindInlineIndexBuffer(); + Tegra::GPU& gpu; ScreenInfo& screen_info; From 496695618a14d8e850fdd38c1b4e5159ee674bf5 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Fri, 21 Oct 2022 02:34:05 -0400 Subject: [PATCH 59/91] CMakeLists: Treat -Wall and -Wextra as errors --- src/CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3575a3cb3e..3b48627373 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -99,8 +99,9 @@ if (MSVC) set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /MANIFEST:NO /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE) else() add_compile_options( - -Wall + -Werror=all -Werror=array-bounds + -Werror=extra -Werror=implicit-fallthrough -Werror=missing-declarations -Werror=missing-field-initializers @@ -112,8 +113,7 @@ else() -Werror=unused-function -Werror=unused-result -Werror=unused-variable - -Wextra - -Wmissing-declarations + -Wno-attributes -Wno-invalid-offsetof -Wno-unused-parameter From 91c410c91833c3c4a466ab1ff6ca8ba74c0529cf Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Fri, 21 Oct 2022 02:34:05 -0400 Subject: [PATCH 60/91] CMakeLists: Consolidate all unused warnings into -Wunused --- src/CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3b48627373..f774c2791e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -110,13 +110,13 @@ else() -Werror=sign-compare -Werror=switch -Werror=uninitialized - -Werror=unused-function - -Werror=unused-result - -Werror=unused-variable + -Werror=unused -Wno-attributes -Wno-invalid-offsetof -Wno-unused-parameter + + $<$:-Wno-unused-private-field> ) if (ARCHITECTURE_x86_64) From 93297d14d8fc5c8f73e8ccba5ca989590a453c05 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Fri, 21 Oct 2022 02:34:06 -0400 Subject: [PATCH 61/91] CMakeLists: Remove all redundant warnings These are already explicitly or implicitly set in src/CMakeLists.txt --- src/CMakeLists.txt | 7 ------- src/audio_core/CMakeLists.txt | 10 ---------- src/common/CMakeLists.txt | 2 -- src/core/CMakeLists.txt | 9 ++------- src/input_common/CMakeLists.txt | 5 ----- src/shader_recompiler/CMakeLists.txt | 10 ++-------- src/video_core/CMakeLists.txt | 8 +------- 7 files changed, 5 insertions(+), 46 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f774c2791e..71853eaad2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -100,16 +100,9 @@ if (MSVC) else() add_compile_options( -Werror=all - -Werror=array-bounds -Werror=extra - -Werror=implicit-fallthrough -Werror=missing-declarations - -Werror=missing-field-initializers - -Werror=reorder -Werror=shadow - -Werror=sign-compare - -Werror=switch - -Werror=uninitialized -Werror=unused -Wno-attributes diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index 144f1bab29..01b4ffd888 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -206,20 +206,10 @@ if (MSVC) /we4244 # 'conversion': conversion from 'type1' to 'type2', possible loss of data /we4245 # 'conversion': conversion from 'type1' to 'type2', signed/unsigned mismatch /we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data - /we4456 # Declaration of 'identifier' hides previous local declaration - /we4457 # Declaration of 'identifier' hides function parameter - /we4458 # Declaration of 'identifier' hides class member - /we4459 # Declaration of 'identifier' hides global declaration ) else() target_compile_options(audio_core PRIVATE -Werror=conversion - -Werror=ignored-qualifiers - -Werror=shadow - -Werror=unused-variable - - $<$:-Werror=unused-but-set-parameter> - $<$:-Werror=unused-but-set-variable> -Wno-sign-conversion ) diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 46cf75fdeb..6da547a3fa 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -160,8 +160,6 @@ if (MSVC) ) else() target_compile_options(common PRIVATE - -Werror - $<$:-fsized-deallocation> ) endif() diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 055bea6419..72da9cb56d 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -778,15 +778,10 @@ if (MSVC) else() target_compile_options(core PRIVATE -Werror=conversion - -Werror=ignored-qualifiers - - $<$:-Werror=class-memaccess> - $<$:-Werror=unused-but-set-parameter> - $<$:-Werror=unused-but-set-variable> - - $<$:-fsized-deallocation> -Wno-sign-conversion + + $<$:-fsized-deallocation> ) endif() diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index 2cf9eb97f4..7935ac655f 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -48,12 +48,7 @@ if (MSVC) ) else() target_compile_options(input_common PRIVATE - -Werror -Werror=conversion - -Werror=ignored-qualifiers - $<$:-Werror=unused-but-set-parameter> - $<$:-Werror=unused-but-set-variable> - -Werror=unused-variable ) endif() diff --git a/src/shader_recompiler/CMakeLists.txt b/src/shader_recompiler/CMakeLists.txt index af8e51fe82..cbc1daf772 100644 --- a/src/shader_recompiler/CMakeLists.txt +++ b/src/shader_recompiler/CMakeLists.txt @@ -242,23 +242,17 @@ if (MSVC) target_compile_options(shader_recompiler PRIVATE /W4 /WX - /we4018 # 'expression' : signed/unsigned mismatch + + /we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data /we4244 # 'argument' : conversion from 'type1' to 'type2', possible loss of data (floating-point) /we4245 # 'conversion' : conversion from 'type1' to 'type2', signed/unsigned mismatch /we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data - /we4267 # 'var' : conversion from 'size_t' to 'type', possible loss of data - /we4305 # 'context' : truncation from 'type1' to 'type2' /we4800 # Implicit conversion from 'type' to bool. Possible information loss /we4826 # Conversion from 'type1' to 'type2' is sign-extended. This may cause unexpected runtime behavior. ) else() target_compile_options(shader_recompiler PRIVATE - -Werror -Werror=conversion - -Werror=ignored-qualifiers - $<$:-Werror=unused-but-set-parameter> - $<$:-Werror=unused-but-set-variable> - -Werror=unused-variable # Bracket depth determines maximum size of a fold expression in Clang since 9c9974c3ccb6. # And this in turns limits the size of a std::array. diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index cb8b46edf0..1069919691 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -279,14 +279,8 @@ if (MSVC) else() target_compile_options(video_core PRIVATE -Werror=conversion - -Wno-error=sign-conversion - -Werror=pessimizing-move - -Werror=redundant-move - -Werror=type-limits - $<$:-Werror=class-memaccess> - $<$:-Werror=unused-but-set-parameter> - $<$:-Werror=unused-but-set-variable> + -Wno-sign-conversion ) endif() From e6ab1f673b88b1af6bd966886249c7824ec5dbd4 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Fri, 21 Oct 2022 02:34:06 -0400 Subject: [PATCH 62/91] general: Enforce C4800 everywhere except in video_core --- src/CMakeLists.txt | 1 + src/audio_core/CMakeLists.txt | 1 + src/common/CMakeLists.txt | 6 +++++ src/common/bit_field.h | 15 +++++++++---- src/core/CMakeLists.txt | 1 + src/core/file_sys/program_metadata.cpp | 2 +- src/core/hid/emulated_controller.cpp | 22 +++++++++---------- src/core/hle/kernel/svc.cpp | 4 ++-- src/core/hle/service/am/applets/applets.h | 2 +- src/core/hle/service/hid/controllers/npad.cpp | 20 ++++++++--------- src/input_common/CMakeLists.txt | 1 + src/input_common/drivers/sdl_driver.cpp | 4 ++-- src/input_common/input_poller.cpp | 18 +++++++-------- src/shader_recompiler/CMakeLists.txt | 1 - 14 files changed, 57 insertions(+), 41 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 71853eaad2..cfd80886cb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -76,6 +76,7 @@ if (MSVC) /we4549 # 'operator1': operator before comma has no effect; did you intend 'operator2'? /we4555 # Expression has no effect; expected expression with side-effect /we4715 # 'function': not all control paths return a value + /we4826 # Conversion from 'type1' to 'type2' is sign-extended. This may cause unexpected runtime behavior. /we4834 # Discarding return value of function with 'nodiscard' attribute /we5038 # data member 'member1' will be initialized after data member 'member2' /we5245 # 'function': unreferenced function with internal linkage has been removed diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index 01b4ffd888..0a1f3bf187 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -206,6 +206,7 @@ if (MSVC) /we4244 # 'conversion': conversion from 'type1' to 'type2', possible loss of data /we4245 # 'conversion': conversion from 'type1' to 'type2', signed/unsigned mismatch /we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data + /we4800 # Implicit conversion from 'type' to bool. Possible information loss ) else() target_compile_options(audio_core PRIVATE diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 6da547a3fa..043f27fb1c 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -157,6 +157,12 @@ if (MSVC) target_compile_options(common PRIVATE /W4 /WX + + /we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data + /we4244 # 'conversion': conversion from 'type1' to 'type2', possible loss of data + /we4245 # 'conversion': conversion from 'type1' to 'type2', signed/unsigned mismatch + /we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data + /we4800 # Implicit conversion from 'type' to bool. Possible information loss ) else() target_compile_options(common PRIVATE diff --git a/src/common/bit_field.h b/src/common/bit_field.h index 7e1df62b1c..e4e58ea450 100644 --- a/src/common/bit_field.h +++ b/src/common/bit_field.h @@ -141,10 +141,6 @@ public: constexpr BitField(BitField&&) noexcept = default; constexpr BitField& operator=(BitField&&) noexcept = default; - [[nodiscard]] constexpr operator T() const { - return Value(); - } - constexpr void Assign(const T& value) { #ifdef _MSC_VER storage = static_cast((storage & ~mask) | FormatValue(value)); @@ -162,6 +158,17 @@ public: return ExtractValue(storage); } + template + [[nodiscard]] constexpr ConvertedToType As() const { + static_assert(!std::is_same_v, + "Unnecessary cast. Use Value() instead."); + return static_cast(Value()); + } + + [[nodiscard]] constexpr operator T() const { + return Value(); + } + [[nodiscard]] constexpr explicit operator bool() const { return Value() != 0; } diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 72da9cb56d..113e663b5f 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -774,6 +774,7 @@ if (MSVC) /we4244 # 'conversion': conversion from 'type1' to 'type2', possible loss of data /we4245 # 'conversion': conversion from 'type1' to 'type2', signed/unsigned mismatch /we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data + /we4800 # Implicit conversion from 'type' to bool. Possible information loss ) else() target_compile_options(core PRIVATE diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp index 08d489eab6..f00479bd35 100644 --- a/src/core/file_sys/program_metadata.cpp +++ b/src/core/file_sys/program_metadata.cpp @@ -127,7 +127,7 @@ void ProgramMetadata::LoadManual(bool is_64_bit, ProgramAddressSpaceType address } bool ProgramMetadata::Is64BitProgram() const { - return npdm_header.has_64_bit_instructions; + return npdm_header.has_64_bit_instructions.As(); } ProgramAddressSpaceType ProgramMetadata::GetAddressSpaceType() const { diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp index 025f1c78e4..57eff72fec 100644 --- a/src/core/hid/emulated_controller.cpp +++ b/src/core/hid/emulated_controller.cpp @@ -1158,27 +1158,27 @@ bool EmulatedController::IsControllerSupported(bool use_temporary_value) const { const auto type = is_configuring && use_temporary_value ? tmp_npad_type : npad_type; switch (type) { case NpadStyleIndex::ProController: - return supported_style_tag.fullkey; + return supported_style_tag.fullkey.As(); case NpadStyleIndex::Handheld: - return supported_style_tag.handheld; + return supported_style_tag.handheld.As(); case NpadStyleIndex::JoyconDual: - return supported_style_tag.joycon_dual; + return supported_style_tag.joycon_dual.As(); case NpadStyleIndex::JoyconLeft: - return supported_style_tag.joycon_left; + return supported_style_tag.joycon_left.As(); case NpadStyleIndex::JoyconRight: - return supported_style_tag.joycon_right; + return supported_style_tag.joycon_right.As(); case NpadStyleIndex::GameCube: - return supported_style_tag.gamecube; + return supported_style_tag.gamecube.As(); case NpadStyleIndex::Pokeball: - return supported_style_tag.palma; + return supported_style_tag.palma.As(); case NpadStyleIndex::NES: - return supported_style_tag.lark; + return supported_style_tag.lark.As(); case NpadStyleIndex::SNES: - return supported_style_tag.lucia; + return supported_style_tag.lucia.As(); case NpadStyleIndex::N64: - return supported_style_tag.lagoon; + return supported_style_tag.lagoon.As(); case NpadStyleIndex::SegaGenesis: - return supported_style_tag.lager; + return supported_style_tag.lager.As(); default: return false; } diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index b07ae3f027..4aca5b27dd 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -751,8 +751,8 @@ static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) { } system.GetReporter().SaveSvcBreakReport( - static_cast(break_reason.break_type.Value()), break_reason.signal_debugger, info1, - info2, has_dumped_buffer ? std::make_optional(debug_buffer) : std::nullopt); + static_cast(break_reason.break_type.Value()), break_reason.signal_debugger.As(), + info1, info2, has_dumped_buffer ? std::make_optional(debug_buffer) : std::nullopt); if (!break_reason.signal_debugger) { LOG_CRITICAL( diff --git a/src/core/hle/service/am/applets/applets.h b/src/core/hle/service/am/applets/applets.h index e78a576576..12c6a5b1a4 100644 --- a/src/core/hle/service/am/applets/applets.h +++ b/src/core/hle/service/am/applets/applets.h @@ -164,7 +164,7 @@ protected: u32_le size; u32_le library_version; u32_le theme_color; - u8 play_startup_sound; + bool play_startup_sound; u64_le system_tick; }; static_assert(sizeof(CommonArguments) == 0x20, "CommonArguments has incorrect size."); diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index ba8a1f7866..3b26e96dea 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -1502,25 +1502,25 @@ bool Controller_NPad::IsControllerSupported(Core::HID::NpadStyleIndex controller Core::HID::NpadStyleTag style = GetSupportedStyleSet(); switch (controller) { case Core::HID::NpadStyleIndex::ProController: - return style.fullkey; + return style.fullkey.As(); case Core::HID::NpadStyleIndex::JoyconDual: - return style.joycon_dual; + return style.joycon_dual.As(); case Core::HID::NpadStyleIndex::JoyconLeft: - return style.joycon_left; + return style.joycon_left.As(); case Core::HID::NpadStyleIndex::JoyconRight: - return style.joycon_right; + return style.joycon_right.As(); case Core::HID::NpadStyleIndex::GameCube: - return style.gamecube; + return style.gamecube.As(); case Core::HID::NpadStyleIndex::Pokeball: - return style.palma; + return style.palma.As(); case Core::HID::NpadStyleIndex::NES: - return style.lark; + return style.lark.As(); case Core::HID::NpadStyleIndex::SNES: - return style.lucia; + return style.lucia.As(); case Core::HID::NpadStyleIndex::N64: - return style.lagoon; + return style.lagoon.As(); case Core::HID::NpadStyleIndex::SegaGenesis: - return style.lager; + return style.lager.As(); default: return false; } diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index 7935ac655f..d2730bdc16 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -45,6 +45,7 @@ if (MSVC) /we4244 # 'conversion': conversion from 'type1' to 'type2', possible loss of data /we4245 # 'conversion': conversion from 'type1' to 'type2', signed/unsigned mismatch /we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data + /we4800 # Implicit conversion from 'type' to bool. Possible information loss ) else() target_compile_options(input_common PRIVATE diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp index b72e4b3975..c175a88538 100644 --- a/src/input_common/drivers/sdl_driver.cpp +++ b/src/input_common/drivers/sdl_driver.cpp @@ -40,8 +40,8 @@ public: void EnableMotion() { if (sdl_controller) { SDL_GameController* controller = sdl_controller.get(); - has_accel = SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL); - has_gyro = SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO); + has_accel = SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) == SDL_TRUE; + has_gyro = SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) == SDL_TRUE; if (has_accel) { SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE); } diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp index ca33fb4eb2..ccc3076ca4 100644 --- a/src/input_common/input_poller.cpp +++ b/src/input_common/input_poller.cpp @@ -797,8 +797,8 @@ std::unique_ptr InputFactory::CreateButtonDevice( const auto button_id = params.Get("button", 0); const auto keyboard_key = params.Get("code", 0); - const auto toggle = params.Get("toggle", false); - const auto inverted = params.Get("inverted", false); + const auto toggle = params.Get("toggle", false) != 0; + const auto inverted = params.Get("inverted", false) != 0; input_engine->PreSetController(identifier); input_engine->PreSetButton(identifier, button_id); input_engine->PreSetButton(identifier, keyboard_key); @@ -820,8 +820,8 @@ std::unique_ptr InputFactory::CreateHatButtonDevice( const auto button_id = params.Get("hat", 0); const auto direction = input_engine->GetHatButtonId(params.Get("direction", "")); - const auto toggle = params.Get("toggle", false); - const auto inverted = params.Get("inverted", false); + const auto toggle = params.Get("toggle", false) != 0; + const auto inverted = params.Get("inverted", false) != 0; input_engine->PreSetController(identifier); input_engine->PreSetHatButton(identifier, button_id); @@ -879,7 +879,7 @@ std::unique_ptr InputFactory::CreateAnalogDevice( .threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f), .offset = std::clamp(params.Get("offset", 0.0f), -1.0f, 1.0f), .inverted = params.Get("invert", "+") == "-", - .toggle = static_cast(params.Get("toggle", false)), + .toggle = params.Get("toggle", false) != 0, }; input_engine->PreSetController(identifier); input_engine->PreSetAxis(identifier, axis); @@ -895,8 +895,8 @@ std::unique_ptr InputFactory::CreateTriggerDevice( }; const auto button = params.Get("button", 0); - const auto toggle = params.Get("toggle", false); - const auto inverted = params.Get("inverted", false); + const auto toggle = params.Get("toggle", false) != 0; + const auto inverted = params.Get("inverted", false) != 0; const auto axis = params.Get("axis", 0); const Common::Input::AnalogProperties properties = { @@ -926,8 +926,8 @@ std::unique_ptr InputFactory::CreateTouchDevice( }; const auto button = params.Get("button", 0); - const auto toggle = params.Get("toggle", false); - const auto inverted = params.Get("inverted", false); + const auto toggle = params.Get("toggle", false) != 0; + const auto inverted = params.Get("inverted", false) != 0; const auto axis_x = params.Get("axis_x", 0); const Common::Input::AnalogProperties properties_x = { diff --git a/src/shader_recompiler/CMakeLists.txt b/src/shader_recompiler/CMakeLists.txt index cbc1daf772..967da07916 100644 --- a/src/shader_recompiler/CMakeLists.txt +++ b/src/shader_recompiler/CMakeLists.txt @@ -248,7 +248,6 @@ if (MSVC) /we4245 # 'conversion' : conversion from 'type1' to 'type2', signed/unsigned mismatch /we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data /we4800 # Implicit conversion from 'type' to bool. Possible information loss - /we4826 # Conversion from 'type1' to 'type2' is sign-extended. This may cause unexpected runtime behavior. ) else() target_compile_options(shader_recompiler PRIVATE From f3c40f4a208117ceb49396a381702b01c40efdb0 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Fri, 21 Oct 2022 02:34:06 -0400 Subject: [PATCH 63/91] CMakeLists: Treat MSVC warnings as errors --- src/CMakeLists.txt | 2 ++ src/common/CMakeLists.txt | 1 - src/input_common/CMakeLists.txt | 1 - src/shader_recompiler/CMakeLists.txt | 1 - 4 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index cfd80886cb..397c1fa23f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -58,6 +58,8 @@ if (MSVC) # Warnings /W3 + /WX + /we4018 # 'expression': signed/unsigned mismatch /we4062 # Enumerator 'identifier' in a switch of enum 'enumeration' is not handled /we4101 # 'identifier': unreferenced local variable diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 043f27fb1c..72c406fe11 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -156,7 +156,6 @@ if (MSVC) ) target_compile_options(common PRIVATE /W4 - /WX /we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data /we4244 # 'conversion': conversion from 'type1' to 'type2', possible loss of data diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index d2730bdc16..bff75338e4 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -39,7 +39,6 @@ add_library(input_common STATIC if (MSVC) target_compile_options(input_common PRIVATE /W4 - /WX /we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data /we4244 # 'conversion': conversion from 'type1' to 'type2', possible loss of data diff --git a/src/shader_recompiler/CMakeLists.txt b/src/shader_recompiler/CMakeLists.txt index 967da07916..bde1c1329a 100644 --- a/src/shader_recompiler/CMakeLists.txt +++ b/src/shader_recompiler/CMakeLists.txt @@ -241,7 +241,6 @@ target_link_libraries(shader_recompiler PUBLIC common fmt::fmt sirit) if (MSVC) target_compile_options(shader_recompiler PRIVATE /W4 - /WX /we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data /we4244 # 'argument' : conversion from 'type1' to 'type2', possible loss of data (floating-point) From bad30259515d0e3ce438ae8d6236216e393f463e Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Fri, 21 Oct 2022 02:34:06 -0400 Subject: [PATCH 64/91] decoders: Use 2's complement instead of unary - Resolves C4146 on MSVC --- src/video_core/textures/decoders.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp index 52d067a2dc..fd1a4b9877 100644 --- a/src/video_core/textures/decoders.cpp +++ b/src/video_core/textures/decoders.cpp @@ -21,7 +21,7 @@ constexpr u32 pdep(u32 value) { u32 m = mask; for (u32 bit = 1; m; bit += bit) { if (value & bit) - result |= m & -m; + result |= m & (~m + 1); m &= m - 1; } return result; From cae108404a88a37bd767b46b6162a6e711ee805a Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Fri, 21 Oct 2022 02:34:06 -0400 Subject: [PATCH 65/91] CMakeLists: Remove redundant warnings These warnings are already included in /W3. --- src/CMakeLists.txt | 6 ------ src/common/CMakeLists.txt | 2 -- src/input_common/CMakeLists.txt | 2 -- src/shader_recompiler/CMakeLists.txt | 2 -- 4 files changed, 12 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 397c1fa23f..0bec3c53b3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -60,13 +60,9 @@ if (MSVC) /W3 /WX - /we4018 # 'expression': signed/unsigned mismatch /we4062 # Enumerator 'identifier' in a switch of enum 'enumeration' is not handled - /we4101 # 'identifier': unreferenced local variable /we4189 # 'identifier': local variable is initialized but not referenced /we4265 # 'class': class has virtual functions, but destructor is not virtual - /we4267 # 'var': conversion from 'size_t' to 'type', possible loss of data - /we4305 # 'context': truncation from 'type1' to 'type2' /we4388 # 'expression': signed/unsigned mismatch /we4389 # 'operator': signed/unsigned mismatch /we4456 # Declaration of 'identifier' hides previous local declaration @@ -77,9 +73,7 @@ if (MSVC) /we4547 # 'operator': operator before comma has no effect; expected operator with side-effect /we4549 # 'operator1': operator before comma has no effect; did you intend 'operator2'? /we4555 # Expression has no effect; expected expression with side-effect - /we4715 # 'function': not all control paths return a value /we4826 # Conversion from 'type1' to 'type2' is sign-extended. This may cause unexpected runtime behavior. - /we4834 # Discarding return value of function with 'nodiscard' attribute /we5038 # data member 'member1' will be initialized after data member 'member2' /we5245 # 'function': unreferenced function with internal linkage has been removed ) diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 72c406fe11..c0555f840c 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -158,8 +158,6 @@ if (MSVC) /W4 /we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data - /we4244 # 'conversion': conversion from 'type1' to 'type2', possible loss of data - /we4245 # 'conversion': conversion from 'type1' to 'type2', signed/unsigned mismatch /we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data /we4800 # Implicit conversion from 'type' to bool. Possible information loss ) diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index bff75338e4..cc6f0ffc0c 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -41,8 +41,6 @@ if (MSVC) /W4 /we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data - /we4244 # 'conversion': conversion from 'type1' to 'type2', possible loss of data - /we4245 # 'conversion': conversion from 'type1' to 'type2', signed/unsigned mismatch /we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data /we4800 # Implicit conversion from 'type' to bool. Possible information loss ) diff --git a/src/shader_recompiler/CMakeLists.txt b/src/shader_recompiler/CMakeLists.txt index bde1c1329a..bcdd60db9c 100644 --- a/src/shader_recompiler/CMakeLists.txt +++ b/src/shader_recompiler/CMakeLists.txt @@ -243,8 +243,6 @@ if (MSVC) /W4 /we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data - /we4244 # 'argument' : conversion from 'type1' to 'type2', possible loss of data (floating-point) - /we4245 # 'conversion' : conversion from 'type1' to 'type2', signed/unsigned mismatch /we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data /we4800 # Implicit conversion from 'type' to bool. Possible information loss ) From 3822e313232039af810b588646b724e19bc29bfc Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Fri, 21 Oct 2022 02:34:07 -0400 Subject: [PATCH 66/91] CMakeLists: Disable C4100 and C4324 Disabling C4100 is similar to -Wno-unused-parameter --- CMakeLists.txt | 4 ++-- src/CMakeLists.txt | 3 +++ src/common/bounded_threadsafe_queue.h | 9 --------- .../backend/glasm/emit_glasm_not_implemented.cpp | 4 ---- .../backend/glsl/emit_glsl_not_implemented.cpp | 4 ---- 5 files changed, 5 insertions(+), 19 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 957df54f54..b625743ea7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -541,9 +541,9 @@ add_definitions(-DBOOST_ERROR_CODE_HEADER_ONLY # Adjustments for MSVC + Ninja if (MSVC AND CMAKE_GENERATOR STREQUAL "Ninja") add_compile_options( - /wd4711 # function 'function' selected for automatic inline expansion /wd4464 # relative include path contains '..' - /wd4820 # 'identifier1': '4' bytes padding added after data member 'identifier2' + /wd4711 # function 'function' selected for automatic inline expansion + /wd4820 # 'bytes' bytes padding added after construct 'member_name' ) endif() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0bec3c53b3..c94b78eb70 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -76,6 +76,9 @@ if (MSVC) /we4826 # Conversion from 'type1' to 'type2' is sign-extended. This may cause unexpected runtime behavior. /we5038 # data member 'member1' will be initialized after data member 'member2' /we5245 # 'function': unreferenced function with internal linkage has been removed + + /wd4100 # 'identifier': unreferenced formal parameter + /wd4324 # 'struct_name': structure was padded due to __declspec(align()) ) if (USE_CCACHE) diff --git a/src/common/bounded_threadsafe_queue.h b/src/common/bounded_threadsafe_queue.h index 7e465549bd..21217801e6 100644 --- a/src/common/bounded_threadsafe_queue.h +++ b/src/common/bounded_threadsafe_queue.h @@ -21,11 +21,6 @@ constexpr size_t hardware_interference_size = std::hardware_destructive_interfer constexpr size_t hardware_interference_size = 64; #endif -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4324) -#endif - template class MPSCQueue { public: @@ -160,8 +155,4 @@ private: static_assert(std::is_nothrow_destructible_v, "T must be nothrow destructible"); }; -#ifdef _MSC_VER -#pragma warning(pop) -#endif - } // namespace Common diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_not_implemented.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_not_implemented.cpp index 7094d8e42d..1f4ffdd624 100644 --- a/src/shader_recompiler/backend/glasm/emit_glasm_not_implemented.cpp +++ b/src/shader_recompiler/backend/glasm/emit_glasm_not_implemented.cpp @@ -5,10 +5,6 @@ #include "shader_recompiler/backend/glasm/glasm_emit_context.h" #include "shader_recompiler/frontend/ir/value.h" -#ifdef _MSC_VER -#pragma warning(disable : 4100) -#endif - namespace Shader::Backend::GLASM { #define NotImplemented() throw NotImplementedException("GLASM instruction {}", __LINE__) diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_not_implemented.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_not_implemented.cpp index b03a8ba1e5..9f1ed95a47 100644 --- a/src/shader_recompiler/backend/glsl/emit_glsl_not_implemented.cpp +++ b/src/shader_recompiler/backend/glsl/emit_glsl_not_implemented.cpp @@ -7,10 +7,6 @@ #include "shader_recompiler/backend/glsl/glsl_emit_context.h" #include "shader_recompiler/frontend/ir/value.h" -#ifdef _MSC_VER -#pragma warning(disable : 4100) -#endif - namespace Shader::Backend::GLSL { void EmitGetRegister(EmitContext& ctx) { From b02c3f2314a5561803f873ddd2d2f4dc024c1ed4 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Fri, 21 Oct 2022 02:34:07 -0400 Subject: [PATCH 67/91] CMakeLists: Enforce C5233 on MSVC This is similar to Clang's -Wunused-lambda-capture --- src/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c94b78eb70..9ff96c044d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -75,6 +75,7 @@ if (MSVC) /we4555 # Expression has no effect; expected expression with side-effect /we4826 # Conversion from 'type1' to 'type2' is sign-extended. This may cause unexpected runtime behavior. /we5038 # data member 'member1' will be initialized after data member 'member2' + /we5233 # explicit lambda capture 'identifier' is not used /we5245 # 'function': unreferenced function with internal linkage has been removed /wd4100 # 'identifier': unreferenced formal parameter From 347432524c179af443ef0f377abb0e44a84ec1e7 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Fri, 21 Oct 2022 02:34:07 -0400 Subject: [PATCH 68/91] ipc_helpers: Ignore GCC compiler warnings only on GCC Clang and ICC for whatever reason also defines __GNUC__. Exclude them from this check. --- src/core/hle/ipc_helpers.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h index aa27be7678..18fde8bd6d 100644 --- a/src/core/hle/ipc_helpers.h +++ b/src/core/hle/ipc_helpers.h @@ -406,7 +406,7 @@ inline s32 RequestParser::Pop() { } // Ignore the -Wclass-memaccess warning on memcpy for non-trivially default constructible objects. -#if defined(__GNUC__) +#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wclass-memaccess" #endif @@ -417,7 +417,7 @@ void RequestParser::PopRaw(T& value) { std::memcpy(&value, cmdbuf + index, sizeof(T)); index += (sizeof(T) + 3) / 4; // round up to word length } -#if defined(__GNUC__) +#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) #pragma GCC diagnostic pop #endif From 6908ea2284cdf2beeb1d623a6600fe2fc22753a0 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Fri, 21 Oct 2022 02:34:07 -0400 Subject: [PATCH 69/91] general: Resolve -Wclass-memaccess --- src/audio_core/renderer/behavior/info_updater.cpp | 2 +- src/audio_core/renderer/command/effect/biquad_filter.cpp | 2 +- .../renderer/command/effect/multi_tap_biquad_filter.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/audio_core/renderer/behavior/info_updater.cpp b/src/audio_core/renderer/behavior/info_updater.cpp index c0a307b895..574cf09828 100644 --- a/src/audio_core/renderer/behavior/info_updater.cpp +++ b/src/audio_core/renderer/behavior/info_updater.cpp @@ -91,7 +91,7 @@ Result InfoUpdater::UpdateVoices(VoiceContext& voice_context, voice_info.Initialize(); for (u32 channel = 0; channel < in_param.channel_count; channel++) { - std::memset(voice_states[channel], 0, sizeof(VoiceState)); + *voice_states[channel] = {}; } } diff --git a/src/audio_core/renderer/command/effect/biquad_filter.cpp b/src/audio_core/renderer/command/effect/biquad_filter.cpp index 1baae74fd6..edb30ce724 100644 --- a/src/audio_core/renderer/command/effect/biquad_filter.cpp +++ b/src/audio_core/renderer/command/effect/biquad_filter.cpp @@ -94,7 +94,7 @@ void BiquadFilterCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor void BiquadFilterCommand::Process(const ADSP::CommandListProcessor& processor) { auto state_{reinterpret_cast(state)}; if (needs_init) { - std::memset(state_, 0, sizeof(VoiceState::BiquadFilterState)); + *state_ = {}; } auto input_buffer{ diff --git a/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp index b3c3ba4ba5..48a7cba8a9 100644 --- a/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp +++ b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp @@ -30,7 +30,7 @@ void MultiTapBiquadFilterCommand::Process(const ADSP::CommandListProcessor& proc for (u32 i = 0; i < filter_tap_count; i++) { auto state{reinterpret_cast(states[i])}; if (needs_init[i]) { - std::memset(state, 0, sizeof(VoiceState::BiquadFilterState)); + *state = {}; } ApplyBiquadFilterFloat(output_buffer, input_buffer, biquads[i].b, biquads[i].a, *state, From c7e079a5d441840e1ae2aabd5be0f45ffb737f1c Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Fri, 21 Oct 2022 02:34:07 -0400 Subject: [PATCH 70/91] general: Resolve -Wunused-lambda-capture and C5233 --- src/core/file_sys/card_image.cpp | 4 +-- src/core/memory.cpp | 37 +++++++++----------- src/video_core/texture_cache/texture_cache.h | 8 ++--- src/video_core/textures/astc.cpp | 4 +-- 4 files changed, 24 insertions(+), 29 deletions(-) diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp index f23d9373bd..5d02865f43 100644 --- a/src/core/file_sys/card_image.cpp +++ b/src/core/file_sys/card_image.cpp @@ -232,8 +232,8 @@ const std::vector>& XCI::GetNCAs() const { std::shared_ptr XCI::GetNCAByType(NCAContentType type) const { const auto program_id = secure_partition->GetProgramTitleID(); - const auto iter = std::find_if( - ncas.begin(), ncas.end(), [this, type, program_id](const std::shared_ptr& nca) { + const auto iter = + std::find_if(ncas.begin(), ncas.end(), [type, program_id](const std::shared_ptr& nca) { return nca->GetType() == type && nca->GetTitleId() == program_id; }); return iter == ncas.end() ? nullptr : *iter; diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 9637cb5b1e..3ca80c8ff0 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -233,18 +233,17 @@ struct Memory::Impl { current_vaddr, src_addr, size); std::memset(dest_buffer, 0, copy_amount); }, - [&dest_buffer](const std::size_t copy_amount, const u8* const src_ptr) { + [&](const std::size_t copy_amount, const u8* const src_ptr) { std::memcpy(dest_buffer, src_ptr, copy_amount); }, - [&system = system, &dest_buffer](const VAddr current_vaddr, - const std::size_t copy_amount, - const u8* const host_ptr) { + [&](const VAddr current_vaddr, const std::size_t copy_amount, + const u8* const host_ptr) { if constexpr (!UNSAFE) { system.GPU().FlushRegion(current_vaddr, copy_amount); } std::memcpy(dest_buffer, host_ptr, copy_amount); }, - [&dest_buffer](const std::size_t copy_amount) { + [&](const std::size_t copy_amount) { dest_buffer = static_cast(dest_buffer) + copy_amount; }); } @@ -267,17 +266,16 @@ struct Memory::Impl { "Unmapped WriteBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})", current_vaddr, dest_addr, size); }, - [&src_buffer](const std::size_t copy_amount, u8* const dest_ptr) { + [&](const std::size_t copy_amount, u8* const dest_ptr) { std::memcpy(dest_ptr, src_buffer, copy_amount); }, - [&system = system, &src_buffer](const VAddr current_vaddr, - const std::size_t copy_amount, u8* const host_ptr) { + [&](const VAddr current_vaddr, const std::size_t copy_amount, u8* const host_ptr) { if constexpr (!UNSAFE) { system.GPU().InvalidateRegion(current_vaddr, copy_amount); } std::memcpy(host_ptr, src_buffer, copy_amount); }, - [&src_buffer](const std::size_t copy_amount) { + [&](const std::size_t copy_amount) { src_buffer = static_cast(src_buffer) + copy_amount; }); } @@ -301,8 +299,7 @@ struct Memory::Impl { [](const std::size_t copy_amount, u8* const dest_ptr) { std::memset(dest_ptr, 0, copy_amount); }, - [&system = system](const VAddr current_vaddr, const std::size_t copy_amount, - u8* const host_ptr) { + [&](const VAddr current_vaddr, const std::size_t copy_amount, u8* const host_ptr) { system.GPU().InvalidateRegion(current_vaddr, copy_amount); std::memset(host_ptr, 0, copy_amount); }, @@ -313,22 +310,20 @@ struct Memory::Impl { const std::size_t size) { WalkBlock( process, dest_addr, size, - [this, &process, &dest_addr, &src_addr, size](const std::size_t copy_amount, - const VAddr current_vaddr) { + [&](const std::size_t copy_amount, const VAddr current_vaddr) { LOG_ERROR(HW_Memory, "Unmapped CopyBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})", current_vaddr, src_addr, size); ZeroBlock(process, dest_addr, copy_amount); }, - [this, &process, &dest_addr](const std::size_t copy_amount, const u8* const src_ptr) { + [&](const std::size_t copy_amount, const u8* const src_ptr) { WriteBlockImpl(process, dest_addr, src_ptr, copy_amount); }, - [this, &system = system, &process, &dest_addr]( - const VAddr current_vaddr, const std::size_t copy_amount, u8* const host_ptr) { + [&](const VAddr current_vaddr, const std::size_t copy_amount, u8* const host_ptr) { system.GPU().FlushRegion(current_vaddr, copy_amount); WriteBlockImpl(process, dest_addr, host_ptr, copy_amount); }, - [&dest_addr, &src_addr](const std::size_t copy_amount) { + [&](const std::size_t copy_amount) { dest_addr += static_cast(copy_amount); src_addr += static_cast(copy_amount); }); @@ -575,7 +570,7 @@ struct Memory::Impl { [vaddr]() { LOG_ERROR(HW_Memory, "Unmapped Read{} @ 0x{:016X}", sizeof(T) * 8, vaddr); }, - [&system = system, vaddr]() { system.GPU().FlushRegion(vaddr, sizeof(T)); }); + [&]() { system.GPU().FlushRegion(vaddr, sizeof(T)); }); if (ptr) { std::memcpy(&result, ptr, sizeof(T)); } @@ -599,7 +594,7 @@ struct Memory::Impl { LOG_ERROR(HW_Memory, "Unmapped Write{} @ 0x{:016X} = 0x{:016X}", sizeof(T) * 8, vaddr, static_cast(data)); }, - [&system = system, vaddr]() { system.GPU().InvalidateRegion(vaddr, sizeof(T)); }); + [&]() { system.GPU().InvalidateRegion(vaddr, sizeof(T)); }); if (ptr) { std::memcpy(ptr, &data, sizeof(T)); } @@ -613,7 +608,7 @@ struct Memory::Impl { LOG_ERROR(HW_Memory, "Unmapped WriteExclusive{} @ 0x{:016X} = 0x{:016X}", sizeof(T) * 8, vaddr, static_cast(data)); }, - [&system = system, vaddr]() { system.GPU().InvalidateRegion(vaddr, sizeof(T)); }); + [&]() { system.GPU().InvalidateRegion(vaddr, sizeof(T)); }); if (ptr) { const auto volatile_pointer = reinterpret_cast(ptr); return Common::AtomicCompareAndSwap(volatile_pointer, data, expected); @@ -628,7 +623,7 @@ struct Memory::Impl { LOG_ERROR(HW_Memory, "Unmapped WriteExclusive128 @ 0x{:016X} = 0x{:016X}{:016X}", vaddr, static_cast(data[1]), static_cast(data[0])); }, - [&system = system, vaddr]() { system.GPU().InvalidateRegion(vaddr, sizeof(u128)); }); + [&]() { system.GPU().InvalidateRegion(vaddr, sizeof(u128)); }); if (ptr) { const auto volatile_pointer = reinterpret_cast(ptr); return Common::AtomicCompareAndSwap(volatile_pointer, data, expected); diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 0e0fd410f8..8ef75fe734 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -442,7 +442,7 @@ void TextureCache

    ::WriteMemory(VAddr cpu_addr, size_t size) { template void TextureCache

    ::DownloadMemory(VAddr cpu_addr, size_t size) { std::vector images; - ForEachImageInRegion(cpu_addr, size, [this, &images](ImageId image_id, ImageBase& image) { + ForEachImageInRegion(cpu_addr, size, [&images](ImageId image_id, ImageBase& image) { if (!image.IsSafeDownload()) { return; } @@ -1502,9 +1502,9 @@ void TextureCache

    ::UnregisterImage(ImageId image_id) { image.flags &= ~ImageFlagBits::BadOverlap; lru_cache.Free(image.lru_index); const auto& clear_page_table = - [this, image_id](u64 page, - std::unordered_map, Common::IdentityHash>& - selected_page_table) { + [image_id](u64 page, + std::unordered_map, Common::IdentityHash>& + selected_page_table) { const auto page_it = selected_page_table.find(page); if (page_it == selected_page_table.end()) { ASSERT_MSG(false, "Unregistering unregistered page=0x{:x}", page << YUZU_PAGEBITS); diff --git a/src/video_core/textures/astc.cpp b/src/video_core/textures/astc.cpp index 15b9d4182b..69a32819a5 100644 --- a/src/video_core/textures/astc.cpp +++ b/src/video_core/textures/astc.cpp @@ -1661,8 +1661,8 @@ void Decompress(std::span data, uint32_t width, uint32_t height, for (u32 z = 0; z < depth; ++z) { const u32 depth_offset = z * height * width * 4; for (u32 y_index = 0; y_index < rows; ++y_index) { - auto decompress_stride = [data, width, height, depth, block_width, block_height, output, - rows, cols, z, depth_offset, y_index] { + auto decompress_stride = [data, width, height, block_width, block_height, output, rows, + cols, z, depth_offset, y_index] { const u32 y = y_index * block_height; for (u32 x_index = 0; x_index < cols; ++x_index) { const u32 block_index = (z * rows * cols) + (y_index * cols) + x_index; From 42c4ef737315ce32596a70635634ad72a33ffaa5 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Fri, 21 Oct 2022 02:34:07 -0400 Subject: [PATCH 71/91] general: Resolve -Wunused-but-set-variable --- src/video_core/memory_manager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp index d07b21bd67..384350dbdc 100644 --- a/src/video_core/memory_manager.cpp +++ b/src/video_core/memory_manager.cpp @@ -133,7 +133,7 @@ inline void MemoryManager::SetBigPageContinous(size_t big_page_index, bool value template GPUVAddr MemoryManager::PageTableOp(GPUVAddr gpu_addr, [[maybe_unused]] VAddr cpu_addr, size_t size, PTEKind kind) { - u64 remaining_size{size}; + [[maybe_unused]] u64 remaining_size{size}; if constexpr (entry_type == EntryType::Mapped) { page_table.ReserveRange(gpu_addr, size); } @@ -159,7 +159,7 @@ GPUVAddr MemoryManager::PageTableOp(GPUVAddr gpu_addr, [[maybe_unused]] VAddr cp template GPUVAddr MemoryManager::BigPageTableOp(GPUVAddr gpu_addr, [[maybe_unused]] VAddr cpu_addr, size_t size, PTEKind kind) { - u64 remaining_size{size}; + [[maybe_unused]] u64 remaining_size{size}; for (u64 offset{}; offset < size; offset += big_page_size) { const GPUVAddr current_gpu_addr = gpu_addr + offset; [[maybe_unused]] const auto current_entry_type = GetEntry(current_gpu_addr); From f86774c1aca0e431248cf320e65e7785ad22cf3c Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Fri, 21 Oct 2022 02:34:08 -0400 Subject: [PATCH 72/91] startup_checks: Resolve -Wformat --- src/yuzu/startup_checks.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/yuzu/startup_checks.cpp b/src/yuzu/startup_checks.cpp index fc2693f9d7..9bb2a6ef4b 100644 --- a/src/yuzu/startup_checks.cpp +++ b/src/yuzu/startup_checks.cpp @@ -49,7 +49,7 @@ bool CheckEnvVars(bool* is_child) { *is_child = true; return false; } else if (!SetEnvironmentVariableA(IS_CHILD_ENV_VAR, ENV_VAR_ENABLED_TEXT)) { - std::fprintf(stderr, "SetEnvironmentVariableA failed to set %s with error %d\n", + std::fprintf(stderr, "SetEnvironmentVariableA failed to set %s with error %lu\n", IS_CHILD_ENV_VAR, GetLastError()); return true; } @@ -62,7 +62,7 @@ bool StartupChecks(const char* arg0, bool* has_broken_vulkan, bool perform_vulka // Set the startup variable for child processes const bool env_var_set = SetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, ENV_VAR_ENABLED_TEXT); if (!env_var_set) { - std::fprintf(stderr, "SetEnvironmentVariableA failed to set %s with error %d\n", + std::fprintf(stderr, "SetEnvironmentVariableA failed to set %s with error %lu\n", STARTUP_CHECK_ENV_VAR, GetLastError()); return false; } @@ -81,22 +81,22 @@ bool StartupChecks(const char* arg0, bool* has_broken_vulkan, bool perform_vulka DWORD exit_code = STILL_ACTIVE; const int err = GetExitCodeProcess(process_info.hProcess, &exit_code); if (err == 0) { - std::fprintf(stderr, "GetExitCodeProcess failed with error %d\n", GetLastError()); + std::fprintf(stderr, "GetExitCodeProcess failed with error %lu\n", GetLastError()); } // Vulkan is broken if the child crashed (return value is not zero) *has_broken_vulkan = (exit_code != 0); if (CloseHandle(process_info.hProcess) == 0) { - std::fprintf(stderr, "CloseHandle failed with error %d\n", GetLastError()); + std::fprintf(stderr, "CloseHandle failed with error %lu\n", GetLastError()); } if (CloseHandle(process_info.hThread) == 0) { - std::fprintf(stderr, "CloseHandle failed with error %d\n", GetLastError()); + std::fprintf(stderr, "CloseHandle failed with error %lu\n", GetLastError()); } } if (!SetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, nullptr)) { - std::fprintf(stderr, "SetEnvironmentVariableA failed to clear %s with error %d\n", + std::fprintf(stderr, "SetEnvironmentVariableA failed to clear %s with error %lu\n", STARTUP_CHECK_ENV_VAR, GetLastError()); } @@ -149,7 +149,7 @@ bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi, int flags) { pi // lpProcessInformation ); if (!process_created) { - std::fprintf(stderr, "CreateProcessA failed with error %d\n", GetLastError()); + std::fprintf(stderr, "CreateProcessA failed with error %lu\n", GetLastError()); return false; } From bb31b0f261fbcd6398f644c0070d89f67b92a9ef Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Fri, 21 Oct 2022 02:34:08 -0400 Subject: [PATCH 73/91] startup_checks: Resolve -Wstringop-truncation Copies up to sizeof(p_name) - 1 in strncpy and null terminates it at p_name[254] --- src/yuzu/startup_checks.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yuzu/startup_checks.cpp b/src/yuzu/startup_checks.cpp index 9bb2a6ef4b..6a91212e20 100644 --- a/src/yuzu/startup_checks.cpp +++ b/src/yuzu/startup_checks.cpp @@ -135,7 +135,8 @@ bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi, int flags) { startup_info.cb = sizeof(startup_info); char p_name[255]; - std::strncpy(p_name, arg0, 255); + std::strncpy(p_name, arg0, 254); + p_name[254] = '\0'; const bool process_created = CreateProcessA(nullptr, // lpApplicationName p_name, // lpCommandLine From f51c71e956cb39c08c605b8a4b1f5886b0563944 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Fri, 21 Oct 2022 02:34:08 -0400 Subject: [PATCH 74/91] yuzu: Resolve -Wpessimizing-move --- src/yuzu/multiplayer/state.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yuzu/multiplayer/state.cpp b/src/yuzu/multiplayer/state.cpp index ae2738ad4c..285bb150d8 100644 --- a/src/yuzu/multiplayer/state.cpp +++ b/src/yuzu/multiplayer/state.cpp @@ -268,7 +268,7 @@ bool MultiplayerState::OnCloseRoom() { return true; } // Save ban list - UISettings::values.multiplayer_ban_list = std::move(room->GetBanList()); + UISettings::values.multiplayer_ban_list = room->GetBanList(); room->Destroy(); announce_multiplayer_session->Stop(); From 120cd450e5335bc05ab8c6bf6d78da09374c23c6 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Fri, 21 Oct 2022 02:34:08 -0400 Subject: [PATCH 75/91] CMakeLists: Disable -Wbraced-scalar-init on Clang Clang erroneously emits this warning when using designated initializers. --- src/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9ff96c044d..0ac3d254e5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -110,6 +110,7 @@ else() -Wno-invalid-offsetof -Wno-unused-parameter + $<$:-Wno-braced-scalar-init> $<$:-Wno-unused-private-field> ) From 2ccbf5abdd6d2b2a7c0d80371077d6585865cea6 Mon Sep 17 00:00:00 2001 From: german77 Date: Sat, 22 Oct 2022 14:05:00 -0500 Subject: [PATCH 76/91] core: hid: Add handheld to nfc devices --- src/core/hid/emulated_controller.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp index 025f1c78e4..28c654df5e 100644 --- a/src/core/hid/emulated_controller.cpp +++ b/src/core/hid/emulated_controller.cpp @@ -1048,6 +1048,7 @@ bool EmulatedController::HasNfc() const { case NpadStyleIndex::JoyconRight: case NpadStyleIndex::JoyconDual: case NpadStyleIndex::ProController: + case NpadStyleIndex::Handheld: break; default: return false; From 2d90a927c9f7652aa7e259ba57fa22ddbf8bed24 Mon Sep 17 00:00:00 2001 From: Liam Date: Sun, 23 Oct 2022 05:45:45 -0400 Subject: [PATCH 77/91] core: barrier service thread shutdown --- src/core/core.cpp | 1 + src/core/hle/kernel/kernel.cpp | 12 +++++++++--- src/core/hle/service/nvflinger/nvflinger.cpp | 12 ++++++++---- src/core/hle/service/nvflinger/nvflinger.h | 2 ++ src/core/hle/service/service.cpp | 4 ++++ src/core/hle/service/service.h | 2 ++ 6 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/core/core.cpp b/src/core/core.cpp index 7fb8bc0195..40a6104353 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -384,6 +384,7 @@ struct System::Impl { kernel.ShutdownCores(); cpu_manager.Shutdown(); debugger.reset(); + services->KillNVNFlinger(); kernel.CloseServices(); services.reset(); service_manager.reset(); diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index eed2dc9f3e..fdc774e307 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -48,8 +48,8 @@ namespace Kernel { struct KernelCore::Impl { explicit Impl(Core::System& system_, KernelCore& kernel_) - : time_manager{system_}, - service_threads_manager{1, "ServiceThreadsManager"}, system{system_} {} + : time_manager{system_}, service_threads_manager{1, "ServiceThreadsManager"}, + service_thread_barrier{2}, system{system_} {} void SetMulticore(bool is_multi) { is_multicore = is_multi; @@ -737,7 +737,12 @@ struct KernelCore::Impl { } void ClearServiceThreads() { - service_threads_manager.QueueWork([this]() { service_threads.clear(); }); + service_threads_manager.QueueWork([this] { + service_threads.clear(); + default_service_thread.reset(); + service_thread_barrier.Sync(); + }); + service_thread_barrier.Sync(); } std::mutex server_objects_lock; @@ -802,6 +807,7 @@ struct KernelCore::Impl { std::unordered_set> service_threads; std::weak_ptr default_service_thread; Common::ThreadWorker service_threads_manager; + Common::Barrier service_thread_barrier; std::array shutdown_threads; std::array, Core::Hardware::NUM_CPU_CORES> schedulers{}; diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp index aa14d2cbcf..dad93b38e2 100644 --- a/src/core/hle/service/nvflinger/nvflinger.cpp +++ b/src/core/hle/service/nvflinger/nvflinger.cpp @@ -102,15 +102,19 @@ NVFlinger::~NVFlinger() { system.CoreTiming().UnscheduleEvent(single_composition_event, {}); } + ShutdownLayers(); + + if (nvdrv) { + nvdrv->Close(disp_fd); + } +} + +void NVFlinger::ShutdownLayers() { for (auto& display : displays) { for (size_t layer = 0; layer < display.GetNumLayers(); ++layer) { display.GetLayer(layer).Core().NotifyShutdown(); } } - - if (nvdrv) { - nvdrv->Close(disp_fd); - } } void NVFlinger::SetNVDrvInstance(std::shared_ptr instance) { diff --git a/src/core/hle/service/nvflinger/nvflinger.h b/src/core/hle/service/nvflinger/nvflinger.h index 99509bc5bf..b8191c5954 100644 --- a/src/core/hle/service/nvflinger/nvflinger.h +++ b/src/core/hle/service/nvflinger/nvflinger.h @@ -48,6 +48,8 @@ public: explicit NVFlinger(Core::System& system_, HosBinderDriverServer& hos_binder_driver_server_); ~NVFlinger(); + void ShutdownLayers(); + /// Sets the NVDrv module instance to use to send buffers to the GPU. void SetNVDrvInstance(std::shared_ptr instance); diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index dadaf897f1..5db6588e48 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -303,4 +303,8 @@ Services::Services(std::shared_ptr& sm, Core::System& system Services::~Services() = default; +void Services::KillNVNFlinger() { + nv_flinger->ShutdownLayers(); +} + } // namespace Service diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h index 5bf197c519..ec9deeee43 100644 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h @@ -238,6 +238,8 @@ public: explicit Services(std::shared_ptr& sm, Core::System& system); ~Services(); + void KillNVNFlinger(); + private: std::unique_ptr hos_binder_driver_server; std::unique_ptr nv_flinger; From 05f2673648115e53de9e04791f1d30163f90e5ee Mon Sep 17 00:00:00 2001 From: Liam Date: Sun, 23 Oct 2022 19:25:57 -0400 Subject: [PATCH 78/91] nvdrv: fix container destruction order --- src/core/hle/service/nvdrv/nvdrv.cpp | 2 +- src/core/hle/service/nvdrv/nvdrv.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/hle/service/nvdrv/nvdrv.cpp b/src/core/hle/service/nvdrv/nvdrv.cpp index 9d99243959..9f4c7c99a8 100644 --- a/src/core/hle/service/nvdrv/nvdrv.cpp +++ b/src/core/hle/service/nvdrv/nvdrv.cpp @@ -53,7 +53,7 @@ void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger } Module::Module(Core::System& system) - : service_context{system, "nvdrv"}, events_interface{*this}, container{system.Host1x()} { + : container{system.Host1x()}, service_context{system, "nvdrv"}, events_interface{*this} { builders["/dev/nvhost-as-gpu"] = [this, &system](DeviceFD fd) { std::shared_ptr device = std::make_shared(system, *this, container); diff --git a/src/core/hle/service/nvdrv/nvdrv.h b/src/core/hle/service/nvdrv/nvdrv.h index 146d046a93..f3c81bd882 100644 --- a/src/core/hle/service/nvdrv/nvdrv.h +++ b/src/core/hle/service/nvdrv/nvdrv.h @@ -97,6 +97,9 @@ private: friend class EventInterface; friend class Service::NVFlinger::NVFlinger; + /// Manages syncpoints on the host + NvCore::Container container; + /// Id to use for the next open file descriptor. DeviceFD next_fd = 1; @@ -108,9 +111,6 @@ private: EventInterface events_interface; - /// Manages syncpoints on the host - NvCore::Container container; - std::unordered_map> builders; }; From 1689e0a71f5d0518e91d82226612cc5336fe6d4f Mon Sep 17 00:00:00 2001 From: FengChen Date: Sat, 22 Oct 2022 21:27:34 +0800 Subject: [PATCH 79/91] file_sys: Priority display of game titles in the current language --- src/core/file_sys/control_metadata.cpp | 43 ++++++++++++++++++++------ src/core/file_sys/control_metadata.h | 6 ++-- 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/core/file_sys/control_metadata.cpp b/src/core/file_sys/control_metadata.cpp index be25da2f6d..50f44f598f 100644 --- a/src/core/file_sys/control_metadata.cpp +++ b/src/core/file_sys/control_metadata.cpp @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "common/settings.h" #include "common/string_util.h" #include "common/swap.h" #include "core/file_sys/control_metadata.h" @@ -37,6 +38,27 @@ std::string LanguageEntry::GetDeveloperName() const { developer_name.size()); } +constexpr std::array language_to_codes = {{ + Language::Japanese, + Language::AmericanEnglish, + Language::French, + Language::German, + Language::Italian, + Language::Spanish, + Language::Chinese, + Language::Korean, + Language::Dutch, + Language::Portuguese, + Language::Russian, + Language::Taiwanese, + Language::BritishEnglish, + Language::CanadianFrench, + Language::LatinAmericanSpanish, + Language::Chinese, + Language::Taiwanese, + Language::BrazilianPortuguese, +}}; + NACP::NACP() = default; NACP::NACP(VirtualFile file) { @@ -45,9 +67,13 @@ NACP::NACP(VirtualFile file) { NACP::~NACP() = default; -const LanguageEntry& NACP::GetLanguageEntry(Language language) const { - if (language != Language::Default) { - return raw.language_entries.at(static_cast(language)); +const LanguageEntry& NACP::GetLanguageEntry() const { + Language language = language_to_codes[Settings::values.language_index.GetValue()]; + + { + const auto& language_entry = raw.language_entries.at(static_cast(language)); + if (!language_entry.GetApplicationName().empty()) + return language_entry; } for (const auto& language_entry : raw.language_entries) { @@ -55,16 +81,15 @@ const LanguageEntry& NACP::GetLanguageEntry(Language language) const { return language_entry; } - // Fallback to English - return GetLanguageEntry(Language::AmericanEnglish); + return raw.language_entries.at(static_cast(Language::AmericanEnglish)); } -std::string NACP::GetApplicationName(Language language) const { - return GetLanguageEntry(language).GetApplicationName(); +std::string NACP::GetApplicationName() const { + return GetLanguageEntry().GetApplicationName(); } -std::string NACP::GetDeveloperName(Language language) const { - return GetLanguageEntry(language).GetDeveloperName(); +std::string NACP::GetDeveloperName() const { + return GetLanguageEntry().GetDeveloperName(); } u64 NACP::GetTitleId() const { diff --git a/src/core/file_sys/control_metadata.h b/src/core/file_sys/control_metadata.h index 75295519c7..6a81873b15 100644 --- a/src/core/file_sys/control_metadata.h +++ b/src/core/file_sys/control_metadata.h @@ -101,9 +101,9 @@ public: explicit NACP(VirtualFile file); ~NACP(); - const LanguageEntry& GetLanguageEntry(Language language = Language::Default) const; - std::string GetApplicationName(Language language = Language::Default) const; - std::string GetDeveloperName(Language language = Language::Default) const; + const LanguageEntry& GetLanguageEntry() const; + std::string GetApplicationName() const; + std::string GetDeveloperName() const; u64 GetTitleId() const; u64 GetDLCBaseTitleId() const; std::string GetVersionString() const; From 1a378a776902e1142b907110c6dd3a3a1647d328 Mon Sep 17 00:00:00 2001 From: Liam Date: Sun, 23 Oct 2022 05:24:38 -0400 Subject: [PATCH 80/91] kernel: refactor dummy thread wakeups --- .../hle/kernel/global_scheduler_context.cpp | 22 ++++++++++ .../hle/kernel/global_scheduler_context.h | 8 ++++ src/core/hle/kernel/k_scheduler.cpp | 26 ++++++++++- src/core/hle/kernel/k_thread.cpp | 44 +++++++++---------- src/core/hle/kernel/k_thread.h | 8 ++-- 5 files changed, 79 insertions(+), 29 deletions(-) diff --git a/src/core/hle/kernel/global_scheduler_context.cpp b/src/core/hle/kernel/global_scheduler_context.cpp index 65576b8c4b..fd911a3a5e 100644 --- a/src/core/hle/kernel/global_scheduler_context.cpp +++ b/src/core/hle/kernel/global_scheduler_context.cpp @@ -49,4 +49,26 @@ bool GlobalSchedulerContext::IsLocked() const { return scheduler_lock.IsLockedByCurrentThread(); } +void GlobalSchedulerContext::RegisterDummyThreadForWakeup(KThread* thread) { + ASSERT(IsLocked()); + + woken_dummy_threads.insert(thread); +} + +void GlobalSchedulerContext::UnregisterDummyThreadForWakeup(KThread* thread) { + ASSERT(IsLocked()); + + woken_dummy_threads.erase(thread); +} + +void GlobalSchedulerContext::WakeupWaitingDummyThreads() { + ASSERT(IsLocked()); + + for (auto* thread : woken_dummy_threads) { + thread->DummyThreadEndWait(); + } + + woken_dummy_threads.clear(); +} + } // namespace Kernel diff --git a/src/core/hle/kernel/global_scheduler_context.h b/src/core/hle/kernel/global_scheduler_context.h index 67bb9852da..220ed61922 100644 --- a/src/core/hle/kernel/global_scheduler_context.h +++ b/src/core/hle/kernel/global_scheduler_context.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include #include "common/common_types.h" @@ -58,6 +59,10 @@ public: /// Returns true if the global scheduler lock is acquired bool IsLocked() const; + void UnregisterDummyThreadForWakeup(KThread* thread); + void RegisterDummyThreadForWakeup(KThread* thread); + void WakeupWaitingDummyThreads(); + [[nodiscard]] LockType& SchedulerLock() { return scheduler_lock; } @@ -76,6 +81,9 @@ private: KSchedulerPriorityQueue priority_queue; LockType scheduler_lock; + /// Lists dummy threads pending wakeup on lock release + std::set woken_dummy_threads; + /// Lists all thread ids that aren't deleted/etc. std::vector thread_list; std::mutex global_list_guard; diff --git a/src/core/hle/kernel/k_scheduler.cpp b/src/core/hle/kernel/k_scheduler.cpp index c34ce7a178..b1cabbca06 100644 --- a/src/core/hle/kernel/k_scheduler.cpp +++ b/src/core/hle/kernel/k_scheduler.cpp @@ -81,8 +81,8 @@ void KScheduler::RescheduleCurrentHLEThread(KernelCore& kernel) { // HACK: we cannot schedule from this thread, it is not a core thread ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() == 1); - // Special case to ensure dummy threads that are waiting block - GetCurrentThread(kernel).IfDummyThreadTryWait(); + // Ensure dummy threads that are waiting block. + GetCurrentThread(kernel).DummyThreadBeginWait(); ASSERT(GetCurrentThread(kernel).GetState() != ThreadState::Waiting); GetCurrentThread(kernel).EnableDispatch(); @@ -314,6 +314,16 @@ u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) { idle_cores &= ~(1ULL << core_id); } + // HACK: any waiting dummy threads can wake up now. + kernel.GlobalSchedulerContext().WakeupWaitingDummyThreads(); + + // HACK: if we are a dummy thread, and we need to go sleep, indicate + // that for when the lock is released. + KThread* const cur_thread = GetCurrentThreadPointer(kernel); + if (cur_thread->IsDummyThread() && cur_thread->GetState() != ThreadState::Runnable) { + cur_thread->RequestDummyThreadWait(); + } + return cores_needing_scheduling; } @@ -531,11 +541,23 @@ void KScheduler::OnThreadStateChanged(KernelCore& kernel, KThread* thread, Threa GetPriorityQueue(kernel).Remove(thread); IncrementScheduledCount(thread); SetSchedulerUpdateNeeded(kernel); + + if (thread->IsDummyThread()) { + // HACK: if this is a dummy thread, it should no longer wake up when the + // scheduler lock is released. + kernel.GlobalSchedulerContext().UnregisterDummyThreadForWakeup(thread); + } } else if (cur_state == ThreadState::Runnable) { // If we're now runnable, then we weren't previously, and we should add. GetPriorityQueue(kernel).PushBack(thread); IncrementScheduledCount(thread); SetSchedulerUpdateNeeded(kernel); + + if (thread->IsDummyThread()) { + // HACK: if this is a dummy thread, it should wake up when the scheduler + // lock is released. + kernel.GlobalSchedulerContext().RegisterDummyThreadForWakeup(thread); + } } } diff --git a/src/core/hle/kernel/k_thread.cpp b/src/core/hle/kernel/k_thread.cpp index b7bfcdce31..d57b42fdf7 100644 --- a/src/core/hle/kernel/k_thread.cpp +++ b/src/core/hle/kernel/k_thread.cpp @@ -148,7 +148,9 @@ Result KThread::Initialize(KThreadFunction func, uintptr_t arg, VAddr user_stack physical_affinity_mask.SetAffinity(phys_core, true); // Set the thread state. - thread_state = (type == ThreadType::Main) ? ThreadState::Runnable : ThreadState::Initialized; + thread_state = (type == ThreadType::Main || type == ThreadType::Dummy) + ? ThreadState::Runnable + : ThreadState::Initialized; // Set TLS address. tls_address = 0; @@ -1174,30 +1176,29 @@ Result KThread::Sleep(s64 timeout) { R_SUCCEED(); } -void KThread::IfDummyThreadTryWait() { - if (!IsDummyThread()) { - return; - } +void KThread::RequestDummyThreadWait() { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(kernel)); + ASSERT(this->IsDummyThread()); - if (GetState() != ThreadState::Waiting) { - return; - } - - ASSERT(!kernel.IsPhantomModeForSingleCore()); - - // Block until we are no longer waiting. - std::unique_lock lk(dummy_wait_lock); - dummy_wait_cv.wait( - lk, [&] { return GetState() != ThreadState::Waiting || kernel.IsShuttingDown(); }); + // We will block when the scheduler lock is released. + dummy_thread_runnable.store(false); } -void KThread::IfDummyThreadEndWait() { - if (!IsDummyThread()) { - return; - } +void KThread::DummyThreadBeginWait() { + ASSERT(this->IsDummyThread()); + ASSERT(!kernel.IsPhantomModeForSingleCore()); + + // Block until runnable is no longer false. + dummy_thread_runnable.wait(false); +} + +void KThread::DummyThreadEndWait() { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(kernel)); + ASSERT(this->IsDummyThread()); // Wake up the waiting thread. - dummy_wait_cv.notify_one(); + dummy_thread_runnable.store(true); + dummy_thread_runnable.notify_one(); } void KThread::BeginWait(KThreadQueue* queue) { @@ -1231,9 +1232,6 @@ void KThread::EndWait(Result wait_result_) { } wait_queue->EndWait(this, wait_result_); - - // Special case for dummy threads to wakeup if necessary. - IfDummyThreadEndWait(); } } diff --git a/src/core/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h index e2a27d6036..30aa10c9a8 100644 --- a/src/core/hle/kernel/k_thread.h +++ b/src/core/hle/kernel/k_thread.h @@ -643,8 +643,9 @@ public: // therefore will not block on guest kernel synchronization primitives. These methods handle // blocking as needed. - void IfDummyThreadTryWait(); - void IfDummyThreadEndWait(); + void RequestDummyThreadWait(); + void DummyThreadBeginWait(); + void DummyThreadEndWait(); [[nodiscard]] uintptr_t GetArgument() const { return argument; @@ -777,8 +778,7 @@ private: bool is_single_core{}; ThreadType thread_type{}; StepState step_state{}; - std::mutex dummy_wait_lock; - std::condition_variable dummy_wait_cv; + std::atomic dummy_thread_runnable{true}; // For debugging std::vector wait_objects_for_debugging; From 165bce3c2d265760190ea848a064f0d6538710bb Mon Sep 17 00:00:00 2001 From: Feng Chen Date: Tue, 25 Oct 2022 12:57:25 +0800 Subject: [PATCH 81/91] Revert "shader_recompiler/dead_code_elimination: Add DeadBranchElimination pass" --- .../frontend/ir/microinstruction.cpp | 5 - src/shader_recompiler/frontend/ir/value.h | 4 - .../ir_opt/dead_code_elimination_pass.cpp | 100 ++---------------- 3 files changed, 10 insertions(+), 99 deletions(-) diff --git a/src/shader_recompiler/frontend/ir/microinstruction.cpp b/src/shader_recompiler/frontend/ir/microinstruction.cpp index 468782eb17..84417980bc 100644 --- a/src/shader_recompiler/frontend/ir/microinstruction.cpp +++ b/src/shader_recompiler/frontend/ir/microinstruction.cpp @@ -325,11 +325,6 @@ void Inst::AddPhiOperand(Block* predecessor, const Value& value) { phi_args.emplace_back(predecessor, value); } -void Inst::ErasePhiOperand(size_t index) { - const auto operand_it{phi_args.begin() + static_cast(index)}; - phi_args.erase(operand_it); -} - void Inst::OrderPhiArgs() { if (op != Opcode::Phi) { throw LogicError("{} is not a Phi instruction", op); diff --git a/src/shader_recompiler/frontend/ir/value.h b/src/shader_recompiler/frontend/ir/value.h index 1a2e4ccb61..6a673ca05a 100644 --- a/src/shader_recompiler/frontend/ir/value.h +++ b/src/shader_recompiler/frontend/ir/value.h @@ -178,13 +178,9 @@ public: /// Get a pointer to the block of a phi argument. [[nodiscard]] Block* PhiBlock(size_t index) const; - /// Add phi operand to a phi instruction. void AddPhiOperand(Block* predecessor, const Value& value); - // Erase the phi operand at the given index. - void ErasePhiOperand(size_t index); - /// Orders the Phi arguments from farthest away to nearest. void OrderPhiArgs(); diff --git a/src/shader_recompiler/ir_opt/dead_code_elimination_pass.cpp b/src/shader_recompiler/ir_opt/dead_code_elimination_pass.cpp index 9a7d47344d..1bd8afd6f6 100644 --- a/src/shader_recompiler/ir_opt/dead_code_elimination_pass.cpp +++ b/src/shader_recompiler/ir_opt/dead_code_elimination_pass.cpp @@ -1,104 +1,24 @@ // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include - -#include - #include "shader_recompiler/frontend/ir/basic_block.h" #include "shader_recompiler/frontend/ir/value.h" #include "shader_recompiler/ir_opt/passes.h" namespace Shader::Optimization { -namespace { -template -void DeadInstElimination(IR::Block* const block) { - // We iterate over the instructions in reverse order. - // This is because removing an instruction reduces the number of uses for earlier instructions. - auto it{block->end()}; - while (it != block->begin()) { - --it; - if constexpr (TEST_USES) { - if (it->HasUses() || it->MayHaveSideEffects()) { - continue; - } - } - it->Invalidate(); - it = block->Instructions().erase(it); - } -} - -void DeletedPhiArgElimination(IR::Program& program, std::span dead_blocks) { - for (IR::Block* const block : program.blocks) { - for (IR::Inst& phi : *block) { - if (!IR::IsPhi(phi)) { - continue; - } - for (size_t i = 0; i < phi.NumArgs(); ++i) { - if (std::ranges::find(dead_blocks, phi.PhiBlock(i)) == dead_blocks.end()) { - continue; - } - // Phi operand at this index is an unreachable block - phi.ErasePhiOperand(i); - --i; - } - } - } -} - -void DeadBranchElimination(IR::Program& program) { - boost::container::small_vector dead_blocks; - const auto begin_it{program.syntax_list.begin()}; - for (auto node_it = begin_it; node_it != program.syntax_list.end(); ++node_it) { - if (node_it->type != IR::AbstractSyntaxNode::Type::If) { - continue; - } - IR::Inst* const cond_ref{node_it->data.if_node.cond.Inst()}; - const IR::U1 cond{cond_ref->Arg(0)}; - if (!cond.IsImmediate()) { - continue; - } - if (cond.U1()) { - continue; - } - // False immediate condition. Remove condition ref, erase the entire branch. - cond_ref->Invalidate(); - // Account for nested if-statements within the if(false) branch - u32 nested_ifs{1u}; - while (node_it->type != IR::AbstractSyntaxNode::Type::EndIf || nested_ifs > 0) { - node_it = program.syntax_list.erase(node_it); - switch (node_it->type) { - case IR::AbstractSyntaxNode::Type::If: - ++nested_ifs; - break; - case IR::AbstractSyntaxNode::Type::EndIf: - --nested_ifs; - break; - case IR::AbstractSyntaxNode::Type::Block: { - IR::Block* const block{node_it->data.block}; - DeadInstElimination(block); - dead_blocks.push_back(block); - break; - } - default: - break; - } - } - // Erase EndIf node of the if(false) branch - node_it = program.syntax_list.erase(node_it); - // Account for loop increment - --node_it; - } - if (!dead_blocks.empty()) { - DeletedPhiArgElimination(program, std::span(dead_blocks.data(), dead_blocks.size())); - } -} -} // namespace void DeadCodeEliminationPass(IR::Program& program) { - DeadBranchElimination(program); + // We iterate over the instructions in reverse order. + // This is because removing an instruction reduces the number of uses for earlier instructions. for (IR::Block* const block : program.post_order_blocks) { - DeadInstElimination(block); + auto it{block->end()}; + while (it != block->begin()) { + --it; + if (!it->HasUses() && !it->MayHaveSideEffects()) { + it->Invalidate(); + it = block->Instructions().erase(it); + } + } } } From 0ec1801bc1e07e0630e9be55ef123294c8155b6a Mon Sep 17 00:00:00 2001 From: FengChen Date: Tue, 25 Oct 2022 22:39:29 +0800 Subject: [PATCH 82/91] video_core: Catch vulkan clear op not all channel need clear --- .../renderer_vulkan/vk_rasterizer.cpp | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 9f05a7a18c..6ab68892c0 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -305,14 +305,19 @@ void RasterizerVulkan::Clear() { } } - scheduler.Record([color_attachment, clear_value, clear_rect](vk::CommandBuffer cmdbuf) { - const VkClearAttachment attachment{ - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .colorAttachment = color_attachment, - .clearValue = clear_value, - }; - cmdbuf.ClearAttachments(attachment, clear_rect); - }); + if (regs.clear_surface.R && regs.clear_surface.G && regs.clear_surface.B && + regs.clear_surface.A) { + scheduler.Record([color_attachment, clear_value, clear_rect](vk::CommandBuffer cmdbuf) { + const VkClearAttachment attachment{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .colorAttachment = color_attachment, + .clearValue = clear_value, + }; + cmdbuf.ClearAttachments(attachment, clear_rect); + }); + } else { + UNIMPLEMENTED_MSG("Unimplemented Clear only the specified channel"); + } } if (!use_depth && !use_stencil) { From fa9b7db76f4c179e2af2f6f1974f92858586d533 Mon Sep 17 00:00:00 2001 From: Alexandre Bouvier Date: Tue, 25 Oct 2022 15:20:23 +0000 Subject: [PATCH 83/91] tests: fix for -Wall Fix #9123 --- src/tests/video_core/buffer_base.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/video_core/buffer_base.cpp b/src/tests/video_core/buffer_base.cpp index 71121e42a0..f7236afabf 100644 --- a/src/tests/video_core/buffer_base.cpp +++ b/src/tests/video_core/buffer_base.cpp @@ -44,7 +44,7 @@ public: [[nodiscard]] unsigned Count() const noexcept { unsigned count = 0; - for (const auto [index, value] : page_table) { + for (const auto& [index, value] : page_table) { count += value; } return count; From 8b4d5aeb4f01348312ca38555d7951ff54b23fc3 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Wed, 26 Oct 2022 00:41:54 -0400 Subject: [PATCH 84/91] concepts: Use the std::contiguous_iterator concept This also covers std::span, which does not have a const iterator. Also renames IsSTLContainer to IsContiguousContainer to explicitly convey its semantics. --- src/common/concepts.h | 16 +++------------- src/common/fs/file.h | 12 ++++++------ src/core/hle/kernel/hle_ipc.h | 2 +- 3 files changed, 10 insertions(+), 20 deletions(-) diff --git a/src/common/concepts.h b/src/common/concepts.h index e8ce30dfed..a9acff3e79 100644 --- a/src/common/concepts.h +++ b/src/common/concepts.h @@ -3,24 +3,14 @@ #pragma once +#include #include namespace Common { -// Check if type is like an STL container +// Check if type satisfies the ContiguousContainer named requirement. template -concept IsSTLContainer = requires(T t) { - typename T::value_type; - typename T::iterator; - typename T::const_iterator; - // TODO(ogniK): Replace below is std::same_as when MSVC supports it. - t.begin(); - t.end(); - t.cbegin(); - t.cend(); - t.data(); - t.size(); -}; +concept IsContiguousContainer = std::contiguous_iterator; // TODO: Replace with std::derived_from when the header // is available on all supported platforms. diff --git a/src/common/fs/file.h b/src/common/fs/file.h index 69b53384c5..167c4d8261 100644 --- a/src/common/fs/file.h +++ b/src/common/fs/file.h @@ -209,8 +209,8 @@ public: /** * Helper function which deduces the value type of a contiguous STL container used in ReadSpan. - * If T is not a contiguous STL container as defined by the concept IsSTLContainer, this calls - * ReadObject and T must be a trivially copyable object. + * If T is not a contiguous container as defined by the concept IsContiguousContainer, this + * calls ReadObject and T must be a trivially copyable object. * * See ReadSpan for more details if T is a contiguous container. * See ReadObject for more details if T is a trivially copyable object. @@ -223,7 +223,7 @@ public: */ template [[nodiscard]] size_t Read(T& data) const { - if constexpr (IsSTLContainer) { + if constexpr (IsContiguousContainer) { using ContiguousType = typename T::value_type; static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); @@ -235,8 +235,8 @@ public: /** * Helper function which deduces the value type of a contiguous STL container used in WriteSpan. - * If T is not a contiguous STL container as defined by the concept IsSTLContainer, this calls - * WriteObject and T must be a trivially copyable object. + * If T is not a contiguous STL container as defined by the concept IsContiguousContainer, this + * calls WriteObject and T must be a trivially copyable object. * * See WriteSpan for more details if T is a contiguous container. * See WriteObject for more details if T is a trivially copyable object. @@ -249,7 +249,7 @@ public: */ template [[nodiscard]] size_t Write(const T& data) const { - if constexpr (IsSTLContainer) { + if constexpr (IsContiguousContainer) { using ContiguousType = typename T::value_type; static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h index a0522bca0b..1083638a9e 100644 --- a/src/core/hle/kernel/hle_ipc.h +++ b/src/core/hle/kernel/hle_ipc.h @@ -304,7 +304,7 @@ public: */ template >> std::size_t WriteBuffer(const T& data, std::size_t buffer_index = 0) const { - if constexpr (Common::IsSTLContainer) { + if constexpr (Common::IsContiguousContainer) { using ContiguousType = typename T::value_type; static_assert(std::is_trivially_copyable_v, "Container to WriteBuffer must contain trivially copyable objects"); From e0ec9ffc36892319a5cd847dfd42f6aba4671685 Mon Sep 17 00:00:00 2001 From: Lioncash Date: Wed, 26 Oct 2022 11:15:25 -0400 Subject: [PATCH 85/91] audio_in/out_system: Pass Initialize members by value where applicable applet_resource_user_id isn't actually modified and is just assigned to a member variable, so this doesn't need to be a mutable reference. Similarly, the device name itself isn't modified and is only moved. We pass by value here, since we can still perform the move, but eliminate a sneaky set of calls that can unintentionally destroy the original string. Given how nested the calls are, it's good to get rid of this potential vector for a use-after-move bug. --- src/audio_core/in/audio_in_system.cpp | 2 +- src/audio_core/in/audio_in_system.h | 2 +- src/audio_core/out/audio_out_system.cpp | 4 ++-- src/audio_core/out/audio_out_system.h | 4 ++-- src/core/hle/service/audio/audin_u.cpp | 2 +- src/core/hle/service/audio/audout_u.cpp | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/audio_core/in/audio_in_system.cpp b/src/audio_core/in/audio_in_system.cpp index 6b7e6715cf..4324cafd84 100644 --- a/src/audio_core/in/audio_in_system.cpp +++ b/src/audio_core/in/audio_in_system.cpp @@ -56,7 +56,7 @@ Result System::IsConfigValid(const std::string_view device_name, return ResultSuccess; } -Result System::Initialize(std::string& device_name, const AudioInParameter& in_params, +Result System::Initialize(std::string device_name, const AudioInParameter& in_params, const u32 handle_, const u64 applet_resource_user_id_) { auto result{IsConfigValid(device_name, in_params)}; if (result.IsError()) { diff --git a/src/audio_core/in/audio_in_system.h b/src/audio_core/in/audio_in_system.h index b9dc0e60ff..1c51546381 100644 --- a/src/audio_core/in/audio_in_system.h +++ b/src/audio_core/in/audio_in_system.h @@ -97,7 +97,7 @@ public: * @param applet_resource_user_id - Unused. * @return Result code. */ - Result Initialize(std::string& device_name, const AudioInParameter& in_params, u32 handle, + Result Initialize(std::string device_name, const AudioInParameter& in_params, u32 handle, u64 applet_resource_user_id); /** diff --git a/src/audio_core/out/audio_out_system.cpp b/src/audio_core/out/audio_out_system.cpp index 48a8019232..a66208ed9c 100644 --- a/src/audio_core/out/audio_out_system.cpp +++ b/src/audio_core/out/audio_out_system.cpp @@ -49,8 +49,8 @@ Result System::IsConfigValid(std::string_view device_name, return Service::Audio::ERR_INVALID_CHANNEL_COUNT; } -Result System::Initialize(std::string& device_name, const AudioOutParameter& in_params, u32 handle_, - u64& applet_resource_user_id_) { +Result System::Initialize(std::string device_name, const AudioOutParameter& in_params, u32 handle_, + u64 applet_resource_user_id_) { auto result = IsConfigValid(device_name, in_params); if (result.IsError()) { return result; diff --git a/src/audio_core/out/audio_out_system.h b/src/audio_core/out/audio_out_system.h index 0817b2f371..b95cb91bec 100644 --- a/src/audio_core/out/audio_out_system.h +++ b/src/audio_core/out/audio_out_system.h @@ -88,8 +88,8 @@ public: * @param applet_resource_user_id - Unused. * @return Result code. */ - Result Initialize(std::string& device_name, const AudioOutParameter& in_params, u32 handle, - u64& applet_resource_user_id); + Result Initialize(std::string device_name, const AudioOutParameter& in_params, u32 handle, + u64 applet_resource_user_id); /** * Start this system. diff --git a/src/core/hle/service/audio/audin_u.cpp b/src/core/hle/service/audio/audin_u.cpp index 48a9a73a0e..608925dfcd 100644 --- a/src/core/hle/service/audio/audin_u.cpp +++ b/src/core/hle/service/audio/audin_u.cpp @@ -17,7 +17,7 @@ using namespace AudioCore::AudioIn; class IAudioIn final : public ServiceFramework { public: explicit IAudioIn(Core::System& system_, Manager& manager, size_t session_id, - std::string& device_name, const AudioInParameter& in_params, u32 handle, + const std::string& device_name, const AudioInParameter& in_params, u32 handle, u64 applet_resource_user_id) : ServiceFramework{system_, "IAudioIn"}, service_context{system_, "IAudioIn"}, event{service_context.CreateEvent("AudioInEvent")}, diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp index 49c0923016..122290c6a6 100644 --- a/src/core/hle/service/audio/audout_u.cpp +++ b/src/core/hle/service/audio/audout_u.cpp @@ -24,7 +24,7 @@ using namespace AudioCore::AudioOut; class IAudioOut final : public ServiceFramework { public: explicit IAudioOut(Core::System& system_, AudioCore::AudioOut::Manager& manager, - size_t session_id, std::string& device_name, + size_t session_id, const std::string& device_name, const AudioOutParameter& in_params, u32 handle, u64 applet_resource_user_id) : ServiceFramework{system_, "IAudioOut", ServiceThreadType::CreateNew}, service_context{system_, "IAudioOut"}, event{service_context.CreateEvent( From f6e7cae62c3a3df41fe86566d4b786454f90b48a Mon Sep 17 00:00:00 2001 From: FengChen Date: Thu, 27 Oct 2022 13:21:47 +0800 Subject: [PATCH 86/91] video_core: Fix drawing trigger mechanism regression --- src/video_core/engines/maxwell_3d.cpp | 141 ++++++++++++++------------ src/video_core/engines/maxwell_3d.h | 2 + 2 files changed, 76 insertions(+), 67 deletions(-) diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index 5208bea75b..f9794dfe43 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -123,9 +123,6 @@ void Maxwell3D::InitializeRegisterDefaults() { draw_command[MAXWELL3D_REG_INDEX(vertex_buffer.count)] = true; draw_command[MAXWELL3D_REG_INDEX(index_buffer.first)] = true; draw_command[MAXWELL3D_REG_INDEX(index_buffer.count)] = true; - draw_command[MAXWELL3D_REG_INDEX(index_buffer32_first)] = true; - draw_command[MAXWELL3D_REG_INDEX(index_buffer16_first)] = true; - draw_command[MAXWELL3D_REG_INDEX(index_buffer8_first)] = true; draw_command[MAXWELL3D_REG_INDEX(draw_inline_index)] = true; draw_command[MAXWELL3D_REG_INDEX(inline_index_2x16.even)] = true; draw_command[MAXWELL3D_REG_INDEX(inline_index_4x8.index0)] = true; @@ -216,6 +213,21 @@ void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argume return ProcessCBBind(3); case MAXWELL3D_REG_INDEX(bind_groups[4].raw_config): return ProcessCBBind(4); + case MAXWELL3D_REG_INDEX(index_buffer32_first): + regs.index_buffer.count = regs.index_buffer32_first.count; + regs.index_buffer.first = regs.index_buffer32_first.first; + dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; + return ProcessDraw(); + case MAXWELL3D_REG_INDEX(index_buffer16_first): + regs.index_buffer.count = regs.index_buffer16_first.count; + regs.index_buffer.first = regs.index_buffer16_first.first; + dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; + return ProcessDraw(); + case MAXWELL3D_REG_INDEX(index_buffer8_first): + regs.index_buffer.count = regs.index_buffer8_first.count; + regs.index_buffer.first = regs.index_buffer8_first.first; + dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; + return ProcessDraw(); case MAXWELL3D_REG_INDEX(topology_override): use_topology_override = true; return; @@ -583,70 +595,7 @@ void Maxwell3D::ProcessClearBuffers() { rasterizer->Clear(); } -void Maxwell3D::ProcessDeferredDraw() { - if (deferred_draw_method.empty()) { - return; - } - - enum class DrawMode { - Undefined, - General, - Instance, - }; - DrawMode draw_mode{DrawMode::Undefined}; - u32 instance_count = 1; - - auto first_method = deferred_draw_method[0]; - if (MAXWELL3D_REG_INDEX(draw.begin) == first_method) { - // The minimum number of methods for drawing must be greater than or equal to - // 3[draw.begin->vertex(index)count->draw.end] to avoid errors in index mode drawing - if (deferred_draw_method.size() < 3) { - return; - } - draw_mode = (regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::Subsequent) || - (regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::Unchanged) - ? DrawMode::Instance - : DrawMode::General; - } else if (MAXWELL3D_REG_INDEX(index_buffer32_first) == first_method || - MAXWELL3D_REG_INDEX(index_buffer16_first) == first_method || - MAXWELL3D_REG_INDEX(index_buffer8_first) == first_method) { - draw_mode = DrawMode::General; - } - - // Drawing will only begin with draw.begin or index_buffer method, other methods directly - // clear - if (draw_mode == DrawMode::Undefined) { - deferred_draw_method.clear(); - return; - } - - if (draw_mode == DrawMode::Instance) { - ASSERT_MSG(deferred_draw_method.size() % 4 == 0, "Instance mode method size error"); - instance_count = static_cast(deferred_draw_method.size()) / 4; - } else { - if (MAXWELL3D_REG_INDEX(index_buffer32_first) == first_method) { - regs.index_buffer.count = regs.index_buffer32_first.count; - regs.index_buffer.first = regs.index_buffer32_first.first; - dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; - } else if (MAXWELL3D_REG_INDEX(index_buffer32_first) == first_method) { - regs.index_buffer.count = regs.index_buffer16_first.count; - regs.index_buffer.first = regs.index_buffer16_first.first; - dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; - } else if (MAXWELL3D_REG_INDEX(index_buffer32_first) == first_method) { - regs.index_buffer.count = regs.index_buffer8_first.count; - regs.index_buffer.first = regs.index_buffer8_first.first; - dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; - } else { - auto second_method = deferred_draw_method[1]; - if (MAXWELL3D_REG_INDEX(draw_inline_index) == second_method || - MAXWELL3D_REG_INDEX(inline_index_2x16.even) == second_method || - MAXWELL3D_REG_INDEX(inline_index_4x8.index0) == second_method) { - regs.index_buffer.count = static_cast(inline_index_draw_indexes.size() / 4); - regs.index_buffer.format = Regs::IndexFormat::UnsignedInt; - } - } - } - +void Maxwell3D::ProcessDraw(u32 instance_count) { LOG_TRACE(HW_GPU, "called, topology={}, count={}", regs.draw.topology.Value(), regs.vertex_buffer.count); @@ -669,6 +618,64 @@ void Maxwell3D::ProcessDeferredDraw() { } else { regs.vertex_buffer.count = 0; } +} + +void Maxwell3D::ProcessDeferredDraw() { + if (deferred_draw_method.empty()) { + return; + } + + enum class DrawMode { + Undefined, + General, + Instance, + }; + DrawMode draw_mode{DrawMode::Undefined}; + u32 instance_count = 1; + + u32 index = 0; + u32 method = 0; + u32 method_count = static_cast(deferred_draw_method.size()); + for (; index < method_count && + (method = deferred_draw_method[index]) != MAXWELL3D_REG_INDEX(draw.begin); + ++index) + ; + + if (MAXWELL3D_REG_INDEX(draw.begin) != method) { + return; + } + + // The minimum number of methods for drawing must be greater than or equal to + // 3[draw.begin->vertex(index)count(first)->draw.end] to avoid errors in index mode drawing + if ((method_count - index) < 3) { + return; + } + draw_mode = (regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::Subsequent) || + (regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::Unchanged) + ? DrawMode::Instance + : DrawMode::General; + + // Drawing will only begin with draw.begin or index_buffer method, other methods directly + // clear + if (draw_mode == DrawMode::Undefined) { + deferred_draw_method.clear(); + return; + } + + if (draw_mode == DrawMode::Instance) { + ASSERT_MSG(deferred_draw_method.size() % 4 == 0, "Instance mode method size error"); + instance_count = static_cast(method_count - index) / 4; + } else { + method = deferred_draw_method[index + 1]; + if (MAXWELL3D_REG_INDEX(draw_inline_index) == method || + MAXWELL3D_REG_INDEX(inline_index_2x16.even) == method || + MAXWELL3D_REG_INDEX(inline_index_4x8.index0) == method) { + regs.index_buffer.count = static_cast(inline_index_draw_indexes.size() / 4); + regs.index_buffer.format = Regs::IndexFormat::UnsignedInt; + } + } + + ProcessDraw(instance_count); deferred_draw_method.clear(); inline_index_draw_indexes.clear(); diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index bd23ebc12c..a948fcb14d 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -3143,6 +3143,8 @@ private: /// Handles use of topology overrides (e.g., to avoid using a topology assigned from a macro) void ProcessTopologyOverride(); + void ProcessDraw(u32 instance_count = 1); + void ProcessDeferredDraw(); /// Returns a query's value or an empty object if the value will be deferred through a cache. From cdb9fe978ff29b1de2256f0d0cece550195f3fef Mon Sep 17 00:00:00 2001 From: Liam Date: Tue, 25 Oct 2022 17:42:02 -0400 Subject: [PATCH 87/91] vi: implement CloseDisplay --- src/core/hle/service/nvflinger/nvflinger.cpp | 13 +++++++++++++ src/core/hle/service/nvflinger/nvflinger.h | 5 +++++ src/core/hle/service/vi/display/vi_display.h | 6 ++++++ src/core/hle/service/vi/vi.cpp | 8 ++++---- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp index dad93b38e2..c3af12c90a 100644 --- a/src/core/hle/service/nvflinger/nvflinger.cpp +++ b/src/core/hle/service/nvflinger/nvflinger.cpp @@ -138,6 +138,19 @@ std::optional NVFlinger::OpenDisplay(std::string_view name) { return itr->GetID(); } +bool NVFlinger::CloseDisplay(u64 display_id) { + const auto lock_guard = Lock(); + auto* const display = FindDisplay(display_id); + + if (display == nullptr) { + return false; + } + + display->Reset(); + + return true; +} + std::optional NVFlinger::CreateLayer(u64 display_id) { const auto lock_guard = Lock(); auto* const display = FindDisplay(display_id); diff --git a/src/core/hle/service/nvflinger/nvflinger.h b/src/core/hle/service/nvflinger/nvflinger.h index b8191c5954..460bef976d 100644 --- a/src/core/hle/service/nvflinger/nvflinger.h +++ b/src/core/hle/service/nvflinger/nvflinger.h @@ -58,6 +58,11 @@ public: /// If an invalid display name is provided, then an empty optional is returned. [[nodiscard]] std::optional OpenDisplay(std::string_view name); + /// Closes the specified display by its ID. + /// + /// Returns false if an invalid display ID is provided. + [[nodiscard]] bool CloseDisplay(u64 display_id); + /// Creates a layer on the specified display and returns the layer ID. /// /// If an invalid display ID is specified, then an empty optional is returned. diff --git a/src/core/hle/service/vi/display/vi_display.h b/src/core/hle/service/vi/display/vi_display.h index 33d5f398cd..0b65a65da9 100644 --- a/src/core/hle/service/vi/display/vi_display.h +++ b/src/core/hle/service/vi/display/vi_display.h @@ -106,6 +106,12 @@ public: /// void CloseLayer(u64 layer_id); + /// Resets the display for a new connection. + void Reset() { + layers.clear(); + got_vsync_event = false; + } + /// Attempts to find a layer with the given ID. /// /// @param layer_id The layer ID. diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp index 9c917cacfe..bb283e74e3 100644 --- a/src/core/hle/service/vi/vi.cpp +++ b/src/core/hle/service/vi/vi.cpp @@ -324,10 +324,10 @@ private: IPC::RequestParser rp{ctx}; const u64 display = rp.Pop(); - LOG_WARNING(Service_VI, "(STUBBED) called. display=0x{:016X}", display); + const Result rc = nv_flinger.CloseDisplay(display) ? ResultSuccess : ResultUnknown; IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(rc); } void CreateManagedLayer(Kernel::HLERequestContext& ctx) { @@ -508,10 +508,10 @@ private: IPC::RequestParser rp{ctx}; const u64 display_id = rp.Pop(); - LOG_WARNING(Service_VI, "(STUBBED) called. display_id=0x{:016X}", display_id); + const Result rc = nv_flinger.CloseDisplay(display_id) ? ResultSuccess : ResultUnknown; IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(rc); } // This literally does nothing internally in the actual service itself, From 2cdfbbc07d74548527fbefd738f860ce66f52e34 Mon Sep 17 00:00:00 2001 From: Liam Date: Wed, 26 Oct 2022 21:35:19 -0400 Subject: [PATCH 88/91] nvnflinger: release queued handles immediately on disconnection --- src/core/hle/service/nvdrv/core/nvmap.cpp | 5 +++-- src/core/hle/service/nvdrv/core/nvmap.h | 1 + src/core/hle/service/nvdrv/devices/nvmap.cpp | 10 ++++++---- .../hle/service/nvflinger/buffer_queue_producer.cpp | 7 +++++++ 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/core/hle/service/nvdrv/core/nvmap.cpp b/src/core/hle/service/nvdrv/core/nvmap.cpp index fbd8a74a55..a51ca54447 100644 --- a/src/core/hle/service/nvdrv/core/nvmap.cpp +++ b/src/core/hle/service/nvdrv/core/nvmap.cpp @@ -255,15 +255,16 @@ std::optional NvMap::FreeHandle(Handle::Id handle, bool interna .address = handle_description->address, .size = handle_description->size, .was_uncached = handle_description->flags.map_uncached.Value() != 0, + .can_unlock = true, }; } else { return std::nullopt; } - // Handle hasn't been freed from memory, set address to 0 to mark that the handle wasn't freed + // If the handle hasn't been freed from memory, mark that if (!hWeak.expired()) { LOG_DEBUG(Service_NVDRV, "nvmap handle: {} wasn't freed as it is still in use", handle); - freeInfo.address = 0; + freeInfo.can_unlock = false; } return freeInfo; diff --git a/src/core/hle/service/nvdrv/core/nvmap.h b/src/core/hle/service/nvdrv/core/nvmap.h index b9dd3801f4..a8e5738909 100644 --- a/src/core/hle/service/nvdrv/core/nvmap.h +++ b/src/core/hle/service/nvdrv/core/nvmap.h @@ -105,6 +105,7 @@ public: u64 address; //!< Address the handle referred to before deletion u64 size; //!< Page-aligned handle size bool was_uncached; //!< If the handle was allocated as uncached + bool can_unlock; //!< If the address region is ready to be unlocked }; explicit NvMap(Tegra::Host1x::Host1x& host1x); diff --git a/src/core/hle/service/nvdrv/devices/nvmap.cpp b/src/core/hle/service/nvdrv/devices/nvmap.cpp index b606790217..44388655d3 100644 --- a/src/core/hle/service/nvdrv/devices/nvmap.cpp +++ b/src/core/hle/service/nvdrv/devices/nvmap.cpp @@ -251,10 +251,12 @@ NvResult nvmap::IocFree(const std::vector& input, std::vector& output) { } if (auto freeInfo{file.FreeHandle(params.handle, false)}) { - ASSERT(system.CurrentProcess() - ->PageTable() - .UnlockForDeviceAddressSpace(freeInfo->address, freeInfo->size) - .IsSuccess()); + if (freeInfo->can_unlock) { + ASSERT(system.CurrentProcess() + ->PageTable() + .UnlockForDeviceAddressSpace(freeInfo->address, freeInfo->size) + .IsSuccess()); + } params.address = freeInfo->address; params.size = static_cast(freeInfo->size); params.flags.raw = 0; diff --git a/src/core/hle/service/nvflinger/buffer_queue_producer.cpp b/src/core/hle/service/nvflinger/buffer_queue_producer.cpp index 77ddbb6efb..41ba44b215 100644 --- a/src/core/hle/service/nvflinger/buffer_queue_producer.cpp +++ b/src/core/hle/service/nvflinger/buffer_queue_producer.cpp @@ -742,6 +742,13 @@ Status BufferQueueProducer::Disconnect(NativeWindowApi api) { return Status::NoError; } + // HACK: We are not Android. Remove handle for items in queue, and clear queue. + // Allows synchronous destruction of nvmap handles. + for (auto& item : core->queue) { + nvmap.FreeHandle(item.graphic_buffer->BufferId(), true); + } + core->queue.clear(); + switch (api) { case NativeWindowApi::Egl: case NativeWindowApi::Cpu: From 3e6840a74cf2203223058d4fa1e0a0e4784cbd70 Mon Sep 17 00:00:00 2001 From: Liam Date: Tue, 25 Oct 2022 17:47:18 -0400 Subject: [PATCH 89/91] arm_interface: curb infinite recursion in stacktrace generation --- src/core/arm/dynarmic/arm_dynarmic_32.cpp | 2 +- src/core/arm/dynarmic/arm_dynarmic_64.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp index d1e70f19d6..287ba102e6 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp @@ -450,7 +450,7 @@ std::vector ARM_Dynarmic_32::GetBacktrace(Core::S // Frame records are two words long: // fp+0 : pointer to previous frame record // fp+4 : value of lr for frame - while (true) { + for (size_t i = 0; i < 256; i++) { out.push_back({"", 0, lr, 0, ""}); if (!fp || (fp % 4 != 0) || !memory.IsValidVirtualAddressRange(fp, 8)) { break; diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp index 22b5d56568..afb7fb3a00 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp @@ -517,7 +517,7 @@ std::vector ARM_Dynarmic_64::GetBacktrace(Core::S // Frame records are two words long: // fp+0 : pointer to previous frame record // fp+8 : value of lr for frame - while (true) { + for (size_t i = 0; i < 256; i++) { out.push_back({"", 0, lr, 0, ""}); if (!fp || (fp % 4 != 0) || !memory.IsValidVirtualAddressRange(fp, 16)) { break; From d867ae5ab6d958c2a3eb20ba4a9a2aafe9c1a033 Mon Sep 17 00:00:00 2001 From: Liam Date: Sat, 29 Oct 2022 23:05:56 -0400 Subject: [PATCH 90/91] k_server_session: fix crashes --- src/core/hle/kernel/k_server_session.h | 2 +- src/core/hle/service/sm/sm.cpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/core/hle/kernel/k_server_session.h b/src/core/hle/kernel/k_server_session.h index 32135473b7..188aef4af2 100644 --- a/src/core/hle/kernel/k_server_session.h +++ b/src/core/hle/kernel/k_server_session.h @@ -91,7 +91,7 @@ private: /// List of threads which are pending a reply. boost::intrusive::list m_request_list; - KSessionRequest* m_current_request; + KSessionRequest* m_current_request{}; KLightLock m_lock; }; diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp index 48e70f93c7..e2b8d87206 100644 --- a/src/core/hle/service/sm/sm.cpp +++ b/src/core/hle/service/sm/sm.cpp @@ -80,7 +80,6 @@ ResultVal ServiceManager::GetServicePort(const std::string& name } auto* port = Kernel::KPort::Create(kernel); - SCOPE_EXIT({ port->Close(); }); port->Initialize(ServerSessionCountMax, false, name); auto handler = it->second; From 6f0f7f1547cc9b238fe526813d7408ba3e2ce409 Mon Sep 17 00:00:00 2001 From: german77 Date: Sun, 30 Oct 2022 00:34:22 -0500 Subject: [PATCH 91/91] service: am: Stub SetRecordVolumeMuted Used by bayonetta 3 --- src/core/hle/service/am/am.cpp | 13 ++++++++++++- src/core/hle/service/am/am.h | 1 + 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index e55233054f..8ea7fd7608 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -299,7 +299,7 @@ ISelfController::ISelfController(Core::System& system_, NVFlinger::NVFlinger& nv {100, &ISelfController::SetAlbumImageTakenNotificationEnabled, "SetAlbumImageTakenNotificationEnabled"}, {110, nullptr, "SetApplicationAlbumUserData"}, {120, &ISelfController::SaveCurrentScreenshot, "SaveCurrentScreenshot"}, - {130, nullptr, "SetRecordVolumeMuted"}, + {130, &ISelfController::SetRecordVolumeMuted, "SetRecordVolumeMuted"}, {1000, nullptr, "GetDebugStorageChannel"}, }; // clang-format on @@ -597,6 +597,17 @@ void ISelfController::SaveCurrentScreenshot(Kernel::HLERequestContext& ctx) { rb.Push(ResultSuccess); } +void ISelfController::SetRecordVolumeMuted(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const auto is_record_volume_muted = rp.Pop(); + + LOG_WARNING(Service_AM, "(STUBBED) called. is_record_volume_muted={}", is_record_volume_muted); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + AppletMessageQueue::AppletMessageQueue(Core::System& system) : service_context{system, "AppletMessageQueue"} { on_new_message = service_context.CreateEvent("AMMessageQueue:OnMessageReceived"); diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index bb75c62817..a0fbfcfc5f 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -182,6 +182,7 @@ private: void GetAccumulatedSuspendedTickChangedEvent(Kernel::HLERequestContext& ctx); void SetAlbumImageTakenNotificationEnabled(Kernel::HLERequestContext& ctx); void SaveCurrentScreenshot(Kernel::HLERequestContext& ctx); + void SetRecordVolumeMuted(Kernel::HLERequestContext& ctx); enum class ScreenshotPermission : u32 { Inherit = 0,