diff options
39 files changed, 1149 insertions, 302 deletions
diff --git a/externals/dynarmic b/externals/dynarmic -Subproject 550d662f0ece868209f0f760dddc8b2ad4698b1 +Subproject a42f301c28832bb2b4ffaf4fe7b84b9765542f4 diff --git a/src/common/telemetry.cpp b/src/common/telemetry.cpp index bf1f54886..f53a8d193 100644 --- a/src/common/telemetry.cpp +++ b/src/common/telemetry.cpp @@ -3,8 +3,15 @@ // Refer to the license.txt file included. #include <algorithm> +#include <cstring> +#include "common/assert.h" +#include "common/scm_rev.h" #include "common/telemetry.h" +#ifdef ARCHITECTURE_x86_64 +#include "common/x64/cpu_detect.h" +#endif + namespace Telemetry { void FieldCollection::Accept(VisitorInterface& visitor) const { @@ -37,4 +44,62 @@ template class Field<std::string>; template class Field<const char*>; template class Field<std::chrono::microseconds>; +#ifdef ARCHITECTURE_x86_64 +static const char* CpuVendorToStr(Common::CPUVendor vendor) { + switch (vendor) { + case Common::CPUVendor::INTEL: + return "Intel"; + case Common::CPUVendor::AMD: + return "Amd"; + case Common::CPUVendor::OTHER: + return "Other"; + } + UNREACHABLE(); +} +#endif + +void AppendBuildInfo(FieldCollection& fc) { + const bool is_git_dirty{std::strstr(Common::g_scm_desc, "dirty") != nullptr}; + fc.AddField(FieldType::App, "Git_IsDirty", is_git_dirty); + fc.AddField(FieldType::App, "Git_Branch", Common::g_scm_branch); + fc.AddField(FieldType::App, "Git_Revision", Common::g_scm_rev); + fc.AddField(FieldType::App, "BuildDate", Common::g_build_date); + fc.AddField(FieldType::App, "BuildName", Common::g_build_name); +} + +void AppendCPUInfo(FieldCollection& fc) { +#ifdef ARCHITECTURE_x86_64 + fc.AddField(FieldType::UserSystem, "CPU_Model", Common::GetCPUCaps().cpu_string); + fc.AddField(FieldType::UserSystem, "CPU_BrandString", Common::GetCPUCaps().brand_string); + fc.AddField(FieldType::UserSystem, "CPU_Vendor", CpuVendorToStr(Common::GetCPUCaps().vendor)); + fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AES", Common::GetCPUCaps().aes); + fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AVX", Common::GetCPUCaps().avx); + fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AVX2", Common::GetCPUCaps().avx2); + fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_BMI1", Common::GetCPUCaps().bmi1); + fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_BMI2", Common::GetCPUCaps().bmi2); + fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_FMA", Common::GetCPUCaps().fma); + fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_FMA4", Common::GetCPUCaps().fma4); + fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_SSE", Common::GetCPUCaps().sse); + fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_SSE2", Common::GetCPUCaps().sse2); + fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_SSE3", Common::GetCPUCaps().sse3); + fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_SSSE3", Common::GetCPUCaps().ssse3); + fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_SSE41", Common::GetCPUCaps().sse4_1); + fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_SSE42", Common::GetCPUCaps().sse4_2); +#else + fc.AddField(FieldType::UserSystem, "CPU_Model", "Other"); +#endif +} + +void AppendOSInfo(FieldCollection& fc) { +#ifdef __APPLE__ + fc.AddField(FieldType::UserSystem, "OsPlatform", "Apple"); +#elif defined(_WIN32) + fc.AddField(FieldType::UserSystem, "OsPlatform", "Windows"); +#elif defined(__linux__) || defined(linux) || defined(__linux) + fc.AddField(FieldType::UserSystem, "OsPlatform", "Linux"); +#else + fc.AddField(FieldType::UserSystem, "OsPlatform", "Unknown"); +#endif +} + } // namespace Telemetry diff --git a/src/common/telemetry.h b/src/common/telemetry.h index 3bab75b59..8d6ab986b 100644 --- a/src/common/telemetry.h +++ b/src/common/telemetry.h @@ -180,4 +180,16 @@ struct NullVisitor : public VisitorInterface { void Complete() override {} }; +/// Appends build-specific information to the given FieldCollection, +/// such as branch name, revision hash, etc. +void AppendBuildInfo(FieldCollection& fc); + +/// Appends CPU-specific information to the given FieldCollection, +/// such as instruction set extensions, etc. +void AppendCPUInfo(FieldCollection& fc); + +/// Appends OS-specific information to the given FieldCollection, +/// such as platform name, etc. +void AppendOSInfo(FieldCollection& fc); + } // namespace Telemetry diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 67ad6109a..31a7bf6fd 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -122,6 +122,8 @@ add_library(core STATIC hle/service/acc/acc_u0.h hle/service/acc/acc_u1.cpp hle/service/acc/acc_u1.h + hle/service/acc/profile_manager.cpp + hle/service/acc/profile_manager.h hle/service/am/am.cpp hle/service/am/am.h hle/service/am/applet_ae.cpp diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h index b82e65ad5..4b74c54ec 100644 --- a/src/core/file_sys/content_archive.h +++ b/src/core/file_sys/content_archive.h @@ -27,6 +27,7 @@ enum class NCAContentType : u8 { Control = 2, Manual = 3, Data = 4, + Data_Unknown5 = 5, ///< Seems to be used on some system archives }; enum class NCASectionCryptoType : u8 { diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index d25eeee34..e90dc6695 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -77,12 +77,13 @@ static ContentRecordType GetCRTypeFromNCAType(NCAContentType type) { case NCAContentType::Control: return ContentRecordType::Control; case NCAContentType::Data: + case NCAContentType::Data_Unknown5: return ContentRecordType::Data; case NCAContentType::Manual: // TODO(DarkLordZach): Peek at NCA contents to differentiate Manual and Legal. return ContentRecordType::Manual; default: - UNREACHABLE(); + UNREACHABLE_MSG("Invalid NCAContentType={:02X}", static_cast<u8>(type)); } } diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp index 54fbd3267..1b3824a61 100644 --- a/src/core/file_sys/romfs_factory.cpp +++ b/src/core/file_sys/romfs_factory.cpp @@ -6,7 +6,9 @@ #include <memory> #include "common/common_types.h" #include "common/logging/log.h" +#include "core/core.h" #include "core/file_sys/romfs_factory.h" +#include "core/hle/kernel/process.h" namespace FileSys { @@ -17,9 +19,41 @@ RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader) { } } -ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id) { - // TODO(DarkLordZach): Use title id. +ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() { return MakeResult<VirtualFile>(file); } +ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, ContentRecordType type) { + switch (storage) { + case StorageId::NandSystem: { + const auto res = Service::FileSystem::GetSystemNANDContents()->GetEntry(title_id, type); + if (res == nullptr) { + // TODO(DarkLordZach): Find the right error code to use here + return ResultCode(-1); + } + const auto romfs = res->GetRomFS(); + if (romfs == nullptr) { + // TODO(DarkLordZach): Find the right error code to use here + return ResultCode(-1); + } + return MakeResult<VirtualFile>(romfs); + } + case StorageId::NandUser: { + const auto res = Service::FileSystem::GetUserNANDContents()->GetEntry(title_id, type); + if (res == nullptr) { + // TODO(DarkLordZach): Find the right error code to use here + return ResultCode(-1); + } + const auto romfs = res->GetRomFS(); + if (romfs == nullptr) { + // TODO(DarkLordZach): Find the right error code to use here + return ResultCode(-1); + } + return MakeResult<VirtualFile>(romfs); + } + default: + UNIMPLEMENTED_MSG("Unimplemented storage_id={:02X}", static_cast<u8>(storage)); + } +} + } // namespace FileSys diff --git a/src/core/file_sys/romfs_factory.h b/src/core/file_sys/romfs_factory.h index c19787cd4..455cd4159 100644 --- a/src/core/file_sys/romfs_factory.h +++ b/src/core/file_sys/romfs_factory.h @@ -11,12 +11,22 @@ namespace FileSys { +enum class StorageId : u8 { + None = 0, + Host = 1, + GameCard = 2, + NandSystem = 3, + NandUser = 4, + SdCard = 5, +}; + /// File system interface to the RomFS archive class RomFSFactory { public: explicit RomFSFactory(Loader::AppLoader& app_loader); - ResultVal<VirtualFile> Open(u64 title_id); + ResultVal<VirtualFile> OpenCurrentProcess(); + ResultVal<VirtualFile> Open(u64 title_id, StorageId storage, ContentRecordType type); private: VirtualFile file; diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp index f3c5b1b9c..1502dbf55 100644 --- a/src/core/hle/service/acc/acc.cpp +++ b/src/core/hle/service/acc/acc.cpp @@ -3,17 +3,19 @@ // Refer to the license.txt file included. #include <array> +#include "common/common_types.h" #include "common/logging/log.h" +#include "common/swap.h" +#include "core/core_timing.h" #include "core/hle/ipc_helpers.h" #include "core/hle/service/acc/acc.h" #include "core/hle/service/acc/acc_aa.h" #include "core/hle/service/acc/acc_su.h" #include "core/hle/service/acc/acc_u0.h" #include "core/hle/service/acc/acc_u1.h" -#include "core/settings.h" +#include "core/hle/service/acc/profile_manager.h" namespace Service::Account { - // TODO: RE this structure struct UserData { INSERT_PADDING_WORDS(1); @@ -25,19 +27,10 @@ struct UserData { }; static_assert(sizeof(UserData) == 0x80, "UserData structure has incorrect size"); -struct ProfileBase { - u128 user_id; - u64 timestamp; - std::array<u8, 0x20> username; -}; -static_assert(sizeof(ProfileBase) == 0x38, "ProfileBase structure has incorrect size"); - -// TODO(ogniK): Generate a real user id based on username, md5(username) maybe? -static constexpr u128 DEFAULT_USER_ID{1ull, 0ull}; - class IProfile final : public ServiceFramework<IProfile> { public: - explicit IProfile(u128 user_id) : ServiceFramework("IProfile"), user_id(user_id) { + explicit IProfile(UUID user_id, ProfileManager& profile_manager) + : ServiceFramework("IProfile"), profile_manager(profile_manager), user_id(user_id) { static const FunctionInfo functions[] = { {0, &IProfile::Get, "Get"}, {1, &IProfile::GetBase, "GetBase"}, @@ -49,46 +42,42 @@ public: private: void Get(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_ACC, "(STUBBED) called"); + LOG_INFO(Service_ACC, "called user_id={}", user_id.Format()); ProfileBase profile_base{}; - profile_base.user_id = user_id; - if (Settings::values.username.size() > profile_base.username.size()) { - std::copy_n(Settings::values.username.begin(), profile_base.username.size(), - profile_base.username.begin()); + std::array<u8, MAX_DATA> data{}; + if (profile_manager.GetProfileBaseAndData(user_id, profile_base, data)) { + ctx.WriteBuffer(data); + IPC::ResponseBuilder rb{ctx, 16}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw(profile_base); } else { - std::copy(Settings::values.username.begin(), Settings::values.username.end(), - profile_base.username.begin()); + LOG_ERROR(Service_ACC, "Failed to get profile base and data for user={}", + user_id.Format()); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultCode(-1)); // TODO(ogniK): Get actual error code } - - IPC::ResponseBuilder rb{ctx, 16}; - rb.Push(RESULT_SUCCESS); - rb.PushRaw(profile_base); } void GetBase(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_ACC, "(STUBBED) called"); - - // TODO(Subv): Retrieve this information from somewhere. + LOG_INFO(Service_ACC, "called user_id={}", user_id.Format()); ProfileBase profile_base{}; - profile_base.user_id = user_id; - if (Settings::values.username.size() > profile_base.username.size()) { - std::copy_n(Settings::values.username.begin(), profile_base.username.size(), - profile_base.username.begin()); + if (profile_manager.GetProfileBase(user_id, profile_base)) { + IPC::ResponseBuilder rb{ctx, 16}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw(profile_base); } else { - std::copy(Settings::values.username.begin(), Settings::values.username.end(), - profile_base.username.begin()); + LOG_ERROR(Service_ACC, "Failed to get profile base for user={}", user_id.Format()); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultCode(-1)); // TODO(ogniK): Get actual error code } - IPC::ResponseBuilder rb{ctx, 16}; - rb.Push(RESULT_SUCCESS); - rb.PushRaw(profile_base); } void LoadImage(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_ACC, "(STUBBED) called"); // smallest jpeg https://github.com/mathiasbynens/small/blob/master/jpeg.jpg // TODO(mailwl): load actual profile image from disk, width 256px, max size 0x20000 - const u32 jpeg_size = 107; - static const std::array<u8, jpeg_size> jpeg{ + constexpr u32 jpeg_size = 107; + static constexpr std::array<u8, jpeg_size> jpeg{ 0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x06, 0x06, 0x05, 0x06, 0x09, 0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a, @@ -98,13 +87,14 @@ private: 0xff, 0xcc, 0x00, 0x06, 0x00, 0x10, 0x10, 0x05, 0xff, 0xda, 0x00, 0x08, 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9, }; - ctx.WriteBuffer(jpeg.data(), jpeg_size); + ctx.WriteBuffer(jpeg); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(jpeg_size); } - u128 user_id; ///< The user id this profile refers to. + const ProfileManager& profile_manager; + UUID user_id; ///< The user id this profile refers to. }; class IManagerForApplication final : public ServiceFramework<IManagerForApplication> { @@ -141,44 +131,57 @@ private: }; void Module::Interface::GetUserCount(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_ACC, "(STUBBED) called"); + LOG_INFO(Service_ACC, "called"); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>(1); + rb.Push<u32>(static_cast<u32>(profile_manager->GetUserCount())); } void Module::Interface::GetUserExistence(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_ACC, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + UUID user_id = rp.PopRaw<UUID>(); + LOG_INFO(Service_ACC, "called user_id={}", user_id.Format()); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push(true); // TODO: Check when this is supposed to return true and when not + rb.Push(profile_manager->UserExists(user_id)); } void Module::Interface::ListAllUsers(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_ACC, "(STUBBED) called"); - // TODO(Subv): There is only one user for now. - const std::vector<u128> user_ids = {DEFAULT_USER_ID}; - ctx.WriteBuffer(user_ids); + LOG_INFO(Service_ACC, "called"); + ctx.WriteBuffer(profile_manager->GetAllUsers()); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } void Module::Interface::ListOpenUsers(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_ACC, "(STUBBED) called"); - // TODO(Subv): There is only one user for now. - const std::vector<u128> user_ids = {DEFAULT_USER_ID}; - ctx.WriteBuffer(user_ids); + LOG_INFO(Service_ACC, "called"); + ctx.WriteBuffer(profile_manager->GetOpenUsers()); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } +void Module::Interface::GetLastOpenedUser(Kernel::HLERequestContext& ctx) { + LOG_INFO(Service_ACC, "called"); + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw<UUID>(profile_manager->GetLastOpenedUser()); +} + void Module::Interface::GetProfile(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - u128 user_id = rp.PopRaw<u128>(); + UUID user_id = rp.PopRaw<UUID>(); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IProfile>(user_id); - LOG_DEBUG(Service_ACC, "called user_id=0x{:016X}{:016X}", user_id[1], user_id[0]); + rb.PushIpcInterface<IProfile>(user_id, *profile_manager); + LOG_DEBUG(Service_ACC, "called user_id={}", user_id.Format()); +} + +void Module::Interface::IsUserRegistrationRequestPermitted(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_ACC, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(profile_manager->CanSystemRegisterUser()); } void Module::Interface::InitializeApplicationInfo(Kernel::HLERequestContext& ctx) { @@ -194,22 +197,20 @@ void Module::Interface::GetBaasAccountManagerForApplication(Kernel::HLERequestCo LOG_DEBUG(Service_ACC, "called"); } -void Module::Interface::GetLastOpenedUser(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_ACC, "(STUBBED) called"); - IPC::ResponseBuilder rb{ctx, 6}; - rb.Push(RESULT_SUCCESS); - rb.PushRaw(DEFAULT_USER_ID); -} +Module::Interface::Interface(std::shared_ptr<Module> module, + std::shared_ptr<ProfileManager> profile_manager, const char* name) + : ServiceFramework(name), module(std::move(module)), + profile_manager(std::move(profile_manager)) {} -Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) - : ServiceFramework(name), module(std::move(module)) {} +Module::Interface::~Interface() = default; void InstallInterfaces(SM::ServiceManager& service_manager) { auto module = std::make_shared<Module>(); - std::make_shared<ACC_AA>(module)->InstallAsService(service_manager); - std::make_shared<ACC_SU>(module)->InstallAsService(service_manager); - std::make_shared<ACC_U0>(module)->InstallAsService(service_manager); - std::make_shared<ACC_U1>(module)->InstallAsService(service_manager); + auto profile_manager = std::make_shared<ProfileManager>(); + std::make_shared<ACC_AA>(module, profile_manager)->InstallAsService(service_manager); + std::make_shared<ACC_SU>(module, profile_manager)->InstallAsService(service_manager); + std::make_shared<ACC_U0>(module, profile_manager)->InstallAsService(service_manager); + std::make_shared<ACC_U1>(module, profile_manager)->InstallAsService(service_manager); } } // namespace Service::Account diff --git a/src/core/hle/service/acc/acc.h b/src/core/hle/service/acc/acc.h index 88cabaa01..c7ed74351 100644 --- a/src/core/hle/service/acc/acc.h +++ b/src/core/hle/service/acc/acc.h @@ -8,11 +8,15 @@ namespace Service::Account { +class ProfileManager; + class Module final { public: class Interface : public ServiceFramework<Interface> { public: - explicit Interface(std::shared_ptr<Module> module, const char* name); + explicit Interface(std::shared_ptr<Module> module, + std::shared_ptr<ProfileManager> profile_manager, const char* name); + ~Interface() override; void GetUserCount(Kernel::HLERequestContext& ctx); void GetUserExistence(Kernel::HLERequestContext& ctx); @@ -22,9 +26,11 @@ public: void GetProfile(Kernel::HLERequestContext& ctx); void InitializeApplicationInfo(Kernel::HLERequestContext& ctx); void GetBaasAccountManagerForApplication(Kernel::HLERequestContext& ctx); + void IsUserRegistrationRequestPermitted(Kernel::HLERequestContext& ctx); protected: std::shared_ptr<Module> module; + std::shared_ptr<ProfileManager> profile_manager; }; }; diff --git a/src/core/hle/service/acc/acc_aa.cpp b/src/core/hle/service/acc/acc_aa.cpp index 280b3e464..9bd595a37 100644 --- a/src/core/hle/service/acc/acc_aa.cpp +++ b/src/core/hle/service/acc/acc_aa.cpp @@ -6,7 +6,8 @@ namespace Service::Account { -ACC_AA::ACC_AA(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "acc:aa") { +ACC_AA::ACC_AA(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager) + : Module::Interface(std::move(module), std::move(profile_manager), "acc:aa") { static const FunctionInfo functions[] = { {0, nullptr, "EnsureCacheAsync"}, {1, nullptr, "LoadCache"}, diff --git a/src/core/hle/service/acc/acc_aa.h b/src/core/hle/service/acc/acc_aa.h index 796f7ef85..2e08c781a 100644 --- a/src/core/hle/service/acc/acc_aa.h +++ b/src/core/hle/service/acc/acc_aa.h @@ -10,7 +10,8 @@ namespace Service::Account { class ACC_AA final : public Module::Interface { public: - explicit ACC_AA(std::shared_ptr<Module> module); + explicit ACC_AA(std::shared_ptr<Module> module, + std::shared_ptr<ProfileManager> profile_manager); }; } // namespace Service::Account diff --git a/src/core/hle/service/acc/acc_su.cpp b/src/core/hle/service/acc/acc_su.cpp index 8b2a71f37..0218ee859 100644 --- a/src/core/hle/service/acc/acc_su.cpp +++ b/src/core/hle/service/acc/acc_su.cpp @@ -6,7 +6,8 @@ namespace Service::Account { -ACC_SU::ACC_SU(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "acc:su") { +ACC_SU::ACC_SU(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager) + : Module::Interface(std::move(module), std::move(profile_manager), "acc:su") { static const FunctionInfo functions[] = { {0, &ACC_SU::GetUserCount, "GetUserCount"}, {1, &ACC_SU::GetUserExistence, "GetUserExistence"}, @@ -15,7 +16,7 @@ ACC_SU::ACC_SU(std::shared_ptr<Module> module) : Module::Interface(std::move(mod {4, &ACC_SU::GetLastOpenedUser, "GetLastOpenedUser"}, {5, &ACC_SU::GetProfile, "GetProfile"}, {6, nullptr, "GetProfileDigest"}, - {50, nullptr, "IsUserRegistrationRequestPermitted"}, + {50, &ACC_SU::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"}, {51, nullptr, "TrySelectUserWithoutInteraction"}, {60, nullptr, "ListOpenContextStoredUsers"}, {100, nullptr, "GetUserRegistrationNotifier"}, diff --git a/src/core/hle/service/acc/acc_su.h b/src/core/hle/service/acc/acc_su.h index 3894a6991..79a47d88d 100644 --- a/src/core/hle/service/acc/acc_su.h +++ b/src/core/hle/service/acc/acc_su.h @@ -11,7 +11,8 @@ namespace Account { class ACC_SU final : public Module::Interface { public: - explicit ACC_SU(std::shared_ptr<Module> module); + explicit ACC_SU(std::shared_ptr<Module> module, + std::shared_ptr<ProfileManager> profile_manager); }; } // namespace Account diff --git a/src/core/hle/service/acc/acc_u0.cpp b/src/core/hle/service/acc/acc_u0.cpp index d84c8b2e1..84a4d05b8 100644 --- a/src/core/hle/service/acc/acc_u0.cpp +++ b/src/core/hle/service/acc/acc_u0.cpp @@ -6,7 +6,8 @@ namespace Service::Account { -ACC_U0::ACC_U0(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "acc:u0") { +ACC_U0::ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager) + : Module::Interface(std::move(module), std::move(profile_manager), "acc:u0") { static const FunctionInfo functions[] = { {0, &ACC_U0::GetUserCount, "GetUserCount"}, {1, &ACC_U0::GetUserExistence, "GetUserExistence"}, @@ -15,7 +16,7 @@ ACC_U0::ACC_U0(std::shared_ptr<Module> module) : Module::Interface(std::move(mod {4, &ACC_U0::GetLastOpenedUser, "GetLastOpenedUser"}, {5, &ACC_U0::GetProfile, "GetProfile"}, {6, nullptr, "GetProfileDigest"}, - {50, nullptr, "IsUserRegistrationRequestPermitted"}, + {50, &ACC_U0::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"}, {51, nullptr, "TrySelectUserWithoutInteraction"}, {60, nullptr, "ListOpenContextStoredUsers"}, {100, &ACC_U0::InitializeApplicationInfo, "InitializeApplicationInfo"}, diff --git a/src/core/hle/service/acc/acc_u0.h b/src/core/hle/service/acc/acc_u0.h index 6ded596b3..e8a114f99 100644 --- a/src/core/hle/service/acc/acc_u0.h +++ b/src/core/hle/service/acc/acc_u0.h @@ -10,7 +10,8 @@ namespace Service::Account { class ACC_U0 final : public Module::Interface { public: - explicit ACC_U0(std::shared_ptr<Module> module); + explicit ACC_U0(std::shared_ptr<Module> module, + std::shared_ptr<ProfileManager> profile_manager); }; } // namespace Service::Account diff --git a/src/core/hle/service/acc/acc_u1.cpp b/src/core/hle/service/acc/acc_u1.cpp index 0ceaf06b5..495693949 100644 --- a/src/core/hle/service/acc/acc_u1.cpp +++ b/src/core/hle/service/acc/acc_u1.cpp @@ -6,7 +6,8 @@ namespace Service::Account { -ACC_U1::ACC_U1(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "acc:u1") { +ACC_U1::ACC_U1(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager) + : Module::Interface(std::move(module), std::move(profile_manager), "acc:u1") { static const FunctionInfo functions[] = { {0, &ACC_U1::GetUserCount, "GetUserCount"}, {1, &ACC_U1::GetUserExistence, "GetUserExistence"}, @@ -15,7 +16,7 @@ ACC_U1::ACC_U1(std::shared_ptr<Module> module) : Module::Interface(std::move(mod {4, &ACC_U1::GetLastOpenedUser, "GetLastOpenedUser"}, {5, &ACC_U1::GetProfile, "GetProfile"}, {6, nullptr, "GetProfileDigest"}, - {50, nullptr, "IsUserRegistrationRequestPermitted"}, + {50, &ACC_U1::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"}, {51, nullptr, "TrySelectUserWithoutInteraction"}, {60, nullptr, "ListOpenContextStoredUsers"}, {100, nullptr, "GetUserRegistrationNotifier"}, diff --git a/src/core/hle/service/acc/acc_u1.h b/src/core/hle/service/acc/acc_u1.h index 5e3e7659b..a77520e6f 100644 --- a/src/core/hle/service/acc/acc_u1.h +++ b/src/core/hle/service/acc/acc_u1.h @@ -10,7 +10,8 @@ namespace Service::Account { class ACC_U1 final : public Module::Interface { public: - explicit ACC_U1(std::shared_ptr<Module> module); + explicit ACC_U1(std::shared_ptr<Module> module, + std::shared_ptr<ProfileManager> profile_manager); }; } // namespace Service::Account diff --git a/src/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp new file mode 100644 index 000000000..e0b03d763 --- /dev/null +++ b/src/core/hle/service/acc/profile_manager.cpp @@ -0,0 +1,236 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <random> +#include <boost/optional.hpp> +#include "core/hle/service/acc/profile_manager.h" +#include "core/settings.h" + +namespace Service::Account { +// TODO(ogniK): Get actual error codes +constexpr ResultCode ERROR_TOO_MANY_USERS(ErrorModule::Account, -1); +constexpr ResultCode ERROR_USER_ALREADY_EXISTS(ErrorModule::Account, -2); +constexpr ResultCode ERROR_ARGUMENT_IS_NULL(ErrorModule::Account, 20); + +const UUID& UUID::Generate() { + std::random_device device; + std::mt19937 gen(device()); + std::uniform_int_distribution<u64> distribution(1, std::numeric_limits<u64>::max()); + uuid[0] = distribution(gen); + uuid[1] = distribution(gen); + return *this; +} + +ProfileManager::ProfileManager() { + // TODO(ogniK): Create the default user we have for now until loading/saving users is added + auto user_uuid = UUID{1, 0}; + CreateNewUser(user_uuid, Settings::values.username); + OpenUser(user_uuid); +} + +/// After a users creation it needs to be "registered" to the system. AddToProfiles handles the +/// internal management of the users profiles +boost::optional<size_t> ProfileManager::AddToProfiles(const ProfileInfo& user) { + if (user_count >= MAX_USERS) { + return boost::none; + } + profiles[user_count] = user; + return user_count++; +} + +/// Deletes a specific profile based on it's profile index +bool ProfileManager::RemoveProfileAtIndex(size_t index) { + if (index >= MAX_USERS || index >= user_count) { + return false; + } + if (index < user_count - 1) { + std::rotate(profiles.begin() + index, profiles.begin() + index + 1, profiles.end()); + } + profiles.back() = {}; + user_count--; + return true; +} + +/// Helper function to register a user to the system +ResultCode ProfileManager::AddUser(const ProfileInfo& user) { + if (AddToProfiles(user) == boost::none) { + return ERROR_TOO_MANY_USERS; + } + return RESULT_SUCCESS; +} + +/// Create a new user on the system. If the uuid of the user already exists, the user is not +/// created. +ResultCode ProfileManager::CreateNewUser(UUID uuid, const ProfileUsername& username) { + if (user_count == MAX_USERS) { + return ERROR_TOO_MANY_USERS; + } + if (!uuid) { + return ERROR_ARGUMENT_IS_NULL; + } + if (username[0] == 0x0) { + return ERROR_ARGUMENT_IS_NULL; + } + if (std::any_of(profiles.begin(), profiles.end(), + [&uuid](const ProfileInfo& profile) { return uuid == profile.user_uuid; })) { + return ERROR_USER_ALREADY_EXISTS; + } + ProfileInfo profile; + profile.user_uuid = uuid; + profile.username = username; + profile.data = {}; + profile.creation_time = 0x0; + profile.is_open = false; + return AddUser(profile); +} + +/// Creates a new user on the system. This function allows a much simpler method of registration +/// specifically by allowing an std::string for the username. This is required specifically since +/// we're loading a string straight from the config +ResultCode ProfileManager::CreateNewUser(UUID uuid, const std::string& username) { + ProfileUsername username_output; + if (username.size() > username_output.size()) { + std::copy_n(username.begin(), username_output.size(), username_output.begin()); + } else { + std::copy(username.begin(), username.end(), username_output.begin()); + } + return CreateNewUser(uuid, username_output); +} + +/// Returns a users profile index based on their user id. +boost::optional<size_t> ProfileManager::GetUserIndex(const UUID& uuid) const { + if (!uuid) { + return boost::none; + } + auto iter = std::find_if(profiles.begin(), profiles.end(), + [&uuid](const ProfileInfo& p) { return p.user_uuid == uuid; }); + if (iter == profiles.end()) { + return boost::none; + } + return static_cast<size_t>(std::distance(profiles.begin(), iter)); +} + +/// Returns a users profile index based on their profile +boost::optional<size_t> ProfileManager::GetUserIndex(const ProfileInfo& user) const { + return GetUserIndex(user.user_uuid); +} + +/// Returns the data structure used by the switch when GetProfileBase is called on acc:* +bool ProfileManager::GetProfileBase(boost::optional<size_t> index, ProfileBase& profile) const { + if (index == boost::none || index >= MAX_USERS) { + return false; + } + const auto& prof_info = profiles[index.get()]; + profile.user_uuid = prof_info.user_uuid; + profile.username = prof_info.username; + profile.timestamp = prof_info.creation_time; + return true; +} + +/// Returns the data structure used by the switch when GetProfileBase is called on acc:* +bool ProfileManager::GetProfileBase(UUID uuid, ProfileBase& profile) const { + auto idx = GetUserIndex(uuid); + return GetProfileBase(idx, profile); +} + +/// Returns the data structure used by the switch when GetProfileBase is called on acc:* +bool ProfileManager::GetProfileBase(const ProfileInfo& user, ProfileBase& profile) const { + return GetProfileBase(user.user_uuid, profile); +} + +/// Returns the current user count on the system. We keep a variable which tracks the count so we +/// don't have to loop the internal profile array every call. +size_t ProfileManager::GetUserCount() const { + return user_count; +} + +/// Lists the current "opened" users on the system. Users are typically not open until they sign +/// into something or pick a profile. As of right now users should all be open until qlaunch is +/// booting +size_t ProfileManager::GetOpenUserCount() const { + return std::count_if(profiles.begin(), profiles.end(), + [](const ProfileInfo& p) { return p.is_open; }); +} + +/// Checks if a user id exists in our profile manager +bool ProfileManager::UserExists(UUID uuid) const { + return (GetUserIndex(uuid) != boost::none); +} + +/// Opens a specific user +void ProfileManager::OpenUser(UUID uuid) { + auto idx = GetUserIndex(uuid); + if (idx == boost::none) { + return; + } + profiles[idx.get()].is_open = true; + last_opened_user = uuid; +} + +/// Closes a specific user +void ProfileManager::CloseUser(UUID uuid) { + auto idx = GetUserIndex(uuid); + if (idx == boost::none) { + return; + } + profiles[idx.get()].is_open = false; +} + +/// Gets all valid user ids on the system +UserIDArray ProfileManager::GetAllUsers() const { + UserIDArray output; + std::transform(profiles.begin(), profiles.end(), output.begin(), + [](const ProfileInfo& p) { return p.user_uuid; }); + return output; +} + +/// Get all the open users on the system and zero out the rest of the data. This is specifically +/// needed for GetOpenUsers and we need to ensure the rest of the output buffer is zero'd out +UserIDArray ProfileManager::GetOpenUsers() const { + UserIDArray output; + std::transform(profiles.begin(), profiles.end(), output.begin(), [](const ProfileInfo& p) { + if (p.is_open) + return p.user_uuid; + return UUID{}; + }); + std::stable_partition(output.begin(), output.end(), [](const UUID& uuid) { return uuid; }); + return output; +} + +/// Returns the last user which was opened +UUID ProfileManager::GetLastOpenedUser() const { + return last_opened_user; +} + +/// Return the users profile base and the unknown arbitary data. +bool ProfileManager::GetProfileBaseAndData(boost::optional<size_t> index, ProfileBase& profile, + ProfileData& data) const { + if (GetProfileBase(index, profile)) { + data = profiles[index.get()].data; + return true; + } + return false; +} + +/// Return the users profile base and the unknown arbitary data. +bool ProfileManager::GetProfileBaseAndData(UUID uuid, ProfileBase& profile, + ProfileData& data) const { + auto idx = GetUserIndex(uuid); + return GetProfileBaseAndData(idx, profile, data); +} + +/// Return the users profile base and the unknown arbitary data. +bool ProfileManager::GetProfileBaseAndData(const ProfileInfo& user, ProfileBase& profile, + ProfileData& data) const { + return GetProfileBaseAndData(user.user_uuid, profile, data); +} + +/// Returns if the system is allowing user registrations or not +bool ProfileManager::CanSystemRegisterUser() const { + return false; // TODO(ogniK): Games shouldn't have + // access to user registration, when we + // emulate qlaunch. Update this to dynamically change. +} + +}; // namespace Service::Account diff --git a/src/core/hle/service/acc/profile_manager.h b/src/core/hle/service/acc/profile_manager.h new file mode 100644 index 000000000..52967844d --- /dev/null +++ b/src/core/hle/service/acc/profile_manager.h @@ -0,0 +1,117 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> + +#include "boost/optional.hpp" +#include "common/common_types.h" +#include "common/swap.h" +#include "core/hle/result.h" + +namespace Service::Account { +constexpr size_t MAX_USERS = 8; +constexpr size_t MAX_DATA = 128; +constexpr u128 INVALID_UUID{{0, 0}}; + +struct UUID { + // UUIDs which are 0 are considered invalid! + u128 uuid = INVALID_UUID; + UUID() = default; + explicit UUID(const u128& id) : uuid{id} {} + explicit UUID(const u64 lo, const u64 hi) : uuid{{lo, hi}} {} + + explicit operator bool() const { + return uuid != INVALID_UUID; + } + + bool operator==(const UUID& rhs) const { + return uuid == rhs.uuid; + } + + bool operator!=(const UUID& rhs) const { + return !operator==(rhs); + } + + // TODO(ogniK): Properly generate uuids based on RFC-4122 + const UUID& Generate(); + + // Set the UUID to {0,0} to be considered an invalid user + void Invalidate() { + uuid = INVALID_UUID; + } + std::string Format() const { + return fmt::format("0x{:016X}{:016X}", uuid[1], uuid[0]); + } +}; +static_assert(sizeof(UUID) == 16, "UUID is an invalid size!"); + +using ProfileUsername = std::array<u8, 0x20>; +using ProfileData = std::array<u8, MAX_DATA>; +using UserIDArray = std::array<UUID, MAX_USERS>; + +/// This holds general information about a users profile. This is where we store all the information +/// based on a specific user +struct ProfileInfo { + UUID user_uuid; + ProfileUsername username; + u64 creation_time; + ProfileData data; // TODO(ognik): Work out what this is + bool is_open; +}; + +struct ProfileBase { + UUID user_uuid; + u64_le timestamp; + ProfileUsername username; + + // Zero out all the fields to make the profile slot considered "Empty" + void Invalidate() { + user_uuid.Invalidate(); + timestamp = 0; + username.fill(0); + } +}; +static_assert(sizeof(ProfileBase) == 0x38, "ProfileBase is an invalid size"); + +/// The profile manager is used for handling multiple user profiles at once. It keeps track of open +/// users, all the accounts registered on the "system" as well as fetching individual "ProfileInfo" +/// objects +class ProfileManager { +public: + ProfileManager(); // TODO(ogniK): Load from system save + ResultCode AddUser(const ProfileInfo& user); + ResultCode CreateNewUser(UUID uuid, const ProfileUsername& username); + ResultCode CreateNewUser(UUID uuid, const std::string& username); + boost::optional<size_t> GetUserIndex(const UUID& uuid) const; + boost::optional<size_t> GetUserIndex(const ProfileInfo& user) const; + bool GetProfileBase(boost::optional<size_t> index, ProfileBase& profile) const; + bool GetProfileBase(UUID uuid, ProfileBase& profile) const; + bool GetProfileBase(const ProfileInfo& user, ProfileBase& profile) const; + bool GetProfileBaseAndData(boost::optional<size_t> index, ProfileBase& profile, + ProfileData& data) const; + bool GetProfileBaseAndData(UUID uuid, ProfileBase& profile, ProfileData& data) const; + bool GetProfileBaseAndData(const ProfileInfo& user, ProfileBase& profile, + ProfileData& data) const; + size_t GetUserCount() const; + size_t GetOpenUserCount() const; + bool UserExists(UUID uuid) const; + void OpenUser(UUID uuid); + void CloseUser(UUID uuid); + UserIDArray GetOpenUsers() const; + UserIDArray GetAllUsers() const; + UUID GetLastOpenedUser() const; + + bool CanSystemRegisterUser() const; + +private: + std::array<ProfileInfo, MAX_USERS> profiles{}; + size_t user_count = 0; + boost::optional<size_t> AddToProfiles(const ProfileInfo& profile); + bool RemoveProfileAtIndex(size_t index); + UUID last_opened_user{INVALID_UUID}; +}; + +}; // namespace Service::Account diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index da658cbe6..f374111c1 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -256,15 +256,28 @@ ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory) { return RESULT_SUCCESS; } -ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id) { - LOG_TRACE(Service_FS, "Opening RomFS for title_id={:016X}", title_id); +ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess() { + LOG_TRACE(Service_FS, "Opening RomFS for current process"); if (romfs_factory == nullptr) { // TODO(bunnei): Find a better error code for this return ResultCode(-1); } - return romfs_factory->Open(title_id); + return romfs_factory->OpenCurrentProcess(); +} + +ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId storage_id, + FileSys::ContentRecordType type) { + LOG_TRACE(Service_FS, "Opening RomFS for title_id={:016X}, storage_id={:02X}, type={:02X}", + title_id, static_cast<u8>(storage_id), static_cast<u8>(type)); + + if (romfs_factory == nullptr) { + // TODO(bunnei): Find a better error code for this + return ResultCode(-1); + } + + return romfs_factory->Open(title_id, storage_id, type); } ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space, diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h index 1d6f922dd..37a2878b0 100644 --- a/src/core/hle/service/filesystem/filesystem.h +++ b/src/core/hle/service/filesystem/filesystem.h @@ -27,7 +27,9 @@ ResultCode RegisterSaveData(std::unique_ptr<FileSys::SaveDataFactory>&& factory) ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory); ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory); -ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id); +ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess(); +ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId storage_id, + FileSys::ContentRecordType type); ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space, FileSys::SaveDataDescriptor save_struct); ResultVal<FileSys::VirtualDir> OpenSDMC(); diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp index 1470f9017..2f8a7a3c1 100644 --- a/src/core/hle/service/filesystem/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp_srv.cpp @@ -23,15 +23,6 @@ namespace Service::FileSystem { -enum class StorageId : u8 { - None = 0, - Host = 1, - GameCard = 2, - NandSystem = 3, - NandUser = 4, - SdCard = 5, -}; - class IStorage final : public ServiceFramework<IStorage> { public: explicit IStorage(FileSys::VirtualFile backend_) @@ -467,7 +458,7 @@ FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") { {110, nullptr, "OpenContentStorageFileSystem"}, {200, &FSP_SRV::OpenDataStorageByCurrentProcess, "OpenDataStorageByCurrentProcess"}, {201, nullptr, "OpenDataStorageByProgramId"}, - {202, nullptr, "OpenDataStorageByDataId"}, + {202, &FSP_SRV::OpenDataStorageByDataId, "OpenDataStorageByDataId"}, {203, &FSP_SRV::OpenRomStorage, "OpenRomStorage"}, {400, nullptr, "OpenDeviceOperator"}, {500, nullptr, "OpenSdCardDetectionEventNotifier"}, @@ -580,7 +571,7 @@ void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) { void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_FS, "called"); - auto romfs = OpenRomFS(Core::System::GetInstance().CurrentProcess()->program_id); + auto romfs = OpenRomFSCurrentProcess(); if (romfs.Failed()) { // TODO (bunnei): Find the right error code to use here LOG_CRITICAL(Service_FS, "no file system interface available!"); @@ -596,10 +587,37 @@ void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) { rb.PushIpcInterface<IStorage>(std::move(storage)); } +void FSP_SRV::OpenDataStorageByDataId(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto storage_id = rp.PopRaw<FileSys::StorageId>(); + const auto unknown = rp.PopRaw<u32>(); + const auto title_id = rp.PopRaw<u64>(); + + LOG_DEBUG(Service_FS, "called with storage_id={:02X}, unknown={:08X}, title_id={:016X}", + static_cast<u8>(storage_id), unknown, title_id); + + auto data = OpenRomFS(title_id, storage_id, FileSys::ContentRecordType::Data); + if (data.Failed()) { + // TODO(DarkLordZach): Find the right error code to use here + LOG_ERROR(Service_FS, + "could not open data storage with title_id={:016X}, storage_id={:02X}", title_id, + static_cast<u8>(storage_id)); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultCode(-1)); + return; + } + + IStorage storage(std::move(data.Unwrap())); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IStorage>(std::move(storage)); +} + void FSP_SRV::OpenRomStorage(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - auto storage_id = rp.PopRaw<StorageId>(); + auto storage_id = rp.PopRaw<FileSys::StorageId>(); auto title_id = rp.PopRaw<u64>(); LOG_DEBUG(Service_FS, "called with storage_id={:02X}, title_id={:016X}", diff --git a/src/core/hle/service/filesystem/fsp_srv.h b/src/core/hle/service/filesystem/fsp_srv.h index 07f99c93d..f073ac523 100644 --- a/src/core/hle/service/filesystem/fsp_srv.h +++ b/src/core/hle/service/filesystem/fsp_srv.h @@ -25,6 +25,7 @@ private: void MountSaveData(Kernel::HLERequestContext& ctx); void GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx); void OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx); + void OpenDataStorageByDataId(Kernel::HLERequestContext& ctx); void OpenRomStorage(Kernel::HLERequestContext& ctx); FileSys::VirtualFile romfs; diff --git a/src/core/hle/service/pctl/module.cpp b/src/core/hle/service/pctl/module.cpp index fcf1f3da3..6cc3b1992 100644 --- a/src/core/hle/service/pctl/module.cpp +++ b/src/core/hle/service/pctl/module.cpp @@ -14,7 +14,8 @@ public: IParentalControlService() : ServiceFramework("IParentalControlService") { static const FunctionInfo functions[] = { {1, &IParentalControlService::Initialize, "Initialize"}, - {1001, nullptr, "CheckFreeCommunicationPermission"}, + {1001, &IParentalControlService::CheckFreeCommunicationPermission, + "CheckFreeCommunicationPermission"}, {1002, nullptr, "ConfirmLaunchApplicationPermission"}, {1003, nullptr, "ConfirmResumeApplicationPermission"}, {1004, nullptr, "ConfirmSnsPostPermission"}, @@ -116,6 +117,12 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 0}; rb.Push(RESULT_SUCCESS); } + + void CheckFreeCommunicationPermission(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_PCTL, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } }; void Module::Interface::CreateService(Kernel::HLERequestContext& ctx) { diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp index 69aa7a7be..7e4584fc2 100644 --- a/src/core/telemetry_session.cpp +++ b/src/core/telemetry_session.cpp @@ -2,34 +2,16 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <cstring> - #include "common/assert.h" +#include "common/common_types.h" #include "common/file_util.h" -#include "common/scm_rev.h" -#ifdef ARCHITECTURE_x86_64 -#include "common/x64/cpu_detect.h" -#endif + #include "core/core.h" #include "core/settings.h" #include "core/telemetry_session.h" namespace Core { -#ifdef ARCHITECTURE_x86_64 -static const char* CpuVendorToStr(Common::CPUVendor vendor) { - switch (vendor) { - case Common::CPUVendor::INTEL: - return "Intel"; - case Common::CPUVendor::AMD: - return "Amd"; - case Common::CPUVendor::OTHER: - return "Other"; - } - UNREACHABLE(); -} -#endif - static u64 GenerateTelemetryId() { u64 telemetry_id{}; return telemetry_id; @@ -112,48 +94,11 @@ TelemetrySession::TelemetrySession() { } // Log application information - const bool is_git_dirty{std::strstr(Common::g_scm_desc, "dirty") != nullptr}; - AddField(Telemetry::FieldType::App, "Git_IsDirty", is_git_dirty); - AddField(Telemetry::FieldType::App, "Git_Branch", Common::g_scm_branch); - AddField(Telemetry::FieldType::App, "Git_Revision", Common::g_scm_rev); - AddField(Telemetry::FieldType::App, "BuildDate", Common::g_build_date); - AddField(Telemetry::FieldType::App, "BuildName", Common::g_build_name); - -// Log user system information -#ifdef ARCHITECTURE_x86_64 - AddField(Telemetry::FieldType::UserSystem, "CPU_Model", Common::GetCPUCaps().cpu_string); - AddField(Telemetry::FieldType::UserSystem, "CPU_BrandString", - Common::GetCPUCaps().brand_string); - AddField(Telemetry::FieldType::UserSystem, "CPU_Vendor", - CpuVendorToStr(Common::GetCPUCaps().vendor)); - AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_AES", Common::GetCPUCaps().aes); - AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_AVX", Common::GetCPUCaps().avx); - AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_AVX2", Common::GetCPUCaps().avx2); - AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_BMI1", Common::GetCPUCaps().bmi1); - AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_BMI2", Common::GetCPUCaps().bmi2); - AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_FMA", Common::GetCPUCaps().fma); - AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_FMA4", Common::GetCPUCaps().fma4); - AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_SSE", Common::GetCPUCaps().sse); - AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_SSE2", Common::GetCPUCaps().sse2); - AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_SSE3", Common::GetCPUCaps().sse3); - AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_SSSE3", - Common::GetCPUCaps().ssse3); - AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_SSE41", - Common::GetCPUCaps().sse4_1); - AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_SSE42", - Common::GetCPUCaps().sse4_2); -#else - AddField(Telemetry::FieldType::UserSystem, "CPU_Model", "Other"); -#endif -#ifdef __APPLE__ - AddField(Telemetry::FieldType::UserSystem, "OsPlatform", "Apple"); -#elif defined(_WIN32) - AddField(Telemetry::FieldType::UserSystem, "OsPlatform", "Windows"); -#elif defined(__linux__) || defined(linux) || defined(__linux) - AddField(Telemetry::FieldType::UserSystem, "OsPlatform", "Linux"); -#else - AddField(Telemetry::FieldType::UserSystem, "OsPlatform", "Unknown"); -#endif + Telemetry::AppendBuildInfo(field_collection); + + // Log user system information + Telemetry::AppendCPUInfo(field_collection); + Telemetry::AppendOSInfo(field_collection); // Log user configuration information AddField(Telemetry::FieldType::UserConfig, "Core_UseCpuJit", Settings::values.use_cpu_jit); diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index 771eb5abc..3c869d3a1 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -679,7 +679,19 @@ public: INSERT_PADDING_WORDS(0x7); - INSERT_PADDING_WORDS(0x46); + INSERT_PADDING_WORDS(0x20); + + struct { + u32 is_instanced[NumVertexArrays]; + + /// Returns whether the vertex array specified by index is supposed to be + /// accessed per instance or not. + bool IsInstancingEnabled(u32 index) const { + return is_instanced[index]; + } + } instanced_arrays; + + INSERT_PADDING_WORDS(0x6); Cull cull; @@ -928,6 +940,7 @@ ASSERT_REG_POSITION(point_coord_replace, 0x581); ASSERT_REG_POSITION(code_address, 0x582); ASSERT_REG_POSITION(draw, 0x585); ASSERT_REG_POSITION(index_array, 0x5F2); +ASSERT_REG_POSITION(instanced_arrays, 0x620); ASSERT_REG_POSITION(cull, 0x646); ASSERT_REG_POSITION(clear_buffers, 0x674); ASSERT_REG_POSITION(query, 0x6C0); diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h index b038a9d92..3ba6fe614 100644 --- a/src/video_core/engines/shader_bytecode.h +++ b/src/video_core/engines/shader_bytecode.h @@ -12,6 +12,7 @@ #include <boost/optional.hpp> +#include "common/assert.h" #include "common/bit_field.h" #include "common/common_types.h" @@ -79,6 +80,9 @@ union Attribute { // shader, and a tuple of (TessCoord.x, TessCoord.y, TessCoord.z, ~) when inside a Tess Eval // shader. TessCoordInstanceIDVertexID = 47, + // This attribute contains a tuple of (Unk, Unk, Unk, gl_FrontFacing) when inside a fragment + // shader. It is unknown what the other values contain. + FrontFacing = 63, }; union { @@ -214,6 +218,18 @@ enum class FlowCondition : u64 { Fcsm_Tr = 0x1C, // TODO(bunnei): What is this used for? }; +enum class PredicateResultMode : u64 { + None = 0x0, + NotZero = 0x3, +}; + +enum class TextureType : u64 { + Texture1D = 0, + Texture2D = 1, + Texture3D = 2, + TextureCube = 3, +}; + union Instruction { Instruction& operator=(const Instruction& instr) { value = instr.value; @@ -254,7 +270,7 @@ union Instruction { BitField<39, 1, u64> invert_a; BitField<40, 1, u64> invert_b; BitField<41, 2, LogicOperation> operation; - BitField<44, 2, u64> unk44; + BitField<44, 2, PredicateResultMode> pred_result_mode; BitField<48, 3, Pred> pred48; } lop; @@ -284,6 +300,10 @@ union Instruction { } alu; union { + BitField<48, 1, u64> negate_b; + } fmul; + + union { BitField<48, 1, u64> is_signed; } shift; @@ -421,6 +441,8 @@ union Instruction { } conversion; union { + BitField<28, 1, u64> array; + BitField<29, 2, TextureType> texture_type; BitField<31, 4, u64> component_mask; bool IsComponentEnabled(size_t component) const { @@ -429,29 +451,88 @@ union Instruction { } tex; union { - BitField<50, 3, u64> component_mask_selector; + BitField<28, 1, u64> array; + BitField<29, 2, TextureType> texture_type; + BitField<56, 2, u64> component; + } tld4; + + union { + BitField<52, 2, u64> component; + } tld4s; + + union { BitField<0, 8, Register> gpr0; BitField<28, 8, Register> gpr28; + BitField<50, 3, u64> component_mask_selector; + BitField<53, 4, u64> texture_info; + + TextureType GetTextureType() const { + // The TEXS instruction has a weird encoding for the texture type. + if (texture_info == 0) + return TextureType::Texture1D; + if (texture_info >= 1 && texture_info <= 9) + return TextureType::Texture2D; + if (texture_info >= 10 && texture_info <= 11) + return TextureType::Texture3D; + if (texture_info >= 12 && texture_info <= 13) + return TextureType::TextureCube; + + UNIMPLEMENTED(); + } + + bool IsArrayTexture() const { + // TEXS only supports Texture2D arrays. + return texture_info >= 7 && texture_info <= 9; + } bool HasTwoDestinations() const { return gpr28.Value() != Register::ZeroIndex; } bool IsComponentEnabled(size_t component) const { - static constexpr std::array<std::array<u32, 8>, 4> mask_lut{ - {{}, - {0x1, 0x2, 0x4, 0x8, 0x3}, - {0x1, 0x2, 0x4, 0x8, 0x3, 0x9, 0xa, 0xc}, - {0x7, 0xb, 0xd, 0xe, 0xf}}}; + static constexpr std::array<std::array<u32, 8>, 4> mask_lut{{ + {}, + {0x1, 0x2, 0x4, 0x8, 0x3, 0x9, 0xa, 0xc}, + {0x1, 0x2, 0x4, 0x8, 0x3, 0x9, 0xa, 0xc}, + {0x7, 0xb, 0xd, 0xe, 0xf}, + }}; size_t index{gpr0.Value() != Register::ZeroIndex ? 1U : 0U}; index |= gpr28.Value() != Register::ZeroIndex ? 2 : 0; - return ((1ull << component) & mask_lut[index][component_mask_selector]) != 0; + u32 mask = mask_lut[index][component_mask_selector]; + // A mask of 0 means this instruction uses an unimplemented mask. + ASSERT(mask != 0); + return ((1ull << component) & mask) != 0; } } texs; union { + BitField<53, 4, u64> texture_info; + + TextureType GetTextureType() const { + // The TLDS instruction has a weird encoding for the texture type. + if (texture_info >= 0 && texture_info <= 1) { + return TextureType::Texture1D; + } + if (texture_info == 2 || texture_info == 8 || texture_info == 12 || + texture_info >= 4 && texture_info <= 6) { + return TextureType::Texture2D; + } + if (texture_info == 7) { + return TextureType::Texture3D; + } + + UNIMPLEMENTED(); + } + + bool IsArrayTexture() const { + // TEXS only supports Texture2D arrays. + return texture_info == 8; + } + } tlds; + + union { BitField<20, 24, u64> target; BitField<5, 1, u64> constant_buffer; @@ -513,10 +594,14 @@ public: LD_A, LD_C, ST_A, + LDG, // Load from global memory + STG, // Store in global memory TEX, - TEXQ, // Texture Query - TEXS, // Texture Fetch with scalar/non-vec4 source/destinations - TLDS, // Texture Load with scalar/non-vec4 source/destinations + TEXQ, // Texture Query + TEXS, // Texture Fetch with scalar/non-vec4 source/destinations + TLDS, // Texture Load with scalar/non-vec4 source/destinations + TLD4, // Texture Load 4 + TLD4S, // Texture Load 4 with scalar / non - vec4 source / destinations EXIT, IPA, FFMA_IMM, // Fused Multiply and Add @@ -724,10 +809,14 @@ private: INST("1110111111011---", Id::LD_A, Type::Memory, "LD_A"), INST("1110111110010---", Id::LD_C, Type::Memory, "LD_C"), INST("1110111111110---", Id::ST_A, Type::Memory, "ST_A"), + INST("1110111011010---", Id::LDG, Type::Memory, "LDG"), + INST("1110111011011---", Id::STG, Type::Memory, "STG"), INST("110000----111---", Id::TEX, Type::Memory, "TEX"), INST("1101111101001---", Id::TEXQ, Type::Memory, "TEXQ"), INST("1101100---------", Id::TEXS, Type::Memory, "TEXS"), INST("1101101---------", Id::TLDS, Type::Memory, "TLDS"), + INST("110010----111---", Id::TLD4, Type::Memory, "TLD4"), + INST("1101111100------", Id::TLD4S, Type::Memory, "TLD4S"), INST("111000110000----", Id::EXIT, Type::Trivial, "EXIT"), INST("11100000--------", Id::IPA, Type::Trivial, "IPA"), INST("0011001-1-------", Id::FFMA_IMM, Type::Ffma, "FFMA_IMM"), diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index 5a593c1f7..9758adcfd 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp @@ -55,6 +55,7 @@ u32 RenderTargetBytesPerPixel(RenderTargetFormat format) { case RenderTargetFormat::RGBA8_UNORM: case RenderTargetFormat::RGBA8_SNORM: case RenderTargetFormat::RGBA8_SRGB: + case RenderTargetFormat::RGBA8_UINT: case RenderTargetFormat::RGB10_A2_UNORM: case RenderTargetFormat::BGRA8_UNORM: case RenderTargetFormat::RG16_UNORM: diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h index 97dcccb92..2697e1c27 100644 --- a/src/video_core/gpu.h +++ b/src/video_core/gpu.h @@ -30,6 +30,7 @@ enum class RenderTargetFormat : u32 { RGBA8_UNORM = 0xD5, RGBA8_SRGB = 0xD6, RGBA8_SNORM = 0xD7, + RGBA8_UINT = 0xD9, RG16_UNORM = 0xDA, RG16_SNORM = 0xDB, RG16_SINT = 0xDC, diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 93eadde7a..fe1f55e85 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -98,7 +98,8 @@ RasterizerOpenGL::~RasterizerOpenGL() {} std::pair<u8*, GLintptr> RasterizerOpenGL::SetupVertexArrays(u8* array_ptr, GLintptr buffer_offset) { MICROPROFILE_SCOPE(OpenGL_VAO); - const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; + const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D(); + const auto& regs = gpu.regs; state.draw.vertex_array = hw_vao.handle; state.draw.vertex_buffer = stream_buffer.GetHandle(); @@ -110,9 +111,13 @@ std::pair<u8*, GLintptr> RasterizerOpenGL::SetupVertexArrays(u8* array_ptr, if (!vertex_array.IsEnabled()) continue; - const Tegra::GPUVAddr start = vertex_array.StartAddress(); + Tegra::GPUVAddr start = vertex_array.StartAddress(); const Tegra::GPUVAddr end = regs.vertex_array_limit[index].LimitAddress(); + if (regs.instanced_arrays.IsInstancingEnabled(index) && vertex_array.divisor != 0) { + start += vertex_array.stride * (gpu.state.current_instance / vertex_array.divisor); + } + ASSERT(end > start); u64 size = end - start + 1; @@ -124,7 +129,15 @@ std::pair<u8*, GLintptr> RasterizerOpenGL::SetupVertexArrays(u8* array_ptr, glBindVertexBuffer(index, stream_buffer.GetHandle(), vertex_buffer_offset, vertex_array.stride); - ASSERT_MSG(vertex_array.divisor == 0, "Instanced vertex arrays are not supported"); + if (regs.instanced_arrays.IsInstancingEnabled(index) && vertex_array.divisor != 0) { + // Tell OpenGL that this is an instanced vertex buffer to prevent accessing different + // indexes on each vertex. We do the instance indexing manually by incrementing the + // start address of the vertex buffer. + glVertexBindingDivisor(index, 1); + } else { + // Disable the vertex buffer instancing. + glVertexBindingDivisor(index, 0); + } } // Use the vertex array as-is, assumes that the data is formatted correctly for OpenGL. diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index 38aa067b6..fb7476fb8 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -94,6 +94,7 @@ struct FormatTuple { static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_format_tuples = {{ {GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, ComponentType::UNorm, false}, // ABGR8U {GL_RGBA8, GL_RGBA, GL_BYTE, ComponentType::SNorm, false}, // ABGR8S + {GL_RGBA8UI, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE, ComponentType::UInt, false}, // ABGR8UI {GL_RGB, GL_RGB, GL_UNSIGNED_SHORT_5_6_5_REV, ComponentType::UNorm, false}, // B5G6R5U {GL_RGB10_A2, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, ComponentType::UNorm, false}, // A2B10G10R10U @@ -245,6 +246,7 @@ static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, Tegra::GPU // clang-format off MortonCopy<true, PixelFormat::ABGR8U>, MortonCopy<true, PixelFormat::ABGR8S>, + MortonCopy<true, PixelFormat::ABGR8UI>, MortonCopy<true, PixelFormat::B5G6R5U>, MortonCopy<true, PixelFormat::A2B10G10R10U>, MortonCopy<true, PixelFormat::A1B5G5R5U>, @@ -299,6 +301,7 @@ static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, Tegra::GPU // clang-format off MortonCopy<false, PixelFormat::ABGR8U>, MortonCopy<false, PixelFormat::ABGR8S>, + MortonCopy<false, PixelFormat::ABGR8UI>, MortonCopy<false, PixelFormat::B5G6R5U>, MortonCopy<false, PixelFormat::A2B10G10R10U>, MortonCopy<false, PixelFormat::A1B5G5R5U>, diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h index beec01746..fc8b44219 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h @@ -25,59 +25,60 @@ struct SurfaceParams { enum class PixelFormat { ABGR8U = 0, ABGR8S = 1, - B5G6R5U = 2, - A2B10G10R10U = 3, - A1B5G5R5U = 4, - R8U = 5, - R8UI = 6, - RGBA16F = 7, - RGBA16U = 8, - RGBA16UI = 9, - R11FG11FB10F = 10, - RGBA32UI = 11, - DXT1 = 12, - DXT23 = 13, - DXT45 = 14, - DXN1 = 15, // This is also known as BC4 - DXN2UNORM = 16, - DXN2SNORM = 17, - BC7U = 18, - ASTC_2D_4X4 = 19, - G8R8U = 20, - G8R8S = 21, - BGRA8 = 22, - RGBA32F = 23, - RG32F = 24, - R32F = 25, - R16F = 26, - R16U = 27, - R16S = 28, - R16UI = 29, - R16I = 30, - RG16 = 31, - RG16F = 32, - RG16UI = 33, - RG16I = 34, - RG16S = 35, - RGB32F = 36, - SRGBA8 = 37, - RG8U = 38, - RG8S = 39, - RG32UI = 40, - R32UI = 41, + ABGR8UI = 2, + B5G6R5U = 3, + A2B10G10R10U = 4, + A1B5G5R5U = 5, + R8U = 6, + R8UI = 7, + RGBA16F = 8, + RGBA16U = 9, + RGBA16UI = 10, + R11FG11FB10F = 11, + RGBA32UI = 12, + DXT1 = 13, + DXT23 = 14, + DXT45 = 15, + DXN1 = 16, // This is also known as BC4 + DXN2UNORM = 17, + DXN2SNORM = 18, + BC7U = 19, + ASTC_2D_4X4 = 20, + G8R8U = 21, + G8R8S = 22, + BGRA8 = 23, + RGBA32F = 24, + RG32F = 25, + R32F = 26, + R16F = 27, + R16U = 28, + R16S = 29, + R16UI = 30, + R16I = 31, + RG16 = 32, + RG16F = 33, + RG16UI = 34, + RG16I = 35, + RG16S = 36, + RGB32F = 37, + SRGBA8 = 38, + RG8U = 39, + RG8S = 40, + RG32UI = 41, + R32UI = 42, MaxColorFormat, // Depth formats - Z32F = 42, - Z16 = 43, + Z32F = 43, + Z16 = 44, MaxDepthFormat, // DepthStencil formats - Z24S8 = 44, - S8Z24 = 45, - Z32FS8 = 46, + Z24S8 = 45, + S8Z24 = 46, + Z32FS8 = 47, MaxDepthStencilFormat, @@ -117,6 +118,7 @@ struct SurfaceParams { constexpr std::array<u32, MaxPixelFormat> compression_factor_table = {{ 1, // ABGR8U 1, // ABGR8S + 1, // ABGR8UI 1, // B5G6R5U 1, // A2B10G10R10U 1, // A1B5G5R5U @@ -175,6 +177,7 @@ struct SurfaceParams { constexpr std::array<u32, MaxPixelFormat> bpp_table = {{ 32, // ABGR8U 32, // ABGR8S + 32, // ABGR8UI 16, // B5G6R5U 32, // A2B10G10R10U 16, // A1B5G5R5U @@ -257,6 +260,8 @@ struct SurfaceParams { return PixelFormat::ABGR8U; case Tegra::RenderTargetFormat::RGBA8_SNORM: return PixelFormat::ABGR8S; + case Tegra::RenderTargetFormat::RGBA8_UINT: + return PixelFormat::ABGR8UI; case Tegra::RenderTargetFormat::BGRA8_UNORM: return PixelFormat::BGRA8; case Tegra::RenderTargetFormat::RGB10_A2_UNORM: @@ -327,6 +332,8 @@ struct SurfaceParams { return PixelFormat::ABGR8U; case Tegra::Texture::ComponentType::SNORM: return PixelFormat::ABGR8S; + case Tegra::Texture::ComponentType::UINT: + return PixelFormat::ABGR8UI; } LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type)); @@ -551,6 +558,7 @@ struct SurfaceParams { case Tegra::RenderTargetFormat::R16_UINT: case Tegra::RenderTargetFormat::RG32_UINT: case Tegra::RenderTargetFormat::R32_UINT: + case Tegra::RenderTargetFormat::RGBA8_UINT: return ComponentType::UInt; case Tegra::RenderTargetFormat::RG16_SINT: case Tegra::RenderTargetFormat::R16_SINT: diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index 6cc223328..214a5fa9a 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -440,13 +440,12 @@ public: } declarations.AddNewLine(); - // Append the sampler2D array for the used textures. - size_t num_samplers = GetSamplers().size(); - if (num_samplers > 0) { - declarations.AddLine("uniform sampler2D " + SamplerEntry::GetArrayName(stage) + '[' + - std::to_string(num_samplers) + "];"); - declarations.AddNewLine(); + const auto& samplers = GetSamplers(); + for (const auto& sampler : samplers) { + declarations.AddLine("uniform " + sampler.GetTypeString() + ' ' + sampler.GetName() + + ';'); } + declarations.AddNewLine(); } /// Returns a list of constant buffer declarations @@ -458,13 +457,14 @@ public: } /// Returns a list of samplers used in the shader - std::vector<SamplerEntry> GetSamplers() const { + const std::vector<SamplerEntry>& GetSamplers() const { return used_samplers; } /// Returns the GLSL sampler used for the input shader sampler, and creates a new one if /// necessary. - std::string AccessSampler(const Sampler& sampler) { + std::string AccessSampler(const Sampler& sampler, Tegra::Shader::TextureType type, + bool is_array) { size_t offset = static_cast<size_t>(sampler.index.Value()); // If this sampler has already been used, return the existing mapping. @@ -473,12 +473,13 @@ public: [&](const SamplerEntry& entry) { return entry.GetOffset() == offset; }); if (itr != used_samplers.end()) { + ASSERT(itr->GetType() == type && itr->IsArray() == is_array); return itr->GetName(); } // Otherwise create a new mapping for this sampler size_t next_index = used_samplers.size(); - SamplerEntry entry{stage, offset, next_index}; + SamplerEntry entry{stage, offset, next_index, type, is_array}; used_samplers.emplace_back(entry); return entry.GetName(); } @@ -543,6 +544,10 @@ private: // shader. ASSERT(stage == Maxwell3D::Regs::ShaderStage::Vertex); return "vec4(0, 0, uintBitsToFloat(instance_id.x), uintBitsToFloat(gl_VertexID))"; + case Attribute::Index::FrontFacing: + // TODO(Subv): Find out what the values are for the other elements. + ASSERT(stage == Maxwell3D::Regs::ShaderStage::Fragment); + return "vec4(0, 0, 0, uintBitsToFloat(gl_FrontFacing ? 1 : 0))"; default: const u32 index{static_cast<u32>(attribute) - static_cast<u32>(Attribute::Index::Attribute_0)}; @@ -652,8 +657,8 @@ private: } /// Generates code representing a texture sampler. - std::string GetSampler(const Sampler& sampler) { - return regs.AccessSampler(sampler); + std::string GetSampler(const Sampler& sampler, Tegra::Shader::TextureType type, bool is_array) { + return regs.AccessSampler(sampler, type, is_array); } /** @@ -761,6 +766,30 @@ private: return op->second; } + /** + * Transforms the input string GLSL operand into one that applies the abs() function and negates + * the output if necessary. When both abs and neg are true, the negation will be applied after + * taking the absolute value. + * @param operand The input operand to take the abs() of, negate, or both. + * @param abs Whether to apply the abs() function to the input operand. + * @param neg Whether to negate the input operand. + * @returns String corresponding to the operand after being transformed by the abs() and + * negation operations. + */ + static std::string GetOperandAbsNeg(const std::string& operand, bool abs, bool neg) { + std::string result = operand; + + if (abs) { + result = "abs(" + result + ')'; + } + + if (neg) { + result = "-(" + result + ')'; + } + + return result; + } + /* * Returns whether the instruction at the specified offset is a 'sched' instruction. * Sched instructions always appear before a sequence of 3 instructions. @@ -774,28 +803,51 @@ private: } void WriteLogicOperation(Register dest, LogicOperation logic_op, const std::string& op_a, - const std::string& op_b) { + const std::string& op_b, + Tegra::Shader::PredicateResultMode predicate_mode, + Tegra::Shader::Pred predicate) { + std::string result{}; switch (logic_op) { case LogicOperation::And: { - regs.SetRegisterToInteger(dest, true, 0, '(' + op_a + " & " + op_b + ')', 1, 1); + result = '(' + op_a + " & " + op_b + ')'; break; } case LogicOperation::Or: { - regs.SetRegisterToInteger(dest, true, 0, '(' + op_a + " | " + op_b + ')', 1, 1); + result = '(' + op_a + " | " + op_b + ')'; break; } case LogicOperation::Xor: { - regs.SetRegisterToInteger(dest, true, 0, '(' + op_a + " ^ " + op_b + ')', 1, 1); + result = '(' + op_a + " ^ " + op_b + ')'; break; } case LogicOperation::PassB: { - regs.SetRegisterToInteger(dest, true, 0, op_b, 1, 1); + result = op_b; break; } default: LOG_CRITICAL(HW_GPU, "Unimplemented logic operation: {}", static_cast<u32>(logic_op)); UNREACHABLE(); } + + if (dest != Tegra::Shader::Register::ZeroIndex) { + regs.SetRegisterToInteger(dest, true, 0, result, 1, 1); + } + + using Tegra::Shader::PredicateResultMode; + // Write the predicate value depending on the predicate mode. + switch (predicate_mode) { + case PredicateResultMode::None: + // Do nothing. + return; + case PredicateResultMode::NotZero: + // Set the predicate to true if the result is not zero. + SetPredicate(static_cast<u64>(predicate), '(' + result + ") != 0"); + break; + default: + LOG_CRITICAL(HW_GPU, "Unimplemented predicate result mode: {}", + static_cast<u32>(predicate_mode)); + UNREACHABLE(); + } } void WriteTexsInstruction(const Instruction& instr, const std::string& coord, @@ -806,29 +858,56 @@ private: ++shader.scope; shader.AddLine(coord); - // TEXS has two destination registers. RG goes into gpr0+0 and gpr0+1, and BA - // goes into gpr28+0 and gpr28+1 - size_t texs_offset{}; - - size_t src_elem{}; - for (const auto& dest : {instr.gpr0.Value(), instr.gpr28.Value()}) { - size_t dest_elem{}; - for (unsigned elem = 0; elem < 2; ++elem) { - if (!instr.texs.IsComponentEnabled(src_elem++)) { - // Skip disabled components - continue; - } - regs.SetRegisterToFloat(dest, elem + texs_offset, texture, 1, 4, false, - dest_elem++); + // TEXS has two destination registers and a swizzle. The first two elements in the swizzle + // go into gpr0+0 and gpr0+1, and the rest goes into gpr28+0 and gpr28+1 + + size_t written_components = 0; + for (u32 component = 0; component < 4; ++component) { + if (!instr.texs.IsComponentEnabled(component)) { + continue; } - if (!instr.texs.HasTwoDestinations()) { - // Skip the second destination - break; + if (written_components < 2) { + // Write the first two swizzle components to gpr0 and gpr0+1 + regs.SetRegisterToFloat(instr.gpr0, component, texture, 1, 4, false, + written_components % 2); + } else { + ASSERT(instr.texs.HasTwoDestinations()); + // Write the rest of the swizzle components to gpr28 and gpr28+1 + regs.SetRegisterToFloat(instr.gpr28, component, texture, 1, 4, false, + written_components % 2); } - texs_offset += 2; + ++written_components; } + + --shader.scope; + shader.AddLine('}'); + } + + /* + * Emits code to push the input target address to the SSY address stack, incrementing the stack + * top. + */ + void EmitPushToSSYStack(u32 target) { + shader.AddLine('{'); + ++shader.scope; + shader.AddLine("ssy_stack[ssy_stack_top] = " + std::to_string(target) + "u;"); + shader.AddLine("ssy_stack_top++;"); + --shader.scope; + shader.AddLine('}'); + } + + /* + * Emits code to pop an address from the SSY address stack, setting the jump address to the + * popped address and decrementing the stack top. + */ + void EmitPopFromSSYStack() { + shader.AddLine('{'); + ++shader.scope; + shader.AddLine("ssy_stack_top--;"); + shader.AddLine("jmp_to = ssy_stack[ssy_stack_top];"); + shader.AddLine("break;"); --shader.scope; shader.AddLine('}'); } @@ -902,13 +981,6 @@ private: switch (opcode->GetType()) { case OpCode::Type::Arithmetic: { std::string op_a = regs.GetRegisterAsFloat(instr.gpr8); - if (instr.alu.abs_a) { - op_a = "abs(" + op_a + ')'; - } - - if (instr.alu.negate_a) { - op_a = "-(" + op_a + ')'; - } std::string op_b; @@ -923,17 +995,10 @@ private: } } - if (instr.alu.abs_b) { - op_b = "abs(" + op_b + ')'; - } - - if (instr.alu.negate_b) { - op_b = "-(" + op_b + ')'; - } - switch (opcode->GetId()) { case OpCode::Id::MOV_C: case OpCode::Id::MOV_R: { + // MOV does not have neither 'abs' nor 'neg' bits. regs.SetRegisterToFloat(instr.gpr0, 0, op_b, 1, 1); break; } @@ -941,6 +1006,8 @@ private: case OpCode::Id::FMUL_C: case OpCode::Id::FMUL_R: case OpCode::Id::FMUL_IMM: { + // FMUL does not have 'abs' bits and only the second operand has a 'neg' bit. + op_b = GetOperandAbsNeg(op_b, false, instr.fmul.negate_b); regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " * " + op_b, 1, 1, instr.alu.saturate_d); break; @@ -948,11 +1015,14 @@ private: case OpCode::Id::FADD_C: case OpCode::Id::FADD_R: case OpCode::Id::FADD_IMM: { + op_a = GetOperandAbsNeg(op_a, instr.alu.abs_a, instr.alu.negate_a); + op_b = GetOperandAbsNeg(op_b, instr.alu.abs_b, instr.alu.negate_b); regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " + " + op_b, 1, 1, instr.alu.saturate_d); break; } case OpCode::Id::MUFU: { + op_a = GetOperandAbsNeg(op_a, instr.alu.abs_a, instr.alu.negate_a); switch (instr.sub_op) { case SubOp::Cos: regs.SetRegisterToFloat(instr.gpr0, 0, "cos(" + op_a + ')', 1, 1, @@ -992,6 +1062,9 @@ private: case OpCode::Id::FMNMX_C: case OpCode::Id::FMNMX_R: case OpCode::Id::FMNMX_IMM: { + op_a = GetOperandAbsNeg(op_a, instr.alu.abs_a, instr.alu.negate_a); + op_b = GetOperandAbsNeg(op_b, instr.alu.abs_b, instr.alu.negate_b); + std::string condition = GetPredicateCondition(instr.alu.fmnmx.pred, instr.alu.fmnmx.negate_pred != 0); std::string parameters = op_a + ',' + op_b; @@ -1005,7 +1078,7 @@ private: case OpCode::Id::RRO_R: case OpCode::Id::RRO_IMM: { // Currently RRO is only implemented as a register move. - // Usage of `abs_b` and `negate_b` here should also be correct. + op_b = GetOperandAbsNeg(op_b, instr.alu.abs_b, instr.alu.negate_b); regs.SetRegisterToFloat(instr.gpr0, 0, op_b, 1, 1); LOG_WARNING(HW_GPU, "RRO instruction is incomplete"); break; @@ -1142,7 +1215,9 @@ private: if (instr.alu.lop32i.invert_b) op_b = "~(" + op_b + ')'; - WriteLogicOperation(instr.gpr0, instr.alu.lop32i.operation, op_a, op_b); + WriteLogicOperation(instr.gpr0, instr.alu.lop32i.operation, op_a, op_b, + Tegra::Shader::PredicateResultMode::None, + Tegra::Shader::Pred::UnusedIndex); break; } default: { @@ -1208,16 +1283,14 @@ private: case OpCode::Id::LOP_C: case OpCode::Id::LOP_R: case OpCode::Id::LOP_IMM: { - ASSERT_MSG(!instr.alu.lop.unk44, "Unimplemented"); - ASSERT_MSG(instr.alu.lop.pred48 == Pred::UnusedIndex, "Unimplemented"); - if (instr.alu.lop.invert_a) op_a = "~(" + op_a + ')'; if (instr.alu.lop.invert_b) op_b = "~(" + op_b + ')'; - WriteLogicOperation(instr.gpr0, instr.alu.lop.operation, op_a, op_b); + WriteLogicOperation(instr.gpr0, instr.alu.lop.operation, op_a, op_b, + instr.alu.lop.pred_result_mode, instr.alu.lop.pred48); break; } case OpCode::Id::IMNMX_C: @@ -1282,8 +1355,6 @@ private: break; } case OpCode::Type::Conversion: { - ASSERT_MSG(!instr.conversion.negate_a, "Unimplemented"); - switch (opcode->GetId()) { case OpCode::Id::I2I_R: { ASSERT_MSG(!instr.conversion.selector, "Unimplemented"); @@ -1480,10 +1551,31 @@ private: break; } case OpCode::Id::TEX: { - const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8); - const std::string op_b = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); - const std::string sampler = GetSampler(instr.sampler); - const std::string coord = "vec2 coords = vec2(" + op_a + ", " + op_b + ");"; + ASSERT_MSG(instr.tex.array == 0, "TEX arrays unimplemented"); + std::string coord{}; + + switch (instr.tex.texture_type) { + case Tegra::Shader::TextureType::Texture2D: { + std::string x = regs.GetRegisterAsFloat(instr.gpr8); + std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + coord = "vec2 coords = vec2(" + x + ", " + y + ");"; + break; + } + case Tegra::Shader::TextureType::Texture3D: { + std::string x = regs.GetRegisterAsFloat(instr.gpr8); + std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + std::string z = regs.GetRegisterAsFloat(instr.gpr20); + coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");"; + break; + } + default: + LOG_CRITICAL(HW_GPU, "Unhandled texture type {}", + static_cast<u32>(instr.tex.texture_type.Value())); + UNREACHABLE(); + } + + const std::string sampler = + GetSampler(instr.sampler, instr.tex.texture_type, instr.tex.array); // Add an extra scope and declare the texture coords inside to prevent // overwriting them in case they are used as outputs of the texs instruction. shader.AddLine("{"); @@ -1505,24 +1597,122 @@ private: break; } case OpCode::Id::TEXS: { - const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8); - const std::string op_b = regs.GetRegisterAsFloat(instr.gpr20); - const std::string sampler = GetSampler(instr.sampler); - const std::string coord = "vec2 coords = vec2(" + op_a + ", " + op_b + ");"; + std::string coord{}; + + switch (instr.texs.GetTextureType()) { + case Tegra::Shader::TextureType::Texture2D: { + if (instr.texs.IsArrayTexture()) { + std::string index = regs.GetRegisterAsInteger(instr.gpr8); + std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + std::string y = regs.GetRegisterAsFloat(instr.gpr20); + coord = "vec3 coords = vec3(" + x + ", " + y + ", " + index + ");"; + } else { + std::string x = regs.GetRegisterAsFloat(instr.gpr8); + std::string y = regs.GetRegisterAsFloat(instr.gpr20); + coord = "vec2 coords = vec2(" + x + ", " + y + ");"; + } + break; + } + case Tegra::Shader::TextureType::TextureCube: { + std::string x = regs.GetRegisterAsFloat(instr.gpr8); + std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + std::string z = regs.GetRegisterAsFloat(instr.gpr20); + coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");"; + break; + } + default: + LOG_CRITICAL(HW_GPU, "Unhandled texture type {}", + static_cast<u32>(instr.texs.GetTextureType())); + UNREACHABLE(); + } + const std::string sampler = GetSampler(instr.sampler, instr.texs.GetTextureType(), + instr.texs.IsArrayTexture()); const std::string texture = "texture(" + sampler + ", coords)"; WriteTexsInstruction(instr, coord, texture); break; } case OpCode::Id::TLDS: { - const std::string op_a = regs.GetRegisterAsInteger(instr.gpr8); - const std::string op_b = regs.GetRegisterAsInteger(instr.gpr20); - const std::string sampler = GetSampler(instr.sampler); - const std::string coord = "ivec2 coords = ivec2(" + op_a + ", " + op_b + ");"; + ASSERT(instr.tlds.GetTextureType() == Tegra::Shader::TextureType::Texture2D); + ASSERT(instr.tlds.IsArrayTexture() == false); + std::string coord{}; + + switch (instr.tlds.GetTextureType()) { + case Tegra::Shader::TextureType::Texture2D: { + if (instr.tlds.IsArrayTexture()) { + LOG_CRITICAL(HW_GPU, "Unhandled 2d array texture"); + UNREACHABLE(); + } else { + std::string x = regs.GetRegisterAsInteger(instr.gpr8); + std::string y = regs.GetRegisterAsInteger(instr.gpr20); + coord = "ivec2 coords = ivec2(" + x + ", " + y + ");"; + } + break; + } + default: + LOG_CRITICAL(HW_GPU, "Unhandled texture type {}", + static_cast<u32>(instr.tlds.GetTextureType())); + UNREACHABLE(); + } + const std::string sampler = GetSampler(instr.sampler, instr.tlds.GetTextureType(), + instr.tlds.IsArrayTexture()); const std::string texture = "texelFetch(" + sampler + ", coords, 0)"; WriteTexsInstruction(instr, coord, texture); break; } + case OpCode::Id::TLD4: { + ASSERT(instr.tld4.texture_type == Tegra::Shader::TextureType::Texture2D); + ASSERT(instr.tld4.array == 0); + std::string coord{}; + + switch (instr.tld4.texture_type) { + case Tegra::Shader::TextureType::Texture2D: { + std::string x = regs.GetRegisterAsFloat(instr.gpr8); + std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + coord = "vec2 coords = vec2(" + x + ", " + y + ");"; + break; + } + default: + LOG_CRITICAL(HW_GPU, "Unhandled texture type {}", + static_cast<u32>(instr.tld4.texture_type.Value())); + UNREACHABLE(); + } + + const std::string sampler = + GetSampler(instr.sampler, instr.tld4.texture_type, instr.tld4.array); + // Add an extra scope and declare the texture coords inside to prevent + // overwriting them in case they are used as outputs of the texs instruction. + shader.AddLine("{"); + ++shader.scope; + shader.AddLine(coord); + const std::string texture = "textureGather(" + sampler + ", coords, " + + std::to_string(instr.tld4.component) + ')'; + + size_t dest_elem{}; + for (size_t elem = 0; elem < 4; ++elem) { + if (!instr.tex.IsComponentEnabled(elem)) { + // Skip disabled components + continue; + } + regs.SetRegisterToFloat(instr.gpr0, elem, texture, 1, 4, false, dest_elem); + ++dest_elem; + } + --shader.scope; + shader.AddLine("}"); + break; + } + case OpCode::Id::TLD4S: { + const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8); + const std::string op_b = regs.GetRegisterAsFloat(instr.gpr20); + // TODO(Subv): Figure out how the sampler type is encoded in the TLD4S instruction. + const std::string sampler = + GetSampler(instr.sampler, Tegra::Shader::TextureType::Texture2D, false); + const std::string coord = "vec2 coords = vec2(" + op_a + ", " + op_b + ");"; + const std::string texture = "textureGather(" + sampler + ", coords, " + + std::to_string(instr.tld4s.component) + ')'; + WriteTexsInstruction(instr, coord, texture); + break; + } default: { LOG_CRITICAL(HW_GPU, "Unhandled memory instruction: {}", opcode->GetName()); UNREACHABLE(); @@ -1882,13 +2072,13 @@ private: ASSERT_MSG(instr.bra.constant_buffer == 0, "Constant buffer SSY is not supported"); u32 target = offset + instr.bra.GetBranchTarget(); - shader.AddLine("ssy_target = " + std::to_string(target) + "u;"); + EmitPushToSSYStack(target); break; } case OpCode::Id::SYNC: { // The SYNC opcode jumps to the address previously set by the SSY opcode ASSERT(instr.flow.cond == Tegra::Shader::FlowCondition::Always); - shader.AddLine("{ jmp_to = ssy_target; break; }"); + EmitPopFromSSYStack(); break; } case OpCode::Id::DEPBAR: { @@ -1959,7 +2149,13 @@ private: } else { labels.insert(subroutine.begin); shader.AddLine("uint jmp_to = " + std::to_string(subroutine.begin) + "u;"); - shader.AddLine("uint ssy_target = 0u;"); + + // TODO(Subv): Figure out the actual depth of the SSY stack, for now it seems + // unlikely that shaders will use 20 nested SSYs. + constexpr u32 SSY_STACK_SIZE = 20; + shader.AddLine("uint ssy_stack[" + std::to_string(SSY_STACK_SIZE) + "];"); + shader.AddLine("uint ssy_stack_top = 0u;"); + shader.AddLine("while (true) {"); ++shader.scope; diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h index 4729ce0fc..db48da645 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.h +++ b/src/video_core/renderer_opengl/gl_shader_gen.h @@ -11,6 +11,7 @@ #include <vector> #include "common/common_types.h" #include "common/hash.h" +#include "video_core/engines/shader_bytecode.h" namespace GLShader { @@ -72,8 +73,9 @@ class SamplerEntry { using Maxwell = Tegra::Engines::Maxwell3D::Regs; public: - SamplerEntry(Maxwell::ShaderStage stage, size_t offset, size_t index) - : offset(offset), stage(stage), sampler_index(index) {} + SamplerEntry(Maxwell::ShaderStage stage, size_t offset, size_t index, + Tegra::Shader::TextureType type, bool is_array) + : offset(offset), stage(stage), sampler_index(index), type(type), is_array(is_array) {} size_t GetOffset() const { return offset; @@ -88,8 +90,41 @@ public: } std::string GetName() const { - return std::string(TextureSamplerNames[static_cast<size_t>(stage)]) + '[' + - std::to_string(sampler_index) + ']'; + return std::string(TextureSamplerNames[static_cast<size_t>(stage)]) + '_' + + std::to_string(sampler_index); + } + + std::string GetTypeString() const { + using Tegra::Shader::TextureType; + std::string glsl_type; + + switch (type) { + case TextureType::Texture1D: + glsl_type = "sampler1D"; + break; + case TextureType::Texture2D: + glsl_type = "sampler2D"; + break; + case TextureType::Texture3D: + glsl_type = "sampler3D"; + break; + case TextureType::TextureCube: + glsl_type = "samplerCube"; + break; + default: + UNIMPLEMENTED(); + } + if (is_array) + glsl_type += "Array"; + return glsl_type; + } + + Tegra::Shader::TextureType GetType() const { + return type; + } + + bool IsArray() const { + return is_array; } static std::string GetArrayName(Maxwell::ShaderStage stage) { @@ -100,11 +135,14 @@ private: static constexpr std::array<const char*, Maxwell::MaxShaderStage> TextureSamplerNames = { "tex_vs", "tex_tessc", "tex_tesse", "tex_gs", "tex_fs", }; + /// Offset in TSC memory from which to read the sampler object, as specified by the sampling /// instruction. size_t offset; - Maxwell::ShaderStage stage; ///< Shader stage where this sampler was used. - size_t sampler_index; ///< Value used to index into the generated GLSL sampler array. + Maxwell::ShaderStage stage; ///< Shader stage where this sampler was used. + size_t sampler_index; ///< Value used to index into the generated GLSL sampler array. + Tegra::Shader::TextureType type; ///< The type used to sample this texture (Texture2D, etc) + bool is_array; ///< Whether the texture is being sampled as an array texture or not. }; struct ShaderEntries { diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h index 8f719fdd8..5d91a0c2f 100644 --- a/src/video_core/renderer_opengl/maxwell_to_gl.h +++ b/src/video_core/renderer_opengl/maxwell_to_gl.h @@ -147,6 +147,8 @@ inline GLenum WrapMode(Tegra::Texture::WrapMode wrap_mode) { // GL_CLAMP_TO_BORDER to get the border color of the texture, and then sample the edge to // manually mix them. However the shader part of this is not yet implemented. return GL_CLAMP_TO_BORDER; + case Tegra::Texture::WrapMode::MirrorOnceClampToEdge: + return GL_MIRROR_CLAMP_TO_EDGE; } LOG_CRITICAL(Render_OpenGL, "Unimplemented texture wrap mode={}", static_cast<u32>(wrap_mode)); UNREACHABLE(); diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index f867118d9..bc4b93033 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -438,7 +438,7 @@ void GameListWorker::AddInstalledTitlesToGameList() { std::vector<u8> icon; std::string name; - u64 program_id; + u64 program_id = 0; loader->ReadProgramId(program_id); const auto& control = @@ -509,7 +509,7 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign std::vector<u8> icon; const auto res1 = loader->ReadIcon(icon); - u64 program_id; + u64 program_id = 0; const auto res2 = loader->ReadProgramId(program_id); std::string name = " "; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 2df65023a..11d2331df 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -377,6 +377,8 @@ bool GMainWindow::SupportsRequiredGLExtensions() { unsupported_ext.append("ARB_vertex_attrib_binding"); if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev) unsupported_ext.append("ARB_vertex_type_10f_11f_11f_rev"); + if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge) + unsupported_ext.append("ARB_texture_mirror_clamp_to_edge"); // Extensions required to support some texture formats. if (!GLAD_GL_EXT_texture_compression_s3tc) diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp index e2945b6cf..351dd9225 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp @@ -89,6 +89,8 @@ bool EmuWindow_SDL2::SupportsRequiredGLExtensions() { unsupported_ext.push_back("ARB_vertex_attrib_binding"); if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev) unsupported_ext.push_back("ARB_vertex_type_10f_11f_11f_rev"); + if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge) + unsupported_ext.push_back("ARB_texture_mirror_clamp_to_edge"); // Extensions required to support some texture formats. if (!GLAD_GL_EXT_texture_compression_s3tc) |